Problem/Motivation
When adding a new translation to an existing content entity, saving the translation form throws an uncaught exception and produces a white screen / "The website encountered an unexpected error":
InvalidArgumentException: Invalid translation language () specified. in Drupal\Core\Entity\ContentEntityBase->getTranslation() (line 919 of core/lib/Drupal/Core/Entity/ContentEntityBase.php). Drupal\diff_translation\Hook\DiffTranslationHooks->menuLocalTasksAlter() (Line: 460) Drupal\Core\Extension\ModuleHandler->alter() (Line: 385) Drupal\Core\Menu\LocalTaskManager->getLocalTasks() (Line: 100) Drupal\Core\Menu\Plugin\Block\LocalTasksBlock->build() (Line: 221) ...
The bug is in DiffTranslationHooks::menuLocalTasksAlter():
$other_languages = $route_parameter->getTranslationLanguages(FALSE); $langs = array_keys($languages); $other_langs = array_keys($other_languages); $original_lang = array_diff($langs, $other_langs)[0];
array_diff() preserves the original array keys of its first argument ($langs). $langs comes from array_keys($languages), i.e. getTranslationLanguages(TRUE), whose entries are ordered by array_intersect_key($this->getLanguages(), $translations) in core — meaning the order follows the site's configured language weights, not the order in which translations were created on the entity.
As a result, the code silently assumes the entity's original/default language always sits at position 0 of that diff. This only holds when the entity's default language happens to be the first language by weight on the site. If another enabled language has a lower weight than the entity's default language, the differing item ends up at a key other than 0, so array_diff(...)[0] is undefined and evaluates to NULL. That NULL is then passed to getTranslation(), which throws the InvalidArgumentException above, and since this happens while rendering local task tabs (menu block), the exception is uncaught and breaks the whole page.
Steps to reproduce
- Configure a multilingual site with at least two enabled languages, where a language other than the site's "first" content language has a lower weight (e.g. French weight
-10, English weight-9, so French sorts before English inConfigurableLanguageManager::getLanguages()). - Create a new content entity (e.g. an article/node) in the language that does not have the lowest weight (e.g. English, in the example above).
- Save and publish the entity.
- Go to the "Translate" tab and add a translation in the other language (e.g. French).
- Save the translation.
Expected: the translation saves normally and the page reloads with the usual local tasks (including the diff_translation comparison tab).
Actual: an uncaught InvalidArgumentException: Invalid translation language () specified. is thrown from menuLocalTasksAlter() while building the local tasks block, and the site shows "The website encountered an unexpected error."
Proposed resolution
Do not rely on the numeric key 0 of the array_diff() result, since array_diff() preserves the original (non-reindexed) keys of its first argument. Use reset() to get the first remaining value regardless of its key:
$other_languages = $route_parameter->getTranslationLanguages(FALSE); $langs = array_keys($languages); $other_langs = array_keys($other_languages); $original_langs = array_diff($langs, $other_langs); $original_lang = reset($original_langs);
A patch implementing this fix is attached.
Remaining tasks
- Review and confirm the fix.
- Consider adding a defensive check in case
$original_lang(or$other_langs[0]) is empty, to fail gracefully (skip adding the diff tab) instead of throwing, in case the assumption of "exactly one non-translated default language" is ever violated. - Add a test covering a site where the entity's default language is not the lowest-weighted enabled language.
User interface changes
None, beyond fixing the fatal error that currently prevents saving a translation and displaying local tasks in this scenario.
API changes
None.
Data model changes
None.
Issue fork diff_translation-3612523
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
ilyna commented