Skip to content

Commit 92d9610

Browse files
authoredAug 30, 2024··
feat(i18n): support Nuxt I18n v9 (#351)
1 parent e879913 commit 92d9610

19 files changed

+746
-512
lines changed
 

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
"@nuxt/module-builder": "0.8.3",
7171
"@nuxt/test-utils": "^3.14.1",
7272
"@nuxt/ui": "^2.18.4",
73-
"@nuxtjs/i18n": "8.5.1",
73+
"@nuxtjs/i18n": "9.0.0-alpha.1",
7474
"@nuxtjs/robots": "4.1.3",
7575
"bumpp": "^9.5.2",
7676
"eslint": "9.9.1",

‎pnpm-lock.yaml

+563-418
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/module.ts

+12-15
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,22 @@ import type {
2222
AutoI18nConfig,
2323
ModuleRuntimeConfig,
2424
MultiSitemapEntry,
25-
NormalisedLocales,
2625
SitemapDefinition,
2726
SitemapSourceBase,
2827
SitemapSourceInput,
2928
SitemapSourceResolved,
3029
ModuleOptions as _ModuleOptions, FilterInput,
31-
NormalisedLocale,
3230
} from './runtime/types'
3331
import { convertNuxtPagesToSitemapEntries, generateExtraRoutesFromNuxtConfig, resolveUrls } from './util/nuxtSitemap'
3432
import { createNitroPromise, createPagesPromise, extendTypes, getNuxtModuleOptions, resolveNitroPreset } from './util/kit'
3533
import { includesSitemapRoot, isNuxtGenerate, setupPrerenderHandler } from './prerender'
36-
import { mergeOnKey } from './runtime/utils-pure'
3734
import { setupDevToolsUI } from './devtools'
3835
import { normaliseDate } from './runtime/nitro/sitemap/urlset/normalise'
39-
import { generatePathForI18nPages, getOnlyLocalesFromI18nConfig, splitPathForI18nLocales } from './util/i18n'
36+
import {
37+
generatePathForI18nPages,
38+
normalizeLocales,
39+
splitPathForI18nLocales,
40+
} from './util/i18n'
4041
import { normalizeFilters } from './util/filter'
4142

4243
// eslint-disable-next-line
@@ -155,18 +156,14 @@ export default defineNuxtModule<ModuleOptions>({
155156
let nuxtI18nConfig = {} as NuxtI18nOptions
156157
let resolvedAutoI18n: false | AutoI18nConfig = typeof config.autoI18n === 'boolean' ? false : config.autoI18n || false
157158
const hasDisabledAutoI18n = typeof config.autoI18n === 'boolean' && !config.autoI18n
158-
let normalisedLocales: NormalisedLocales = []
159+
let normalisedLocales: AutoI18nConfig['locales'] = []
159160
let usingI18nPages = false
160161
if (hasNuxtModule('@nuxtjs/i18n')) {
161162
const i18nVersion = await getNuxtModuleVersion('@nuxtjs/i18n')
162163
if (!await hasNuxtModuleCompatibility('@nuxtjs/i18n', '>=8'))
163164
logger.warn(`You are using @nuxtjs/i18n v${i18nVersion}. For the best compatibility, please upgrade to @nuxtjs/i18n v8.0.0 or higher.`)
164165
nuxtI18nConfig = (await getNuxtModuleOptions('@nuxtjs/i18n') || {}) as NuxtI18nOptions
165-
normalisedLocales = mergeOnKey((nuxtI18nConfig.locales || []).map((locale: any) => typeof locale === 'string' ? { code: locale } : locale), 'code')
166-
const onlyLocales = getOnlyLocalesFromI18nConfig(nuxtI18nConfig)
167-
if (onlyLocales.length) {
168-
normalisedLocales = normalisedLocales.filter((locale: NormalisedLocale) => onlyLocales.includes(locale.code))
169-
}
166+
normalisedLocales = normalizeLocales(nuxtI18nConfig)
170167
usingI18nPages = !!Object.keys(nuxtI18nConfig.pages || {}).length
171168
if (usingI18nPages && !hasDisabledAutoI18n) {
172169
const i18nPagesSources: SitemapSourceBase = {
@@ -189,20 +186,20 @@ export default defineNuxtModule<ModuleOptions>({
189186
// add to sitemap
190187
const alternatives = Object.keys(pageLocales)
191188
.map(l => ({
192-
hreflang: normalisedLocales.find(nl => nl.code === l)?.iso || l,
189+
hreflang: normalisedLocales.find(nl => nl.code === l)?._hreflang || l,
193190
href: generatePathForI18nPages({ localeCode: l, pageLocales: pageLocales[l], nuxtI18nConfig, normalisedLocales }),
194191
}))
195192
if (alternatives.length && nuxtI18nConfig.defaultLocale && pageLocales[nuxtI18nConfig.defaultLocale])
196193
alternatives.push({ hreflang: 'x-default', href: generatePathForI18nPages({ normalisedLocales, localeCode: nuxtI18nConfig.defaultLocale, pageLocales: pageLocales[nuxtI18nConfig.defaultLocale], nuxtI18nConfig }) })
197194
i18nPagesSources.urls!.push({
198-
_sitemap: locale.iso || locale.code,
195+
_sitemap: locale._sitemap,
199196
loc: generatePathForI18nPages({ normalisedLocales, localeCode, pageLocales: pageLocales[localeCode], nuxtI18nConfig }),
200197
alternatives,
201198
})
202199
// add extra loc with the default locale code prefix on prefix and default strategy
203200
if (nuxtI18nConfig.strategy === 'prefix_and_default' && localeCode === nuxtI18nConfig.defaultLocale) {
204201
i18nPagesSources.urls!.push({
205-
_sitemap: locale.iso || locale.code,
202+
_sitemap: locale._sitemap,
206203
loc: generatePathForI18nPages({ normalisedLocales, localeCode, pageLocales: pageLocales[localeCode], nuxtI18nConfig, forcedStrategy: 'prefix' }),
207204
alternatives,
208205
})
@@ -240,7 +237,7 @@ export default defineNuxtModule<ModuleOptions>({
240237
config.sitemaps = { index: [...(config.sitemaps?.index || []), ...(config.appendSitemaps || [])] }
241238
for (const locale of resolvedAutoI18n.locales)
242239
// @ts-expect-error untyped
243-
config.sitemaps[locale.iso || locale.code] = { includeAppSources: true }
240+
config.sitemaps[locale._sitemap] = { includeAppSources: true }
244241
isI18nMapped = true
245242
usingMultiSitemaps = true
246243
}
@@ -603,7 +600,7 @@ declare module 'vue-router' {
603600
if (!pageSource.length) {
604601
pageSource.push(nuxt.options.app.baseURL || '/')
605602
}
606-
if (!resolvedConfigUrls) {
603+
if (!resolvedConfigUrls && config.urls) {
607604
config.urls && userGlobalSources.push({
608605
context: {
609606
name: 'sitemap:urls',

‎src/prerender.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ export function setupPrerenderHandler(_options: { runtimeConfig: ModuleRuntimeCo
8080
// if it's missing a locale then we put it in the default locale sitemap
8181
const locale = match[0] || options.autoI18n.defaultLocale
8282
if (options.isI18nMapped) {
83-
const { code, iso } = options.autoI18n.locales.find(l => l.code === locale) || { code: locale, iso: locale }
83+
const { _sitemap } = options.autoI18n.locales.find(l => l.code === locale) || { _sitemap: locale }
8484
// this will filter the results to only the sitemap that matches the locale
85-
route._sitemap._sitemap = iso || code
85+
route._sitemap._sitemap = _sitemap
8686
}
8787
}
8888
route._sitemap = defu(extractSitemapMetaFromHtml(html, {

‎src/runtime/nitro/sitemap/builder/sitemap.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export function resolveSitemapEntries(sitemap: SitemapDefinition, sources: Sitem
7979
}
8080
entries.push({
8181
href: u.loc,
82-
hreflang: u._locale.code || autoI18n.defaultLocale,
82+
hreflang: u._locale._hreflang || autoI18n.defaultLocale,
8383
})
8484
return entries
8585
})
@@ -98,15 +98,15 @@ export function resolveSitemapEntries(sitemap: SitemapDefinition, sources: Sitem
9898
e.alternatives = [
9999
{
100100
// apply default locale domain
101-
...autoI18n.locales.find(l => [l.code, l.iso].includes(autoI18n.defaultLocale)),
101+
...autoI18n.locales.find(l => [l.code, l.language].includes(autoI18n.defaultLocale)),
102102
code: 'x-default',
103103
},
104104
...autoI18n.locales
105105
.filter(l => !!l.domain),
106106
]
107107
.map((locale) => {
108108
return {
109-
hreflang: locale.iso || locale.code,
109+
hreflang: locale._hreflang,
110110
href: joinURL(withHttps(locale.domain!), e._pathWithoutPrefix),
111111
}
112112
})
@@ -117,15 +117,15 @@ export function resolveSitemapEntries(sitemap: SitemapDefinition, sources: Sitem
117117
let loc = joinURL(`/${l.code}`, e._pathWithoutPrefix)
118118
if (autoI18n.differentDomains || (['prefix_and_default', 'prefix_except_default'].includes(autoI18n.strategy) && l.code === autoI18n.defaultLocale))
119119
loc = e._pathWithoutPrefix
120-
const _sitemap = isI18nMapped ? (l.iso || l.code) : undefined
120+
const _sitemap = isI18nMapped ? l._sitemap : undefined
121121
const newEntry: NormalizedI18n = preNormalizeEntry({
122122
_sitemap,
123123
...e,
124124
_index: undefined,
125125
_key: `${_sitemap || ''}${loc}`,
126126
_locale: l,
127127
loc,
128-
alternatives: [{ code: 'x-default' }, ...autoI18n.locales].map((locale) => {
128+
alternatives: [{ code: 'x-default', _hreflang: 'x-default' }, ...autoI18n.locales].map((locale) => {
129129
const code = locale.code === 'x-default' ? autoI18n.defaultLocale : locale.code
130130
const isDefault = locale.code === 'x-default' || locale.code === autoI18n.defaultLocale
131131
let href = ''
@@ -141,11 +141,10 @@ export function resolveSitemapEntries(sitemap: SitemapDefinition, sources: Sitem
141141
href = joinURL('/', code, e._pathWithoutPrefix)
142142
}
143143
}
144-
const hreflang = locale.iso || locale.code
145144
if (!filterPath(href))
146145
return false
147146
return {
148-
hreflang,
147+
hreflang: locale._hreflang,
149148
href,
150149
}
151150
}).filter(Boolean),
@@ -163,7 +162,7 @@ export function resolveSitemapEntries(sitemap: SitemapDefinition, sources: Sitem
163162
}
164163
}
165164
if (isI18nMapped) {
166-
e._sitemap = e._sitemap || e._locale.iso || e._locale.code
165+
e._sitemap = e._sitemap || e._locale._sitemap
167166
}
168167
if (e._index)
169168
_urls[e._index] = e
@@ -207,7 +206,7 @@ export async function buildSitemapUrls(sitemap: SitemapDefinition, resolvers: Ni
207206
return urls
208207
}
209208
if (autoI18n?.differentDomains) {
210-
const domain = autoI18n.locales.find(e => [e.iso, e.code].includes(sitemap.sitemapName))?.domain
209+
const domain = autoI18n.locales.find(e => [e.language, e.code].includes(sitemap.sitemapName))?.domain
211210
if (domain) {
212211
const _tester = resolvers.canonicalUrlResolver
213212
resolvers.canonicalUrlResolver = (path: string) => resolveSitePath(path, {

‎src/runtime/types.ts

+24-3
Original file line numberDiff line numberDiff line change
@@ -204,12 +204,33 @@ export type AppSourceContext = 'nuxt:pages' | 'nuxt:prerender' | 'nuxt:route-rul
204204

205205
export type SitemapSourceInput = string | [string, FetchOptions] | SitemapSourceBase | SitemapSourceResolved
206206

207-
export interface NormalisedLocale { code: string, iso?: string, domain?: string }
207+
// copied from @nuxtjs/i18n, types do not appear to be working
208+
interface LocaleObject extends Record<string, any> {
209+
code: string
210+
name?: string
211+
dir?: 'ltr' | 'rtl' | 'auto'
212+
domain?: string
213+
domains?: string[]
214+
defaultForDomains?: string[]
215+
file?: string | {
216+
path: string
217+
cache?: boolean
218+
}
219+
files?: string[] | {
220+
path: string
221+
cache?: boolean
222+
}[]
223+
isCatchallLocale?: boolean
224+
/**
225+
* @deprecated in v9, use `language` instead
226+
*/
227+
iso?: string
228+
language?: string
229+
}
208230

209-
export type NormalisedLocales = NormalisedLocale[]
210231
export interface AutoI18nConfig {
211232
differentDomains?: boolean
212-
locales: NormalisedLocales
233+
locales: (LocaleObject & { _sitemap: string, _hreflang: string })[]
213234
defaultLocale: string
214235
strategy: 'prefix' | 'prefix_except_default' | 'prefix_and_default' | 'no_prefix'
215236
}

‎src/util/i18n.ts

+23-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import type { NuxtI18nOptions } from '@nuxtjs/i18n'
1+
import type { NuxtI18nOptions, LocaleObject } from '@nuxtjs/i18n'
22
import type { Strategies } from 'vue-i18n-routing'
33
import { joinURL, withBase, withHttps } from 'ufo'
4-
import type { AutoI18nConfig, FilterInput, NormalisedLocales } from '../runtime/types'
5-
import { splitForLocales } from '../runtime/utils-pure'
4+
import type { AutoI18nConfig, FilterInput } from '../runtime/types'
5+
import { mergeOnKey, splitForLocales } from '../runtime/utils-pure'
66

77
export interface StrategyProps {
88
localeCode: string
99
pageLocales: string
1010
nuxtI18nConfig: NuxtI18nOptions
1111
forcedStrategy?: Strategies
12-
normalisedLocales: NormalisedLocales
12+
normalisedLocales: AutoI18nConfig['locales']
1313
}
1414

1515
export function splitPathForI18nLocales(path: FilterInput, autoI18n: AutoI18nConfig) {
@@ -27,13 +27,6 @@ export function splitPathForI18nLocales(path: FilterInput, autoI18n: AutoI18nCon
2727
]
2828
}
2929

30-
export function getOnlyLocalesFromI18nConfig(nuxtI18nConfig: NuxtI18nOptions) {
31-
const onlyLocales = nuxtI18nConfig?.bundle?.onlyLocales
32-
if (!onlyLocales) return []
33-
const includedLocales = typeof onlyLocales === 'string' ? [onlyLocales] : onlyLocales
34-
return includedLocales
35-
}
36-
3730
export function generatePathForI18nPages(ctx: StrategyProps): string {
3831
const { localeCode, pageLocales, nuxtI18nConfig, forcedStrategy, normalisedLocales } = ctx
3932
const locale = normalisedLocales.find(l => l.code === localeCode)
@@ -49,3 +42,22 @@ export function generatePathForI18nPages(ctx: StrategyProps): string {
4942
}
5043
return locale?.domain ? withHttps(withBase(path, locale.domain)) : path
5144
}
45+
46+
export function normalizeLocales(nuxtI18nConfig: NuxtI18nOptions): AutoI18nConfig['locales'] {
47+
let locales = nuxtI18nConfig.locales || []
48+
let onlyLocales = nuxtI18nConfig?.bundle?.onlyLocales || []
49+
onlyLocales = typeof onlyLocales === 'string' ? [onlyLocales] : onlyLocales
50+
locales = mergeOnKey(locales.map((locale: any) => typeof locale === 'string' ? { code: locale } : locale), 'code')
51+
if (onlyLocales.length) {
52+
locales = locales.filter((locale: LocaleObject) => onlyLocales.includes(locale.code))
53+
}
54+
return locales.map((locale) => {
55+
// we prefer i18n v9 config
56+
if (locale.iso && !locale.language) {
57+
locale.language = locale.iso
58+
}
59+
locale._hreflang = locale.language || locale.code
60+
locale._sitemap = locale.language || locale.code
61+
return locale
62+
})
63+
}

‎src/util/nuxtSitemap.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { extname } from 'pathe'
66
import { defu } from 'defu'
77
import type { ConsolaInstance } from 'consola'
88
import { withBase, withHttps } from 'ufo'
9-
import type { NormalisedLocales, SitemapDefinition, SitemapUrl, SitemapUrlInput } from '../runtime/types'
9+
import type { AutoI18nConfig, SitemapDefinition, SitemapUrl, SitemapUrlInput } from '../runtime/types'
1010
import { createPathFilter } from '../runtime/utils-pure'
1111
import type { CreateFilterOptions } from '../runtime/utils-pure'
1212

@@ -28,7 +28,7 @@ export async function resolveUrls(urls: Required<SitemapDefinition>['urls'], ctx
2828
}
2929

3030
export interface NuxtPagesToSitemapEntriesOptions {
31-
normalisedLocales: NormalisedLocales
31+
normalisedLocales: AutoI18nConfig['locales']
3232
routesNameSeparator?: string
3333
autoLastmod: boolean
3434
defaultLocale: string
@@ -118,8 +118,8 @@ export function convertNuxtPagesToSitemapEntries(pages: NuxtPage[], config: Nuxt
118118
const [name, locale] = e.page!.name.split(routesNameSeparator)
119119
if (!acc[name])
120120
acc[name] = []
121-
const { iso, code } = config.normalisedLocales.find(l => l.code === locale) || { iso: locale, code: locale }
122-
acc[name].push({ ...e, _sitemap: config.isI18nMapped ? (iso || code) : undefined, locale })
121+
const { _sitemap } = config.normalisedLocales.find(l => l.code === locale) || { _sitemap: locale }
122+
acc[name].push({ ...e, _sitemap: config.isI18nMapped ? _sitemap : undefined, locale })
123123
}
124124
else {
125125
acc.default = acc.default || []
@@ -141,7 +141,7 @@ export function convertNuxtPagesToSitemapEntries(pages: NuxtPage[], config: Nuxt
141141
return false
142142
const defaultLocale = config.normalisedLocales.find(l => l.code === config.defaultLocale)
143143
if (defaultLocale && config.isI18nMapped)
144-
e._sitemap = defaultLocale.iso || defaultLocale.code
144+
e._sitemap = defaultLocale._sitemap
145145
delete e.page
146146
delete e.locale
147147
return { ...e }
@@ -151,12 +151,11 @@ export function convertNuxtPagesToSitemapEntries(pages: NuxtPage[], config: Nuxt
151151
const alternatives = entries.map((entry) => {
152152
const locale = config.normalisedLocales.find(l => l.code === entry.locale)
153153
// check if the locale has a iso code
154-
const hreflang = locale?.iso || entry.locale
155154
if (!pathFilter(entry.loc))
156155
return false
157156
const href = locale?.domain ? withHttps(withBase(entry.loc, locale?.domain)) : entry.loc
158157
return {
159-
hreflang,
158+
hreflang: locale?._hreflang,
160159
href,
161160
}
162161
}).filter(Boolean)
@@ -171,8 +170,8 @@ export function convertNuxtPagesToSitemapEntries(pages: NuxtPage[], config: Nuxt
171170
}
172171
const e = { ...entry }
173172
if (config.isI18nMapped) {
174-
const { iso, code } = config.normalisedLocales.find(l => l.code === entry.locale) || { iso: locale, code: locale }
175-
e._sitemap = iso || code
173+
const { _sitemap } = config.normalisedLocales.find(l => l.code === entry.locale) || { _sitemap: locale }
174+
e._sitemap = _sitemap
176175
}
177176
delete e.page
178177
delete e.locale

‎test/integration/i18n/dynamic-urls.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('i18n dynamic urls', () => {
4242
<url>
4343
<loc>https://nuxtseo.com/english-url</loc>
4444
<xhtml:link rel="alternate" href="https://nuxtseo.com/english-url" hreflang="x-default" />
45-
<xhtml:link rel="alternate" href="https://nuxtseo.com/english-url" hreflang="en" />
45+
<xhtml:link rel="alternate" href="https://nuxtseo.com/english-url" hreflang="en-US" />
4646
</url>
4747
<url>
4848
<loc>https://nuxtseo.com/__sitemap/url</loc>
@@ -58,8 +58,8 @@ describe('i18n dynamic urls', () => {
5858
<url>
5959
<loc>https://nuxtseo.com/en/dynamic/foo</loc>
6060
<xhtml:link rel="alternate" href="https://nuxtseo.com/en/dynamic/foo" hreflang="x-default" />
61-
<xhtml:link rel="alternate" href="https://nuxtseo.com/en/dynamic/foo" hreflang="en" />
62-
<xhtml:link rel="alternate" href="https://nuxtseo.com/fr/dynamic/foo" hreflang="fr" />
61+
<xhtml:link rel="alternate" href="https://nuxtseo.com/en/dynamic/foo" hreflang="en-US" />
62+
<xhtml:link rel="alternate" href="https://nuxtseo.com/fr/dynamic/foo" hreflang="fr-FR" />
6363
</url>
6464
</urlset>"
6565
`)

‎test/integration/i18n/filtering.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ describe('i18n filtering', () => {
3434
<url>
3535
<loc>https://nuxtseo.com/no-i18n</loc>
3636
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="x-default" />
37-
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="en" />
37+
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="en-US" />
3838
</url>
3939
<url>
4040
<loc>https://nuxtseo.com/en/__sitemap/url</loc>

‎test/integration/i18n/generate.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('generate', () => {
5151
<url>
5252
<loc>https://nuxtseo.com/no-i18n</loc>
5353
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="x-default" />
54-
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="en" />
54+
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="en-US" />
5555
</url>
5656
<url>
5757
<loc>https://nuxtseo.com/en/test</loc>

‎test/integration/i18n/prefix-and-default.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ describe('i18n prefix and default', () => {
6767
<url>
6868
<loc>https://nuxtseo.com/no-i18n</loc>
6969
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="x-default" />
70-
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="en" />
70+
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="en-US" />
7171
</url>
7272
<url>
7373
<loc>https://nuxtseo.com/test</loc>

‎test/integration/i18n/prefix-except-default.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ describe('i18n prefix except default', () => {
6767
<url>
6868
<loc>https://nuxtseo.com/no-i18n</loc>
6969
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="x-default" />
70-
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="en" />
70+
<xhtml:link rel="alternate" href="https://nuxtseo.com/no-i18n" hreflang="en-US" />
7171
</url>
7272
<url>
7373
<loc>https://nuxtseo.com/test</loc>

0 commit comments

Comments
 (0)
Please sign in to comment.