Code Monkey home page Code Monkey logo

laravel-translatable's Introduction

This package has been deprecated. But worry not. You can use Astrotomic/laravel-translatable.


Laravel-Translatable

Total Downloads Build Status Code Coverage Latest Stable Version License SensioLabsInsight StyleCI

Laravel Translatable

If you want to store translations of your models into the database, this package is for you.

This is a Laravel package for translatable models. Its goal is to remove the complexity in retrieving and storing multilingual model instances. With this package you write less code, as the translations are being fetched/saved when you fetch/save your instance.

Docs

Demo

Getting translated attributes

  $greece = Country::where('code', 'gr')->first();
  echo $greece->translate('en')->name; // Greece
  
  App::setLocale('en');
  echo $greece->name;     // Greece

  App::setLocale('de');
  echo $greece->name;     // Griechenland

Saving translated attributes

  $greece = Country::where('code', 'gr')->first();
  echo $greece->translate('en')->name; // Greece
  
  $greece->translate('en')->name = 'abc';
  $greece->save();
  
  $greece = Country::where('code', 'gr')->first();
  echo $greece->translate('en')->name; // abc

Filling multiple translations

  $data = [
    'code' => 'gr',
    'en'  => ['name' => 'Greece'],
    'fr'  => ['name' => 'Grèce'],
  ];

  $greece = Country::create($data);
  
  echo $greece->translate('fr')->name; // Grèce

Laravel compatibility

Laravel Translatable
5.8 9.*
5.7 9.*
5.6 9.*
5.5 8.*
5.4 7.*
5.3 6.*
5.2 5.5 - 6.*
5.1 5.0 - 6.*
5.0 5.0 - 5.4
4.2.x 4.4.x
4.1.x 4.4.x
4.0.x 4.3.x

Tutorials

Installation in 4 steps

Step 1: Install package

Add the package in your composer.json by executing the command.

composer require dimsav/laravel-translatable

Next, add the service provider to app/config/app.php

Dimsav\Translatable\TranslatableServiceProvider::class,

Step 2: Migrations

In this example, we want to translate the model Country. We will need an extra table country_translations:

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

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

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

Step 3: Models

  1. The translatable model Country should use the trait Dimsav\Translatable\Translatable.
  2. The convention for the translation model is CountryTranslation.
// models/Country.php
class Country extends Eloquent {
    
    use \Dimsav\Translatable\Translatable;
    
    public $translatedAttributes = ['name'];
    protected $fillable = ['code'];
    
    /**
     * The relations to eager load on every query.
     *
     * @var array
     */
    // (optionaly)
    // protected $with = ['translations'];

}

// models/CountryTranslation.php
class CountryTranslation extends Eloquent {

    public $timestamps = false;
    protected $fillable = ['name'];

}

The array $translatedAttributes contains the names of the fields being translated in the "Translation" model.

Step 4: Configuration

We copy the configuration file to our project.

Laravel 5.*

php artisan vendor:publish --tag=translatable 

Laravel 4.*

php artisan config:publish dimsav/laravel-translatable

Note: There isn't any restriction for the format of the locales. Feel free to use whatever suits you better, like "eng" instead of "en", or "el" instead of "gr". The important is to define your locales and stick to them.

Configuration

The config file

You can see the options for further customization in the config file.

The translation model

The convention used to define the class of the translation model is to append the keyword Translation.

So if your model is \MyApp\Models\Country, the default translation would be \MyApp\Models\CountryTranslation.

To use a custom class as translation model, define the translation class (including the namespace) as parameter. For example:

<?php 

namespace MyApp\Models;

use Dimsav\Translatable\Translatable;
use Illuminate\Database\Eloquent\Model as Eloquent;

class Country extends Eloquent
{
    use Translatable;

    public $translationModel = 'MyApp\Models\CountryAwesomeTranslation';
}

Features list

Please read the installation steps first, to understand what classes need to be created.

Available methods

// Before we get started, this is how we determine the default locale.
// It is set by laravel or other packages.
App::getLocale(); // 'fr' 

// To use this package, first we need an instance of our model
$germany = Country::where('code', 'de')->first();

// This returns an instance of CountryTranslation of using the default locale.
// So in this case, french. If no french translation is found, it returns null.
$translation = $germany->translate();

// It is possible to define a default locale per model by overriding the model constructor.
public function __construct(array $attributes = [])
{
    parent::__construct($attributes);
    
    $this->defaultLocale = 'de';
}

// It is also possible to define a default locale for our model on the fly:
$germany->setDefaultLocale('de');

// If an german translation exists, it returns an instance of 
// CountryTranslation. Otherwise it returns null.
$translation = $germany->translate('de');

// If a german translation doesn't exist, it attempts to get a translation  
// of the fallback language (see fallback locale section below).
$translation = $germany->translate('de', true);

// Alias of the above.
$translation = $germany->translateOrDefault('de');

