Multilingual with Laravel Eloquent

Hiện nay, yêu cầu hỗ trợ đa ngôn ngữ đã trở lên phổ biến hơn và có nhiều framework hỗ trợ việc hiển thị đa ngôn ngữ. Bằng việc tạo ra các file, thư mục chứa nội dung đa ngôn ngữ ứng với các key, laravel cho phép lập trình viên thể hiện trang web của mình bằng nhiều ngôn ngữ khác nhau. Tuy nhiên việc này chỉ giới hạn trong các thành phần cố định như: title, button, message. Đối với việc hiển thị nôi dung hay text thì laravel chưa hỗ trợ. Tuy nhiên, cũng không quá khó khăn để thực hiện việc này bằng tay.

Đầu tiên, chúng ta tạo một migration chưa hỗ trợ multilingual:

Schema::create('articles', function (Blueprint $table) {
   $table->increments('id');
   $table->string('name');
   $table->text('text');
   $table->timestamps();
});

Cách đơn giản nhất để bảng articles này hỗ trợ nhiều ngôn ngữ là chúng ta thêm các cột tương ứng cho các ngôn ngữ mà chúng ta muốn hỗ trợ. Ví dụ, chúng ta muốn hỗ trợ 2 ngôn ngữ là EnglishTiếng Việt:

Schema::create('articles', function (Blueprint $table) {
   $table->increments('id');
   $table->string('name_en');
   $table->text('text_en');
   $table->string('name_vi');
   $table->text('text_vi');
   $table->boolean('online');
   $table->timestamps();
});

Bây giờ, bảng này đã hộ trợ cho việc lưu text với 2 ngôn ngữ là EnglishTiếng Việt. Đây là cách đơn giản nhất tuy nhiên nó sẽ gặp phải nhiều vấn đề khi chúng ta muốn thêm một hoặc nhiều ngôn ngữ mới, chúng ta phải thay đổi tất cả các bảng cần hỗ trợ đa ngôn ngữ.

Có một cách tốt hơn để thực hiện việc này là chúng ta lưu các trường cần được translate vào một bảng khác với flag chứa locale tương ứng.

Schema::create('articles', function (Blueprint $table) {
    $table->increments('id');
    $table->boolean('online');
    $table->timestamps();
});

Schema::create('article_translations', function (Blueprint $table) {
    $table->increments('id');
    $table->integer('article_id')->unsigned();
    $table->string('locale')->index();

    $table->string('name');
    $table->text('text');

    $table->unique(['article_id','locale']);
    $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade');
});

Với cách trên chúng ta không cần phải thêm bất kì trường (field) nào vào cơ sở dữ liệu nếu chúng ta cần hỗ trợ thêm ngôn ngữ nào khác.

Cột locale sử dụng để xác định ngôn ngữ của record trong bảng.

Chúng ta đã setup xong cơ sở dữ liệu cho các dữ liệu đa ngôn ngữ. Tiếp theo chúng ta cần lấy chúng ra tương ứng với từng ngôn ngữ.

// app/Article.php
class Article extends Model
{
    ...
    public function articleTranslations() 
    {
        $this->hasMany(ArticleTranslation::class, `article_id`);
    }

    public function getTranslation($locale = 'en')
    {
        return $this->articleTranslations()->where('locale', $locale)->first();
    }
    ...
}

// Somewhere
$vietnameseText = $article->getTranslation('vi')->text;

Câu lệnh ở trên cho phép chúng ta lấy dữ liệu với ngôn ngữ vi, cách này đã khá là ổn, tuy nhiên nó vẫn khá dài.

Chúng ta không muốn lặp đi lặp lại việc sử dụng phương getTranslation() ở trên. Rất may mắn, chúng ta có cách khác đơn giản và tiện dụng hơn để lấy các dữ liệu đa ngôn ngữ bằng việc sử dụng Translatable Trait được viết bởi Dimitris Savvopoulos.

Thêm package vào file composer.json bằng lệnh:

composer require dimsav/laravel-translatable

Tiếp thep, chúng ta thêm service provider vào file app/config/app.php:

// app/config/app.php
    'providers' => [
         ...
         Dimsav\Translatable\TranslatableServiceProvider::class,
    ]
    ...

Sau khi thêm service provider, chúng ta cần cập nhật lại các model để sử dụng Translatable Trait, các model cần phải có property $translatableAttributes chứa các field đa ngôn ngữ:

// app/Article.php
...
use Dimsav\Translatable\Translatable:

class Article extends Model
{
    use Translatable;

    public $translatedAttributes = ['name', 'text'];

    ...
}

// app/ArticleTranslation.php
class ArticleTranslation extends Model
{
    public $timestamps = false;

    ...
}
app()->setLocale('vi');
$vietnameseText = $article->text;

Các bạn có thể thể thấy không có gì khác việc chúng ta lấy dữ liệu khác trong bảng như $article->online.

Route::get('{locale}', function($locale) {
   app()->setLocale($locale);

   $article = Article::first();

   return view('article')->with(compact('article'));
});

Trong view article.blade.php:

<h1>{{ $article->name }}</h1>
{{ $article->text }}

Done! Vậy là chúng ta đã thực hiện xong việc lưu trữ và lấy các dữ liệu đa ngôn ngữ bằng eloquent 😄