Internationalization (i18n, v1)
Serve translated WordPress content with locale-prefixed URLs and a frontend language switcher. Off by default — with I18N_ENABLED unset, behavior is byte-for-byte identical to a single-locale deployment.
Enabling it
Install Polylang or WPML on WordPress and create your translations.
Set
I18N_ENABLED=1in the Next.js environment.Edit
nextjs-starter-kit/locales.config.json(committed with a single default locale so the app works out of the box). Seelocales.config.example.json:{ "locales": [ { "code": "en", "name": "English", "isDefault": true }, { "code": "fr", "name": "Français" }, { "code": "ta", "name": "தமிழ்" } ] }Add UI translations in
nextjs-starter-kit/messages/{locale}.jsonfor chrome strings (footer, admin link label, etc.). CMS content comes from WordPress.
How it works
URL routing
middleware.tsreads the first path segment againstlocales.config.json./fr/blog→ setsx-headlesscore-locale: fr, internally rewrites to/blog.- The default locale (
isDefault: true) has no URL prefix —/blogstays/blog. - Non-default locales are prefixed:
/fr/blog,/ta/corporate, etc.
WordPress content language
fetchAPI()sends anX-HeadlessCore-Languageheader on every GraphQL request.inc/i18n.phpswitches the Polylang/WPML language context before WPGraphQL resolves queries.- Cache tags are locale-scoped:
${tenant.id}:${locale}:${tag}so translations don't collide in the ISR cache.
Frontend UI strings
- next-intl loads messages from
messages/{locale}.json. src/i18n/request.tsresolves the locale from the middleware header.- Layout chrome (footer tagline, "WordPress Admin" label) is translated; page content comes from WordPress.
Language switcher
LanguageSwitcherin the global header lists locales fromlocales.config.json(whenI18N_ENABLED=1) or fromthemeSettings.languages(Polylang/WPML via GraphQL).- Hidden automatically when only one locale is configured.
SEO
buildMetadata()acceptslocalesandcurrentLocaleto emithreflangalternates when i18n is enabled.<html lang="...">is set from the active locale.
GraphQL fields
themeSettings {
languages {
code
name
isDefault
}
defaultLocale
}
Populated from Polylang/WPML when installed; falls back to a single en entry.
Revalidation
/api/revalidate busts cache tags for every configured locale when a tag is provided, since WordPress webhooks don't carry a locale. Prefer tag-based revalidation over path-based in i18n deployments.
Deferred (not in v1)
- Translated navigation menus per locale — menus resolve from WordPress but aren't auto-switched by Polylang menu locations yet.
- Locale-aware
generateStaticParams— dynamic pages aren't pre-built per locale at build time. - RTL layout support — no automatic
dir="rtl"for Arabic/Hebrew. - WPML String Translation for theme option labels.
Requirements
| Component | Required? |
|---|---|
| Polylang or WPML (WordPress) | Yes, for translated CMS content |
locales.config.json |
Yes, when I18N_ENABLED=1 |
messages/{locale}.json |
Yes, for each locale with UI strings |
next-intl |
Bundled — powers UI translations |
See also: Multi-Tenant (can be combined — tenant resolution runs before locale resolution in middleware).