语言检测

插件支持多种语言检测方式,可以组合使用以满足不同的业务需求。

检测方式

1. URL 路径检测(localePathRedirect)

localePathRedirect 设置为 true 时,插件会从 URL 路径中检测语言。

示例

  • /zh/about → 检测到语言:zh
  • /en/about → 检测到语言:en
  • /about → 如果没有语言前缀,会重定向到默认语言路径

配置

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

路由配置(约定式路由):

使用约定式路由时,需要在 routes/ 目录下创建 [lang] 目录来表示语言参数:

routes/
├── [lang]/
   ├── layout.tsx    # 布局组件
   ├── page.tsx      # 首页
   └── about/
       └── page.tsx  # About 页面

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

如果使用自定义路由(modern.routes.ts),需要在路由配置中添加 :lang 动态参数。约定式路由会自动根据文件结构生成对应的路由。

2. i18next 语言检测器

i18nextDetector 设置为 true 时,会启用 i18next 的语言检测器,支持从以下位置检测语言:

  • Cookie:从 Cookie 中读取语言设置
  • LocalStorage:从浏览器 LocalStorage 中读取
  • 查询参数:从 URL 查询参数中读取(如 ?lng=en
  • 请求头:从 HTTP 请求头中读取(如 Accept-Language
  • HTML 标签:从 HTML 标签的 lang 属性读取
  • 子域名:从子域名中读取(如 en.example.com

配置

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

3. 自定义检测配置

通过 detection 选项可以自定义检测行为:

i18nPlugin({
  localeDetection: {
    i18nextDetector: true,
    detection: {
      // 检测顺序
      order: ['path', 'cookie', 'querystring', 'header'],

      // Cookie 相关
      lookupCookie: 'i18next',
      cookieExpirationDate: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), // 1年后过期
      cookieDomain: '.example.com',

      // 查询参数相关
      lookupQuerystring: 'lng',

      // 请求头相关
      lookupHeader: 'accept-language',

      // 缓存配置
      caches: ['cookie', 'localStorage'],
    },
  },
});

检测优先级

插件的语言检测遵循以下优先级顺序(从高到低):

  1. SSR 数据(最高优先级):从 window._SSR_DATA 中读取服务端渲染时设置的语言,适用于 SSR 和 CSR 项目
  2. 路径检测:如果 localePathRedirecttrue,从 URL 路径中检测语言前缀
  3. i18next 检测器:按照 detection.order 配置的顺序执行检测(Cookie、LocalStorage、查询参数、请求头等)
  4. 用户配置语言:使用 initOptions.lng 中配置的语言
  5. 回退语言:使用 fallbackLanguage 作为最终回退
Info

SSR 数据检测优先级最高,这是为了确保客户端能够使用服务端渲染时检测到的语言,避免客户端重新检测导致的语言闪烁问题。

示例

// 配置的检测顺序(仅影响 i18next 检测器内部的优先级)
detection: {
  order: ['path', 'cookie', 'querystring', 'header'],
}

// 实际检测流程:
// 1. 首先检查 SSR 数据(window._SSR_DATA)
// 2. 然后检查 URL 路径(如果启用 localePathRedirect)
// 3. 然后按照 order 顺序检查 i18next 检测器:
//    - Cookie
//    - 查询参数
//    - 请求头
// 4. 然后使用 initOptions.lng(如果配置)
// 5. 最后使用 fallbackLanguage

检测选项说明

order(检测顺序)

指定 i18next 检测器内部的检测顺序,可选值:

  • path:从 URL 路径检测(需要 localePathRedirecttrue
  • querystring:从查询参数检测(如 ?lng=en
  • cookie:从 Cookie 检测
  • localStorage:从 LocalStorage 检测(仅浏览器环境)
  • sessionStorage:从 SessionStorage 检测(仅浏览器环境)
  • navigator:从浏览器语言设置检测(仅浏览器环境)
  • htmlTag:从 HTML 标签的 lang 属性检测(仅浏览器环境)
  • header:从 HTTP 请求头检测(如 Accept-Language
  • subdomain:从子域名检测(如 en.example.com

默认检测顺序

如果不配置 order,插件会使用以下默认顺序:

order: [
  'querystring', // 查询参数优先级最高
  'cookie', // 然后是 Cookie
  'localStorage', // 然后是 LocalStorage
  'header', // 然后是请求头
  'navigator', // 然后是浏览器语言
  'htmlTag', // 然后是 HTML 标签
  'path', // 然后是路径
  'subdomain', // 最后是子域名
];
Warning

path 检测需要 localePathRedirecttruelocalStoragesessionStoragenavigatorhtmlTag 仅在浏览器环境可用。

Info

注意:order 配置只影响 i18next 检测器内部的优先级。实际的检测优先级还包括 SSR 数据和路径检测,它们有更高的优先级(见上面的"检测优先级"章节)。

caches(缓存方式)

指定检测到的语言应该缓存在哪里,可选值:

  • false:不缓存
  • ['cookie']:缓存到 Cookie
  • ['localStorage']:缓存到 LocalStorage(仅浏览器)
  • ['cookie', 'localStorage']:同时缓存到 Cookie 和 LocalStorage

lookupQuerystring、lookupCookie、lookupLocalStorage、lookupSession、lookupHeader

指定从查询参数、Cookie、LocalStorage、SessionStorage 或请求头中读取语言时使用的键名:

  • lookupQuerystring:默认 'lng',例如 ?lng=en
  • lookupCookie:默认 'i18next'
  • lookupLocalStorage:默认 'i18nextLng'(仅浏览器环境)
  • lookupSession:SessionStorage 的键名(仅浏览器环境)
  • lookupHeader:默认 'accept-language'

lookupFromPathIndex

指定从 URL 路径的哪个位置开始检测语言(当 order 中包含 'path' 时):

  • lookupFromPathIndex:路径段索引,默认为 0(第一个路径段)

示例

// URL: /api/v1/en/users
// 如果 lookupFromPathIndex = 2,则从第三个路径段('en')开始检测
detection: {
  order: ['path'],
  lookupFromPathIndex: 2,
}

cookieMinutes、cookieExpirationDate

控制 Cookie 的过期时间:

  • cookieMinutes:Cookie 过期时间(分钟),默认 525600(1 年)
  • cookieExpirationDate:Cookie 过期日期(Date 对象),优先级高于 cookieMinutes

示例

detection: {
  cookieMinutes: 60 * 24 * 7, // 7天后过期
  // 或
  cookieExpirationDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7天后过期
}

ignoreRedirectRoutes

指定哪些路由应该忽略自动语言重定向。这对于 API 路由、静态资源等不需要语言前缀的路径非常有用。

配置方式

i18nPlugin({
  localeDetection: {
    localePathRedirect: true,
    languages: ['zh', 'en'],
    fallbackLanguage: 'en',
    // 字符串数组:支持精确匹配和前缀匹配
    ignoreRedirectRoutes: ['/api', '/admin', '/static'],
    // 或使用函数进行更灵活的判断
    ignoreRedirectRoutes: pathname => {
      return pathname.startsWith('/api') || pathname.startsWith('/admin');
    },
  },
});

匹配规则

  • 字符串数组:支持精确匹配('/api')和前缀匹配('/api' 会匹配 /api/api/users
  • 函数:接收路径名(已去除语言前缀),返回 true 表示忽略重定向

示例

// 忽略所有 API 路由和静态资源
ignoreRedirectRoutes: ['/api', '/static', '/assets'];

// 使用函数忽略所有以 /api 开头的路径
ignoreRedirectRoutes: pathname => pathname.startsWith('/api');