国际化 i18n:vue-i18n 集成
组件库的文本不能写死中文——"发送"按钮、"暂无消息"空态、错误提示都需要支持多语言。这节用 Vue 生态最主流的 vue-i18n 方案完成集成,并把 i18n 纪律固化为 skill 与 rules,让后续新增组件自动遵守。
为什么需要 i18n
ai-chat-ui 是一个开源组件库,用户可能来自全球各地。硬编码中文文本会限制使用场景。需要让用户能:使用内置的中英文翻译、注入自定义语言、动态切换语言。
有了大模型之后,翻译本身不再是瓶颈——主流模型对中英文都非常友好,缺的是一个中枢工具:字典统一驱动所有文案,翻译工作交给模型批量完成。要做的只是把方案集成对、把目录组织好、把纪律固化住。
方案选型:直接用 vue-i18n
Vue 和 React 生态都有自己的主流 i18n 方案,组件库直接选生态内的主流方案(vue-i18n)即可,不重复造轮子。选它的理由:
- 组件库的通用性要求深度集成(不是十几个文案换一下就完),官方方案的插值、复数、fallback、类型支持都是长期能力
- 宿主零负担:库内自建实例(见下文),不要求宿主也安装或配置 vue-i18n
- 生态方案文档齐全,AI 工具(Context7 等)内置了它的文档,协作开发成本低
演进注记:为什么放弃了自研轻量方案。本课程早期版本曾自研过一套"翻译字典 + reactive + provide/inject"的轻量 i18n(createI18n/useI18n 各约 50 行),动机是零依赖、不强制宿主安装 vue-i18n。真实落地后转向 vue-i18n 模块级单例:自研要持续补插值/复数/fallback/类型推导这些 vue-i18n 已经解决好的问题,而"库内自建实例 + 直接引用 composer"同样做到了与宿主隔离、宿主零配置——自研的两大动机都不再成立。自研方案适合的场景只剩"宿主环境极端严格禁止额外依赖"。
版本现状:项目使用 vue-i18n ^11.4.8(2026-11-07 核实:v11 线最新 11.4.18 仍为 latest,v12 尚未转正);vue-i18n v12 已移除 Legacy API 模式,Composition API(Composer 实例)成为唯一支持模式(来源:vue-i18n 官方文档 migration/breaking11 与 Getting started)。本项目从一开始就用 legacy: false,升 v12 无迁移成本。
集成路径:把官方文档喂给模型
集成 vue-i18n 只需一句朴素的提示词,但喂文档的方式有两种:
- 整页安装文档复制发给大模型——直接、可靠
- 把文档链接作为上下文发送——配置了 local-search 这类 skill 时,模型可以快速爬取 HTML 页面了解项目用法;Context7 等 MCP 内置了主流前端框架的文档,也能做基础集成,但覆盖不如官方文档全面
提示词原文要点(朴素但每个短语都有用):
"在项目中设计并集成 vue-i18n,按照业界及官方的推荐来组织目录及相关的翻译文件,方便后续的更新与使用。后续创建 i18n skill,在创建新的组件及页面时自动调用并翻译。优先完成中文与英文,并设计语言切换(LanguageToggle)作为一个基础组件。"
"按照官方推荐组织目录"决定了后续所有维护成本;"创建 i18n skill 自动调用"把一次性集成分割成了"集成 + 可复用的纪律"两件事。
Plan 模式下的四个关键决策
复杂集成同样走 Plan 模式,模型会给出多个选项并附分析。四个决策点及选择依据:
1. 集成深度——深度集成(库内 + 应用层都用),不选可选依赖。组件库不做国际化,通用性大打折扣,没有中间态。
2. 本轮范围——playground + 组件库先覆盖,文档站全量后置。范围先收窄把 MVP 做完,文档站主要服务课程学员,后续要覆盖也是一句明确指令的事("把文档站 X 部分作为国际化范围")。
3. 字典组织——三种方案:全部放一个文件 / 按组件分目录 / 每语言单文件。跟官方推荐走:每语言单文件(locales/zh-CN.ts + locales/en-US.ts),现在组件有限、每文件要翻译的内容不多,等文件冗长了再按功能拆分——过早拆分反而增加维护成本。
4. 实例组织(最关键)——库内创建模块级单例实例并导出,组件内不用 useI18n(),而是直接引用实例的 composer。选择的判定句是模型给的:"与宿主自己的 vue-i18n 实例天然隔离"——这正是组件库要的。宿主自己也用 vue-i18n 时,两边实例互不干扰。
提醒:模型给出的多个选项不会都标着"推荐",也不一定都附详细分析。看不懂差异时,选最后一项让模型梳理各方案差异与优缺点,比盲选稳妥;实在拿不准,先按倾向做出来,与预期不符再重构——这类集成代码量不大,返工成本可控。
真实实现:模块级单例
// packages/vue/src/locales/index.ts
import { createI18n } from 'vue-i18n'
import zhCN from './zh-CN'
import enUS from './en-US'
export type { MessageSchema } from './zh-CN'
/**
* 库内模块级单例(与 useTheme 同模式):
* - 宿主零配置即用,不要求 app.use
* - 与宿主自有 vue-i18n 实例天然隔离
* - SSR 下 locale 跨请求共享,静态文档站(VitePress)可接受
*/
export const aiChatI18n = createI18n({
legacy: false,
locale: 'zh-CN',
fallbackLocale: 'zh-CN',
messages: { 'zh-CN': zhCN, 'en-US': enUS },
})
/** composer:组件内 `const { t } = aiChatI18n.global` 解构安全 */
export const composer = aiChatI18n.global
/** 切换语言:写 locale + 同步 <html lang> + 持久化(官方 setI18nLanguage 模式) */
export function setAiChatLocale(locale: string, opts: { persist?: boolean } = {}) {
composer.locale.value = locale as AiChatLocale
if (typeof document !== 'undefined') document.documentElement.lang = locale
if (opts.persist && typeof window !== 'undefined') {
localStorage.setItem(STORAGE_KEY, locale)
}
}
typescript
组件内的用法是 const { t } = aiChatI18n.global,不走 useI18n()——useI18n 要求组件处于 app.use(i18n) 的应用上下文里,库内单例直接引用 composer 不受此约束。
字典:zh-CN 是 source of truth,en-US 用类型锁死
// zh-CN.ts —— 不加 as const:MessageSchema = typeof zhCN 需要值为拓宽的 string,
// en-US 才能用 satisfies 通过(字面量类型会要求英文值 === 中文值)
const zhCN = {
promptInput: { placeholder: '给 AI Chat UI 发送消息...', send: '发送', /* ... */ },
thinking: { title: '思考过程', thoughtFor: '已思考 {duration} 秒', thinking: '正在思考…' },
// shared / conversation / message / toolCall / citation / attachments / comparison / markdown
}
export default zhCN
// en-US.ts
const enUS = { /* ... */ } satisfies MessageSchema // 从 './zh-CN' import type
typescript
en-US satisfies MessageSchema 让缺 key、写错 key 在编译期报错,双语同步不靠自觉。带变量的文案用 vue-i18n 插值 t('thinking.thoughtFor', { duration }),禁止字符串拼接——拼接无法进字典、无法翻译。
语言切换组件
LanguageToggle 做成 shared 基础组件,交互选下拉选择器(点击展开列表选择)而非图标点击循环切换——循环切换无法一眼看到有哪些语言可选。宿主也可传 locales 自定义选项列表、modelValue 受控。宿主注入自定义语言的路径:aiChatI18n.global.setLocaleMessage(locale, messages) 后 setAiChatLocale(locale)——注意 setLocaleMessage 是整体替换,合并场景要带上 getLocaleMessage(locale) 的原有内容。
纪律固化:i18n skill 与 rules
集成完成后,把开发流程固化下来(这正是提示词里"后续创建 i18n skill"的兑现)。skill 与 rules 的分工:
- skill 是能力:模型按需加载——只在"创建/修改组件、新增 UI 文案、新增语言"时被调用,不占用每次会话的上下文
- rules 是约束:每次对话都加载,像门禁一样逐条检查——代码门禁、文档同步门禁
i18n skill(.claude/skills/i18n/SKILL.md)五条纪律:
- UI 文案禁止硬编码:placeholder、title、aria-label、按钮/空态/错误文案全部进字典,组件内经
const { t } = aiChatI18n.global使用 - 双语同次提交:zh-CN.ts 与 en-US.ts 必须同时更新,en-US 保持
satisfies MessageSchema - key 命名:namespace = 组件域(shared/promptInput/conversation/message/toolCall/...),新组件建自己的 namespace,通用文案进 shared
- props 优先、t() 兜底:文本 props 不写死默认值,用
computed(() => props.title ?? t('xx.yy'));禁止 withDefaults 里给文本 props 中文默认值 - 文档同步门禁:新增/修改组件 API 后同步 VitePress 对应页面
提交前 checklist 只有三问:两份字典都有新文案吗?en-US satisfies 编译通过吗?没有新的硬编码文案吗?——很短,但直击要害。
Hooks 自动执行
更进一步:明确要求"i18n skill 必须在提交前、或开发了新的组件功能之后自动执行",模型把它注册进 hooks。效果是本轮开发结束时,没有人提示过要补文档,但文档站的国际化指南页、中英字典的组件域接入全部自动完成了——之前设置的钩子把该做的都做了。
项目里同时存在 Codex 时,脚本直接创建到 Codex 的配置里,同一套纪律在不同 AI 工具间复用。
小结
- 选型:直接用生态主流的 vue-i18n,库内模块级单例(
aiChatI18n)实现与宿主实例天然隔离、宿主零配置 - 四个决策:深度集成 / 范围先收窄(playground+库,文档后置)/ 每语言单文件(长了再拆)/ 直接引用 composer 不走 useI18n
- 字典纪律:zh-CN 为 source of truth,en-US
satisfies MessageSchema编译期锁同步,插值进字典禁止拼接 - 纪律固化:skill(按需加载的能力)+ rules(每次加载的约束)分工,hooks 让提交前检查自动化
- 版本前瞻:vue-i18n v12 移除 Legacy API,Composition API 是唯一模式;
legacy: false的写法升级零成本
下一节做组件 API 审查——用 AI 全面检查和打磨组件的 Props、Events、Slots 设计。
↑