Skip to content

fix: fetch a region-tagged locale's base tag when it's configured - #4124

Open
Vincentdevreede wants to merge 3 commits into
nuxt-modules:mainfrom
Vincentdevreede:docs/locale-fallback-lazy-load
Open

fix: fetch a region-tagged locale's base tag when it's configured#4124
Vincentdevreede wants to merge 3 commits into
nuxt-modules:mainfrom
Vincentdevreede:docs/locale-fallback-lazy-load

Conversation

@Vincentdevreede

@Vincentdevreede Vincentdevreede commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

vue-i18n automatically falls back a region-tagged locale to its base language tag (en-US tries en) before it even looks at fallbackLocale. This is documented, default behavior in vue-i18n itself. Nuxt i18n's lazy loading never fetched that base tag's messages unless it was listed explicitly in fallbackLocale, so this fallback silently did nothing for anyone with a region-tagged locale like en-US alongside en.

getFallbackLocaleCodes now adds a locale's base tag to its fallbacks when that base tag is itself a configured locale, walking multiple dash-separated levels where needed (de-DE-bavariande-DEde). If the base tag isn't configured at all, nothing changes, there's no file to load for it and no reason to widen the lazy-loading footprint for a tag nobody defined.

Also fixes a duplicate entry this introduced: an explicit fallbackLocale entry can name the same locale the base-tag walk already adds on its own (writing {'en-US': ['en']} achieves exactly what en-US's implicit en already does), so the result is deduped.

Summary

Vue I18n automatically falls back a region-tagged locale to its base language tag (fr-CA tries fr), but that only works for messages already loaded in memory. Since Nuxt i18n lazy-loads locale files, it only fetches what's explicitly listed in fallbackLocale, so that implicit chain silently does nothing unless the base tag is configured too.

I ran into this and initially thought it was a bug, but on reflection I think it's probably intentional: automatically fetching a base tag's file on top of whatever's explicitly configured would mean loading more locale files than the user asked for, which cuts against the whole point of lazy loading. So instead of changing behavior, this PR just documents it, with a callout showing how to get the same effect by listing the base tag yourself in fallbackLocale.

@BobbieGoede can you confirm that's the intended reasoning? If not, and this is actually considered a bug, let me know and I'll put together a code fix instead of the docs note.

This is an older summary from before I force-pushed the changes. I’m just keeping it here for reference.

Summary by CodeRabbit

  • Bug Fixes

    • Improved locale fallback handling for regional and multi-level locale tags.
    • Regional locales now correctly include configured base locales in fallback results.
    • Prevented duplicate fallback locales across disabled, array-based, and mapped fallback configurations.
  • Tests

    • Added coverage for regional, plain, multi-level, missing-base, and duplicate locale scenarios.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

getFallbackLocaleCodes now derives configured base locale tags from regional locale codes and adds them before configured fallback values across fallback modes. Tests cover regional and plain locales, multi-level tags, disabled and array-based fallbacks, absent base locales, and duplicate suppression.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding the configured base tag as a fallback for region-tagged locales.
✨ 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.

@BobbieGoede

Copy link
Copy Markdown
Member

This looks like a bug, at the same time I can imagine users are already defining their fallback locale files using the file arrays (e.g. files: ['en.json', 'en-US.json']). Not sure if we're already deduplicating file chains, otherwise we may need to take that into account when fixing this.

Explicitly configuring fallbacks does work (example below), so I guess we only need to parse the codes and add these as fallbackLocales if such locales exist.

// in nuxt.config.ts i18n config
export default defineNuxtConfig({
  // ...
  i18n: {
    locales: [
      { code: 'en', language: 'en', file: 'en.json', name: 'English' },
      { code: 'en-US', language: 'en-US', file: 'en-US.json', name: 'English (US)' },
    ]
  }
})
// i18n.config.ts
export default defineI18nConfig(() => {
  return {
    fallbackLocale: {
      'en-US': ['en'], // en.json is loaded when locale is en-US
    },
  }
})

I ran into this and initially thought it was a bug, but on reflection I think it's probably intentional: automatically fetching a base tag's file on top of whatever's explicitly configured would mean loading more locale files than the user asked for, which cuts against the whole point of lazy loading.

This is true for some projects (with dynamic locale files or files that need to be run from client-side/nuxt), but most projects (static files) will fetch through the messages endpoint where the server already has the locale messages loaded/merged.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/runtime/shared/locales.ts (1)

39-56: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Deduplicate the final fallback chain.

Line 40 excludes base tags that are already requested locales. It does not exclude repeated derived base tags or a derived base tag that is also in fallbackLocale.

For example, getFallbackLocaleCodes({ 'en-US': ['en'] }, ['en-US']) returns ['en', 'en']. Two regional locales with the same configured base tag also repeat that base tag. Return an ordered unique list after all fallback sources are appended. Add regressions for both cases.

Proposed fix
+function uniqueLocaleCodes(codes: string[]) {
+  return [...new Set(codes)]
+}
+
 export function getFallbackLocaleCodes(fallback: FallbackLocale, locales: string[]): string[] {
   const baseTags = locales.flatMap(configuredBaseTags).filter(tag => !locales.includes(tag))
 
-  if (fallback === false) { return baseTags }
-  if (isArray(fallback)) { return [...baseTags, ...fallback] }
+  if (fallback === false) { return uniqueLocaleCodes(baseTags) }
+  if (isArray(fallback)) { return uniqueLocaleCodes([...baseTags, ...fallback]) }
 
   const fallbackLocales: string[] = [...baseTags]
   // ...
-  return fallbackLocales
+  return uniqueLocaleCodes(fallbackLocales)
 }
🤖 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 `@src/runtime/shared/locales.ts` around lines 39 - 56, Update
getFallbackLocaleCodes to deduplicate the completed fallback chain after
combining derived base tags, configured fallback values, and string/object
fallbacks, while preserving first-occurrence order. Add regressions covering a
fallback value duplicating a derived base tag and multiple regional locales
producing the same base tag.
🤖 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.

Outside diff comments:
In `@src/runtime/shared/locales.ts`:
- Around line 39-56: Update getFallbackLocaleCodes to deduplicate the completed
fallback chain after combining derived base tags, configured fallback values,
and string/object fallbacks, while preserving first-occurrence order. Add
regressions covering a fallback value duplicating a derived base tag and
multiple regional locales producing the same base tag.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 56dc3f3b-e046-4159-a66f-6e8cb95e51e6

📥 Commits

Reviewing files that changed from the base of the PR and between 55fe621 and 14a12db.

📒 Files selected for processing (2)
  • src/runtime/shared/locales.ts
  • test/locales.test.ts

@Vincentdevreede Vincentdevreede changed the title docs: note that lazy loading skips vue-i18n's implicit base-tag fallback fix: fetch a region-tagged locale's base tag when it's configured Aug 6, 2026
@Vincentdevreede

Copy link
Copy Markdown
Contributor Author

@BobbieGoede Updated this to an actual fix rather than just a docs note. getFallbackLocaleCodes now adds a region-tagged locale's base tag as an implicit fallback when that base tag is itself configured (e.g. en alongside en-US), matching vue-i18n's own behavior.

@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 `@src/runtime/shared/locales.ts`:
- Around line 46-53: Update the conditional formatting in the fallback handling
block so each else if follows the preceding closing brace on the same line,
resolving the ESLint brace-style violations without changing behavior.
🪄 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: 1facb29a-e4ec-4408-8026-27f19e63ed82

📥 Commits

Reviewing files that changed from the base of the PR and between 14a12db and 5b9b4e2.

📒 Files selected for processing (2)
  • src/runtime/shared/locales.ts
  • test/locales.test.ts

Comment thread src/runtime/shared/locales.ts Outdated
@Vincentdevreede
Vincentdevreede force-pushed the docs/locale-fallback-lazy-load branch from 5b9b4e2 to 7579b99 Compare August 6, 2026 09:20
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@Vincentdevreede
Vincentdevreede force-pushed the docs/locale-fallback-lazy-load branch from 7579b99 to 8081eb3 Compare August 6, 2026 09:46
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@Vincentdevreede

Copy link
Copy Markdown
Contributor Author

@BobbieGoede Opened #4128 on top of this branch.
Testing this fix against vue-i18n's real chain building turned up a few more cases it doesn't cover yet: a fallbackLocale map entry can redirect the walk before it ever reaches a base tag, redirecting abandons any other entries still left in that block, and there's a ! suffix that stops a redirect target from walking its own base tag.

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.

2 participants