/tap-i18n

A comprehensive internationalization solution for Meteor.

Primary LanguageJavaScriptMIT LicenseMIT

tap-i18n

Internationalization for Meteor

tap-i18n is a Meteor package that provides a comprehensive i18n solution for Meteor apps and packages, with the goal of standardizing the way package developers internationalize their packages.

Add your projects/packages to our Internationalized with tap:i18n wiki page

Users of tap-i18n v0.9 and below, read tap:i18n v1.0 New Feautres & Backward Compatibility and update your app to v1.0 .

Check tap-i18n-db for Meteor collections internationalization.

Contents

Key Features

All Encompassing

tap-i18n is designed in a way that distinguishes the role of the package developer, that is, making the package available in multiple languages, from the role of the app developer which is to translate the app, but more importantly, to manage the app's internationalization aspects, such as: setting the supported languages for the project, setting the client language, configuring CDNs for language files, and so on.

Readable Syntax

<div class="btn">{{_ "sign_up"}}</div>

Advanced i18n

tap-i18n uses i18next as its internationalization engine and exposes all its capabilities to the Meteor's templates - variables, dialects, count/context aware keys, and more.

client/messages.html

<template name="messages_today">
  <p>{{_ "inbox_status" "Daniel" count=18}}</p>
</template>

i18n/en.i18n.json

{ 
  "inbox_status": "Hey, %s! You have received one new message today.",
  "inbox_status_plural": "Hey, %s! You have received %s new messages today." 
}

See more examples below.

Transparent Namespacing

You don't need to worry about domain prefixing or package conflicts when you translate your project or package. Behind the scenes we automatically generate scoped namesapaces for you.

Ready to Scale

  • Translations are unified into a single JSON file per language that includes both package and project-level translations
  • On-demand: translations are loaded only when they are needed
  • 3rd Party CDN Support

Quickstart

Step 1: Install tap-i18n using meteorite in your project's root directory:

$ meteor add tap:i18n

Step 2: Add translation helpers to your markup:

*.html

<div>{{_ "hello"}}</div>

Step 3: Define translations in JSON format:

i18n/en.i18n.json

{ "hello": "Hey there" }

i18n/fr.i18n.json

{ "hello": "Bonjour" }

Translations files should end with lang_tag.i18n.json.

You can split translations of a certain language to multiple files. We ignore prefixed text. You can have file named: menu.en.i18n.json, we add its translations the same way we add those of en.i18n.json .

Step 4: Initiate the client language on startup

If you want the client to be served by a specific language on startup

Assuming that you have a function getUserLanguage() that returns the language for tag for the current user.

getUserLanguage = function () {
  // Put here the logic for determining the user language

  return "fr";
};

if (Meteor.isClient) {
  Meteor.startup(function () {
    Session.set("showLoadingIndicator", true);

    TAPi18n.setLanguage(getUserLanguage())
      .done(function () {
        Session.set("showLoadingIndicator", false);
      })
      .fail(function (error_message) {
        // Handle the situation
        console.log(error_message);
      });
  });
}
  • If you won't set a language on startup your project will be served in the default language: English
  • You probably want to show a loading indicator until the language is ready (as shown in the example), otherwise the templates in your projects will be in English until the language will be ready

Documentation & Examples

TAPi18n API

TAPi18n.setLanguage(language_tag) (Client)

Sets the client's translation language.

Returns a jQuery deferred object that resolves if the language load succeed and fails otherwise.

Notes:

  • language_tag has to be a supported Language.
  • jQuery deferred docs: jQuery Deferred

TAPi18n.getLanguage() (Client)

Returns the tag of the client's current language or null if tap-i18n is not installed.

If inside a reactive computation, invalidate the computation the next time the client language get changed (by TAPi18n.setLanguage)

TAPi18n.getLanguages() (Anywhere)

Returns an object with all the languages the project or one of the packages it uses are translated to.

The returned object is in the following format:

{
  'en': {
    'name':'English', // Local name
    'en':'English'    // English name
  },
  'zh': {
    'name':'中文'     // Local name
    'en':'Chinese'    // English name
  }
  .
  .
  .
}

TAPi18n.__(key, options, lang_tag) (Server) TAPi18n.__(key, options) (Client)

Translates key to the current client's language. If inside a reactive computation, invalidate the computation the next time the client language get changed (by TAPi18n.setLanguage).

The function is a proxy to the i18next.t() method. Refer to the documentation of i18next.t() to learn about its possible options.

On the server, TAPi18n.__ is not a reactive resource. You have to specify the language tag you want to translate the key to.

The tap-i18n Helpers

The _ Helper

To use tap-i18n to internationalize your templates you can use the _ helper that we set on the project's templates and on packages' templates for packages that uses tap-i18n:

{{_ "key" "sprintf_arg1" "sprintf_arg2" ... op1="option-value" op2="option-value" ... }}

You can customize the helper name, see "Configuring tap-i18n" section.

