Conditional Substitution

Let's internationalize our Hello World component example.

The previous conditional package map doesn't scale well to dealing with many different possible conditional outcomes as with a language set. For this, we can use conditional substitution.

First create some language variations:

  • src/en/text.js: export default 'Hello World';
  • src/de/text.js: export default 'Hallo Welt';
  • src/es/text.js: export default 'Hola Mundo';
  • src/zh/text.js: export default '你好,世界';

Then in the component, we can load the correct variation using conditional substitution with:

import React from 'react';
import text from './#{lang}/text.js';
export class HelloWorld extends React.Component {
  render() {
    return <h1>{text}</h1>;
  }
}

This conditional syntax #{...} is entirely a SystemJS feature and construct.

lang is simply a module reference itself equivalent to import lang from 'lang';. That is, it is not provided for us at all, so we need to ensure we create this correct language detection module for our environment.

For this example, let's just select the language randomly by creating this conditional environment module in src/lang.js as:

export default ['en', 'de', 'es', 'zh'][Math.floor(Math.random() * 3)];

We can then provide the map alias for lang to this language condition module in the main map config inside jspm.config.js:

SystemJS.config({
  ...

  map: {
    ...
    "lang": "jspm-react-component/lang.js",
    ...
  },

  ...
});

At risk of spelling out the obvious, this relative map informs the package that any load to lang within the package should use the file ./lang.js relative to the package itself. Since the conditional substitution is treated just like a require within the package, it will respect these mappings.

Loading the test.html page will no display a different language variation on each page load.

To create language bundles, we can just bundle our app normally:

jspm bundle test.js - react

SystemJS builder will by default include all substitution variations within the tree giving the output:

    Building the bundle tree for test.js - react...

       github:capaj/systemjs-hot-reloader@0.5.3.json
       jspm-react-component/component.js
       jspm-react-component/de/text.js
       jspm-react-component/en/text.js
       jspm-react-component/es/text.js
       jspm-react-component/lang.js
       jspm-react-component/zh/text.js
       npm:react-dom@0.14.6.json
       npm:react-dom@0.14.6/index.js
       npm:systemjs-plugin-babel@0.0.2.json
       npm:systemjs-plugin-babel@0.0.2/babel-helpers/classCallCheck.js
       npm:systemjs-plugin-babel@0.0.2/babel-helpers/createClass.js
       npm:systemjs-plugin-babel@0.0.2/babel-helpers/inherits.js
       npm:systemjs-plugin-babel@0.0.2/babel-helpers/possibleConstructorReturn.js
       test.js

ok   Built into build.js with source maps, unminified.

The conditional substitution variations are detected by globbing the filesystem for possible resolutions. Substitution values are not allowed to cross package or map boundaries or include a / separator so that this globbed resolution remains well-defined.

We can also create a main bundle for our app with separate language bundles that get loaded conditionally.

First we create the base bundle without any language variations, by passing SystemJS builder conditions environment trace object.

jspm bundle test.js common-bundle.js --conditions "{'src/lang.js':false}"

We include this common-bundle.js directly in the test.html page:

<!docype html>
<meta charset="utf-8">
<script src="jspm_packages/system.js"></script>
<script src="jspm.browser.js"></script>
<script src="jspm.config.js"></script>
<script src="common-bundle.js"></script>
<body>
  <div id="container"></div>
  <script>
    SystemJS.import('test.js');
  </script>

Next, to build individual language bundles (this process can likely be optimized):

jspm bundle test.js - common-bundle.js bundle-en.js --conditions "{'src/lang.js':'en'}"
jspm bundle test.js - common-bundle.js bundle-de.js --conditions "{'src/lang.js':'de'}"
jspm bundle test.js - common-bundle.js bundle-es.js --conditions "{'src/lang.js':'es'}"
jspm bundle test.js - common-bundle.js bundle-zh.js --conditions "{'src/lang.js':'zh'}"

And to load those language bundles conditionally in the environment, we add a bundles entry to ths jspm.browser.js config:

SystemJS.config({
  baseURL: ".",
  production: true,
  paths: {
    "github:*": "jspm_packages/github/*",
    "npm:*": "jspm_packages/npm/*",
    "jspm-react-component/": "src/"
  },
  bundles: {
    'bundle-#{lang}.js': ['src/*/text.js']
  }
});

Loading the test.html with Chrome devtools closed for performance, the page should then randomly load a fully-built language pack bundle.