Updated: Comment #166
Suggested commit message
Issue #2019055 by plach, fago, berdir, yched, Gábor Hojtsy, kfritsche, jibran: Switch from field-level language fallback to entity-level language fallback.
Currently language fallback is supported only for fields provided by the Field API. This means that in any context where language fallback is required (the most common use case is entity rendering), Field API fields for which there is no translation available in the active language will be replaced by an existing translation. However this will not happen for any other field attached to the entity being displayed.
Additionally the current code provides two non-cleanly separated tasks: entity language negotiation and field language fallback.
- Entity language negotiation aims to determine which actual entity (translation) language should be used in a particular context, such as entity rendering. Usually a context implies a language, for instance the current page language, but we might not have an entity translation matching that language, hence we may need to apply some fallback logic to determine which actual language we should use.
- Field language fallback aims to provide an alternative field translation value, should a value for the language implied by the current context be missing. Also in this case some fallback logic needs to be applied.
The D7 code was initially wrote to solve a rendering issue: when displaying an entity in a language for which a translation was not available, field values disappeared. This clearly was not desirable. Since in D7 core there is no concept of entity translation (we have only language-aware fields), the only way we found to fix this issue was applying some fallback rules to every field so that existing values would be rendered. The underlying assumption was that all fields would behave the same way. What we didn't take into consideration was that empty fields values are not stored, hence they appear as missing values. The unwanted consequence was we unwillingly introduced a new "feature", that is language fallback at field level. In fact in D7 you can render the french translation of an english node, but if the body translation is empty its original english value will be rendered. This behavior was not intended initially and it does not even make much sense in many scenarios. Instead we should leave translators the ability to enter empty values if they need to, but this is now impossible without disabling field language fallback altogether.
Providing a reusable/swappable language fallback system that coupled with an entity language negotiation system can improve DX when dealing with multilingual entities.
The current solution is based on the Entity Translation API. Basically everything boils down to retrieving the most appropriate entity translation available for a particular context. A
$context array holding arbitrary data useful to describe the context where fallback is being perfomed can be optionally passed. The most common case is the "entity_view" context, but we can have for instance a "views_query" context or a (not present in this issue) "entity_serialize" context:
$translation = \Drupal::entityManager()->getTranslationFromContext($entity, 'it');
// $translation might or might not be italian depending on its availability
$value = $translation->field_foo->value;
The current language fallback code has been moved to the language manager and is now responsible for building a list of language fallback candidates, possibily based on a given context. The returned values are alterable through dedicated hooks.
EntityManagerInterface::getTranslationFromContext() performs entity language negotiation by inspecting the language fallback candidates and returning an existing translation object.
This was discussed in Prague. The discussion was attended by: attrib, berdir, das_peter, fago, Gábor Hojtsy, plach, swentel, tstoeckler, webflo, yched.
Ensure there is consensus on the current approach or explore new ones Complete test coverage Complete PHP docs and inline comments Fix failing tests
(follow-up to be created) Evaluate whether it makes sense to provide a generic language fallback API that could be used for implementing field-level fallbacks in contrib.
(follow-up to be created) Evaluate whether it makes sense to perform entity language negotiation in the route param converters.
User interface changes
- The current language fallback code has been moved to the language manager.
- Field-level language fallback is no longer available.
Original report by fago
Right now we've language fallback implemented for field API fields, however with entity fields being translatable as well, we really to support language fallbacks on entity field level as well. Then, we can probably remove the field API variant in favour of the general one.
I think we should remove any possibility to have run-time alterations language fallbacks that depend on context, but get a general list of language fallback rules (per entity type, per field?), which then could be applied to queries as well — later on (contrib?, d9?).
As this is rather important to have for multi-lingual content, setting to major.