/** * Babel JavaScript Support * * Copyright (C) 2008 Edgewall Software * All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://babel.edgewall.org/wiki/License. * * This software consists of voluntary contributions made by many * individuals. For the exact contribution history, see the revision * history and logs, available at http://babel.edgewall.org/log/. */ /** * A simple module that provides a gettext like translation interface. * The catalog passed to load() must be a object conforming to this * interface:: * * { * messages: an object of {msgid: translations} items where * translations is an array of messages or a single * string if the message is not pluralizable. * plural_expr: the plural expression for the language. * locale: the identifier for this locale. * domain: the name of the domain. * } * * Missing elements in the object are ignored. * * Typical usage:: * * var translations = babel.Translations.load(...).install(); */ var babel = new function() { var defaultPluralExpr = function(n) { return n == 1 ? 0 : 1; }; var formatRegex = /%(?:(?:\(([^\)]+)\))?([disr])|%)/g; var translations = {}; var merged; /** * A translations object implementing the gettext interface */ var Translations = this.Translations = function(locale, domain) { this.messages = {}; this.locale = locale || 'unknown'; this.domain = domain || 'messages'; this.pluralexpr = defaultPluralExpr; }; /** * Create a new translations object from the catalog and return it. * See the babel-module comment for more details. */ Translations.load = function(catalog) { var rv = new Translations(); rv.load(catalog); translations[rv.domain] = rv; merged.load(catalog); return rv; }; /** * Get a Translations instance from the loaded translations. If the * specified domain doesn't exist, returns a dummy Translations * instance. */ Translations.get = function(domain) { return translations[domain] || (new Translations({domain: domain})); }; Translations.prototype = { /** * translate a single string * * If extra parameters are given, use them to fill the format * specified by the string. */ gettext: function(string) { var translated = this.messages[string]; if (typeof translated == 'undefined') translated = string; else if (typeof translated != 'string') translated = translated[0]; if (arguments.length > 1) { arguments[0] = translated; return babel.format.apply(this, arguments); } return translated; }, /** * translate a pluralizable string * * If extra parameters are given, use them to fill the format * specified by the string. */ ngettext: function(singular, plural, n) { var translated = this.messages[singular]; if (typeof translated == 'undefined') translated = (n == 1) ? singular : plural; else translated = translated[this.pluralexpr(n)]; if (arguments.length > 3) { var format_args = Array.prototype.slice.call(arguments, 3); format_args.unshift(translated); return babel.format.apply(this, format_args) } return translated; }, /** * Install this translation document wide. After this call, there are * three new methods on the window object: _, gettext and ngettext */ install: function() { window._ = window.gettext = function() { return merged.gettext.apply(merged, arguments); }; window.ngettext = function(singular, plural, n) { return merged.ngettext.apply(merged, arguments); }; return this; }, /** * Works like Translations.load but updates the instance rather * then creating a new one. */ load: function(catalog) { if (catalog.messages) this.update(catalog.messages) if (catalog.plural_expr) this.setPluralExpr(catalog.plural_expr); if (catalog.locale) this.locale = catalog.locale; if (catalog.domain) this.domain = catalog.domain; return this; }, /** * Updates the translations with the object of messages. */ update: function(mapping) { for (var key in mapping) if (mapping.hasOwnProperty(key)) this.messages[key] = mapping[key]; return this; }, /** * Sets the plural expression */ setPluralExpr: function(expr) { this.pluralexpr = new Function('n', 'return +(' + expr + ')'); return this; } }; merged = new Translations({}); /** * Translate a single string in the specified domain. * * If extra parameters are given, use them to fill the format * specified by the string. */ window.dgettext = this.dgettext = function(domain, string) { var rv = translations[domain]; var args = Array.prototype.slice.call(arguments, 1); if (typeof rv != 'undefined') return rv.gettext.apply(rv, args); if (arguments.length > 1) return babel.format.apply(this, args); return string; }; /** * Translate a pluralizable string in the specified domain. * * If extra parameters are given, use them to fill the format * specified by the string. */ window.dngettext = this.dngettext = function(domain, singular, plural, n) { var rv = translations[domain]; if (typeof rv != 'undefined') { var args = Array.prototype.slice.call(arguments, 1); return rv.ngettext.apply(rv, args); } if (arguments.length > 4) { var args = Array.prototype.slice.call(arguments, 4); args.unshift(singular); return babel.format.apply(this, format_args) } return (n == 1) ? singular : plural; }; /** * A python inspired string formatting function. Supports named and * positional placeholders and "s", "d" and "i" as type characters * without any formatting specifications. * * Examples:: * * babel.format(_('Hello %s'), name) * babel.format(_('Progress: %(percent)s%%'), {percent: 100}) */ this.format = function() { var arg, string = arguments[0], idx = 0; if (arguments.length == 1) return string; else if (arguments.length == 2 && typeof arguments[1] == 'object') arg = arguments[1]; else { arg = []; for (var i = 1, n = arguments.length; i != n; ++i) arg[i - 1] = arguments[i]; } return string.replace(formatRegex, function(all, name, type) { if (all == '%%') return '%'; var value = arg[name || idx++]; return (type == 'i' || type == 'd') ? +value : value; }); } };