Globalize Web Content: Globalize and Webpack

Adding Webpack to the mix

The second example is more complex and uses Globalize, the CLDR data files, IANA timezone data and Webpack specific tools, including a custom globalize-webpack-plugin.

The first component is the package.json file that will tell NPM what packages and versions to install and what commands to run. If you’re familiar with Webpack the commands in the script commands should look familiar.

{
  "private": true,
  "devDependencies": {
    "cldr-data": ">=25",
    "globalize": "^1.3.0",
    "globalize-webpack-plugin": "0.4.x",
    "html-webpack-plugin": "^1.1.0",
    "iana-tz-data": "^2017.1.0",
    "nopt": "^3.0.3",
    "webpack": "^1.9.0",
    "webpack-dev-server": "^1.9.0"
  },
  "scripts": {
    "start": "webpack-dev-server --config webpack-config.js \
    --hot --progress --colors --inline",
    "build": "webpack --production --config webpack-config.js"
  },
  "cldr-data-urls-filter": "(core|dates|numbers|units)"
}

webpack-config.js build the Webpack side of the equation. It requires the packages needed, configures Webpack and specifies the bunldes to build:

  1. vendor, which holds Globalize Runtime modules and other third-party libraries
  2. i18n precompiled data, which means the minimum yet sufficient set of precompiled i18n data that your application needs (one file for each supported locale)
  3. app, which means your application code. Also, note that all the production code is already minified using UglifyJS.

The configuration uses html-webpack-plugin to populate links and names for our HTML template file (discussed later) and globalize-webpack-plugin to handle generating the Globalize bundles.

In a later section, we’ll add another plugin to this Webpack configuration. You can also add more to it based on your build system requirements.

var webpack = require( "webpack" );
var CommonsChunkPlugin = require( "webpack/lib/optimize/CommonsChunkPlugin" );
var HtmlWebpackPlugin = require( "html-webpack-plugin" );
var GlobalizePlugin = require( "globalize-webpack-plugin" );
var nopt = require( "nopt" );

var options = nopt({
  production: Boolean
});

module.exports = {
  entry: options.production ?  {
    main: "./app/index.js",
    vendor: [
      "globalize",
      "globalize/dist/globalize-runtime/number",
      "globalize/dist/globalize-runtime/currency",
      "globalize/dist/globalize-runtime/date",
      "globalize/dist/globalize-runtime/message",
      "globalize/dist/globalize-runtime/plural",
      "globalize/dist/globalize-runtime/relative-time",
      "globalize/dist/globalize-runtime/unit"
    ]
  } : "./app/index.js",
  debug: !options.production,
  output: {
    path: options.production ? "./dist" : "./tmp",
    publicPath: options.production ? "" : "http://localhost:8080/",
    filename: options.production ? "app.[hash].js" : "app.js"
  },
  resolve: {
    extensions: [ "", ".js" ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      production: options.production,
      template: "./index-template.html"
    }),
    new GlobalizePlugin({
      production: options.production,
      developmentLocale: "en",
      supportedLocales: [ "ar", "de", "en", "es", "pt", "ru", "zh" ],
      messages: "messages/[locale].json",
      output: "i18n/[locale].[hash].js"
    })
  ].concat( options.production ? [
    new webpack.optimize.DedupePlugin(),
    new CommonsChunkPlugin( "vendor", "vendor.[hash].js" ),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ] : [] )
};

index.js runs the globalization tasks that we want the user to see. This is a contrived example meant to exercise the library and show what it can do. That said it gives you an idea of the power of GLobalize and what we can use it for.

This looks like a prime use of ES6 String Literals to make them easier to read and reason through. A lot of it will depend on your target browsers and what your team is comfortable with.

These tasks work on the localization tasks for our application. We’re capable of changing all the localized elements in the page by only changing the locale we are working with. We can also change the language programmatically based on user interaction.

The first two examples work with number formatting, one for numbers (where the locale indicates how to format decimals and number separators) and one for currency.

var Globalize = require( "globalize" );
var startTime = new Date();

// Standalone table.
var numberFormatter = Globalize.numberFormatter({ maximumFractionDigits: 2 });
document.getElementById( "number" ).textContent = numberFormatter( 12345.6789 );

var currencyFormatter = Globalize.currencyFormatter( "USD" );
document.getElementById( "currency" ).textContent = currencyFormatter( 69900 );

Dates are more complicated. The first example formats a date using the default formatting rules for the locale we are using.