// Returns instance of CountryTranslation of using the default locale.
// If no translation is found, it returns a fallback translation
// if enabled in the configuration.
$translation = $germany->getTranslation();

// If an german translation exists, it returns an instance of 
// CountryTranslation. Otherwise it returns null.
// Same as $germany->translate('de');
$translation = $germany->getTranslation('de', true);

// To set the translation for a field you can either update the translation model.
// Saving the model will also save all the related translations.
$germany->translate('en')->name = 'Germany';
$germany->save();

// Alternatively we can use the shortcut
$germany->{'name:en'} = 'Germany';
$germany->save();

// There are two ways of inserting mutliple translations into the database
// First, using the locale as array key.
$greece = $country->fill([
    'en'  => ['name' => 'Greece'],
    'fr'  => ['name' => 'Grèce'],
]);

// The second way is to use the following syntax.  
$greece = $country->fill([
    'name:en' => 'Greece',
    'name:fr' => 'Grèce',
]);

// Returns true/false if the model has translation about the current locale. 
$germany->hasTranslation();

// Returns true/false if the model has translation in french. 
$germany->hasTranslation('fr');

// If a german translation doesn't exist, it returns
// a new instance of CountryTranslation.
$translation = $germany->translateOrNew('de');

// Returns a new CountryTranslation instance for the selected
// language, and binds it to $germany
$translation = $germany->getNewTranslation('it');

// The eloquent model relationship. Do what you want with it ;) 
$germany->translations();

// Remove all translations linked to an object
$germany->deleteTranslations();

// Delete one or multiple translations
$germany->deleteTranslations('de');
$germany->deleteTranslations(['de', 'en']);

// Gel all the translations as array
$germany->getTranslationsArray();
// Returns
[
 'en' => ['name' => 'Germany'],
 'de' => ['name' => 'Deutschland'],
 'fr' => ['name' => 'Allemagne'],
];

// Creates a clone and clones the translations
$replicate = $germany->replicateWithTranslations(); 

Available scopes

// Returns all countries having translations in english
Country::translatedIn('en')->get();

// Returns all countries not being translated in english
Country::notTranslatedIn('en')->get();

// Returns all countries having translations
Country::translated()->get();

// Eager loads translation relationship only for the default
// and fallback (if enabled) locale
Country::withTranslation()->get();

// Returns an array containing pairs of country ids and the translated
// name attribute. For example: 
// [
//     ['id' => 1, 'name' => 'Greece'], 
//     ['id' => 2, 'name' => 'Belgium']
// ]
Country::listsTranslations('name')->get()->toArray();

// Filters countries by checking the translation against the given value 
Country::whereTranslation('name', 'Greece')->first();

// Filters countries by checking the translation against the given value, only in the specified locale
Country::whereTranslation('name', 'Greece', 'en')->first();

// Or where translation
Country::whereTranslation('name', 'Greece')->orWhereTranslation('name', 'France')->get();

// Filters countries by checking the translation against the given string with wildcards
Country::whereTranslationLike('name', '%Gree%')->first();

// Or where translation like
Country::whereTranslationLike('name', '%eece%')->orWhereTranslationLike('name', '%ance%')->get();

Magic properties

To use the magic properties, you have to define the property $translatedAttributes in your main model:

class Country extends Eloquent {

    use \Dimsav\Translatable\Translatable;

    public $translatedAttributes = ['name'];
}
// Again we start by having a country instance
$germany = Country::where('code', 'de')->first();

// We can reference properties of the translation object directly from our main model.
// This uses the default locale and is the equivalent of $germany->translate()->name
$germany->name; // 'Germany'

// We can also quick access a translation with a custom locale
$germany->{'name:de'} // 'Deutschland'

Fallback

Fallback locales

If you want to fallback to a default translation when a translation has not been found, enable this in the configuration using the use_fallback key. And to select the default locale, use the fallback_locale key.

Configuration example:

return [
    'use_fallback' => true,

    'fallback_locale' => 'en',    
];

You can also define per-model the default for "if fallback should be used", by setting the $useTranslationFallback property:

class Country {

    public $useTranslationFallback = true;

}

Fallback per property

Even though we try having all models nicely translated, some fields might left empty. What's the result? You end up with missing translations for those fields!

The property fallback feature is here to help. When enabled, translatable will return the value of the fallback language for those empty properties.

The feature is enabled by default on new installations. If your config file was setup before v7.1, make sure to add the following line to enable the feature:

'use_property_fallback' => true,

Of course the fallback locales must be enabled to use this feature.

If the property fallback is enabled in the configuration, then translatable will return the translation of the fallback locale for the fields where the translation is empty.

customize empty translation property detection

This package is made to translate strings, but in general it's also able to translate numbers, bools or whatever you want to. By default a simple empty() call is used to detect if the translation value is empty or not. If you want to customize this or use different logic per property you can override isEmptyTranslatableAttribute() in your main model.

