国际化 (i18n)
基于 @nuxtjs/i18n 构建的多语言支持——framework 层默认仅 zh-CN,多语言经 cli 创建时多选或显式传 locales 扩展(starter 模板内置简体中文、繁体中文、英文三份 locale 文件);翻译文件扁平化管理,AI 友好。
启用 i18n
// nuxt.config.ts
swiftConfig: {
i18n: {
enabled: true,
defaultLocale: 'zh-CN',
locales: ['zh-CN', 'zh-TW', 'en'],
}
}
swiftConfig.i18n.locales 只做一件事:把语言码映射成 @nuxtjs/i18n 的 { code, language } 用于生成路由前缀(/en/、/zh-TW/)。它不注册翻译文件 —— 里面没有 file 字段。
翻译文件靠 nuxt.config.ts 顶层的 i18n 块接上(sw config set framework.i18n.enabled true 会自动解开 starter 里这段注释):
// nuxt.config.ts —— 与 swiftConfig 平级的顶层 i18n
i18n: {
langDir: 'locales', // 相对 i18n/ 目录
locales: [
{ code: 'zh-CN', language: 'zh-CN', name: '简体中文', file: 'zh-CN.json' },
{ code: 'zh-TW', language: 'zh-TW', name: '繁體中文', file: 'zh-TW.json' },
{ code: 'en', language: 'en', name: 'English', file: 'en.json' },
],
}
顶层 i18n.locales 会覆盖 framework 给的默认值,所以两处的语言码要保持一致。只配 swiftConfig.i18n.locales、不配顶层 file 的后果是:路由前缀正常、$t() 却拿不到任何翻译(原样吐 key)。策略 / 浏览器检测 / bundle 等其余项由 framework 提供默认值,不必重复写。
framework 内置文案的多语言行为
framework 组件自身也有文案(分页器的「共 N 条」、feed 的「N 秒后可关闭广告」、弹窗的
「关闭 / 取消 / 确认」,以及约 33 条 aria-label)。这些会自动跟随你的活动语言,业务零改动。
- 启用 i18n 时:跟随用户当前选择的语言(不是构建期的
defaultLocale)—— 用户点SwLangSwitch切换,framework 文案当场跟着变 - 未启用 i18n 时:一律中文,零额外开销
- 内置
zh-CN/zh-TW/en三语;选了其它语言(如ja)时回落中文,不会出现空白 - 语言码按主码归一:
zh-Hant/zh-HK/zh-MO都归繁体,en-US/en-GB都归英文
<!-- 你什么都不用做:切到 en 后分页器自己就是英文 -->
<SwPagination v-model:page="page" :total="total" show-total />
<!-- zh-CN → 「共 100 条」/ zh-TW → 「共 100 筆」/ en → 「100 items」 -->
已有 slot / prop 逃生舱的组件不受影响——SwModal 的 cancelText / confirmText、
SwGallery 的 #empty / #finished 等你传了就用你的,没传才走内置词条。
翻译文件结构
翻译文件位于 i18n/locales/ 目录,采用扁平化 JSON 格式:
// i18n/locales/zh-CN.json
{
"首页": "首页",
"快速上手": "快速上手",
"立即开始": "立即开始",
"首页SEO描述": "Swift Web 框架的官方文档站,涵盖组件库和开发规范"
}
// i18n/locales/en.json
{
"首页": "Home",
"快速上手": "Quick Start",
"立即开始": "Get Started",
"首页SEO描述": "Official docs for Swift Web framework, covering components and standards"
}
在页面中使用
<script setup lang="ts">
const { t } = useI18n()
useSeo({
title: () => t('首页'),
description: () => t('首页SEO描述'),
})
</script>
<template>
<h1>{{ t('快速上手') }}</h1>
<button>{{ t('立即开始') }}</button>
</template>
语言切换
<template>
<!-- 内置切换器:仅 i18n 启用且 locales ≥ 2 时渲染(自 gate,否则占 0 空间);
支持 token(--sw-lang-switch-*)/ 具名 icon slot / default 作用域 slot 三档定制 -->
<SwLangSwitch />
</template>
需要完全自定义 UI 时再手写(注意用 <option :selected> 而非 <select :value>——后者是非法 HTML,SSR 直出会选错项):
<script setup lang="ts">
const { locale, setLocale, locales } = useI18n()
</script>
<template>
<select @change="setLocale(($event.target as HTMLSelectElement).value)">
<option v-for="loc in locales" :key="loc.code" :value="loc.code" :selected="loc.code === locale">
{{ loc.name }}
</option>
</select>
</template>
翻译 Key 规范
- 短文案(≤50字):直接用中文原文作为 key,如
t('立即开始') - 长文案(>50字):用简短中文描述作 key,全文放在 JSON value 里,如
t('首页SEO描述') - 禁止将长句直接作为 key,否则翻译文件难以阅读
URL 本地化
@nuxtjs/i18n 默认使用路径前缀策略:
- zh-CN(默认语言):
example.com/(无前缀) - zh-TW:
example.com/zh-TW/ - en:
example.com/en/
canonical 标签由 useSeo() 统一注入;多语言站(locales ≥ 2、prefix 策略)的 sitemap 自动按 locale 拆分并输出 hreflang 互链(零配置,详见 SEO)。
浏览器语言自动检测在可缓存页上不生效
@nuxtjs/i18n 的 detectBrowserLanguage(按 Accept-Language / Cookie 把访客带到对应语言前缀)在 ISR / 可缓存页上结构性失效:nitro 的缓存包装只向渲染透传 cache.varies 白名单里的请求头(host / x-forwarded-proto),Accept-Language 与 Cookie 根本到不了检测逻辑。这不是配置问题,把它们加进 varies 也不是出路——varies 会进缓存 key,按 Cookie 分裂等于命中率归零。
实际行为与取舍:
- 直达可缓存页(含首页)不会发生语言自动跳转,所有访客拿到默认语言的缓存副本。这是确定性行为——也因此检测重定向不可能被缓存广播,无正确性风险。
- 访客换语言靠 SwLangSwitch,路径前缀(
/en/...)是语言的持久载体。 - framework 会把可缓存页渲染期写出的 Set-Cookie(含 i18n 的检测标记 cookie)从响应剥离:可缓存响应带 Set-Cookie 会进共享缓存副本回放,还会让未配
proxy_ignore_headers的 nginx / CDN 拒绝缓存整页(排障判据见部署手册)。dev 模式下剥离伴随 console 告警说明剥了哪个 cookie;需要按用户写 Cookie 请在客户端写,或把该路由改为不可缓存。
html lang 自动跟随语言
多语言站(locales ≥ 2)下,framework 通过内置运行时插件把 <html lang> 接管为当前语言(取 locale 的 language 字段,兜底 code):
- 访问
/en/xxx→<html lang="en">,SSR 首帧即正确 - 站内切语言(
SwLangSwitch)→lang实时跟随 - 单语言站 / i18n 关闭时保持构建期静态值(
i18n.defaultLocale),零行为变化
applier 零配置,也不要在业务侧再用 useHead({ htmlAttrs: { lang } }) 双写 —— htmlAttrs 所有权在 framework,双写会互相覆盖。