终极指南:Spatie Laravel Translatable让Eloquent模型翻译从未如此简单
你还在为多语言应用中的模型翻译编写繁琐的数据库结构和访问逻辑吗?还在为如何优雅地获取和设置不同语言的模型属性而头疼吗?本文将带你深入探索Spatie Laravel Translatable扩展包,彻底解决这些痛点。读完本文后,你将能够:
- 掌握模型翻译的核心原理与实现方式
- 熟练使用各种翻译获取与设置方法
- 灵活运用查询作用域筛选多语言模型
- 解决嵌套JSON属性翻译的复杂问题
- 避免在多语言开发中常见的性能陷阱
为什么选择Spatie Laravel Translatable?
在全球化应用开发中,多语言支持是必不可少的功能。传统实现模型翻译的方式通常有两种:
| 实现方式 | 优点 | 缺点 |
|---|---|---|
| 独立翻译表 | 符合数据库范式,查询单一语言性能好 | 需维护额外表结构,关联查询复杂,增加数据库负担 |
| 多列存储 | 实现简单,查询直观 | 扩展性差,新增语言需修改表结构,不支持动态语言 |
| JSON字段存储 | 无需额外表结构,支持动态语言,查询灵活 | 需处理JSON解析,复杂查询可能影响性能 |
Spatie Laravel Translatable采用JSON字段存储方案,通过trait方式为Eloquent模型提供翻译能力,完美平衡了灵活性、性能和开发体验。该扩展包在GitHub上获得了超过3500星标,被广泛应用于生产环境,是Laravel生态中处理模型翻译的首选解决方案。
快速上手:安装与基础配置
环境要求
- Laravel 8.0+
- PHP 7.4+
- MySQL 5.7+ 或其他支持JSON字段的数据库
安装步骤
# 通过Composer安装包
composer require spatie/laravel-translatable
# 如需手动注册服务提供者(Laravel 5.5+支持自动发现)
# 在config/app.php的providers数组中添加
Spatie\Translatable\TranslatableServiceProvider::class,
数据库迁移准备
为模型添加翻译字段,使用JSON类型:
// 创建文章表迁移示例
Schema::create('news_items', function (Blueprint $table) {
$table->id();
$table->json('name'); // 翻译字段
$table->json('description')->nullable(); // 可为空的翻译字段
$table->json('meta')->nullable(); // 可用于嵌套翻译的JSON字段
$table->timestamps();
});
模型配置
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Spatie\Translatable\HasTranslations;
class NewsItem extends Model
{
use HasTranslations;
// 指定可翻译的属性
public $translatable = ['name', 'description', 'meta->title', 'meta->description'];
// 可选:禁用回退语言
public $useFallbackLocale = false;
// 可选:自定义回退语言
public function getFallbackLocale(): string
{
return 'en';
}
}
核心功能:翻译的获取与设置
设置翻译的5种方式
1. 直接赋值(当前语言)
$newsItem = new NewsItem();
$newsItem->name = 'Breaking News'; // 使用应用当前语言
$newsItem->save();
2. 数组赋值(多语言)
NewsItem::create([
'name' => [
'en' => 'Breaking News',
'es' => 'Noticias Importantes',
'fr' => 'Actualités Importantes'
],
'description' => [
'en' => 'Latest developments in technology',
'es' => 'Últimos avances en tecnología',
'fr' => 'Dernières avancées en technologie'
]
]);
3. setTranslation方法(指定语言)
$newsItem->setTranslation('name', 'de', 'Breaking News auf Deutsch')
->setTranslation('name', 'nl', 'Breaking Nieuws')
->save();
4. setTranslations方法(批量设置)
$translations = [
'en' => 'Hello World',
'es' => 'Hola Mundo',
'fr' => 'Bonjour Monde'
];
$newsItem->setTranslations('name', $translations)->save();
5. 嵌套JSON属性翻译
// 设置嵌套属性翻译
$newsItem->setTranslation('meta->title', 'en', 'Latest Technology News')
->setTranslation('meta->description', 'en', 'Stay updated with tech trends')
->save();
// 直接访问嵌套翻译属性
echo $newsItem->{'meta->title'}; // 输出当前语言的title
获取翻译的6种方式
1. 直接访问(当前语言)
app()->setLocale('es');
echo $newsItem->name; // 输出西班牙语翻译
2. getTranslation方法(指定语言)
// 获取指定语言翻译,默认使用回退语言
echo $newsItem->getTranslation('name', 'fr');
// 不使用回退语言
echo $newsItem->getTranslation('name', 'ar', false);
// 方法别名
echo $newsItem->translate('name', 'de');
3. 获取所有翻译
// 获取单个属性的所有翻译
$allNameTranslations = $newsItem->getTranslations('name');
// 输出: ['en' => 'Breaking News', 'es' => 'Noticias Importantes', ...]
// 获取所有属性的所有翻译
$allTranslations = $newsItem->getTranslations();
// 输出: ['name' => [...], 'description' => [...], ...]
// 使用访问器
$allTranslations = $newsItem->translations;
4. 获取特定语言集合
// 只获取指定语言的翻译
$selectedTranslations = $newsItem->getTranslations('name', ['en', 'fr']);
// 输出: ['en' => 'Breaking News', 'fr' => 'Actualités Importantes']
5. 检查翻译是否存在
if ($newsItem->hasTranslation('name', 'en')) {
// 英语翻译存在
}
// 获取模型已有的所有语言
$availableLocales = $newsItem->locales();
// 输出: ['en', 'es', 'fr']
6. 带回退机制的翻译获取流程
高级查询:多语言模型的筛选与检索
按语言存在性筛选
// 获取所有有英语名称的新闻
$englishNews = NewsItem::whereLocale('name', 'en')->get();
// 获取所有有英语或法语名称的新闻
$multiLingualNews = NewsItem::whereLocales('name', ['en', 'fr'])->get();
按翻译内容筛选
// 获取名称为"Breaking News"的英文新闻
$specificNews = NewsItem::whereJsonContainsLocale('name', 'en', 'Breaking News')->get();
// 使用模糊匹配
$matchingNews = NewsItem::whereJsonContainsLocale('name', 'en', 'Breaking%', 'like')->get();
// 多语言内容匹配
$globalNews = NewsItem::whereJsonContainsLocales('name', ['en', 'fr'], 'Breaking%', 'like')->get();
性能优化:只选择需要的翻译字段
// 只选择特定语言的翻译字段
$newsItems = NewsItem::select(
'id',
DB::raw("JSON_EXTRACT(name, '$.en') as name_en"),
DB::raw("JSON_EXTRACT(name, '$.fr') as name_fr")
)->get();
常见问题与解决方案
问题1:处理缺失的翻译
// 全局配置处理缺失翻译
// 在AppServiceProvider中
use Spatie\Translatable\Translatable;
public function boot()
{
Translatable::fallbackLocale('en');
Translatable::allowNullForTranslation(); // 允许返回null
Translatable::allowEmptyStringForTranslation(); // 允许返回空字符串
// 设置缺失翻译回调
Translatable::missingKeyCallback(function($model, $key, $locale, $value, $normalizedLocale) {
// 记录缺失翻译日志
Log::warning("Missing translation: {$key} in {$locale} for {$model->getMorphClass()}:{$model->id}");
// 可选:返回自定义内容
return "[Missing translation: {$key} ({$locale})]";
});
}
问题2:翻译属性的表单验证
use Illuminate\Validation\Rule;
$request->validate([
'name.en' => 'required|string|max:255',
'name.es' => 'required|string|max:255',
'name.fr' => 'nullable|string|max:255',
// 嵌套属性验证
'meta.title.en' => 'required|string|max:100',
'meta.description.en' => 'required|string|max:500',
]);
// 保存验证后的翻译
$newsItem->fill($request->validated())->save();
问题3:翻译事件监听
// 监听翻译设置事件
Event::listen(TranslationHasBeenSetEvent::class, function (TranslationHasBeenSetEvent $event) {
// $event->model - 模型实例
// $event->key - 属性名称
// $event->locale - 语言
// $event->oldValue - 旧值
// $event->newValue - 新值
// 可以在这里触发翻译缓存清除、通知等操作
});
性能优化指南
1. 避免N+1查询问题
// 不佳:会导致N+1查询
$newsItems = NewsItem::all();
foreach ($newsItems as $item) {
echo $item->getTranslation('name', 'en');
}
// 良好:预加载翻译(实际上是同一查询)
$newsItems = NewsItem::all(); // JSON字段已包含所有翻译
2. 只获取需要的语言数据
// 使用查询作用域只选择特定语言的翻译
class NewsItem extends Model
{
use HasTranslations;
public function scopeWithSpecificLocales($query, array $locales)
{
foreach ($this->translatable as $field) {
foreach ($locales as $locale) {
$query->addSelect(DB::raw("JSON_UNQUOTE(JSON_EXTRACT({$field}, '$.{$locale}')) as {$field}_{$locale}"));
}
}
return $query;
}
}
// 使用
$newsItems = NewsItem::withSpecificLocales(['en', 'fr'])->get();
3. 缓存翻译结果
use Illuminate\Support\Facades\Cache;
class NewsItem extends Model
{
// ...
public function getTranslation(string $attributeName, string $locale, bool $useFallbackLocale = true)
{
$cacheKey = "translation_{$this->getMorphClass()}_{$this->id}_{$attributeName}_{$locale}";
return Cache::remember($cacheKey, now()->hourly(), function () use ($attributeName, $locale, $useFallbackLocale) {
return parent::getTranslation($attributeName, $locale, $useFallbackLocale);
});
}
}
最佳实践总结
1. 模型设计
- 合理规划可翻译字段,避免过度翻译
- 对长文本内容考虑使用单独的翻译表存储
- 嵌套JSON属性翻译使用
->语法明确定义
2. 语言管理
- 定义应用支持的语言列表常量
- 使用配置文件统一管理回退语言策略
- 实现翻译缺失监控与报警机制
3. 性能考量
- 避免在循环中反复调用翻译方法
- 对热门内容实施翻译缓存
- 复杂查询使用原生JSON函数优化
4. 代码组织
学习资源与工具推荐
官方资源
相关扩展包
- spatie/laravel-translation-loader: 数据库驱动的翻译管理
- spatie/laravel-package-tools: 简化Laravel扩展包开发
开发工具
- Laravel Debugbar: 调试JSON翻译字段
- PHPStorm JSON插件: 提供JSON翻译字段自动完成
总结与展望
Spatie Laravel Translatable通过简洁而强大的API,彻底改变了Laravel应用中处理多语言模型的方式。它摒弃了传统复杂的数据库结构,采用JSON字段存储翻译,既简化了开发流程,又保持了良好的性能和灵活性。
从基本的翻译获取与设置,到高级的查询作用域和事件监听,这个扩展包提供了全方位的功能支持。无论是小型博客还是大型企业应用,都能从中获益。
随着全球化应用的普及,多语言支持将变得越来越重要。未来,我们可以期待更多AI辅助翻译功能的集成,以及更智能的翻译缓存和优化策略。
掌握Spatie Laravel Translatable,让你的应用轻松走向世界!
如果你觉得这篇文章有帮助,请点赞、收藏并关注,以便获取更多Laravel高级开发技巧。下一期我们将探讨如何构建多语言CMS系统的完整解决方案。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



