Skip to content

fix: walk vue-i18n's actual fallback chain instead of approximating it - #4128

Draft
Vincentdevreede wants to merge 4 commits into
nuxt-modules:mainfrom
Vincentdevreede:bugfix/locale-fallback-chain-walk
Draft

fix: walk vue-i18n's actual fallback chain instead of approximating it#4128
Vincentdevreede wants to merge 4 commits into
nuxt-modules:mainfrom
Vincentdevreede:bugfix/locale-fallback-chain-walk

Conversation

@Vincentdevreede

@Vincentdevreede Vincentdevreede commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Builds on the changes from #4124.
That PR fixed one specific case: a region tagged locale like en-US implicitly falls back to its base tag en before vue-i18n even looks at fallbackLocale, and lazy loading never fetched that base tag's messages unless it was also listed explicitly.
Testing that fix directly against vue-i18n's real internal resolver(fallbackWithLocaleChain in @intlify/core-base) showed it only covered part of the actual behavior. vue-i18n'sreal algorithm is a per-tag walk, not base tags unioned with whatever's in fallbackLocale:

  • A fallbackLocale map entry keyed by the exact tag being resolved intercepts the walk before it ever reaches that tag's own base tag.
  • A map entry keyed by a tag reached mid-walk intercepts from there instead.
  • Once a fallback entry redirects the walk, every sibling still left in that block gets abandoned, never visited.
  • A trailing ! on any entry stops the walk at that tag, skipping its own base tag descent, scoped to just that one redirect.
  • The default block still applies afterward regardless of whether the walk was redirected.

The previous approach could both under fetch (miss locales vue-i18n would actually reach) and over fetch (pull in locales vue-i18n would never touch).
This replaces that approximation with a small walk (walkLocale, walkBlock, walkFallbackChain in src/runtime/shared/locales.ts) that mirrors vue-i18n's real chain building closely enough to answer which locales it will actually reach, without taking a runtime dependency on that internal.

Also documents the implicit fallback behavior and how to control it in docs/content/docs/02.guide/11.locale-fallback.md, including the ! suffix and its scoping (it stops the redirect target's own walk, not the whole locale's fallback everywhere).

Summary by CodeRabbit

  • New Features

    • Improved locale fallback resolution for regional and base locales.
    • Added support for explicit fallback mappings, default fallbacks, suppression markers, redirect scoping, and cycle prevention.
    • Preserved correct fallback ordering while avoiding invalid or duplicate locale targets.
  • Documentation

    • Added guidance and examples explaining implicit locale fallback, explicit overrides, default entries, and suppression behavior.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The locale fallback resolver now builds ordered fallback chains from locale hierarchy, explicit fallback maps, default targets, and suppression markers. It prevents cycles, preserves explicit fallback tags, filters implicit tags against configured locales, and removes duplicate or primary locales. A new test suite covers the resolution rules. Documentation explains base-locale fallback, redirect behavior, and ! target syntax.

Estimated code review effort: 4 (Complex) | ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: implementing Vue I18n's actual fallback-chain traversal.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/content/docs/02.guide/11.locale-fallback.md`:
- Around line 29-33: Update the paragraph under “Implicit fallback for region
tags” to state that `en-US` normally tries `en` before `fallbackLocale`, except
when an exact `fallbackLocale` entry for `en-US` is configured, which takes
precedence and prevents the base-language walk.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3d68acf9-93c5-4e43-bb59-d45d0a7a7d5f

📥 Commits

Reviewing files that changed from the base of the PR and between ccee57f and 9f0ed82.

📒 Files selected for processing (3)
  • docs/content/docs/02.guide/11.locale-fallback.md
  • src/runtime/shared/locales.ts
  • test/locales.test.ts

Comment on lines +29 to +33
## Implicit fallback for region tags

Vue I18n also falls back a region tagged locale to its base language tag on its own, before it even looks at `fallbackLocale`. A missing key on `en-US` tries `en` first, regardless of what `fallbackLocale` says. Nuxt i18n lazy loads that base tag's messages too, so this keeps working, as long as the base tag is also a configured locale. If it isn't configured, there's no file to load for it and nothing changes.

To stop a locale from trying its own base tag, add a `fallbackLocale` entry keyed on that exact locale. Once such an entry exists, it takes over before the base tag walk ever starts:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the exact-entry exception.

Line 31 says that en-US tries en regardless of fallbackLocale. An exact fallbackLocale['en-US'] entry prevents that walk, as Line 33 states. State this exception in Line 31.

Proposed fix
-Vue I18n also falls back a region tagged locale to its base language tag on its own, before it even looks at `fallbackLocale`. A missing key on `en-US` tries `en` first, regardless of what `fallbackLocale` says.
+Vue I18n also falls back from a region-tagged locale to its base language tag before it uses `default` or entries for other locales. If `fallbackLocale` has no exact `en-US` entry, a missing key on `en-US` tries `en` first.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## Implicit fallback for region tags
Vue I18n also falls back a region tagged locale to its base language tag on its own, before it even looks at `fallbackLocale`. A missing key on `en-US` tries `en` first, regardless of what `fallbackLocale` says. Nuxt i18n lazy loads that base tag's messages too, so this keeps working, as long as the base tag is also a configured locale. If it isn't configured, there's no file to load for it and nothing changes.
To stop a locale from trying its own base tag, add a `fallbackLocale` entry keyed on that exact locale. Once such an entry exists, it takes over before the base tag walk ever starts:
## Implicit fallback for region tags
Vue I18n also falls back from a region-tagged locale to its base language tag before it uses `default` or entries for other locales. If `fallbackLocale` has no exact `en-US` entry, a missing key on `en-US` tries `en` first, regardless of what `fallbackLocale` says. Nuxt i18n lazy loads that base tag's messages too, so this keeps working, as long as the base tag is also a configured locale. If it isn't configured, there's no file to load for it and nothing changes.
To stop a locale from trying its own base tag, add a `fallbackLocale` entry keyed on that exact locale. Once such an entry exists, it takes over before the base tag walk ever starts:
🧰 Tools
🪛 LanguageTool

[grammar] ~31-~31: Use a hyphen to join words.
Context: ... tags Vue I18n also falls back a region tagged locale to its base language tag o...

(QB_NEW_EN_HYPHEN)


[style] ~31-~31: To elevate your writing, try using more formal phrasing here.
Context: ...s that base tag's messages too, so this keeps working, as long as the base tag is also a conf...

(CONTINUE_TO_VB)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/content/docs/02.guide/11.locale-fallback.md` around lines 29 - 33,
Update the paragraph under “Implicit fallback for region tags” to state that
`en-US` normally tries `en` before `fallbackLocale`, except when an exact
`fallbackLocale` entry for `en-US` is configured, which takes precedence and
prevents the base-language walk.

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