LogoNEXTDEVKIT Docs

Internationalization

Learn how to implement multi-language support with next-intl in NEXTDEVKIT

NextDevKit uses next-intl to build an internationalization system that provides automatic routing, type safety, and seamless multi-language support.

Internationalization (i18n) is an essential feature for modern global applications. NextDevKit comes with a modern i18n system based on next-intl, making it easy to implement multi-language support.

🤔 What is Next-intl?

next-intl is an internationalization library specifically designed for Next.js:

  • 🎯 Next.js Optimized: Perfect integration with App Router and Pages Router
  • 🔒 Type Safe: TypeScript support with compile-time translation key checking
  • 🌍 Automatic Routing: Language-based URL routing management
  • 🍪 Smart Detection: Browser language + cookie preference settings
  • 🚀 High Performance: Server-side rendering + message merging optimization

🏗️ I18n Architecture

Project Structure

messages.ts
navigation.ts
routing.ts
request.ts
locale-cookie.ts
update-locale.ts
layout.tsx
page.tsx
index.ts
middleware.ts
en.json
zh.json

Core Components:

  • src/i18n/: Core i18n configuration and utilities
  • messages/: Translation files for all languages
  • app/[locale]/: Language-based routing structure
  • middleware.ts: Language detection and redirect middleware

⚙️ Configuration and Initialization

App Configuration

Manage i18n configuration in src/config/index.ts:

src/config/index.ts
export const appConfig = {
  i18n: {
    enabled: true,                    // Enable/disable i18n
    defaultLocale: "en",              // Default language
    locales: {                        // Supported languages
      en: { name: "English" },
      zh: { name: "简体中文" },
    },
    localeCookieName: "NEXT_LOCALE",  // Cookie name
  },
  // ... other config
} as const;

Configuration Details:

  • enabled: Global switch to quickly disable i18n
  • defaultLocale: Default language for fallback and first visit
  • locales: Language mapping, key is language code, value is display name
  • localeCookieName: Cookie name for storing user language preference

Routing Configuration

src/i18n/routing.ts defines next-intl routing behavior:

src/i18n/routing.ts
import { appConfig } from "@/config";
import { defineRouting } from "next-intl/routing";

export const routing = defineRouting({
  locales: Object.keys(appConfig.i18n.locales),    // ['en', 'zh']
  defaultLocale: appConfig.i18n.defaultLocale,     // 'en'
  localeCookie: {
    name: appConfig.i18n.localeCookieName,          // 'NEXT_LOCALE'
  },
  localeDetection: appConfig.i18n.enabled,         // Auto-detect language
  localePrefix: appConfig.i18n.enabled ? "as-needed" : "never",
});

Routing Strategies:

  • as-needed: Only non-default languages show prefix (/zh/about, default language /about)
  • never: No prefix for all languages
  • always: All languages show prefix (/en/about, /zh/about)

📁 Translation Message Organization

Message File Structure

Use nested structure to organize translation messages:

English Message Example

messages/en.json
{
  "app": {
    "name": "NEXTDEVKIT",
    "metadata": {
      "title": "NEXTDEVKIT - Next.js SaaS Starter Kit",
      "description": "Build production-ready SaaS apps faster..."
    }
  },
  "menu": {
    "application": {
      "dashboard": {
        "title": "Dashboard"
      }
    },
    "settings": {
      "title": "Settings"
    }
  },
  "auth": {
    "login": {
      "title": "Sign in to your account",
      "email": "Email address",
      "password": "Password",
      "submit": "Sign in"
    }
  }
}

Chinese Message Example

messages/zh.json
{
  "app": {
    "name": "NEXTDEVKIT",
    "metadata": {
      "title": "NEXTDEVKIT - Next.js SaaS 开发模板",
      "description": "更快构建和部署生产级 SaaS 应用..."
    }
  },
  "menu": {
    "application": {
      "dashboard": {
        "title": "仪表板"
      }
    },
    "settings": {
      "title": "设置"
    }
  },
  "auth": {
    "login": {
      "title": "登录您的账户",
      "email": "电子邮箱",
      "password": "密码",
      "submit": "登录"
    }
  }
}

Organization Strategy

Group by Feature Module:

  • app: Application-level information
  • common: Common vocabulary
  • auth: Authentication-related
  • menu: Navigation menu
  • settings: Settings pages

