The i18n library is an AngularJS module composed of services and filters used to easily internationalize applications.
To configure the locale of your application, all you need to do is inject the
TranslateProvider
into your Angular config block and pass it a String that
indicates the desired locale.
angular.module('myMod', ['i18n'])
.config(function (TranslateProvider) {
TranslateProvider.setLocale('en-US');
});
The problem this library aims to solve is easily translating data into different languages, so it is left up to you (the developer) to come up with the actual translations. This library is opinionated in the fact that it expects you to supply them in JSON format
e.g.
{
"btn_cancel": "Cancel",
"btn_retry": "Retry"
}
It is also opinionated in where it expects to find these translation files. By
default it uses XMLHttpRequest
and expects
there to be a data folder in the root of your project. It then expects a
share folder inside of the data folder
Inside of the share folder, the library then expects a folder for each locale you wish to support. Each folder should be named after its respective locale.
data
| - share
| - en-US
| - labels.json
| - de-DE
| - labels.json
Now that we have our dictionaries for our different locales, now we need to load one of them based on the current locale.
angular.module('myMod', ['i18n'])
.config(function (TranslateProvider) {
TranslateProvider.setLocale('en-US');
TranslateProvider.loadMap('labels.json');
});
The file path is created based on the locale, so setting the locale to 'en-US'
will
result in the library loading data/share/en-US/labels.json
You can load in as many JSON files as you like, the common use case for this is when dealing with shared bower components that require translations.
If you're concerned that a component might have conflicting key in your dictionary, you can also specify a namespace to act as a prefix for a particular file
TranslateProvider.loadMap('labels.json', 'labels');
So in the above example btn_cancel
then becomes labels.btn_cancel
It's also possible to pass in an object of files/namespaces if you need to pull in multiple dictionaries at the same time.
Translate.provider.loadMap({
'appshop.json': null, // no namespace for this file
'labels.json', : 'labels'
});
The translate service comes with two primary methods of translating data
The get method is just a getter to your previously loaded translated dictionaries. The first argument it expects is the key name within your JSON file
$scope.label = Translate.get('btn_cancel');
This is fine for simple translations, but what happens when we need to embed dynamic data inside of one of our translations? That's easily achieved by passing in a second argument specifying the data to be bound.
Pretend that the following data was loaded into our translation dictionary
{
"greeting": "Hello, {{name}}!"
}
We could then inject dynamic data into this by simply passing an object in as our second argument.
$scope.greeting = Translate.get('greeting', {
name: 'Dave'
});
This would then yield 'Hello, Dave!'
The read method is for digesting files that are not loaded into the dictionary
- this is better suited for large chunks of HTML/Text that you don't want to stick in a translation dictionary.
data/share/en-US/lorem.html
<div>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt
in culpa qui officia deserunt mollit anim id est laborum.
</div>
$scope.lorem = Translate.read('lorem.html');
Just like reading translation dictionaries, the library assumes that your file lives in the data/share directory.
It is also possible to inject dynamic data into a file using Angular expressions in your template and passing in an object to the read method as a second argument
data/share/en-US/lorem-dynamic.html
<div>
Lorem ipsum dolor sit amet {{name}}, sed do eiusmod tempor incididunt ut labore
et dolore magna aliqua, {{day}}.
</div>
$scope.loremDynamic = Translate.read('lorem-dynamic.html', {
name: 'Beemo',
day: 'Saturday'
});
Sometimes injecting a service and binding data to the scope seems a bit tedious
just to translate some text. For that reason there are filters available for
both the get()
and read()
methods.
The translate filter simply retrieves an item from the translation dictionary
<button>{{ 'btn_retry' | translate }}</button>
It is also possible to pass an object to the translate
filter in a similar
fashion to the Translate.get()
method for dynamic data
<div>{{ 'greeting' | translate:{ name: 'Dave' } }}</div>
If you wish to just dump out an entire file in a similar fashion to
Translate.read()
, you can alternatively use the translateFile
filter.
<section>{{ 'lorem.html' | translateFile }}</section>
And just like its service counterpart, you can also pass in data to be interpolated.
<section>{{ 'lorem-dynamic.html' | translateFile:{ name: 'Beemo' } }}</section>
Ideally you would not need to include the actual Translate library in your unit tests, however if any of your tests are dependent on live text, you may run into some problems.
1. Update your karma.conf.js file to include your dictionaries as fixtures via the file array.
files: [
{
pattern: 'data/share/l10n/**/*.json',
watched: true,
included: false,
served: true
}
]
2. Because Karma doesn't actually spin up a web server for our code, we need to proxy any requests for dictionaries to the data folder. This is also done in the karma.conf.js file.
proxies: {
'/data': 'http://127.0.0.1:9876/base/data'
}
3. The last step is to run/configure the module within our unit tests. This is done in your actual spec files
// global module declaration
angular.module('mock.i18n', ['i18n'])
config(function (TranslateProvider) {
TranslateProvider.loadMap('labels.json');
});
// later on down the line when we're initializing our module for testing..
describe('AppModule', function () {
beforeEach(module('AppModule', 'mock.i18n'));
// assertions/tests/etc..
});