A GNU gettext based localization workflow for ember.
ember install ember-l10n
Using the string extractor requires:
- GNU gettext, xgettext
- OS X:
brew install gettext; brew link --force gettext
- xgettext-template - extracts strings from handlebars templates
- gettext.js - lightweight port of gettext for JS
ember-l10n uses ember-ajax to fetch locale data.
There are two primary parts to ember-l10n
- Ember side: Service, Helpers, and Components
- CLI: Contains a string extractor for
JS
andHBS
files generatingPOT
/PO
files, a converter to convertPO
files toJSON
as well as a synchronizer for exchanging message ids inJS
andHBS
files (f.e. after proof read original version).
ember-l10n follows the gettext convention that the message ids in your source files are the default language (usually English).
In the ember-l10n workflow, you use the t
, and n
helpers and l10n.t()
/ l10n.n()
functions to define your strings in your project. Then you run the extractor script
to generate pot and po files, which you send off to your translators.
After receiving the translated po files for additional locales, you use the same script to convert them into json files. These json files are then loaded by ember-l10n in your application and replaced at runtime.
ember-l10n provides powerful string substitution and even component substitution for dynamic strings. See the Components section below.
The latest version has introduced fingerprinting for the generated JSON files. Please read the Fingerprinting & Converter sections.
The service translates through gettext.js. There are two available methods to be used for translations message ids from JS source:
t(msgid, hash)
n(msgid, msgidPlural, count, hash)
tVar(msgid, hash)
tVar()
works exactly the same as t()
, but it will be ignored by the
gettext parser. This is useful if your message ids are variables, for example:
l10n.t(myProperty)
would create a myProperty
entry in your po-file
when gettext is run. So in this case, l10n.tVar(myProperty)
should be used
instead.
Furthermore, there's an auto initialization feature (default: true), which
detects user's locale according to system preferences. If the user's locale is
supported in availableLocales
, the corresponding translations are loaded. If
the user's locale is not supported, the default locale will be used instead
(default: "en"). Please use the following method to change locales:
setLocale(locale)
The following utility methods are also available:
getLocale()
hasLocale(locale)
detectLocale()
To configure the path of the JSON files (depending on the path configured via convertor's -o
option) use the jsonPath
property (default: "/assets/locales").
When installing via ember install ember-l10n
, an l10n
service will be created for you under app/services/l10n.js
.
There, you can configure (and overwrite) all service properties/methods:
import computed from 'ember-computed';
import L10n from 'ember-l10n/services/l10n';
import l10nFingerprintMap from './../utils/l10n-fingerprint-map';
export default L10n.extend({
availableLocales: computed(function() {
return {
'en': this.t('en')
};
}),
autoInitialize: true,
jsonPath: '/assets/locales',
// Make this return null if you do not want to use fingerprinting
fingerprintMap: l10nFingerprintMap
});
You can create an initializer to inject the l10n-service everywhere with the following blueprint:
ember g ember-l10n-initializer my-l10n-initializer
This will produce an initializer such as:
export function initialize(application) {
application.inject('model', 'l10n', 'service:l10n');
application.inject('route', 'l10n', 'service:l10n');
application.inject('controller', 'l10n', 'service:l10n');
application.inject('component', 'l10n', 'service:l10n');
}
export default {
initialize: initialize,
name: 'my-l10n-initializer'
};
By default, it is assumed that the locale files are fingerprinted. This makes it possible to aggressively cache them. For this, the default blueprint will generate a file app/utils/l10n-fingerprint-map.js
. (If you upgraded recently, run ember g ember-l10n
to create the file).
Whenever you convert a .po file to JSON with ember l10n:convert
, it will then by default put the created JSON in a fingerprinted subfolder, and update the l10n-fingerprint-map.js
file with the new fingerprint. This map is then in turn used by the l10n
service. Note that this means that every language file is fingerprinted separately.
If you do not wish to use fingerprinting, make the fingerprintMap
property on the service return null
. Also, you need to deactivate it when converting the .po file: ember l10n:convert -fingerprint-map=false
.
For handling your translations within your handlebar templates you can use t
and n
helper:
The t
helper provides gettext singularization for message ids. It takes
singular message id as positional arguments. All placeholders can be provided
through named arguments.
If you have strings which are variables (e.g. enums), you can also
use the t-var helper: {{t-var myProperty}}
. It works the same way
as the t-helper, but it will be ignored by the gettext parser.
The n
helper provides gettext pluralization for message ids. It takes
singular and plural message ids as well as actual amount as positional
arguments. All placeholders can be provided through named arguments (hash).
Short version:
Long version:
Please note: If your count placeholder has another name than "{{count}}", you have to explicitly provide it as named hashed in addition to positional parameter (as well as for all other placeholders within those message ids!).
If you have complex message ids, which should contain "dynamic" placeholders,
which can also be replaced with components (such as a link-to
), you can use
the get-text
component.
Please note: If your message id contains HTML, you have to set
escapeText=true
on the component.
In acceptance tests, ember-l10n should work without any further work.
In integration tests, you can use the provided test helpers to provide easy to use {{t}}
,{{tVar}}
and {{n}}
helpers:
// tests/integration/components/my-component-test.js
import l10nTestHelper from 'ember-l10n/test-helpers';
moduleForComponent('my-component', 'Integration | Component | my component', {
integration: true,
beforeEach() {
l10nTestHelper(this);
}
});
These helpers will basically just pass the string through.
The extractor extracts message ids from the JS and HBS files in your Ember project. It generates the corresponding PO files for translation. Later, it will convert the translated POs into JSON files to be used for client side translation within your Ember app. Please note: Make sure turning off the development server while extracting, otherwise process slows down due to livereload!
Run the following command from your Ember project root for extraction:
ember l10n:extract
To see all available command line options for the extractor script please run:
ember l10n:extract -h
ember l10n:extract <options...>
Extract message ids from app
--default-language (String) (Default: 'en') The default language used in message ids
aliases: -d <value>
--bug-address (String) (Default: '[email protected]') The email address for translation bugs (configured in config/l10n-extract.js)
aliases: -b <value>
--copyright (String) (Default: 'My Company') The copyright information (configured in config/l10n-extract.js)
aliases: -c <value>
--from-code (String) (Default: 'UTF-8') The encoding of the input files
aliases: -e <value>
--extract-from (Array) (Default: ['./app']) The directory from which to extract the strings
aliases: -i <value>
--exclude-patterns (Array) (Default: []) List of regex patterns to put into a dedicated `excluded.pot` file (configured in config/l10n-extract.js)
aliases: -x <value>
--skip-patterns (Array) (Default: ['mirage','fixtures','styleguide']) List of regex patterns to completely ignore from extraction
aliases: -s <value>
--skip-dependencies (Array) (Default: ) An array of dependency names to exclude from parsing.
aliases: -sd <value>
--skip-all-dependencies (Boolean) (Default: true) If this is true, do not parse the node_modules/lib folders. (configured in config/l10n-extract.js)
aliases: -sad
--extract-to (String) (Default: './translations') Output directory of the PO-file
aliases: -o <value>
--keys (Array) (Default: ['t','n:1,2']) Function/Helper Keys to be used for lookup
aliases: -k <value>
--language (String) (Default: 'en') Target language of the PO-file
aliases: -l <value>
--pot-name (String) (Default: 'messages.pot') The name of generated POT-file (configured in config/l10n-extract.js)
aliases: -n <value>
--package (String) (Default: 'My App') The name of the package (configured in config/l10n-extract.js)
aliases: -p <value>
--version (String) (Default: '1.0') The version of the package
aliases: -v <value>
--generate-only (Boolean) (Default: false) If only PO-file should be created from POT without extraction
aliases: -g
--generate-from (String) (Default: 'messages.pot') Source POT-file to be used in conjunction with `-g` flag
aliases: -f <value>
--generate-to (String) (Default: null) Target PO-file to be used in conjunction with `-g` flag - CAUTION: uses `${language}.po` as default
aliases: -t <value>
--xgettext-template-path (String) (Default: './node_modules/xgettext-template/bin/xgettext-template') The path where xgettext-template is available
--gettextjs-path (String) (Default: './node_modules/gettext.js/bin/po2json') The path where gettext.js is available
Once you have extracted message ids with ember l10n:extract
, which creates a domain messages.pot
file, you can generate PO-files
for other languages by using -g
option without having to run extraction again like so:
ember l10n:extract -g -l de
(creates german PO file from POT file)
If you have excluded some files from prior extractions with -x
and want to merge them with your messages.pot you can do:
ember l10n:extract -g -f excluded.pot -t messages.pot
(merge POT files)ember l10n:extract -g -l en
(merge english PO file)ember l10n:extract -g -l de
(merge german PO file)
The converter will turn a given PO
into a JSON
file to be loaded by the service.
Run the following command from your Ember project root for conversion:
ember l10n:convert
To see all available command line options for the converter script please run:
ember l10n:convert -h
ember l10n:convert <options...>
Convert PO files to JSON
--convert-from (String) (Default: './translations') Directory of PO file to convert
aliases: -i <value>
--convert-to (String) (Default: './public/assets/locales') Directory to write JSON files to
aliases: -o <value>
--fingerprint-map (String) (Default: ./app/utils/l10n-fingerprint-map.js) Path to the fingerprint-map file. Set to false to deactivate fingerprinting.
aliases: -f <value>
--language (String) (Default: 'en') Target language for PO to JSON conversion
aliases: -l <value>
--gettextjs-path (String) (Default: './node_modules/gettext.js/bin/po2json') The path where gettext.js is available
Note that by default, this will create a fingerprinted json file & update the app/utils/l10n-fingerprint-map.js
accordingly. If you do not wish to use fingerprinting, use --fingerprint-map=false
.
The synchronizer will parse a given PO
file, use the message ids from each entry and uses them as new message ids accross JS
and HBS
files in your app. This is especially helpful if you proof read your current message ids before handing them over to translators for other languages.
Run the following command from your Ember project root for synchronization:
ember l10n:sync
To see all available command line options for the synchronization script please run:
ember l10n:sync -h
ember l10n:sync <options...>
Synchronize message strings with message ids (proof reading)
--sync-from (String) (Default: './translations') Directory of PO files
aliases: -i <value>
--sync-to (Array) (Default: ['./app']) Directory of JS/HBS files
aliases: -o <value>
--language (String) (Default: 'en') Language of PO file being used as base
aliases: -l <value>
--keys (Array) (Default: ['t','n:1,2']) Function/Helper Keys to be used for lookup
aliases: -k <value>
If you want to set global options for any of the above commands for your project, you can do so by providing a config file under config/l10n-${command}.js
. An example of global options for extract
command located under config/l10n-extract.js
could look like this:
module.exports = {
"bug-address": "[email protected]",
"copyright": "Copyright by Another Company",
"package": "Another App",
"exclude-patterns": [
"\/some\/exclude\/path",
"some-other-pattern(?!not-followed-by-this)",
],
"skip-patterns": [
"\/some\/skip\/path",
"(?:\/another\/skip)?path",
]
}
If it is a bug please open an issue on github.
This library follows Semantic Versioning
Cropster, GmbH © 2017