The second data example uses an array to indicate the format for the date (datetime) and a full IANA time zone like America/Sao_Paulo or America/Los_Angeles) to indicate the offset to use and the name of the Time Zone that corresponds to the location. It then uses the array we created to display the date.

var dateFormatter = Globalize.dateFormatter({ datetime: "medium" });
document.getElementById( "date" ).textContent = dateFormatter( new Date() );

var dateWithTimeZoneFormatter = Globalize.dateFormatter({
    datetime: "full",
    timeZone: "America/Sao_Paulo"
});

document.getElementById( "date-time-zone" ).textContent =
  dateWithTimeZoneFormatter(new Date());

Another way to parse dates according to locale is to break it into its constituent parts. In this example we break it down into parts and, for illustrative purposes, it makes the month part bold.

var _dateToPartsFormatter =
  Globalize.dateToPartsFormatter({ datetime: "medium" });
var dateToPartsFormatter = function( value ) {
  return _dateToPartsFormatter( value, {
    datetime: "medium"
  }).map(function( part ) {
    switch(part.type) {
      case "month": return "<strong>" + part.value + "</strong>";
      default: return part.value;
    }
  }).reduce(function( memo, value ) {
    return memo + value;
  });
  };
document.getElementById( "date-to-parts" ).innerHTML =
  dateToPartsFormatter( new Date() );

Next, we test relative time formatting (10 minutes ago type) and unit type (60 MPH) type formatting.

var relativeTimeFormatter = Globalize.relativeTimeFormatter( "second" );
document.getElementById( "relative-time" ).textContent =
  relativeTimeFormatter( 0 );

var unitFormatter = Globalize.unitFormatter( "mile/hour", { form: "short" } );
document.getElementById( "unit" ).textContent =
  unitFormatter( 60 );

These examples work with formatMessage for different strings. These will be used and localized in the HTML file described in the next section

// Messages.
document.getElementById( "intro-1" ).textContent =
 Globalize.formatMessage( "intro-1" );
document.getElementById( "number-label" ).textContent =
 Globalize.formatMessage( "number-label" );
document.getElementById( "currency-label" ).textContent =
 Globalize.formatMessage( "currency-label" );
document.getElementById( "date-label" ).textContent =
 Globalize.formatMessage( "date-label" );
document.getElementById( "date-time-zone-label" ).textContent =
 Globalize.formatMessage( "date-time-zone-label" );
document.getElementById( "date-to-parts-label" ).textContent =
 Globalize.formatMessage( "date-to-parts-label" );
document.getElementById( "relative-time-label" ).textContent =
 Globalize.formatMessage( "relative-time-label" );
document.getElementById( "unit-label" ).textContent =
 Globalize.formatMessage( "unit-label" );
document.getElementById( "message-1" ).textContent =
 Globalize.formatMessage( "message-1", {
    currency: currencyFormatter( 69900 ),
    date: dateFormatter( new Date() ),
    number: numberFormatter( 12345.6789 ),
    relativeTime: relativeTimeFormatter( 0 ),
    unit: unitFormatter( 60 )
});

document.getElementById( "message-2" ).textContent =
  Globalize.formatMessage( "message-2", {
      count: 3
});

// Display demo.
document.getElementById( "requirements" ).style.display = "none";
document.getElementById( "demo" ).style.display = "block";

The final bin in the script is a timing function that will update the valies in the timer once every 1000 milliseconds.

// Refresh elapsed time
setInterval(function() {
    var elapsedTime = +( ( startTime - new Date() ) / 1000 ).toFixed( 0 );
    document.getElementById( "date" ).textContent =
    dateFormatter( new Date() );
  document.getElementById( "date-time-zone" ).textContent =
    dateWithTimeZoneFormatter( new Date() );
  document.getElementById( "date-to-parts" ).innerHTML =
    dateToPartsFormatter( new Date() );
  document.getElementById( "relative-time" ).textContent =
    relativeTimeFormatter( elapsedTime );
  document.getElementById( "message-1" ).textContent =
    Globalize.formatMessage( "message-1", {
      currency: currencyFormatter( 69900 ),
      date: dateFormatter( new Date() ),
      number: numberFormatter( 12345.6789 ),
      relativeTime: relativeTimeFormatter( elapsedTime ),
      unit: unitFormatter( 60 )
    });
}, 1000);

The final piece is the HTML documebnt that will hold all the localized messages generated during the build process and also links to the Webpack generated bundles as well.

