GithubHelp home page GithubHelp logo

spatie / laravel-translatable Goto Github PK

View Code? Open in Web Editor NEW
2.2K 27.0 270.0 278 KB

Making Eloquent models translatable

Home Page: https://spatie.be/docs/laravel-translatable

License: MIT License

PHP 100.00%
laravel php i18n translated-attributes eloquent

laravel-translatable's Introduction

A trait to make Eloquent models translatable

Latest Version on Packagist MIT Licensed GitHub Workflow Status Total Downloads

This package contains a trait HasTranslations to make Eloquent models translatable. Translations are stored as json. There is no extra table needed to hold them.

use Illuminate\Database\Eloquent\Model;
use Spatie\Translatable\HasTranslations;

class NewsItem extends Model
{
    use HasTranslations;
    
    // ...
}

After the trait is applied on the model you can do these things:

$newsItem = new NewsItem;
$newsItem
   ->setTranslation('name', 'en', 'Name in English')
   ->setTranslation('name', 'nl', 'Naam in het Nederlands')
   ->save();

$newsItem->name; // Returns 'Name in English' given that the current app locale is 'en'
$newsItem->getTranslation('name', 'nl'); // returns 'Naam in het Nederlands'

app()->setLocale('nl');

$newsItem->name; // Returns 'Naam in het Nederlands'

// If you want to query records based on locales, you can use the `whereLocale` and `whereLocales` methods.

NewsItem::whereLocale('name', 'en')->get(); // Returns all news items with a name in English

NewsItem::whereLocales('name', ['en', 'nl'])->get(); // Returns all news items with a name in English or Dutch

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Documentation

All documentation is available on our documentation site.

Testing

composer test

Contributing

Please see CONTRIBUTING for details.

Security

If you've found a bug regarding security please mail [email protected] instead of using the issue tracker.

Postcardware

You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using.

Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium.

We publish all received postcards on our company website.

Credits

We got the idea to store translations as json in a column from Mohamed Said. Parts of the readme of his multilingual package were used in this readme.

License

The MIT License (MIT). Please see License File for more information.

laravel-translatable's People

Contributors

adrianmrn avatar ahmetbarut avatar alexjoffroy avatar ankurk91 avatar bram-pkg avatar ctf0 avatar dependabot[bot] avatar dvlpp avatar freekmurze avatar gdebrauwer avatar github-actions[bot] avatar itsrd avatar khalilst avatar laravel-shift avatar messi89 avatar mokhosh avatar mstaack avatar muetze42 avatar nateritter avatar nielsvanpach avatar odeland avatar patinthehat avatar propaganistas avatar robinmartini avatar roblui avatar ronmelkhior avatar royduin avatar sebastiandedeyne avatar yannikfirre avatar yoeriboven 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

laravel-translatable's Issues

WhereIn not working

I am using Laravel 5.4. Is there any way to use whereIn on the translatable column.

This works totally fine:-
NewsItem::where('name->en', 'Name in English')->get();

This is not working:-
NewsItem::whereIn('name->en', $names)->get();

Thanks

How to Apply Translatable to Pivot Tables

I have a pivot table with additional fields that I want to apply "laravel-translateable" to them. How can I apply HasTranslations trait to the pivot tables? Should I create additional models for each pivot table?

Using MariaDB

Hi guys.

Wonderful library.
We're using MariaDB in stead of MySQL. I don't think it supports the json data type.

How does the library work when it is using text columns when we are querying with a where clause?
Am I right in thinking that it fetches the data from the database doing a where column like '%searchstring%' and then you're filtering in code?

Any mechanism to work with Slugs?

In your post in laravel news I liked the idea of articles. But we know article have various properties like slugs, title and content. This package makes easy to retrieve title and content according to locale. But there is one catch how can we make work with slug.

How can we correctly display the post according to slug that also has been localized.?

Thanks for this awesome package :)

Select many items in a specific language?

Hello Freek,

I recently played with this package, but I'm stuck with a problem:
How to select many items in only one language ?

I tried:

->select('content->'.app()->getLocale().' AS content')

But I get quotes around the value.

Then I tried without luck to JSON_UNQUOTE:

->select(DB::raw('JSON_UNQUOTE(content->'.app()->getLocale().')'))

For now I created a custom collection filter but it's far from ideal:

