Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/content/docs/02.guide/01.index.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,29 @@ export default defineNuxtConfig({
// ...
})
```

## Custom Locale Prefix

By default, locale codes are used as URL prefixes (e.g., `/en/about`, `/fr/about`). You can customize the URL prefix for each locale using the `prefix` property. This is useful when you want URLs that differ from your locale codes.

```ts [nuxt.config.ts]
export default defineNuxtConfig({
i18n: {
defaultLocale: 'en',
locales: [
{ code: 'en', language: 'en-US' },
{ code: 'pt-BR', language: 'pt-BR', prefix: 'brazil' },
{ code: 'es-MX', language: 'es-MX', prefix: 'mexico' }
]
}
})
```

With this configuration, your routes will look like:
- `/about` (English - default locale)
- `/brazil/about` (Brazilian Portuguese)
- `/mexico/about` (Mexican Spanish)

::callout{icon="i-heroicons-light-bulb"}
The locale `code` is still used internally for route names (e.g., `about___pt-BR`) and i18n functions. Only the URL path uses the custom prefix.
::
19 changes: 19 additions & 0 deletions docs/content/docs/02.guide/91.new-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ toc:
depth: 3
---

### Custom URL prefix per locale
You can now configure a custom URL prefix for each locale using the `prefix` property. This is useful for business-oriented or SEO-friendly URLs where you want to use a different prefix than the locale code.

For example, if you want to use `/brazil/` instead of `/pt-BR/` for your Brazilian Portuguese locale:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
i18n: {
locales: [
{ code: 'en', language: 'en-US' },
{ code: 'pt-BR', language: 'pt-BR', prefix: 'brazil' }
]
}
})
```

This will generate routes like `/brazil/about` instead of `/pt-BR/about`, while keeping the locale code (`pt-BR`) for internal use (route names, i18n functions, etc.).

See the [`prefix` option](/docs/api/options#prefix) for more details.

### Custom routes via `definePageMeta()`{lang="ts"}
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.
Expand Down
18 changes: 18 additions & 0 deletions docs/content/docs/04.api/00.options.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,24 @@ See also [Multiple files lazy loading](/docs/guide/lazy-load-translations#multip
- type: `null | boolean`{lang="ts-type"}
- Set `domainDefault` to `true` for each locale that should act as a default locale for the particular domain. This property is required when using [`differentDomains`](/docs/api/options#differentdomains) while one or more of the domains having multiple locales

### `prefix`

- type: `null | string`{lang="ts-type"}
- A custom URL prefix for this locale. When set, routes will use this prefix instead of the locale code.

For example, setting `prefix: 'brazil'` for `pt-BR` locale will result in `/brazil/about` instead of `/pt-BR/about`.

```json
[
{ "code": "en", "language": "en-US", "file": "en.js" },
{ "code": "pt-BR", "language": "pt-BR", "file": "pt-BR.js", "prefix": "brazil" }
]
```

::callout{icon="i-heroicons-light-bulb"}
The route name will still use the locale `code` (e.g., `about___pt-BR`), only the URL path uses the custom prefix.
::

### `...`

- any custom property set on the object will be exposed at runtime. This can be used, for example, to define the language name for the purpose of using it in a language selector on the page.
Expand Down
9 changes: 7 additions & 2 deletions src/kit/gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ function createLocalizeAliases(ctx: RouteContext): RouteContext['localizeAliases
return aliases.map((x) => {
const alias = ctx.handleTrailingSlash(x, !!options.parent)
const shouldPrefix = options.shouldPrefix(x, locale, options)
return shouldPrefix ? join('/', locale, alias) : alias
const prefix = ctx.getLocalePrefix(locale)
return shouldPrefix ? join('/', prefix, alias) : alias
})
}
}
Expand Down Expand Up @@ -117,7 +118,8 @@ export function localizeSingleRoute(
// use custom path if found
const unprefixed = routeOptions.paths?.[locale] ?? route.path

const prefixed = join('/', locale, unprefixed)
const prefix = ctx.getLocalePrefix(locale)
const prefixed = join('/', prefix, unprefixed)
const usePrefix = options.shouldPrefix(unprefixed, locale, options)

const data = { route, prefixed, unprefixed, locale, usePrefix, ctx, options }
Expand All @@ -144,6 +146,7 @@ export type RouteContext = {
trailingSlash: boolean
optionsResolver: RouteOptionsResolver
isDefaultLocale: (locale: string) => boolean
getLocalePrefix: (locale: string) => string
localizeAliases: (route: LocalizableRoute, locale: string, options: LocalizeRouteParams) => string[]
localizeChildren: (
route: LocalizableRoute,
Expand Down Expand Up @@ -188,13 +191,15 @@ function createLocalizeRouteName(opts: {
export function createRouteContext(opts: {
trailingSlash: boolean
defaultLocales: string[]
localePrefixes?: Record<string, string>
optionsResolver?: RouteOptionsResolver
routesNameSeparator?: string
defaultLocaleRouteNameSuffix?: string
}) {
const ctx = { localizers: [] as RouteContext['localizers'] } as RouteContext
ctx.trailingSlash = opts.trailingSlash ?? false
ctx.isDefaultLocale = (locale: string) => opts.defaultLocales.includes(locale)
ctx.getLocalePrefix = (locale: string) => opts.localePrefixes?.[locale] || locale
ctx.localizeRouteName = createLocalizeRouteName(opts)
ctx.optionsResolver = createDefaultOptionsResolver(opts)
ctx.localizeAliases = createLocalizeAliases(ctx)
Expand Down
15 changes: 15 additions & 0 deletions src/routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ type SetupLocalizeRoutesOptions = {
optionsResolver?: RouteOptionsResolver
}

/**
* Build a map of locale codes to their custom prefixes
*/
function buildLocalePrefixes(locales: LocaleObject[]): Record<string, string> {
const prefixes: Record<string, string> = {}
for (const locale of locales) {
// Use custom prefix if defined and non-empty, otherwise use locale code
if (locale.prefix && locale.prefix.length > 0) {
prefixes[locale.code] = locale.prefix
}
}
return prefixes
}

/**
* Localize routes
*/
Expand All @@ -74,6 +88,7 @@ export function localizeRoutes(routes: LocalizableRoute[], config: SetupLocalize
optionsResolver: config.optionsResolver,
trailingSlash: config.trailingSlash ?? false,
defaultLocales: resolveDefaultLocales(config),
localePrefixes: buildLocalePrefixes(config.locales),
routesNameSeparator: config.routesNameSeparator,
defaultLocaleRouteNameSuffix: config.defaultLocaleRouteNameSuffix,
})
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,13 @@ export interface LocaleObject<T = Locale> {
* This property is required when using differentDomains while one or more of the domains having multiple locales.
*/
domainDefault?: boolean
/**
* Custom URL prefix for this locale.
*
* When set, routes will use this prefix instead of the locale code.
* For example, setting `prefix: 'brazil'` for `pt-BR` locale will result in `/brazil/about` instead of `/pt-BR/about`.
*/
prefix?: string
/**
* The name of the file containing locale messages for this locale.
*
Expand Down
Loading