JavaScript library for internationalization
int17 is a pure JavaScript library providing internationalization and localization. It can be used normally in any browser as well as in the node.js environment (especially with Express).
Install using the package manager for your desired environment(s):
# for node.js:
$ npm install int17
# OR; for the browser:
$ bower install int17
In the browser:
<html>
<head>
<script src="http://neocotic.com/int17/lib/int17.min.js"></script>
<script>
(function () {
var i18n = int17.create();
i18n.init({ locale: 'en-GB' }, function (err) {
if (err) throw err;
i18n.traverse();
});
}());
</script>
</head>
<body>
<h1 i18n-subs="2013" i18n-content="date_header"></h1>
<div>
<p i18n-values=".style.direction:dir;.innerHTML:main_body"></p>
<span i18n-content="my_label"></span>
<select i18n-options="default_option:-1;option1;option2"></select>
</div>
</body>
</html>
In node.js:
var i18n = require('int17').create();
i18n.initSync({ locale: 'en-GB' });
console.log(i18n.get('welcome'));
Both using the same JSON structure, inspired by that used by Google Chrome extensions:
{
"date_header": {
"message": "The date is $DATE$!",
"placeholders": {
"date": {
"content": "$1",
"example": "2012"
}
}
},
"default_option": {
"message": "All"
},
"main_body": {
"message": "This is an example of $name$'s greatness!",
"placeholders": {
"name": {
"content": "int17"
}
}
},
"my_label": {
"message": "Options example:"
},
"option1": {
"message": "1st option"
},
"option2": {
"message": "2nd option"
},
"welcome": {
"message": "Hello, World!",
"description": "Simple welcome message"
}
}
The global int17 variable in the browser or the return value of
require('int17') in node.js is an instance of
Int17. This is used to expose factory functionality required to get started with
globalizing your application.
create([name])
Returns a new instance of Internationalization. Optionally,
if a name is specified, a shared instance can be retrieved/created which is most
useful within node.js as it saves passing an object reference
between modules.
var i18n1 = int17.create();
var i18n2 = int17.create('i18n');
var i18n3 = int17.create('i18n');
console.log(i18n1 == i18n2); // "false"
console.log(i18n2 == i18n3); // "true"
clearCache()Removes all internal references to shared instances which are created by create([name]).
var i18n1 = int17.create('i18n');
var i18n2 = int17.create('i18n');
int17.clearCache();
var i18n3 = int17.create('i18n');
var i18n4 = int17.create('i18n');
console.log(i18n1 == i18n2); // "true"
console.log(i18n1 == i18n3); // "false"
console.log(i18n3 == i18n4); // "true"
optimize(messages)
Removes all meta data (i.e. message descriptions and placeholder examples) from the specified messages,
which can either be a message bundle (Object) or resource contents (String).
This can be really useful for build systems trying to optimize/minify message bundle resources to improve load times and/or reduce bandwidth in their production environments.
var messages = {
greet: {
message: 'Welcome, $name$',
description: 'Greeting message for logged in users',
placeholders: {
name: {
content: '$1',
example: 'Alasdair'
}
}
}
};
console.log(int17.optimize(messages));
/*
{
greet: {
message: 'Welcome, $name$',
placeholders: {
name: {
content: '$1'
}
}
}
}
*/
parse(contents)Builds a message bundle from the specified resource contents.
console.log(int17.parse(fs.readFile('./path/to/file', 'utf8')));
/*
{
greet: {
message: 'Welcome, $1'
}
}
*/
noConflict()
Returns int17 in a no-conflict state, reallocating the int17 global
variable name to its previous owner, where possible.
This is really just intended for use within a browser.
<head>
<script src="http://neocotic.com/path/to/conflict-lib.js"></script>
<script src="http://neocotic.com/int17/lib/int17.min.js"></script>
<script>
var int17nc = int17.noConflict();
// Conflicting lib works again and use int17nc for this library onwards...
</script>
</head>
versionThe current version of int17.
console.log(int17.version); // "0.3.0"
An instance of Internationalization is returned by create([name])
and is your new best friend when it comes to all your internationalization needs.
init([options], [callback(err)])Initializes the instance with any options provided before asynchronously loading the localalized messages from their resource file/URL.
i18n.init(options, function (err) {
if (err) throw err;
// Do anything...
});
initSync([options])Initializes the instance with any options provided before synchronously (thread-blocking) loading the localized messages from their resource file/URL.
i18n.init(options);
// Do anything...
The following options are recognised by these methods (all of which are optional):
| Option | Description | Default Value |
|---|---|---|
| clean | Remove all recognized int17 attributes are removed from each processed element when traverse([element]) is called. | false |
| defaultLocale | Default locale to be used if one is not specified or could not be derived. | 'en' |
| encoding | Encoding to be used when reading locale files. | 'UTF-8' |
| extension | File extension used by your locale files. | '.json' |
| fallback |
If there is a problem reading the file for the current locale, retry using the parent of
the current locale (if applicable) or, in node.js only, one
of its child locales (again, if applicable).
The fallback process may only retry once and then the appropriate error is thrown but may impact performance when used in production. |
false |
| fileName |
Base file name used by your locale files.
This is only relevant when the folders option is enabled. See the Locale Files section for more information and examples. |
'messages' |
| folders |
Use a folder-based file structure for your locale files.
See the Locale Files section for more information and examples. |
false |
| ignoreCase | Ignore the case of placeholders when looking up their contents to be substituted. Disabling this will improve performance but means that the case of placeholders in messages must exactly match. | true |
| languages |
Specify a pre-defined list of available languages.
See the Languages section for more information on the benefits of this option. |
[] |
| locale |
Specify the locale whose messages are to be retrieved.
By default, int17 attempts to derive the best locale based on your environment
before falling back to the defaultLocale
option.
|
Derived |
| messages |
Specify a pre-defined message bundle.
If a non-empty bundle is provided, no additional messages are loaded during initialization. This can be useful if you want to pre-render your message bundles on the page. |
{} |
| path | Absolute/relative file path pointing at the root directory containing the locale files/folders. | './locales' |
| separator | File separator to be used when building file paths. |
Browser: '/'
node.js: path.sep |
attribute(selector, attr, name, [...])
Sets the value of the specified attribute on all of the selected elements with the message for
the specified name. All remaining arguments are used to replace indexed placeholders
within the message before it is returned.
i18n.attribute('a', 'title', 'link_title');
i18n.attribute('a.download', 'title', 'open_file');
content(selector, name, [...])
Replaces the contents of all of the selected elements with the message for the specified
name. All remaining arguments are used to replace indexed placeholders within the
message before it is returned.
i18n.content('h1', 'page_header');
i18n.content('p', 'welcome', 'World');
node
The element whose children are within scope of all browser only
methods. By default this references window.document but this can be changed to
reduce the scope of internationalization and improve performance of some of these methods.
console.log(i18n.node); // "HTMLDocument"
options(selector, names, [...])
Creates option elements containing the messages for the specified names and appends
them to all of the selected elements. All remaining arguments are used to replace indexed
placeholders within the message before it is returned.
names can consist of a mix of strings and objects containing a name
string and, optionally, a subs list as well as a value. When used, the
optional subs property of a name object overrides any replacement arguments passed
to the method when that particular message is processed while the value property is
transfered to that option.
i18n.options('select', [
{ name: 'default_option', value: '-1' },
'option1',
{ name: 'option2' },
{ name: 'option2', subs: ['2'] }
], 'Two');
property(selector, prop, name, [...])
Sets the value of the specified property on all of the selected elements with the message for the
specified name. All remaining arguments are used to replace indexed placeholders
within the message before it is returned.
prop can be identified using paths to change the values of deep properties.
If the property is innerHTML, the contents of each element are traversed
once being processed to ensure any newly inserted attributes are
processed.
i18n.property('p', 'style.direction', 'dir');
i18n.property('p', 'innerHTML', 'welcome', 'World');
traverse([element])
Searches the children of the specified element (defaulting to node)
for elements with any of the recognized HTML attributes
and then processes each child accordingly.
element can either be an HTML
element node or a query selector string which, when used, will be used to query the children of node
for the actual element to be traversed.
// Traverses the children of i18n.node
i18n.traverse();
// Both of these do the same thing: traverses the children of <body> element
i18n.traverse(document.body);
i18n.traverse('body');
languages([parent], [callback(err, languages)])
Retrieves a list of available languages for the current environment. Optionally, this can be
filtered to only include languages that extend from a specific parent locale,
excluding the parent itself.
The languages are initially fetched
asynchronously so a callback function must be provided in order to use the results.
However, this does not really happen in the browser where languagesSync([parent])]
is called while still supporting the callback pattern.
// Fetch all available languages
i18n.languages(function (err, languages) {
if (err) throw err;
console.log(languages); // e.g. "en", "en-GB", "en-US", "fr"
});
// Fetch available languages that extend from "en"
i18n.languages('en', function (err, languages) {
if (err) throw err;
console.log(languages); // e.g. "en-GB", "en-US"
});
languagesSync([parent])
Returns a list of available languages for the current environment. Optionally, this can be
filtered to only include languages that extend from a specific parent locale,
excluding the parent itself.
The languages are initially fetched synchronously (thread-blocking).
// Return all available languages
console.log(i18n.languagesSync()); // e.g. "en", "en-GB", "en-US", "fr"
// Return available languages that extend from "en"
console.log(i18n.languagesSync('en')); // e.g. "en-GB", "en-US"
The list of available languages is populated only with the current locale in the browser but, in node.js, the locale root directory is scanned and detects locales from the children file/folder names.
Alternatively, the languages option can be used to pre-populate the list. This is extremely beneficial in a browser as there's no other way of int17 knowing what languages are available in your configuration.
Regardless, the list is only populated when no languages have previously been fetched (or provided - in the previous statement's case) and results are cached to improve the performance of subsequent requests.
locale()Returns the current locale.
console.log(i18n.locale()); // e.g. "en", "en-GB"
all(names, [...])
Returns a list of messages for each of the specified names. All remaining arguments
are used to replace indexed placeholders within each message before they are returned.
names can consist of a mix of strings and objects containing a name
string and, optionally, a subs list as well. When used, the optional
subs property of a name object overrides any replacement arguments passed to the
method when that particular message is processed.
console.log(i18n.all([
'my_message',
{ name: 'welcome' },
{ name: 'welcome', subs: [] },
{ name: 'welcome', subs: ['Universe'] }
], 'World'));
/*
[
'Lorem ipsum',
'Hello, World!',
'Hello, $1!',
'Hello, Universe!'
]
*/
get(name, [...])
Returns the message for the specified name. All remaining arguments are used to
replace indexed placeholders within the message before it is returned.
console.log(i18n.get('welcome')); // "Hello, $1!"
console.log(i18n.get('welcome', 'World')); // "Hello, World!"
map(names, [...])
Maps each of the specified names to their corresponding message. All remaining
arguments are used to replace indexed placeholders within each message before they are returned.
names can consist of a mix of strings and objects containing a name
string and, optionally, a subs list as well. When used, the optional
subs property of a name object overrides any replacement arguments passed to the
method when that particular message is processed.
console.log(i18n.map([
'my_message',
{ name: 'welcome' },
{ name: 'welcome', subs: ['Universe'] }
], 'World'));
/*
{
my_message: 'Lorem ipsum',
welcome: 'Hello, Universe!'
}
*/
You can also access each of these methods on the escape namespace, where their
results have already been escaped so that it is safe for HTML
interpolation.
console.log(i18n.escape.all(['north_south']));
// [ 'North & South' ]
console.log(i18n.escape.get('breadcrumb'));
// "Home > News"
console.log(i18n.escape.map([
'north_south',
'breadcrumb'
]));
/*
{
north_south: 'North & South',
breadcrumb: 'Home > News'
}
*/
express(app, [namespace])
Extends the app provided to expose the instance to Express.
Optionally, a namespace can be specified to customize what property name is used to
access the int17 functionality via requests and responses.
See the Express section for more information and examples.
int17 can be configured to use one of two different file structures using options:
Flat
Folders
Regardless of which file structure is used, the contents of each files should be in a JSON format while adhering to the following structure:
{
"<message_name>": {
"message": "<message_content>",
"description": "<optional_description>",
"placeholders": {
"<placeholder_name>": {
"content": "<example_content>",
"example": "<optional_example>"
}
}
}
}
The traverse([element])] method automatically recognizes int17-specific HTML attributes and handles each element they're attached to accordingly.
Alternatively, you can use HTML5 data attribute names if you want your pages to contain only
strictly valid HTML5 (e.g. data-i18n-content).
int17 prefix have been deprecrated
as of v0.3.0 in favour of the more generic i18n
prefix and will be removed completely in a future release.
i18n-contentReplaces the HTML contents of the element with message for the attribute's value.
<h1 i18n-content="page_header"></h1>
i18n-optionsCreates option elements containg the message for each name in the attribute's value.
The attribute value contains semi-colon separated names which can themselves be separated by colons to specify values.
<select i18n-options="default_value:-1;option1;option2;option3"></select>
i18n-valuesSets the attribute/property values to their corresponding messages as definied in the attribute's value.
The attribute value contains semi-colon separated names which are themselves separated by colons
to specify their message names. To identify property paths (useful for changing deep
properties) it must begin with a decimal point (.). If the property is
.innerHTML, the contents of each element are traversed once being processed
to ensure any newly inserted attributes are processed.
<p i18n-values=".innerHTML:page_content;.style.direction:dir;title:main_title"></p>
i18n-subsSpecifies replacements for indexed placeholders within the messages looked up while processing the other attributes.
The attribute value contains semi-colon separated values.
<p i18n-subs="World" i18n-content="welcome"></p>
Due to the huge popularity of jQuery it deserves our full support, especially it can really simplify common usage. Take the JavaScript in the original browser example:
$.int17({ locale: 'en-GB' }).done(function () {
$(document).int17();
});
As you may have noticed, we're using jQuery.Deferred
here, but you can also use simple callback functions as well. For convenience, the Internationalization
instance that is created by the call to $.int17 is passed as an argument to whatever
callback mechanism is used.
$.int17 accepts the same options
as the core initialization methods, but it also supports an additional int17 option
which, when specified, will result in the plugin reusing that instance. However, you must ensure
that the value of int17 is initialized before calling $.fn.int17.
If you love Express as much as I do, you'll be happy to know that you don't have to do any fancy hacking to get it to work well with int17.
Just configure it:
var express = require('express'),
i18n = require('int17').create();
// ...
// During app's configuration...
app.configure(function () {
// ...
// Binds the int17 instance to requests and responses handled by app
i18n.express(app);
// ...
});
That's it! Now you can use it in your view:
app.get('/', function (req, res) {
res.render('index', {
header: req.int17.get('page_header'),
intro: req.int17.get('welcome_message')
});
});
And just as easily in your templates:
<% include header %>
<h1><%- int17.get('page_header_prefix') %> <%- header %></h1>
<p><%- intro %></p>
<% include footer %>
i18n prefix
optimize and validate options)
ignoreCase
option to control how placeholders are looked up
defaultLocale
option to allow specific fallback locale to be used
messages option
to allow pre-defined message bundles to be used
_
Internationalization, Messenger) via int17
If you have any problems with this library or would like to see the changes currently in development browse our issues.
Developers should run all tests locally and ensure they pass before submitting a pull request.
Take a look at the documentation to get a better understanding of what the code is doing.
If that doesn't help, feel free to follow me on Twitter, @neocotic.