针对 500+ 键的 Vue 3 i18n 工作流,避免生产时出现惊喜
面向大型应用的实用 Vue 3 国际化工作流:键命名、复数规则、QA 检查与发布步骤,避免生产环境缺失翻译。

500+ 个 i18n 键时常出什么问题
当你的应用积累到几百条字符串时,第一个出问题的通常不是 Vue I18n,而是一致性。人们以不同风格添加键、把相同意图用不同名字重复写入,而且没人确定哪些消息可以安全删除。
缺失翻译也不再罕见。它们会出现在常见的用户路径中,尤其是较少使用的界面,比如设置、错误状态、空状态和通知。
当翻译缺失时,用户通常会看到三种失败之一:空白的界面(按钮没有标签)、显示原始键(比如 checkout.pay_now),或页面部分语言突然回退的怪异现象。无论是哪种,都不像是小问题——它们会让应用显得有缺陷。
这就是为什么一个 Vue 3 i18n 工作流比具体库更重要。库会按你要求去做,但在大规模下,团队常常对“完成”的定义不一致。
一个常见的例子:开发者发布了一个新的 “邀请队友” 流程,增加了 40 个字符串。英文文件更新了,但法语文件没有。在预发布环境中,一切看起来正常,因为测试者使用的是英语。而生产环境中,法语用户看到部分翻译、部分未翻译的界面,支持收到原始键的截图。
解决方法是明确定义“已完成”的含义。它不能只是“字符串已添加”。一个实用的完成定义通常包括:键遵守命名规则、各语言在构建时没有缺失键的警告、复数与变量在真实数据下能正确渲染、至少检查过一个非默认语言,以及跟踪文案变更以防旧键滞留。
当键数量超过 500 时,最好把本地化当作一个发布流程来对待,而不是最后一刻的文件修改。
在添加更多字符串前设定几条规则
当字符串超过几百条后,翻译本身不再是最混乱的部分,而是一致性。少量规则能让你的 Vue 3 i18n 工作流在多人每周触碰文案时仍保持可预测。
先决定什么叫“概念”,并为它保留一个单一的真实来源。如果相同的 UI 意图出现在五个地方(例如 “保存更改”),你希望只有一个键,而不是 save、saveChanges、save_update、saveBtn 这样的五个变体。重复的键会随着时间在含义上漂移,用户会感到不一致。
接着决定格式化由谁负责。团队常会无意中把格式拆开:有些消息包含标点和大小写,另一些则由代码添加。选定一种做法并坚持下去。
一个实用的默认做法:
- 将语法、标点和面向用户的格式(例如 “(可选)”)放在消息里。
- 将纯数据格式化留在代码中(日期、货币、单位),然后把结果传入 i18n。
- 对于姓名和计数使用占位符,而不是字符串拼接。
- 把消息里的 HTML 当作特例并制定明确规则(允许或不允许)。
然后定义所有权。决定谁可以添加新键、谁审查基础语言文案、谁批准其它语言。没有这些,字符串会匆忙添加却从未审查。
最后,选择并记录回退策略。如果键缺失,用户应该看到什么:键名、默认语言文本,还是一个安全的通用消息?在生产环境中,许多团队偏好回退到默认语言并记录日志,这样用户不会被阻塞,同时你还能收到问题信号。
如果你使用类似 AppMaster(Vue 3 Web UI 加真实后端代码)这样的生成器构建 Vue 3 应用,这些规则同样适用。把翻译当作产品内容处理,而不是“仅仅是开发文本”,就能避免大多数临近发布的惊喜。
保持可读性的键命名约定
当字符串超过几百条时,一致性是最大的放大器。选一种键风格(多数团队使用点路径,如 billing.invoice.title),并把它作为规则。混用点、斜杠、snake_case 和无规则的大小写会让搜索和审查变慢。
使用能经受文案变更的稳定键。像 “Please enter your email” 这样的键在营销改一句话时就会失效。优先使用以意图命名的键,比如 auth.email.required 或 auth.email.invalid。
先按产品区域或界面层级对键分组,再按用途划分。按你的应用已有的分类来考虑:auth、billing、settings、support、dashboard。这能让语言文件更易浏览,并减少当两个界面需要相同意图时出现重复。
在每个区域内,为常见的 UI 组件保留一小套模式:
- 按钮:
*.actions.save、*.actions.cancel - 标签:
*.fields.email.label、*.fields.password.label - 提示/帮助文本:
*.fields.email.hint - 错误/校验:
*.errors.required、*.errors.invalidFormat - 通知/吐司:
*.notices.saved、*.notices.failed
动态消息应说明哪个部分会变化,而不是说明如何变化。按意图为消息命名,并用参数表示可变部分。例如,billing.invoice.dueInDays 搭配 {days} 比 billing.invoice.dueIn3Days 更清晰。
示例(在 Vue 3 i18n 工作流中效果很好):orders.summary.itemsCount 使用 {count} 表示数量,orders.summary.total 使用 {amount} 表示金额。当开发者在代码中看到键时,应该能知道它所属的位置和用途,即使最终文案改变也不会迷失。
不出意外的复数规则与消息格式化
当你把每种语言都当成英语来处理时,复数文本会悄悄出问题。尽早决定何时使用 ICU 消息语法,何时只用简单占位符。
对不会随数量变化的短标签等,用简单替换(例如 “Welcome, {name}”)。对任何基于计数的文本,切换到 ICU,因为它能把所有形式放在一起,并显式表达规则。
{
"notifications.count": "{count, plural, =0 {No notifications} one {# notification} other {# notifications}}"
}
把计数消息写成便于翻译的完整句子,并将数字占位符(#)靠近名词。避免像在代码里重用同一个键去拼接 “1 item”和“2 items” 这样的巧妙做法。译者需要看到完整消息,而不是猜测如何拼接。
至少为 =0、one 和 other 做好规划,并记录你对 0 的期望。有些产品要显示 “0 items”,有些则要 “No items”。选定一种风格并坚持下去,这样界面会显得一致。
还要注意有些语言的复数类别比你预期的多。很多语言不遵循“单数 vs 复数”的简单规则。如果后来新增了某种语言,只有 one 和 other 的消息可能在语法上不正确,尽管它能渲染。
在发布前,用真实的计数在界面上测试复数,而不仅仅盯着 JSON。一个能捕捉大多数问题的快速检查是使用 0、1、2、5 和 21。
如果你在构建 Vue 3 Web 应用(例如在 AppMaster 中),在文字实际出现的屏幕上做此项测试。布局问题、截断文本和尴尬的断行会首先在那儿暴露出来。
为增长组织语言文件
当字符串达到几百条后,一个大的 en.json 会成为瓶颈。多人修改同一个文件会导致合并冲突增加,你也会失去对文案位置的把控。一个好的结构能让你的 Vue 3 i18n 工作流在产品持续变化时仍保持稳定。
建议的结构
对于 2 到 5 个语言,按功能拆分通常就够了。每个语言保持相同的文件布局,这样添加键就是可预测的编辑。
locales/en/common.json、locales/en/auth.json、locales/en/billing.jsonlocales/es/common.json、locales/es/auth.json、locales/es/billing.jsonlocales/index.ts(加载并合并消息)
对于 20+ 个语言,同样的思路要更严谨。把英语当作真理来源,强制每个语言镜像相同的文件夹和文件名。如果出现新域(例如 notifications),它应当出现在每个语言中,即便文本是临时的。
按域拆分能减少合并冲突,因为两个人可以在不同文件添加字符串而不会互相干扰。域应与应用的构建方式匹配:common、navigation、errors、settings、reports,以及较大功能的特定文件夹。
保持键一致
在每个文件内,跨语言保持相同的键形态:相同的嵌套、相同的键名、不同的文本。避免为每种语言使用“有创意”的键,即便某短语难以翻译。如果英语需要 billing.invoice.status.paid,每个语言都必须有这个精确的键。
仅将确实在各处重复的内容集中:按钮标签、通用校验错误和全局导航。把特定功能的文案保持在功能域附近,即便它看起来可复用。“保存”属于 common,而“保存付款方式”属于 billing。
长文本内容
长篇帮助文本、引导步骤和电子邮件模板会很快变得混乱。几条规则能帮忙:
- 把长文本放在自己的域(例如
help或onboarding),并避免过深的嵌套。 - 更倾向短段落而不是一个巨大的字符串,让译者更容易处理。
- 如果市场或支持经常编辑文案,把这些消息放在专门文件以减少其他地方的波动。
- 对于邮件,分别存储主题与正文,并保持占位符一致(姓名、日期、金额)。
这种设置让审查变更、持续翻译并在发布前避免意外缺失更容易。
添加与发布字符串的逐步工作流
稳定的 Vue 3 i18n 工作流更像是重复相同的小步骤,而不是工具本身。新的 UI 文本不应在没有键、默认消息和清晰翻译状态的情况下进入生产。
先把键添加到基础语言(通常是 en)。把默认文本写成真实的文案,而不是占位符。这给产品和 QA 可读内容审查,并防止出现“神秘字符串”。
在组件中使用该键时,从一开始就包含参数与复数逻辑,让译者能看到消息的完整形态。
// simple param
$t('billing.invoiceDue', { date: formattedDate })
// plural
$t('files.selected', count, { count })
如果翻译尚未准备好,不要让键缺失。可以在其它语言添加占位翻译,或以一致方式标记为待办(例如在值前加 TODO:)。重要的是应用能可预测地渲染,并且审查者能一眼看出哪些文案未完成。
在合并前,运行快速的自动化检查。保持这些检查枯燥且严格:跨语言缺失键、未使用键、占位符不匹配(缺少 {count}、{date} 或错误的大括号)、支持语言的复数形式非法、以及意外覆盖。
最后,在至少一个非基础语言做一个简短的界面检查。选一个字符串较长(通常是德语或法语)的语言来捕捉溢出、按钮被截断和尴尬换行。如果你的 Vue 3 UI 是生成的或和产品其他部分一起维护(例如由 AppMaster 生成的 Vue3 Web 应用),这一步依然重要,因为随着功能演进布局会改变。
把这些步骤当作任何新增文案功能的完成定义。
团队常犯的错误
把 i18n 键当作“仅仅是字符串”是让本地化变痛苦的最快方式。超过几百个键后,小习惯会演化成生产问题。
键漂移、冲突或误导
拼写错误和大小写差异是缺失文本的经典原因:一处写 checkout.title,另一处写 Checkout.title。代码审查时看起来没问题,但你的回退语言可能悄悄上线。
另一个常见问题是用同一个键承载不同含义。“Open” 在支持界面可能指 “打开工单”,在商店页面可能是 “立即开业”。如果复用同一个键,其他语言中其中一个界面会出错,译者只能猜测。
一个简单规则能帮忙:一键对应一含义。如果含义变了,就新建键,即使英文文本一样。
因“字符串形态”假设造成的布局错误
团队常把标点、空格或 HTML 的一部分硬编码到翻译中。这在某语言需要不同标点或你的 UI 对标记有不同处理时会失败。把标记决策放在模板中,消息集中在文本上。
移动端是问题藏匿的地方。英文中能放下的标签在德语中可能换行成三行,或在泰语中溢出。如果只测试一种屏幕尺寸,你会漏掉这些问题。
注意常见犯错:假设插入变量(姓名、计数、日期)时依旧是英语词序;用拼接片段而不是单条完整消息;没有测试长值(产品名、地址、错误详情);在开启静默回退时发布,这会让缺失键在英语环境下看起来“正常”;编辑时只改英文值却复制粘贴键。
如果你想要在 500+ 个键时依然保持冷静,把键当作 API 的一部分:稳定、具体并像其他代码一样经过测试。
能早期抓到缺失翻译的 QA 步骤
缺失翻译容易被忽视,因为应用看起来仍然“可用”。它只是回退到键、错误语言或空字符串。把翻译覆盖率当作测试:你要在任何东西进入生产前得到快速反馈。
自动化检查(每个 PR 运行)
从让构建失败的检查开始,而不是那些没人看的警告:
- 扫描代码库中的
$t('...')和t('...')用法,然后验证每个使用的键都存在于基础语言。 - 比较各语言的键集,如果某个语言缺少键则失败(除非该键在一个小的、经过审查的例外列表里,比如“仅 en 的法律说明”)。
- 标记存在于语言文件但未被使用的孤立键。
- 验证消息语法(占位符、ICU/复数区块)。一个损坏的消息可能在运行时导致页面崩溃。
- 把重复键或不一致大小写视为错误。
把例外列表保持简短并由团队维护,而不是“随便通过 CI 的东西”。
运行时与视觉检查(预发布)
即便有 CI,你仍然需要预发布环境作为安全网,因为真实的用户路径会触发你忘记的动态字符串。
在预发布环境启用缺失翻译日志,并包含足够的上下文以便快速修复:语言、路由、组件名(如可用)和缺失键。让日志足够嘈杂。如果容易被忽略,它就会被忽略。
添加伪本地化并用它做快速界面检查。一个简单方法是变换每个字符串(变长并加上标记),例如:[!!! 𝗧𝗲𝘅𝘁 𝗲𝘅𝗽𝗮𝗻𝗱𝗲𝗱 !!!],这样布局问题会跳出来,这能在发布前捕捉被截断的按钮、破碎的表头和缺失空格。
最后,在发布前对最重要的流程做一次短暂抽查:登录、结账/支付、核心设置以及你要发布的新功能。这是你会发现“已翻译但占位符错了”之类错误的地方。
处理新增语言与持续文案变更
把新增语言当作“文案工作”而不是一次小型产品发布会很麻烦。让应用即便在语言不完整时也能构建,同时在用户看到之前让差距明显,是保持 Vue 3 i18n 工作流稳定的最简单方法。
新增语言时,先生成与源语言(通常为 en)相同的文件结构,不要先翻译,先搭结构。
- 创建新语言的文件夹/文件并复制源语言的完整键集。
- 将值标记为 TODO 占位符,使缺失字符串在 QA 中可见。
- 仅在基础功能完成后才把该语言加入语言切换器。
- 做一次逐屏检查以捕捉布局问题(更长的单词、换行)。
翻译常常延迟到最后,所以事先决定“部分完成”对你的产品意味着什么。如果某功能面向客户,考虑使用功能开关以确保功能和其字符串一起发布。如果必须在未完全翻译的情况下发布,宁可清晰回退(如英语)而不是空标签,但要在预发布环境大声提示。
文案更新是团队破坏键的一大来源。如果你改了措辞,保持键不变。键应该描述意图,而不是确切句子。只有在含义改变时才重命名键,而且要以受控的迁移方式进行。
为避免“僵尸字符串”,有目的地标记弃用键:注明弃用日期和替代键,保留一个发布周期,然后在确认无任何引用后再删除。
把翻译注释保存靠近键。如果你的 JSON 格式不能包含注释,就把注释放在小型的配套文档或相邻的元数据文件中(例如 “用于结账成功页面”)。当你的 Vue 3 Web 应用由像 AppMaster 这样的工具生成,且多人触碰文案和 UI 时,这尤其有帮助。
示例:发布一个包含 60 个新字符串的功能
某次迭代,你们团队同时交付三件事:一个新的设置页、一个计费页面和三封电子邮件模板(收据、付款失败、试用到期)。大约会新增 60 条字符串,这通常就是混乱 i18n 的起点。
团队同意按功能再按界面分组键。为每个功能创建新文件,并在任何地方都遵循相同模式:feature.area.element。
// settings
"settings.profile.title": "Profile",
"settings.security.mfa.enable": "Enable two-factor authentication",
// billing
"billing.plan.current": "Current plan",
"billing.invoice.count": "{count} invoice | {count} invoices",
// email
"email.receipt.subject": "Your receipt",
"email.payment_failed.cta": "Update payment method"
在开始翻译之前,用真实值测试复数字符串,而不是猜测。对于 billing.invoice.count,QA 用 0、1、2 和 5 的数据来检查(可以用种子数据或简单的开发切换)。这能及早捕捉像 “0 invoice” 这样的尴尬情况。
然后 QA 运行一组有针对性的流程,容易暴露缺失键:打开设置和计费页面并逐个点击每个标签,使用测试账号在预发布环境触发每个邮件模板,在非默认语言下运行应用,并在日志中出现任何缺失翻译警告时使构建失败。
在这个迭代中,QA 发现了两个缺失键:计费界面上的一个标签和一封邮件的主题。团队在发布前修复了它们。
在决定阻塞发布还是允许回退时,他们使用一个简单规则:面向用户的界面和事务性邮件阻塞发布;低风险的仅管理员可见文本可以暂时回退到默认语言,但必须有工单和明确的截止日期。
下一步与一个简单的发布检查清单
当你把翻译当作代码一样对待时:以相同方式添加、测试并记录它,Vue 3 i18n 工作流就会保持稳定。
发布检查清单(合并前 5 分钟)
- 键:遵循命名模式,范围清晰(页面、功能、组件)。
- 复数:在至少一种有多种复数规则的语言中确认复数形式正确渲染。
- 占位符:验证变量存在、在各语言命名一致,并在真实数据下看起来正确。
- 回退:确认缺失键的行为符合当前策略。
- 屏幕:抽查最可能出问题的几个界面(表格、吐司、模态、空状态)。
决定测量什么(让问题早出现)
挑几项简单的数字并定期复查:测试运行中的缺失键率、非默认语言中未翻译字符串数,以及未使用键数(不再出现在任何地方的字符串)。如果可能,按发布跟踪这些指标,这样你能看到趋势而不是孤立故障。
把添加字符串、更新翻译和验证结果的确切步骤写下来。保持简短,并包含键名和复数用法的示例。新贡献者应能在不提问的情况下跟着做。
如果你构建内部工具,AppMaster (appmaster.io) 可以是将 UI 文案与翻译键在 Vue3 Web 应用和配套移动应用中保持一致的实用方式,因为一切都在一个地方管理。
每个迭代安排一项小的 i18n 健康任务:删除死键、修复不一致的占位符并复查近期漏翻。小规模清理比生产中的紧急翻译搜索更有效。


