Model translations

Laravel i18n provides a translatable model attributes solution making translatable attributes something really easy to implement.

Design

When you want to make translatable some attributes of a given model, a new table must be created in your database in order to persist the translations. By convention, that table name will be the same name as the given model table (in singular) adding the suffix _translations.

So, for example, if we want to add translatable attributes to the model Car which uses the table cars, a car_translations table will be created.

In this new table we'll persist all the translatable attributes.

Creating the migration

Laravel i18n provides a command for generating a boilerplate migration file:

php artisan i18n:translatable

This command will ask for which model in your App namespace do you want to create translatable attributes. Once you choose the model a migration file will be created in database/migrations folder.

This migration is ready to be applied. You just need to add the columns (attributes) which will be translatable. To do that, just open the file and add them in the place a comment indicates.

So, for example, let's say that I have the model Car which I want to make description attribute translatable. After call to php artisan i18n:generate translatable and choose the Car when the command ask for it, the migration 0000_00_00_000000_create_car_translations_table.php is generated. Now I should add the attributes I need:

    ....
    //TODO: You should add the model translatable attributes here.
    $table->text('description');

Then, we can apply the migration:

php artisan migrate

Making our model translatable

Once the translations table is created, we need to add the HasTranslations trait and add a custom cast in our Eloquent Model:

use Kodilab\LaravelI18n\Traits\HasTranslations;

class Car extends Model
{
    use HasTranslations;

    protected $casts = [
        ...
        'description' => 'translatable',
        ...
    ];
}

Is important to know that description attribute value will be taken from the translation table if you add this previous cast. That means if you have the duplicated an attribute name between the model table and translation table, the value will be taken from translation table and ignored from model table.

Translatable attribute methods

Those methods allows you manage attribute translations:

    /**
     * Saves (or updates) one translated attribute
     *
     * @param Locale $locale
     * @param string $attribute
     * @param string $translation
     */
    public function setTranslatedAttribute(Locale $locale, string $attribute, string $translation)

    /**
     * Saves (or updates) multiple translated attributes
     *
     * @param Locale $locale
     * @param array $translation
     */
    public function setTranslatedAttributes(Locale $locale, array $translation)

    /**
     * Returns whether an attribute is translated for a given locale
     *
     * @param Locale $locale
     * @param string $attribute
     * @return bool
     */
    public function isTranslated(Locale $locale, string $attribute)

    /**
     * Returns the translated attribute for a given locale
     *
     * @param Locale $locale
     * @param string $field
     * @return |null
     */
    public function getTranslatedAttribute(Locale $locale, string $field)

Translatable attribute forwarding

Instead of accessing to the translated values through the getTranslatedAttribute() method, you can do it as it was a Model attribute.

$locale = Locale::getLocale(i18n::getLocale()); // Loaded locale

$car->getTranslatedAttribute($locale, 'description') // It will return the loaded locale translation of description
$car->description // It will return the same result

When you use that feature, the locale used would be the locale set during the request. If the translation is not available for that locale, then fallback locale is used.

If your Model is already extending __get(string $name) method, then you need to add the following snippet in your __get(string $name) method in order to allow translatable attribute being forwarded:

public function __get($name)
{
    ....

    if ($this->isTranslatableAttribute($name)) {
        return $this->getTranslatedAttribute(
            Locale::getLocaleOrFallback(app('i18n')->getLocale(), 
            $name
        );
    }

    return parent::__get($name);
}