3

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.

scharette
  • 605
  • 1
  • 9
  • 25

2 Answers2

1

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)
    }
}
scharette
  • 605
  • 1
  • 9
  • 25
  • 2
    Thanks, that does work. FYI it seems to be something to do with the webpackChunkName option, removing it does work for me but then you lose the ability to rename the i18n chunks. not a deal breaker for me but a bit annoying. – tom_h Nov 09 '20 at 13:38
1

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" },
//...
];
zcoop98
  • 2,590
  • 1
  • 18
  • 31
lastlink
  • 1,505
  • 2
  • 19
  • 29
  • This worked wonders, thanks. The only bit I needed to disable the prefetch was adding a chunkName starting with `lang` to my dynamic imports and the line `options[0].fileBlacklist.push(/lang*/);` to blacklist all such chunks. Cheers! – belvederef Jun 03 '21 at 17:22