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

fix: rename locale iso property to language #3055

Merged
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
6 changes: 5 additions & 1 deletion docs/content/docs/5.v9/2.guide/18.breaking-changes-in-v9.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@ nuxt.config.ts

Reasons for change
1. Context - i18n files are used both server-side and client-side, using a dedicated `i18n/` folder in the root directory outside `app/` and `server/` makes more sense.
2. Clean - less clutter/fragmentation of i18n files, and should make resolving and loading files easier for us.
2. Clean - less clutter/fragmentation of i18n files, and should make resolving and loading files easier for us.

## Locale `iso` renamed to `language`

The `iso` property on a locale object has been renamed to `language` to be consistent with the usage of Language Tags on the web (e.g. `navigator.language` and `Accept-Language`). The original `iso` property name referred to ISO standards which describe valid Language Tags, see the [related issue](https://github.com/nuxt-modules/i18n/issues/2449) for more details.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default defineNuxtConfig({
For better SEO, it's recommended to set `redirectOn` to `root` (which is the default value). When set, the language detection is only attempted when the user visits the root path (`/`) of the site. This allows crawlers to access the requested page rather than being redirected away based on detected locale. It also allows linking to pages in specific locales.
::

Browser language is detected either from `navigator` when running on client-side, or from the `accept-language` HTTP header. Configured `locales` (or locales `iso` and/or `code` when locales are specified in object form) are matched against locales reported by the browser (for example `en-US,en;q=0.9,no;q=0.8`). If there is no exact match for the full locale, the language code (letters before `-`) are matched against configured locales.
Browser language is detected either from `navigator` when running on client-side, or from the `accept-language` HTTP header. Configured `locales` (or locales `language` and/or `code` when locales are specified in object form) are matched against locales reported by the browser (for example `en-US,en;q=0.9,no;q=0.8`). If there is no exact match for the full locale, the language code (letters before `-`) are matched against configured locales.

To prevent redirecting users every time they visit the app, **Nuxt i18n module** sets a cookie using the detected locale. You can change the cookie's name by setting `detectBrowserLanguage.cookieKey` option to whatever you'd like, the default is _i18n_redirected_.

Expand Down
26 changes: 13 additions & 13 deletions docs/content/docs/5.v9/2.guide/6.seo.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,23 @@ Here are the specific optimizations and features that it enables:

## Requirements

To leverage the SEO benefits, you must configure the `locales` option as an array of objects, where each object has an `iso` option set to the locale language tags:
To leverage the SEO benefits, you must configure the `locales` option as an array of objects, where each object has an `language` option set to the locale language tags:

```ts [nuxt.config.ts]
export default defineNuxtConfig({
i18n: {
locales: [
{
code: 'en',
iso: 'en-US'
language: 'en-US'
},
{
code: 'es',
iso: 'es-ES'
language: 'es-ES'
},
{
code: 'fr',
iso: 'fr-FR'
language: 'fr-FR'
}
]
}
Expand Down Expand Up @@ -165,11 +165,11 @@ useHead({

- `lang` attribute for the `<html>` tag

Sets the correct `lang` attribute, equivalent to the current locale's `iso` value, in the `<html>` tag.
Sets the correct `lang` attribute, equivalent to the current locale's `language` value, in the `<html>` tag.

- `hreflang` alternate link

Generates `<link rel="alternate" hreflang="x">` tags for every configured locale. The locales' `iso` value are used as `hreflang` values.
Generates `<link rel="alternate" hreflang="x">` tags for every configured locale. The locales' `language` value are used as `hreflang` values.

A "catchall" locale hreflang link is provided for each locale group (e.g. `en-*`). By default, it is the first locale provided, but another locale can be selected by setting `isCatchallLocale` to `true` on that specific locale object in your **Nuxt i18n module** configuration. [More on hreflang](https://support.google.com/webmasters/answer/189077)

Expand All @@ -181,11 +181,11 @@ useHead({
locales: [
{
code: 'en',
iso: 'en-US' // Will be used as "catchall" locale by default
language: 'en-US' // Will be used as "catchall" locale by default
},
{
code: 'gb',
iso: 'en-GB'
language: 'en-GB'
}
]
}
Expand All @@ -200,31 +200,31 @@ useHead({
locales: [
{
code: 'en',
iso: 'en-US'
language: 'en-US'
},
{
code: 'gb',
iso: 'en-GB',
language: 'en-GB',
isCatchallLocale: true // This one will be used as catchall locale
}
]
}
})
```

In case you already have an `en` locale `iso` set, it'll be used as the "catchall" without doing anything
In case you already have an `en` locale `language` set, it'll be used as the "catchall" without doing anything

```ts [nuxt.config.ts]
export default defineNuxtConfig({
i18n: {
locales: [
{
code: 'gb',
iso: 'en-GB'
language: 'en-GB'
},
{
code: 'en',
iso: 'en' // will be used as "catchall" locale
language: 'en' // will be used as "catchall" locale
}
]
}
Expand Down
8 changes: 4 additions & 4 deletions docs/content/docs/5.v9/3.options/2.routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ List of locales supported by your app. Can either be an array of codes (`['en',

```json
[
{ "code": "en", "iso": "en-US", "file": "en.js", "dir": "ltr" },
{ "code": "ar", "iso": "ar-EG", "file": "ar.js", "dir": "rtl" },
{ "code": "fr", "iso": "fr-FR", "file": "fr.js" }
{ "code": "en", "language": "en-US", "file": "en.js", "dir": "ltr" },
{ "code": "ar", "language": "ar-EG", "file": "ar.js", "dir": "rtl" },
{ "code": "fr", "language": "fr-FR", "file": "fr.js" }
]
```

When using an object form, the properties can be:

- `code` (**required**) - unique identifier of the locale
- `iso` (required when using SEO features) - A language-range used for SEO features and for matching browser locales when using [`detectBrowserLanguage`](/docs/options/browser#detectbrowserlanguage) functionality. Should use the [language tag syntax](https://www.w3.org/International/articles/language-tags/) as defined by the IETF's [BCP47](https://www.rfc-editor.org/info/bcp47), for example:
- `language` (required when using SEO features) - A language-range used for SEO features and for matching browser locales when using [`detectBrowserLanguage`](/docs/options/browser#detectbrowserlanguage) functionality. Should use the [language tag syntax](https://www.w3.org/International/articles/language-tags/) as defined by the IETF's [BCP47](https://www.rfc-editor.org/info/bcp47), for example:
- `'en'` (`language` subtag for English)
- `'fr-CA'` (`language+region` subtags for French as used in Canada)
- `'zh-Hans'` (`language+script` subtags for Chinese written with Simplified script)
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/composables/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function useSetI18nParams(seoAttributes?: SeoAttributesOptions): SetI18nP
})

const currentLocale = getNormalizedLocales(locales).find(l => l.code === locale) || { code: locale }
const currentLocaleIso = currentLocale.iso
const currentLocaleLanguage = currentLocale.language

const setMeta = () => {
const metaObject: HeadParam = {
Expand All @@ -94,8 +94,8 @@ export function useSetI18nParams(seoAttributes?: SeoAttributesOptions): SetI18nP

metaObject.meta.push(
...getOgUrl(common, idAttribute, seoAttributes),
...getCurrentOgLocale(currentLocale, currentLocaleIso, idAttribute),
...getAlternateOgLocales(locales, currentLocaleIso, idAttribute)
...getCurrentOgLocale(currentLocale, currentLocaleLanguage, idAttribute),
...getAlternateOgLocales(locales, currentLocaleLanguage, idAttribute)
)
}

Expand Down
40 changes: 20 additions & 20 deletions src/runtime/routing/compatibles/head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function localeHead(
const currentLocale = getNormalizedLocales(locales).find(l => l.code === locale) || {
code: locale
}
const currentIso = currentLocale.iso
const currentLanguage = currentLocale.language
const currentDir = currentLocale.dir || defaultDirection

// Adding Direction Attribute
Expand All @@ -55,8 +55,8 @@ export function localeHead(

// Adding SEO Meta
if (seoAttributes && locale && unref(i18n.locales)) {
if (currentIso) {
metaObject.htmlAttrs.lang = currentIso
if (currentLanguage) {
metaObject.htmlAttrs.lang = currentLanguage
}

metaObject.link.push(
Expand All @@ -66,8 +66,8 @@ export function localeHead(

metaObject.meta.push(
...getOgUrl(common, idAttribute, seoAttributes),
...getCurrentOgLocale(currentLocale, currentIso, idAttribute),
...getAlternateOgLocales(unref(locales) as LocaleObject[], currentIso, idAttribute)
...getCurrentOgLocale(currentLocale, currentLanguage, idAttribute),
...getAlternateOgLocales(unref(locales) as LocaleObject[], currentLanguage, idAttribute)
)
}

Expand All @@ -93,29 +93,29 @@ export function getHreflangLinks(

const localeMap = new Map<string, LocaleObject>()
for (const locale of locales) {
const localeIso = locale.iso
const localeLanguage = locale.language

if (!localeIso) {
console.warn('Locale ISO code is required to generate alternate link')
if (!localeLanguage) {
console.warn('Locale `language` ISO code is required to generate alternate link')
continue
}

const [language, region] = localeIso.split('-')
const [language, region] = localeLanguage.split('-')
if (language && region && (locale.isCatchallLocale || !localeMap.has(language))) {
localeMap.set(language, locale)
}

localeMap.set(localeIso, locale)
localeMap.set(localeLanguage, locale)
}

for (const [iso, mapLocale] of localeMap.entries()) {
for (const [language, mapLocale] of localeMap.entries()) {
const localePath = switchLocalePath(common, mapLocale.code)
if (localePath) {
links.push({
[idAttribute]: `i18n-alt-${iso}`,
[idAttribute]: `i18n-alt-${language}`,
rel: 'alternate',
href: toAbsoluteUrl(localePath, baseUrl),
hreflang: iso
hreflang: language
})
}
}
Expand Down Expand Up @@ -199,26 +199,26 @@ export function getOgUrl(

export function getCurrentOgLocale(
currentLocale: LocaleObject,
currentIso: string | undefined,
currentLanguage: string | undefined,
idAttribute: NonNullable<I18nHeadOptions['identifierAttribute']>
) {
if (!currentLocale || !currentIso) return []
if (!currentLocale || !currentLanguage) return []

// Replace dash with underscore as defined in spec: language_TERRITORY
return [{ [idAttribute]: 'i18n-og', property: 'og:locale', content: hypenToUnderscore(currentIso) }]
return [{ [idAttribute]: 'i18n-og', property: 'og:locale', content: hypenToUnderscore(currentLanguage) }]
}

export function getAlternateOgLocales(
locales: LocaleObject[],
currentIso: string | undefined,
currentLanguage: string | undefined,
idAttribute: NonNullable<I18nHeadOptions['identifierAttribute']>
) {
const alternateLocales = locales.filter(locale => locale.iso && locale.iso !== currentIso)
const alternateLocales = locales.filter(locale => locale.language && locale.language !== currentLanguage)

return alternateLocales.map(locale => ({
[idAttribute]: `i18n-og-alt-${locale.iso}`,
[idAttribute]: `i18n-og-alt-${locale.language}`,
property: 'og:locale:alternate',
content: hypenToUnderscore(locale.iso!)
content: hypenToUnderscore(locale.language!)
}))
}

Expand Down
10 changes: 5 additions & 5 deletions src/runtime/routing/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export interface BrowserLocale {
* @remarks
* This type is used by {@link BrowserLocaleMatcher} first argument
*/
export type TargetLocale = Required<Pick<LocaleObject, 'code' | 'iso'>>
export type TargetLocale = Required<Pick<LocaleObject, 'code' | 'language'>>

/**
* The browser locale matcher
Expand Down Expand Up @@ -119,7 +119,7 @@ function matchBrowserLocale(locales: TargetLocale[], browserLocales: string[]):

// first pass: match exact locale.
for (const [index, browserCode] of browserLocales.entries()) {
const matchedLocale = locales.find(l => l.iso.toLowerCase() === browserCode.toLowerCase())
const matchedLocale = locales.find(l => l.language.toLowerCase() === browserCode.toLowerCase())
if (matchedLocale) {
matchedLocales.push({ code: matchedLocale.code, score: 1 - index / browserLocales.length })
break
Expand All @@ -129,7 +129,7 @@ function matchBrowserLocale(locales: TargetLocale[], browserLocales: string[]):
// second pass: match only locale code part of the browser locale (not including country).
for (const [index, browserCode] of browserLocales.entries()) {
const languageCode = browserCode.split('-')[0].toLowerCase()
const matchedLocale = locales.find(l => l.iso.split('-')[0].toLowerCase() === languageCode)
const matchedLocale = locales.find(l => l.language.split('-')[0].toLowerCase() === languageCode)
if (matchedLocale) {
// deduct a thousandth for being non-exact match.
matchedLocales.push({ code: matchedLocale.code, score: 0.999 - index / browserLocales.length })
Expand Down Expand Up @@ -175,8 +175,8 @@ export function findBrowserLocale(
const normalizedLocales = []
for (const l of locales) {
const { code } = l
const iso = l.iso || code
normalizedLocales.push({ code, iso })
const language = l.language || code
normalizedLocales.push({ code, language })
}

// finding!
Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,11 @@ export interface LocaleObject<T = Locale> extends Record<string, any> {
file?: string | LocaleFile
files?: string[] | LocaleFile[]
isCatchallLocale?: boolean
/**
* @deprecated in v9, use `language` instead
*/
iso?: string
language?: string
}

/**
Expand Down
12 changes: 10 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function getNormalizedLocales(locales: NuxtI18nOptions['locales']): Local
const normalized: LocaleObject[] = []
for (const locale of locales) {
if (isString(locale)) {
normalized.push({ code: locale, iso: locale })
normalized.push({ code: locale, language: locale })
} else {
normalized.push(locale)
}
Expand Down Expand Up @@ -508,13 +508,21 @@ export const mergeConfigLocales = (configs: LocaleConfig[], baseLocales: LocaleO

// set normalized locale or to existing entry
if (typeof locale === 'string') {
mergedLocales.set(code, merged ?? { iso: code, code })
mergedLocales.set(code, merged ?? { language: code, code })
continue
}

const resolvedFiles = resolveRelativeLocales(locale, config)
delete locale.file

if (locale.iso) {
console.warn(
`Locale ${locale.iso} uses deprecated \`iso\` property, this will be replaced with \`language\` in v9`
)
locale.language = locale.iso
delete locale.iso
}

// merge locale and files with existing entry
if (merged != null) {
merged.files ??= [] as LocaleFile[]
Expand Down
10 changes: 5 additions & 5 deletions test/routing-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ describe('findBrowserLocale', () => {

test('matches ISO locale code', () => {
const locales = [
{ code: 'cn', iso: 'zh-CN' },
{ code: 'en', iso: 'en-US' }
{ code: 'cn', language: 'zh-CN' },
{ code: 'en', language: 'en-US' }
]
const browserLocales = ['zh', 'zh-CN']

Expand All @@ -181,8 +181,8 @@ describe('findBrowserLocale', () => {

test('matches full ISO code', () => {
const locales = [
{ code: 'us', iso: 'en-US' },
{ code: 'gb', iso: 'en-GB' }
{ code: 'us', language: 'en-US' },
{ code: 'gb', language: 'en-GB' }
]
const browserLocales = ['en-GB', 'en']

Expand All @@ -203,7 +203,7 @@ describe('findBrowserLocale', () => {
const matchedLocales = [] as utils.BrowserLocale[]
for (const [index, browserCode] of browserLocales.entries()) {
const languageCode = browserCode.split('-')[0].toLowerCase()
const matchedLocale = locales.find(l => l.iso.split('-')[0].toLowerCase() === languageCode)
const matchedLocale = locales.find(l => l.language.split('-')[0].toLowerCase() === languageCode)
if (matchedLocale) {
matchedLocales.push({ code: matchedLocale.code, score: 1 * index })
break
Expand Down