diff --git a/.gitignore b/.gitignore index de749b982..ac243d8a5 100755 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,8 @@ node_modules .idea *.log* .nuxt +.nuxt-generate .vscode .DS_STORE coverage -dist \ No newline at end of file +dist diff --git a/src/plugins/main.js b/src/plugins/main.js index 1d3415d12..c8dbce935 100644 --- a/src/plugins/main.js +++ b/src/plugins/main.js @@ -14,12 +14,14 @@ const STRATEGIES = <%= JSON.stringify(options.STRATEGIES) %> const STRATEGY = '<%= options.strategy %>' const lazy = <%= options.lazy %> const vuex = <%= JSON.stringify(options.vuex) %> +// Helpers +const getLocaleCodes = <%= options.getLocaleCodes %> +const localeCodes = getLocaleCodes(<%= JSON.stringify(options.locales) %>) export default async (context) => { - const { app, route, store, req, res, redirect } = context; + const { app, route, store, req, res, redirect } = context // Helpers - const getLocaleCodes = <%= options.getLocaleCodes %> const getLocaleFromRoute = <%= options.getLocaleFromRoute %> const getHostname = <%= options.getHostname %> const getForwarded = <%= options.getForwarded %> @@ -84,7 +86,7 @@ export default async (context) => { const getLocaleCookie = () => { if (useCookie) { if (process.client) { - return JsCookie.get(cookieKey); + return JsCookie.get(cookieKey) } else if (req && typeof req.headers.cookie !== 'undefined') { const cookies = req.headers && req.headers.cookie ? Cookie.parse(req.headers.cookie) : {} return cookies[cookieKey] @@ -94,7 +96,7 @@ export default async (context) => { const setLocaleCookie = locale => { if (!useCookie) { - return; + return } const date = new Date() if (process.client) { @@ -210,4 +212,49 @@ export default async (context) => { } await loadAndSetLocale(locale, { initialSetup: true }) + + app.i18n.__detectBrowserLanguage = async route => { + const { alwaysRedirect, fallbackLocale } = detectBrowserLanguage + + if (detectBrowserLanguage) { + let browserLocale + + if (useCookie && (browserLocale = getLocaleCookie()) && browserLocale !== 1 && browserLocale !== '1') { + // Get preferred language from cookie if present and enabled + // Exclude 1 for backwards compatibility and fallback when fallbackLocale is empty + } else if (process.client && typeof navigator !== 'undefined' && navigator.language) { + // Get browser language either from navigator if running on client side, or from the headers + browserLocale = navigator.language.toLocaleLowerCase().substring(0, 2) + } else if (req && typeof req.headers['accept-language'] !== 'undefined') { + browserLocale = req.headers['accept-language'].split(',')[0].toLocaleLowerCase().substring(0, 2) + } + + if (browserLocale) { + // Handle cookie option to prevent multiple redirections + if (!useCookie || alwaysRedirect || !getLocaleCookie()) { + let redirectToLocale = fallbackLocale + + // Use browserLocale if we support it, otherwise use fallbackLocale + if (localeCodes.includes(browserLocale)) { + redirectToLocale = browserLocale + } + + if (redirectToLocale && localeCodes.includes(redirectToLocale)) { + if (redirectToLocale !== app.i18n.locale) { + // We switch the locale before redirect to prevent loops + await app.i18n.setLocale(redirectToLocale) + } else if (useCookie && !getLocaleCookie()) { + app.i18n.setLocaleCookie(redirectToLocale) + } + } + + return true + } + } + } + + return false + } + + await app.i18n.__detectBrowserLanguage(route) } diff --git a/src/templates/middleware.js b/src/templates/middleware.js index 054d193e8..ebe71f239 100644 --- a/src/templates/middleware.js +++ b/src/templates/middleware.js @@ -1,31 +1,15 @@ -import Cookie from 'cookie' -import JsCookie from 'js-cookie' import middleware from '../middleware' middleware['i18n'] = async (context) => { - const { app, req, route, store, redirect, isHMR } = context; + const { app, req, route, redirect, isHMR } = context if (isHMR) { return } - // Options - const STRATEGIES = <%= JSON.stringify(options.STRATEGIES) %> - const STRATEGY = '<%= options.strategy %>' - const lazy = <%= options.lazy %> - const vuex = <%= JSON.stringify(options.vuex) %> - const differentDomains = <%= options.differentDomains %> - // Helpers const LOCALE_CODE_KEY = '<%= options.LOCALE_CODE_KEY %>' const getLocaleCodes = <%= options.getLocaleCodes %> - const getLocaleFromRoute = <%= options.getLocaleFromRoute %> - const routesNameSeparator = '<%= options.routesNameSeparator %>' - const defaultLocaleRouteNameSuffix = '<%= options.defaultLocaleRouteNameSuffix %>' - const locales = getLocaleCodes(<%= JSON.stringify(options.locales) %>) - const syncVuex = <%= options.syncVuex %> - - let locale = app.i18n.locale || app.i18n.defaultLocale || null // Handle root path redirect const rootRedirect = '<%= options.rootRedirect %>' @@ -37,49 +21,19 @@ middleware['i18n'] = async (context) => { // Update for setLocale to have up to date route app.i18n.__route = route - // Handle browser language detection const detectBrowserLanguage = <%= JSON.stringify(options.detectBrowserLanguage) %> - const routeLocale = getLocaleFromRoute(route, routesNameSeparator, defaultLocaleRouteNameSuffix, locales) - - const { useCookie, cookieKey, alwaysRedirect, fallbackLocale } = detectBrowserLanguage - const { getLocaleCookie } = app.i18n - - if (detectBrowserLanguage) { - let browserLocale - - if (useCookie && (browserLocale = getLocaleCookie()) && browserLocale !== 1 && browserLocale !== '1') { - // Get preferred language from cookie if present and enabled - // Exclude 1 for backwards compatibility and fallback when fallbackLocale is empty - } else if (process.client && typeof navigator !== 'undefined' && navigator.language) { - // Get browser language either from navigator if running on client side, or from the headers - browserLocale = navigator.language.toLocaleLowerCase().substring(0, 2) - } else if (req && typeof req.headers['accept-language'] !== 'undefined') { - browserLocale = req.headers['accept-language'].split(',')[0].toLocaleLowerCase().substring(0, 2) - } - if (browserLocale) { - // Handle cookie option to prevent multiple redirections - if (!useCookie || alwaysRedirect || !getLocaleCookie()) { - let redirectToLocale = fallbackLocale - - // Use browserLocale if we support it, otherwise use fallbackLocale - if (locales.includes(browserLocale)) { - redirectToLocale = browserLocale - } + if (detectBrowserLanguage && await app.i18n.__detectBrowserLanguage(route)) { + return + } - if (redirectToLocale && locales.includes(redirectToLocale)) { - if (redirectToLocale !== app.i18n.locale) { - // We switch the locale before redirect to prevent loops - await app.i18n.setLocale(redirectToLocale) - } else if (useCookie && !getLocaleCookie()) { - app.i18n.setLocaleCookie(redirectToLocale) - } - } + const locale = app.i18n.locale || app.i18n.defaultLocale || null + const getLocaleFromRoute = <%= options.getLocaleFromRoute %> + const routesNameSeparator = '<%= options.routesNameSeparator %>' + const defaultLocaleRouteNameSuffix = '<%= options.defaultLocaleRouteNameSuffix %>' + const locales = getLocaleCodes(<%= JSON.stringify(options.locales) %>) - return - } - } - } + const routeLocale = getLocaleFromRoute(route, routesNameSeparator, defaultLocaleRouteNameSuffix, locales) await app.i18n.setLocale(routeLocale ? routeLocale : locale) } diff --git a/test/browser.test.js b/test/browser.test.js index fc0bcebdf..bf206cfac 100644 --- a/test/browser.test.js +++ b/test/browser.test.js @@ -1,8 +1,20 @@ +import getPort from 'get-port' +import { resolve } from 'path' import { createBrowser } from 'tib' -import { setup, loadConfig, url } from '@nuxtjs/module-test-utils' +import { generate, setup, loadConfig, url } from '@nuxtjs/module-test-utils' const browserString = process.env.BROWSER_STRING || 'puppeteer/core' +const createNavigator = page => { + return async path => { + // When returning value resolved by `push`, `chrome/selenium`` crashes with: + // WebDriverError: unknown error: unhandled inspector error: {"code":-32000,"message":"Object reference chain is too long"} + // Chain and return nothing to work around. + await page.runAsyncScript(path => window.$nuxt.$router.push(path).then(() => {}), path) + await new Promise(resolve => setTimeout(resolve, 50)) + } +} + describe(browserString, () => { let nuxt let browser @@ -15,13 +27,7 @@ describe(browserString, () => { staticServer: false, extendPage (page) { return { - async navigate (path) { - // When returning value resolved by `push`, `chrome/selenium`` crashes with: - // WebDriverError: unknown error: unhandled inspector error: {"code":-32000,"message":"Object reference chain is too long"} - // Chain and return nothing to work around. - await page.runAsyncScript(path => window.$nuxt.$router.push(path).then(() => {}), path) - await new Promise(resolve => setTimeout(resolve, 50)) - } + navigate: createNavigator(page) } } }) @@ -53,3 +59,51 @@ describe(browserString, () => { expect(await page.getText('body')).toContain('page: À propos') }) }) + +describe(`${browserString} (generate)`, () => { + let browser + let page + let port + // Local method that overrides imported one. + let url + + beforeAll(async () => { + const distDir = resolve(__dirname, 'fixture', 'basic', '.nuxt-generate') + + await generate(loadConfig(__dirname, 'basic', { generate: { dir: distDir } })) + + port = await getPort() + url = path => `http://localhost:${port}${path}` + + browser = await createBrowser(browserString, { + folder: distDir, + staticServer: { + folder: distDir, + port + }, + extendPage (page) { + return { + navigate: createNavigator(page) + } + } + }) + }) + + afterAll(async () => { + if (browser) { + await browser.close() + } + }) + + // Issue https://github.com/nuxt-community/nuxt-i18n/issues/378 + test('navigate to non-default locale', async () => { + page = await browser.page(url('/')) + expect(await page.getText('body')).toContain('locale: en') + + await page.navigate('/fr') + expect(await page.getText('body')).toContain('locale: fr') + + await page.navigate('/') + expect(await page.getText('body')).toContain('locale: en') + }) +})