Skip to content

Commit 568135a

Browse files
feat: support custom routes via definePageMeta (#3578)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 269ca92 commit 568135a

21 files changed

Lines changed: 403 additions & 66 deletions

File tree

docs/content/docs/02.guide/03.custom-paths.md

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: Custom Route Paths
33
description: Customize the names of the paths for specific locale.
44
---
55

6-
In some cases, you might want to translate URLs in addition to having them prefixed with the locale code. There are two methods of configuring custom paths, through [Module configuration](#module-configuration) or from within each [Page component](#page-component).
6+
In some cases, you might want to translate URLs in addition to having them prefixed with the locale code. There are two methods of configuring custom paths, through [Module configuration](#module-configuration) or from within each [Page component](#definepagemeta).
77

88
Which method is used is configured by setting the [`customRoutes` options](/docs/api/options#customroutes) this is set to `'page'`{lang="ts-type"} by default. Using both methods at the same time is not possible.
99

@@ -183,17 +183,47 @@ export default defineNuxtConfig({
183183
})
184184
```
185185

186-
## Page component
186+
## `definePageMeta`
187+
188+
You can use the `i18n` property in `definePageMeta()`{lang="ts"} to set custom paths for each page component.
189+
```vue [pages/about.vue]
190+
<script setup>
191+
definePageMeta({
192+
i18n: {
193+
paths: {
194+
en: '/about-us', // -> accessible at /about-us (no prefix since it's the default locale)
195+
fr: '/a-propos', // -> accessible at /fr/a-propos
196+
es: '/sobre' // -> accessible at /es/sobre
197+
}
198+
}
199+
})
200+
</script>
201+
```
202+
203+
To configure a custom path for a dynamic route, you need to use it in double square brackets in the paths similar to how you would do it in [Nuxt Dynamic Routes](https://nuxt.com/docs/guide/directory-structure/pages#dynamic-routes):
204+
205+
```vue [pages/articles/[name].vue]
206+
<script setup>
207+
definePageMeta({
208+
i18n: {
209+
paths: {
210+
en: '/articles/[name]',
211+
es: '/artículo/[name]'
212+
}
213+
}
214+
})
215+
</script>
216+
```
217+
218+
## `defineI18nRoute`
187219

188220
::callout{icon="i-heroicons-exclamation-triangle" color="warning" title="notice"}
189-
Note for those updating to `v8.0.1`{lang="sh"} or higher
190-
:br :br
191-
Path parameters parsing has been changed to match that of [Nuxt 3](https://nuxt.com/docs/guide/directory-structure/pages#dynamic-routes), you will have to update your custom paths (e.g. `'/example/:param'`{lang="ts-type"} should now be `'/example/[param]'`{lang="ts-type"})
221+
This method is deprecated in favor of `definePageMeta()`{lang="ts"} and will be removed in v11.
192222
::
193223

194224
You can use the `defineI18nRoute()`{lang="ts"} compiler macro to set custom paths for each page component.
195225

196-
```vue {}[pages/about.vue]
226+
```vue [pages/about.vue]
197227
<script setup>
198228
defineI18nRoute({
199229
paths: {
@@ -205,9 +235,9 @@ defineI18nRoute({
205235
</script>
206236
```
207237

208-
To configure a custom path for a dynamic route, you need to use it in double square brackets in the paths similarly to how you would do it in [Nuxt Dynamic Routes](https://nuxt.com/docs/guide/directory-structure/pages#dynamic-routes):
238+
To configure a custom path for a dynamic route, you need to use it in double square brackets in the paths similar to how you would do it in [Nuxt Dynamic Routes](https://nuxt.com/docs/guide/directory-structure/pages#dynamic-routes):
209239

210-
```vue {}[pages/articles/[name].vue]
240+
```vue [pages/articles/[name].vue]
211241
<script setup>
212242
defineI18nRoute({
213243
paths: {

docs/content/docs/02.guide/04.ignoring-localized-routes.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@ If you'd like some pages to be available in some languages only, you can configu
1313

1414
::code-group
1515

16-
```vue [pages/about.vue]
16+
```vue [about-meta.vue]
17+
// pages/about.vue
18+
<script setup>
19+
definePageMeta({
20+
i18n: { locales: ['fr', 'es'] }
21+
})
22+
</script>
23+
```
24+
25+
```vue [about-macro.vue]
26+
// pages/about.vue
1727
<script setup>
1828
defineI18nRoute({
1929
locales: ['fr', 'es']
@@ -37,7 +47,15 @@ i18n: {
3747

3848
::code-group
3949

40-
```vue [pages/about.vue]
50+
```vue [about-meta.vue]
51+
// pages/about.vue
52+
<script setup>
53+
definePageMeta({ i18n: false })
54+
</script>
55+
```
56+
57+
```vue [about-macro.vue]
58+
// pages/about.vue
4159
<script setup>
4260
defineI18nRoute(false)
4361
</script>

docs/content/docs/02.guide/90.migrating.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,29 @@ We have upgrade from Vue I18n v10 to v11, this major version bump deprecates the
1010

1111
Check the documentation detailing the breaking changes [here](https://vue-i18n.intlify.dev/guide/migration/breaking11.html).
1212

13+
### Custom routes via `definePageMeta()`{lang="ts"}
14+
We have added support for setting custom routes for pages using the `definePageMeta()`{lang="ts"} API, which is now the recommended way to set custom routes for pages.
15+
This method is enabled by setting `customRoutes: 'meta'`{lang="ts"} in the module options.
16+
17+
To migrate from the `defineI18nRoute()`{lang="ts"} macro, you can simply replace it with `definePageMeta()`{lang="ts"} and set the `i18n` property with the same options:
18+
```vue [pages/about.vue]
19+
<script setup>
20+
definePageMeta({
21+
i18n: {
22+
paths: {
23+
en: '/about-us',
24+
fr: '/a-propos',
25+
}
26+
}
27+
})
28+
</script>
29+
```
30+
31+
1332
### Lazy loading
1433
The `lazy` option has been removed and lazy loading of locale messages is now the default behavior.
1534

16-
### `finalizePendingLocaleChange()`{lang="ts"} signature changed
35+
### Signature changed for `finalizePendingLocaleChange()`{lang="ts"}
1736
The function signature for `finalizePendingLocaleChange()`{lang="ts"} has been corrected from `() => Promise<void>`{lang="ts-type"} to `() => void`{lang="ts-type"}.
1837
This change was made since the function does not rely on any async operations and should not be awaited, and should prevent unnecessary function coloring.
1938

docs/content/docs/04.api/00.options.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,14 @@ Routes generation strategy. Can be set to one of the following:
183183

184184
## customRoutes
185185

186-
- type: `'page' | 'config'`{lang="ts-type"}
186+
- type: `'meta' | 'page' | 'config'`{lang="ts-type"}
187187
- default: `'page'`{lang="ts-type"}
188188

189-
Whether [custom paths](/docs/guide/custom-paths) are extracted from page files
189+
Whether [custom paths](/docs/guide/custom-paths) are extracted from page files or configured in the module configuration:
190+
191+
- `'meta'`{lang="ts-type"}: custom paths are extracted from the `definePageMeta()`{lang="ts"} function in page components.
192+
- `'page'`{lang="ts-type"}: custom paths are extracted from the `defineI18nRoute()`{lang="ts"} macro in page components.
193+
- `'config'`{lang="ts-type"}: custom paths are configured in the `pages` option of the module configuration.
190194

191195
## pages
192196

specs/fixtures/basic_usage/pages/about/index.vue

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,6 @@ const code = computed(() => {
1313
definePageMeta({
1414
title: 'about'
1515
})
16-
17-
/*
18-
// TODO: defineNuxtI18n macro
19-
defineNuxtI18n({
20-
paths: {
21-
en: '/about-us',
22-
fr: '/a-propos'
23-
}
24-
})
25-
*/
2616
</script>
2717

2818
<template>

specs/fixtures/basic_usage_compat_4/app/pages/about/index.vue

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,6 @@ const code = computed(() => {
1313
definePageMeta({
1414
title: 'about'
1515
})
16-
17-
/*
18-
// TODO: defineNuxtI18n macro
19-
defineNuxtI18n({
20-
paths: {
21-
en: '/about-us',
22-
fr: '/a-propos'
23-
}
24-
})
25-
*/
2616
</script>
2717

2818
<template>

specs/fixtures/lazy/pages/about/index.vue

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,6 @@ const localePath = useLocalePath()
88
const code = computed(() => {
99
return localeProperties.value.code
1010
})
11-
12-
/*
13-
// TODO: defineNuxtI18n macro
14-
defineNuxtI18n({
15-
paths: {
16-
en: '/about-us',
17-
fr: '/a-propos'
18-
}
19-
})
20-
*/
2111
</script>
2212

2313
<template>

specs/fixtures/restructure/pages/about/index.vue

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,6 @@ const localePath = useLocalePath()
88
const code = computed(() => {
99
return localeProperties.value.code
1010
})
11-
12-
/*
13-
// TODO: defineNuxtI18n macro
14-
defineNuxtI18n({
15-
paths: {
16-
en: '/about-us',
17-
fr: '/a-propos'
18-
}
19-
})
20-
*/
2111
</script>
2212

2313
<template>

src/gen.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { genDynamicImport, genSafeVariableName, genString } from 'knitwork'
33
import { resolve, relative, join, basename } from 'pathe'
44
import { getLayerI18n } from './utils'
55
import { asI18nVirtual } from './transform/utils'
6+
import { resolveModule } from '@nuxt/kit'
67

78
import type { Nuxt } from '@nuxt/schema'
89
import type { NuxtI18nOptions, LocaleObject } from './types'
@@ -199,6 +200,7 @@ import type { Strategies, Directions, LocaleObject } from '${relative(
199200
join(nuxt.options.buildDir, 'types'),
200201
resolve(distDir, 'types.d.mts')
201202
)}'
203+
import type { I18nRoute } from '#i18n'
202204
203205
declare module 'vue-i18n' {
204206
interface ComposerCustom extends ComposerCustomProperties<${resolvedLocaleType}> {}
@@ -214,10 +216,25 @@ declare module '@intlify/core-base' {
214216
}
215217
}
216218
219+
interface I18nMeta {
220+
i18n?: I18nRoute | false
221+
}
222+
217223
declare module '#app' {
218224
interface NuxtApp {
219225
$i18n: ${i18nType}
220226
}
227+
interface PageMeta extends I18nMeta {}
228+
}
229+
230+
231+
// NOTE: this is a workaround for Nuxt <3.16.2
232+
declare module '${resolve(resolveModule('nuxt'), '../pages/runtime/composables')}' {
233+
interface PageMeta extends I18nMeta {}
234+
}
235+
236+
declare module 'vue-router' {
237+
interface RouteMeta extends I18nMeta {}
221238
}
222239
223240
${typedRouterAugmentations}

src/pages.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { EditableTreeNode, Options as TypedRouterOptions } from 'unplugin-v
1919
import type { NuxtI18nOptions } from './types'
2020
import type { I18nNuxtContext } from './context'
2121
import type { ComputedRouteOptions, RouteOptionsResolver } from './kit/gen'
22+
import type { I18nRoute } from './runtime/composables'
2223

2324
const debug = createDebug('@nuxtjs/i18n:pages')
2425

@@ -55,6 +56,9 @@ export async function setupPages({ localeCodes, options }: I18nNuxtContext, nuxt
5556

5657
const projectLayer = nuxt.options._layers[0]
5758
const typedRouter = await setupExperimentalTypedRoutes(options, nuxt)
59+
60+
nuxt.options.experimental.extraPageMetaExtractionKeys ??= []
61+
nuxt.options.experimental.extraPageMetaExtractionKeys.push('i18n')
5862
nuxt.hook(
5963
nuxt.options.experimental.scanPageMeta === 'after-resolve' ? 'pages:resolved' : 'pages:extend',
6064
async pages => {
@@ -296,15 +300,34 @@ function getRouteFromMacro(
296300
}
297301
}
298302

303+
function getRouteFromMeta(
304+
_ctx: NuxtPageAnalyzeContext,
305+
route: NuxtPage,
306+
localeCodes: string[]
307+
): ComputedRouteOptions | false | undefined {
308+
const resolved = route.meta?.i18n as I18nRoute | false | undefined
309+
if (!resolved) return resolved
310+
return {
311+
paths: (resolved.paths ?? {}) as Record<string, string>,
312+
locales: resolved?.locales || localeCodes
313+
}
314+
}
315+
299316
function getRouteOptions(
300317
route: NuxtPage,
301318
localeCodes: string[],
302319
ctx: NuxtPageAnalyzeContext,
303320
defaultLocale: string,
304-
mode: 'config' | 'page' = 'config'
321+
mode: 'config' | 'page' | 'meta' = 'config'
305322
) {
306-
const resolvedOptions =
307-
mode === 'config' ? getRouteFromConfig(ctx, route, localeCodes) : getRouteFromMacro(ctx, route, localeCodes)
323+
let resolvedOptions
324+
if (mode === 'meta') {
325+
resolvedOptions = getRouteFromMeta(ctx, route, localeCodes)
326+
} else if (mode === 'page') {
327+
resolvedOptions = getRouteFromMacro(ctx, route, localeCodes)
328+
} else {
329+
resolvedOptions = getRouteFromConfig(ctx, route, localeCodes)
330+
}
308331

309332
// routing disabled
310333
if (resolvedOptions === false) {

0 commit comments

Comments
 (0)