protected function isEmptyTranslatableAttribute(string $key, $value): bool
{
    switch($key) {
        case 'name':
            return empty($value);
        case 'price':
            return !is_number($value);
        default:
            return is_null($value);
    }
}

Country based fallback

Since version v5.3 it is possible to use country based locales. For example, you can have the following locales:

  • English: en
  • Spanish: es
  • Mexican Spanish: es-MX
  • Colombian Spanish: es-CO

To configuration for these locales looks like this:

    'locales' => [ 
        'en',
        'es' => [
            'MX',
            'CO',
        ],
    ];

We can also configure the "glue" between the language and country. If for instance we prefer the format es_MX instead of es-MX, the configuration should look like this:

   'locale_separator' => '_',

What applies for the fallback of the locales using the en-MX format?

Let's say our fallback locale is en. Now, when we try to fetch from the database the translation for the locale es-MX but it doesn't exist, we won't get as fallback the translation for en. Translatable will use as a fallback es (the first part of es-MX) and only if nothing is found, the translation for en is returned.

Translation Autoloading

If the toArray() method is called it's possible to autoload all translations. To control this feature the package comes with a config value to_array_always_loads_translations and three static methods in the trait:

  • enableAutoloadTranslations() - forces to load all translations
  • disableAutoloadTranslations() - disables autoload and returns parent attributes
  • defaultAutoloadTranslations() - does not change the default behavior logic (default)

Add ons

Thanks to the community a few packages have been written to make usage of Translatable easier when working with forms:

FAQ

I need some example code!

Examples for all the package features can be found in the code used for the tests.

I need help!

Got any question or suggestion? Feel free to open an Issue.

I want to help!

You are awesome! Watch the repo and reply to the issues. You will help offering a great experience to the users of the package. #communityWorks

Also buy me a beer by making a donation. ❤️

I am getting collisions with other trait methods!

Translatable is fully compatible with all kinds of Eloquent extensions, including Ardent. If you need help to implement Translatable with these extensions, see this example.

How do I migrate my existing table to use laravel-translatable?

Please see the installation steps to understand how your database should be structured.

If your properties are written in english, we recommend using these commands in your migrations:

// We insert the translation attributes into the fresh translated table: 
\DB::statement("insert into country_translations (country_id, name, locale) select id, name, 'en' from countries");

// We drop the translation attributes in our main table: 
Schema::table('countries', function ($table) {
    $table->dropColumn('name');
}); 

How do I sort by translations?

A tip here is to make the MySQL query first and then do the Eloquent one.

To fetch a list of records ordered by a translated field, you can do this:

SELECT * from countries
JOIN country_translations as t on t.country_id = countries.id 
WHERE locale = 'en'
GROUP BY countries.id
ORDER BY t.name desc

The corresponding eloquent query would be:

Country::join('country_translations as t', function ($join) {
        $join->on('countries.id', '=', 't.country_id')
            ->where('t.locale', '=', 'en');
    }) 
    ->groupBy('countries.id')
    ->orderBy('t.name', 'desc')
    ->with('translations')
    ->get();

How can I select a country by a translated field?

For example, let's image we want to find the Country having a CountryTranslation name equal to 'Portugal'.

Country::whereHas('translations', function ($query) {
    $query->where('locale', 'en')
    ->where('name', 'Portugal');
})->first();

You can find more info at the Laravel Querying Relations docs.

Why do I get a mysql error while running the migrations?

If you see the following mysql error:

[Illuminate\Database\QueryException]
SQLSTATE[HY000]: General error: 1005 Can't create table 'my_database.#sql-455_63'
  (errno: 150) (SQL: alter table `country_translations` 
  add constraint country_translations_country_id_foreign foreign key (`country_id`) 
  references `countries` (`id`) on delete cascade)

Then your tables have the MyISAM engine which doesn't allow foreign key constraints. MyISAM was the default engine for mysql versions older than 5.5. Since version 5.5, tables are created using the InnoDB storage engine by default.

How to fix

For tables already created in production, update your migrations to change the engine of the table before adding the foreign key constraint.

public function up()
{
    DB::statement('ALTER TABLE countries ENGINE=InnoDB');
}

public function down()
{
    DB::statement('ALTER TABLE countries ENGINE=MyISAM');
}

For new tables, a quick solution is to set the storage engine in the migration:

Schema::create('language_translations', function(Blueprint $table){
  $table->engine = 'InnoDB';
  $table->increments('id');
    // ...
});

The best solution though would be to update your mysql version. And always make sure you have the same version both in development and production environment!

Donations

This software has been crafted with attention and love.

Show your love and support by sending bitcoin to this address: 167QC4XQ3acgbwVYWAdmS81jARCcVTWBXU

Or by sending to this PayPal address: [email protected]

❤️ Thank you!

laravel-translatable's People

Contributors