Collection::macro('translate', function ($locale = null) {
    $locale = $locale ?: app()->getLocale();

    return $this->map(function ($item) use ($locale) {
        foreach ($item->getAttributes() as $key => $value) {
            if (in_array($key, $item->translatable)) {
                $item->$key = $item->translate($key, $locale);
            }
        }

        return $item;
    });
});

Any advice would be appreciated.
And thank you again for your great packages.

error when using with query builder

the main reason behind this is using the query builder instead of eloq

DB::table('posts')->first()->getTranslation('title', 'en');

which return a StdClass instead and therefore u get the error

Call to undefined method stdClass::getTranslation()

while using

Post::first()->getTranslation('title', 'en');

works as expected, so is there any chance to extend the support to work with DB::table() as well ?

Translate in paginated Json data

I use your package with a backpack and try to make search field with AJAX to a model which has a name which is translatable - but when the API return results I see only JSON inside and not translations of the field.

So how I can have Laravel paginated results with already translated strings inside.

isTranslated

At first that you for the amazing package! I have faced one issue that might be useful for some people. Once implementing existing data I had to check if the model was already translated and if not then to perform the translation. A method isTranslated returning bool would be a really handy addition.

Thank you!

PS. My code doing the same thing is not pretty therefore no pull request ;-)

FatalErrorException in HasTranslations.php line 31:

Hello guys,

I encountered this error.

"Default value for parameters with a class type hint can only be NULL"

I have seen in Code, that this line appears in red, just where I found the error.

Any ideas???

Thanks and Good Job¡¡¡

Is there any practical way to use this package with existing database?

Thank you for the package. I have used dimsav's package for multilingual for a quite time. And I have a project which I want to add support for multilingual models and I dont want to create second table for every table in my database.

So you package seems quite good for my purpose. But I wonder if I can use this package with an existing database. Some of columns are varchar fields and some are text fields. I cannot alter table to json column type easily. Do you have a practical way to do this and use your package?

Unique Translation validation rule

Hello,

First of all, thanks for sharing all of your amazing packages with us!

I'm working on a validation rule that works like the unique rule that Laravel provides, except that it looks inside a JSON column with translations. This is intended to be used with your package, but there may be other use cases...?

https://github.com/codezero-be/laravel-unique-translation

Do you think such a validation rule could be useful for your package?

UTF-8 escaped as \uXXXX

When inserting translations, this word for instance Abée, gets transformed to Ab\u00e9e. This behaviour can be changed by overwriting the asJson method on the Eloquent\Model class.

public function asJson($value)
{
    return json_encode($value, JSON_UNESCAPED_UNICODE);
}

I guess this package shouldn't worry about this and we should fix this ourselves?

Also, search could be tricky if we try searching for Abée.

soft deleted model bug

if the model is soft deleted and you tried to display its data, you would get the full json data and not the usual "per locale value".

to test

  • make a model "posts"
  • enable soft delete
  • remove one of the items
  • try to display the removed item data

Model->toArray() and toJSON() return the JSON-string instead of translated content for translatable columns

When converting a model to an array the translatable columns contain the json string, containing all languages, instead of the translated value.

I've fixed it in my system by changing HasTranslations.php to include:

public function toArray()
{
        $attributes = parent::toArray();

        foreach($this->getTranslatableAttributes() as $field)
        {
            $attributes[$field] = $this->getTranslation($key, config('app.locale'));
        }

        return $attributes;
}

This works for me. Is there a better way?

Set flag to your json_encode

I want to add flag to json_encode JSON_UNESCAPED_UNICODE because you store the Cyrillic information incorrectly.

How can I make this?

How handle form request with json field ?

Hi,

Thanks again for your package. I'm just wondering how I can validate with a form Request cause I've created a JSON field named title and not 'title_fr' or 'title_en'.
How can I do with your package ?
Here is the function store of my controller

