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