actuallymab avatar ahmed-aliraqi avatar alfhen avatar amaelftah avatar askello avatar barryvdh avatar ben-nsng avatar deargonaut avatar derekmd avatar dimsav avatar easteregg avatar elmatella avatar garygreen avatar gummibeer avatar hannesvdvreken avatar hyleeh avatar javichito avatar jhm-ciberman avatar johannesschobel avatar lan0 avatar m13z avatar plivius avatar plmarcelo avatar rigor789 avatar sdebacker avatar sebastiandedeyne avatar shadowalker89 avatar tintamarre avatar voidgraphics avatar yakidahan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-translatable's Issues

When deleting a translated model where child have a FK Restrict, model is not deleted but translations are deleted

According to the schema below, if I delete a menu, the DB stop the deletion because of the FK in menulinks but menu_translations are deleted. It's because laravel-translatable delete translations first.
Why not let the DB delete translations by setting ->onDelete('cascade'); on the FK ?

Here is the schema :

Schema::create('menus', function(Blueprint $table)
{
    $table->increments('id')->unsigned();
    $table->string('name');
});
Schema::create('menu_translations', function(Blueprint $table)
{
    $table->increments('id')->unsigned();
    $table->integer('menu_id')->unsigned();
    $table->string('locale')->index();
    $table->string('title');
    $table->unique(array('menu_id', 'locale'));
    $table->foreign('menu_id')->references('id')->on('menus');
});
Schema::create('menulinks', function(Blueprint $table)
{
    $table->increments('id')->unsigned();
    $table->integer('menu_id')->unsigned();
    $table->foreign('menu_id')->references('id')->on('menus');
});
Schema::create('menulink_translations', function(Blueprint $table)
{
    $table->increments('id')->unsigned();
    $table->integer('menulink_id')->unsigned();
    $table->string('locale')->index();
    $table->string('title');
    $table->unique(array('menulink_id', 'locale'));
    $table->foreign('menulink_id')->references('id')->on('menulinks');
});

Validation of translated model with Ardent

Hi there

I just got Translatable working with Ardent with the help of your gist but I'm facing an issue.

To use your example with Country, both Country and CountryTranslation would extend Ardent. I would like to validate the name of the country (say, max 100 characters). For that I would configure the validator in CountryTranslation. I'm able to save translations when the name is valid. So far so good.

If I do something like this:

$country->translate('en')->name = 'a very long name with more than 100 chars';
$country->save();

Then CountryTranslation doesn't save (which is right, since it's not valid). However, I'm unable to get the error messages. If I do $country->errors()->all(), the list of errors is empty. Also, $country->validate() returns true, which is weird.

Do you have any idea about this? That's probably not an issue with Translatable itself, just the way it integrates with Ardent.

Editing multiple translations in forms (form::model)

I use forms to update translatable fields in my database. Bellow example shows some Company's Project's properties that can be created/updated. Administrator can insert all translations at once (already working). The issue becomes with editing. This code snipped is used for creating and editing. When editing I use {{ form::model($project) }}

The iterating part (I need to make this prettier) looks like this:

@foreach ($company->languages as $language)
<div class="form-group">
    <div class="col-sm-4 col-sm-push-3">
        <h4>{{ $language->name }}</h4>
    </div>
</div>
<div class="form-group">
    {{ Form::label($language->locale . '[title]', 'Title (nice name):', ['class' => 'col-sm-3 control-label']) }}
    <div class="col-sm-4">
        {{ Form::input('text', $language->locale . '[title]', null, ['class' => 'form-control']) }}
    </div>
</div>

<div class="form-group">
    {{ Form::label($language->locale . '[description]', 'Description:', ['class' => 'col-sm-3 control-label']) }}
    <div class="col-sm-4">
        {{ Form::textarea($language->locale . '[description]', null, ['class' => 'form-control']) }}
    </div>
</div>
@endforeach

Inserting the data works. I simply call Project::create(Input::all()) and project data with translations are in the database. When editing the translated fields do not get populated. I'm not sure if this plugin has this support yet or I just used bad naming convention (it works when inserting data though). I know $project->title works, but for editing all translations I need those values as well.

p.s.
Thank you for this package. I really appreciate your work.

Traits and composer.json php version

Hello,

Just found your cool package on packagist and went trough documentation and composer.json and saw that documentation asks for Traits, but composer.json requires only php=>5.3.0.

Doesn't Traits are 5.4+ php version implementation?

Sorry if you got some hack there with closures or something (haven't tried actual package) that i didn't see.

But regardless, i find this package awesome and surely will use it :)

Fetching model by translated field

I have a model Article with an ArticleTranslation model to store its translations. ArticleTranslation has a field called 'slug' which I would like to use to navigate to my blog articles. Something like example.com/blog/slug-to-an-article-in-english. (same approach as somewhat every blog site out there right). Now when someone hits that URL, I would have to fetch an article object based on the translated slug.

So I have this in my controller for that:

