Let’s start by looking at a concrete example, then I’ll justify our different choices.
1 2 3 4
1 2 3 4 5 6 7 8 9
- It gives a way to automatically fetch localizable strings or ids from source code
by searching for
- It allows to use either strings or IDs as value to translate. It is obviously better to use IDs. Because locales will broke each time addon developer fix a typo in the main language hard coded in the code.
But we should not forget that the high level APIs is trying to simplify addon development. So that it has to be really easy to translate a simple addon that has only 2 JS files and less than 50 lines of code! And the simple fact to mandatory require a locale file for the default language appears like a big burden for such small addon.
Having said that, I’m really happy that gettext approach doesn’t discourage, nor makes it harder to use IDs, and so, if an addon developer build a big addon or just want to take more time to use better pratice, he still can do it, easily!
Why JSON for locales?
We could have used properties files, like XUL addons. But this format has some limitations that are not compatible with gettext pattern. Keys can’t contain spaces and are limited to ASCII or something alike, so that we can’t put text in a key.
So instead of using yet another specific format, I’m suggesting here to use JSON. JSON is really easy to parse and generate from both client and server side, and I’m convinced that it is simple enough to be edited with a text editor. On top of that we can build a small web application to ease localization.
In my very first proposal, I used a complex JSON object with nested attributes. But it ends up complexifying the whole story without real advantage. So I’m suggesting now to use the most simple JSON file we can require: one big object with keys being strings or id to translate and values being translated strings. Then we will be able to use JSON features to implement complex localization features, like plurals handling. So that values may be an array of plurals forms.
The big picture
Everything starts with one addon developer or one of its contributor. If one of them want to make the addon localizable, they have to use this new localization module.
There is already multiple choices that has been made here:
_is not a magic global. We need to explicitely require it. This choice will simplify compatibility with other CommonJS environnements, like NodeJS.
- The name of the module itself is
localizationin order to ease the use of it.
- This module expose
getattribute in order to be able to expose another methods. I’m quite confident we will need some functions for plurals or files localization.
Then, they need to use
_ on localizable strings:
1 2 3 4 5
Now, they have two choices:
- use a string written in their prefered language, like here. So that they don’t have to create a locale file.
- use an ID. Instead of
_("My Menu Item"), we will use:
_("contextMenuLabel"). But it forces to create a localization file in order to map
My Menu Item.
Then, either a developer or a localizer can generate or modify locales files.
Each jetpack package can have its own
This folder contains one JSON file per supported language.
Here is how looks like a jetpack addon:
* my-addon/ * package.json # manifest file with addon name, description, version, ... * data/ # folder for all static files * images, * html files, * ... * lib / # folder that contains all JS modules: * main.js # main module to execute on startup * my-module.js # custom module that may use localization module * ... * locale/ # our main interest! * en-US.json * fr-FR.json * en-GB.json * ...
The next iteration will add a new feature to our command line tool,
that is going to generate or update a locale file for a given language by fetching localization strings from source code.
For example, the following command will generate
1 2 3
Finally, we need to replace right side values with the localized strings:
1 2 3
And build the final addon XPI file with:
Any kind of feedback would be highly appreciated on this group thread.
If you want to follow this work, subscribe to bug 691782.