Skip to content
Open
Show file tree
Hide file tree
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 Mar 28, 2026
fe7981f
feat: add auto-config and locale utilities
JonathanXDR Mar 28, 2026
ab89991
feat: refine collection queries and result sorting
JonathanXDR Mar 28, 2026
11b6f6f
refactor: replace defu deep-merge with shallow spread
JonathanXDR Mar 28, 2026
e795838
refactor: replace regexp with string operations for locale stem strip…
JonathanXDR Mar 28, 2026
0ccf8a9
feat: add sorted array merge for locale fallback results
JonathanXDR Mar 28, 2026
fa1a21c
fix: optimize locale query with select and conditional merge strategy
JonathanXDR Mar 28, 2026
0f567dd
chore: remove prepare script
JonathanXDR Mar 28, 2026
1280746
feat: auto-detect locale from @nuxtjs/i18n context
JonathanXDR Mar 28, 2026
5900912
fix: skip auto-locale in locale listing and optimize default locale q…
JonathanXDR Mar 28, 2026
ef57d23
feat: add stem() method with automatic source prefix resolution
JonathanXDR Mar 28, 2026
f8491fc
fix: exclude collection name from stem prefix computation
JonathanXDR Mar 28, 2026
b7d612f
feat: add useQueryCollection composable
JonathanXDR Mar 28, 2026
bc19995
refactor: include all query params in useQueryCollection cache key
JonathanXDR Mar 28, 2026
ecf01b8
fix: expose localeExplicitlySet in params for accurate cache keys
JonathanXDR Mar 28, 2026
93ae75e
fix: ensure stem in locale fallback select and fix dev watcher key ma…
JonathanXDR Mar 28, 2026
04f13d3
refactor: replay ops in useQueryCollection for reactive locale
JonathanXDR Mar 28, 2026
11b5300
refactor: extract watchSources helper and include fallback in cache key
JonathanXDR Mar 28, 2026
b6c6ebb
feat: add generic type override to useQueryCollection
JonathanXDR Mar 28, 2026
21bef61
fix: use function key and raw locale ref in useQueryCollection
JonathanXDR Mar 28, 2026
b922036
perf: track key params inline instead of replaying temp query builder
JonathanXDR Mar 28, 2026
78f9512
fix: serialize andWhere/orWhere conditions for deterministic cache keys
JonathanXDR Mar 28, 2026
cb9e218
fix: deep-merge locale overrides for data collections
JonathanXDR Mar 28, 2026
2ee227d
feat: add defuByIndex merge strategy for array fields
JonathanXDR Mar 28, 2026
0f69353
refactor: rewrite defuByIndex with explicit loop and add array merge …
JonathanXDR Mar 28, 2026
a5660b1
fix: apply index-based array merge recursively in defuByIndex
JonathanXDR Mar 28, 2026
0837c22
test: add edge case tests for defuByIndex merge strategy
JonathanXDR Mar 28, 2026
0c74057
docs: rewrite integration guide and add useQueryCollection and queryC…
JonathanXDR Mar 29, 2026
f7e61cd
fix: quote count field names and reject newlines in queries
JonathanXDR Mar 29, 2026
e38580b
fix: handle doubled-quote escapes in SQL string parser
JonathanXDR Mar 29, 2026
dded51b
docs: add i18n sections to collection definition, YAML, and JSON pages
JonathanXDR Mar 29, 2026
da8bf59
fix: address PR review feedback
JonathanXDR Mar 29, 2026
111a918
fix: resolve TypeScript strict-mode errors in i18n code paths
JonathanXDR Mar 29, 2026
6ea2702
fix: differentiate count cache keys, clean stale locale variants, and…
JonathanXDR Mar 29, 2026
a576ca0
fix: preserve doubled-quote escapes in cleanupQuery output
JonathanXDR Mar 29, 2026
301ed5e
fix: correct count with locale fallback and strip injected stem field
JonathanXDR Mar 29, 2026
a44b89e
fix: use deep merge for all collection types and replace body AST who…
JonathanXDR Mar 29, 2026
aaf195d
fix: align useQueryCollection return types with useAsyncData signature
JonathanXDR Mar 29, 2026
b5e4767
fix: bypass pagination and ensure counted field in locale fallback count
JonathanXDR Mar 29, 2026
cb576f2
fix: add fallback locale detection path for alternative i18n context …
JonathanXDR Mar 29, 2026
19d4dae
test: add security and defuByIndex edge case tests
JonathanXDR Mar 29, 2026
6fbf6ae
fix: use port 0 in i18n integration test to avoid EADDRINUSE on CI
JonathanXDR Mar 29, 2026
28817cf
refactor: extract expandI18nData and detectLocaleFromPath into utils
JonathanXDR Mar 31, 2026
a2a4905
refactor: deduplicate expansion logic in module and dev watcher
JonathanXDR Mar 31, 2026
8dcb794
refactor: use detectLocaleFromPath in content parser and add query bu…
JonathanXDR Mar 31, 2026
6ba6f63
fix: clean up bare row in dev watcher when i18n is added to a file
JonathanXDR Mar 31, 2026
3f7291c
fix: handle multi-fragment dump entries when splicing collection updates
JonathanXDR Mar 31, 2026
45ef233
fix: use correct key variable and capture source locale before mutation
JonathanXDR Mar 31, 2026
169fc56
fix: use default type parameter in generateCollectionLocales generic
JonathanXDR Mar 31, 2026
99e6942
fix: add non-null assertions to satisfy strict type checks
JonathanXDR Mar 31, 2026
61c15ba
fix: simplify type casting in generateCollectionLocales
JonathanXDR Apr 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/content/docs/2.collections/1.define.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,38 @@ Indexes are created automatically when the database schema is generated. They wo
- **`unique`** (optional): Set to `true` to create a unique index (default: `false`)
- **`name`** (optional): Custom index name. If omitted, auto-generates as `idx_{collection}_{column1}_{column2}`

