diff --git a/src/module.ts b/src/module.ts index c3b739f9..e3fa2713 100644 --- a/src/module.ts +++ b/src/module.ts @@ -904,7 +904,6 @@ export default defineNuxtModule({ include: normalizeFilters(config.include) as (string | RegExp)[], exclude: normalizeFilters(config.exclude) as (string | RegExp)[], }, - isI18nMicro: i18nModule === 'nuxt-i18n-micro', autoI18n: !!resolvedAutoI18n, }) if (!pageSource.length) { diff --git a/src/utils-internal/i18n.ts b/src/utils-internal/i18n.ts index cc9f5979..b9c8f4d6 100644 --- a/src/utils-internal/i18n.ts +++ b/src/utils-internal/i18n.ts @@ -2,7 +2,7 @@ import type { AutoI18nConfig } from 'nuxtseo-shared/i18n' import type { FilterInput } from '../runtime/types' import { splitPathForI18nLocales as _splitPathForI18nLocales } from 'nuxtseo-shared/i18n' -export { generatePathForI18nPages, normalizeLocales } from 'nuxtseo-shared/i18n' +export { expandCompactLocaleRoute, generatePathForI18nPages, normalizeLocales } from 'nuxtseo-shared/i18n' export type { AutoI18nConfig, Strategies, StrategyProps } from 'nuxtseo-shared/i18n' export function splitPathForI18nLocales(path: FilterInput, autoI18n: AutoI18nConfig): FilterInput | FilterInput[] { diff --git a/src/utils-internal/nuxtSitemap.ts b/src/utils-internal/nuxtSitemap.ts index 257b1653..75df8b93 100644 --- a/src/utils-internal/nuxtSitemap.ts +++ b/src/utils-internal/nuxtSitemap.ts @@ -9,6 +9,7 @@ import { defu } from 'defu' import { extname } from 'pathe' import { withBase, withHttps } from 'ufo' import { createPathFilter } from '../runtime/utils-pure' +import { expandCompactLocaleRoute } from './i18n' export async function resolveUrls(urls: Required['urls'], ctx: { logger: ConsolaInstance, path: string }): Promise { try { @@ -41,7 +42,6 @@ export interface NuxtPagesToSitemapEntriesOptions { defaultLocale: string strategy: 'no_prefix' | 'prefix_except_default' | 'prefix' | 'prefix_and_default' isI18nMapped: boolean - isI18nMicro: boolean filter: CreateFilterOptions autoI18n: boolean } @@ -69,19 +69,16 @@ function deepForEachPage( } let didCallback = false - if (opts.isI18nMicro) { - const localePattern = /\/:locale\(([^)]+)\)/ - const match = localePattern.exec(currentPath || '') - if (match && match[1]) { - const locales = match[1].split('|') - locales.forEach((locale) => { - const subPage = { ...page } - const localizedPath = (currentPath || '').replace(localePattern, `/${locale}`) - subPage.name += opts.routesNameSeparator + locale - subPage.path = localizedPath - callback(subPage, localizedPath || '', depth) - didCallback = true - }) + // Expand compacted i18n routes (`/:locale(en|fr)/about`) back into one entry per + // locale. Used by `nuxt-i18n-micro` and by `@nuxtjs/i18n` experimental compactRoutes. + const compacted = expandCompactLocaleRoute(currentPath || '', opts.normalisedLocales.map(l => l.code)) + if (compacted) { + for (const { locale, path: localizedPath } of compacted) { + const subPage = { ...page } + subPage.name = `${page.name || ''}${opts.routesNameSeparator}${locale}` + subPage.path = localizedPath + callback(subPage, localizedPath, depth) + didCallback = true } } if (!didCallback) { diff --git a/test/unit/parsePages.test.ts b/test/unit/parsePages.test.ts index e98b317d..08638e4c 100644 --- a/test/unit/parsePages.test.ts +++ b/test/unit/parsePages.test.ts @@ -203,7 +203,6 @@ describe('page parser', () => { defaultLocale: 'en', normalisedLocales: normalizeLocales({ locales: [{ code: 'en' }, { code: 'fr' }] }), strategy: 'no_prefix', - isI18nMicro: false, autoI18n: true, })).toMatchInlineSnapshot(` [ @@ -644,6 +643,110 @@ describe('page parser', () => { `) }) + it('@nuxtjs/i18n compactRoutes (prefix_except_default)', () => { + // @nuxtjs/i18n experimental.compactRoutes emits an unprefixed default route plus a + // single compacted regex route for the non-default locales, keeping the base name. + expect(convertNuxtPagesToSitemapEntries([ + { + name: 'about___en', + path: '/about', + file: 'pages/about.vue', + children: [], + }, + { + name: 'about', + path: '/:locale(fr|de)/about', + file: 'pages/about.vue', + children: [], + meta: { __i18nCompact: true }, + }, + ], { + filter: { + include: [], + exclude: [], + }, + isI18nMapped: true, + autoLastmod: false, + defaultLocale: 'en', + normalisedLocales: normalizeLocales({ locales: [ + { code: 'en', iso: 'en_EN' }, + { code: 'fr', iso: 'fr_FR' }, + { code: 'de', iso: 'de_DE' }, + ] }), + strategy: 'prefix_except_default', + autoI18n: true, + })).toMatchInlineSnapshot(` + [ + { + "_sitemap": "en_EN", + "alternatives": [ + { + "href": "/about", + "hreflang": "en_EN", + }, + { + "href": "/fr/about", + "hreflang": "fr_FR", + }, + { + "href": "/de/about", + "hreflang": "de_DE", + }, + { + "href": "/about", + "hreflang": "x-default", + }, + ], + "loc": "/about", + }, + { + "_sitemap": "fr_FR", + "alternatives": [ + { + "href": "/about", + "hreflang": "en_EN", + }, + { + "href": "/fr/about", + "hreflang": "fr_FR", + }, + { + "href": "/de/about", + "hreflang": "de_DE", + }, + { + "href": "/about", + "hreflang": "x-default", + }, + ], + "loc": "/fr/about", + }, + { + "_sitemap": "de_DE", + "alternatives": [ + { + "href": "/about", + "hreflang": "en_EN", + }, + { + "href": "/fr/about", + "hreflang": "fr_FR", + }, + { + "href": "/de/about", + "hreflang": "de_DE", + }, + { + "href": "/about", + "hreflang": "x-default", + }, + ], + "loc": "/de/about", + }, + ] + `) + }) + it ('i18n micro', () => { expect(convertNuxtPagesToSitemapEntries([ { @@ -666,7 +769,6 @@ describe('page parser', () => { { code: 'ru', iso: 'ru_RU' }, ] }), strategy: 'prefix_except_default', - isI18nMicro: true, autoI18n: true, })).toMatchInlineSnapshot(` [ @@ -713,7 +815,6 @@ describe('page parser', () => { defaultLocale: 'en', normalisedLocales: normalizeLocales({ locales: [{ code: 'en' }, { code: 'fr' }] }), strategy: 'no_prefix', - isI18nMicro: false, autoI18n: false, }) // no entry should have alternatives when autoI18n is false