实现基于域名的多语言路由
面向 AI 智能体的实现规范:配置语言归属、跨域重定向、多语言链接和 CMS 多语言行为。
当 AI 编程智能体需要让同一个 Creght 站点在不同域名上提供不同默认语言时,请遵循本规范。以下规则是实现约束,不是可选的界面建议。
所需输入
编辑代码前,先收集以下值:
- 站点级基础
defaultLocale,它也是 CMS 多语言字段的回退层。 - 完整的站点级
locales列表。 - 已经绑定到同一个 Creght 站点的全部域名。
- 每个域名所拥有的、无需路径前缀的默认语言。
- 每个域名还需要在本域名下提供的、带前缀的其他语言。
- 是否启用自动语言检测。
硬性要求:
i18n.domains不会配置 DNS,也不会把域名绑定到站点。每个配置的域名都必须已经在“设置 → 域名”中绑定,否则请求无法进入该站点的语言路由器。
标准配置
在 talizen.config.ts 中把 i18n 写成普通对象,不要导入配置辅助函数。
export default {
i18n: {
defaultLocale: 'zh-CN',
locales: ['zh-CN', 'en'],
localeDetection: false,
domains: [
{
domain: 'www.example.cn',
defaultLocale: 'zh-CN',
locales: ['zh-CN', 'en'],
},
{
domain: 'www.example.com',
defaultLocale: 'en',
locales: ['en'],
},
],
},
}
每个域名使用的语言都必须存在于顶层 locales 中。路由并不要求把域名自己的 defaultLocale 再写入 domains[].locales,但为了让配置明确且便于审计,建议保留。
路由决策算法
- 用请求域名匹配
i18n.domains[].domain。 - 把该条目的
defaultLocale作为此域名无前缀路径的路由默认语言。 - 如果 URL 包含显式语言前缀,验证该语言是否受站点级配置支持。
- 如果当前域名通过
defaultLocale或locales提供该语言,则继续在当前域名响应。 - 否则查找由哪个域名的
defaultLocale拥有该语言,并返回 307 临时重定向。 - 如果没有域名拥有该语言,则保留当前域名,以显式语言前缀提供内容。
不要混淆两种默认值:顶层
i18n.defaultLocale始终是站点的基础内容语言;域名条目的defaultLocale只控制该域名上无前缀请求的默认路由语言。
域名的语言归属
单语言域名
如果域名只提供自己的 defaultLocale,可省略其他语言。请求另一个域名拥有的语言时,应重定向到对应域名。
多语言域名
在 domains[].locales 中列出需要在本域名提供的其他语言。这些语言保留当前域名,并使用显式路径前缀。
无前缀路径
当域名声明 defaultLocale: 'fr' 时,example.fr/about 解析为法语。不要添加多余的 /fr 前缀。
显式语言路径
只有当 example.cn 也提供英语时,example.cn/en/about 才留在该域名;否则可重定向到拥有英语的域名。
使用 Talizen API 生成链接
内部链接必须使用 Talizen 的多语言导航 API。禁止手动拼接 /${locale},也不要从 window.location.pathname 推断语言。
import { Link, localizedPath } from 'talizen'
// 渲染内部导航时优先使用。
<Link href='/about'>About</Link>
// 组件必须接收原始 URL 字符串时使用。
const href = localizedPath('/about', locale)
当目标语言是当前域名的无前缀默认语言时,Link 和 localizedPath() 会正确省略语言前缀。这是预期行为,不是缺陷。
语言切换器
语言切换器应保留当前不含语言前缀的路由,不要把所有用户都送回首页。
import { Link } from 'talizen'
import { getLocalePath } from 'talizen/router'
<Link href={getLocalePath()} locale='en'>English</Link>
正确读取语言与 CMS 内容
服务端使用 context.locale 或 getLocale() 读取已解析语言;React 组件使用 useLocale()。不要解析 URL,因为页面代码运行前路由器可能已经移除语言前缀。
import { getLocale } from 'talizen'
import { getContent } from 'talizen/cms'
export async function getServerSideProps() {
const { locale } = getLocale()
const page = await getContent('pages', 'about', {})
return { props: { locale, page } }
}
talizen/cms 返回的字段已经按当前语言解码,直接读取普通字段即可。不要在页面代码中手动合并 _i18n。
验收矩阵
对上面的标准配置,验证以下准确行为:
www.example.cn/about → zh-CN,200,无语言前缀
www.example.cn/en/about → en,200,保留 .cn 域名
www.example.com/about → en,200,无语言前缀
www.example.com/zh-CN/about → 根据域名归属重定向或输出带前缀内容
如果从 www.example.cn.locales 中移除 en,则 www.example.cn/en/about 应返回 307,重定向到 www.example.com/about。
实现检查清单
- 修改
talizen.config.ts前,确认每个域名都已绑定到同一个站点。 - 确认所有域名默认语言和附加语言都存在于顶层
i18n.locales。 - 保持配置为普通对象,不要引入 Next.js 配置辅助函数。
- 内部链接使用 Talizen
Link或localizedPath()。 - 使用
getLocale()、context.locale或useLocale(),不要解析 URL。 - 在每个配置域名上测试无前缀和带前缀路由。
- 验证重定向状态码、目标域名、路径与查询参数是否保留,并确保没有循环重定向。
- 验证 CMS 内容语言与路由选择的语言一致。
禁止的实现
- 不要添加
react-router-dom、Next.js 路由包或自定义语言路由器。 - 不要在共享导航组件中硬编码
/en、/fr或其他语言前缀。 - 不要把顶层
defaultLocale当作会随域名变化的值。 - 不要假设配置
i18n.domains会同时绑定或验证域名。 - 不要为每种语言复制 CMS 条目;应在同一条稳定内容中使用字段级翻译。
- 除非产品路由契约明确改变,否则不要把预期的 307 语言重定向改成永久重定向。
