Problem/Motivation
This makes it impossible to build Umami-style demonstrations with multilingual default content.
Default content can ship with translations. A recipe or site template can ship a node written in English with a Spanish translation, and Drupal is expected to end up with both. But Drupal only ends up with both currently if the site has the language of the default content's default configured.
The importer decides the language of each entity while importing it, in \Drupal\Core\DefaultContent\Importer::verifyNormalizedLanguage(). When the content's language is not known to the site, or when the site is being installed and its default language differs from the content's language, the importer looks through the shipped translations, takes the first one whose language the site knows, and makes that the entity's default language:
foreach ($data['translations'] ?? [] as $langcode => $translation_data) {
if ($this->languageManager->getLanguage($langcode)) {
$data['_meta']['default_langcode'] = $langcode;
$data['default'] = \array_merge($data['default'], $translation_data);
unset($data['translations'][$langcode]);
$use_default = FALSE;
break;
}
}
Two things are wrong:
- Bug 1: The original values are lost. The translation's values are merged over the default values and the translation entry is removed, but the original values are never added back as a translation in their own language. Content shipped as English with a Spanish translation, imported on a site whose default language is Spanish, ends up Spanish only. The English text is gone, even when English is a language on the site.
- Bug 2: The first known translation wins, not the one in the site default language. Content shipped as English with French and Spanish translations, imported on a Spanish site, is imported in French if French happens to be listed first and is a language on the site. This is not exactly a problem in core, but for tools like Drupal Canvas, this breaks the assumption that content is originally created in the site default language. (While I debate that assumption, I think for the user it does make sense that we import content primarily in the site default language when such content is available)
The rest of the file is handled as expected in all cases. After the language check, the importer walks whatever is left under translations and adds each entry as a translation, as long as its language exists on the site. Translations in languages the site does not have are skipped, which is good.
The table compares what happens today with what should happen. The content is shipped as English with a Spanish translation. Additionally the last two rows ship French and Spanish translations, listed in that order.
| Default content shipped | Site default language | Languages on the site | Imported today | Should be imported | |
|---|---|---|---|---|---|
| English, Spanish | English | English, Spanish | English, with Spanish translation | Same | ✅ |
| English, Spanish | Spanish | Spanish only | Spanish only | Same: English cannot be kept on a site that does not have it | ✅ |
| English, Spanish | Spanish | English, Spanish | Spanish only. English is lost. | Spanish, with English translation | Bug 1 |
| English, Spanish | Hungarian | Hungarian, English, Spanish | Spanish only, on a Hungarian site. English is lost. | Spanish, with English translation (picking a real translation over relabeling is unchanged) | Bug 1 |
| English, Spanish | Hungarian | Hungarian only | Hungarian, relabeled English text | Same: the relabeling fallback is out of scope here | ✅ |
| English, French, Spanish | Spanish | English, French, Spanish | French, with Spanish translation. English is lost. | Spanish, with English and French translations | Bugs 1 and 2 |
| English, French, Spanish | Spanish | French, Spanish | French, with Spanish translation | Spanish, with French translation | Bug 2 |
This logic came into core unchanged with the recipes API in #3439923: Add recipes api as experimental API to core, from the contrib Default Content module, where it was added to stop installs from crashing when the chosen install language does not match the demo content (#2900089: InvalidArgumentException: Invalid translation language error when selected installation language does not match demo content language).
It matters more now that site templates could ship real multilingual demo content using default content.
I proposed a polyfill for Drupal CMS to work around this for now in https://git.drupalcode.org/project/drupal_cms/-/work_items/3591452
Steps to reproduce
- Create a recipe that installs
language,localeandcontent_translationmodules, importslanguage.entity.enfrom the Language module (config: import: language: [language.entity.en]), shipslanguage.entity.es.yml, shipslanguage.content_settings.node.page.ymlwith content translation enabled, and ships one page node incontent/node/withdefault_langcode: en, an English title, and a Spanish title undertranslations.es. - Install Drupal in Spanish with that recipe, for example with
php core/scripts/drupal install --langcode=es <recipe>or through the installer with?langcode=es. - Load the node.
Expected: the node's default language is Spanish, with the Spanish title, and it has an English translation with the English title.
Actual: the node is Spanish only. There is no English translation and the English title is gone. Any other shipped translation in a language the site has, say French, is imported as a translation as usual.
To see the second problem, add a French translation before the Spanish one in the YAML and ship language.entity.fr.yml as well: the node is imported in French, despite Spanish selected as site default.
Proposed resolution
In Importer::verifyNormalizedLanguage(), when a default content adjustment is needed:
- Prefer the translation whose language is the site default language. Fall back to the first known translation only when there is no translation in the site default.
- When swapping, keep the original default values as a translation in the original language, if that language is a language on the site. Drop them only when the site does not know the language, which is the case the relabeling fallback already handles.
- Leave the relabeling fallback (import as the site default language when no usable translation exists) as it is.
Remaining tasks
TBD
User interface changes
None.
Introduced terminology
None.
API changes
None.
Data model changes
For modules altering the default content on import they will see slightly different default content due to the two bugs fixed.
Release notes snippet
When default content is imported as one of its translations because the site's default language differs from the content's default language, the original text is now kept as a translation instead of being discarded. Also the translation in the site default language will be now used as the content default language.
LLM disclosure
The analysis of the importer and this write-up were drafted with an LLM while working on multilingual site templates for Drupal CMS. I reviewed and edited them and stand by the content.
Issue fork drupal-3622758
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
gábor hojtsyComment #3
gábor hojtsyComment #4
gábor hojtsy