### i18n Support

Enable multi-language content for a collection by adding the `i18n` option. Pass `true` to auto-detect locales from `@nuxtjs/i18n`, or provide an explicit config:

```ts [content.config.ts]
import { defineCollection, defineContentConfig } from '@nuxt/content'
import { z } from 'zod'

export default defineContentConfig({
collections: {
// Auto-detect from @nuxtjs/i18n
docs: defineCollection({
type: 'page',
source: '*/docs/**',
i18n: true,
}),
// Explicit config
team: defineCollection({
type: 'data',
source: 'data/*.yml',
schema: z.object({ name: z.string(), role: z.string() }),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
i18n: {
locales: ['en', 'fr', 'de'],
defaultLocale: 'en',
},
}),
},
})
```

When `i18n` is configured, a `locale` column and a composite `(locale, stem)` index are automatically added to the collection. See the [i18n integration guide](/docs/integrations/i18n) for full documentation.

**Performance Tips:**

- Index columns used in `where()` queries for faster filtering
Expand Down
20 changes: 20 additions & 0 deletions docs/content/docs/3.files/2.yaml.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,26 @@ url: https://github.com/larbish
```
::

## Inline i18n

YAML files in i18n-enabled collections can include an `i18n` section for inline translations. Untranslated fields are preserved from the default locale automatically:

```yaml [jane.yml]
name: Jane Doe
role: Developer
country: Switzerland

i18n:
fr:
role: DΓ©veloppeuse
country: Suisse
de:
role: Entwicklerin
country: Schweiz
```

See the [i18n integration guide](/docs/integrations/i18n) for full documentation.

## Query Data

Now we can query authors:
Expand Down
18 changes: 18 additions & 0 deletions docs/content/docs/3.files/3.json.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ Create authors files in `content/authors/` directory.
Each file in `data` collection should contain only one object, therefore having top level array in a JSON file will cause invalid result in query time.
::

## Inline i18n

JSON files in i18n-enabled collections can include an `i18n` key for inline translations. Untranslated fields are preserved from the default locale automatically:

```json [jane.json]
{
"name": "Jane Doe",
"role": "Developer",
"country": "Switzerland",
"i18n": {
"fr": { "role": "DΓ©veloppeuse", "country": "Suisse" },
"de": { "role": "Entwicklerin", "country": "Schweiz" }
}
}
```

See the [i18n integration guide](/docs/integrations/i18n) for full documentation.

## Query Data

Now we can query authors:
Expand Down
32 changes: 32 additions & 0 deletions docs/content/docs/4.utils/1.query-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,38 @@ const { data } = await useAsyncData(route.path, () => {
})
```

### `locale(locale: string, opts?: { fallback?: string })`

Filter results by locale. Only applicable to collections with `i18n` configured.

- Parameters:
- `locale`: The locale code to filter by (e.g. `'fr'`)
- `opts.fallback`: Optional fallback locale code. When set, items missing in the requested locale will be filled from the fallback locale.

```ts
// Filter by French locale
queryCollection('docs').locale('fr').all()

// With fallback to English for missing items
queryCollection('docs').locale('fr', { fallback: 'en' }).all()
```

::tip
When `@nuxtjs/i18n` is installed and the collection has `i18n` configured, locale filtering is applied automatically based on the current locale. You only need `.locale()` for explicit control.
::

### `stem(stem: string)`

Filter by stem (filename without extension). Automatically resolves the full stem path including the collection's source prefix. Useful for querying data collections by filename.

- Parameter:
- `stem`: The stem to match (e.g. `'navbar'` for `content/navigation/navbar.yml`)

```ts
// Matches content/navigation/navbar.yml when source is 'navigation/*.yml'
queryCollection('navigation').stem('navbar').first()
```

### `count()`

Count the number of matched collection entries based on the query.
Expand Down
115 changes: 115 additions & 0 deletions docs/content/docs/4.utils/5.use-query-collection.md
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 docs/content/docs/4.utils/6.query-collection-locales.md
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>
Comment thread
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>
```
Loading
Loading