最完整的Laravel多语言模型解决方案:laravel-translatable深度指南
你是否还在为Laravel应用的多语言支持编写重复代码?是否在实现多语言模型时遇到数据库设计难题?是否在处理翻译验证时感到复杂繁琐?本文将通过10个实战章节,带你掌握laravel-translatable扩展包的全部核心功能,从安装配置到高级应用,从性能优化到常见问题,让你彻底告别多语言开发的痛点。
读完本文你将获得:
- 多语言模型的最佳数据库设计实践
- 5分钟快速实现模型翻译功能
- 10+翻译相关的查询作用域(Scope)用法
- 翻译表单提交与验证的优雅实现
- 复杂场景下的翻译策略与性能优化技巧
为什么选择laravel-translatable?
在全球化应用开发中,多语言支持是核心需求之一。传统实现方式往往需要在模型中添加大量重复字段(如title_en、title_fr),导致数据库结构臃肿且难以维护。laravel-translatable采用分离翻译表的设计模式,将翻译内容存储在独立表中,通过优雅的API简化多语言模型的CRUD操作。
核心优势
| 传统方式 | laravel-translatable |
|---|---|
| 需手动添加多语言字段 | 自动管理翻译关系 |
| 重复的翻译处理代码 | 统一的翻译API |
| 复杂的多语言查询 | 专用翻译查询作用域 |
| 难以维护的表单处理 | 自动解析多语言表单数据 |
| 繁琐的翻译验证 | 内置翻译验证规则 |
性能对比
安装与配置
环境要求
laravel-translatable支持Laravel 9+和PHP 8.0+,完整版本兼容性如下:
| Package版本 | Laravel版本 | PHP版本 |
|---|---|---|
| v11.16 | 9.* / 10.* / 11.* / 12.* | ^8.0 |
| v11.13 - v11.15 | 9.* / 10.* / 11.* | ^8.0 |
| v11.12 | 8.* / 9.* / 10.* | ^8.0 |
快速安装
通过Composer安装扩展包:
composer require astrotomic/laravel-translatable
发布配置文件:
php artisan vendor:publish --tag=translatable
配置详解
配置文件config/translatable.php包含关键设置:
return [
// 应用支持的语言环境
'locales' => [
'en',
'fr',
'es' => [
'MX', // 墨西哥西班牙语
'CO', // 哥伦比亚西班牙语
],
],
// 语言环境分隔符,用于子语言(如es-MX)
'locale_separator' => '-',
// 是否使用回退语言
'use_fallback' => true,
// 回退语言
'fallback_locale' => 'en',
// 翻译模型后缀
'translation_suffix' => 'Translation',
// 翻译验证规则工厂配置
'rule_factory' => [
'format' => \Astrotomic\Translatable\Validation\RuleFactory::FORMAT_ARRAY,
'prefix' => '%',
'suffix' => '%',
],
];
数据库设计
推荐的数据库结构
laravel-translatable采用主模型表+翻译表的设计模式,例如文章模型:
主表(posts)
Schema::create('posts', function(Blueprint $table) {
$table->increments('id');
$table->string('author'); // 非翻译字段
$table->timestamps();
});
翻译表(post_translations)
Schema::create('post_translations', function(Blueprint $table) {
$table->increments('id');
$table->integer('post_id')->unsigned(); // 关联主表ID
$table->string('locale')->index(); // 语言环境
$table->string('title'); // 翻译字段
$table->text('content'); // 翻译字段
// 联合唯一索引,确保一种语言只有一条翻译
$table->unique(['post_id', 'locale']);
// 外键约束,主表记录删除时级联删除翻译
$table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
});
为什么需要独立翻译表?
独立翻译表的优势:
- 支持无限种语言,无需修改表结构
- 只存储实际需要的翻译,节省空间
- 查询特定语言时更高效
- 简化多语言数据迁移
模型实现
创建翻译模型
实现多语言模型需要创建两个类:主模型和翻译模型。
主模型(Post.php)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Astrotomic\Translatable\Contracts\Translatable as TranslatableContract;
use Astrotomic\Translatable\Translatable;
class Post extends Model implements TranslatableContract
{
use Translatable;
// 需要翻译的字段
public $translatedAttributes = ['title', 'content'];
// 主表可填充字段(非翻译字段)
protected $fillable = ['author'];
}
翻译模型(PostTranslation.php)
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class PostTranslation extends Model
{
// 翻译模型不需要时间戳字段
public $timestamps = false;
// 翻译字段可填充
protected $fillable = ['title', 'content'];
}
自定义翻译模型
如果需要自定义翻译模型的位置或名称,可以在主模型中指定:
class Post extends Model implements TranslatableContract
{
use Translatable;
// 自定义翻译模型
protected $translationModel = App\Models\Translations\PostTranslation::class;
// 自定义翻译表外键
protected $translationForeignKey = 'post_id';
}
单表继承支持
对于单表继承(STI)场景,可以通过重写翻译外键实现:
class ChildPost extends Post
{
protected $table = 'posts';
protected $translationForeignKey = 'post_id'; // 使用父模型外键
}
基本用法
获取翻译
laravel-translatable提供多种方式获取翻译内容:
基本获取方式
$post = Post::find(1);
// 获取当前语言翻译
echo $post->title; // 使用app()->getLocale()
// 显式指定语言
echo $post->translate('en')->title; // 英文翻译
echo $post->translate('fr')->title; // 法文翻译
// 使用便捷语法
echo $post->{'title:en'}; // 英文标题
echo $post->{'content:fr'}; // 法文内容
翻译回退机制
当请求的语言不存在时,可以自动回退到默认语言:
// 配置文件中设置 'use_fallback' => true
$post = Post::find(1);
// 如果没有德文翻译,将返回英文翻译
echo $post->translate('de', true)->title;
// 便捷方法,自动使用回退
echo $post->translateOrDefault('de')->title;
创建翻译
创建带翻译的模型有多种方式:
批量创建
$post = Post::create([
'author' => 'John Doe',
'en' => [
'title' => 'My First Post',
'content' => 'This is the content'
],
'fr' => [
'title' => 'Mon premier article',
'content' => 'Ceci est le contenu'
]
]);
使用翻译包装器
在配置文件中设置translations_wrapper后,可以使用包装器语法:
// config/translatable.php 中设置
// 'translations_wrapper' => 'translations',
$post = Post::create([
'author' => 'John Doe',
'translations' => [
'en' => ['title' => 'My First Post'],
'fr' => ['title' => 'Mon premier article']
]
]);
更新翻译
更新翻译同样简单直观:
直接更新
$post = Post::find(1);
// 更新当前语言翻译
$post->title = 'Updated Title';
$post->save();
// 更新指定语言翻译
$post->translate('en')->title = 'Updated English Title';
$post->save();
// 批量更新多语言
$post->fill([
'en' => ['title' => 'Updated Title'],
'fr' => ['title' => 'Titre mis à jour']
]);
$post->save();
检查翻译是否存在
$post = Post::find(1);
if ($post->hasTranslation('en')) {
// 英文翻译存在
}
if ($post->hasTranslation('de')) {
// 德文翻译存在
}
删除翻译
$post = Post::find(1);
// 删除指定语言翻译
$post->deleteTranslations('fr');
// 删除多种语言翻译
$post->deleteTranslations(['fr', 'es']);
// 删除所有翻译
$post->deleteTranslations();
查询作用域(Scopes)
laravel-translatable提供了丰富的查询作用域,简化多语言查询。
翻译存在性查询
// 获取有翻译的模型
$posts = Post::translated()->get();
// 获取指定语言有翻译的模型
$englishPosts = Post::translatedIn('en')->get();
// 获取指定语言没有翻译的模型
$missingFrench = Post::notTranslatedIn('fr')->get();
翻译内容查询
精确查询
// 查询标题为"Hello"的英文文章
$posts = Post::whereTranslation('title', 'Hello', 'en')->get();
// 或使用当前语言
$posts = Post::whereTranslation('title', 'Hello')->get();
// 或查询多个语言
$posts = Post::whereTranslation('title', 'Hello', 'en')
->orWhereTranslation('title', 'Bonjour', 'fr')
->get();
模糊查询
// 模糊查询标题包含"first"的文章
$posts = Post::whereTranslationLike('title', '%first%')->get();
// 指定语言的模糊查询
$posts = Post::whereTranslationLike('title', '%premier%', 'fr')->get();
翻译排序
// 按英文标题升序排序
$posts = Post::orderByTranslation('title', 'asc')->get();
// 按法文内容降序排序
$posts = Post::orderByTranslation('content', 'desc', 'fr')->get();
翻译列表
// 获取ID和标题的翻译列表
$titles = Post::listsTranslations('title')->get()->toArray();
/*
返回结果:
[
['id' => 1, 'title' => 'My First Post'],
['id' => 2, 'title' => 'Introduction']
]
*/
预加载翻译
为避免N+1查询问题,可以预加载翻译:
// 预加载当前语言和回退语言的翻译
$posts = Post::withTranslation()->get();
// 预加载指定语言的翻译
$posts = Post::withTranslations(['en', 'fr'])->get();
表单处理
处理多语言表单提交是常见需求,laravel-translatable提供了两种优雅的解决方案。
数组语法表单
表单设计
<form method="POST" action="/posts">
@csrf
<!-- 非翻译字段 -->
<input type="text" name="author" value="{{ old('author') }}">
<!-- 英文翻译 -->
<input type="text" name="en[title]" value="{{ old('en.title') }}">
<textarea name="en[content]">{{ old('en.content') }}</textarea>
<!-- 法文翻译 -->
<input type="text" name="fr[title]" value="{{ old('fr.title') }}">
<textarea name="fr[content]">{{ old('fr.content') }}</textarea>
<button type="submit">保存</button>
</form>
控制器处理
public function store(Request $request)
{
$post = new Post();
$post->fill($request->all());
$post->save();
return redirect()->route('posts.show', $post);
}
冒号语法表单
表单设计
<form method="POST" action="/posts">
@csrf
<input type="text" name="author" value="{{ old('author') }}">
<!-- 冒号语法 -->
<input type="text" name="title:en" value="{{ old('title:en') }}">
<textarea name="content:en">{{ old('content:en') }}</textarea>
<input type="text" name="title:fr" value="{{ old('title:fr') }}">
<textarea name="content:fr">{{ old('content:fr') }}</textarea>
<button type="submit">保存</button>
</form>
控制器处理
public function store(Request $request)
{
// 无需特殊处理,fill方法自动解析冒号语法
$post = Post::create($request->all());
return redirect()->route('posts.show', $post);
}
翻译包装器语法
如果在配置中设置了翻译包装器:
// config/translatable.php
'translations_wrapper' => 'translations',
可以使用包装器语法提交表单:
<input type="text" name="translations[en][title]">
<input type="text" name="translations[fr][title]">
翻译验证
laravel-translatable提供了两种翻译验证方案:RuleFactory和自定义验证规则。
RuleFactory验证
RuleFactory可以自动为所有语言生成验证规则:
use Astrotomic\Translatable\Validation\RuleFactory;
public function rules()
{
return RuleFactory::make([
'translations.%title%' => 'required|string|max:255',
'translations.%content%' => 'required|string',
'author' => 'required|string|max:100',
]);
}
上述代码会自动生成以下规则(假设配置了en和fr语言):
[
'translations.en.title' => 'required|string|max:255',
'translations.fr.title' => 'required|string|max:255',
'translations.en.content' => 'required|string',
'translations.fr.content' => 'required|string',
'author' => 'required|string|max:100',
]
自定义RuleFactory配置
可以在运行时自定义RuleFactory行为:
// 自定义格式和分隔符
RuleFactory::make(
$rules,
RuleFactory::FORMAT_KEY, // 使用冒号格式
'{', '}', // 自定义分隔符
['en', 'fr'] // 指定语言
);
翻译唯一性验证
TranslatableUnique规则用于验证翻译字段的唯一性:
use Astrotomic\Translatable\Validation\Rules\TranslatableUnique;
public function rules()
{
return [
'title:en' => [
'required',
new TranslatableUnique(Post::class, 'title')
],
'title:fr' => [
'required',
new TranslatableUnique(Post::class, 'title')
],
];
}
也可以使用Rule门面:
use Illuminate\Validation\Rule;
public function rules()
{
return [
'title:en' => [
'required',
Rule::translatableUnique(Post::class, 'title:en')
],
];
}
翻译存在性验证
TranslatableExists规则用于验证翻译字段是否存在:
use Astrotomic\Translatable\Validation\Rules\TranslatableExists;
public function rules()
{
return [
'category:en' => [
'required',
new TranslatableExists(Category::class, 'name')
],
];
}
高级功能
翻译复制
replicateWithTranslations方法可以复制模型及其所有翻译:
// 复制模型和翻译
$post = Post::find(1);
$newPost = $post->replicateWithTranslations();
$newPost->author = 'New Author';
$newPost->save();
翻译数组转换
getTranslationsArray方法可以将翻译转换为数组:
$post = Post::find(1);
$translations = $post->getTranslationsArray();
/*
返回结果:
[
'en' => [
'title' => 'My First Post',
'content' => 'Content in English'
],
'fr' => [
'title' => 'Mon premier article',
'content' => 'Contenu en français'
]
]
*/
翻译自动加载控制
可以控制toArray()方法是否自动加载翻译:
// 全局配置
// config/translatable.php
'to_array_always_loads_translations' => false,
// 运行时控制
Post::enableAutoloadTranslations();
Post::disableAutoloadTranslations();
Post::defaultAutoloadTranslations();
模型默认语言
可以为模型实例设置默认语言:
$post = Post::find(1);
$post->setDefaultLocale('fr');
// 之后的操作将默认使用法语
echo $post->title; // 法语标题
性能优化
减少查询次数
使用withTranslation和withTranslations预加载翻译:
// 预加载当前语言和回退语言翻译
$posts = Post::withTranslation()->get();
// 预加载指定语言翻译
$posts = Post::withTranslations(['en', 'fr'])->get();
缓存翻译
对于频繁访问的多语言内容,建议使用缓存:
$posts = Cache::remember('posts_with_translations', 60, function () {
return Post::withTranslations(['en', 'fr'])->get();
});
禁用自动加载翻译
对于大型数据集,可以禁用自动加载翻译:
// 全局禁用
Post::disableAutoloadTranslations();
$posts = Post::all(); // 不包含翻译数据
// 需要时手动加载特定翻译
foreach ($posts as $post) {
$post->load('translations')->where('locale', app()->getLocale());
}
分语言查询
只查询特定语言的翻译:
$englishPosts = Post::whereHas('translations', function ($query) {
$query->where('locale', 'en')
->where('title', 'like', '%Laravel%');
})->get();
常见问题与解决方案
1. 迁移现有表到翻译表结构
将现有单表多语言结构迁移到翻译表:
// 1. 创建翻译表
Schema::create('post_translations', function(Blueprint $table) {
// ... 结构定义 ...
});
// 2. 迁移数据
DB::statement("INSERT INTO post_translations (post_id, title, content, locale)
SELECT id, title, content, 'en' FROM posts");
// 3. 删除原表中的翻译字段
Schema::table('posts', function($table) {
$table->dropColumn(['title', 'content']);
});
2. 处理翻译冲突与 trait 方法冲突
当与其他trait冲突时,可以重命名冲突方法:
class Post extends Model implements TranslatableContract
{
use Translatable {
Translatable::getAttribute as getTranslatableAttribute;
}
// 自定义getAttribute方法
public function getAttribute($key)
{
// 先尝试自定义逻辑
// ...
// 调用trait方法
return $this->getTranslatableAttribute($key);
}
}
3. 解决MySQL外键错误
如果迁移时出现外键错误,可能是因为表引擎不是InnoDB:
// 修复引擎问题
public function up()
{
// 先修改主表引擎
DB::statement('ALTER TABLE posts ENGINE=InnoDB');
// 再创建翻译表
Schema::create('post_translations', function(Blueprint $table) {
$table->engine = 'InnoDB';
// ... 字段定义 ...
});
}
4. 排序翻译结果
使用orderByTranslation作用域排序:
// 按英文标题升序排序
$posts = Post::orderByTranslation('title', 'asc', 'en')->get();
// 按当前语言标题降序排序
$posts = Post::orderByTranslation('title', 'desc')->get();
5. 按翻译字段搜索模型
使用whereTranslation作用域搜索:
// 搜索英文标题包含"Laravel"的文章
$posts = Post::whereTranslation('title', 'like', '%Laravel%', 'en')->get();
// 多语言搜索
$posts = Post::whereTranslation('title', 'like', '%Laravel%', 'en')
->orWhereTranslation('title', 'like', '%Laravel%', 'fr')
->get();
总结与展望
laravel-translatable通过简洁的API和强大的功能,彻底简化了Laravel应用的多语言支持实现。从数据库设计到表单处理,从查询优化到翻译验证,该扩展包提供了一站式解决方案。
最佳实践总结
- 始终使用withTranslation预加载翻译,避免N+1查询问题
- 对频繁访问的多语言内容实施缓存策略
- 使用RuleFactory简化翻译验证规则
- 对于大型应用,考虑禁用自动加载翻译,采用按需加载策略
- 复杂查询使用whereHas关联查询代替多次查询
未来发展方向
laravel-translatable持续活跃开发,未来版本可能包含:
- 翻译审核工作流
- 翻译版本控制
- 与Laravel Scout的集成
- 翻译导出/导入功能
掌握laravel-translatable,让你的Laravel应用轻松走向全球化!
如果觉得本文对你有帮助,请点赞、收藏并关注,以便获取更多Laravel高级开发技巧。下一篇我们将探讨laravel-translatable与Vue.js前端的无缝集成方案。
参考资料
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



