最完整的Laravel多语言模型解决方案:laravel-translatable深度指南

最完整的Laravel多语言模型解决方案:laravel-translatable深度指南

【免费下载链接】laravel-translatable A Laravel package for multilingual models 【免费下载链接】laravel-translatable 项目地址: https://gitcode.com/gh_mirrors/lara/laravel-translatable

你是否还在为Laravel应用的多语言支持编写重复代码?是否在实现多语言模型时遇到数据库设计难题?是否在处理翻译验证时感到复杂繁琐?本文将通过10个实战章节,带你掌握laravel-translatable扩展包的全部核心功能,从安装配置到高级应用,从性能优化到常见问题,让你彻底告别多语言开发的痛点。

读完本文你将获得:

  • 多语言模型的最佳数据库设计实践
  • 5分钟快速实现模型翻译功能
  • 10+翻译相关的查询作用域(Scope)用法
  • 翻译表单提交与验证的优雅实现
  • 复杂场景下的翻译策略与性能优化技巧

为什么选择laravel-translatable?

在全球化应用开发中,多语言支持是核心需求之一。传统实现方式往往需要在模型中添加大量重复字段(如title_entitle_fr),导致数据库结构臃肿且难以维护。laravel-translatable采用分离翻译表的设计模式,将翻译内容存储在独立表中,通过优雅的API简化多语言模型的CRUD操作。

核心优势

传统方式laravel-translatable
需手动添加多语言字段自动管理翻译关系
重复的翻译处理代码统一的翻译API
复杂的多语言查询专用翻译查询作用域
难以维护的表单处理自动解析多语言表单数据
繁琐的翻译验证内置翻译验证规则

性能对比

mermaid

安装与配置

环境要求

laravel-translatable支持Laravel 9+和PHP 8.0+,完整版本兼容性如下:

Package版本Laravel版本PHP版本
v11.169.* / 10.* / 11.* / 12.*^8.0
v11.13 - v11.159.* / 10.* / 11.*^8.0
v11.128.* / 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');
});

为什么需要独立翻译表?

mermaid

独立翻译表的优势:

  • 支持无限种语言,无需修改表结构
  • 只存储实际需要的翻译,节省空间
  • 查询特定语言时更高效
  • 简化多语言数据迁移

模型实现

创建翻译模型

实现多语言模型需要创建两个类:主模型和翻译模型。

主模型(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应用的多语言支持实现。从数据库设计到表单处理,从查询优化到翻译验证,该扩展包提供了一站式解决方案。

最佳实践总结

  1. 始终使用withTranslation预加载翻译,避免N+1查询问题
  2. 对频繁访问的多语言内容实施缓存策略
  3. 使用RuleFactory简化翻译验证规则
  4. 对于大型应用,考虑禁用自动加载翻译,采用按需加载策略
  5. 复杂查询使用whereHas关联查询代替多次查询

未来发展方向

laravel-translatable持续活跃开发,未来版本可能包含:

  • 翻译审核工作流
  • 翻译版本控制
  • 与Laravel Scout的集成
  • 翻译导出/导入功能

掌握laravel-translatable,让你的Laravel应用轻松走向全球化!

如果觉得本文对你有帮助,请点赞、收藏并关注,以便获取更多Laravel高级开发技巧。下一篇我们将探讨laravel-translatable与Vue.js前端的无缝集成方案。

参考资料

【免费下载链接】laravel-translatable A Laravel package for multilingual models 【免费下载链接】laravel-translatable 项目地址: https://gitcode.com/gh_mirrors/lara/laravel-translatable

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值