diff --git a/specs/basic_usage.spec.ts b/specs/basic_usage.spec.ts index 0bc380cc6..07f816b86 100644 --- a/specs/basic_usage.spec.ts +++ b/specs/basic_usage.spec.ts @@ -355,7 +355,3 @@ test('dynamic parameters', async () => { const product2dom = getDom(product2Html) expect(product2dom.querySelector('#i18n-alt-en').href).toEqual('/products/red-mug') }) - -test('(#2554) using `setLocale` in plugin should not throw an error', async () => { - await expect($fetch('/?pluginSetLocale')).resolves.to.not.toThrowError() -}) diff --git a/specs/fixtures/basic_usage/plugins/set-locale-plugin.ts b/specs/fixtures/basic_usage/plugins/set-locale-plugin.ts deleted file mode 100644 index 50eb9451a..000000000 --- a/specs/fixtures/basic_usage/plugins/set-locale-plugin.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineNuxtPlugin } from '#imports' - -export default defineNuxtPlugin(async nuxtApp => { - if ('pluginSetLocale' in nuxtApp._route.query) { - const app = useNuxtApp() - await app.$i18n.setLocale('fr') - } -}) diff --git a/specs/fixtures/issues/2554/app.vue b/specs/fixtures/issues/2554/app.vue new file mode 100644 index 000000000..8f62b8bf9 --- /dev/null +++ b/specs/fixtures/issues/2554/app.vue @@ -0,0 +1,3 @@ + diff --git a/specs/fixtures/issues/2554/nuxt.config.ts b/specs/fixtures/issues/2554/nuxt.config.ts new file mode 100644 index 000000000..dede1304e --- /dev/null +++ b/specs/fixtures/issues/2554/nuxt.config.ts @@ -0,0 +1,7 @@ +export default defineNuxtConfig({ + modules: ['@nuxtjs/i18n'], + i18n: { + locales: ['en', 'fr'], + strategy: 'no_prefix' + } +}) diff --git a/specs/fixtures/issues/2554/package.json b/specs/fixtures/issues/2554/package.json new file mode 100644 index 000000000..733066968 --- /dev/null +++ b/specs/fixtures/issues/2554/package.json @@ -0,0 +1,14 @@ +{ + "name": "nuxt3-test-issues-2473", + "private": true, + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "generate": "nuxt generate", + "preview": "nuxt preview" + }, + "devDependencies": { + "@nuxtjs/i18n": "latest", + "nuxt": "latest" + } +} diff --git a/specs/fixtures/issues/2554/pages/index.vue b/specs/fixtures/issues/2554/pages/index.vue new file mode 100644 index 000000000..89eb55fef --- /dev/null +++ b/specs/fixtures/issues/2554/pages/index.vue @@ -0,0 +1,3 @@ + diff --git a/specs/fixtures/issues/2554/plugins/set-locale-plugin.ts b/specs/fixtures/issues/2554/plugins/set-locale-plugin.ts new file mode 100644 index 000000000..530afed87 --- /dev/null +++ b/specs/fixtures/issues/2554/plugins/set-locale-plugin.ts @@ -0,0 +1,8 @@ +import { defineNuxtPlugin } from '#imports' + +export default defineNuxtPlugin(async nuxtApp => { + if ('pluginSetLocale' in nuxtApp._route.query && typeof nuxtApp._route.query.pluginSetLocale === 'string') { + const app = useNuxtApp() + await app.$i18n.setLocale(nuxtApp._route.query.pluginSetLocale) + } +}) diff --git a/specs/issues/2554.spec.ts b/specs/issues/2554.spec.ts new file mode 100644 index 000000000..9222afa6b --- /dev/null +++ b/specs/issues/2554.spec.ts @@ -0,0 +1,22 @@ +import { test, expect, describe } from 'vitest' +import { fileURLToPath } from 'node:url' +import { URL } from 'node:url' +import { setup, url } from '../utils' +import { renderPage } from '../helper' + +describe('#2554', async () => { + await setup({ + rootDir: fileURLToPath(new URL(`../fixtures/issues/2554`, import.meta.url)), + browser: true + }) + + test('should not throw an error when using `setLocale` from plugin', async () => { + const { page } = await renderPage('/') + + const res1 = await page.goto(url('/?pluginSetLocale=fr')) + expect(res1?.ok()).toBeTruthy() + + const res2 = await page.goto(url('/?pluginSetLocale=en')) + expect(res2?.ok()).toBeTruthy() + }) +}) diff --git a/src/runtime/internal.ts b/src/runtime/internal.ts index b72ca4d4b..6a70c95c5 100644 --- a/src/runtime/internal.ts +++ b/src/runtime/internal.ts @@ -25,6 +25,7 @@ import { initCommonComposableOptions, type CommonComposableOptions } from './uti import type { Locale } from 'vue-i18n' import type { DetectBrowserLanguageOptions, LocaleObject } from '#build/i18n.options.mjs' import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router' +import type { CookieRef } from 'nuxt/app' export function formatMessage(message: string) { return NUXT_I18N_MODULE_ID + ' ' + message @@ -93,7 +94,25 @@ export function getBrowserLocale(): string | undefined { return ret } -export function getLocaleCookie(): string | undefined { +export function getI18nCookie() { + const detect = nuxtI18nOptions.detectBrowserLanguage + const cookieKey = (detect && detect.cookieKey) || nuxtI18nOptionsDefault.detectBrowserLanguage.cookieKey + const date = new Date() + const cookieOptions: Record = { + expires: new Date(date.setDate(date.getDate() + 365)), + path: '/', + sameSite: detect && detect.cookieCrossOrigin ? 'none' : 'lax', + secure: (detect && detect.cookieCrossOrigin) || (detect && detect.cookieSecure) + } + + if (detect && detect.cookieDomain) { + cookieOptions.domain = detect.cookieDomain + } + + return useNuxtCookie(cookieKey, cookieOptions) +} + +export function getLocaleCookie(cookieRef: CookieRef): string | undefined { const detect = nuxtI18nOptions.detectBrowserLanguage __DEBUG__ && @@ -107,8 +126,7 @@ export function getLocaleCookie(): string | undefined { return } - const localeCookie = useNuxtCookie(detect.cookieKey) - const localeCode: string | undefined = localeCookie.value ?? undefined + const localeCode: string | undefined = cookieRef.value ?? undefined __DEBUG__ && console.log(`getLocaleCookie cookie (${process.client ? 'client' : 'server'}) -`, localeCode) if (localeCode && localeCodes.includes(localeCode)) { @@ -116,28 +134,14 @@ export function getLocaleCookie(): string | undefined { } } -export function setLocaleCookie(locale: string) { - const { useCookie, cookieKey, cookieDomain, cookieSecure, cookieCrossOrigin } = - nuxtI18nOptions.detectBrowserLanguage || nuxtI18nOptionsDefault.detectBrowserLanguage +export function setLocaleCookie(cookieRef: CookieRef, locale: string) { + const { useCookie } = nuxtI18nOptions.detectBrowserLanguage || nuxtI18nOptionsDefault.detectBrowserLanguage if (!useCookie) { return } - const date = new Date() - const cookieOptions: Record = { - expires: new Date(date.setDate(date.getDate() + 365)), - path: '/', - sameSite: cookieCrossOrigin ? 'none' : 'lax', - secure: cookieCrossOrigin || cookieSecure - } - - if (cookieDomain) { - cookieOptions.domain = cookieDomain - } - - const localeCookie = useNuxtCookie(cookieKey, cookieOptions) - localeCookie.value = locale + cookieRef.value = locale } export type DetectBrowserLanguageNotDetectReason = @@ -160,6 +164,7 @@ export type DetectLocaleContext = { ssg: DetectLocaleForSSGStatus callType: DetectLocaleCallType firstAccess: boolean + localeCookie: CookieRef } export const DefaultDetectBrowserLanguageFromResult: DetectBrowserLanguageFromResult = { @@ -176,7 +181,7 @@ export function detectBrowserLanguage( locale: Locale = '' ): DetectBrowserLanguageFromResult { const { strategy } = nuxtI18nOptions - const { ssg, callType, firstAccess } = detectLocaleContext + const { ssg, callType, firstAccess, localeCookie } = detectLocaleContext __DEBUG__ && console.log('detectBrowserLanguage: (ssg, callType, firstAccess) - ', ssg, callType, firstAccess) // browser detection is ignored if it's a nuxt generate. @@ -223,7 +228,7 @@ export function detectBrowserLanguage( // get preferred language from cookie if present and enabled if (useCookie) { - matchedLocale = cookieLocale = getLocaleCookie() + matchedLocale = cookieLocale = localeCookie.value localeFrom = 'cookie' __DEBUG__ && console.log('detectBrowserLanguage: cookieLocale', cookieLocale) } diff --git a/src/runtime/plugins/i18n.ts b/src/runtime/plugins/i18n.ts index 084ad2e2e..e922b823d 100644 --- a/src/runtime/plugins/i18n.ts +++ b/src/runtime/plugins/i18n.ts @@ -25,7 +25,8 @@ import { getLocaleCookie as _getLocaleCookie, setLocaleCookie as _setLocaleCookie, detectBrowserLanguage, - DefaultDetectBrowserLanguageFromResult + DefaultDetectBrowserLanguageFromResult, + getI18nCookie } from '../internal' import { getComposer, getLocale, setLocale } from '../routing/utils' import { extendI18n, createLocaleFromRouteGetter } from '../routing/extends' @@ -68,6 +69,7 @@ export default defineNuxtPlugin({ const getLocaleFromRoute = createLocaleFromRouteGetter() const getDefaultLocale = (defaultLocale: string) => defaultLocale || vueI18nOptions.locale || 'en-US' + const localeCookie = getI18nCookie() // detect initial locale let initialLocale = detectLocale( route, @@ -77,7 +79,8 @@ export default defineNuxtPlugin({ { ssg: isSSG && nuxtI18nOptions.strategy === 'no_prefix' ? 'ssg_ignore' : 'normal', callType: 'setup', - firstAccess: true + firstAccess: true, + localeCookie } ) __DEBUG__ && console.log('first detect initial locale', initialLocale) @@ -124,7 +127,7 @@ export default defineNuxtPlugin({ ? detectBrowserLanguage( route, vueI18nOptions.locale, - { ssg: 'ssg_setup', callType: 'setup', firstAccess: true }, + { ssg: 'ssg_setup', callType: 'setup', firstAccess: true, localeCookie }, initialLocale ) : DefaultDetectBrowserLanguageFromResult @@ -187,8 +190,8 @@ export default defineNuxtPlugin({ composer.differentDomains = nuxtI18nOptions.differentDomains composer.defaultLocale = nuxtI18nOptions.defaultLocale composer.getBrowserLocale = () => _getBrowserLocale() - composer.getLocaleCookie = () => _getLocaleCookie() - composer.setLocaleCookie = (locale: string) => _setLocaleCookie(locale) + composer.getLocaleCookie = () => _getLocaleCookie(localeCookie) + composer.setLocaleCookie = (locale: string) => _setLocaleCookie(localeCookie, locale) composer.onBeforeLanguageSwitch = (oldLocale, newLocale, initialSetup, context) => nuxt.callHook('i18n:beforeLocaleSwitch', { oldLocale, newLocale, initialSetup, context }) @@ -394,7 +397,8 @@ export default defineNuxtPlugin({ { ssg: isSSGModeInitialSetup() && nuxtI18nOptions.strategy === 'no_prefix' ? 'ssg_ignore' : 'normal', callType: 'routing', - firstAccess: routeChangeCount === 0 + firstAccess: routeChangeCount === 0, + localeCookie } ) __DEBUG__ && console.log('detect locale', locale) diff --git a/src/runtime/utils.ts b/src/runtime/utils.ts index 4a3bb9499..2a82e819f 100644 --- a/src/runtime/utils.ts +++ b/src/runtime/utils.ts @@ -18,7 +18,6 @@ import { import { wrapComposable, detectBrowserLanguage, - getLocaleCookie, callVueI18nInterfaces, getVueI18nPropertyValue, defineGetter, @@ -179,7 +178,7 @@ export function detectLocale( const initialLocale = isFunction(initialLocaleLoader) ? initialLocaleLoader() : initialLocaleLoader __DEBUG__ && console.log('detectLocale: initialLocale -', initialLocale) - const { ssg, callType, firstAccess } = detectLocaleContext + const { ssg, callType, firstAccess, localeCookie } = detectLocaleContext __DEBUG__ && console.log('detectLocale: (ssg, callType, firstAccess) - ', ssg, callType, firstAccess) const { @@ -232,7 +231,7 @@ export function detectLocale( _detectBrowserLanguage ) if (!finalLocale && _detectBrowserLanguage && _detectBrowserLanguage.useCookie) { - finalLocale = getLocaleCookie() || '' + finalLocale = localeCookie.value || '' } __DEBUG__ && console.log('detectLocale: finalLocale last (finalLocale, defaultLocale) -', finalLocale, defaultLocale)