public function showArticle($slug) 
{
    $article = Article::where('slug', '=', $slug)->first();
    if(!is_object($article)) App::abort(404);

    return \View::make('blog::blog.show')->with('article', $article);
}

When I run that query I get the following error:
"Unknown column 'slug' in 'where clause' "

The problem is that by using the Model::where only allows me to search into the Model itself but not by fields the translated model. I know I could run a query on the ArticleTranslation model, but that would only return an ArticleTranslation object and any fields in the Article model itself would be inaccessible (unless I use additional queries). Would it be possible to add a method that allows you to query on the joined table of a Model + it's ModelTranslation?

In my example that would result in a query like:
select * from articles inner join article_translations on articles.id = article_translations.article_id where slug = 'my-slug' and locale = 'current locale';

Logical problem with protected useTranslationFallback = true;

I'm not exactly sure how this one can be resolved, so let's think about it together:

I understand the intention of useTranslationFallback is to be able to retrieve (read) some meaningful translation of an item even if the item hasn't been translated into the target language yet. However, consider the following line inside class Dimsav\Translatable\Translatable:

18    $withFallback = isset($this->useTranslationFallback) ? $this->useTranslationFallback : $withFallback;

Also consider the rest of the code in context:

public function getTranslation($locale = null, $withFallback = false)
{
    $locale = $locale ?: App::getLocale();
    $withFallback = isset($this->useTranslationFallback) ? $this->useTranslationFallback : $withFallback;

    if ($this->getTranslationByLocaleKey($locale))
    {
        $translation = $this->getTranslationByLocaleKey($locale);
    }
    elseif ($withFallback
        && App::make('config')->has('app.fallback_locale')
        && $this->getTranslationByLocaleKey(App::make('config')->get('app.fallback_locale'))
    )
    {
        $translation = $this->getTranslationByLocaleKey(App::make('config')->get('app.fallback_locale'));
    }
    else
    {
        $translation = $this->getNewTranslationInstance($locale);
        $this->translations->add($translation);
    }

    return $translation;
}

If you follow the code presuming that useTranslationFallback == true, then there is no way to create a new translation for a given new locale.

To further illustrate the problem, let's say I have a widget where the Description attribute is translatable. And let's say I have it translated into english and spanish.

NOTE: At this point, a french translation does not exist in the widgetTranslations table.

Now, if I try to save a french translation with useTranslationFallback == true in my widget model, I cannot save a french translation. Rather, it overwrites whatever the default locale is; in my case, english.

// The following code will save "Some french text." as the Description for whatever the default locale is.
$widget = My\Namespace\Widget::find(1);
$widget->translate('fr')->Description = "Some french text.";
$widget->save();

I think the intended behavior is that when we are reading from the database, we want the Description to resolve to something meaningful, like the default locale translation. However, if I'm writing to the database, I want to be able to save my new translation.

Does this make sense?

Given the current architecture of the package, I'm not sure how to overcome this problem. The ideal goal would be to use the useTranslationFallback variable only when reading, not when writing. However, since you must decide to instantiate a new instance or not before you know whether you're reading or writing, the useTranslationFallback feature might not actually be able to work as intended.

orderBy

Hi,
how do i orderBy on translated attributes?

I have tried Category::translate(App::getLocale()) and

public function scopeTranslated($query) {
    return $query->with(['translations' => function($query) {
        $query->where('locale', App::getLocale());
    }]);
}

without any luck.

Get attributes by default.

I've setup laravel-translateable by following the guide in the README. Though, I'm not getting back the translateable items by default. If I return $countries in an API call it doesn't return the name attribute by default. Instead I do create an transformer to fetch the name value and then it works. Is there a way to get back all translateable items by default?

Saving bool values

Hi,

I have an issue using saving boolean on translatable model when I use the update or create model static method with array data. Only a boolean value of the default lang seams to doesn't work and ignore the value I defined.

Do I missed something, or there is special trick for it.
Thanks for the good work, cheers

some way of inheriting functions of parent model

I love this package, however I find myself having to re-do functions in the translated model... am I doing something wrong? Lets say for example I have a ->getLinkAttribute() in my main model, should I only have this in the translated model?

Also lets say I have a user_id in my main table, which of course should not be translated, and I do author() on the translated model, I get of course an error ( a relationship function of course). When paginating this can be a b***h. Should I save the original as well? seems like a hassle, how about a reference to the untranslated columns?

Latest Ardent and Translatable

Hey,

Just pluged in Ardent and getting this message:

Declaration of Dimsav\Translatable\Translatable::save() should be compatible with LaravelBook\Ardent\Ardent::save(array $rules = Array, array $customMessages = Array, array $options = Array, Closure $beforeSave = NULL, Closure $afterSave = NULL)

Composer package used: "laravelbook/ardent": "dev-master" also tried "laravelbook/ardent": "2.4.*" same results.

Model itself plain simple:

use LaravelBook\Ardent\Ardent;

