Skip to content

Commit

Permalink
fix: rename locale iso property to language (#3055)
Browse files Browse the repository at this point in the history
* fix: rename locale property `iso` to `language`

* refactor: rename parameters

* fix: locale `language` merging

* docs: expand `iso` rename explanation
  • Loading branch information
BobbieGoede authored Aug 11, 2024
1 parent 08638d7 commit 6a29add
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 54 deletions.
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

0 comments on commit 6a29add

Please sign in to comment.