public function store(StoreorUpdateMyModel $request) { // Fields to translate $translatables = ((new MyModel())->translatable); // Locales concerned foreach ($translatables as $translatable) { $translations = []; foreach (config('app.locales') as $locale) { $translations[$locale] = $request->{$translatable.'_'.$locale}; } $request->merge([$translatable => $translations]); } $this->affirm($request); MyModel::create($this->getRequestsData($request)); Session::flash('success_message', trans('labels.backend.added_success')); return redirect()->route('mymodel.index'); }

Thanks for your help

Regards

update translation

translation table doesn't update.

       $admin = Admin::findOrFail($id);

        foreach (\Config::get('app.locales') as $locale) {
            if (!$request->title[$locale]) {
                $admin->deleteTranslations($locale);
                continue;
            }

            $admin->translateOrNew($locale)->title = $request->title[$locale]; // new value
        }

       $admin->save();

[Question] Get All Translations

Hey guys,

first of all - nice work on this package. I am considering using this package, however, i would like to get some more information.

Consider the following scenario:
My application stores FAQs in different languages (e.g., en, de, fr, ...) A FAQ entry consists of a question and an answer, both are texts (obviously).

A regular User may browse all available FAQs in his language (by using the Accept-Language HTTP Header). However, an Editor can create / update FAQs and, therefore, needs the texts of the respective entity in all available languages.

How can i achieve this scenario? Note that I am using your laravel-fractal package as well.

For example, I would like to have an FAQTransformer that outputs the respective data, like so:

// ... skipping class information
    public function transform(Faq $entity)
    {
        return [
            'id' => $entity->id,
            'question' => $entity->question,
            'answer' => $entity->answer,
        ];
    }

Obviously, I could make an additional transformer that uses

// ... 
'question' => $entity->getTranslations('question'),
// ...

but that would be redundant code.

It would be perfectly fine for me to set the Accept-Language' header to '*' or something like that to indicate that i would like to get all entities..

How could one achieve this behaviour? Or how would you tackle this issue?
Thanks a lot and cheers from Germany

Why not using the default `app.fallback_locale` config option?

First of all thanks for this awesome package, neat and elegant API. The cleanest, easiest, and most intuitive translatable Eloquent models API actually.

My question is: Why do you duplicate the fallback_locale config option and not using the default app.fallback_locale for locale fallback, just like the app.locale ?

It doesn't support UTF8.

$item->setTranslations('description',['en' => 'Hello', 'kh' => 'សួរស្តី']);

I got this TypeError: Return value of getTranslations() must be of the type array, string returned.

Thanks,

Compatibility with Accessor/Mutator?

the HasTranslations trait doesn't seem to be able to cope with translatable attributes who also have a mutator.

Given this Model:

    class PortfolioItem extends Model
    {
        use HasTranslations;
    
        protected $table = 'portfolioitems';
    
        protected $fillable = ['category'];
    
        public $translatable = ['category'];
       
        public function setCategoryAttribute($category)
        {
            $this->attributes['category'] = strtolower($category);
        }
    }

And this statement:

$model->category = 'category';
echo $model->category;

Gives:

[
    'en' => null,
]

Slug problem

Why SLUG is not translated in the database? title is OK.
What am I doing wrong?

PHP 7.1
MySql 5.7.17
Laravel 5.4
spatie/laravel-sluggable
spatie/laravel-translatable

Migration

$table->json('title');
$table->json('slug');

Model - Page:

use Illuminate\Database\Eloquent\Model;
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
use Spatie\Translatable\HasTranslations;

class Page extends Model
{

    use HasTranslations;
    use HasSlug;

    protected $table = 'pages';

    protected $fillable = ['title','slug'];

    public $translatable = ['title','slug'];


	public function getSlugOptions() : SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom('title')
            ->saveSlugsTo('slug')
	    ->doNotGenerateSlugsOnUpdate();
    }

}

Make POST Form:

$request->all();

array:2 [▼
  "_token" => ""
  "title" => array:2 [▼
    "pl" => "aaaaaaac"
    "en" => "bbbbbbc"
  ]
]

Post Validate:

$this->validate($request, [
   'title.pl' => 'required|max:255',
   'title.en' => 'max:255',
]);

Page::create($request->all());

Page {#233 ▼
  #table: "pages"
  #fillable: array:2 [▼
    0 => "title"
    1 => "slug"
  ]
  +translatable: array:2 [▼
    0 => "title"
    1 => "slug"
  ]
  #connection: "mysql"
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #with: []
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: true
  #attributes: array:5 [▼
    "title" => "{"pl":"aaaaaaac","en":"bbbbbbc"}"
    "slug" => ""aaaaaaac-7""
    "updated_at" => "2017-02-02 07:43:03"
    "created_at" => "2017-02-02 07:43:03"
    "id" => 20
  ]
  #original: array:5 [▼
    "title" => "{"pl":"aaaaaaac","en":"bbbbbbc"}"
    "slug" => ""aaaaaaac-7""
    "updated_at" => "2017-02-02 07:43:03"
    "created_at" => "2017-02-02 07:43:03"
    "id" => 20
  ]
  #casts: []
  #dates: []
  #dateFormat: null
  #appends: []
  #events: []
  #observables: []
  #relations: []
  #touches: []
  +timestamps: true
  #hidden: []
  #visible: []
  #guarded: array:1 [▼
    0 => "*"
  ]
  #slugOptions: SlugOptions {#236 ▼
    +generateSlugFrom: "title"
    +slugField: "slug"
    +generateUniqueSlugs: true
    +maximumLength: 250
    +generateSlugsOnCreate: true
    +generateSlugsOnUpdate: false
  }
}

