diff --git a/docs/content/docs/2.guide/5.browser-language-detection.md b/docs/content/docs/2.guide/5.browser-language-detection.md index e449dff2b..f3c6c02a1 100644 --- a/docs/content/docs/2.guide/5.browser-language-detection.md +++ b/docs/content/docs/2.guide/5.browser-language-detection.md @@ -23,7 +23,7 @@ For better SEO, it's recommended to set `redirectOn` to `root` (which is the def 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. -To prevent redirecting users every time they visit the app, **Nuxt i18n module** sets a cookie after the first redirection. You can change the cookie's name by setting `detectBrowserLanguage.cookieKey` option to whatever you'd like, the default is _i18n_redirected_. +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_. ```ts [nuxt.config.ts] i18n: { diff --git a/docs/content/docs/5.v7/3.options-reference.md b/docs/content/docs/5.v7/3.options-reference.md index ba81e383a..727649160 100644 --- a/docs/content/docs/5.v7/3.options-reference.md +++ b/docs/content/docs/5.v7/3.options-reference.md @@ -160,7 +160,7 @@ Supported properties: - `all` - detect browser locale on all paths. - `root` (recommended for improved SEO) - only detect the browser locale on the root path (`/`) of the site. Only effective when using strategy other than `'no_prefix'`. - `no prefix` - a more permissive variant of `root` that will detect the browser locale on the root path (`/`) and also on paths that have no locale prefix (like `/foo`). Only effective when using strategy other than `'no_prefix'`. -- `useCookie` (default: `true`) - If enabled, a cookie is set once the user has been redirected to browser's preferred locale, to prevent subsequent redirections. Set to `false` to redirect every time. +- `useCookie` (default: `true`) - If enabled, a cookie is set (if unset) using the browser's preferred locale, to prevent subsequent redirections. Set to `false` to redirect every time. - `cookieAge` (default: `365`) - Sets the max age of the cookie in days. - `cookieKey` (default: `'i18n_redirected'`) - Cookie name. - `cookieDomain` (default: `null`) - Set to override the default domain of the cookie. Defaults to the **host** of the site. diff --git a/specs/browser_language_detection/no_prefix.spec.ts b/specs/browser_language_detection/no_prefix.spec.ts index ac4ad2258..efbfe59e4 100644 --- a/specs/browser_language_detection/no_prefix.spec.ts +++ b/specs/browser_language_detection/no_prefix.spec.ts @@ -38,6 +38,10 @@ test('detection with cookie', async () => { }) const { page } = await renderPage('/', { locale: 'en' }) const ctx = await page.context() + expect(await ctx.cookies()).toMatchObject([ + { name: 'my_custom_cookie_name', value: 'en', secure: true, sameSite: 'None' } + ]) + // click `fr` lang switch link await page.locator('#set-locale-link-fr').click() expect(await ctx.cookies()).toMatchObject([ diff --git a/specs/browser_language_detection/prefix_and_default.spec.ts b/specs/browser_language_detection/prefix_and_default.spec.ts index 8f26471f1..faa7987ce 100644 --- a/specs/browser_language_detection/prefix_and_default.spec.ts +++ b/specs/browser_language_detection/prefix_and_default.spec.ts @@ -110,6 +110,7 @@ test('alwaysRedirect: no prefix', async () => { // detect locale from navigator language expect(await getText(page, '#lang-switcher-current-locale code')).toEqual('en') + expect(await ctx.cookies()).toMatchObject([{ name: 'i18n_redirected', value: 'en' }]) // click `fr` lang switch with nutlink await page.locator('#set-locale-link-fr').click() diff --git a/specs/issues/2262.spec.ts b/specs/issues/2262.spec.ts index 177aaac15..0df086d71 100644 --- a/specs/issues/2262.spec.ts +++ b/specs/issues/2262.spec.ts @@ -16,6 +16,7 @@ describe('#2262', async () => { const ctx = await page.context() expect(await getText(page, '#msg')).toEqual('Welcome') + expect(await ctx.cookies()).toMatchObject([{ name: 'i18n_redirected', value: 'en' }]) // change to `fr` await page.locator('#fr').click() diff --git a/src/runtime/plugins/i18n.ts b/src/runtime/plugins/i18n.ts index bf81e7ef1..49bce230f 100644 --- a/src/runtime/plugins/i18n.ts +++ b/src/runtime/plugins/i18n.ts @@ -166,7 +166,7 @@ export default defineNuxtPlugin({ ) composer.setLocale = async (locale: string) => { const localeSetup = isInitialLocaleSetup(locale) - const [modified] = await loadAndSetLocale(locale, i18n, runtimeI18n, localeSetup) + const modified = await loadAndSetLocale(locale, i18n, runtimeI18n, localeSetup) if (modified && localeSetup) { notInitialSetup = false @@ -450,7 +450,7 @@ export default defineNuxtPlugin({ const localeSetup = isInitialLocaleSetup(locale) __DEBUG__ && console.log('localeSetup', localeSetup) - const [modified] = await loadAndSetLocale(locale, i18n, runtimeI18n, localeSetup) + const modified = await loadAndSetLocale(locale, i18n, runtimeI18n, localeSetup) if (modified && localeSetup) { notInitialSetup = false diff --git a/src/runtime/utils.ts b/src/runtime/utils.ts index d4dc43a60..a329e0043 100644 --- a/src/runtime/utils.ts +++ b/src/runtime/utils.ts @@ -107,39 +107,57 @@ export async function loadAndSetLocale( i18n: I18n, runtimeI18n: ModulePublicRuntimeConfig['i18n'], initial: boolean = false -): Promise<[boolean, string]> { +): Promise { const { differentDomains, skipSettingLocaleOnNavigate, lazy } = runtimeI18n const opts = runtimeDetectBrowserLanguage(runtimeI18n) const nuxtApp = useNuxtApp() - let ret = false const oldLocale = getLocale(i18n) + const localeCodes = getLocaleCodes(i18n) + + // sets the locale cookie if unset or not up to date + function syncCookie(locale: Locale = oldLocale) { + if (opts === false || !opts.useCookie) return + if (skipSettingLocaleOnNavigate) return + + setCookieLocale(i18n, locale) + } + __DEBUG__ && console.log('setLocale: new -> ', newLocale, ' old -> ', oldLocale, ' initial -> ', initial) + + // `newLocale` is unset or empty if (!newLocale) { - return [ret, oldLocale] + syncCookie() + return false } - // abort if different domains option enabled + // no change if different domains option enabled if (!initial && differentDomains) { - return [ret, oldLocale] + syncCookie() + return false } if (oldLocale === newLocale) { - return [ret, oldLocale] + syncCookie() + return false } - // call onBeforeLanguageSwitch + // call `onBeforeLanguageSwitch` which may return an override for `newLocale` const localeOverride = await onBeforeLanguageSwitch(i18n, oldLocale, newLocale, initial, nuxtApp) - const localeCodes = getLocaleCodes(i18n) - if (localeOverride && localeCodes && localeCodes.includes(localeOverride)) { - if (localeOverride === oldLocale) { - return [ret, oldLocale] + if (localeOverride && localeCodes.includes(localeOverride)) { + // resolved `localeOverride` is already in use + if (oldLocale === localeOverride) { + syncCookie() + return false } + newLocale = localeOverride } - const i18nFallbackLocales = getVueI18nPropertyValue(i18n, 'fallbackLocale') + // load locale messages required by `newLocale` if (lazy) { + const i18nFallbackLocales = getVueI18nPropertyValue(i18n, 'fallbackLocale') + const setter = (locale: Locale, message: Record) => mergeLocaleMessage(i18n, locale, message) if (i18nFallbackLocales) { const fallbackLocales = makeFallbackLocaleCodes(i18nFallbackLocales, [newLocale]) @@ -149,19 +167,16 @@ export async function loadAndSetLocale( } if (skipSettingLocaleOnNavigate) { - return [ret, oldLocale] + return false } - // set the locale - if (opts !== false && opts.useCookie) { - setCookieLocale(i18n, newLocale) - } + // sync cookie and set the locale + syncCookie(newLocale) setLocale(i18n, newLocale) await onLanguageSwitched(i18n, oldLocale, newLocale) - ret = true - return [ret, oldLocale] + return true } type LocaleLoader = () => Locale