Repository: xiCO2k/laravel-vue-i18n (ts)

Issue:
I18n instance appears to load 'en' twice
Hi there - many thanks for your fantastic work on this plugin, it has made making a multilingual site much easier for us.

I am currently working on a bilingual website that will support both English and Arabic, with English being the default (and fallback) locale. The way I currently have it set up seems to be working absolutely fine on first visit to the page, with the English version (as stored in en.json for me) being displayed properly using the strings from the JSON file. It is also possible for me to change languages to the Arabic version (as stored in ar.json) with no problem, and all translates correctly.

However, the issue that arises, is when I try to swap **back** to the English version (via clicking a button which triggers `loadLanguageAsync()`). It seems as though the I18n.loaded Array ends up having two keys with `en` as the lang, and the empty one is the one being returned.

So far, I have been using the same code for the `resolve` function as recommended for Vite in the readme:

```js
...
resolve: async (lang) => {
                   const langs = import.meta.glob("../../lang/*.json");
                   return await langs[`../../lang/${lang}.json`]();
               },
...
```

I have also been using `loadLanguageAsync()` to load the new locale when changing language, as mentioned above.

As a simple test, I adjusted the code for my language switcher dropdown to await the result of `loadLanguageAsync('ar')` and then to execute `console.log(I18n.loaded)`, which returns an Array with three items instead of the two expected:

```js
0: 
  lang: "en"
  messages: {}
  [[Prototype]]: Object
1: 
  lang: "en"
  messages: {(and :count more error): '(and :count more error)', (and :count more errors): '(and :count more errors)', …}
  [[Prototype]]: Object
2: 
  lang: "ar"
  messages: {(and :count more error): '(و :count خطأ إضافي)', (and :count more errors): '(و :count أخطاء إضافية)', …}
  [[Prototype]]: Object
```

It seems to be that first item that is being returned when I change the language back to English with loadLanguageAsync perhaps, as the second item contains all of the strings properly (and so if it were being loaded, would display everything as expected). I wonder if this could be because 'en' is also the fallbackLocale?

This definitely could be an issue somewhere on my side as I am quite new to Vue in the first place, so any pointers would be very welcome! Please let me know if I can give any other details to help.

Many thanks in advance!


Pull request description:
fix: Issue when calling the loadLanguageAsync in parallel.
Fixes #119

Patch:
diff --git a/src/index.ts b/src/index.ts
index 25af773..49317df 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -132,7 +132,7 @@ export class I18n {
   /**
    * Stores the loaded languages.
    */
-  private static loaded: LanguageInterface[] = []
+  public static loaded: LanguageInterface[] = []
 
   // Stores options for the current instance
   private options: OptionsInterface
@@ -317,7 +317,7 @@ export class I18n {
     }
 
     const data: LanguageInterface = { lang, messages }
-    I18n.loaded.push(data)
+    this.addLoadedLang(data)
 
     return this.setLanguage(data)
   }
@@ -327,12 +327,27 @@ export class I18n {
       this.fallbackMessages[key] = value
     }
 
-    I18n.loaded.push({
+    this.addLoadedLang({
       lang: this.options.fallbackLang,
       messages
     })
   }
 
+  /**
+   * Adds to the array of loaded languages.
+   */
+  addLoadedLang(data: LanguageInterface): void {
+    const foundIndex = I18n.loaded.findIndex((item) => item.lang === data.lang)
+
+    if (foundIndex !== -1) {
+      I18n.loaded[foundIndex] = data
+
+      return
+    }
+
+    I18n.loaded.push(data)
+  }
+
   /**
    * Sets the language messages to the activeMessages.
    */


Test patch:
diff --git a/test/translate.test.ts b/test/translate.test.ts
index 3265f42..6e7715c 100644
--- a/test/translate.test.ts
+++ b/test/translate.test.ts
@@ -1,5 +1,5 @@
 import { mount } from '@vue/test-utils'
-import { i18nVue, trans, trans_choice, loadLanguageAsync, reset, getActiveLanguage, isLoaded, wTrans } from '../src'
+import { i18nVue, trans, trans_choice, loadLanguageAsync, reset, getActiveLanguage, isLoaded, wTrans, I18n } from '../src'
 import { reset as resetLoader } from '../src/loader'
 
 beforeEach(() => reset());
@@ -109,6 +109,21 @@ it('loads a lang', async () => {
   expect(wrapper.html()).toBe('<h1>Bem-vindo, Francisco!</h1>')
 })
 
+it('only keep one item for each lang even with multiple calls of the `loadLanguageAsync`', async () => {
+  const wrapper = await global.mountPlugin();
+
+  expect(I18n.loaded.length).toBe(1);
+
+  // Purpose call multiple times to replicate a multiple call.
+  await Promise.all([
+    loadLanguageAsync('en'),
+    loadLanguageAsync('en'),
+  ]);
+
+  expect(I18n.loaded.length).toBe(2);
+
+})
+
 it('returns the active lang', async () => {
   await global.mountPlugin();