Skip to content
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

feat: custom routes named config #2408

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
153 changes: 152 additions & 1 deletion docs/content/2.guide/4.custom-paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ Customize the names of the paths for specific locale.

---

In some cases, you might want to translate URLs in addition to having them prefixed with the locale code. There are 2 ways of configuring custom paths for your [Module configuration](#nodule-configuration) or your pages [Page component](#page-component).
In some cases, you might want to translate URLs in addition to having them prefixed with the locale code. There are 3 ways of configuring custom paths:

[Module configuration](#module-configuration) (`customRoutes: 'config'`)

[Module named configuration](#module-named-configuration) (`customRoutes: 'named-config'`)

[Page component](#page-component) (`customRoutes: 'page'`)

::alert{type="warning"}
Custom paths are not supported when using the `no-prefix` [strategy](/guide/routing-strategies).
Expand Down Expand Up @@ -187,6 +193,151 @@ export default defineNuxtConfig({
})
```

### Module named configuration

Make sure you set the `customRoutes` option to `named-config` and add your custom paths in the `pages` option:

```ts {}[nuxt.config.ts]
export default defineNuxtConfig({
// ...

i18n: {
customRoutes: 'config', // disable custom route with page components
pages: {
about: {
en: '/about-us', // -> accessible at /about-us (no prefix since it's the default locale)
fr: '/a-propos', // -> accessible at /fr/a-propos
es: '/sobre' // -> accessible at /es/sobre
}
}
},

// ...
})
```

Note that each key within the `pages` object should **correspond to the route name**.

Customized route paths **must start with a `/`** and **not include the locale prefix**.

#### Example 1: Basic URL localization

You have some routes with the following `pages` directory:

```asciidoc
pages/
β”œβ”€β”€ about.vue
β”œβ”€β”€ me.vue
β”œβ”€β”€ services/
β”œβ”€β”€β”€β”€ index.vue
β”œβ”€β”€β”€β”€ advanced.vue
|---- [param].vue

```
You would need to set up your `pages` property as follows:

```ts {}[nuxt.config.ts]
export default defineNuxtConfig({
// ...

i18n: {
customRoutes: 'named-config',
pages: {
about: {
fr: '/a-propos',
},
me: {
fr: '/je',
},
'services': {
fr: '/offres',
}
'services-advanced': {
fr: '/offres/avancee',
}
'services-param': {
fr: '/offres/:param()',
}
}
},

// ...
})
```

Nuxt generates routes names based on you files structure
Basically it just removes special characters and join all segments with '-'

if you not sure about routes names just add that simple module before `'@nuxtjs/i18n'`:

```ts{}[nuxt.config.ts]
export default defineNuxtConfig({
modules: [
(_, nuxt) => {
nuxt.hook('pages:extend', pages => {
let pagesCopy = JSON.parse(JSON.stringify(pages))
function processRoutes(routes) {
return routes.map(r => {
if (r.children) {
r.children = processRoutes(r.children)
}
return {
name: r.name,
path: r.path,
children: r.children
}
})
}
console.log('PAGES:')
console.log(JSON.stringify(processRoutes(pagesCopy), null, 4))
})
},
'@nuxtjs/i18n',
],
})
```

::alert{type="warning"}
All the URL should start with `/`
::

#### Example 3: Dynamic Routes

Say you have some dynamic routes like:

```asciidoc
pages/
β”œβ”€β”€ blog/
β”œβ”€β”€β”€β”€ [date]/
β”œβ”€β”€β”€β”€β”€β”€ [slug].vue
β”œβ”€β”€ news/
β”œβ”€β”€β”€β”€ [[slug]].vue
```

Here's how you would configure these particular pages in the configuration:

```ts {}[nuxt.config.ts]
export default defineNuxtConfig({
// ...

i18n: {
customRoutes: 'named-config',
pages: {
'blog-date-slug': {
// params need to be put back here as you would declare with Vue Router
// https://router.vuejs.org/guide/essentials/dynamic-matching.html
ja: '/blog/tech/:date()/:slug()'
},
'news-slug':{
ja: '/news-ja/:slug?'
}
}
},

// ...
})
```

### Page component

You can use the `defineI18nRoute` compiler macro to set some custom paths for each page component.
Expand Down
24 changes: 23 additions & 1 deletion docs/content/2.guide/5.ignoring-localized-routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,19 @@ If you'd like some pages to be available in some languages only, you can configu
::code-block{label="Module configuration"}
```js {}[nuxt.config.js]
i18n: {
customRoutes: false,
customRoutes: 'config',
pages: {
about: {
en: false,
}
}
}
```
::
::code-block{label="Module named configuration"}
```js {}[nuxt.config.js]
i18n: {
customRoutes: 'named-config',
pages: {
about: {
en: false,
Expand Down Expand Up @@ -56,4 +68,14 @@ If you'd like some pages to be available in some languages only, you can configu
}
```
::
::code-block{label="Module named configuration"}
```js {}[nuxt.config.js]
i18n: {
customRoutes: 'named-config',
pages: {
about: false
}
}
```
::
::
4 changes: 2 additions & 2 deletions docs/content/3.options/2.routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Routes generation strategy. Can be set to one of the following:

## `customRoutes`

- type: `string` (`page` or `config`) | `undefined`
- type: `string` (`page`, `config` or `named-config`) | `undefined`
- default: `page`

Whether [custom paths](/guide/custom-paths) are extracted from page files
Expand All @@ -89,7 +89,7 @@ Whether [custom paths](/guide/custom-paths) are extracted from page files
- type: `object`
- default: `{}`

If `customRoutes` option is disabled with `config`, the module will look for custom routes in the `pages` option. Refer to the [Routing](/guide/routing-strategies) for usage.
If `customRoutes` option is disabled with `config` or `named-config`, the module will look for custom routes in the `pages` option. Refer to the [Routing](/guide/routing-strategies) for usage.

## `skipSettingLocaleOnNavigate`

Expand Down
36 changes: 36 additions & 0 deletions playground/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default defineNuxtConfig({
(_, nuxt) => {
console.log(nuxt.options._installedModules)
},

Module1,
ModuleExperimental,
LayerModule,
Expand Down Expand Up @@ -88,6 +89,25 @@ export default defineNuxtConfig({
}
],
*/
(_, nuxt) => {
nuxt.hook('pages:extend', pages => {
let pagesCopy = JSON.parse(JSON.stringify(pages))
function processRoutes(routes) {
return routes.map(r => {
if (r.children) {
r.children = processRoutes(r.children)
}
return {
name: r.name,
path: r.path,
children: r.children
}
})
}
console.log('PAGES:')
console.log(JSON.stringify(processRoutes(pagesCopy), null, 4))
})
},
'@nuxtjs/i18n',
'@nuxt/devtools'
],
Expand Down Expand Up @@ -171,6 +191,22 @@ export default defineNuxtConfig({
ja: '/about-ja'
}
},

// // NAMED-CONFIG
// customRoutes: 'named-config',
// pages: {
// history: {
// ja: '/history-ja'
// },
// about: {
// ja: '/about-ja'
// },
// 'category-id': {
// ja: '/category-ja/:id()'
// }
// },
// // END OF NAMED-CONFIG

// differentDomains: true,
skipSettingLocaleOnNavigate: true,
detectBrowserLanguage: false,
Expand Down
69 changes: 64 additions & 5 deletions src/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,27 @@
): RouteOptionsResolver {
const { pages, defaultLocale, customRoutes } = options

const useConfig = customRoutes === 'config'
debug('getRouteOptionsResolver useConfig', useConfig)
let customRoutesProvider = 'page'
if (customRoutes && ['config', 'named-config', 'page'].includes(customRoutes)) {
customRoutesProvider = customRoutes
} else if (customRoutes) {
console.warn(
formatMessage(
`Unknown customRoutes property (${JSON.stringify(customRoutes)}), will be reset to default ("page")`
)
)
}

debug('getRouteOptionsResolver customRoutes:', customRoutes)

return (route, localeCodes): ComputedRouteOptions | null => {
const ret = useConfig
? getRouteOptionsFromPages(ctx, route, localeCodes, pages, defaultLocale)
: getRouteOptionsFromComponent(route, localeCodes)
const routesGetters = {
config: () => getRouteOptionsFromPages(ctx, route, localeCodes, pages, defaultLocale),
'named-config': () => getRouteOptionsFromRoutesNames(ctx, route, localeCodes, pages, defaultLocale),
page: () => getRouteOptionsFromComponent(route, localeCodes)
}

const ret = routesGetters[customRoutesProvider]()

Check failure on line 145 in src/pages.ts

View workflow job for this annotation

GitHub Actions / Test on Node.js 16

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ config: () => ComputedRouteOptions | null; 'named-config': () => ComputedRouteOptions | null; page: () => ComputedRouteOptions | null; }'.
debug('getRouteOptionsResolver resolved', route.path, route.name, ret)
return ret
}
Expand Down Expand Up @@ -198,6 +212,51 @@
return options
}

function getRouteOptionsFromRoutesNames(
ctx: NuxtPageAnalyzeContext,
route: I18nRoute,
localeCodes: string[],
pages: CustomRoutePages,
defaultLocale: string
) {
const options: ComputedRouteOptions = {
locales: localeCodes,
paths: {}
}
const pageOptions = pages[route.name]

Check failure on line 226 in src/pages.ts

View workflow job for this annotation

GitHub Actions / Test on Node.js 16

Type 'undefined' cannot be used as an index type.

// routing disabled
if (pageOptions === false) {
return null
}

// skip if no page options defined
if (!pageOptions) {
return options
}

// remove disabled locales from page options
options.locales = options.locales.filter(locale => pageOptions[locale] !== false)

// construct paths object
for (const locale of options.locales) {
const customLocalePath = pageOptions[locale]
if (isString(customLocalePath)) {
// set custom path if any
options.paths[locale] = customLocalePath
continue
}

const customDefaultLocalePath = pageOptions[defaultLocale]
if (isString(customDefaultLocalePath)) {
// set default locale's custom path if any
options.paths[locale] = customDefaultLocalePath
}
}

return options
}

function getRouteOptionsFromComponent(route: I18nRoute, localeCodes: string[]) {
debug('getRouteOptionsFromComponent', route)
const file = route.component || route.file
Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export type NuxtI18nOptions<Context = unknown> = {
langDir?: string | null
lazy?: boolean
pages?: CustomRoutePages
customRoutes?: 'page' | 'config'
customRoutes?: 'page' | 'config' | 'named-config'
/**
* @internal
*/
Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, we have a diff happening between nuxt ...

We want to use parseSegment and getRoutePath that are exported by nuxt/kit.
We need to sync the implementation.

Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ export function parseSegment(segment: string) {
if (c === '[' && state === SegmentParserState.dynamic) {
state = SegmentParserState.optional
}
if (c === ']' && (state !== SegmentParserState.optional || buffer[buffer.length - 1] === ']')) {
if (c === ']' && (state !== SegmentParserState.optional || segment[i - 1] === ']')) {
if (!buffer) {
throw new Error('Empty param')
} else {
Expand Down Expand Up @@ -508,7 +508,7 @@ export function getRoutePath(tokens: SegmentToken[]): string {
(token.type === SegmentTokenType.optional
? `:${token.value}?`
: token.type === SegmentTokenType.dynamic
? `:${token.value}`
? `:${token.value}()`
: token.type === SegmentTokenType.catchall
? `:${token.value}(.*)*`
: encodePath(token.value))
Expand Down