The translation files that will be used to translate key depends on the template from which it is being used:

  • If the helper is being used in a template that belongs to a package that uses tap-i18n we'll always look for the translation in that package's translation files.
  • If the helper is being used in one of the project's templates we'll look for the translation in the project's translation files (tap-i18n has to be installed of course).

Usage Examples:

Assuming the client language is en.

Example 1: Simple key:

en.i18n.json:
-------------
{
    "click": "Click Here"
}

page.html:
----------
<template name="x">
    {{_ "click"}}
</template>

output:
-------
Click Here

Example 2: Sprintf:

en.i18n.json:
-------------
{
    "hello": "Hello %s, your last visit was on: %s"
}

page.html:
----------
<template name="x">
    {{_ "hello" "Daniel" "2014-05-22"}}
</template>

output:
-------
Hello Daniel, your last visit was on: 2014-05-22

Example 3: Named variables and sprintf:

en.i18n.json:
-------------
{
    "hello": "Hello __user_name__, your last visit was on: %s"
}

page.html:
----------
<template name="x">
    {{_ "hello" "2014-05-22" user_name="Daniel"}}
</template>

output:
-------
Hello Daniel, your last visit was on: 2014-05-22

Note: Named variables have to be after all the sprintf parameters.

Example 4: Named variables, sprintf, singular/plural:

en.i18n.json:
-------------
{
    "inbox_status": "__username__, You have a new message (inbox last checked %s)",
    "inbox_status_plural": "__username__, You have __count__ new messages (last checked %s)"
}

page.html:
----------
<template name="x">
    {{_ "inbox_status" "2014-05-22" username="Daniel" count=1}}
    {{_ "inbox_status" "2014-05-22" username="Chris" count=4}}
</template>

output:
-------
Daniel, You have a new message (inbox last checked 2014-05-22)
Chris, You have 4 new messages (last checked 2014-05-22)

Example 5: Singular/plural, context:

en.i18n.json:
-------------
{
    "actors_count": "There is one actor in the movie",
    "actors_count_male": "There is one actor in the movie",
    "actors_count_female": "There is one actress in the movie",
    "actors_count_plural": "There are __count__ actors in the movie",
    "actors_count_male_plural": "There are __count__ actors in the movie",
    "actors_count_female_plural": "There are __count__ actresses in the movie",
}

page.html:
----------
<template name="x">
    {{_ "actors_count" count=1 }}
    {{_ "actors_count" count=1 context="male" }}
    {{_ "actors_count" count=1 context="female" }}
    {{_ "actors_count" count=2 }}
    {{_ "actors_count" count=2 context="male" }}
    {{_ "actors_count" count=2 context="female" }}
</template>

output:
-------
There is one actor in the movie
There is one actor in the movie
There is one actress in the movie
There are 2 actors in the movie
There are 2 actors in the movie
There are 2 actresses in the movie
  • Refer to the documentation of i18next.t() to learn more about its possible options.
  • The translation will get updated automatically after calls to TAPi18n.setLanguage().

More helpers

{{languageTag}}:

The {{languageTag}} helper calls TAPi18n.getLanguage().

It's useful when you need to load assets depending on the current language, for example:

<template name="example">
  <img src="welcome_{{languageTag}}.png">
</template>

Languages Tags and Translations Prioritization

We use the IETF language tag system for languages tagging. With it developers can refer to a certain language or pick one of its dialects.

Example: A developer can either refer to English in general using: "en" or to use the Great Britain dialect with "en-GB".

If tap-i18n is install we'll attempt to look for a translation of a certain string in the following order:

  • Language dialect, if specified ("pt-BR")
  • Base language ("pt")
  • Base English ("en")

Notes:

  • We currently support only one dialect level. e.g. nan-Hant-TW is not supported.
  • "en-US" is the dialect we use for the base English translations "en".
  • If tap-i18n is not installed, packages will be served in English, the fallback language.

Structure of Languages Files

Languages files should be named: arbitrary.text.lang_tag.i18n.json . e.g., en.i18n.json, menu.pt-BR.i18n.json.

You can have more than one file for the same language.

You can put languages files anywhere in your project tree.

Example for languages files:

en.i18n.json
{
    "sky": "Sky",
    "color": "Color"
}

pt.i18n.json
{
    "sky": "Céu",
    "color": "Cor"
}

fr.i18n.json
{
    "sky": "Ciel"
}

en-GB.i18n.json
{
    "color": "Colour"
}
  • To avoid translation bugs all the keys in your package must be translated to English ("en") which is the default language, and the fallback language when tap-i18n is not installed or when it can't find a translation for a certain key.
  • In the above example there is no need to translate "sky" in en-GB which is the same in en. Remember that thanks to the Languages Tags and Translations Prioritization (see above) if a translation for a certain key is the same for a language and one of its dialects you don't need to translate it again in the dialect file.
  • The French file above have no translation for the color key above, it will fallback to English.
  • Check i18next features documentation for more advanced translations structures you can use in your JSONs files (Such as variables, plural form, etc.).

