Astrotomic / laravel-translatable

A Laravel package for multilingual models
https://docs.astrotomic.info/laravel-translatable/
MIT License
1.25k stars 157 forks source link

The default locale and fallback changed. #190

Closed buzzclue closed 4 years ago

buzzclue commented 4 years ago

Hi There,

I have said this before but what a great package.

I am having a small issue with the fallback option, in my project we used to have Spanish as default and fallback language (most of the old pages are translated in Spanish and some of them have English translations too) now our default language and fallback is changed English but the translateOrDefault returns null for the old pages.

Is there a work around to use Spanish(es) as fallback for old pages? without running new queries?

Gummibeer commented 4 years ago

Hey,

thanks for the kind words! 🙂

Just to summarize what I've understood: You have a Page model which is translatable. Until timestamp XYZ your required app locale was es and optional locale was en. Now you have switched it and have en as required for all new pages and es is the optional language.

You now have the problem that you have a mixed translation base and not this single fallback language anymore. For some of the next ideas I assume that you only have these two locales in your app - if you have any additional locales (fr for example) they don't work this easy.

The first things that come to mind:

At all I will again promote the v12 #129 as you can use multiple fallbacks or your very own fallback strategy when v12 is released.

buzzclue commented 4 years ago

Thank you for the quick response.

if you have any additional locales (fr for example) they don't work this easy.

Yes we do have more languages but I wanted to ask question in simple way.

I have a system that several people are using and some might change default/fallback locales back and forth tracking with created_at isn't practical in such case. I was thinking maybe add a new column and store fallback locale with each page/post.

Is there a method to load all translations for specific model?

Yes multiple fallback idea is good and will work. v12 will be exciting 👍

Gummibeer commented 4 years ago

There's a translations relationship which is used in the package and fully loaded by default. All logic goes down to this method: https://github.com/Astrotomic/laravel-translatable/blob/ee5caabd8172f08a70187eae5f92e0b790690db3/src/Translatable/Translatable.php#L425-L436

While scrolling through the code I've found this - strange syntax and was the original trigger for v12 but right now it's in the package like this so you can use it like this.^^ https://github.com/Astrotomic/laravel-translatable/blob/ee5caabd8172f08a70187eae5f92e0b790690db3/src/Translatable/Translatable.php#L214-L226 If you set translatable.fallback_locale to null and translatable.use_fallback or $this->useTranslationFallback to true it will loop all configured locales (in order of configuration) until a translation is found. It still runs only one query - the logic explained in a bit of pseudo-code:

locales = [
  en,
  es,
  fr,
]

translations = SELECT * FROM translations WHERE model_id = model.id;
if translations[fr]
  return translations[fr]

foreach locales as locale // 1: en | 2: es | 3: fr
  if translations[locale]
    return translations[locale]

return null

Like you can see there's only one query - because we utilize default eloquent logic it also works by eager-loading the relation for a collection of pages and will run only one query $pages->load('translations'). It will only be heavier on PHP side, so configure your locales (order) wisely and don't have too many locales. 😉

buzzclue commented 4 years ago

My translations relationship is in the model because I needed some other data with translations.

public function translations(): HasMany
{
    return $this->hasMany(PostTranslation::class, 'post_id')->with('postmeta');
}

I have already tried the translatable.fallback_locale=null and use_fallback in my configuration is set to true with these settings the following queries run:

select * from `posts` where exists (select * from `users` where `posts`.`author_id` = `users`.`id` and `users`.`deleted_at` is null) and `posts`.`status` = 1 and `posts`.`visibility` = 'public' and `posts`.`type` is not null and `posts`.`deleted_at` is null limit 10

and for translations

select * from `post_translations` where `post_translations`.`post_id` in (2, 5, 6, 7, 9, 10, 11, 12, 13, 15) and `post_translations`.`locale` in ('es', '')

That loop will return null because translations are not loaded.

My locales in translations are

'locales' => [
      'en',
      'es',
      'ar',
      'fr',
],
Gummibeer commented 4 years ago

Could you show me the code that produces the translations query? It seems like you are loading the translation (singular) relationship - which should only be used if you know EXACTLY what you want/need and depending on your translated data and amount of locales it doesn't really speed up your app as the transfer of some texts from DB to PHP is most times not the bottleneck.

buzzclue commented 4 years ago

Thank you for the hint that's exactly what I was doing wrong, i didn't realized until you mentioned. I was using withTranslation scope instead of with('translations')