i18nfp
This is project for JavaScript i18n with a simple solution for factualization and pluralization.
Introduction
Installation
> npm install i18nfp -g
What we offer?
We are offering a solution for those developers:
- who have to deals PROPERTIES file as resource bundles due to legacy translation process requirements
- who want to handle i18n in JavaScript, either in browser or in backend like NodeJS, in a most convenient way
- who have to deal with factualization for different domains (e.g. '.com' and '.co.uk')
- who have to consider to deal with pluralization
And particularly, we are offering below set of tools for different purposes
- A global command line tool. It defines a set of rules for composing your PROPERTIES files, and provides corresponding tools to generate JavaScript-friendly resources from PROPERTIES files. See Command line tool.
- A Connect compliant plugin. It provides a simple way to use these generated resources files in NodeJS server like ExpressJS. See Work with Connect
- A browser side JavaScript file. It is designed to work with RequireJS and its i18n plugin. You can find the file under
client
folder, and put it to your project folder. See Work with RequireJS
Why we want this?
We can search out lots of i18n tools over internet, but none of them could handle factualization.
What's Factualization? Why we need this?
Factualization is a special i18n case for different display logic on different domains. For example
on US domain site (.com), use below entry to display price:
ticket.price.all=Price in all: {0}
but on UK domain site (.co.uk), you need to add VAT to the price display, so the format looks like:
ticket.price.all=Price in all: {0} + VAT {1}
When we use some set of resource bundles, we need to distinguish these difference between domains. One solution which is using by StubHub is to add a prefix:
ticket.price.all=Price in all: {0}
//For US domain logic
us.ticket.price.all=Price in all: {0}
//For UK domain logic
uk.ticket.price.all=Price in all: {0} + VAT {1}
All these entries are contained in the dafault resource bundle file, like message.properties. When this file is sent to for translation, people in that process could understand better for the translation.
Command line tool
Introduction
This tool is aimed to provide an easy way to pre-compile all resource bundle properties files into JSON-format javascript files, which could be used in NodeJS or client side.
Installation
Make sure you have installed i18nfp into global. If you're Mac or Linux user, please use sudo
ahead of the command if necessary.
> npm install i18nfp -g
Usage
# get how to use via
> i18nfp --help
# compile all properties file under i18n/ to target folder
> i18nfp -s ./i18n -t ./output
What will be generated?
Take above command for example, under the target output
folder, it generated a nls
folder, and a few files. The files under nls
are used for RequreJS i18n plugins. The files under output
folder could be used by NodeJS server and RequireJS.
- output
|- nls
| |- _com
| | |- fr-fr
| | | |~ messages.js
| | |~ messages.js
| |- fr-fr
| | |~ messages.js
| |~ messages.js
| |~ messages_client.js
| |~ messages_fr-FR.js
| |~ messages_fr-FR_client.js
|- properties
|~ messages.properties
|~ messages_fr_FR.properties
To generate resource bundles for RequireJS, you need mark [i18nfpclient]
in properties file, see below section Handling Scope for usage.
As i18nfp is designed to support factualization, the nls
folder has more contents than what RequireJS i18n plugin needs. We also enhanced the i18n plugin for RequireJS to support domain specific resource bundles. The new i18n plugin could be download at A new i18n plugin for RequireJS or find it at client/i18n.js
;
From the above figure, you can find that there is a _com
folder under nls
. All the resource bundles for .com
site will be placed here. To support more site domains, before executing the command, please find the i18nfp at global node_modules folder (where are global node_modules?) or local workspace node_modules folder, then edit lib/config.js
.
factulizationPrefix : {
'us' : '_com',
'uk': '_co_uk',
'eu': '_eu'
}
Add or change factualization prefix to map its domain suffix, with replacing dot .
to underscore _
. More public domain suffix, please refer to Public Suffix List.
Now only a few Top-level domains are added by default as list below:
Handling peroperties file
Factualization
Add prefix to entries for factualization. The prefix indicates the domain for the entry.
ticket.price.all=Price in all: {0}
//For US domain logic
us.ticket.price.all=Price in all: {0}
//For UK domain logic
uk.ticket.price.all=Price in all: {0} + VAT {1}
User only needs to use the entry name, without mentioning the domain. The domain and prefix mapping could be set in config file.
Pluralization and Logic set
i18nfp provides a specific expression to allow you handling pluralization and simple logic set in properites file. Please see below samples to better understand it.
For an entry that have different expression for handling pluraization, follow below grammar:
# Sample 1 - Properties file
# displaying a message with one placeholde
tickets = {0} tickets
# use '__' to as i18nfp specific separator
tickets__1 = {0} ticket
Then in JavaScript codes, only need to care about 'tickets' like below codes:
// Sample 1 - JavaScript code
var i18nfp = require('./i18nfp'),
msg = require('./nls/message.js'),
getMsg = i18nfp(msg);
console.log(getMsg('tickets', 0)); //print '0 tickets'
console.log(getMsg('tickets', 1)); //print '1 ticket'
console.log(getMsg('tickets', 2)); //print '2 tickets'
From above sample, you can see that we could allow you to append a __
and a number to entry key to form a logic set. If argument equals to 1, then we use entry tickets__1
, otherwise, we use entry tickets
as default. You can also use other number as threshold, this means if the paramenter is equal or larger than the number, use the mapping entry. Please note that number 0 and 1 are preserved for handling parameter that must be equal to 0 or 1. See below sample:
# Sample 2 - Properties file
# displaying a message with one placeholde
tickets = {0} tickets
# use '__' to as i18nfp specific separator
tickets__0 = no ticket
tickets__1 = {0} ticket
tickets__5 = a few tickets
tickets__10 = plenty of tickets
With above entries defined in properties file, you can avoid logic control in your JavaScript codes, and just use tickets
as key to get the corresponding message.
// Sample 2 - JavaScript code
var i18nfp = require('./i18nfp'),
msg = require('./nls/message.js'),
getMsg = i18nfp(msg);
console.log(getMsg('tickets', 0)); //print no ticket', use '__0'
console.log(getMsg('tickets', 1)); //print '1 ticket', use '__1'
console.log(getMsg('tickets', 2)); //print '2 tickets', use default
console.log(getMsg('tickets', 3)); //print '3 tickets', use default
console.log(getMsg('tickets', 6)); //print 'a few tickets', use '__5'
console.log(getMsg('tickets', 20)); //print 'plenty of tickets', use '__10' threshold
There is another few use cases to use this feature with i18nfp:
-
Use a parameter to control logic, and second parameter for passing in data. In this case, use
{1}
rather than{0}
in your entry value -
Use more than one parameters to control logic, then you can append another
_
with a number, liketickets__0_1
. Once you have two or more parameter for logic control, you can define default value in case i18nfp can't find any mapping entry. e.g. definetickets__0
as default value for any other entriestickets__0_\d
(*). See example like below:tickets.attibute__1 = only one ticket tickets.attibute__1_1 = one ticket with one parking pass tickets.attibute__2 = {0} tickets tickets.attibute__2_1 = {0} tickets with one parking pass tickets.attibute__2_2 = {0} tickets with {1} parking passes
*Note: i18nfp is trying to resolve simple logic in properties file, but once more than two parameters are used in logic control, you may need to define a large set of entries for all conditions. And there must be some conditions that can not be handled well with i18nfp.
Handling Scope
When you define a properties file, some of them are used in server side, while some in client side. You don't need to serve all property entries to client side JavaScript codes. In this case, you can put all your client side entries in the bottom of properties file, with a specific entry name to separate them
# Scope sample - Properties file
# Below are used in Server side
...
...
# Below are used in Client side, [i18nfpclient] is required
[i18nfpclient]
...
...
Usage and samples
Work with Connect
Usage
Take ExpressJS for example, in ExpressJS app.js
//Require this module into app
var i18nfp = require('i18nfp');
Create a config variable for setting up where the nls files, and which files will be used, and accepted locales. domainMapping
provides a way to map hostname to a specific factualization prefix. With this, we could test UK site by mapping localhost
to uk.
var i18nfp_config = {
nlsRoot: path.join(__dirname, "nls"),
nlsFiles: ['messages', 'domainConstrants', 'localeConstrants'],
acceptLocales: ['en-us', 'en-gb'],
structureType: 1, // keep it as 1 by now, will support more later
// set hostname & factualization prefix mapping
domainMapping: {
'localhost': 'us.',
'.co.uk': 'uk.'
}
};
Init i18nfp with the config variable.
i18nfp.init(i18nfp_config);
var app = express();
app.configure(function() {
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
//Add a handle for app
app.use(i18nfp.handle);
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
});
// register a helper
i18nfp.registerAppHelper(app);
Then do something in template file, e.g. in below jade file. message
and domainMsg
are exposed for use (they are the same). No need to worry about whether it is domain-specific.
//- headMeta.jade
meta(charset="utf-8")
meta(name="keywords", content=message("sh.meta.keywords"))
meta(name="description", content='#{domainMsg("sh.meta.description")}')
If using DustJS as template, a good way to use i18nfp
is to create a dust helper. Sample codes as below:
...
i18nfp.init(i18nfp_config);
// Register dust helper for template rendering
var dust_i18n_helperGen = require('./lib/dust-i18n-helperGen');
dust.registerHelper('i18n', function(chunk, context, bodies, params){
//context.message will be registered later in below by using i18nfp.handle
var msg = dust_i18n_helperGen(context.get("message"));
return msg(chunk, context, bodies, params);
});
// create app
var app = express();
app.engine('dust', dust);
...
Note: dust.registerHelper
is not a official method of any DustJS library for ExpressJS, it's customized to set a helper to Dust. dust-i18n-helperGen
codes could be found in sample/server/
folder.
How to use the dust helper, see Dust i18n helper usage
Reference
inspired by i18next
Work with RequireJS
As mentioned in Command line tool, i18nfp command will generate resource bundles for RequireJS to use. Copy client/i18nfp.js to your project javascript folder.
With RequireJS i18n plugin
define('yourModule', ['lib/i18nfp', 'i18n!output/nls/messages'],
function(i18nfp, message){
var getMsg = i18nfp.t(message);
console.log(getMsg('tickets', 0));
console.log(getMsg('helloword', 'RequireJS and i18nfp'));
}
);
In above sample, the i18n plugin of RequireJS will help you get the expected resource bundles according to domain and locale, and i18nfp will help you deal with pluralization.
Without RequireJS i18n plugin
If you don't want to use i18n plugin, you can use below ways to set factualization for i18nfp if all factualized property entries are defined in a single file.
define('yourModule', ['lib/i18nfp', 'output/messages_client'],
function(i18nfp, message){
var config = {
domainMapping:{
'yoursite.com':'us.',
'yoursite.co.uk':'uk.',
'yoursite.au':'au.',
'localhost:':'us.' // you can map your dev env to any domain
}
};
i18nfp.init(config);
var getMsg = i18nfp.t(message);
console.log(getMsg('tickets', 0));
console.log(getMsg('helloword', 'RequireJS and i18nfp'));
}
);
Work with DustJS on Client side
sample/client/dust-i18n-helper.js
shows a sample to work with RequireJS
, i18n
, and DustJS
. It provides a simple way to bind resource bundles to DustJS helper.
Dust i18n helper usage
After integrating i18nfp with DustJS, you can specify message in dust tempalte file directly in below ways:
<!-- use a static key -->
<div>{@i18n key="sh.menu.home" /}</div>
<!-- give a default value by using text attribute -->
<div>{@i18n key="sh.menu.home" text="Höme" /}</div>
<!-- use a variable key -->
<div>{@i18n key="sh.menu.{type}" /}</div>
<div>{@i18n key=somevariable /}</div>
<!-- use a parameter from context -->
<div>{@i18n key="sh.search.results.count" p0=nums /}</div>
<!-- use multiple parameter from context or string -->
<div>{@i18n key="sh.search.results.count.in.region" p0=rows p1=geoName p2=numFound/}</div>