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