0%

大型企业级多语言平台设计:从翻译资源到前端交互

多语言平台不应该只是一个翻译人员填写文本的后台。对前端开发者来说,页面上的每段文案都应该能定位、能编辑、能预览、能发布;对最终用户来说,切换语言后应看到完整、自然且不破坏布局的界面。

一、前端多语言最容易遇到的问题

一个页面里经常同时存在按钮、提示、表格列、状态、错误信息和动态变量。如果文案直接写在 JSX、模板或脚本中,后续会出现:找不到来源、Key 重复、变量丢失、某个语言漏翻、修改后不知道影响了哪些页面。

更好的方式是让每个文本拥有三个身份:

1
2
3
显示文本:用户当前看到的内容
稳定 Key:代码和语言包使用的语义标识
文本 ID:多语言平台追踪、编辑和发布使用的唯一绑定

Key 适合开发,ID 适合平台追踪,显示文本适合用户。三者不要互相替代。

二、前端文本如何绑定多语言 ID

开发者新增文案时,可以通过组件或宏标记它属于多语言文本:

1
2
3
<I18nText id="text_1024" defaultValue="支付成功,订单号:{orderNo}">
{t("order.payment.success", { orderNo })}
</I18nText>

平台绑定记录可以保存来源页面、组件、文件和行号:

1
2
3
4
5
6
7
8
type TextBinding = {
textId: string;
key: string;
defaultValue: string;
module: string;
sourceFile?: string;
sourceLine?: number;
};

如果团队不希望业务代码手写 ID,也可以在开发环境第一次扫描文本时生成候选绑定,开发者确认后写入本地映射文件。映射文件需要纳入版本控制,避免同一段文案在不同分支生成不同 ID。

三、点击页面文本直接编辑

开发环境可以给带有 i18n 标识的文本增加轻量边界和编辑图标。用户点击文本后,打开一个编辑抽屉,显示文本 ID、Key、默认语言、当前语言、使用位置和发布状态;生产环境关闭这层能力,只保留正常文本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type I18nTextProps = {
id: string;
children: React.ReactNode;
};

export function I18nText({ id, children }: I18nTextProps) {
const editable = import.meta.env.DEV;
const openEditor = () => window.dispatchEvent(new CustomEvent("i18n:edit", { detail: { id } }));

return (
<span data-i18n-id={id} className={editable ? "i18n-editable" : undefined} onClick={editable ? openEditor : undefined}>
{children}
</span>
);
}

编辑抽屉不应该把用户带离当前页面。抽屉里修改并保存后,开发环境可以刷新当前文本;关闭抽屉后,用户仍然保留原来的滚动位置、筛选条件和表单状态。

四、文本绑定的实际操作流程

1
2
3
4
5
6
7
8
9
10
11
页面发现未绑定文本

开发模式显示“未绑定”标识

点击文本 → 打开绑定面板

选择已有 Text ID 或创建新文本

填写模块、描述、变量和默认文案

保存绑定 → 页面立即回显

已有文本应优先搜索复用,避免“订单已创建”和“订单创建成功”被重复录入。创建新文本时,平台应提示相似 Key、相同默认文案和可能冲突的变量。

五、Key、变量和上下文

Key 应表达语义而不是页面位置:

1
2
3
4
5
6
{
"key": "order.payment.success",
"value": "支付成功,订单号:{orderNo}",
"description": "订单完成支付后的成功提示",
"placeholders": ["orderNo"]
}

变量必须可校验,翻译人员不能误删:

1
2
3
function validatePlaceholders(source: string, target: string, names: string[]) {
return names.every((name) => source.includes(`{${name}}`) && target.includes(`{${name}}`));
}

上下文信息也很重要。同一个“关闭”可能是按钮、状态还是动作提示,平台应允许开发者附加说明、截图和使用位置,让翻译人员看到真实页面语境。

六、机翻应该如何接入

机翻适合生成初稿,不应该直接覆盖已发布语言。前端开发者或翻译人员选中文本后,可以选择目标语言和术语集发起任务:

1
2
3
4
5
6
7
const task = await i18nPlatform.translate({
textId: "text_1024",
sourceLocale: "zh-CN",
targetLocales: ["en-US", "fr-FR"],
context: "订单支付成功提示",
useGlossary: true,
});

任务面板要展示处理中、成功、失败、待人工确认四种状态;失败时允许重试或手工填写。批量机翻应支持逐条查看差异,不能只显示一个“已完成”数字。

七、翻译编辑和审核体验

翻译编辑器建议采用左右对照:左侧源文案和上下文,右侧目标语言编辑区,下方显示变量、术语提示、历史版本和字符长度。长文本、按钮文本和错误信息要有不同的长度提示。

1
2
3
源语言:支付成功,订单号:{orderNo}
目标语言:Payment successful. Order: {orderNo}
状态:机器初稿 → 人工修改 → 已确认 → 待发布

保存草稿不等于发布。翻译人员可以保存自己的修改,业务人员可以确认语义,发布人员选择需要进入版本的模块。每次修改都应显示前后差异,方便回退某条文本而不是整包撤销。

八、发布与前端实时回显

发布前提供预览环境,让用户直接切换语言查看真实页面。发布后生成一个可识别的版本号,前端开发环境可以手动切换或自动检测,生产环境按正式版本加载:

1
2
3
4
5
6
7
8
const release = await i18nPlatform.publish({
projectCode: "portal",
modules: ["order", "settings"],
locales: ["zh-CN", "en-US"],
changeNote: "更新订单模块文案",
});

await i18n.reload({ releaseId: release.id });

如果页面正在填写表单,语言刷新不能清空用户输入。应该只更新文本资源和需要重新渲染的文案,保留路由、滚动位置、筛选条件和表单状态。发布异常时,前端显示上一版本或默认语言,并提供重新加载入口。

九、语言切换的用户体验

语言切换入口应显示当前语言的本地名称,例如 中文(简体)EnglishFrançais,而不是只显示内部 Code。切换过程中显示轻量 Loading,完成后重新计算菜单、面包屑、标签页标题和页面文案。

1
2
3
4
5
6
7
8
9
async function changeLocale(locale: string) {
setLocaleLoading(true);
try {
await i18n.load(locale);
i18n.setLocale(locale);
} finally {
setLocaleLoading(false);
}
}

缺少目标语言时使用明确回退链:目标语言 → 默认语言 → Key。开发环境应显示缺失标识,生产环境可以展示默认语言,但要保留缺失记录,避免问题长期隐藏。

十、前端布局和多语言验收

不同语言会改变文本长度,因此需要把语言切换当成响应式测试的一部分:

1
2
3
4
5
6
7
8
□ 导航文本超长时省略并可查看完整内容
□ 按钮不会被长文本撑破
□ 表格列可以调整或横向滚动
□ 弹窗标题和表单错误可以换行
□ RTL 语言左右布局正确
□ 日期、数字、货币和时区格式正确
□ 占位符在每种语言中都保留
□ 切换语言不丢失页面状态

伪本地化可以把文本统一扩展为原长度的 1.5 倍,快速发现按钮、面包屑、菜单和弹窗的溢出问题。

小结

真正好用的多语言平台,应让开发者在页面上就能找到文本,让翻译人员在上下文中完成修改,让机翻成为可审核的初稿,让发布人员能够预览和控制版本,让用户在切换语言后仍然获得完整稳定的体验。文本 ID、i18n 标识、编辑抽屉、发布回显和布局验收,才是多语言前端落地时最值得投入的细节。

bulb