Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Replace node-gettext with using JSON files. Fixes #12

* Adds Gobbledygook Fixes #9
* Brings code closer to browserid's changes to i18n.js
* Replaces `locale_directory` with `translation_directory` in options
* Loses ability to do ngettext :(
  • Loading branch information...
commit f15b96a6215d0ec4350eedb407e89fc4cfcc5509 1 parent 2bec592
Austin King ozten authored
Showing with 222 additions and 131 deletions.
  1. +6 −3 README.md
  2. +11 −7 docs/USAGE.md
  3. +2 −3 examples/express2/app.js
  4. +2 −2 examples/express2/i18n/db_LB/messages.json
  5. +2 −2 examples/express2/i18n/de/messages.json
  6. +1 −1  examples/express2/i18n/en_US/messages.json
  7. +2 −2 examples/express2/i18n/es/messages.json
  8. BIN  examples/express2/locale/db_LB/LC_MESSAGES/messages.mo
  9. BIN  examples/express2/locale/de/LC_MESSAGES/messages.mo
  10. BIN  examples/express2/locale/en_US/LC_MESSAGES/messages.mo
  11. BIN  examples/express2/locale/es/LC_MESSAGES/messages.mo
  12. +2 −2 examples/express3/app.js
  13. +12 −0 examples/express3/i18n/db_LB/messages.json
  14. +12 −0 examples/express3/i18n/de/messages.json
  15. +12 −0 examples/express3/i18n/en_US/messages.json
  16. +12 −0 examples/express3/i18n/es/messages.json
  17. BIN  examples/express3/locale/db_LB/LC_MESSAGES/messages.mo
  18. BIN  examples/express3/locale/de/LC_MESSAGES/messages.mo
  19. BIN  examples/express3/locale/en_US/LC_MESSAGES/messages.mo
  20. BIN  examples/express3/locale/es/LC_MESSAGES/messages.mo
  21. +22 −0 examples/express3/scripts/extract-json-only.sh
  22. +97 −91 lib/i18n.js
  23. +1 −1  package.json
  24. +26 −17 tests/i18n-tests.js
9 README.md
View
@@ -27,7 +27,7 @@ In your app where you setup express or connect:
supported_languages: ['en-US', 'de', 'es', 'db-LB', 'it-CH'],
default_lang: 'en-US',
debug_lang: 'it-CH',
- locale_directory: 'locale'
+ translation_directory: 'i18n'
}));
This block sets up the middleware and views with gettext support. We declare
@@ -35,7 +35,7 @@ support for English, German, Spanish, and two debug locales (more on this later)
In your routes, you can use the gettext function in ``.js`` files.
- exports.homepage = function (req, resp) {
+ exports.homepage = function(req, resp) {
resp.render('home', {title: req.gettext("Hey, careful, man, there's a beverage here!")});
};
@@ -53,7 +53,10 @@ In your templates files, you can use the gettext function in ``.ejs`` files:
i18n-abide also provides a ``format`` function for string interpolation.
-These are both server side translations with ``node-gettext``. If you also want to do client-side translations,
+These are both server side translations and client side translations. Server side works out of the box
+and is the most common use case.
+
+If you also want to do client-side translations,
i18n-abide provides ``lib/gettext.js`` and you can do the same in ``.js`` and ``.ejs`` files.
## Setup Gettext
18 docs/USAGE.md
View
@@ -11,7 +11,7 @@ Any copy, label, or error message that will be shown to users **should** be wrap
These strings must be evaluated in the scope of a request, so we know which locale the user has.
-In JavaScript or EJS templates use `gettext` or `ngettext`. If you need to do string interpolation, use the
+In JavaScript or EJS templates use `gettext`. If you need to do string interpolation, use the
[format](../lib/i18n.js) function, which is kind of like node.js' util.format, except crappier.
Using `_` is more idiomatic, but conflicts with `underscore.js` on the client side JS and EJS files.
@@ -26,7 +26,7 @@ The request object and the response's template context have the following variab
* lang - User's preferred language
* lang_dir - rtl or ltr (BIDI language support)
* locale - OS level locale code
- * gettext, ngettext - Gettext functions
+ * gettext - Gettext function
* format - for string interpolation
## Tools
@@ -46,16 +46,20 @@ config file which you'll like to validate. Examples:
## Debugging
-If code is evaluated in node.js (server-side) then node-gettext is providing the string
-translation. Strings are from the messages.mo files under the locale directory. MO files
-are binary, compiled from the PO files.
+If code is evaluated in node.js (server-side) then translation is provided from the
+`i18n/{locale}/messages.json` file which contains the translation. These JSON files come
+from PO files.
-If code is evaluated on the client-side, then resources/static/shared/gettext.js is in
-the house... strings are from resources/static/i18n JSON files.
+If code is evaluated on the client-side, then `static/gettext.js` is in
+the house... strings are `i18n/{locale}/client.json` files.
If code is evaluated in your head, then clearly we are post-singularity. Why are you
still using gettext?
+You can change `i18n` in the above to examples by setting the `translation_directory` in your
+options to i18n-abide. If you do server side and client side strings, it is recommended that you
+put your translation_directory under a web accessible directory like `static`.
+
Use the eo locale for development and debugging. It is auto-translated with:
for catalog in messages client; do
echo "Translating ${catalog}.po"
5 examples/express2/app.js
View
@@ -18,7 +18,7 @@ app.configure(function(){
supported_languages: ['en-US', 'de', 'es', 'db-LB', 'it-CH'],
default_lang: 'en-US',
debug_lang: 'it-CH',
- locale_directory: 'locale'
+ translation_directory: 'i18n'
}));
app.use(express.bodyParser());
app.use(express.methodOverride());
@@ -37,7 +37,6 @@ app.configure('production', function(){
// Routes
app.get('/', routes.index);
-
-app.listen(3001, function(){
+app.listen(process.env.PORT || 3000, function(){
console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
});
4 examples/express2/i18n/db_LB/messages.json
View
@@ -1,11 +1,11 @@
;var json_locale_data = {
"messages": {
"": {
- "Project-Id-Version": " express 2\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-06-24 09:50+0200\nLast-Translator: Automatically generated\nLanguage-Team: none\nLanguage: db_LB\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n"
+ "Project-Id-Version": " PACKAGE VERSION\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\nLast-Translator: FULL NAME <EMAIL@ADDRESS>\nLanguage-Team: LANGUAGE <LL@li.org>\nLanguage: \nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n"
},
"Hello, world!": [
null,
- ""
+ "‮Hǝʅʅo´ ʍoɹʅp¡"
]
}
}
4 examples/express2/i18n/de/messages.json
View
@@ -1,11 +1,11 @@
;var json_locale_data = {
"messages": {
"": {
- "Project-Id-Version": " express 2\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-06-24 09:50+0200\nLast-Translator: Automatically generated\nLanguage-Team: none\nLanguage: de\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n != 1);\n"
+ "Project-Id-Version": " express 2\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-12-14 10:48-0800\nLast-Translator: Austin King <shout@ozten.com>\nLanguage-Team: German\nLanguage: de\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n != 1);\n"
},
"Hello, world!": [
null,
- ""
+ "Hallo Welt"
]
}
}
2  examples/express2/i18n/en_US/messages.json
View
@@ -1,7 +1,7 @@
;var json_locale_data = {
"messages": {
"": {
- "Project-Id-Version": " express 2\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-06-24 09:50+0200\nLast-Translator: Automatically generated\nLanguage-Team: none\nLanguage: en_US\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n != 1);\n"
+ "Project-Id-Version": " express 2\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-12-14 10:49-0800\nLast-Translator: Austin King <shout@ozten.com>\nLanguage-Team: English\nLanguage: en_US\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n != 1);\n"
},
"Hello, world!": [
null,
4 examples/express2/i18n/es/messages.json
View
@@ -1,11 +1,11 @@
;var json_locale_data = {
"messages": {
"": {
- "Project-Id-Version": " express 2\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-06-24 09:50+0200\nLast-Translator: Automatically generated\nLanguage-Team: none\nLanguage: es\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n != 1);\n"
+ "Project-Id-Version": " express 2\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-12-14 10:49-0800\nLast-Translator: Austin King <shout@ozten.com>\nLanguage-Team: Spanish\nLanguage: es\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n != 1);\n"
},
"Hello, world!": [
null,
- ""
+ "¡Hola mundo!"
]
}
}
BIN  examples/express2/locale/db_LB/LC_MESSAGES/messages.mo
View
Binary file not shown
BIN  examples/express2/locale/de/LC_MESSAGES/messages.mo
View
Binary file not shown
BIN  examples/express2/locale/en_US/LC_MESSAGES/messages.mo
View
Binary file not shown
BIN  examples/express2/locale/es/LC_MESSAGES/messages.mo
View
Binary file not shown
4 examples/express3/app.js
View
@@ -13,14 +13,14 @@ var express = require('express'),
var app = express();
app.configure(function(){
- app.set('port', process.env.PORT || 3000);
+ app.set('port', process.env.PORT || 3001);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(i18n.abide({
supported_languages: ['en-US', 'de', 'es', 'db-LB', 'it-CH'],
default_lang: 'en-US',
debug_lang: 'it-CH',
- locale_directory: 'locale'
+ translation_directory: 'i18n'
}));
app.use(express.favicon());
app.use(express.logger('dev'));
12 examples/express3/i18n/db_LB/messages.json
View
@@ -0,0 +1,12 @@
+;var json_locale_data = {
+ "messages": {
+ "": {
+ "Project-Id-Version": " express 3\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-06-24 09:50+0200\nLast-Translator: Automatically generated\nLanguage-Team: none\nLanguage: db_LB\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n"
+ },
+ "Hello, world!": [
+ null,
+ ""
+ ]
+ }
+}
+;
12 examples/express3/i18n/de/messages.json
View
@@ -0,0 +1,12 @@
+;var json_locale_data = {
+ "messages": {
+ "": {
+ "Project-Id-Version": " express 3\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-06-24 09:50+0200\nLast-Translator: Automatically generated\nLanguage-Team: none\nLanguage: de\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n != 1);\n"
+ },
+ "Hello, world!": [
+ null,
+ ""
+ ]
+ }
+}
+;
12 examples/express3/i18n/en_US/messages.json
View
@@ -0,0 +1,12 @@
+;var json_locale_data = {
+ "messages": {
+ "": {
+ "Project-Id-Version": " express 3\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-06-24 09:50+0200\nLast-Translator: Automatically generated\nLanguage-Team: none\nLanguage: en_US\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n != 1);\n"
+ },
+ "Hello, world!": [
+ null,
+ "Hello, world!"
+ ]
+ }
+}
+;
12 examples/express3/i18n/es/messages.json
View
@@ -0,0 +1,12 @@
+;var json_locale_data = {
+ "messages": {
+ "": {
+ "Project-Id-Version": " express 3\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-06-24 09:50+0200\nPO-Revision-Date: 2012-06-24 09:50+0200\nLast-Translator: Automatically generated\nLanguage-Team: none\nLanguage: es\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n != 1);\n"
+ },
+ "Hello, world!": [
+ null,
+ ""
+ ]
+ }
+}
+;
BIN  examples/express3/locale/db_LB/LC_MESSAGES/messages.mo
View
Binary file not shown
BIN  examples/express3/locale/de/LC_MESSAGES/messages.mo
View
Binary file not shown
BIN  examples/express3/locale/en_US/LC_MESSAGES/messages.mo
View
Binary file not shown
BIN  examples/express3/locale/es/LC_MESSAGES/messages.mo
View
Binary file not shown
22 examples/express3/scripts/extract-json-only.sh
View
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+echo "Extracting strings"
+mkdir -p locale/templates/LC_MESSAGES
+./node_modules/i18n-abide/bin/extract-pot -l ./locale .
+
+# Create PO files
+for locale in en_US es de db_LB; do
+ echo "Creating ${locale} language files"
+ mkdir -p locale/${locale}/LC_MESSAGES
+ mkdir -p i18n/${locale}
+ msginit --input=./locale/templates/LC_MESSAGES/messages.pot \
+ --output-file=./locale/${locale}/LC_MESSAGES/messages.po \
+ -l ${locale} --no-translator
+ echo "You can translate i18n/${locale}/messages.json"
+done
+
+# Convert PO files to JSON
+./node_modules/i18n-abide/bin/compile-json.sh locale i18n
+
+# Make current version of the code happy
+./node_modules/i18n-abide/bin/compile-mo.sh locale
188 lib/i18n.js
View
@@ -15,14 +15,19 @@
* See docs/I18N.md for details.
*/
-var Gettext = require('node-gettext'),
- fs = require('fs'),
+var fs = require('fs'),
+ gobbledygook = require('gobbledygook'),
path = require('path'),
util = require('util');
-const BIDI_RTL_LANGS = ['ar', 'db-LB', 'fa', 'he'];
+const DAVID_B_LABYRN = 'db-LB';
+const BIDI_RTL_LANGS = ['ar', DAVID_B_LABYRN, 'fa', 'he'];
-var mo_cache = {};
+var translations = {};
+var logger;
+
+// forward references
+var localeFrom, parseAcceptLanguage, bestLanguage, format;
/**
* Connect middleware which is i18n aware.
@@ -31,65 +36,65 @@ var mo_cache = {};
app.use(i18n.abide({
supported_languages: ['en-US', 'fr', 'pl'],
default_lang: 'en-US',
- locale_directory: 'locale'
+ translation_directory: 'locale'
}));
*
- * Other valid options: gettext_alias, ngettext_alias
+ * Other valid options: gettext_alias, debug_lang, disable_locale_check,
+ * or logger
*/
-exports.abide = function (options) {
+exports.abide = function(options) {
options = options || {};
- if (! options.gettext_alias) options.gettext_alias = 'gettext';
- if (! options.ngettext_alias) options.ngettext_alias = 'ngettext';
- if (! options.supported_languages) options.supported_languages = ['en-US'];
- if (! options.default_lang) options.default_lang = 'en-US';
- if (! options.debug_lang) options.debug_lang = 'it-CH';
- if (! options.disable_locale_check) options.disable_locale_check = false;
- if (! options.locale_directory) options.locale_directory = 'locale';
- if (! options.i18n_json_dir) options.i18n_json_dir = 'resources/static/i18n/';
- if (! options.logger) options.logger = console;
+ if (! options.gettext_alias) options.gettext_alias = 'gettext';
+ if (! options.supported_languages) options.supported_languages = ['en-US'];
+ if (! options.default_lang) options.default_lang = 'en-US';
+ if (! options.debug_lang) options.debug_lang = 'it-CH';
+ if (! options.disable_locale_check) options.disable_locale_check = false;
+ if (! options.translation_directory) options.translation_directory = 'l18n/';
+ if (! options.logger) options.logger = console;
- var exists = fs.existsSync || path.existsSync;
+ var existsSync = fs.existsSync || path.existsSync;
- var logger = options.logger;
+ logger = options.logger;
- var mo_file_path = function (locale) {
+ var json_file_path = function(locale) {
return path.resolve(
path.join(__dirname, '..', '..', '..'),
- options.locale_directory,
- path.join(locale, 'LC_MESSAGES', 'messages.mo'));
+ options.translation_directory,
+ path.join(locale, 'messages.json'));
}, /* TODO This would be a good check if we're using client side gettext
- json_file_path = function (locale) {
+ json_file_path = function(locale) {
return path.resolve(
path.join(__dirname, '..', '..', '..'),
path.join(options.i18n_json_dir, locale, 'messages.json'));
}, */
debug_locale = localeFrom(options.debug_lang);
- options.supported_languages.forEach(function (lang, i) {
- var l = localeFrom(lang),
- default_locale = localeFrom(options.default_lang);
- console.log(l, mo_file_path(l), exists(mo_file_path(l)));
- mo_cache[l] = {
- mo_exists: exists(mo_file_path(l)),
- /* json_exists: path.exists(json_file_path(l)), */
- gt: null
- };
- if (l !== debug_locale) {
- if (! mo_cache[l] || ! mo_cache[l].mo_exists /* || ! mo_cache[l].json_exists*/ ) {
+ options.supported_languages.forEach(function(lang, i) {
+ var l = localeFrom(lang);
- console.log('mo_cache?', mo_cache[l], ' for ', l);
- if (mo_cache[l]) console.log('mo_exists?', mo_cache[l].mo_exists);
- var msg = util.format('Bad locale=[%s] file(s) do not exist [%s]. See locale/README',
- l, mo_file_path(l)/*, json_file_path(l)*/);
+ try {
+ // populate the in-memory translation cache with client.json, which contains
+ // strings relevant on the server
- console.log(l, default_locale);
- if (/* mo_cache[l].json_exists && */ l == default_locale) {
- // mo files aren't critical... carry on
- if (! options.disable_locale_check) logger.warn(msg);
- } else {
- logger.error(msg);
- throw msg;
- }
+ // XXX: these files should be json. not javascript.
+ /*jshint evil:true*/
+ var json_locale_data; // for jshint
+
+ // Yikes (copying from BrowserID codebase, not my gitblame)
+ eval(fs.readFileSync(json_file_path(l)).toString());
+ translations[l] = json_locale_data.messages;
+ } catch (e) {
+ // an exception here means that there was a problem with the translation files for
+ // this locale!
+ if (options.default_lang === lang || options.debug_lang === lang) return;
+
+ var msg = util.format('Bad locale=[%s] missing .json files in [%s]. See locale/README (%s)',
+ l, json_file_path(l), e);
+ if (!options.disable_locale_check) {
+ logger.warn(msg);
+ } else {
+ logger.error(msg);
+ throw msg;
}
}
});
@@ -100,25 +105,26 @@ exports.abide = function (options) {
lang = bestLanguage(langs, options.supported_languages,
options.default_lang),
debug_lang = options.debug_lang.toLowerCase(),
- locale;
+ locale,
+ locals = {},
+ gt;
- if (lang && lang.toLowerCase && lang.toLowerCase() == debug_lang) {
- lang = 'db-LB'; // What? http://www.youtube.com/watch?v=rJLnGjhPT1Q
+ if (lang && lang.toLowerCase && lang.toLowerCase() === debug_lang) {
+ lang = DAVID_B_LABYRN; // What? http://www.youtube.com/watch?v=rJLnGjhPT1Q
}
// Express 2 support
if (!! resp.local) {
- resp.locals = function (args, orValue) {
- if ('string' === typeof args) {
- resp.local(args, orValue);
- } else {
- Object.keys(args).forEach(function (key, i) {
- resp.local(key, args[key]);
- });
- }
+ resp.locals = function(args, orValue) {
+ if ('string' === typeof args) {
+ resp.local(args, orValue);
+ } else {
+ Object.keys(args).forEach(function(key, i) {
+ resp.local(key, args[key]);
+ });
+ }
};
}
- var locals = {};
locals.lang = lang;
// BIDI support, which direction does text flow?
@@ -134,43 +140,39 @@ exports.abide = function (options) {
locals.format = format;
req.format = format;
- if (mo_cache[locale].mo_exists) {
- if (mo_cache[locale].gt === null) {
- mo_cache[locale].gt = new Gettext();
- mo_path = mo_file_path(locale);
- mo_cache[locale].gt.addTextdomain(locale,
- fs.readFileSync(mo_path));
- mo_cache[locale].gt.textdomain(locale);
- }
- var gt = mo_cache[locale].gt;
- locals[options.gettext_alias] = gt.gettext.bind(gt);
- req.gettext = gt.gettext.bind(gt);
- locals[options.ngettext_alias] = gt.ngettext.bind(gt);
- req.ngettext = gt.ngettext.bind(gt);
+ if (lang.toLowerCase() === DAVID_B_LABYRN.toLowerCase()) {
+ gt = gobbledygook;
+ locals.lang = DAVID_B_LABYRN;
+ } else if (translations[locale]) {
+ gt = function(sid) {
+ if (translations[locale][sid] && translations[locale][sid][1].length) {
+ sid = translations[locale][sid][1];
+ }
+ return sid;
+ };
} else {
- // en-US in a non gettext environment... fake it
- var identity = function (a, b) { return a; };
- locals[options.gettext_alias] = identity;
- req.gettext = identity;
- locals[options.ngettext_alias] = identity;
- req.ngettext = identity;
+ // default lang in a non gettext environment... fake it
+ gt = function(a, b) { return a; };
}
+ locals[options.gettext_alias] = gt;
+ req.gettext = gt;
// resp.locals(string, value) doesn't seem to work with EJS
resp.locals(locals);
+
next();
};
};
function qualityCmp(a, b) {
- if (a.quality == b.quality) {
+ if (a.quality === b.quality) {
return 0;
} else if (a.quality < b.quality) {
return 1;
} else {
return -1;
}
-};
+}
/**
* Parses the HTTP accept-language header and returns a
@@ -179,17 +181,17 @@ function qualityCmp(a, b) {
* lang: 'pl', quality: 0.7
* }
*/
-exports.parseAcceptLanguage = parseAcceptLanguage = function (header) {
+exports.parseAcceptLanguage = parseAcceptLanguage = function(header) {
// pl,fr-FR;q=0.3,en-US;q=0.1
if (! header || ! header.split) {
return [];
}
var raw_langs = header.split(',');
- var langs = raw_langs.map(function (raw_lang) {
+ var langs = raw_langs.map(function(raw_lang) {
var parts = raw_lang.split(';');
var q = 1;
- if (parts.length > 1 && parts[1].indexOf('q=') == 0) {
- qval = parseFloat(parts[1].split('=')[1]);
+ if (parts.length > 1 && parts[1].indexOf('q=') === 0) {
+ var qval = parseFloat(parts[1].split('=')[1]);
if (isNaN(qval) === false) {
q = qval;
}
@@ -206,7 +208,7 @@ exports.parseAcceptLanguage = parseAcceptLanguage = function (header) {
//
// languages must be a sorted list, the first match is returned.
exports.bestLanguage = bestLanguage = function(languages, supported_languages, defaultLanguage) {
- var lower = supported_languages.map(function (l) { return l.toLowerCase(); });
+ var lower = supported_languages.map(function(l) { return l.toLowerCase(); });
for(var i=0; i < languages.length; i++) {
var lq = languages[i];
if (lower.indexOf(lq.lang.toLowerCase()) !== -1) {
@@ -225,7 +227,7 @@ exports.bestLanguage = bestLanguage = function(languages, supported_languages, d
* language: en-US
* locale: en_US
*/
-exports.localeFrom = localeFrom = function (language) {
+exports.localeFrom = localeFrom = function(language) {
if (! language || ! language.split) {
return "";
}
@@ -246,7 +248,7 @@ exports.localeFrom = localeFrom = function (language) {
/**
* Given a locale code, return a language code
*/
-exports.languageFrom = function (locale) {
+exports.languageFrom = function(locale) {
if (!locale || !locale.split) {
return "";
}
@@ -262,7 +264,7 @@ exports.languageFrom = function (locale) {
logger.error(util.format("Unable to map a language from locale code [%s]", locale));
return locale;
}
-}
+};
/**
* format provides string interpolation on the client and server side.
@@ -274,11 +276,15 @@ exports.languageFrom = function (locale) {
* Positional Example:
* format("%s %s", ["Hello", "World"]);
*/
-exports.format = format = function (fmt, obj, named) {
- if (! fmt) return "";
- if (named) {
- return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
+exports.format = format = function(fmt, obj, named) {
+ if (!fmt) return "";
+ if (Array.isArray(obj) || named === false) {
+ return fmt.replace(/%s/g, function(){return String(obj.shift());});
+ } else if (typeof obj === 'object' || named === true) {
+ return fmt.replace(/%\(\s*([^)]+)\s*\)s/g, function(m, v){
+ return String(obj[v.trim()]);
+ });
} else {
- return fmt.replace(/%s/g, function(match){return String(obj.shift())});
+ return fmt;
}
};
2  package.json
View
@@ -17,8 +17,8 @@
},
"dependencies": {
"async": "0.1.22",
+ "gobbledygook": "0.0.3",
"jsxgettext": "0.0.4",
- "node-gettext": "0.2.8",
"optimist": "0.3.4"
},
"devDependencies": {
43 tests/i18n-tests.js
View
@@ -12,10 +12,10 @@ var suite = vows.describe('i18n');
suite.addBatch({
"format a string with place values": {
- topic: function () {
+ topic: function() {
return i18n.format("%s %s!", ["Hello", "World"]);
},
- "was interpolated": function (err, str) {
+ "was interpolated": function(err, str) {
assert.equal(str, "Hello World!");
}
}
@@ -23,11 +23,20 @@ suite.addBatch({
suite.addBatch({
"format a string with named values": {
- topic: function () {
+ topic: function() {
var params = { salutation: "Hello", place: "World" };
return i18n.format("%(salutation)s %(place)s!", params, true);
},
- "was interpolated": function (err, str) {
+ "was interpolated": function(err, str) {
+ assert.equal(str, "Hello World!");
+ }
+ },
+ "named values can have spaces": {
+ topic: function() {
+ var params = { salutation: "Hello", place: "World" };
+ return i18n.format("%( salutation )s %( place )s!", params, true);
+ },
+ "was interpolated": function(err, str) {
assert.equal(str, "Hello World!");
}
}
@@ -35,18 +44,18 @@ suite.addBatch({
suite.addBatch({
"format a string without interpolation": {
- topic: function () {
+ topic: function() {
return i18n.format("Hello World!");
},
- "was interpolated": function (err, str) {
+ "was interpolated": function(err, str) {
assert.equal(str, "Hello World!");
}
},
"format a null": {
- topic: function () {
+ topic: function() {
return i18n.format(null);
},
- "was interpolated": function (err, str) {
+ "was interpolated": function(err, str) {
assert.equal(str, "");
}
}
@@ -54,41 +63,41 @@ suite.addBatch({
suite.addBatch({
"We find exact language match": {
- topic: function () {
+ topic: function() {
var accept = 'pa,sv;q=0.8,fi;q=0.7,it-ch;q=0.5,en-us;q=0.3,en;q=0.2';
var supported = ['af', 'en-US', 'pa'];
var def = 'en-US';
return i18n.bestLanguage(
- parseAcceptLanguage(accept),
+ i18n.parseAcceptLanguage(accept),
supported, def);
},
- "For Punjabi": function (err, locale) {
+ "For Punjabi": function(err, locale) {
assert.equal(locale, "pa");
}
},
"Issue#1128 We find best locale even if region doesn't match": {
- topic: function () {
+ topic: function() {
var accept = 'pa-it,sv;q=0.8,fi;q=0.7,it-ch;q=0.5,en-us;q=0.3,en;q=0.2';
var supported = ['af', 'en-US', 'pa'];
var def = 'en-US';
return i18n.bestLanguage(
- parseAcceptLanguage(accept),
+ i18n.parseAcceptLanguage(accept),
supported, def);
},
- "For Punjabi (India) serve Punjabi": function (err, locale) {
+ "For Punjabi (India) serve Punjabi": function(err, locale) {
assert.equal(locale, "pa");
}
},
"We don't extend into a region, unless we have an exact match": {
- topic: function () {
+ topic: function() {
var accept = 'pa,sv;q=0.8,fi;q=0.7,it-ch;q=0.5,en-us;q=0.3,en;q=0.2';
var supported = ['af', 'en-US', 'pa-IT'];
var def = 'en-US';
return i18n.bestLanguage(
- parseAcceptLanguage(accept),
+ i18n.parseAcceptLanguage(accept),
supported, def);
},
- "Don't choose Punjabi (India)": function (err, locale) {
+ "Don't choose Punjabi (India)": function(err, locale) {
assert.equal(locale, "en-us");
}
}
Please sign in to comment.
Something went wrong with that request. Please try again.