Configuring tap-i18n

To configure tap-i18n add to it a file named project-tap.i18n.

This JSON can have the following properties. All of them are optional. The values bellow are the defaults.

project-root/project-tap.i18n
-----------------------------
{
    "helper_name": "_",
    "supported_languages": null,
    "i18n_files_route": "/tap-i18n",
    "cdn_path": null
}

Options:

helper_name: the name for the templates' translation helper.

supported_languages: A list of languages tags you want to make available on your project. If null, all the languages we'll find translation files for, in the project, will be available.

build_files_path: Can be an absolute path or relative to the project's root. If you change this value we assume you want to serve the files yourself (via cdn, or by other means) so we won't initiate the tap-i18n's built-in files server. Therefore if you set build_files_path you must set the browser_path.

i18n_files_route: The route in which the tap-i18n resources will be available in the project.

cdn_path: An alternative path from which you want tap-i18n resources to be loaded. Example: "http://cdn.example.com/tap-i18n".

Notes:

  • We use AJAX to load the languages files so you'll have to set CORS on your CDN.

Disabling tap-i18n

Step 1: Remove tap-i18n method calls from your project.

Step 2: Remove tap-i18n package

$ meteor remove tap:i18n

Developing Packages

Though the decision to translate a package and to internationalize it is a decision made by the package developer, the control over the internationalization configurations are done by the project developer and are global to all the packages within the project.

Therefore if you wish to use tap-i18n to internationalize your Meteor package your docs will have to refer projects developers that will use it to the "Usage - Project Developers" section above to enable internationalization. If the project developer won't enable tap-i18n your package will be served in the fallback language English.

tap-i18n Two Work Modes

tap-i18n can be used to internationalize projects and packages, but its behavior is determined by whether or not it's installed on the project level. We call these two work modes: enabled and disabled.

When tap-i18n is disabled we don't unify the languages files that the packages being used by the project uses, and serve all the packages in the fallback language (English)

Setup tap-i18n

In order to use tap-i18n to internationalize your package:

Step 1: Add the package-tap.i18n configuration file:

You can use empty file or an empty JSON object if you don't need to change them.

The values below are the defaults.

package_dir/package-tap.i18n
----------------------------
{
    "translation_function_name": "__", // The name for the translation function that
                                       // will be available in package's namespace.
    "helper_name": "_" // the name for the package templates' translation helper
}

Step 2: Create your languages_files_dir:

Example for the default languages_files_dir path and its structure:

.
|--package_name
|----package.js
|----package-tap.i18n
|----i18n # Should be the same path as languages_files_dir option above
|------en.i18n.json
|------fr.i18n.json
|------pt.i18n.json
|------pt-BR.i18n.json
.
.
.

Step 3: Setup your package.js:

Your package's package.js should be structured as follow:

Package.on_use(function (api) {
  api.use(["tap:i18n@1.0.0"], ["client", "server"]);

  .
  .
  .

  // You must load your package's package-tap.i18n before you load any
  // template
  api.add_files("package-tap.i18n", ["client", "server"]);

  // Templates loads (if any)

  // List your languages files so Meteor will watch them and rebuild your
  // package as they change.
  // You must load the languages files after you load your templates -
  // otherwise the templates won't have the i18n capabilities (unless
  // you'll register them with tap-i18n yourself, see below).
  api.add_files([
    "i18n/en.i18n.json",
    "i18n/fr.i18n.json",
    "i18n/pt.i18n.json",
    "i18n/pt-br.i18n.json"
  ], ["client", "server"]);
});

Note: en, which is the fallback language, is the only language we integrate into the clients bundle. All the other languages files will be loaded only to the server bundle and will be served as part of the unified languages files, that contain all the project's translations.

Package Level tap-i18n Functions

The following functions are added to your package namespace by tap-i18n:

__("key", options, lang_tag) (Server) __("key", options) (Client)

Translates key to the current client's language. If inside a reactive computation, invalidate the computation the next time the client language get changed (by TAPi18n.setLanguage).

The function is a proxy to the i18next.t() method. Refer to the documentation of i18next.t() to learn about its possible options.

On the server, TAPi18n.__ is not a reactive resource. You have to specify the language tag you want to translate the key to.

You can use package-tap.i18n to change the name of this function.

registerI18nHelper(template_name) (Client) registerTemplate(template_name) (Client) [obsolete alias, will be removed in future versions]

Register the _ helper that maps to the __ function for the template with the given name.

Important: As long as you load the package templates after you add package-tap.i18n and before you start adding the languages files you won't need to register templates yourself.

Using tap-i18n in Your Package Templates

See "The tap-i18n helper" section above.

Unit Testing

See /unittest/test-packages/README.md .

License

MIT

Author

Daniel Chcouri

Contributors

Chris Hitchcott

Credits