Skip to content

Commit

Permalink
fix: sync cookie when setting locale (#2877)
Browse files Browse the repository at this point in the history
  • Loading branch information
BobbieGoede committed Mar 24, 2024
1 parent b7a6c66 commit 1ce4aae
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 23 deletions.
2 changes: 1 addition & 1 deletion docs/content/docs/2.guide/5.browser-language-detection.md
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/5.v7/3.options-reference.md
Expand Up @@ -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.
Expand Down
4 changes: 4 additions & 0 deletions specs/browser_language_detection/no_prefix.spec.ts
Expand Up @@ -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([
Expand Down
Expand Up @@ -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()
Expand Down
1 change: 1 addition & 0 deletions specs/issues/2262.spec.ts
Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/plugins/i18n.ts
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
53 changes: 34 additions & 19 deletions src/runtime/utils.ts
Expand Up @@ -107,39 +107,57 @@ export async function loadAndSetLocale(
i18n: I18n,
runtimeI18n: ModulePublicRuntimeConfig['i18n'],
initial: boolean = false
): Promise<[boolean, string]> {
): Promise<boolean> {
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<FallbackLocale>(i18n, 'fallbackLocale')
// load locale messages required by `newLocale`
if (lazy) {
const i18nFallbackLocales = getVueI18nPropertyValue<FallbackLocale>(i18n, 'fallbackLocale')

const setter = (locale: Locale, message: Record<string, any>) => mergeLocaleMessage(i18n, locale, message)
if (i18nFallbackLocales) {
const fallbackLocales = makeFallbackLocaleCodes(i18nFallbackLocales, [newLocale])
Expand All @@ -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
Expand Down

0 comments on commit 1ce4aae

Please sign in to comment.