rainlab/translate-plugin

[bug] storeTranslatableBasicData method not working correctly

Metallizzer opened this issue ยท 9 comments

Hello.
October CMS v3.3.14
RainLab.Translate plugin v2.2.4

When trying to change the translation without changing the value in the model itself, the storeTranslatableBasicData method resets the value of the attribute_data field

Example

// Sets a single translated attribute for a language
$user->setAttributeTranslated('name', 'Jean-Claude', 'fr');

$user->save();

Because the method tries to save only those values that match the dirty values of the parent model
image
array_intersect_key returns an empty array if the second parameter is empty

We have the same problem but didn't notice it until now and now have multiple, hard to find models with missing translations..

Fixed in v2.2.5

@daftspunk The problem still persists for me. As soon as I enter a attribute for a locale that is not the default but has the same language the value is removed and the field stays empty

Hi @thomas4Bitcraft

This is part of the design. If the locale "message" (language) is the same as the default locale's message, then it isn't stored--the value is still obtained using the fallback logic instead.

To clarify:

// en title is: First Page
$page->setAttributeTranslated('title', 'First Page', 'fr');

If the french title is "First Page", and the english title (default) is also set to "First Page", then it stores as NULL. This way it is considered "untranslated" and if the english message updated, the french message is also updated since they are considered the same source.

@daftspunk Ok, but this causes a problem if I want to know if the model is available in the language, right?

If e.g. the slug is the same in every language and I want to know if the page is available in the respective language. E.g:

Model 1:
Slug DE: test-1
Slug EN: test-1

Model2:
Slug DE: slug-de
Slug EN: slug-en

$locales = array_keys(Locale::listAvailable());
foreach ($locales as $locale) {
       $model->translateContext($locale);
       if (empty($model->slug) { Do something }
}

I have to set $model->noFallbackLocale($locale); as well so I don't receive the default value. But if I do this I would never know if it's not translated, or the translation is just the same then the default one. The same applies if I want to know if a model is translated and NOT empty

Is there any way to opt-out this behavior?

Is there any way to opt-out this behavior?

It is a good idea to include a way to opt-out, the original design was to just duplicate everything, but that broke the "fallback" feature (issue #714). In Translate v1, we used the browser to determine if a field was translated so it could be null or it could be duplicated value ๐Ÿ˜…

I thought something like could work:

// Not reliable
$page->noFallbackLocale()->setAttributeTranslated('title', 'First Page', 'fr');

However, it is only a temporary fix to the state. Next time the model is saved, the comparison check is done and the value is nulled again.

Some other options are a global configuration setting, or a property on the model. In your case, maybe it is best to specify attributes that should not be compared and nulled.

Is your slug an index already? Perhaps we could use this

public $translatable = [
    'name',
    ['slug', 'index' => true]
];

Yes, it's a good idea to enable this behavior on the attribute level, but maybe not with the index key. We have a model with about 20 fields that are translatable. 2 of these fields (slug, title) may contain the same string for each language in about 5% of the cases. Using the index key would also create an entry in the rainlab_translate_indexes table, which would be unnecessary.

Maybe it's better to have something like:

public $translatable = [
    ['title, 'prevent_default' => true],
    ['slug', 'prevent_default' => true, 'index' => true],
    'description',
    ....
];

What do you think?

Looks good. This has been added in ac38a18 and docs updated (below).


Fallback Attribute Values

By default, untranslated attributes will fall back to the default locale. This behavior can be disabled by calling the noFallbackLocale method for reading the value.

$user = User::first();

$user->noFallbackLocale()->lang('fr');

// Returns NULL if there is no French translation
$user->name;

When writing the value, the fallback value is determined when the translated value matches the default value. In these cases, the translated value is considered untranslated and not stored.

Locale Attribute Value Is Stored
en title Hello World Yes (Default)
fr title Hello World No
de title Hallo Welt Yes

For example, if the en default locale stores the message as "Hello World" and the fr locale value is also "Hello World", then the fr value is not stored. The fr value is accessed using the fallback value taken from en.

You may disable this behavior by passing the $transatable attribute value as an array. The first value is the attribute name, the other values represent options, in this case setting the option fallback to false.

public $translatable = [
    ['title', 'fallback' => false]
];

This above definition will force the title attribute value to be duplicated across all locales.

Released in v2.2.6