how to bypass the the package

atm am trying to build a revision manager, so when restoring to an old revision, the package will put the new value under the current locale,

where instead i just want to replace the whole column value without any extra operations.

Is it method to set default locale ?

If I get the value of tw locale, but I don't set the tw locale, what's wrong with package response ?
Have the case like this :
default locale is en, when I get the tw locale, but I don't set tw locale before. Whenever I get the locale I don't set before, the package will return the value of default locale ?

Use fallback locale for missing translations

In the documentation you mention that "If there is no translation set the value of default will be returned.", but the function definition is different in HasTranslations function and there's no way to set the default locale.

Is it possible to use the fallback_locale for the missing translations?

Removing a language from an object?

What would be the recommended way of removing a language from an object?
I'm thinking of getting the current locales, removing en/nl/whatever and then saving it, however the current methods don't support this as far as I can see.
Did I miss something or is this functionality missing?

Search, with text fields, may fail with special characters

When searching in mySQL < 5.7 (text, not JSON field)
where('field', 'regexp', ""{$lang}"\s*:\s*"{$value}"")
If language has special characters those are encoded as Unicode characters (\uxxxxxx) when converting to JSON prior to saving into DB. This may cause the search to fail.

How to perform like search?

Wonderful package, thank you.

The are two things we want to know

  1. Does this package stores data in unicode format like \uxxxx format only? Say I want to store नमस्ते दुनिया
    the does it store as it is or in unicode format?

  2. We are having following data under name filed
    {"en":"Nokia-130","ar":"\u0646\u0648\u0643\u064a\u0627 130 (2017)","ch":"\u8bfa\u57fa\u4e9a130\uff082017\uff09"}

We are trying to hit
SELECT json_extract(NAME, "$.ar") FROM products WHERE json_extract(NAME, "$.ar") LIKE '%\u0646%'

it works fine and gives the result but when we hit

SELECT json_extract(NAME, "$.ar") FROM products WHERE json_extract(NAME, "$.ar") LIKE '%\u0646\u0648%'

it does not work. Can you please help in this? Thank you

is it possible to set value to NULL if request value is empty ?

atm because the package works according to the default locale,
so if the column had an empty value the package will save it as {"en": ""} or {"en": null}.

however, is there a way to save it as NULL instead ?

EDIT

maybe a solution would be forgetting the local if the key value is empty ?

Inserting & Updating Related Models is not work

Sorry for this open issue, I saw in Laravel documentation that we can save related model by call the relation like bellow, but when I do this the related model field is not translate and save raw data to database so it
not the valid json format.

Look at the example:
`$comment = new App\Comment(['message' => 'A new comment.']);

$post = App\Post::find(1);

$post->comments()->save($comment);`

if I call $comment->save(); it work correctlly, but when I call $post->comments()->save($comment); it not work.

Thank for you help.

Prepare for frontend

Is there an easy way to prepare translatable models for the frontend? Let's say I have a product with a translatable title, if I return $products->toJson() in my API, the title attribute is a JSON array.

So to work with the objects in my frontend, I have to call App::getLocale(), pass the value seperately to my frontend and then rework all my custom Vue components to accept a locale, so I can access the translated attribute with product.title[locale]

It would be great to have automatic attribute casting to the current locale when calling toArray() or toJson(). Sorry if this is not an issue directly but I am not clever enough to create a PR for this :D

Return value must be of the type array, null returned

Hi,

how can one translate existing model records?
I have a table job_categories with columns id and name...
This table is already populated with categories. The name column contains plain text...
When I set JobCategory model to be translatable and try to translate the name field I get this error

error: Return value of App\JobCategory::getTranslations() must be of the type array, null returned..

Do I get this error because the name field still contains plain text? Should I first translate everything to a new field and then copy everything back to original?

Decrease and speed up the response

Are there any ideas for implementing such improvements?

  • Get the translation, not full json-object.
  • Use the stored generated column with index for accelerate search.

