Repository: nuxt-community/i18n-module (js)

Issue:
Revert to detecting browser's language after manually setting a locale?
<!--

 **IMPORTANT!**
Please make sure to look for an answer to your question in our documentation before asking a question here.

If relevant to your question, provide nuxt-i18n configuration.

If you have a general question regarding nuxt-i18n use Discord `modules` channel. Thanks!

Documentation: https://i18n.nuxtjs.org/
Nuxt Discord: https://discord.nuxtjs.org/

-->
Hello everyone,

In my application I'd like to have my locale switch have options like this: "English", "French", "Spanish"... and "Auto" which would revert the application to the default i18n state, where it detects the user's browser's locale.

I've tried using `seLocale()` or `setLocale('')` but without luck.

Is it even possible without deleting the cookie and refreshing? Thanks!

Pull request description:
feat: expose API for detecting browser locale
I've exposed a method to get the browser detected locale from nuxt-i18n. This allows me to reset a custom set locale in my application (for instance when logging out). If I used `setLocaleCookie('')` I then needed to refresh the page to apply this "reset" (as intended ofc). So this way I can fetch this method and apply the found locale with `setLocale()`.

fix #1018 

Patch:
diff --git a/docs/content/en/api.md b/docs/content/en/api.md
index 00a80ef7..0c7afdca 100644
--- a/docs/content/en/api.md
+++ b/docs/content/en/api.md
@@ -99,6 +99,14 @@ Instance of [VueI18n class](http://kazupon.github.io/vue-i18n/api/#vuei18n-class
 
   Switches locale of the app to specified locale code. If `useCookie` option is enabled, locale cookie will be updated with new value. If prefixes are enabled (`strategy` other than `no_prefix`), will navigate to new locale's route.
 
+#### getBrowserLocale
+
+  - **Arguments**:
+    - no arguments
+  - **Returns**: `string | undefined`
+
+  Returns browser locale code filtered against the ones defined in options.
+
 ### Properties
 
 #### defaultLocale
@@ -112,7 +120,7 @@ Instance of [VueI18n class](http://kazupon.github.io/vue-i18n/api/#vuei18n-class
   - **Type**: `Array<string | LocaleObject>`
 
   List of locales as defined in options.
-  
+
 #### localeProperties
 
   - **Type**: `LocaleObject`
diff --git a/docs/content/es/api.md b/docs/content/es/api.md
index abfe96b5..30899d1b 100644
--- a/docs/content/es/api.md
+++ b/docs/content/es/api.md
@@ -99,6 +99,14 @@ Instance of [VueI18n class](http://kazupon.github.io/vue-i18n/api/#vuei18n-class
 
   Switches locale of the app to specified locale code. If `useCookie` option is enabled, locale cookie will be updated with new value. If prefixes are enabled (`strategy` other than `no_prefix`), will navigate to new locale's route.
 
+  #### getBrowserLocale
+
+  - **Parámetros**:
+    - sin parámetros
+  - **Devuelve**: `string | undefined`
+
+  Devuelve el código del idioma que utiliza el navegador, filtrado según los códigos definidos en las opciones.
+
 ### Properties
 
 #### defaultLocale
diff --git a/src/templates/plugin.main.js b/src/templates/plugin.main.js
index d75b08bc..feace31b 100644
--- a/src/templates/plugin.main.js
+++ b/src/templates/plugin.main.js
@@ -200,6 +200,17 @@ export default async (context) => {
     return [null, null]
   }
 
+  const getBrowserLocale = () => {
+    if (process.client && typeof navigator !== 'undefined' && navigator.languages) {
+      // Get browser language either from navigator if running on client side, or from the headers
+      return matchBrowserLocale(locales, navigator.languages)
+    } else if (req && typeof req.headers['accept-language'] !== 'undefined') {
+      return matchBrowserLocale(locales, parseAcceptLanguage(req.headers['accept-language']))
+    } else {
+      return undefined
+    }
+  }
+
   const doDetectBrowserLanguage = route => {
     // Browser detection is ignored if it is a nuxt generate.
     if (process.static && process.server) {
@@ -222,11 +233,9 @@ export default async (context) => {
 
     if (useCookie && (matchedLocale = app.i18n.getLocaleCookie())) {
       // Get preferred language from cookie if present and enabled
-    } else if (process.client && typeof navigator !== 'undefined' && navigator.languages) {
-      // Get browser language either from navigator if running on client side, or from the headers
-      matchedLocale = matchBrowserLocale(locales, navigator.languages)
-    } else if (req && typeof req.headers['accept-language'] !== 'undefined') {
-      matchedLocale = matchBrowserLocale(locales, parseAcceptLanguage(req.headers['accept-language']))
+    } else {
+      // Try to get locale from either navigator or header detection
+      matchedLocale = getBrowserLocale()
     }
 
     const finalLocale = matchedLocale || fallbackLocale
@@ -250,6 +259,7 @@ export default async (context) => {
     i18n.setLocaleCookie = locale => setLocaleCookie(locale, res, { useCookie, cookieDomain, cookieKey, cookieSecure, cookieCrossOrigin })
     i18n.getLocaleCookie = () => getLocaleCookie(req, { useCookie, cookieKey, localeCodes })
     i18n.setLocale = (locale) => loadAndSetLocale(locale)
+    i18n.getBrowserLocale = () => getBrowserLocale()
     i18n.__baseUrl = app.i18n.__baseUrl
   }
 
diff --git a/src/templates/utils-common.js b/src/templates/utils-common.js
index f0268892..efcd4cb8 100644
--- a/src/templates/utils-common.js
+++ b/src/templates/utils-common.js
@@ -19,7 +19,7 @@ export const parseAcceptLanguage = input => {
  * Find locale code that best matches provided list of browser locales.
  * @param {(string[]|Object[])} appLocales The user-configured locale codes that are to be matched.
  * @param {string[]} browserLocales The locales to match against configured.
- * @return {string?}
+ * @return {string|undefined}
  */
 export const matchBrowserLocale = (appLocales, browserLocales) => {
   /** @type {{ code: string, score: number }[]} */
@@ -63,7 +63,7 @@ export const matchBrowserLocale = (appLocales, browserLocales) => {
     })
   }
 
-  return matchedLocales.length ? matchedLocales[0].code : null
+  return matchedLocales.length ? matchedLocales[0].code : undefined
 }
 
 /**
diff --git a/types/vue.d.ts b/types/vue.d.ts
index 863f3d40..f091540d 100644
--- a/types/vue.d.ts
+++ b/types/vue.d.ts
@@ -14,6 +14,7 @@ declare module 'vue-i18n' {
     getLocaleCookie() : string | undefined
     setLocaleCookie(locale: string) : undefined
     setLocale(locale: string) : Promise<undefined>
+    getBrowserLocale() : string | undefined
   }
 }
 


Test patch:
diff --git a/test/unit.test.js b/test/unit.test.js
index d9633e81..ef51b45c 100644
--- a/test/unit.test.js
+++ b/test/unit.test.js
@@ -13,7 +13,7 @@ describe('parsePages', () => {
   test('triggers warning with invalid in-component options', async () => {
     const { extractComponentOptions } = await import('../src/helpers/components')
 
-    const spy = jest.spyOn(console, 'warn').mockImplementation(() => {})
+    const spy = jest.spyOn(console, 'warn').mockImplementation(() => { })
     const options = extractComponentOptions(path.join(__dirname, './fixture/typescript/pages/invalidOptions.vue'))
     expect(spy.mock.calls[0][0]).toContain('Error parsing')
     spy.mockRestore()
@@ -64,7 +64,7 @@ describe('matchBrowserLocale', () => {
     const appLocales = ['pl', 'fr']
     const browserLocales = ['en-US', 'en']
 
-    expect(matchBrowserLocale(appLocales, browserLocales)).toBe(null)
+    expect(matchBrowserLocale(appLocales, browserLocales)).toBe(undefined)
   })
 
   test('matches full locale with mixed short and full, full having highest rank', () => {