Skip to content

feat: add language_hierarchy for multilingual translation fallback - #76

Merged
jjroelofs merged 4 commits into
1.xfrom
feat/language-hierarchy-fallback
Jul 9, 2026
Merged

feat: add language_hierarchy for multilingual translation fallback#76
jjroelofs merged 4 commits into
1.xfrom
feat/language-hierarchy-fallback

Conversation

@jjroelofs

Copy link
Copy Markdown
Contributor

Summary

  • Adds the language_hierarchy contrib module to the multilingual recipe, enabling per-language fallback chains for untranslated content
  • All languages fall back to English by default; nl-be (Vlaams) falls back to Dutch first, then English
  • Views with interface-language filters (blog, case_studies, news) are automatically swapped to the hierarchy fallback filter during install, so listing pages show English content when no translation exists instead of empty results
  • Removes the en-gb footer translation (identical to English; now falls back naturally)
  • Adds nl-be language entity config with label "Vlaams"
  • Removes obsolete translated menu link content for deleted pages
  • Adds nl-be homepage translation

Test plan

  • Fresh install with all recipes and multiple languages enabled
  • Verify /nl/blog, /de/case-studies, /ar/news show English content (fallback) instead of empty listings
  • Verify /nl-be pages show Dutch content for entities without nl-be translations (footer, menu links)
  • Verify /en-gb footer falls back to English without a separate en-gb translation
  • Verify nl-be appears as "Vlaams" in the language switcher
  • Verify homepage renders correctly in all 22 languages

jjroelofs added 3 commits July 9, 2026 12:30
Add the language_hierarchy module to the multilingual recipe so that
untranslated content falls back through a configured language chain
instead of showing empty listings or picking an arbitrary translation.

All languages fall back to English by default; nl-be (Vlaams) falls
back to Dutch first. The en-gb footer translation is removed since
it is identical to the English original and now falls back naturally.

Views with interface-language filters (blog, case_studies, news) are
automatically swapped to the hierarchy fallback filter on install via
hook_install in dxpr_cms_multilingual_content.

Also adds nl-be language entity config with label "Vlaams", removes
obsolete translated menu link content for deleted pages, and adds
the nl-be homepage translation.
The recipe config action `language.entity.*` with setThirdPartySettings
does not apply to language entities created by the installer, because
the multilingual form batch runs after recipe application. Move fallback
chain configuration into hook_install (for languages existing at module
install time) and hook_configurable_language_insert (for languages
created afterwards by the installer batch).
Replace em dashes with colons/semicolons in skill markdown files
and fix excess spaces inside empty braces in recipe YAML configs.

@jjroelofs jjroelofs left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pushing this, the direction makes sense, but I found two correctness issues that can keep the fallback behavior from working reliably.

The main blocker is that nl-be is hard-wired to fall back to nl even when Dutch is not installed, which leaves the Views fallback chain without English. I also think the Views rewrite needs to run somewhere more durable than this module's one-time install hook, because optional recipes/views installed after multilingual will keep the old language filter.

Checks run locally: git diff --check, php -l on the changed PHP files, and YAML parsing of changed YAML files with Ruby. I could not run a Drupal bootstrap/drush validation because this machine's CLI PHP is 8.3.22 and the checkout's Composer platform check requires PHP >= 8.4.

*/
function _dxpr_cms_multilingual_content_configure_fallback_chains(): void {
$overrides = [
'nl-be' => 'nl',

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nl-be is now configured to fall back to nl unconditionally, but this recipe does not guarantee that Dutch is installed. If a site has nl-be without nl, language_hierarchy's query fallback candidates become nl-be, nl, and und, so the new Views filter will not include English content at all. That contradicts the PR summary's nl-be -> nl -> en behavior and leaves /nl-be/... listings empty unless Dutch was also selected. Please either install/configure nl as part of this chain, or choose the default language when nl is absent and update nl-be if Dutch is added later. The same hard-coded map also appears in dxpr_cms_multilingual_content.module and the new language.entity.nl-be.yml config.

/**
* Swaps standard language filters in Views with language_hierarchy fallback.
*/
function _dxpr_cms_multilingual_content_swap_views_filters(): void {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing the Views rewrite only from hook_install() means it only sees views that already exist at the exact moment dxpr_cms_multilingual_content is installed. Optional recipes can be applied later, or in a different order, so views such as News/Case Studies can still be imported with the original langcode interface-language filter and never get the fallback filter. That leaves the very listing pages this PR targets without fallback. This needs to be expressed as recipe config actions for the known views, or handled when relevant view config is inserted/updated, or otherwise made order-independent.

1. nl-be fallback now checks if nl is actually installed before using it;
   falls back to the site default language when nl is absent. When nl is
   added later, nl-be is updated automatically.

2. Added hook_view_insert() so views imported after module install (e.g.
   from recipes applied in a different order) also get the
   language_hierarchy fallback filter swap.

3. Removed hardcoded third_party_settings from language.entity.nl-be.yml;
   the hook handles fallback configuration dynamically.

4. Consolidated duplicated filter-swap logic into a shared helper in
   the .module file, called by both hook_install and hook_view_insert.
@jjroelofs
jjroelofs merged commit 483073c into 1.x Jul 9, 2026
6 checks passed
@jjroelofs
jjroelofs deleted the feat/language-hierarchy-fallback branch July 9, 2026 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant