Grunt xgettext plugin for JavaScript, HTML and Handlebars
Gettext is the name of a set of utilities developed by GNU to add multi-language support to applications.
The first step of the translation process is to extract translatable messages from your sources.
To do so, you should mark all strings which you want to translate by wrapping them in a
translator function (often called tr()
, _()
or i18n()
). The xgettext task provided by this
plugin then scans all your source files and generates a POT file containing all the translatable
strings. In addition to scanning plain JavaScript files, this plugin also supports scanning
HTML and Handlebars templates.
The second step is converting the POT file into a PO file which is the file that will be actually
translated by translators. During this step you can merge the POT file with a previously
translated PO file resulting in a new PO file that has all known translations already filled in.
This step can be performed using the msgmerge
command-line utility. It is not provided by this
plugin, but an example of calling the utility through the Grunt Shell plugin is given below as
part of the real-world example.
The third step is taking the translated PO files and getting the translations to show up in your
project. In order to facilitate this, use the po2json from the grunt-po2json plugin which
converts the translations to a JSON map, optionally wrapped in a Require.js definition. To finish
this step, you will have to make sure the JSON translations are loaded into your application and
actually used by the tr()
, _()
or i18n()
function you use.
Happy translating!
To find more information about the original gettext utilities, visit the GNU project: http://www.gnu.org/software/gettext/
GNU also maintains a list of additional tools to work with PO files, including translation programs useful for translators: http://www.gnu.org/software/trans-coord/manual/web-trans/html_node/PO-Files.html
This plugin requires Grunt ~0.4.5
If you haven't used Grunt before, be sure to check out the Getting Started guide, as it explains how to create a Gruntfile as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command:
npm install grunt-xgettext --save-dev
Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:
grunt.loadNpmTasks('grunt-xgettext');
In your project's Gruntfile, add a section named xgettext
to the data object passed into
grunt.initConfig()
.
grunt.initConfig({
xgettext: {
options: {
functionName: "tr",
potFile: "messages.pot",
//processMessage: function(message) { ... }
},
target: {
files: {
handlebars: [],
javascript: []
}
}
},
})
Type: String
Default value: "tr"
The function name that marks translatable messages.
Type: String
Default value: "messages.pot"
The .pot file that is generated by this task (the output file). It will contain all extracted messages, regardless of the source in which they were found (be it Handlebars or JavaScript files).
Warning: This file is overwritten every time you run this task.
Type: Function
Default value: _.identity
Custom function that will process every extracted message before it's included in the POT file. This may come in handy, for example, if you want to simplify whitespace.
The function takes the message string as its sole argument and should return the processed message.
Handlebars files to scan for translatable messages.
Assuming the default functionName is used, translatable messages look like this:
{{tr "Some translatable message"}}
{{tr "You have %1 follower" "You have %1 followers" numFollowers}}
In both cases, all string arguments inside the {{tr ...}} invocation are extracted as translatable messages.
HTML files to scan for translatable messages.
Assuming the default functionName is used, translatable messages look like this:
tr("Some translatable message")
tr("You have %1 follower" "You have %1 followers" numFollowers)
In both cases, all string arguments inside the tr() invocation are extracted as translatable messages.
JavaScript files to scan for translatable texts.
Assuming the default functionName is used, translatable messages look like this:
tr("Some translatable messages")
tr("You have %1 follower", "You have %1 followers").arg(numFollowers)
In both cases, all string arguments inside the tr() function call are extracted as translatable messages.
Be aware that concatenating translatable strings with variables is inherently not possible. For example, this will NOT work:
tr("Some value: " + value)
The reason this fails is because the input to the tr() function will be different every time it is called, and therefore does not have a stable key for looking up the proper translation.
Note that concatenating multiple strings to create a single (multi-line) translatable string does work with JavaScript files. Example:
tr("This is the first line " +
"of a multiline translatable message")
Finally, you can pass context and comments for your messages by using an options object. Examples:
tr("September", { comment: "Translators: use all lower-case if months are not " +
"capitalized in your language" })
tr("May")
tr("May", { context: "Abbreviation", comment: "Abbreviation of May" })
tr("September")
tr("Sept.", { context: "Abbreviation", comment: "Abbreviation of September" })
For Handlebars templates, you can achieve the same effect through named arguments:
{{tr "May" context="Abbreviation" comment="Abbreviation of May"}}
For JavaScript, you can also use triple-slash comments to provide translator comment:
/// Translators: use all lower-case if months are not capitalized in your language
tr("September")
var _ = grunt.util._;
var locales = ["de-DE", "en-GB", "en-US", "fr-FR", "it-IT", "ja-JP", "nl-NL"];
grunt.initConfig({
xgettext: {
target: {
files: {
handlebars: ["tmpl/**.handlebars"],
javascript: ["js/**.js"]
},
options: {
functionName: "i18n",
potFile: "translations/messages.pot",
processMessage: function(message) {
return message.replace(/\s+/g, " "); // simplify whitespace
}
}
}
},
po2json: {
target: {
src: ["translations/*.po"],
dest: ["translations/"],
options: {
requireJs: true
}
}
},
shell: {
options: {
failOnError: true
},
msgmerge: {
command: _.map(locales, function(locale) {
var po = "translations/" + locale + ".po";
return "if [ -f \"" + po + "\" ]; then\n" +
" echo \"Updating " + po + "\"\n" +
" msgmerge " + po + " translations/messages.pot > .new.po.tmp\n" +
" exitCode=$?\n" +
" if [ $exitCode -ne 0 ]; then\n" +
" echo \"Msgmerge failed with exit code $?\"\n" +
" exit $exitCode\n" +
" fi\n" +
" mv .new.po.tmp " + po + "\n" +
"fi\n";
}).join("")
}
}
}
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using Grunt.
- Added Underscore templates extractor.
- Bugfixes for multi-line translator comments.
- Introduced support for triple-slash translator comments in JavaScript extractor.
- Introduced new Handlebars extractor, with support for context and comment options.
- Bugfixes for JavaScript extractor.
- Bugfixes for JavaScript extractor.
- Introduced new JavaScript extractor, with support for context and comment options.