-
-
Notifications
You must be signed in to change notification settings - Fork 749
feat: native i18n support for collections #3753
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JonathanXDR
wants to merge
51
commits into
nuxt:main
Choose a base branch
from
JonathanXDR:feature/better-i18n
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 38 commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
5e292e3
feat: add i18n support for collections
JonathanXDR fe7981f
feat: add auto-config and locale utilities
JonathanXDR ab89991
feat: refine collection queries and result sorting
JonathanXDR 11b6f6f
refactor: replace defu deep-merge with shallow spread
JonathanXDR e795838
refactor: replace regexp with string operations for locale stem stripβ¦
JonathanXDR 0ccf8a9
feat: add sorted array merge for locale fallback results
JonathanXDR fa1a21c
fix: optimize locale query with select and conditional merge strategy
JonathanXDR 0f567dd
chore: remove prepare script
JonathanXDR 1280746
feat: auto-detect locale from @nuxtjs/i18n context
JonathanXDR 5900912
fix: skip auto-locale in locale listing and optimize default locale qβ¦
JonathanXDR ef57d23
feat: add stem() method with automatic source prefix resolution
JonathanXDR f8491fc
fix: exclude collection name from stem prefix computation
JonathanXDR b7d612f
feat: add useQueryCollection composable
JonathanXDR bc19995
refactor: include all query params in useQueryCollection cache key
JonathanXDR ecf01b8
fix: expose localeExplicitlySet in params for accurate cache keys
JonathanXDR 93ae75e
fix: ensure stem in locale fallback select and fix dev watcher key maβ¦
JonathanXDR 04f13d3
refactor: replay ops in useQueryCollection for reactive locale
JonathanXDR 11b5300
refactor: extract watchSources helper and include fallback in cache key
JonathanXDR b6c6ebb
feat: add generic type override to useQueryCollection
JonathanXDR 21bef61
fix: use function key and raw locale ref in useQueryCollection
JonathanXDR b922036
perf: track key params inline instead of replaying temp query builder
JonathanXDR 78f9512
fix: serialize andWhere/orWhere conditions for deterministic cache keys
JonathanXDR cb9e218
fix: deep-merge locale overrides for data collections
JonathanXDR 2ee227d
feat: add defuByIndex merge strategy for array fields
JonathanXDR 0f69353
refactor: rewrite defuByIndex with explicit loop and add array merge β¦
JonathanXDR a5660b1
fix: apply index-based array merge recursively in defuByIndex
JonathanXDR 0837c22
test: add edge case tests for defuByIndex merge strategy
JonathanXDR 0c74057
docs: rewrite integration guide and add useQueryCollection and queryCβ¦
JonathanXDR f7e61cd
fix: quote count field names and reject newlines in queries
JonathanXDR e38580b
fix: handle doubled-quote escapes in SQL string parser
JonathanXDR dded51b
docs: add i18n sections to collection definition, YAML, and JSON pages
JonathanXDR da8bf59
fix: address PR review feedback
JonathanXDR 111a918
fix: resolve TypeScript strict-mode errors in i18n code paths
JonathanXDR 6ea2702
fix: differentiate count cache keys, clean stale locale variants, andβ¦
JonathanXDR a576ca0
fix: preserve doubled-quote escapes in cleanupQuery output
JonathanXDR 301ed5e
fix: correct count with locale fallback and strip injected stem field
JonathanXDR a44b89e
fix: use deep merge for all collection types and replace body AST whoβ¦
JonathanXDR aaf195d
fix: align useQueryCollection return types with useAsyncData signature
JonathanXDR b5e4767
fix: bypass pagination and ensure counted field in locale fallback count
JonathanXDR cb576f2
fix: add fallback locale detection path for alternative i18n context β¦
JonathanXDR 19d4dae
test: add security and defuByIndex edge case tests
JonathanXDR 6fbf6ae
fix: use port 0 in i18n integration test to avoid EADDRINUSE on CI
JonathanXDR 28817cf
refactor: extract expandI18nData and detectLocaleFromPath into utils
JonathanXDR a2a4905
refactor: deduplicate expansion logic in module and dev watcher
JonathanXDR 8dcb794
refactor: use detectLocaleFromPath in content parser and add query buβ¦
JonathanXDR 6ba6f63
fix: clean up bare row in dev watcher when i18n is added to a file
JonathanXDR 3f7291c
fix: handle multi-fragment dump entries when splicing collection updates
JonathanXDR 45ef233
fix: use correct key variable and capture source locale before mutation
JonathanXDR 169fc56
fix: use default type parameter in generateCollectionLocales generic
JonathanXDR 99e6942
fix: add non-null assertions to satisfy strict type checks
JonathanXDR 61c15ba
fix: simplify type casting in generateCollectionLocales
JonathanXDR File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| --- | ||
| title: useQueryCollection | ||
| description: The useQueryCollection composable wraps queryCollection with useAsyncData for automatic caching and locale reactivity. | ||
| --- | ||
|
|
||
| ## Usage | ||
|
|
||
| `useQueryCollection` provides the same chainable API as `queryCollection`, but wraps execution in `useAsyncData` with automatic cache key generation and locale-reactive re-fetching. | ||
|
|
||
| ```vue [pages/technologies.vue] | ||
| <script setup> | ||
| const { data: techs } = await useQueryCollection('technologies').all() | ||
| </script> | ||
| ``` | ||
|
|
||
| ::warning | ||
| `useQueryCollection` must be called in a Vue component setup context, just like `useAsyncData` and `useFetch`. It cannot be called in event handlers, watchers, or lifecycle hooks. | ||
| :: | ||
|
|
||
| ## API | ||
|
|
||
| ### Type | ||
|
|
||
| ```ts | ||
| function useQueryCollection<R = never, T extends keyof Collections = keyof Collections>( | ||
| collection: T | ||
| ): UseQueryCollectionBuilder<R, T> | ||
| ``` | ||
|
|
||
| The optional generic `R` overrides the return type. When omitted, the collection's generated type is used. | ||
|
|
||
| ### Methods | ||
|
|
||
| `useQueryCollection` supports all the same chainable methods as `queryCollection`: | ||
|
|
||
| - `.where(field, operator, value)` | ||
| - `.andWhere(groupFactory)` | ||
| - `.orWhere(groupFactory)` | ||
| - `.order(field, direction)` | ||
| - `.select(...fields)` | ||
| - `.skip(n)` | ||
| - `.limit(n)` | ||
| - `.path(path)` | ||
| - `.stem(stem)` | ||
| - `.locale(locale, opts?)` | ||
|
|
||
| ### Terminal Methods | ||
|
|
||
| Terminal methods execute the query and return `AsyncData`: | ||
|
|
||
| - `.all()` β returns `AsyncData<R[], NuxtError>` | ||
| - `.first()` β returns `AsyncData<R | null, NuxtError>` | ||
| - `.count(field?, distinct?)` β returns `AsyncData<number, NuxtError>` | ||
|
|
||
| ## Locale Reactivity | ||
|
|
||
| When `@nuxtjs/i18n` is installed and the collection has `i18n` configured, `useQueryCollection` automatically: | ||
|
|
||
| 1. Detects the current locale | ||
| 2. Includes it in the cache key | ||
| 3. Watches the locale ref for changes | ||
| 4. Re-fetches content when the locale changes (no page reload needed) | ||
|
|
||
| ```vue [app/layouts/default.vue] | ||
| <script setup> | ||
| // Automatically updates when user switches locale | ||
| const { data: navbar } = await useQueryCollection('navigation').stem('navbar').first() | ||
| </script> | ||
| ``` | ||
|
|
||
| ## Type Override | ||
|
|
||
| Use the generic parameter to override the return type when the collection's generated type doesn't match your component's expected interface: | ||
|
|
||
| ```vue [pages/technologies.vue] | ||
| <script setup> | ||
| import type { CardItemType } from '#shared/types/components/card-item' | ||
|
|
||
| const { data: cards } = await useQueryCollection<CardItemType>('technologies').all() | ||
| // cards is Ref<CardItemType[]> | ||
| </script> | ||
| ``` | ||
|
|
||
| ## Examples | ||
|
|
||
| ### Single Item by Stem | ||
|
|
||
| ```vue | ||
| <script setup> | ||
| const { data: about } = await useQueryCollection('sections').stem('about').first() | ||
| </script> | ||
| ``` | ||
|
|
||
| ### Filtered and Ordered | ||
|
|
||
| ```vue | ||
| <script setup> | ||
| const { data: posts } = await useQueryCollection('blog') | ||
| .where('published', '=', true) | ||
| .order('date', 'DESC') | ||
| .limit(10) | ||
| .all() | ||
| </script> | ||
| ``` | ||
|
|
||
| ### With Explicit Locale | ||
|
|
||
| ```vue | ||
| <script setup> | ||
| // Explicit locale disables auto-detection and locale watching | ||
| const { data: frDocs } = await useQueryCollection('docs') | ||
| .locale('fr', { fallback: 'en' }) | ||
| .all() | ||
| </script> | ||
| ``` |
100 changes: 100 additions & 0 deletions
100
docs/content/docs/4.utils/6.query-collection-locales.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| --- | ||
| title: queryCollectionLocales | ||
| description: Query all locale variants of a content item for language switchers and hreflang tags. | ||
| --- | ||
|
|
||
| ## Usage | ||
|
|
||
| `queryCollectionLocales` returns all locale variants for a given content stem. This is useful for building language switchers, generating hreflang SEO tags, and implementing `defineI18nRoute()` with `@nuxtjs/i18n`. | ||
|
|
||
| ```vue [app/components/LanguageSwitcher.vue] | ||
| <script setup> | ||
| const locales = await queryCollectionLocales('docs', 'docs/getting-started') | ||
| </script> | ||
|
|
||
| <template> | ||
| <nav> | ||
| <NuxtLink v-for="entry in locales" :key="entry.locale" :to="entry.path"> | ||
| {{ entry.locale }} | ||
| </NuxtLink> | ||
| </nav> | ||
| </template> | ||
| ``` | ||
|
|
||
| ::tip | ||
| `queryCollectionLocales` bypasses automatic locale filtering β it always returns all locale variants regardless of the current locale. | ||
| :: | ||
|
|
||
| ## API | ||
|
|
||
| ### Type | ||
|
|
||
| ```ts | ||
| // Client-side (auto-imported) | ||
| function queryCollectionLocales<T extends keyof Collections>( | ||
| collection: T, | ||
| stem: string | ||
| ): Promise<ContentLocaleEntry[]> | ||
|
|
||
| // Server-side | ||
| function queryCollectionLocales<T extends keyof Collections>( | ||
| event: H3Event, | ||
| collection: T, | ||
| stem: string | ||
| ): Promise<ContentLocaleEntry[]> | ||
|
|
||
| interface ContentLocaleEntry { | ||
| locale: string | ||
| stem: string | ||
| path?: string // Only for page collections | ||
| title?: string // Only for page collections | ||
| } | ||
| ``` | ||
|
|
||
| ### Parameters | ||
|
|
||
| - `collection`: The collection name | ||
| - `stem`: The content stem (e.g. `'docs/getting-started'`) | ||
|
|
||
| ## Server Usage | ||
|
|
||
| ```ts [server/api/locales.ts] | ||
| export default eventHandler(async (event) => { | ||
| const stem = getQuery(event).stem as string | ||
| return await queryCollectionLocales(event, 'docs', stem) | ||
| }) | ||
| ``` | ||
|
|
||
| ## Use Cases | ||
|
|
||
| ### Language Switcher | ||
|
|
||
| ```vue | ||
| <script setup> | ||
| const route = useRoute() | ||
| // Use the page's stem (without locale prefix) β path-based locale detection | ||
| // already strips the locale prefix, so the stem is shared across locales | ||
| const { data: page } = await useAsyncData(route.path, () => { | ||
| return queryCollection('docs').path(route.path).first() | ||
| }) | ||
| const locales = page.value?.stem | ||
| ? await queryCollectionLocales('docs', page.value.stem) | ||
| : [] | ||
| </script> | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| ### Hreflang Meta Tags | ||
|
|
||
| ```vue | ||
| <script setup> | ||
| const locales = await queryCollectionLocales('docs', 'docs/getting-started') | ||
|
|
||
| useHead({ | ||
| link: locales.map(entry => ({ | ||
| rel: 'alternate', | ||
| hreflang: entry.locale, | ||
| href: entry.path, | ||
| })), | ||
| }) | ||
| </script> | ||
| ``` | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.