Naming Conventions:

  • Use camelCase: firstName instead of first_name
  • Semantic naming: submitButton instead of btn1
  • Hierarchical structure: auth.login.title instead of authLoginTitle

🎨 Using in Components

Server Components

src/app/[locale]/(marketing)/page.tsx
import { getTranslations } from "next-intl/server";

export default async function HomePage() {
  const t = await getTranslations("app.metadata");
  
  return (
    <div>
      <h1>{t("title")}</h1>
      <p>{t("description")}</p>
    </div>
  );
}

Client Components

src/components/auth/login-form.tsx
'use client';

import { useTranslations } from "next-intl";

export function LoginForm() {
  const t = useTranslations("auth.login");
  
  return (
    <form>
      <h2>{t("title")}</h2>
      <input 
        type="email" 
        placeholder={t("email")}
      />
      <input 
        type="password" 
        placeholder={t("password")}
      />
      <button type="submit">
        {t("submit")}
      </button>
    </form>
  );
}

🧭 Internationalized Navigation

NextDevKit's Link component automatically handles language routing:

src/components/shared/header/index.tsx
import { Link } from "@/i18n/navigation";
import { useTranslations } from "next-intl";

export function Header() {
  const t = useTranslations("menu");

  return (
    <nav>
      {/* Basic usage - automatically adds language prefix */}
      <Link href="/">Home</Link>
      <Link href="/blog">Blog</Link>
      <Link href="/docs">Docs</Link>
      
      {/* Using translated text */}
      <Link href="/app/dashboard">
        {t("application.dashboard.title")}
      </Link>
    </nav>
  );
}

Automatic Language Prefix:

// When current language is zh
<Link href="/about">关于</Link>
// Renders: <a href="/zh/about">关于</a>

// When current language is en (default)
<Link href="/about">About</Link>
// Renders: <a href="/about">About</a>

Programmatic Navigation:

'use client';

import { useRouter, usePathname } from "@/i18n/navigation";

export function NavigationExample() {
  const router = useRouter();
  const pathname = usePathname();
  
  const handleNavigation = () => {
    // Navigate to another page
    router.push('/dashboard');
    
    // Navigate with query parameters
    router.push('/search?q=nextjs');
    
    // Replace current history entry
    router.replace('/new-page');
    
    // Go back
    router.back();
  };
  
  return (
    <div>
      <p>Current path: {pathname}</p>
      <button onClick={handleNavigation}>Navigate</button>
    </div>
  );
}

Server-side Redirect:

import { redirect } from "@/i18n/navigation";
import { getTranslations } from "next-intl/server";

export default async function ProtectedPage() {
  const session = await getSession();
  
  if (!session) {
    // Automatically adds language prefix for redirect
    redirect('/auth/login');
  }
  
  return <div>Protected content</div>;
}

Middleware Configuration

middleware.ts
import createMiddleware from "next-intl/middleware";
import { routing } from "@/i18n/routing";

export default createMiddleware(routing);

export const config = {
  // Match all paths except api, _next/static, _next/image and file extensions
  matcher: ["/((?!api|_next/static|_next/image|.*\\..*).*)"]
};

🔧 Adding New Languages

1. Create Translation File

Create a new language file in the messages/ directory:

messages/ja.json
{
  "app": {
    "name": "NEXTDEVKIT",
    "metadata": {
      "title": "NEXTDEVKIT - Next.js SaaS スターターキット",
      "description": "より高速にプロダクション対応のSaaSアプリを構築..."
    }
  },
  "auth": {
    "login": {
      "title": "アカウントにサインイン",
      "email": "メールアドレス",
      "password": "パスワード",
      "submit": "サインイン"
    }
  }
}

2. Update App Configuration

src/config/index.ts
export const appConfig = {
  i18n: {
    enabled: true,
    defaultLocale: "en",
    locales: {
      en: { name: "English" },
      zh: { name: "简体中文" },
      ja: { name: "日本語" },  // Add Japanese
    },
    localeCookieName: "NEXT_LOCALE",
  },
} as const;

3. Test New Language

Start the development server and visit:

  • / (default language)
  • /zh (Chinese)
  • /ja (Japanese)

📚 Best Practices

Translation Key Naming

// ✅ Good naming
t("auth.login.submitButton")
t("common.confirmDialog.title") 
t("dashboard.metrics.totalUsers")

// ❌ Avoid
t("button1")
t("text_for_login")
t("authLoginSubmitButtonText")