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:

  1. 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.
  2. 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

  1. Create a recipe that installs language, locale and content_translation modules, imports language.entity.en from the Language module (config: import: language: [language.entity.en]), ships language.entity.es.yml, ships language.content_settings.node.page.yml with content translation enabled, and ships one page node in content/node/ with default_langcode: en, an English title, and a Spanish title under translations.es.
  2. 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.
  3. 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

Command icon 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

gábor hojtsy created an issue. See original summary.

gábor hojtsy’s picture

Issue summary: View changes
gábor hojtsy’s picture