内容提要
文章提出数据库多语言设计方案:将翻译抽为独立子模式,用语言表、文本表、译文表统一管理业务文本与界面文字,业务表仅存文本编号。新增语言只需补数据,无需改表结构和代码。加列、JSON字段、每语言一行、每表配翻译表四种方案因冗余、绑定或维护问题均不推荐。
延伸解读
界面文案与业务文本:为何要分开处理
文章指出,界面文案总量有限,适合启动时全量加载到内存,运行时按 key 读取;而业务文本如文章标题、正文会随用户发布持续增长,且访问模式是低频、按需读取。若将两者混用同一套缓存策略,会互相拖累。因此,多语言数据库设计主要针对存储在数据库中的业务文本,界面文案可继续沿用内存 i18n 方案,两者并不冲突。
方案四的局限:每表配翻译表为何不够
方案四为每张业务表单独配翻译表,解决了数据冗余,方向正确。但它有两个明显短板:一是每张需要翻译的业务表都要配一张或多张翻译表,表数量随业务表增长而膨胀,维护成本高;二是菜单、按钮、错误提示等界面文字不属于任何业务表,没有外键可关联,无法用这套结构管理。因此它只解决了业务数据翻译,未覆盖界面文字。
保留原文字段的工程取舍
方案五建议业务表保留默认语言的原文副本,同时存 text_id 指向翻译中心。这样做虽引入冗余,但能简化日常查询、方便报表和调试,也便于翻译录入界面显示原文。关键是要守住两条原则:原文列只作为默认语言副本,不参与多语言展示逻辑;写入时必须在同一事务中同步更新业务表和文本表,防止数据漂移。若不想冗余,也可用视图替代,但下游系统需改为查视图。
界面文字的 key 选择:可读 key 与 hash 的权衡
界面文字没有天然主键,需额外定位方式。可读 key 直观、可搜索,但需人工维护,易命名不一致;原文 hash 无需维护 key、天然去重,但原文一旦修改 hash 即变,旧翻译会丢失,且对非母语开发者不友好,也无法直接表达带变量的文案。实际项目中,界面文字常用可读 key,数据库业务文本用 text_id,hash 更适合文案量大、团队同母语且不愿维护 key 的场景。
Q&A
为什么界面文案适合用内存 i18n 方案,而数据库业务文本不适合?
界面文案总量有限,几千条也只有几百 KB,启动时一次性加载到内存完全可行。但数据库业务文本(如文章、标题)会随业务动态增长,可能达到几十万字,全量加载不现实;且长文本与短文案的访问模式不同,混用同一套缓存策略会互相拖累。
给每种语言加一列(方案一)有什么缺点?
每支持一门新语言就要给表加一列并修改应用代码,导致表结构频繁变动;列名会失控,若有多语言字段,列数成倍增长;只适合极少数表,无法应对全应用多语言需求。
把翻译存进 JSON 字段(方案二)为什么不被推荐?
查询必须依赖特定数据库的 JSON 函数(如 OPENJSON、JSON_EXTRACT),导致查询与数据库绑定,换库需重写;数据库无法有效索引和约束 JSON 内部字段,数据质量全靠应用层保证。
方案三(每种语言单独存一条记录)的主要问题是什么?
同一业务对象每种语言存一行,导致与语言无关的字段(如导演、年份)被重复存储,产生数据冗余,一致性靠人工保证;查询变复杂,需要加 WHERE 条件或为每种语言建视图,维护成本高。
方案四(为每张业务表配翻译表)解决了什么问题,又留下什么不足?
解决了方案三的数据冗余问题,将翻译从业务表分离,主表只存与语言无关的数据。但每张需要翻译的业务表都要配翻译表,表数量多难维护;且无法处理菜单、按钮等界面文字的翻译,因为它们不属于任何业务表。
方案五(统一翻译中心)的核心思路是什么?
任何会出现在用户眼前的文字都不直接存在业务表里,业务表只存文本编号(text_id),真正的文字统一放在翻译中心,由语言表、文本表、译文表三张表管理。新增语言只需往语言表和译文表插数据,无需改表结构和代码。
在方案五中,为什么建议在业务表里保留原文字段(如 title)?
保留原文字段作为默认语言的副本,不参与多语言逻辑,但能让日常查询、报表、调试更方便,翻译录入界面也需要原文。代价是数据冗余,需遵守两个原则:该字段仅作默认语言副本,不用于多语言展示;写入时必须在同一事务中同步更新。
方案五中,界面文字如何定位和加载?
界面文字没有天然主键,可用人类可读的 key(如 menu.home)或原文的 hash 作为 key。应用启动时,将当前语言的所有界面文字一次性加载到内存,运行时按 key 取值,不再查数据库。新增语言只需补数据,代码不变。