class Brand extends Ardent {

    use \Dimsav\Translatable\Translatable;
}

Is the problem on my end or any of the packages?
Or maybe there's data on last know working version of Ardent with Translatable?

Proposition of dryer models

In the country model, in place of having:

public $translatedAttributes = ['name'];
protected $fillable = ['iso', 'name'];

What about having:

public $translatedAttributes = ['name'];
protected $fillable = ['iso'];

Or only $fillable array:

protected $fillable = ['iso'];

It should be great if translatable trait could automatically build $translatedAttributes based on CountryTranslation model. What do you think?

Does It works in test environment?

I have a seed migration like this

$place = array(

    'company_id'           =>  1,

    'ca'                   =>   array(  
                                  'name' =>  "ca",
                                  'description' =>  "ca",
                                ),
    'es'                   =>   array(  
                                  'name' =>  "ca",
                                  'description' =>  "ca",
                                ),
    'en'                   =>   array(  
                                  'name' =>  "ca",
                                  'description' =>  "ca",
                                ),
    'created_at'           => date('Y-m-d H:i:s'),
    'updated_at'           => date('Y-m-d H:i:s')                                               

);

$place = Place::create($place);

If i do

php artisan migrate --seed

is working well

If i do

php artisan --env=testing migrate --seed
  [ErrorException]                                                             
  preg_replace(): Parameter mismatch, pattern is a string while replacement i  
  s an array  

Can you help me, please?
Thanks!!

Questions regarding installation and setup

First of all, thanks for this bundle, which looks great.
After running composer update. do i need to set up the provider in app.php? should i put this 'Dimsav\Translatable\TranslatableServiceProvider'?

Second, if that's not necessary maybe the error (500) it's coming from another place. The 'php artisan serve' doesn't give me an error. But trying

dd($posts->first()->en->title);

in the controller makes laravel crash. it looks like it doesn't find the translated attributed.
I'm also using presenters so i have something like this, i don't know if this is the proper way of getting the translated and preformated properties:

class Post extends Eloquent implements PresentableInterface {
    use \Dimsav\Translatable\Translatable;

    public $translatedAttributes = ['title','excerpt','content','meta_title','meta_description','meta_keywords','header_image'];

     /**
     * Returns a formatted and localized post content entry,
     * this ensures that line breaks are returned.
     *
     * @return string
     */
    public function content()
    {
        return nl2br($this->content);
    }
}

on the post translation i have this:

class PostTranslation extends Eloquent {

    public $timestamps = false;
    protected $fillable = ['title','excerpt','content','meta_title','meta_description','meta_keywords','header_image']; 

}

This is my migration file:

