N
HeadlessCoreHeadless Core
Back to Home

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

  1. Install Polylang or WPML on WordPress and create your translations.

  2. Set I18N_ENABLED=1 in the Next.js environment.

  3. Edit nextjs-starter-kit/locales.config.json (committed with a single default locale so the app works out of the box). See locales.config.example.json:

    {
      "locales": [
        { "code": "en", "name": "English", "isDefault": true },
        { "code": "fr", "name": "Français" },
        { "code": "ta", "name": "தமிழ்" }
      ]
    }
    
  4. Add UI translations in nextjs-starter-kit/messages/{locale}.json for chrome strings (footer, admin link label, etc.). CMS content comes from WordPress.

How it works

URL routing

  • middleware.ts reads the first path segment against locales.config.json.
  • /fr/blog → sets x-headlesscore-locale: fr, internally rewrites to /blog.
  • The default locale (isDefault: true) has no URL prefix/blog stays /blog.
  • Non-default locales are prefixed: /fr/blog, /ta/corporate, etc.

WordPress content language

  • fetchAPI() sends an X-HeadlessCore-Language header on every GraphQL request.
  • inc/i18n.php switches 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.ts resolves the locale from the middleware header.
  • Layout chrome (footer tagline, "WordPress Admin" label) is translated; page content comes from WordPress.

Language switcher

  • LanguageSwitcher in the global header lists locales from locales.config.json (when I18N_ENABLED=1) or from themeSettings.languages (Polylang/WPML via GraphQL).
  • Hidden automatically when only one locale is configured.

SEO

  • buildMetadata() accepts locales and currentLocale to emit hreflang alternates 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).