站点配置实现基于域名的多语言路由

实现基于域名的多语言路由

面向 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,但为了让配置明确且便于审计,建议保留。

路由决策算法

  1. 用请求域名匹配 i18n.domains[].domain
  2. 把该条目的 defaultLocale 作为此域名无前缀路径的路由默认语言。
  3. 如果 URL 包含显式语言前缀,验证该语言是否受站点级配置支持。
  4. 如果当前域名通过 defaultLocalelocales 提供该语言,则继续在当前域名响应。
  5. 否则查找由哪个域名的 defaultLocale 拥有该语言,并返回 307 临时重定向。
  6. 如果没有域名拥有该语言,则保留当前域名,以显式语言前缀提供内容。

不要混淆两种默认值:顶层 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)

当目标语言是当前域名的无前缀默认语言时,LinklocalizedPath() 会正确省略语言前缀。这是预期行为,不是缺陷。

语言切换器

语言切换器应保留当前不含语言前缀的路由,不要把所有用户都送回首页。

import { Link } from 'talizen'
import { getLocalePath } from 'talizen/router'

<Link href={getLocalePath()} locale='en'>English</Link>

正确读取语言与 CMS 内容

服务端使用 context.localegetLocale() 读取已解析语言;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 LinklocalizedPath()
  • 使用 getLocale()context.localeuseLocale(),不要解析 URL。
  • 在每个配置域名上测试无前缀和带前缀路由。
  • 验证重定向状态码、目标域名、路径与查询参数是否保留,并确保没有循环重定向。
  • 验证 CMS 内容语言与路由选择的语言一致。

禁止的实现

  • 不要添加 react-router-dom、Next.js 路由包或自定义语言路由器。
  • 不要在共享导航组件中硬编码 /en/fr 或其他语言前缀。
  • 不要把顶层 defaultLocale 当作会随域名变化的值。
  • 不要假设配置 i18n.domains 会同时绑定或验证域名。
  • 不要为每种语言复制 CMS 条目;应在同一条稳定内容中使用字段级翻译。
  • 除非产品路由契约明确改变,否则不要把预期的 307 语言重定向改成永久重定向。

Render diagnostics