diff --git a/docs/content/docs/2.guide/1.index.md b/docs/content/docs/2.guide/1.index.md index e681b97c2..4d4f02d93 100644 --- a/docs/content/docs/2.guide/1.index.md +++ b/docs/content/docs/2.guide/1.index.md @@ -58,7 +58,7 @@ There are 4 supported strategies that affect how app's routes are generated: With this strategy, your routes won't have a locale prefix added. The locale will be detected & changed without changing the URL. This implies that you have to rely on browser & cookie detection, and implement locale switches by calling the i18n API. ::callout{icon="i-heroicons-light-bulb"} -This strategy doesn't support [Custom paths](/docs/guide/custom-paths) and [Ignore routes](/docs/guide/ignoring-localized-routes) features. +This strategy doesn't support [Custom paths](/docs/guide/custom-paths) and [Ignore routes](/docs/guide/ignoring-localized-routes) features unless you're also using [`differentDomains`](/docs/guide/different-domains). :: ### `prefix_except_default` diff --git a/docs/content/docs/2.guide/3.custom-paths.md b/docs/content/docs/2.guide/3.custom-paths.md index 7b38b0a37..a403f8e64 100644 --- a/docs/content/docs/2.guide/3.custom-paths.md +++ b/docs/content/docs/2.guide/3.custom-paths.md @@ -8,7 +8,7 @@ In some cases, you might want to translate URLs in addition to having them prefi Which method is used is configured by setting the [`customRoutes` options](/docs/options/routing#customroutes) this is set to `'page'` by default. Using both methods at the same time is not possible. ::callout{icon="i-heroicons-exclamation-triangle" color="amber"} -Custom paths are not supported when using the `no-prefix` [strategy](/docs/guide). +Custom paths are not supported when using the `no_prefix` [strategy](/docs/guide) unless combined with [`differentDomains`](/docs/guide/different-domains). :: ### Module configuration diff --git a/docs/content/docs/2.guide/4.ignoring-localized-routes.md b/docs/content/docs/2.guide/4.ignoring-localized-routes.md index 3feb8e6d5..ea091424f 100644 --- a/docs/content/docs/2.guide/4.ignoring-localized-routes.md +++ b/docs/content/docs/2.guide/4.ignoring-localized-routes.md @@ -4,7 +4,7 @@ description: Customize localized route exclusions per page component. --- ::callout{icon="i-heroicons-exclamation-triangle" color="amber"} -This feature is not supported with the `no-prefix` [strategy](/docs/guide). +This feature is not supported when using the `no_prefix` [strategy](/docs/guide) unless you're also using [`differentDomains`](/docs/guide/different-domains). :: If you'd like some pages to be available in some languages only, you can configure the list of supported languages to override the global settings. The options can be specified within either the page components themselves or globally, within the module configuration. diff --git a/docs/content/docs/3.options/5.domain.md b/docs/content/docs/3.options/5.domain.md index b1eff90c2..fb8741869 100644 --- a/docs/content/docs/3.options/5.domain.md +++ b/docs/content/docs/3.options/5.domain.md @@ -8,4 +8,4 @@ description: Browser locale management options. - type: `boolean` - default: `false` -Set this to `true` when using different domains for each locale. If enabled, no prefix is added to your routes and you MUST configure locales as an array of objects, each containing a `domain` key. Refer to the [Different domains](/docs/guide/different-domains) for more information. +Set this to `true` when using different domains for each locale, with this enabled you MUST configure locales as an array of objects, each containing a `domain` key. Refer to the [Different domains](/docs/guide/different-domains) for more information. diff --git a/docs/content/docs/5.v9/2.guide/1.index.md b/docs/content/docs/5.v9/2.guide/1.index.md index e681b97c2..4d4f02d93 100644 --- a/docs/content/docs/5.v9/2.guide/1.index.md +++ b/docs/content/docs/5.v9/2.guide/1.index.md @@ -58,7 +58,7 @@ There are 4 supported strategies that affect how app's routes are generated: With this strategy, your routes won't have a locale prefix added. The locale will be detected & changed without changing the URL. This implies that you have to rely on browser & cookie detection, and implement locale switches by calling the i18n API. ::callout{icon="i-heroicons-light-bulb"} -This strategy doesn't support [Custom paths](/docs/guide/custom-paths) and [Ignore routes](/docs/guide/ignoring-localized-routes) features. +This strategy doesn't support [Custom paths](/docs/guide/custom-paths) and [Ignore routes](/docs/guide/ignoring-localized-routes) features unless you're also using [`differentDomains`](/docs/guide/different-domains). :: ### `prefix_except_default` diff --git a/docs/content/docs/5.v9/2.guide/3.custom-paths.md b/docs/content/docs/5.v9/2.guide/3.custom-paths.md index 7b38b0a37..a403f8e64 100644 --- a/docs/content/docs/5.v9/2.guide/3.custom-paths.md +++ b/docs/content/docs/5.v9/2.guide/3.custom-paths.md @@ -8,7 +8,7 @@ In some cases, you might want to translate URLs in addition to having them prefi Which method is used is configured by setting the [`customRoutes` options](/docs/options/routing#customroutes) this is set to `'page'` by default. Using both methods at the same time is not possible. ::callout{icon="i-heroicons-exclamation-triangle" color="amber"} -Custom paths are not supported when using the `no-prefix` [strategy](/docs/guide). +Custom paths are not supported when using the `no_prefix` [strategy](/docs/guide) unless combined with [`differentDomains`](/docs/guide/different-domains). :: ### Module configuration diff --git a/docs/content/docs/5.v9/2.guide/4.ignoring-localized-routes.md b/docs/content/docs/5.v9/2.guide/4.ignoring-localized-routes.md index 3feb8e6d5..ea091424f 100644 --- a/docs/content/docs/5.v9/2.guide/4.ignoring-localized-routes.md +++ b/docs/content/docs/5.v9/2.guide/4.ignoring-localized-routes.md @@ -4,7 +4,7 @@ description: Customize localized route exclusions per page component. --- ::callout{icon="i-heroicons-exclamation-triangle" color="amber"} -This feature is not supported with the `no-prefix` [strategy](/docs/guide). +This feature is not supported when using the `no_prefix` [strategy](/docs/guide) unless you're also using [`differentDomains`](/docs/guide/different-domains). :: If you'd like some pages to be available in some languages only, you can configure the list of supported languages to override the global settings. The options can be specified within either the page components themselves or globally, within the module configuration. diff --git a/docs/content/docs/5.v9/3.options/5.domain.md b/docs/content/docs/5.v9/3.options/5.domain.md index b1eff90c2..fb8741869 100644 --- a/docs/content/docs/5.v9/3.options/5.domain.md +++ b/docs/content/docs/5.v9/3.options/5.domain.md @@ -8,4 +8,4 @@ description: Browser locale management options. - type: `boolean` - default: `false` -Set this to `true` when using different domains for each locale. If enabled, no prefix is added to your routes and you MUST configure locales as an array of objects, each containing a `domain` key. Refer to the [Different domains](/docs/guide/different-domains) for more information. +Set this to `true` when using different domains for each locale, with this enabled you MUST configure locales as an array of objects, each containing a `domain` key. Refer to the [Different domains](/docs/guide/different-domains) for more information. diff --git a/docs/content/docs/6.v7/7.custom-paths.md b/docs/content/docs/6.v7/7.custom-paths.md index d53e09cce..dd68a19bb 100644 --- a/docs/content/docs/6.v7/7.custom-paths.md +++ b/docs/content/docs/6.v7/7.custom-paths.md @@ -6,7 +6,7 @@ description: nuxt/i18n v7 custom route paths. 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 pages: [in-component options](#in-component-options) or via the [module's configuration](#modules-configuration). ::callout{icon="i-heroicons-light-bulb"} -Custom paths are not supported when using the `no-prefix` [strategy](./strategies). +Custom paths are not supported when using the `no_prefix` [strategy](./strategies). :: ### In-component options diff --git a/docs/content/docs/6.v7/8.ignoring-localized-routes.md b/docs/content/docs/6.v7/8.ignoring-localized-routes.md index 9d2914308..44b8d4ed2 100644 --- a/docs/content/docs/6.v7/8.ignoring-localized-routes.md +++ b/docs/content/docs/6.v7/8.ignoring-localized-routes.md @@ -3,7 +3,7 @@ title: Ignoring Localized Routes --- ::callout{icon="i-heroicons-light-bulb"} -This feature is not supported with the `no-prefix` [strategy](./strategies). +This feature is not supported with the `no_prefix` [strategy](./strategies). :: If you'd like some pages to be available in some languages only, you can configure the list of supported languages to override the global settings. The options can be specified within either the page components themselves or globally, within then module options. diff --git a/specs/different_domains/different_domains.spec.ts b/specs/different_domains/different_domains.spec.ts index 9ae2ab2fe..cfe8fd694 100644 --- a/specs/different_domains/different_domains.spec.ts +++ b/specs/different_domains/different_domains.spec.ts @@ -45,6 +45,15 @@ await setup({ strategy: 'no_prefix', detectBrowserLanguage: { useCookie: true + }, + customRoutes: 'config', + pages: { + 'localized-route': { + en: '/localized-in-english', + fr: '/localized-in-french', + ja: '/localized-in-japanese', + nl: '/localized-in-dutch' + } } } } @@ -152,3 +161,27 @@ test('(#2374) detect with x-forwarded-host on server', async () => { expect(dom.querySelector('#welcome-text').textContent).toEqual('Bienvenue') }) + +test("supports custom routes with `strategy: 'no_prefix'`", async () => { + const res = await undiciRequest('/localized-in-french', { + headers: { + host: 'fr.nuxt-app.localhost' + } + }) + const resBody = await res.body.text() + const dom = getDom(resBody) + + // `en` link uses project domain configuration, overrides layer + expect(dom.querySelector('#switch-locale-path-usages .switch-to-en a').getAttribute('href')).toEqual( + `http://en.nuxt-app.localhost/localized-in-english` + ) + + // `nl` link uses layer domain configuration + expect(dom.querySelector('#switch-locale-path-usages .switch-to-nl a').getAttribute('href')).toEqual( + `http://layer-nl.example.com/localized-in-dutch` + ) + // `ja` link uses layer domain configuration + expect(dom.querySelector('#switch-locale-path-usages .switch-to-ja a').getAttribute('href')).toEqual( + `http://layer-ja.example.com/localized-in-japanese` + ) +}) diff --git a/specs/fixtures/different_domains/pages/localized-route.vue b/specs/fixtures/different_domains/pages/localized-route.vue new file mode 100644 index 000000000..398791399 --- /dev/null +++ b/specs/fixtures/different_domains/pages/localized-route.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/module.ts b/src/module.ts index 7dddc82e1..5538c0e77 100644 --- a/src/module.ts +++ b/src/module.ts @@ -179,7 +179,7 @@ export default defineNuxtModule({ * setup nuxt/pages */ - if (options.strategy !== 'no_prefix' && localeCodes.length) { + if (localeCodes.length) { setupPages(options, nuxt) } diff --git a/src/pages.ts b/src/pages.ts index 8d0a212b6..a7bbe9f1b 100644 --- a/src/pages.ts +++ b/src/pages.ts @@ -70,8 +70,12 @@ export function setupPages(options: Required, nuxt: Nuxt) { localizedPages.unshift(indexPage) } - pages.splice(0, pages.length) - pages.unshift(...localizedPages) + // do not mutate pages if localization is skipped + if (pages !== localizedPages) { + pages.splice(0, pages.length) + pages.unshift(...localizedPages) + } + debug('... made pages', pages) }) } diff --git a/src/routing.ts b/src/routing.ts index ab0be694a..25e441d55 100644 --- a/src/routing.ts +++ b/src/routing.ts @@ -33,6 +33,7 @@ export function prefixLocalizedRoute( return ( !extra && !isChildWithRelativePath && + options.strategy !== 'no_prefix' && // skip default locale if strategy is 'prefix_except_default' !(isDefaultLocale && options.strategy === 'prefix_except_default') ) @@ -51,6 +52,31 @@ export type LocalizeRoutesParams = MarkRequired< optionsResolver?: RouteOptionsResolver } +export function shouldLocalizeRoutes(options: NuxtI18nOptions) { + if (options.strategy === 'no_prefix') { + // no_prefix is only supported when using a separate domain per locale + if (!options.differentDomains) return false + + // check if domains are used multiple times + const domains = new Set() + for (const locale of options.locales || []) { + if (typeof locale === 'string') continue + if (locale.domain) { + if (domains.has(locale.domain)) { + console.error( + `Cannot use \`strategy: no_prefix\` when using multiple locales on the same domain - found multiple entries with ${locale.domain}` + ) + return false + } + + domains.add(locale.domain) + } + } + } + + return true +} + type LocalizedRoute = NuxtPage & { locale: Locale; parent: NuxtPage | undefined } type LocalizeRouteParams = { /** @@ -82,9 +108,7 @@ type LocalizeRouteParams = { * @public */ export function localizeRoutes(routes: NuxtPage[], options: LocalizeRoutesParams): NuxtPage[] { - if (options.strategy === 'no_prefix') { - return routes - } + if (!shouldLocalizeRoutes(options)) return routes let defaultLocales = [options.defaultLocale ?? ''] if (options.differentDomains) { diff --git a/src/runtime/routing/compatibles/routing.ts b/src/runtime/routing/compatibles/routing.ts index 17e7f0185..5ee25f315 100644 --- a/src/runtime/routing/compatibles/routing.ts +++ b/src/runtime/routing/compatibles/routing.ts @@ -132,8 +132,7 @@ export function localeLocation( export function resolveRoute(common: CommonComposableOptions, route: RouteLocationRaw, locale: Locale | undefined) { const { router, i18n } = common const _locale = locale || getLocale(i18n) - const { routesNameSeparator, defaultLocale, defaultLocaleRouteNameSuffix, strategy, trailingSlash } = - common.runtimeConfig.public.i18n + const { defaultLocale, strategy, trailingSlash } = common.runtimeConfig.public.i18n const prefixable = extendPrefixable(common.runtimeConfig) // if route parameter is a string, check if it's a path or name of route. let _route: RouteLocationPathRaw | RouteLocationNamedRaw @@ -163,12 +162,7 @@ export function resolveRoute(common: CommonComposableOptions, route: RouteLocati const resolvedRouteName = getRouteBaseName(common, resolvedRoute) if (isString(resolvedRouteName)) { localizedRoute = { - name: getLocaleRouteName(resolvedRouteName, _locale, { - defaultLocale, - strategy, - routesNameSeparator, - defaultLocaleRouteNameSuffix - }), + name: getLocaleRouteName(resolvedRouteName, _locale, common.runtimeConfig.public.i18n), // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- FIXME params: resolvedRoute.params, @@ -194,12 +188,7 @@ export function resolveRoute(common: CommonComposableOptions, route: RouteLocati localizedRoute.name = getRouteBaseName(common, router.currentRoute.value) } - localizedRoute.name = getLocaleRouteName(localizedRoute.name, _locale, { - defaultLocale, - strategy, - routesNameSeparator, - defaultLocaleRouteNameSuffix - }) + localizedRoute.name = getLocaleRouteName(localizedRoute.name, _locale, common.runtimeConfig.public.i18n) } try { diff --git a/src/runtime/routing/utils.ts b/src/runtime/routing/utils.ts index d10165022..d675493f0 100644 --- a/src/runtime/routing/utils.ts +++ b/src/runtime/routing/utils.ts @@ -39,10 +39,18 @@ export function getLocaleRouteName( defaultLocale, strategy, routesNameSeparator, - defaultLocaleRouteNameSuffix - }: { defaultLocale: string; strategy: Strategies; routesNameSeparator: string; defaultLocaleRouteNameSuffix: string } + defaultLocaleRouteNameSuffix, + differentDomains + }: { + defaultLocale: string + strategy: Strategies + routesNameSeparator: string + defaultLocaleRouteNameSuffix: string + differentDomains: boolean + } ) { - let name = getRouteName(routeName) + (strategy === 'no_prefix' ? '' : routesNameSeparator + locale) + const localizedRoutes = strategy !== 'no_prefix' || differentDomains + let name = getRouteName(routeName) + (localizedRoutes ? routesNameSeparator + locale : '') if (locale === defaultLocale && strategy === 'prefix_and_default') { name += routesNameSeparator + defaultLocaleRouteNameSuffix } diff --git a/test/routing-utils.test.ts b/test/routing-utils.test.ts index 38ba30f96..651f6e891 100644 --- a/test/routing-utils.test.ts +++ b/test/routing-utils.test.ts @@ -53,7 +53,8 @@ describe('getLocaleRouteName', () => { defaultLocale: 'en', strategy: 'prefix_and_default', routesNameSeparator: '___', - defaultLocaleRouteNameSuffix: 'default' + defaultLocaleRouteNameSuffix: 'default', + differentDomains: false }), 'route1___en___default' ) @@ -67,7 +68,8 @@ describe('getLocaleRouteName', () => { defaultLocale: 'en', strategy: 'prefix_except_default', routesNameSeparator: '___', - defaultLocaleRouteNameSuffix: 'default' + defaultLocaleRouteNameSuffix: 'default', + differentDomains: false }), 'route1___en' ) @@ -81,7 +83,8 @@ describe('getLocaleRouteName', () => { defaultLocale: 'en', strategy: 'no_prefix', routesNameSeparator: '___', - defaultLocaleRouteNameSuffix: 'default' + defaultLocaleRouteNameSuffix: 'default', + differentDomains: false }), 'route1' ) @@ -96,7 +99,8 @@ describe('getLocaleRouteName', () => { defaultLocale: 'en', strategy: 'prefix_and_default', routesNameSeparator: '___', - defaultLocaleRouteNameSuffix: 'default' + defaultLocaleRouteNameSuffix: 'default', + differentDomains: false }), '(null)___en___default' )