Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bundle and then load i18n modules at runtime?

Tags:

webpack

I'm trying to migrate from RequireJS to Webpack and I'm not sure the best way to handle our locale files.

Currently, we generate a separate JS file for each locale. These files contain 7+ module definitions for i18n messages as well as library configurations (such as moment). For example, our da_DK file looks something like:

(function (global, factory) {
   typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('../moment')) :
   typeof define === 'function' && define.amd ? define('moment-locale',['moment'], factory) :
   factory(global.moment)
}(this, function (moment) { 'use strict';


    var da = moment.defineLocale('da', {
        ...
    });

    return da;

}));

define('messages',[],function(){
  var da_messages = {
    ...
  };
  
  return da_messages;
 });

At runtime, we determine the appropriate language file to load with the rest of our app. Our app code has no idea which locale is loaded; any modules that depend on the locale will do require('moment-locale') and require('messages').

I'm wanting to do something similar with Webpack, but I haven't found a good way to accomplish this yet.

I've seen require.context for dynamic requires, but it sounds like that would end up bundling all of the possible locales with my app, which I'd prefer not to do.

I also looked into the DllPlugin, thinking each locale file could be a "dll", but I noticed the dll manifest contains specific module names (ex: node_modules/moment/locale/de-at.js) and I think I'd need that to be more generic so webpack knows when I require('moment-locale'), it needs to look in that dll.

One way I was able to get this to work was by updating my locale bundle generation code so that it creates an entry for each locale that looks like:

module.exports = {
    'messages': require('messages'),
    'moment-locale': require('moment-locale'),
    ...
};

and then in the webpack configuration, I set the library field to a namespace for my app. And then in my app's webpack config, I referenced these modules in the externals. So in other words, when da_DK.js is loaded, it places all the modules on the window under a namespace for the app to reference when it loads. I'd rather not use this approach, but it's the only way I've been able to get this to work so far.

Is there another/better way to accomplish this?

like image 367
Sean Parmelee Avatar asked Apr 11 '16 18:04

Sean Parmelee


1 Answers

There are multiple ways you can go about this. 3 feasible ways are:

  1. Package all locales in your bundle.
  2. Make your bundle lazy load the locale
  3. Make individual bundles for each locale

Package all locales in your bundle

This way you'll trade a higher bundle size for one less http request. If the locales are small this might be the route to take. In all other cases I would advice against it as it'll make the bundle size bigger and will increase load.

Make your bundle lazy load the locale.

To do this, take advantage of Webpacks code splitting functionality. This way you can create a bundle.js, a da_DK.js, a sv_SE.js and so on and use require()/require.ensure to load one of the locale files depending on the logic in bundle.js.

Make individual bundles for each locale

Depending on how you do the locale negotiation the best approach might be to create individual bundles on build time. In other words, creating a bundle.da_DK.js a bundle.sv_SE.js and so on. If you do locale negotiation based on something available before the bundle is loaded (e.g. a /da/ slug in the url, a session setting, and so on) this might be the way to go.

Doing it this way you'll a) Create the smallest possible bundle and b) Will get a performance increase (albeit small) as no translation needs to be done runtime. The i18n plugin for Webpack will help you create individual bundles.

Side note

Moment.js, although it's a really good library, it's not really Webpack friendly. It will load all locales. Take a look at this thread to change that behavior: https://stackoverflow.com/a/25426019/2533681

like image 103
Emil Oberg Avatar answered Oct 19 '22 17:10

Emil Oberg