插件支持多种语言检测方式,可以组合使用以满足不同的业务需求。
当 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>;
}如果使用自定义路由(modern.routes.ts),需要在路由配置中添加 :lang 动态参数。约定式路由会自动根据文件结构生成对应的路由。
当 i18nextDetector 设置为 true 时,会启用 i18next 的语言检测器,支持从以下位置检测语言:
?lng=en)Accept-Language)lang 属性读取en.example.com)配置:
i18nPlugin({
localeDetection: {
i18nextDetector: true,
detection: {
order: ['cookie', 'querystring', 'header'],
lookupCookie: 'i18next',
lookupQuerystring: 'lng',
lookupHeader: 'accept-language',
caches: ['cookie'],
},
},
});通过 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'],
},
},
});插件的语言检测遵循以下优先级顺序(从高到低):
window._SSR_DATA 中读取服务端渲染时设置的语言,适用于 SSR 和 CSR 项目localePathRedirect 为 true,从 URL 路径中检测语言前缀detection.order 配置的顺序执行检测(Cookie、LocalStorage、查询参数、请求头等)initOptions.lng 中配置的语言fallbackLanguage 作为最终回退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指定 i18next 检测器内部的检测顺序,可选值:
path:从 URL 路径检测(需要 localePathRedirect 为 true)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', // 最后是子域名
];path 检测需要 localePathRedirect 为 true,localStorage、sessionStorage、navigator、htmlTag 仅在浏览器环境可用。
注意:order 配置只影响 i18next 检测器内部的优先级。实际的检测优先级还包括 SSR 数据和路径检测,它们有更高的优先级(见上面的"检测优先级"章节)。
指定检测到的语言应该缓存在哪里,可选值:
false:不缓存['cookie']:缓存到 Cookie['localStorage']:缓存到 LocalStorage(仅浏览器)['cookie', 'localStorage']:同时缓存到 Cookie 和 LocalStorage指定从查询参数、Cookie、LocalStorage、SessionStorage 或请求头中读取语言时使用的键名:
lookupQuerystring:默认 'lng',例如 ?lng=enlookupCookie:默认 'i18next'lookupLocalStorage:默认 'i18nextLng'(仅浏览器环境)lookupSession:SessionStorage 的键名(仅浏览器环境)lookupHeader:默认 'accept-language'指定从 URL 路径的哪个位置开始检测语言(当 order 中包含 'path' 时):
lookupFromPathIndex:路径段索引,默认为 0(第一个路径段)示例:
// URL: /api/v1/en/users
// 如果 lookupFromPathIndex = 2,则从第三个路径段('en')开始检测
detection: {
order: ['path'],
lookupFromPathIndex: 2,
}控制 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天后过期
}指定哪些路由应该忽略自动语言重定向。这对于 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');