vue-i18n lazy loading is loading all locales

Viewed 2855

I'm trying to implement lazy loading for vue-i18n, I'm following instructions from https://kazupon.github.io/vue-i18n/guide/lazy-loading.html.

But the import is not behaving the way I expect it. I would expect it to load the files only when called, but when the page is refreshed, it will download all files that match the pattern "@i18n/messages/*.js" even if the import is not executed. The fact that the line is present in the code is enough for all bundles to be loaded.

import(/* webpackChunkName: "lang-[request]" */ `@/i18n/messages/${lang}.js`)

What am I missing?

I'm using using vue-cli.

2 Answers

I was not able to instruct webpack to package each file individually using the syntax from the documentation, but found the following workaround which is not ideal but works for me.

I'm replacing the loadLanguageAsync(lang) method from the documentation:

export function loadLanguageAsync(lang) {
    // If the same language
    if (i18n.locale === lang) {
        return Promise.resolve(setI18nLanguage(lang))
    }

    // If the language was already loaded
    if (loadedLanguages.includes(lang)) {
        return Promise.resolve(setI18nLanguage(lang))
    }

    // If the language hasn't been loaded yet
    return import(/* webpackChunkName: "lang-[request]" */ `@/i18n/messages/${lang}.js`).then(
        messages => {
        i18n.setLocaleMessage(lang, messages.default)
        loadedLanguages.push(lang)
        return setI18nLanguage(lang)
    }
)

With this one:

export async function loadLanguageAsync(lang) {
    // If the same language
    if (i18n.locale === lang) {
        return Promise.resolve(setI18nLanguage(lang))
    }

    // If the language was already loaded
    if (loadedLanguages.includes(lang)) {
        return Promise.resolve(setI18nLanguage(lang))
    }

    // If the language hasn't been loaded yet
    let messages = null;
    switch (lang) {
        case 'en':
            messages = await import(/* webpackChunkName: "i18n-en" */ `@/i18n/messages/en.js`)
            break;
        case 'fr':
            messages = await import(/* webpackChunkName: "i18n-fr" */ `@/i18n/messages/fr.js`)
            break;
        case 'de':
            messages = await import(/* webpackChunkName: "i18n-de" */ `@/i18n/messages/de.js`)
            break;
    }
    if (messages != null) {
        i18n.setLocaleMessage(lang, messages.default)
        loadedLanguages.push(lang)
        return setI18nLanguage(lang)
    }
}

As this has been bugging me for a while and I've now just solved it here is my lazy loading solution (using vue and typescript). Went from 4.5 - 7 mb to 2.6 mb. I lazy load and don't prefetch the languages.

vue.config.js

   ...
   chainWebpack: config => {
    // remove the prefetch plugin
    // config.plugins.delete("prefetch");

    // or:
    // modify its options:
    config.plugin("prefetch").tap(options => {
      options[0].fileBlacklist = options[0].fileBlacklist || [];
      options[0].fileBlacklist.push(/lang(.)+?\.js$/);
      options[0].fileBlacklist.push(/lang(.)+?\.js.map$/);
      return options;
    });
  },
  ...

update

if using vue cli pages this is a more dynamic prefetch selector

vue.config.js

   ...
   chainWebpack: config => {
    // remove the prefetch plugin
    // config.plugins.delete("prefetch");

    // or:
    // modify its options:
    if (config && config.plugins && config.plugins.store) {
      const pluginKeys = Array.from(config.plugins.store.keys());

      const prefetchKeys = pluginKeys.filter(x => x.indexOf("prefetch") != -1);

      prefetchKeys.forEach(key => {
        if (config.plugins.has(key)) {
           config.plugin(key).tap(options => {
              options[0].fileBlacklist = options[0].fileBlacklist || [];
              options[0].fileBlacklist.push(/lang(.)+?\.js$/);
              options[0].fileBlacklist.push(/lang(.)+?\.js.map$/);
              return options;
           });
        }
      }
    }
  },
  ...

i18n.ts

import Vue from "vue";
import VueI18n from "vue-i18n";
import { LanguageList } from "@/types/languages";

import messages from "@/locales/en_US.json";

Vue.use(VueI18n);

export const i18n = new VueI18n({
  locale:
    process.env.VUE_APP_I18N_LOCALE ||
    "en_US",
  silentTranslationWarn: true,
  fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en_US",
  messages: { en_US: messages }
});

const loadedLanguages = ["en_US"]; // our default language that is preloaded

export function setI18nLanguage(lang: string, load = true) {
  if (load) {
    /* eslint-disable @typescript-eslint/no-use-before-define */
    loadLanguageAsync(lang).then(() => {
      return lang;
    });
  } else {
    i18n.locale = lang;
    // axios.defaults.headers.common['Accept-Language'] = lang
    (document.querySelector("html") as HTMLElement).setAttribute("lang", lang);
    return lang;
  }
}

export function loadLanguageAsync(lang: string) {
  if (
    i18n.locale !== lang ||
    (!loadedLanguages.includes(lang) && LanguageList.find(l => l.code == lang))
  ) {
    if (!loadedLanguages.includes(lang)) {
      return import(
        /* webpackChunkName: "lang-[request]" */ `@/locales/${lang}`
      ).then(msgs => {
        debugger;
        console.log(msgs.default);
        i18n.setLocaleMessage(lang, msgs);
        loadedLanguages.push(lang);
        return setI18nLanguage(lang);
      });
    }
    return Promise.resolve(setI18nLanguage(lang, false));
  }
  return Promise.resolve(setI18nLanguage(lang, false));
}

App.vue

<script>
import {setI18nLanguage } from "@/i18n";
...
beforeMount() {
   setI18nLanguage(i18n.locale);
}
...
</script>

main.ts

...
import { i18n } from "@/i18n";
...

new Vue({
  router,
  store,
  i18n,
  render: h => h(App)
}).$mount("#app");

languages.ts

export interface LanguageI {
  code: string;
  language: string;
  english: string;
}

export const LanguageList: LanguageI[] = [
  { code: "en_US", language: "English", english: "English" },
//...
];

Related