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
Core Components:
src/i18n/
: Core i18n configuration and utilitiesmessages/
: Translation files for all languagesapp/[locale]/
: Language-based routing structuremiddleware.ts
: Language detection and redirect middleware
⚙️ Configuration and Initialization
App Configuration
Manage i18n configuration in 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 i18ndefaultLocale
: Default language for fallback and first visitlocales
: Language mapping, key is language code, value is display namelocaleCookieName
: Cookie name for storing user language preference
Routing Configuration
src/i18n/routing.ts
defines next-intl routing behavior:
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 languagesalways
: All languages show prefix (/en/about
,/zh/about
)
📁 Translation Message Organization
Message File Structure
Use nested structure to organize translation messages:
English Message Example
{
"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
{
"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 informationcommon
: Common vocabularyauth
: Authentication-relatedmenu
: Navigation menusettings
: Settings pages
Naming Conventions:
- Use camelCase:
firstName
instead offirst_name
- Semantic naming:
submitButton
instead ofbtn1
- Hierarchical structure:
auth.login.title
instead ofauthLoginTitle
🎨 Using in Components
Server Components
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
'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
Using Link Component
NextDevKit's Link
component automatically handles language routing:
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>
);
}
Link Component Features
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
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:
{
"app": {
"name": "NEXTDEVKIT",
"metadata": {
"title": "NEXTDEVKIT - Next.js SaaS スターターキット",
"description": "より高速にプロダクション対応のSaaSアプリを構築..."
}
},
"auth": {
"login": {
"title": "アカウントにサインイン",
"email": "メールアドレス",
"password": "パスワード",
"submit": "サインイン"
}
}
}
2. Update App Configuration
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")