Skip to content

feat: native i18n support for collections#3753

Open
JonathanXDR wants to merge 69 commits into
nuxt:mainfrom
JonathanXDR:feature/better-i18n
Open

feat: native i18n support for collections#3753
JonathanXDR wants to merge 69 commits into
nuxt:mainfrom
JonathanXDR:feature/better-i18n

Conversation

@JonathanXDR

@JonathanXDR JonathanXDR commented Mar 29, 2026

Copy link
Copy Markdown

🔗 Linked issue

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

Adds native i18n support to @nuxt/content, so collections are defined once with i18n: true and content files use an inline i18n section for translations. Only translated strings are specified, everything else is preserved from the default locale. queryCollection automatically detects the current locale from @nuxtjs/i18n and returns locale-resolved content with zero configuration.

Features

  • Define collections once: i18n: true auto-detects locales from @nuxtjs/i18n, or pass { locales, defaultLocale } explicitly
  • Inline translations: YAML/JSON i18n section with per-locale overrides; arrays merged by index via defuByIndex (preserves untranslated fields at every nesting level)
  • Automatic locale detection: queryCollection reads the current locale from @nuxtjs/i18n transparently (client: $i18n.locale, server: event.context.nuxtI18n); default locale uses single query, non-default uses two-query fallback merge
  • useQueryCollection composable: wraps queryCollection + useAsyncData with auto cache keys, locale-reactive re-fetching, and generic type override
  • queryCollectionLocales: returns all locale variants for a content item (for language switchers and hreflang SEO)
  • .stem() convenience method: query data collections by filename without knowing the source directory prefix
  • .locale() explicit override: manual locale control with optional fallback
  • Translator change tracking: non-default locale items include _i18nSourceHash for detecting outdated translations
  • HMR support: inline i18n expansion in dev mode with stale locale cleanup on file save

Security

  • assertSafeQuery rejects newlines (prevents multi-statement injection)
  • COUNT() field names are quoted (prevents field injection)
  • cleanupQuery string parser rewritten with proper state machine (fixes triple-quote bypass)

Known Limitations

The following items were requested in #3579 but are not included in this PR.

  • Nuxt Studio integration: Language select and translation helper UI
  • DeepL/Google Translate integration: Automatic translation of content fields
  • Translatable slugs with different filenames: When locales use different filenames (e.g., en/products.md vs de/produkte.md), queryCollectionLocales cannot link them because the stems differ. Same-filename content across locale directories works. This requires coordination with @nuxtjs/i18n (see nuxt-modules/i18n#3028).
  • Locale-specific field visibility: Fields can be added per locale via the i18n section, but there is no mechanism to hide a default-locale field for a specific locale.

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

Tests

  • 24 unit tests: inline expansion, path detection, source hash, defuByIndex edge cases
  • 24 unit tests: query builder: locale, stem, fallback, auto-locale, count
  • 36 unit tests: assertSafeQuery security validation
  • 17 integration tests: full i18n fixture with path-based + inline content

Documentation

  • Rewrote i18n integration guide with full setup, inline translations, and all new APIs
  • Added useQueryCollection and queryCollectionLocales utility docs
  • Added .locale() and .stem() to query-collection API reference
  • Added i18n option to collection definition docs
  • Added inline i18n examples to YAML and JSON file format docs

Comment thread src/runtime/internal/locales.ts Outdated
@JonathanXDR JonathanXDR requested a review from edimitchel April 3, 2026 14:59
@captnCC

captnCC commented Apr 24, 2026

Copy link
Copy Markdown

This feature is really great and well needed for nuxt content! Thanks @JonathanXDR for the work.
Is there any plan on the way forward with PR or points that need work and could be supported?

@JonathanXDR

Copy link
Copy Markdown
Author

@captnCC Thanks for the kind words :D
First, we'd need a review from the Nuxt Content maintainer, @farnabaz. He's best positioned to confirm whether this approach works for him as is or if any adjustments or further work are needed.

@narr07

narr07 commented Apr 30, 2026

Copy link
Copy Markdown

can i test with this pr?

@captnCC Thanks for the kind words :D First, we'd need a review from the Nuxt Content maintainer, @farnabaz. He's best positioned to confirm whether this approach works for him as is or if any adjustments or further work are needed.

can i test this with pr?

@JonathanXDR

Copy link
Copy Markdown
Author

can i test with this pr?

@captnCC Thanks for the kind words :D First, we'd need a review from the Nuxt Content maintainer, @farnabaz. He's best positioned to confirm whether this approach works for him as is or if any adjustments or further work are needed.

can i test this with pr?

@narr07 Yes sure!

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.

Support i18n inline translations Implement Nuxt Content

5 participants