The implementation through SELECT:

public static function __callStatic($method, $parameters)
{

    if ($method == 'select') {

        $instance = new static;

        $parameters = array_map(function($key) use ($instance) {
                if (isset($instance->translatable) && in_array($key, $instance->translatable)) {
                    return $key . '__' . config('app.locale') . ' as ' . $key;
                }

                return $key;
            }, $parameters);

        return $instance->$method(...$parameters);
    }

    return parent::__callStatic($method, $parameters);
}

Migration:

...
$table->string('name__en')->virtualAs("`name` ->> '$.en'");
$table->string('name__ua')->virtualAs("`name` ->> '$.ua'");

//add index where need, because will take memory in DB
$table->index('name__en'); 
$table->index('name__ua');
...

Example:

$book = Book::select('name')->first();
$book->name; //name by current lacale

WhereIn?

In the README.md file is written that we can use NewsItem::where('name->en', 'Name in English')->get();.
I want to do this basically: NewsItem::whereIn('name->en', ['test_1', 'test_2'])->get();, however, this does not yield any results.
If I do toSql() and run the query with and without the whereIn section, I can see that without I get results as expected (though of course not filtered), but I get nothing when using the whereIn part of the query.
I also get an error stating: Invalid JSON path expression. The error is around character position 1..
The data in the tables is valid JSON.

Country::all()

Hi, nice job!

   countries table:
 
   id INT
   name JSON

Works

   // locale = de

   $country = Country::first();
   dd($country->name);
   ---> dütsch

But, collections doest not work

 $countries = Country::all();  

Any workaround?

getTranslations() must be of the type array, string returned

I saw the previous issue that was created in this repository but hasn't been solved there yet.

I have the exact same error;

Type error: Return value of App\Models\Entity::getTranslations() must be of the type array, string returned

    protected $translatable = [
        'meta_title'
    ];
$table->text('meta_title')->nullable();

When I leave the $translatable property empty there is no problem.

Error:

local.ERROR: Symfony\Component\Debug\Exception\FatalThrowableError: Type error: Return value of App\Models\Entity::getTranslations() must be of the type array, string returned in /Users/****/Workspace/community/vendor/spatie/laravel-translatable/src/HasTranslations.php:72

translation in seeds

The traslations in seeders are encoded in wrong way and in tha database are inserted strings like
this "{"bg":"\u041c\u0435\u043a\u0438 \u043a\u043e\u0440\u0438\u0446\u0438"}"
everything is OK when I insert from web pages.

[Feature proposal] When fallback isn't available, but others languages are...

Hi guys!
I'm currently handling something that it could be interesting to add to the core.
If the current locale isn't available, and the fallback one neither (easy if the current is the fallback), the string will be empty, even if there are some translations in the field.

My example:
I'm building a form to fill a menu of products. Products are translatable and the user can choose the language with a selector. Basic.

If I create a product when English (fallback) is selected, when changing to French, my product will have all english translations, and it's ok.

If I create a product when French is selected, when changing to English, my product will have all attributes empty... and it's not ok!

2 solutions, in \Spatie\Translatable\HasTranslations::normalizeLocale:

  • Set an array of fallback in config, and return the first fallback available.
  • Fallback to the first language available, if the fallback isn't available.

Imo, both should be combined.
It can be configurable, to preserve the actual behaviour.

What do you think?

case insensitive search in JSON column

Hey,
I'v been struggling with this for a while so I was wondering what your approach is.

How to query JSON columns in a case insensitive manner?

JSON is stored in a binary column, so it's treated as case sensitive. And collation can't be changed to eg 'utf8_unicode_ci'
assuming this content in a JSON column

{
	"en": "Chair",
	"fr": "Chaise",
	"nl": "Stoel"
}

These will return zero results:

select * from `products` where `name`->'$.nl' like '%stoel%';

Changing the column to 'TEXT' with a 'utf8_unicode_ci' collation only works half and loses the benefit of the JSON column type

select * from `products` where `name` like '%stoel%'; # works, but can't be used
select * from `products` where `name`->'$.nl' like '%stoel%'; # no results

This works, but is hard to implement on a global scale:

select * from `products` where `name`->'$.nl' COLLATE utf8mb4_unicode_ci like '%stoel%';

Any thought on how to implement this easily in Laravel?
Or even an idea on how you implement a search in a site using JSON columns for eg product names would be really helpful to a lot of people.

Internet seems to lack solutions of people with the same problem
Thanks

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.