< !doctype html>
<html>
<head>
  <meta charset="utf-8"/>
  <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
  <title>Globalize App example using Webpack</title>
</head>
<body>
  <h1>Globalize App example using Webpack</h1>
  <div id="requirements">
    <h2>Requirements</h2>
    <ul>
      <li>Read README.md for instructions on how to run the demo.
      </li>
    </ul>
  </div>
  <div id="demo" style="display: none">
    <p id="intro-1">Use Globalize to internationalize your application.</p>
  <table border="1" style="marginBottom: 1em;">
    <tbody>
      <tr>
        <td><span id="number-label">Standalone Number</span></td>
        <td>"<span id="number"></span>"</td>
      </tr>
      <tr>
        <td><span id="currency-label">Standalone Currency</span></td>
        <td>"<span id="currency"></span>"</td>
      </tr>
      <tr>
        <td><span id="date-label">Standalone Date</span></td>
        <td>"<span id="date"></span>"</td>
      </tr>
      <tr>
        <td><span id="date-time-zone-label">Standalone Date
          (in a specific IANA time zone, e.g.,
          America/Los_Angeles)</span></td>
        <td>"<span id="date-time-zone"></span>"</td>
      </tr>
      <tr>
        <td><span id="date-to-parts-label">Standalone Date (note the highlighted month, the markup was added using formatDateToParts)</span></td>
        <td>"<span id="date-to-parts"></span>"</td>
      </tr>
      <tr>
        <td><span id="relative-time-label">Standalone Relative Time</span></td>
        <td>"<span id="relative-time"></span>"</td>
      </tr>
      <tr>
        <td><span id="unit-label">Standalone Unit</span></td>
        <td>"<span id="unit"></span>"</td>
      </tr>
      </tbody>
    </table>
    <p id="message-1">An example of a message using mixed number "{number}", currency "{currency}", date "{date}", relative time "{relativeTime}", and unit "{unit}".
    </p>
    <p id="message-2">
        An example of a message with pluralization support:
        {count, plural,
            one {You have one remaining task}
            other {You have # remaining tasks}
        }.
    </p>
</div>
{%
var hasShownLocaleHelp;
for ( var chunk in o.htmlWebpackPlugin.files.chunks ) {
  if ( /globalize-compiled-data-/.test( chunk ) &&
    chunk !== "globalize-compiled-data-en" ) {
    if ( !hasShownLocaleHelp ) {
      hasShownLocaleHelp = true;
%}
<!--
Load support for the `en` (English) locale.
For displaying the application in a different locale, replace `en` with
whatever other supported locales you want, e.g., `pt` (Portuguese).
For supporting additional locales simultaneously and then having your
application to change it dynamically, load the multiple files here. Then,
use `Globalize.locale( <locale> )` in your application to dynamically set
it.
-->
{%      } %}
<!-- <script src="{%=o.htmlWebpackPlugin.files.chunks[chunk].entry %}"> -->
{%  } else { %}
<script src="{%=o.htmlWebpackPlugin.files.chunks[chunk].entry %}"></script>
{%
    }
}
%}
</body>
</html>

Globalizing web content: Globalize.js

GLobalize is the heavy gun in the i18n world. It’ll automate most of the i18n work and integrate ICU and CLDR into one application.

npm install --save globalize 

or build from the Github development branch

git clone https://github.com/globalizejs/globalize.git
bower install && npm install
grunt

Globalize makes heavy use of the CLDR data set and, in the examples below, the installation process through NPM will take care of installing the CLDR data. To illustrate how this works we’ll look at two examples from the Globalize repository, one running NPM to create text-based responses and one using NPM and WebPack to generate bundles for each of our target languages.

NPM

The first example we’ll look at it is a Node-based app that will output all the results to the console. The package.json file, as with any Node-based project, tells NPM what packages and versions to install.

{
  "name": "globalize-hello-world-node-npm",
  "private": true,
  "dependencies": {
    "cldr-data": "latest",
    "globalize": "^1.3.0",
    "iana-tz-data": ">=2017.0.0"
  },
  "cldr-data-urls-filter": "(core|dates|numbers|units)"
}

main.js has all the code that will load and run Globalize tools. I’ve commented the code to illustrate what it does.

const Globalize = require( "globalize" );

let like;

// Before we can use Globalize, we need to feed it on
// the appropriate I18n content (Unicode CLDR).
Globalize.load(
    require( "cldr-data/main/en/ca-gregorian" ),
    require( "cldr-data/main/en/currencies" ),
    require( "cldr-data/main/en/dateFields" ),
    require( "cldr-data/main/en/numbers" ),
    require( "cldr-data/main/en/timeZoneNames" ),
    require( "cldr-data/main/en/units" ),
    require( "cldr-data/supplemental/currencyData" ),
    require( "cldr-data/supplemental/likelySubtags" ),
    require( "cldr-data/supplemental/metaZones" ),
    require( "cldr-data/supplemental/plurals" ),
    require( "cldr-data/supplemental/timeData" ),
    require( "cldr-data/supplemental/weekData" )
);
// Load messages for our default language
Globalize.loadMessages( require( "./messages/en" ) );
// Load time zone data
Globalize.loadTimeZone( require( "iana-tz-data" ) );

// Set "en" as our default locale.
Globalize.locale( "en" );

// Use Globalize to format dates.
console.log( Globalize.formatDate( new Date(), { datetime: "medium" } ) );

// Use Globalize to format dates in specific time zones.
console.log( Globalize.formatDate( new Date(), {
    datetime: "full",
    timeZone: "America/Sao_Paulo"
}));

// Use Globalize to format dates to parts.
console.log( Globalize.formatDateToParts( new Date(), { datetime: "medium" } ) );

// Use Globalize to format numbers.
console.log( Globalize.formatNumber( 12345.6789 ) );

// Use Globalize to format currencies.
console.log( Globalize.formatCurrency( 69900, "USD" ) );

// Use Globalize to get the plural form of a numeric value.
console.log( Globalize.plural( 12345.6789 ) );

// Use Globalize to format a message with plural inflection.
like = Globalize.messageFormatter( "like" );
console.log( like( 0 ) ); // Be the first to like this
console.log( like( 1 ) ); // You liked this
console.log( like( 2 ) ); // You and someone else liked this
console.log( like( 3 ) ); // You and 2 others liked this

// Use Globalize to format relative time.
console.log( Globalize.formatRelativeTime( -35, "second" ) );

// Use Globalize to format unit.
console.log( Globalize.formatUnit( 60, "mile/hour", { form: "short" } ) );

Run the program from a terminal by running node main.js.

Globalize Web Content: Basic Strategies

Quick and Dirty: JS Template String Literals

Andrea Giamarchi wrote an article: “Easy i18n in 10 lines of JavaScript (PoC)” that provides an idea of how to do translation using template literals. This code has been further developed in a Github Repo.

See the article in my blog and Andrea’s post for more information.

Messages: Gender- and plural-capable messages

The next step is to use Messages. We use the library to separate your code from your text formatting while enabling much more humane expressions. This library will eliminate the following from your UI:

  • There are 1 results.
  • There are 1 result(s).
  • Number of results: 5.

The installation process is just like any other application:

npm --save install messageformat

Once it’s installed we require it like any other in a Node application.

const MessageFormat = require('messageformat');

We then build the message we want to display to our users. In this case, we build a message with three rules:

  • A gender (GENDER) rule with values for male, female and other
  • A Resource (RES) rule with values for no results, (exactly) 1 result and more than one result
  • A Category (rule) with ordinal values for one, two, third and other categories
const msg =
  '{GENDER, select, male{He} female{She} other{They} }' +
  ' found ' +
  '{RES, plural, =0{no results} one{1 result} other{# results} }' +
  ' in the ' +
  '{CAT, selectordinal, one{#st} two{#nd} few{#rd} other{#th} }' +
  ' category.';

The last step is to compile the rules and use it as needed. The compilation makes it possible to use the different combinations of the values defined in our message variables.

Using the compiled message we build using mfunc and the values for the categories that we created when we defined the message. The examples below show how the different combinations of messages.

// Compiles the messages and formats.
const mfunc = new MessageFormat('en').compile(msg);

mfunc({ GENDER: 'male', RES: 1, CAT: 2 })
// 'He found 1 result in the 2nd category.'

mfunc({ GENDER: 'female', RES: 1, CAT: 2 })
// 'She found 1 result in the 2nd category.'

mfunc({ GENDER: 'male', RES: 2, CAT: 1 })
// 'He found 2 results in the 1st category.'

mfunc({ RES: 2, CAT: 2 })
// 'They found 2 results in the 2nd category.'

For more information on how to use the formatting capabilities of Messageformat, check the Format Guide particularly the sections where it gives instructions for what values to use in what situation.

Globalizing Web Content

I’ve always been interested in internationalization (i18n) and localization (l10n) and how they relate to the web. My interest got picked again when I started wondering how much extra work would it be to keep a site or app in multiple languages and how much time and how many additional resources I’d need to get it done.

This goes beyond translating the content; tools like Google Translate make that part easier than it used to be but also the changes and modifications that we need to do to our code to accommodate for the different languages and cultures we want to deploy our application in.

Difference between l10n and i18n

Before we can jump in and talk about localizing web content and the challenges involved we need to understand the difference between localization (l10n for the 10 letters between l and n in the word localization in English) and Internationalization (i18n for the 18 letters between i and n in the word internationalization in English)

Localization

Localization refers to the adaptation of a product, application or document content to meet the language, cultural and other requirements of a specific target market (a locale).

Often thought of as a synonym for translation of the user interface and documentation, localization is often a much more complex issue. It can entail customization related to:

  • Numeric, date and time formats
  • Use of currency
  • Keyboard usage
  • Collation and sorting of content
  • Symbols, icons, and colors
  • Text and graphics containing references to objects, actions or ideas which, in a given culture, may be subject to misinterpretation or viewed as insensitive
  • Varying legal requirements

and potentially other aspects of our applications.

Localization may even require a comprehensive rethinking of logic, visual design, or presentation if the way of doing business (eg., accounting) or the accepted paradigm for learning (eg., focus on individual vs. group) in a given locale differs substantially from the originating culture.

Internationalization

Definitions of internationalization vary. This is a high-level working definition for use with W3C Internationalization Activity material. Some people use other terms, such as globalization to refer to the same concept.

Internationalization is the design and development of a product, application or document content that enables easy localization for target audiences that vary in culture, region, or language.

Internationalization typically entails:

  1. Designing and developing in a way that removes barriers to localization or international deployment. This includes activities like enabling the use of Unicode, or ensuring the proper handling of legacy character encodings (where appropriate), taking care over the concatenation of strings, decoupling the backend code from the UI text, etc
  2. Providing support for features that may not be used until localization occurs. For example, adding markup or CSS code to support bidirectional text, or for identifying the language, or adding to CSS support for vertical text or other non-Latin typographic features.
  3. Enabling code to support local, regional, language, or culturally related preferences. Typically this involves incorporating predefined localization data and features derived from existing libraries or user preferences. Examples include date and time formats, local calendars, number formats and numeral systems, sorting and presentation of lists, handling of personal names and forms of address, etc.
  4. Separating localizable elements from source code or content, such that localized alternatives can be loaded or selected based on the user’s international preferences as needed.
  5. Notice that these items do not necessarily include the localization of the content, application, or product into another language; they are design and development practices which allow such a migration to take place easily in the future but which may have significant utility even if no localization ever takes place.

What type of internationalization can we automate?

For the most part, we automate UI internationalization and localize only those aspects of the user interface that will change when we change the language we use. Note that this will work in applications and sites that generate HTML from Javascript.

Depending on the tools we use we may be further limited in what we can and cannot localize programmatically, particularly content other than UI.

JS Template Literals

If you’ve worked with Javascript for a while you’ve probably hit the nightmare of string concatenation and how error prone the process is and how hard it is to troubleshoot if you’re not careful.

var sentence = 'This is a very long sentence that we want to '
 + 'concatenate together.'

In ES6/ES2015 there is a new way to create interpolated strings: Template String Literals.

In this post we’ll discuss three areas:

  • How to build template string literals and multi-line string literals
  • Variable interpolation
  • Possible ways to use template string literals for localization

None of these things are new. You’ve always been able to do in Javascript with template literals it’s now easier and more efficient to do so.

Building Template Literals

To build a Template Literal use the backtick (`) character to open and close the string. In essence it’s not different than concatenating strings but it allows you to create the string literals without worrying about whether you interpolated the correct type of quotation mark (' and ") or the + sign when creating multi-line strings.

At it’s simplest, an ES6 Template String Literal is written like this:

let demoString = `Hello World.`
console.log(demoString);

We can also create Template Literal Strings using the same system. Also, note how we’ve been able to add angle brackets to open and close tags and single quotation marks to the longer example.

let longerString = `The second, greyed out example shown here shows the faux subscripts the browser creates by scaling whatever you put in the <code></sub></code> tag; by using the sinf class from Utility OpenType instead, you’ll use the optically correct glyphs included in your web font. In browsers that don’t support the OpenType features, the browser default will still be used.`
console.log(longerString);

Variable interpolation

Where the Template String Literals really show their strengths is when we interpolate variables in the longer string. Going back to our first example we’ll add a variable to show the name of the person we’re greeting:

var userName2 = "carlos"
var greeting = `Hello, ${userName2}!`
console.log(greeting);
// Returns Hello, carlos!

In this example, the interpolation is the ${userName} string that will take the value of the corresponding variable and put its name in placeholder.

We can also work with arrays as the source of interpolation data, something like the example below where we interpolate the values in the userData array:

var userData = {
  "name": "Carlos",
  "home": "Chile",
};

var greeting2 = `Hello ${userData.name}!.

The weather in ${userData.home} is...`;
console.log(greeting2);

Using that last bit of code we can visit an interesting idea. Using String Template Literals when doing Translation.

var userData = {
  "en": {
    "chapter": "Chapter",
    "section": "Section",
  },
  "es": {
    "chapter": "Capítulo",
    "section": "Sección",
  },
};

var chapterHeading = `${userData.en.chapter} 1, ${userData.en.section} 1.`;
console.log(chapterHeading);
// Produces: Chapter 1, Section 1.

var chapterHeadingEs = `${userData.es.chapter} 1, ${userData.es.section} 1.`;
console.log(chapterHeadingEs);
// Produces: Capítulo 1, Sección 1.

Using the code above we can also insert it in our HTML, looking something like this:

<h1>`${chapterHeading}` (English)</h1>

<h1>`${chapterHeadingEs}` (Spanish)</h1>

The challenge is to dynamically set the current language and use the corresponding entry from the language database. I did a naive pass before finding an external solution that works better.

A complete example

It’s tempting to try and reinvent the wheel (and fail miserably like I did) it’s good to go around and see what’s out there.

Andrea Giamarchi’s Easy i18n in 10 lines of JavaScript (PoC) provides a more robust idea of how to do translation using template literals. This code has been further developed in a Github Repo. I will stay with the original idea of the post, and leave it to you if you want to use the library.

The first part of this process is to define how we’ll handle the i18n templates. This will query the database and, based on the language key, we return the string for the matched language.

It will also set up the default language (en) and an empty internationalization database (i18n.db).

function i18n(template) {
  for (var
    info = i18n.db[i18n.locale][template.join('\x01')],
    out = [info.t[0]],
    i = 1, length = info.t.length; i < length; i++
  ) out[i] = arguments[1 + info.v[i - 1]] + info.t[i];
  return out.join('');
}
i18n.locale = 'en';
i18n.db = {};

The next function creates the database for the translation. We’ll use this to populate the database that we’ll feed translations to.

i18n.set = locale => (tCurrent, ...rCurrent) => {
  const key = tCurrent.join('\x01');
  let db = i18n.db[locale] || (i18n.db[locale] = {});
  db[key] = {
    t: tCurrent.slice(),
    v: rCurrent.map((value, i) => i)
  };
  const config = {
    for: other => (tOther, ...rOther) => {
      db = i18n.db[other] || (i18n.db[other] = {});
      db[key] = {
        t: tOther.slice(),
        v: rOther.map((value, i) => rCurrent.indexOf(value))
      };
      return config;
    }
  };
  return config;
};

Andrea provides multiple ways to populate the database. For this example, I will populate it using the set method. The example below set a group of entries using English as the default language and then using .for to identify additional languages and their translation.

i18n.set('en') `Hello ${'name'}`
  .for('de') `Hallo ${'name'}`
  .for('it') `Ciao ${'name'}`
  .for('sp') `Hola ${'name'}`;

TO create a database containing translation information for our books could look like this:

i18n.set('en') `Chapter ${'number'}`
  .for('es') `Capítulo ${'number'}`
  .for('de') `Kapitel ${'number'}`
  .for('fr') `Chapitre ${'number'}`;

We can then use the default language and type the data in English.

// default
i18n`Chapter ${'73'}`;
// "Chapter 73"

We also have the option of switching languages at runtime, continue writing our text in English and see it translated using the database content.

// we switch to German but still write in English
i18n.locale = 'de';
i18n`Chapter ${'73'}`;
// "Kapitel 73"

i18n.locale = 'es';
i18n`Chapter ${'73'}`;
// Capítulo 73

This code presents a basic engine that will cover most of our needs if we’re willing to do the data entry ourselves or use the libraries and utilities Andreas present in Github.

This project is not meant to replace libraries like Globalize, ICU, Unicode CLDR.