Locale Detection

The plugin supports multiple language detection methods, which can be combined to meet different business requirements.

Detection Methods

1. URL Path Detection (localePathRedirect)

When localePathRedirect is set to true, the plugin will detect the language from the URL path.

Examples:

  • /zh/about → Detected language: zh
  • /en/about → Detected language: en
  • /about → If there's no language prefix, will redirect to the default language path

Configuration:

i18nPlugin({
  localeDetection: {
    localePathRedirect: true,
    languages: ['zh', 'en'],
    fallbackLanguage: 'en',
  },
});

Route Configuration (Convention-based Routing):

When using convention-based routing, you need to create a [lang] directory under the routes/ directory to represent the language parameter:

routes/
├── [lang]/
   ├── layout.tsx    # Layout component
   ├── page.tsx      # Home page
   └── about/
       └── page.tsx  # About page

routes/[lang]/layout.tsx:

import { Outlet } from '@modern-js/runtime/router';

export default function Layout() {
  return <Outlet />;
}

routes/[lang]/page.tsx:

export default function Home() {
  return <div>Home</div>;
}

routes/[lang]/about/page.tsx:

export default function About() {
  return <div>About</div>;
}
Info

If using custom routing (modern.routes.ts), you need to add :lang dynamic parameter in the route configuration. Convention-based routing will automatically generate corresponding routes based on the file structure.

2. i18next Language Detector

When i18nextDetector is set to true, the i18next language detector will be enabled, supporting language detection from the following locations:

  • Cookie: Read language settings from cookies
  • LocalStorage: Read from browser LocalStorage
  • Query Parameters: Read from URL query parameters (e.g., ?lng=en)
  • Request Headers: Read from HTTP request headers (e.g., Accept-Language)
  • HTML Tag: Read from the lang attribute of HTML tags
  • Subdomain: Read from subdomain (e.g., en.example.com)

Configuration:

i18nPlugin({
  localeDetection: {
    i18nextDetector: true,
    detection: {
      order: ['cookie', 'querystring', 'header'],
      lookupCookie: 'i18next',
      lookupQuerystring: 'lng',
      lookupHeader: 'accept-language',
      caches: ['cookie'],
    },
  },
});

3. Custom Detection Configuration

You can customize detection behavior through the detection option:

i18nPlugin({
  localeDetection: {
    i18nextDetector: true,
    detection: {
      // Detection order
      order: ['path', 'cookie', 'querystring', 'header'],

      // Cookie related
      lookupCookie: 'i18next',
      cookieExpirationDate: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // Expires in 1 year
      cookieDomain: '.example.com',

      // Query parameter related
      lookupQuerystring: 'lng',

      // Request header related
      lookupHeader: 'accept-language',

      // Cache configuration
      caches: ['cookie', 'localStorage'],
    },
  },
});

Detection Priority

The plugin's language detection follows the following priority order (from highest to lowest):

  1. SSR Data (highest priority): Read language from window._SSR_DATA set during server-side rendering, applicable to both SSR and CSR projects
  2. Path Detection: If localePathRedirect is true, detect language prefix from URL path
  3. i18next Detector: Execute detection according to the order configured in detection.order (Cookie, LocalStorage, query parameters, request headers, etc.)
  4. User Configured Language: Use the language configured in initOptions.lng
  5. Fallback Language: Use fallbackLanguage as the final fallback
Info

SSR data detection has the highest priority to ensure the client uses the language detected during server-side rendering, avoiding language flickering issues caused by client-side re-detection.

Example:

// Configured detection order (only affects priority within i18next detector)
detection: {
  order: ['path', 'cookie', 'querystring', 'header'],
}

// Actual detection flow:
// 1. First check SSR data (window._SSR_DATA)
// 2. Then check URL path (if localePathRedirect is enabled)
// 3. Then check i18next detector according to order:
//    - Cookie
//    - Query parameters
//    - Request headers
// 4. Then use initOptions.lng (if configured)
// 5. Finally use fallbackLanguage

Detection Options

order (Detection Order)

Specifies the order of language detection, optional values:

  • path: Detect from URL path
  • querystring: Detect from query parameters
  • cookie: Detect from cookies
  • localStorage: Detect from LocalStorage
  • sessionStorage: Detect from SessionStorage
  • navigator: Detect from browser language settings
  • htmlTag: Detect from HTML tags
  • header: Detect from HTTP request headers
  • subdomain: Detect from subdomain
Warning

path detection requires localePathRedirect to be true. localStorage, sessionStorage, navigator, and htmlTag are only available in browser environments.

caches (Cache Method)

Specifies where the detected language should be cached, optional values:

  • false: No caching
  • ['cookie']: Cache to Cookie
  • ['localStorage']: Cache to LocalStorage (browser only)
  • ['cookie', 'localStorage']: Cache to both Cookie and LocalStorage

lookupQuerystring, lookupCookie, lookupLocalStorage, lookupSession, lookupHeader

Specifies the key name used when reading language from query parameters, cookies, LocalStorage, SessionStorage, or request headers:

  • lookupQuerystring: Default 'lng', e.g., ?lng=en
  • lookupCookie: Default 'i18next'
  • lookupLocalStorage: Default 'i18nextLng' (browser only)
  • lookupSession: SessionStorage key name (browser only)
  • lookupHeader: Default 'accept-language'

lookupFromPathIndex

Specifies which position in the URL path to start detecting language (when 'path' is included in order):

  • lookupFromPathIndex: Path segment index, defaults to 0 (first path segment)

Example:

// URL: /api/v1/en/users
// If lookupFromPathIndex = 2, detection starts from the third path segment ('en')
detection: {
  order: ['path'],
  lookupFromPathIndex: 2,
}

cookieMinutes, cookieExpirationDate

Controls Cookie expiration time:

  • cookieMinutes: Cookie expiration time (minutes), default 525600 (1 year)
  • cookieExpirationDate: Cookie expiration date (Date object), takes precedence over cookieMinutes

Example:

detection: {
  cookieMinutes: 60 * 24 * 7, // Expires in 7 days
  // or
  cookieExpirationDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Expires in 7 days
}

ignoreRedirectRoutes

Specifies which routes should ignore automatic language redirection. This is very useful for API routes, static resources, and other paths that don't need language prefixes.

Configuration:

i18nPlugin({
  localeDetection: {
    localePathRedirect: true,
    languages: ['zh', 'en'],
    fallbackLanguage: 'en',
    // String array: supports exact match and prefix match
    ignoreRedirectRoutes: ['/api', '/admin', '/static'],
    // Or use function for more flexible judgment
    ignoreRedirectRoutes: pathname => {
      return pathname.startsWith('/api') || pathname.startsWith('/admin');
    },
  },
});

Matching Rules:

  • String array: Supports exact match ('/api') and prefix match ('/api' will match /api and /api/users)
  • Function: Receives pathname (with language prefix removed), returns true to indicate ignoring redirection

Example:

// Ignore all API routes and static resources
ignoreRedirectRoutes: ['/api', '/static', '/assets'];

// Use function to ignore all paths starting with /api
ignoreRedirectRoutes: pathname => pathname.startsWith('/api');