Internationalization

Internationalization is a common task for many projects. And even if we don’t need to support multiple languages, it still can be useful to store all user messages together to have better perspective and avoid duplication.

CUBA provides the CubaLocalizeBehavior that can assist you in this task. Basically, it just introduces the msg() method that gets messages from the messages property depending on the current locale. See an example below.

index.html
<html>
<head>
	<link rel="import" href="src/recipes/i18n/simple-greeting-component.html">
	<script src="bower_components/webcomponentsjs/webcomponents-loader.js"></script>
</head>
<body>
    <simple-greeting-component></simple-greeting-component>
</body>
</html>
src/recipes/i18n/simple-greeting-component.html
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../../bower_components/polymer/lib/elements/dom-if.html">
<link rel="import" href="../../../bower_components/cuba-app/cuba-app.html">
<link rel="import" href="../../../bower_components/cuba-app/cuba-app-aware-behavior.html">
<link rel="import" href="../../../bower_components/cuba-app/cuba-localize-behavior.html">

<dom-module id="simple-greeting-component">
  <template>

    <cuba-app api-url="/app/rest/"></cuba-app>

    <template is="dom-if" if="[[appReady]]">
      <h3>[[msg('greeting.caption')]]</h3>
      <p>[[msg('greeting.text')]]</p>
    </template>

  </template>
  <script>
    class SimpleGreetingComponent
      extends Polymer.mixinBehaviors([CubaLocalizeBehavior, CubaAppAwareBehavior], Polymer.Element) {

      static get is() {
        return 'simple-greeting-component';
      }

      static get observers() {
        return [
          '_init(app)'
        ];
      }

      static get properties() {
        return {
          appReady: Boolean,
          messages: {
            type: Array,
            value: function() {
              return {
                en: {
                  'greeting.caption': 'Hello, Username!',
                  'greeting.text': 'Welcome to the exiting and wonderful world of the internationalization!!!'
                },
                ru: {
                  'greeting.caption': 'Здравствуй, Анонимус!',
                  'greeting.text': 'Добро пожаловать в увлекательный и захватыващий мир приложений на нескольких языках!!!'
                }
              };
            }
          }
        }
      }

      _init() {
        this.set('appReady', true);
      }

    }

    customElements.define(SimpleGreetingComponent.is, SimpleGreetingComponent);
  </script>
</dom-module>

Result

CubaLocalizeBehavior requires an initialized cuba-app.

How the default locale is determined is up to you: you can set a locale on component initialization or provide some kind of language switcher that changes the locale. Below is an example of switching locale.

index.html
<html>
<head>
	<link rel="import" href="src/recipes/i18n/locale-switcher.html">
	<script src="bower_components/webcomponentsjs/webcomponents-loader.js"></script>
</head>
<body>
    <locale-switcher></locale-switcher>
</body>
</html>
src/recipes/i18n/locale-switcher.html
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../../bower_components/polymer/lib/elements/dom-if.html">
<link rel="import" href="../../../bower_components/cuba-app/cuba-app.html">
<link rel="import" href="../../../bower_components/cuba-app/cuba-app-aware-behavior.html">
<link rel="import" href="../../../bower_components/cuba-app/cuba-localize-behavior.html">

<link rel="import" href="../../../bower_components/paper-radio-group/paper-radio-group.html">
<link rel="import" href="../../../bower_components/paper-radio-button/paper-radio-button.html">

<dom-module id="locale-switcher">
  <template>

    <cuba-app api-url="/app/rest/"></cuba-app>

    <template is="dom-if" if="[[appReady]]">
      <p>[[msg('languageDescriptor')]]</p>

      <paper-radio-group selected="{{app.locale}}">
        <paper-radio-button name="en">[[msg('enLanguageName')]]</paper-radio-button>
        <paper-radio-button name="ru">[[msg('ruLanguageName')]]</paper-radio-button>
      </paper-radio-group>

    </template>

  </template>
  <script>
    class LocaleSwitcher
      extends Polymer.mixinBehaviors([CubaLocalizeBehavior, CubaAppAwareBehavior], Polymer.Element) {

      static get is() {
        return 'locale-switcher';
      }

      static get observers() {
        return [
          '_init(app)'
        ];
      }

      static get properties() {
        return {
          appReady: Boolean,
          messages: {
            type: Array,
            value: function() {
              return {
                en: {
                  'languageDescriptor': 'This text is written in English.',
                  'enLanguageName': 'English',
                  'ruLanguageName': 'Russian'
                },
                ru: {
                  'languageDescriptor': 'Вы видете текст на русском языке.',
                  'enLanguageName': 'Английский',
                  'ruLanguageName': 'Русский'
                }
              };
            }
          }
        }
      }

      _init() {
        this.set('appReady', true);
      }
    }

    customElements.define(LocaleSwitcher.is, LocaleSwitcher);
  </script>
