国际化,也叫internationalization(i18n),基本上所有的软件都会涉及。一般都只涉及静态国际化,即国际化的内容事先已确定。但也有需要对动态数据国际化的时候,例如电子商务网站的用户希望上传多种语言的商品描述信息展现给不同语言的客户。如何在数据库中保存这些信息?一般来说有3种方法。

1. 每种语言一张表

为每个实体的每种语言建立一张表,以语言名称为表后缀。例如炉石传说卡牌为例

t_cards_en
+----+--------------+-----------+
| id | card_name    | mana_cost |
+----+--------------+-----------+
| 1  | Molten Giant | 20        |
| 2  | Deathwing    | 10        |
| 3  | Frost Giant  | 10        |
+----+--------------+-----------+

t_cards_cn
+----+-----------+-----------+
| id | card_name | mana_cost |
+----+-----------+-----------+
| 1  | 熔核巨人   | 20         |
| 2  | 死亡之翼   | 10         |
| 3  | 冰霜巨人   | 10         |
+----+----------+-------------+

这种方法违背了数据库设计的第三范式[任何非主属性不依赖于其它非主属性],两个表都包含了mana_cost这列,这是不要求国际化的属性。虽然我们也可以用ORM框架或者触发器保证数据同步更新,但还是十分蹩脚。

2. 只要一张语言表

把所有实体国际化的信息放在一张表里,通过外键引用。


t_cards
+-------------------+-----------+
| id | lang_id      | mana_cost |
+----+--------------+-----------+
| 1  | lang_1       | 20        |
| 2  | lang_2       | 10        |
| 3  | lang_3       | 10        |
+----+--------------+-----------+

t_language
+---------+-----------+--------------+
| id      | lang_code | content      |
+---------+-----------+--------------+
| lang_1  | en        | Molten Giant |
| lang_2  | en        | Deathwing    |
| lang_3  | en        | Frost Giant  |
| lang_1  | zh        | 熔核巨人       |
| lang_2  | zh        | 死亡之翼       |
| lang_3  | zh        | 冰霜巨人       |
+---------+-----------+--------------+

这种方法比起第一种要好一些,没有重复数据,t_language中也可以保存其他实体的国际化信息,content为了兼容所有字段一般会申明为TEXT(mysql)。这种方法也有弊端,t_language一个属性占一条记录,如果属性比较多会导致t_language急剧增大。

3. 中庸之道

一个实体两张表,一张表主键为id, 另一张表主键为id+lang_code保存国际化信息。t_card_ext可以拥有多列多语言属性(例子没体现这一点,明显地)。这种方法的好处显而易见,它比第二种的效率要稍微高一些,在更新n个多语言属性可以只有一条插入/更新操作,而第二种需要n条操作,而且表的规模要大的多(还包含其他实体的多语言属性)。这种设计使用ORM(比如hibernate onetomany/manytoone)框架实现起来非常简单,也更方便查看。

t_cards
+----------------+
| id | mana_cost |
+----+-----------+
| 1  | 20        |
| 2  | 10        |
| 3  | 10        |
+----+-----------+

t_card_ext
+---------+-----------+--------------+
| id      | lang_code | card_name    |
+---------+-----------+--------------+
| 1       | en        | Molten Giant |
| 2       | en        | Deathwing    |
| 3       | en        | Frost Giant  |
| 1       | zh        | 熔核巨人       |
| 2       | zh        | 死亡之翼       |
| 3       | zh        | 冰霜巨人       |
+---------+-----------+--------------+