// Create the `Posts` table
        Schema::create('posts', function($table)
        {
            $table->engine = 'InnoDB';
            $table->increments('id')->unsigned();
            $table->string('slug');
            $table->string('slug_es');
            $table->string('slug_ca');
            $table->integer('user_id')->unsigned();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->integer('category_id')->unsigned();
            $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
            // columns in the home (only journal)
            $table->smallInteger('importance')->nullable();
            // date of the party
            $table->dateTime('date')->nullable();
            // can this post be deleted?
            $table->boolean('deleteable')->default(true);
            // open to comments?
            $table->boolean('comment_status')->default(false);
            $table->boolean('published')->default(false);
            // type = Event or Journal
            $table->string('type', 30);

            $table->timestamps();
            $table->softDeletes();
        });

        Schema::create('posts_translations', function($table)
        {
            $table->increments('id');
            $table->integer('post_id')->unsigned();
            $table->string('title');
            $table->string('header_image');
            $table->text('excerpt')->nullable();
            $table->text('content');
            $table->string('meta_title');
            $table->string('meta_keywords');
            $table->string('meta_description');
            $table->string('locale')->index();

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

Is the presenter going to take the translated attributes on the view? how should i do this?

Also, what about if i want to use a slug for a post, so every language has it's own slug? how can i access the post by the translation? Currently i have the translated slugs on the Post table but i don't know if that's the best way.

trouble testing with translatable models

when testing my controllers; which have translatable models I get the error:

Class 'App' could not be found.

Any idea? It is of course referring to the one used by App::getLocale() but why cant it find it, should it be using the Facade\App??

Working with Eloquent's lists() Method

Hey @dimsav, first thanks for this amazing package.

When I want to get translatable data with lists() I got "Fields couldn't find" errors. Code is here:

Category::lists('name', 'slug');

But I got data with this:

Category::all()->lists('name', 'slug');

But, this way couses a performance problem. First we get all lines, after select them. Is there any way to get data like first code?

Known Caveats?

What are some known caveats of this library if any when working with things like presenters, collections, advanced relationships and say eager loading?

Are there any known at this time?

Undefined property $translations

hello, i'm getting an error, and i don't know where screwed up. Any help would be appriciated.

here is the error:
Undefined property: Product::$translations
private function getTranslationByLocaleKey($key)
{
--> foreach ($this->translations as $translation)
{
if ($translation->getAttribute($this->getLocaleKey()) == $key)
...
I followed every instruction to the letter..
any idea what could couse this error? is it maybe some debug setting?

Timestamps on entity_translations table break queries

Hi, dimsav. Your package is really great. But somehow I just have faced an issue. So, I would like to add timestamps columns to translations table to distinguish the time a certain translation was posted on. But something went wrong when I did it. $entity->translations became empty.

I have something like this:

 Schema::create('posts', function(Blueprint $table)
{
    $table->increments('id');
    $table->string('slug');

    // something else here

    $table->timestamps();
    $table->softDeletes();
});

Schema::create('post_translations', function(Blueprint $table)
{
    $table->increments('post_translation_id');
    $table->integer('post_id')->unsigned();
    $table->boolean('active')->default(0);

    // title, excerpt and content are in $translatedAttributes as they should
    $table->string('title');
    $table->text('excerpt')->default('');
    $table->longText('content');
    $table->string('locale')->index();
    $table->timestamps();
    $table->softDeletes();

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

Creating (non-fallback) translations doesn't work if $useTranslationFallback is set

When setting the $useTranslationFallback attribute on a model, creating non-default translations doesn't work out of the box.

An example:

App::make('config')->set('app.fallback_locale', 'en');
$country = Country::create(['iso' => 'gr']);
$country->useTranslationFallback = true;
$country->translate('en')->name = 'Greece';
$country->translate('de')->name = 'Griechenland';
dd($country->translate('en')->name);

This should dump Greece, but the entry for the fallback locale got overwritten by Griechenland.

I looked through your code and didn't find a simple solution to this problem, as this behaviour is intended when fetching translations but not when setting them. To distinguish between this two cases I added a third parameter to getTranslation() that is true when a translation should get created (when called from fill()). Then I added && ! $creating to the elseif here. Of course this doesn't work for setting attributes via translate(), which is why I didn't submit a pull request.

Move configuration to its own file.

Moved from #48. Rather then updating app/config/app.php we move the configuration into its own file. To publish the config file into app/config/packages we can run the command below.

php artisan config:publish dimsav/laravel-translatable

Just wanted to share the approach. What do you think?

problem with the way models are created

suppose you have a model of with a constructor, like so :

public $translatedAttributes = array('title');
protected $fillable = ['iso', 'title'];
protected $table = 'products';
protected $module = 'products';
private $ns;

public function __construct(array $attributes = array())
{

    $this->ns ='FrontEnd\Models';
    if ( ! isset(static::$booted[get_class($this)]))
    {
        static::boot();

        static::$booted[get_class($this)] = true;
    }

}

when creating a model like so :

$newProduct = new \FrontEnd\Models\Products();
$newProduct->iso = 'gr';
$newProduct->title = 'my test products';
$newProduct->save();

or like so :

$newProduct = \FrontEnd\Models\Products::create(array(
    'iso'=>'gr',
    'title'=>'my test'
));

the protected/private variables in the constructor are pulled into the insert method ending with an error of SQL: insert into products_translations (ns, locale, title, products_id) values (Mcms\Core\Models, en, my test products, 3689))

I have tried a couple of workarounds but none of the works. Any ideas ?

Thanks

Eager loading doesn't work

Hi,
I'm trying to load a laravel-translatable model from a different models relationship throught eager loading. Only the untranslated attributes are returned. Is this by design or am i "doing it wrong"™?

$game = Game::with('players', 'rounds', 'rounds.questions')->find($game->id);

It's rounds.questions that contains laravel-translatable models.

The question model has this:

use \Dimsav\Translatable\Translatable;
public $translatedAttributes = array('question', 'correct', 'incorrect0', 'incorrect1', 'incorrect2');

And it's exactly the translatedAttributes that are missing.

Locales like 'en-US'

First of all thanks for this package!

I have a problem with the locales like 'en-US'.

At the moment, I have 2 locales (es-ES for spain and es-CO for Colombia) and seems the same but there are differencies. The text on the web will show this differencies but when a user saves a registry, I don't want to save 'es-ES' or 'es-CO', I want to save only ES.

I have tested on a fork, and something like this works well

    /**
     * Returns only the language if the format locale is like 'en-US'
     */
    public function getLanguageLocale($locale)
    {
        if (strpos($locale, '-') !== false)
        {
            $array = explode('-', $locale);
            return $array[0];
        }
        else
        {
            return $locale;
        }
    }

and when get the locale:

$locale = $locale ?: $this->getLanguageLocale(App::getLocale());

What do you think about this? I need something similar ...

Collection->lists() does not get translated content

The Collection->lists() function only pulls the fields from the original table as opposed to the translation table. A work around would be to build the array myself using a loop, but it would be better if the lists() function uses the translation table.

Datatables

Hello,
Have you ever tried to use bllim/laravel4-datatables-package with translatable models? will be great to make it compatible with this package

With Model called News don't work

Hi! Thanks for this library! Works fine!!

I have a problem, when a create a model named News and another NewsTranslation don't work.
News is saved Ok, but NewsTranslation don´t save nothing.

I think that is a issue with the model names, but in this case, i cannot use New as model name, because there is a conflict in php.

News migration

    public function up()
    {
        Schema::create('news', function(Blueprint $table){
            $table->increments('id');
            $table->timestamp('publication_date');
            $table->string('author', 50);
            $table->string('image', 50);
            $table->integer('entity_id')->unsigned()->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade');            
            $table->timestamps();
        }); 
    }

NewsTranslation migration

    public function up()
    {
        Schema::create('news_translations', function(Blueprint $table)
        {
            $table->increments('id');
            $table->integer('news_id')->unsigned();
            $table->string('title', 256);
            $table->text('subtitle');
            $table->text('description');
            $table->string('youtube', 256);
            $table->string('locale')->index();
        });         

        Schema::table('news_translations', function(Blueprint $table){
            $table->unique(['news_id','locale']);
            $table->foreign('news_id')->references('id')->on('news')->onDelete('cascade');
        });
    }

The same behaviour happen with the followings model names:
PlaceSchedule (TableName: schedules) - PlaceScheduleTranslation (TableName: schedules_translations)

Can you help me, please?

Spelling

getNewTranslationInsstance() has one s too much :)

dynamic languages

So, love the package. But I have dynamic languages... Currently I am using a closure for the available languages in the config, but this is less than ideal.

Would this not be a nice addition to the package, to make a language table? It could also include a column for 'active', which could help in real-world applications.

Or atleast make it easier to use dynamic languages, couldnt think of a great way to implement this....

Search function

error

we can"t apply Query like "LIKE" to the database which is created by laravel translatable

Search by localized value

Hi. Is this possible to make request like next, when title is a translatable attribute?

Model::where('title','LIKE','%test%')->get();

Problems with documentation

Hello, according to your documentation this code must work

 $data = array(
    'iso' => 'gr',
    'en'  => array('name' => 'Greece'),
    'fr'  => array('name' => 'Grèce'),
  );
  $country = Country::create($data);
  echo $country->translate('fr')->name; // Grèce

But actually it does not :( Translations are not saved to DB.
However, smth like this works:

$album = new Album;
$album->translate('fr')->title = Input::get('fr_title');
$album->translate('en')->title = Input::get('en_title');
$album->save();

Thank you!

Translations are not saved when using Baum\Node

Hi,

in my project I need to create a model that uses both a nested tree structured and translations. To translate the model I use your package. The nested set structure is provided by https://github.com/etrepat/baum.

If my model extends Baum/Node the translations are not saved at all.

<?php

use Dimsav\Translatable\Translatable;

class Category extends Baum\Node
{
    use Translatable;

    protected $guarded = array('id', 'url');

    public $translatedAttributes = ['name', 'url'];
    public $translationModel = 'CategoryTranslation';

}

When changing Baum/Node to Eloquent the translations are saved correctly.

How can this problem be solved?

A new record is not saved if there are no changed fields

Consider this new User object (translatable-trait is not used in the model)

$user = new User();
$user->save();
echo $user->id //returns 1 (if your user table was empty), db-record created

It's a bit silly to store a user without properties, but the point i want to make is that normally a record without any properties get saved in the db with only the id.

Now consider this new News object (translatable-trait is used on this model)

$news = new News();
$news->save();
echo $news->id //returns NULL, record is not saved in the db

The record should be saved even when no properties are set.

Translations not used when using $object->toJson();

In any model, if you call toJson() on your object, the underlying Eloquent Model will not get the translated attributes.

I have composed and tested a fix. All you have to do is add the following method override to your Translatable trait:

/**
 * Convert the model instance to an array.
 *
 * @return array
 */
public function toArray()
{
    $attributes = $this->attributesToArray();

    foreach($this->translatedAttributes AS $field)
    {
        $attributes[$field] = $this->getTranslation()->$field;
    }

    return array_merge($attributes, $this->relationsToArray());
}

Since the underlying model uses toArray() in order to convert something to JSON first, this will cover both cases where you need an array, and cases where you need a JSON object.

Some questions regarding the code

I hope it is okay if I leave multiple questions in one issue. I have just stumbled upon your repo when looking for something comparable to Doctrine Translatable for Laravel, and have not tested it at all, so pardon the newbishness :)

  1. What is the role of $this->translations? How/when is it set, and what type of object is it?
  2. Will there potentially be conflicts between iso codes and model properties, for instance if using language Malayalam (ml) and you have a database column for milliliters (ml)? ;)
  3. How does your model affect database performance? Do I have to do joins for Countries and CountriesTranslations every time my Countries model is used? For instance when doing something easy like $article = Articles::find(1);.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.