优先用翻译表,别图省事上json字段——除非确定永远只读、不搜索、不校验、不走AR关联、不考虑MySQL 5.7以下兼容性。

Yii2 多语言字段用 json 还是翻译表?先说结论
优先用翻译表,别图省事上 json 字段——除非你确定永远只读、不搜索、不校验、不走 AR 关联、不考虑 MySQL 5.7 以下兼容性。
为什么 json 字段在 Yii2 里容易翻车
表面上看,json 字段写法简单:title_json 存 {"zh-CN":"标题","en-US":"Title"},读的时候 $model->title_json['en-US'] 就行。但实际跑起来问题一堆:
-
json字段无法被 MySQL 索引(5.7+ 支持生成列索引,但 Yii2 AR 默认不识别,得手写virtual generated column+ 显式映射) - AR 层没法对
json字段做原生验证(比如要求zh-CN必填、en-US长度 ≤100),只能靠 PHP 层手动validate(),错失数据库层约束 - 关联查询失效:你想查「所有英文标题含 “Admin” 的文章」,
where得写JSON_CONTAINS(title_json, '"Admin"', '$."en-US"'),Yii2ActiveQuery不原生支持这种写法,得andWhere(['like', new \yii\db\Expression('JSON_UNQUOTE(JSON_EXTRACT(title_json, "$.en-US"))'), 'Admin']),又臭又容易漏转义 - 迁移和备份麻烦:
json字段内容不可读,diff 差异难定位;mysqldump 出来是转义字符串,人工检查成本高
翻译表怎么建才不踩坑
标准做法是拆出 article_i18n 表,主键为 (article_id, locale),字段含 title、content 等翻译字段。关键点在 Yii2 的集成方式:
- 主模型(如
Article)不存任何翻译字段,只保留id、created_at等通用字段 - 翻译模型(如
ArticleI18n)必须定义复合主键:primaryKey()返回['article_id', 'locale'],否则save()会报Invalid Configuration – yii\base\InvalidConfigException - 关联必须用
hasOne+viaTable或显式hasOne指向翻译模型,并在getI18n()方法里传入当前语言:return $this->hasOne(ArticleI18n::class, ['article_id' => 'id'])->onCondition(['locale' => \Yii::$app->language]); - 批量更新时别直接
$model->i18n->title = 'xxx',要先确保$model->i18n已加载(with('i18n'))且非 null;否则得先findOrCreateI18n()手动处理
临时用 json 字段的底线配置
如果真要上 json(比如 CMS 后台小功能、原型阶段),至少守住这三条线:
- 数据库字段类型必须是
JSON(MySQL 5.7+)或TEXT(低版本),并在 migration 中加注释说明:"// json field for i18n fallback, not for search" - 模型里用
afterFind和beforeSave自动处理序列化:public function afterFind()里把$this->title_json = json_decode($this->title_json, true) ?: [];,beforeSave反向json_encode - 禁止在
rules()里对title_json做required或string校验——校验逻辑移到validate()方法里,针对具体 locale 检查键存在性和格式
最常被忽略的是 locale 键名一致性:前端传 zh,后端存 zh-CN,中间没统一转换,结果一半数据查不到。这个点没自动化兜底,全靠开发时手抖一次,线上就丢翻译。


