</dom-module>

Result

Still it would be more convenient to have a single place where all messages are stored. It can be easily achieved by creating a proxy between CubaLocalizeBehavior and the rest of your application.

index.html
<html>
<head>
	<link rel="import" href="src/recipes/i18n/calcium-adv.html">
	<script src="bower_components/webcomponentsjs/webcomponents-loader.js"></script>
</head>
<body>
    <calcium-adv></calcium-adv>
</body>
</html>
src/recipes/i18n/calcium-adv.html
<link rel="import" href="../../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../../bower_components/polymer/lib/elements/dom-if.html">
<link rel="import" href="../../../bower_components/cuba-app/cuba-app.html">
<link rel="import" href="../../../bower_components/cuba-app/cuba-app-aware-behavior.html">

<link rel="import" href="../../../bower_components/paper-radio-group/paper-radio-group.html">
<link rel="import" href="../../../bower_components/paper-radio-button/paper-radio-button.html">

<link rel="import" href="i18n-mixin.html">

<dom-module id="calcium-adv">
  <template>

    <cuba-app api-url="/app/rest/"></cuba-app>

    <template is="dom-if" if="[[appReady]]">
      <h3>[[msg('calciumAdv.caption')]]</h3>

      <p>[[msg('calciumAdv.body')]]</p>

      <paper-radio-group selected="{{app.locale}}">
        <paper-radio-button name="en">[[msg('languages.en')]]</paper-radio-button>
        <paper-radio-button name="ru">[[msg('languages.ru')]]</paper-radio-button>
      </paper-radio-group>

    </template>

  </template>
  <script>
    class CalciumAdvertisement
      extends Polymer.mixinBehaviors([CubaAppAwareBehavior], I18nMixin(Polymer.Element)) {

      static get is() {
        return 'calcium-adv';
      }

      static get observers() {
        return [
          '_init(app)'
        ];
      }

      static get properties() {
        return {
          appReady: Boolean
        }
      }

      _init() {
        this.set('appReady', true);
      }
    }

    customElements.define(CalciumAdvertisement.is, CalciumAdvertisement);
  </script>
</dom-module>
src/recipes/i18n/i18n-mixin.html
<link rel="import" href="../../../bower_components/cuba-app/cuba-localize-behavior.html">

<script>

  I18nMixin = function(superClass) {
    return class extends Polymer.mixinBehaviors([CubaLocalizeBehavior], superClass) {
      static get properties() {
        return {
          messages: {
            type: Array,
            value: function() {
              return {
                en: {
                  'calciumAdv.caption': 'Calcium biological role',
                  'calciumAdv.body': 'Calcium is an essential element needed in large quantities. ' +
                    'The Ca2+ ion acts as an electrolyte and is vital to the health of the muscular, circulatory, ' +
                    'and digestive systems; is indispensable to the building of bone; ' +
                    'and supports synthesis and function of blood cells. For example, ' +
                    'it regulates the contraction of muscles, nerve conduction, and the clotting of blood.',

                  'languages.en': 'English',
                  'languages.ru': 'Russian'
                },
                ru: {
                  'calciumAdv.caption': 'Биологическое значение кальция',
                  'calciumAdv.body': 'Кальций - это важный элемент, необходимый в больших количествах. ' +
                  'Ионы Ca2+ работают, как электролиты, и представляют важность для здоровья системы кровообращения, ' +
                  'мышечной и пищеварительной систем. Кальций незаменим для строительства костей и способствует ' +
                  'синтезу и функционированию клеток крови. Например, кальций, регулирует мышечное сокращение, свертывание крови ' +
                  'и проводимость нейронов.',

                  'languages.en': 'Английский',
                  'languages.ru': 'Русский'
                }
              };
            }
          }
        };
      }
    }
  };

</script>

Result

As you can see, now the components just implement I18nMixin and don’t contain any actual messages.