Translate any column in your Database in Laravel models. You need only one additional table to store translations for all your models.
Edit your project's composer.json
file to require:
"require": {
"igaster/laravel-translate-eloquent": "~1.0"
}
and install with composer update
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
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();
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!
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!
$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 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.
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
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 samegroup_id
valueTranslation
object is an Eloquent model (maps to thetranslations
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.
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.
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
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);
}
Cascade delete model + translationsFixed- Eager Load multiple keys per request...
- any ideas? Send me a request...