igaster/laravel-translate-eloquent

v1.2.8 2019-10-25 13:37 UTC

This package is auto-updated.

Last update: 2020-08-25 15:57:51 UTC


README

Laravel License Downloads Build Status Codecov

Translate any column in your Database in Laravel models. You need only one additional table to store translations for all your models.

Installation

Edit your project's composer.json file to require:

"require": {
    "igaster/laravel-translate-eloquent": "~1.0"
}

and install with composer update

Setup

Step 1: Create Translation Table:

Create a new migration with artisan make:migration translations and create the following table:

    public function up()
    {
        Schema::create('translations', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('group_id')->unsigned()->index();
            $table->text('value')->nullable();
            $table->string('locale', 2)->index(); // Can be any length!
        });
    }

    public function down()
    {
        Schema::drop('translations');
    }

migrate the database: php artisan migrate

Step 2: Add translatable keys to you models

In your migrations define any number of integer keys that you want to hold translations. (Actually they are foreign key to the translatable.group_id). This is an example migration that will create a translatable key:

    $table->integer('key')->unsigned()->nullable();

Step 3: Setup your model:

Apply the TranslationTrait trait to any model that you want to have translatable keys and add these keys into the $translatable array:

class ExampleModel extends Eloquent
{
    use \igaster\TranslateEloquent\TranslationTrait;

    protected static $translatable = ['key'];
}

Now you are ready to use translated keys!

Usage

When you access a translatable key, then it's translation will be retrieved in the application's current locale. If no translation is defined then the Laravel's 'app.fallback_locale' will be used. If neither translation is found then an empty string will be returned. So simple!

Work with translations:

$model->key='Monday';                    // Set the translation for the current Locale.  
$model->key;                             // Get the translation for the current Locale

$model->translate('de')->key = 'Montag'; // Set translation in a locale
$model->translate('de')->key;            // Get translation in a locale
$model->translate('de','en')->key;       // Get translation in a locale / fallback locale

$model->key = [                          // Set a batch of translations
    'el' => 'Δευτέρα',
    'en' => 'Monday',
    'de' => 'Montag',
];

Important notes:

  • When you create a new translation for the first time, you must save your model to persist the relationship: $model->save();. This is not necessary when updating a translation or adding a new locale.
  • When you set a value for a translation then an entry in the translations table will be created / updated.

Create/Update translated models:

// Create a model translated to current locale
Day::create([
    'name' => 'Πέμπτη',
]);

// Create a model with multiple translations
Day::create([
    'name' => [
        'el' => 'Σάββατο',
        'en' => 'Saturday',
    ]
]);

You can also use $model->update(); with the same way.

Laravel & Locales:

A short refreshment in Laravel locale functions (Locale is defined in app.php configuration file):

App::setLocale('de');                    // Set curent Locale
App::getLocale();                        // Get curent Locale
Config::set('app.fallback_locale','el'); // Set fallback Locale

Working with the Translations object

You can achieve the same functionality with the igaster\TranslateEloquent\Translations object.

$translations = $model->translations('key'); // Get instance of Translations

$translations->in('de');             // Get a translation in a locale
$translations->set('el', 'Δευτέρα'); // Set a translation in a locale
$translations->has('el');            // Check if a translation exists

$translations->set([                 // Set a batch of translations
    'el' => 'Δευτέρα',
    'en' => 'Monday',
    'de' => 'Montag',
]);

Want to dive deeper into the internals?

  • Translations object holds all translations for a key. Multiple transltions are grouped under the same group_id value
  • Translation object is an Eloquent model (maps to the translations table) that represents a single translation for one key in one locale
$translations = new Translations();          // Create a new Translations collection
$translations = new Translations($group_id); // or load Translations with $group_id
$translations->group_id;  // column `translations.group_id` groups all translations for a key

$translation = $translations->get('en');  // Get instance of `Translation` (a single translation)
$translation->id;                   // From this model you have access to the actual translations record
$translation->value='New Value';    // in your database. You can perform any raw opperation on it.

Eager Loading:

You can use these query scopes as if you want to retrieve a translation with the same query:

Day::findWithTranslation(1,'name');   // Day with id 1 with 'name' translated in the current Locale
Day::firstWithTranslation('name');    // First Day from the query with 'name' translated in the current Locale
Day::getWithTranslation('name');      // Collection of Day with 'name' translated in the current Locale
Day::allWithTranslation('name');      // Collection of Day with 'name' translated in the current Locale

// You can specify a locale as an extra parameter in all above scopes:
Day::firstWithTranslation('name', 'en');    // First Day from the query with 'name' translated in English

// The column name is optional and defaults to first item in your `$translatable` array:
Day::firstWithTranslation();    // First Day from the query with the first $translatable column (='name') 
                                // translated in the current Locale

Notes:

  • The above query scopes should be used as an endpoint of your queries as they will return either a Model or a Collection
  • Eager loading is designed to reduce to a single query the read operations when you are retrieving a model from the Database. It uses a JOIN statement and not two subsequent queries as opposed to Eloquent eager loading. One limitation of this implementation is that you can only request the translation of a single field.
  • If your models have multiple keys that should be translated then all the subsequent read operations will result in an extra query.

Performance considerations:

Please notice that using a single Table for all translations is not the optimal architecture when considering database performance. Each translation requires one seperate query to the database. If performance is an issue you can check alternative implementations like dimsav/laravel-translatable

Handle __get() & __set() Conflicts:

This Trait makes use of the __get() and __set() magic methods to perform its ... well... magic! However if you want to implement these functions in your model or another trait then php will complain about conflicts. To overcome this problem you have to hide the Traits methods when you import it:

use igaster\TranslateEloquent\TranslationTrait {
    __get as private; 
    __set as private; 
}

and call them manually from your __get() / __set() methods:

//--- copy these in your model if you need to implement __get() __set() methods

public function __get($key) {
    // Handle Translatable keys
    $result=$this->translatable_get($key);
    if ($this->translatable_handled)
        return $result;

    //your code goes here
    
    return parent::__get($key);
}

public function __set($key, $value) {
    // Handle Translatable keys
    $this->translatable_set($key, $value);
    if ($this->translatable_handled)
        return;

    //your code goes here

    parent::__set($key, $value);
} 

Todo

  • Cascade delete model + translations Fixed
  • Eager Load multiple keys per request...
  • any ideas? Send me a request...