Skip to Content
DevelopmentPlatformWebNextJSLocalization

Next.js Internationalization (i18n) with next-intl

In this guide you will learn how to set up internationalization (i18n) in your Next.js app.

With Next.js 13, the App Router  along with support for React Server Components was introduced and announced as stable with version 13.4. Following the lead of Next.js, next-intl also recommends this paradigm since it increases both simplicity as well as flexibility when it comes to i18n.

Getting Started

Best Practices for Managing Translations with next-intl

1. Split translation files by feature or page instead of having one large file per locale

messages/ ├── en/ │ ├── home.json │ ├── auth.json │ ├── common.json │ └── settings.json └── vi/ ├── home.json ├── auth.json ├── common.json └── settings.json
messages/ ├── en.json └── vi.json
BenefitExplanation
✅ Modular and organizedEach file maps to a specific feature or page (e.g., Home, Auth)
✅ Easy to scaleAs you add more pages, just add more message files without clutter
✅ Easier collaborationDevelopers and translators can focus only on relevant sections
✅ Cleaner version controlSmaller files reduce merge conflicts when working in teams
✅ Enables lazy loadingYou can load only the required message files per route if needed

Setup Instructions

Step 1: Example common.json (shared UI labels)

Messages represent the translations that are available per language and can be provided either locally or loaded from a remote data source.

The simplest option is to add JSON files in your local project folder:

messages/en/common.json

{ "nav": { "home": "Home", "settings": "Settings" }, "buttons": { "submit": "Submit", "cancel": "Cancel" } }

messages/vi/common.json

{ "nav": { "home": "Trang chủ", "settings": "Cài đặt" }, "buttons": { "submit": "Gửi", "cancel": "Hủy" } }

Step 2: next.config.ts

Now, set up the plugin which creates an alias to provide a request-specific i18n configuration like your messages to Server Components—more on this in the following steps.

Go to official documentation for more details: next.config.ts 

Step 3: src/i18n/routing.ts

Go to official documentation for more details: src/i18n/routing.ts 

Step 4: src/i18n/navigation.ts

Go to official documentation for more details: src/i18n/navigation.ts 

Step 5: src/middleware.ts

Go to official documentation for more details: src/middleware.ts 

Step 6: src/i18n/request.ts

When using features from next-intl inside Server Components, the internationalization setup is automatically loaded from a central configuration file located at src/i18n/request.ts.

By convention, this file is responsible for providing locale-aware configuration scoped to the current request. When you use a modular file structure (e.g. splitting messages into home.json, auth.json, etc.), this module can dynamically import and combine those files:

import { getRequestConfig } from "next-intl/server" import { hasLocale } from "next-intl" import { routing } from "./routing" export default getRequestConfig(async ({ requestLocale }) => { const requested = await requestLocale const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale // Load split message files and merge const common = (await import(`../../messages/${locale}/common.json`)).default const home = (await import(`../../messages/${locale}/home.json`)).default const auth = (await import(`../../messages/${locale}/auth.json`)).default const messages = { ...common, ...home, ...auth, } return { locale, messages, } })

Step 7: src/app/[locale]/layout.tsx

Go to official documentation for more details: src/app/[locale]/layout.tsx 

Step 8: src/app/[locale]/page.tsx

Go to official documentation for more details: src/app/[locale]/page.tsx 

That’s all it takes!

💡 Key Benefits:

  • Supports modular translation files per page or feature
  • Keeps translations organized and scalable
  • Ensures the right messages are loaded for each request
  • Works seamlessly with Server Components

2. Translation Key Naming Convention

A well-structured translation system is essential for building scalable and maintainable multilingual applications. Below is a recommended convention for naming translation keys when using next-intl, especially with split JSON files per feature or page.

✅ Recommended Key Naming Convention

Since each file is already scoped by context, keys inside the file should use nested groupings, but do not need a redundant namespace prefix.

✅ Good Example – auth.json:

{ "form": { "email": "Email", "password": "Password" }, "button": { "login": "Login", "register": "Register" }, "error": { "invalid": "Invalid email or password" } }

📦 In your code:

import {useTranslations} from 'next-intl'; const t = useTranslations('auth'); <Form.Input label={t('form.email')} /> <Form.Input label={t('form.password')} />

❌ Bad Examples

Flat and long keys with manual namespacing:

{ "auth_form_email": "Email", "auth_button_login": "Login" }

Issues:

  • ❌ Verbose and repetitive
  • ❌ Difficult to manage and group logically
  • ❌ Weak IDE support

Redundant nesting with filenames:

{ "Auth": { "form": { "email": "Email" } } }

Issues:

  • ❌ Duplicates the file context (file is already auth.json)
  • ❌ Requires t('Auth.form.email') instead of t('form.email')
  • ❌ Harder to scan visually

References

  • next-intl — Internationalization for Next.js
Last updated on