Previous implementation

In previous implementation of my project I used a mix of Backbone and React and translations were handled by Initialization was done before the first render:

import React from 'react';
import { render } from 'react-dom';
import LibraryRouter from 'routers/LibraryRouter';{
    name: 'Messages',
    path: '/js/bundle/',
    mode: 'map',
    checkAvailableLanguages: true,
    async: true,
    callback: function() {
        render(<LibraryRouter/>, document.getElementById('app-div'));

translation files were kept in /js/bundle folder in .properties files with the default English values in

books-search-text=ISBN / Title / Author
book-retrieve-error=Error on retrieve book: API responded with {0}!

and the actual translation logic was in a common function:

import i18n from 'i18n';

export default {
    localize: function (key) {
        return jQuery.i18n.prop.apply(jQuery.i18n, arguments);

that took the key and the dynamic values used in the translations for that key. An actual example on how I used it is:

localizer.localize('book-retrieve-error', error.response.status);

After I migrated my project completely to React, I wanted to get rid of and jQuery and use something different. The first obvious choice was React Intl since it is one of the most used i18n solutions in React but I did not like it because it looked like that I had to change too much to accommodate it. I wouldn’t mind if the replacement had a completely different way to initialize itself but my expectations for the new library were:

  • to have support for .properties file, I just didn’t want to use a different format.

  • to have a similar way to use load translations so that I had to change only the localize function and everything else kept as it is.

The first library that appeared to have this was i18next and the steps are below.

Use i18next instead of


I had to add two new npm packages:


import React from 'react';
import { render } from 'react-dom';
import LibraryRouter from 'routers/LibraryRouter';
import i18next from 'i18next';
import Fetch from 'i18next-fetch-backend';
import PROP from 'utils/PROP';

        backend: {
            loadPath: lng => {
                const suffix = 'en' !== lng ? `_${lng}` : '';
                return `/js/bundle/Messages${suffix}.properties`
            parse: data => PROP.parse(data)
        lng: "en",
        fallbackLng: "en",
        interpolation: {
            prefix: "{",
            suffix: "}"
        debug: true
    }, () => {
        render(<LibraryRouter/>, document.getElementById('app-div'));
  • backend - I chose to load resources asynchronously because of the possibility to keep the name and the content of files unchanged.
  • loadPath - It can be a string or a function with the language the first parameter. The second parameter is the namespace but since I’m not using it in my project, I omited it. I chose to use the function just to be able to make a difference between English, with the default file, and the rest of the locales that have the locale as the file suffix name like
  • parse - A function that receives the content of the file and returns an object that has the file keys mapped as the object properties and the file translations mapped as the object properties values. Default format for i18next property files is json so I had to write a small utility, PROP.js, that does a similar job as JSON JavaScript object.
export default {
    parse: function (properties) {
        return properties.split('\n')
            .filter(line => '' !== line.trim())
            .map(line => line.split('='))
            .map(tokens => ({
                [tokens[0]]: tokens[1]
            .reduce((properties, property) => ({
            }), {})
  • lng - Language to use.
  • fallbackLng - Language to use if translations in user language are not available.
  • interpolation - Configuration used to diferentiate between regular text and dynamic values placeholders. Since in I used something like {0} I had to override default configurations for prefix and suffix.

Code changes

Translation common logic was updated to:

import i18n from 'i18next';

const Localizer = {

    localize: function (key, ...args) {
        const values = args.reduce((values, value, idx) => ({
            [idx]: value
        }) , {});
        return i18n.t(key, values);


export default Localizer;

Above logic was need to accommodate the migration from a usage like:

localizer.localize('book-retrieve-error', error.response.status);


i18n.t('book-retrieve-error', {0: error.response.status});

as i18next expects.


I liked that the needed changes were small and I could replace with i18next easily without changing too much of existing code. This is how a mature library should look like.