Home

Awesome

webpack-localize-assets-plugin <a href="https://npm.im/webpack-localize-assets-plugin"><img src="https://badgen.net/npm/v/webpack-localize-assets-plugin"></a> <a href="https://npm.im/webpack-localize-assets-plugin"><img src="https://badgen.net/npm/dm/webpack-localize-assets-plugin"></a> <a href="https://packagephobia.now.sh/result?p=webpack-localize-assets-plugin"><img src="https://packagephobia.now.sh/badge?p=webpack-localize-assets-plugin"></a>

Localize your Webpack bundle with multiple locales.

Features

How does it compare to i18n-webpack-plugin? Answered in the FAQ.

<sub>Support this project by ⭐️ starring and sharing it. Follow me to see what other cool projects I'm working on! ❤️</sub>

🚀 Install

npm i -D webpack-localize-assets-plugin

🚦 Quick setup

In webpack.config.js:

+ const LocalizeAssetsPlugin = require('webpack-localize-assets-plugin')

  const locales = {
      en: { ... },
      es: { ... },
      ja: { ... },
      ...
  }

  module.exports = {
      ...,

      output: {
+         filename: '[name].[locale].js',
          ...
      },

      plugins: [
          ...,
+         new LocalizeAssetsPlugin({
+             locales
+         })
      ]
  }

⚙️ Options

locales

Required

Type:

type Locales = {
    [locale: string]: string | {
        [stringKey: string]: string
    }
}

An object containing all the localization strings.

The key should be the locale name, and the value can either be the path to the locale JSON file or an object mapping the string key to the localized string.

Using a JSON path has the advantage of automatically detecting changes across compilations, which is useful in development.

Example:

new LocalizeAssetsPlugin({
    locales: {
        en: './locales/en.json',
        es: './locales/es.json'
        // ...
    }
    // ...
})

Or:

new LocalizeAssetsPlugin({
    locales: {
        en: {
            helloWorld: 'Hello World!',
            goodbyeWorld: 'Goodbye World!'
            // ...
        },
        es: {
            helloWorld: '¡Hola Mundo!',
            goodbyeWorld: '¡Adiós Mundo!'
            // ...
        }
        // ...
    }
    // ...
})

functionName

Type: string

Default: __

The function name to use to detect localization string keys.

const message = __('helloWorld') // => 'Hello world!'

throwOnMissing

Type: boolean

Default: false

Throw an error if a string key is not found in a locale object.

sourceMapForLocales

Type: string[]

An array of locales that source-maps should be emitted for. Source-maps are enabled via devtool.

warnOnUnusedString

Type: boolean

Default: false

Enable to see warnings when unused string keys are found.

localizeCompiler

Type:

type LocalizeCompiler = {
    // localizer function name (eg. __)
    [functionName: string]: (
        this: LocalizeCompilerContext,
        localizerArguments: string[],
        localeName: string,
    ) => string
}

Default:

const localizeCompiler = {
    __(localizerArguments) {
        const [key] = localizerArguments
        const keyResolved = this.resolveKey()
        return keyResolved ? JSON.stringify(keyResolved) : key
    }
}

An object of functions to generate a JS string to replace the __() call with. The object key is the localize function name, and its function gets called for each localize function call (eg. __(...)) for each locale. This allows you to have multiple localization functions, with separate compilation logic for each of them.

Note, you cannot use both functionName and localizeCompiler. Simply set the function name as a key in the localizeCompiler object instead.

localizerArguments

An array of strings containing JavaScript expressions. The expressions are stringified arguments of the original call. So localizerArguments[0] will be a JavaScript expression containing the translation key.

localeName

The name of the current locale

this context

NameTypeDescription
resolveKey(key?: string) => stringA function to get the localized data given a key. Defaults to the key passed in.
emitWarning(message: string) => voidCall this function to emit a warning into the Webpack build.
emitError(message: string) => voidCall this function to emit an error into the Webpack build.
callNodeCallExpressionAST node representing the original call to the localization function (eg. __()).

localizeCompiler must return a string containing a JavaScript expression. The expression will be injected into the bundle in the place of the original __() call. The expression should represent the localized string.

You can use localizeCompiler to do inject more localization logic (eg. pluralization).

💁‍♀️ FAQ

How does this work and how is it so fast?

This plugin has two modes: Single-locale and Multi-locale.

In Single-locale mode, it works just like i18n-webpack-plugin. It replaces the localization calls with localized strings during Webpack's module parsing stage. Since there is only one locale, localization only needs to be done once at the earliest possible stage.

In Multi-locale mode, it inserts placeholders instead of the localized strings at the module parsing stage. After minification, all assets are duplicated for each locale and the placeholders are replaced with the localized strings via find-and-replace.

The speed gains come from:

How does this compare to i18n-webpack-plugin?

First of all, thank you to i18n-webpack-plugin for the original idea and implementation and serving the community.

webpack-localize-assets-plugin vs i18n-webpack-plugin:

How does this approach compare to run-time localization?

There are two approaches to localization:

Here is a comparison:

<table> <thead> <tr> <th></th> <th>Run-time</th> <th>Build-time</th> </tr> </thead> <tbody> <tr valign="top"> <th>Output size</th> <td> <strong>Small.</strong> <br> Application code is agnostic to locale so it only needs to be produced once. Locale data files are produced for each locale to be loaded by application at run-time. </td> <td> <strong>Large.</strong> <br> The entire build is multiplied for every locale. The impact of this multiplication increases with assets that don't require localization (eg. source maps, vendor chunks). <br><br> Comparing the size of one locale between build-time and run-time, build-time has a slight advantage because there is no "loading overhead" (requesting locale data, long reference keys, etc.). This difference is small but can be negligible after <a href="https://github.com/privatenumber/webpack-json-access-optimizer">good minification</a> & compression. </td> </tr> <tr valign="top"> <th>Build time</th> <td> <strong>Fast.</strong> <br> It's a lot faster because the build only needs to be produced once. Each locale data needs to be produced but there's no processing cost. </td> <td> <strong>Slow.</strong> <br> Build speed gets slower with size; so with the build being multiplied for each locale, it's very slow. Although optimizations (like this plugin) can apply localization post-bundling to re-use bundles across multiple locales, it's still a lot slower because large assets take time to localize and write to disk. This gets much slower when enabling things like source maps. To improve speed, you might enable source maps only for the main locale at the cost of debugging experience in other locales. </td> </tr> <tr valign="top"> <th>Loading time</th> <td> <strong>Fast.</strong> <br> Although initial page-load might need to request at least two assets instead of one (localization data and application), this can still be very fast with minification & compression. Even faster when using <a href="https://stackoverflow.com/a/59310436/911407">HTTP/2 multiplexing</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preload">preloading</a>/<a href="https://javascript.info/script-async-defer">async</a>. <br><br> There's also a notable benefit of fast locale switching. When a user changes their locale, only the new locale asset needs to be loaded because the application code (the larger asset) is already loaded or will have a cache-hit. </td> <td> <strong>Fast.</strong> <br> Since localization is baked-in, there is no need to load an additional asset of just locale strings. <br><br> However, there is a large cost to when users switch locales as the entire app will need to be re-loaded and there will be no cache-hits if it's a new locale. </td> </tr> </tbody> </table>