LnkWcb Framework

LnkWcb  2.5.0

LnkWcb Framework > LnkWcb > intl.js (source view)
Search:
 
Filters
/*!
 * LNK WCB Framework v2.5
 * (c) 2010 Linkeo.com
 * Use and redistribution are permitted. No warranty.
 * 
 * Internationalization utilities
 */

var LnkLog, LnkWcb; // if already defined, log won't get clobbered just defining it
LnkLog = LnkLog || {};
LnkLog.log = LnkLog.log || function () {}; // if not defined, use a safe default value instead
LnkWcb = LnkWcb || {};

/**
 * @module LnkWcb
 */
/**
 * Internationalization utilities
 * @class intl
 * @namespace LnkWcb
 */
LnkWcb.intl = LnkWcb.intl || {};

(function () {
	var INTL = LnkWcb.intl,
		languages = {},
		resources = {},
		translators = {},
		globalLang = '',
		defaultEvents = [
			/**
			 * Fires when a the global language is changed, after any translators have been applied.
			 * @event onLangChanged
			 * @param lang {String} the new global language setting
			 * @protected
			 */
			'onLangChanged'
		];

	/**
	 * Execute translators for the current global language.
	 * @method applyTranslators
	 * @private
	 */
	function applyTranslators() {
		var ident, tr, i;
		for (ident in translators) {
			tr = translators[ident] && translators[ident][globalLang];
			if (tr) {
				for (i = 0; i < tr.length; ++i) {
					if (typeof tr[i] === 'function') {
						tr[i]();
					}
				}
			}
		}
	};

	INTL.events = LnkWcb.events.create(INTL, defaultEvents);
	LnkWcb.events.register(INTL, defaultEvents);

	/**
	 * Register a resource.
	 * @method setRsc
	 * @param ident {String} an identifier for the resource
	 * @param lang {String} the BCP-47 language tag of the resource content
	 * @param rsc {Object} the resource to register
	 * @protected
	 * @static
	 */
	INTL.setRsc = function (ident, lang, rsc) {
		resources[ident] = resources[ident] || {};
		resources[ident][lang] = rsc;
		languages[lang] = true;
	};

	/**
	 * Register a translator function.
	 * @method addTranslator
	 * @param ident {String} an identifier for the translator
	 * @param lang {String} the BCP-47 language tag of the translator
	 * @param translator {Function} the translator
	 * @protected
	 * @static
	 */
	INTL.addTranslator = function (ident, lang, translator) {
		var moduleTransltrs, transltrs;
		moduleTransltrs = translators[ident] = translators[ident] || {};
		transltrs = moduleTransltrs[lang] = moduleTransltrs[lang] || [];
		transltrs.push(translator);
	};

	/**
	 * Get the current global language setting.
	 * @method getLang
	 * @return {String} the current global language
	 * @protected
	 * @static
	 */
	INTL.getLang = function () {
		return globalLang;
	};

	/**
	 * Set the global language setting to a new value.
	 * @method setLang
	 * @param lang {String} the new global language
	 * @public
	 * @static
	 */
	INTL.setLang = function (lang) {
		if (lang !== globalLang) {
			globalLang = lang;
			applyTranslators();
			INTL.events.onLangChanged.fire(lang);
		}
	};

	/**
	 * Obtain a resource.
	 * @method getRsc
	 * @param ident {String} an identifier for the resource
	 * @param lang {String} the optional BCP-47 language tag of the resource, defaulting to the global language set
	 * @protected
	 * @static
	 */
	INTL.getRsc = function (ident, lang) {
		var r = resources[ident];
		return r ? r[lang !== undefined ? lang : globalLang] : null;
	};

	/**
	 * Expands a format string with parameters values.
	 * <p>
	 * For example, if <code>{foobar}</code> appears in the format string, then it is replaced by the value of <code>attrs.foobar</code>.
	 * Whenever <code>{foobar}</code> appears twice, only the <u>first</u> occurrence is expanded.
	 * <p>
	 * The method accepts as many <code>attrs</code> parameters as you like.
	 * They will apply in order, so that you can enforce priorities in the expansion process.
	 * This is very useful when enforcing cascaded substitutions.
	 * For example, <code>fmt("oh-{foo}", { foo: "{bar}" }, { bar: "yeah!" })</code> will produce <code>"oh-yeah!"</code>.
	 * @method fmt
	 * @param str {String} the format string
	 * @param attrs {Object&lt;String&gt;} an optional object of attributes to substitute
	 * @public
	 * @static
	 */
	INTL.fmt = function (str /*, attrs...*/) {
		var i, name, val,
			res = str;
		if (str) {
			for (i = 1; i < arguments.length; ++i) {
				attrs = arguments[i];
				for (name in attrs) {
					val = attrs[name];
					res = res.replace('{' + name + '}', val);
				}
			}
		}
		return res;
	};

	/**
	 * Look for the best matching BCP-47 language.
	 * <p>
	 * For example, with preferred languages being <code>"fr-FR,en,de-DE"</code> the lokup will try in order:
	 * <code>fr-FR</code>, <code>fr</code>, <code>en</code>, <code>de-DE</code>, and finally <code>de</code>.
	 * An empty string is returned if the match fails, i.e. if none of these values appear in available languages.
	 * <p>
	 * The set of preferred languages might be a string of comma separated values, or an array.
	 * Languages appearing first are preferred over those appearing last. <code>*</code> wildcard value is ignored.
	 * <p>
	 * When <code>availableLanguages</code> is not specified, the set of all ever languages is used.
	 * These are any languages that have some resources registered for any identifier.
	 * @method lookupBestLang
	 * @param preferredLanguages {Array|String} a set of preferred languages
	 * @param availableLanguages {Array} an optional set of available languages
	 * @private
	 * @static
	 */
	INTL.lookupBestLang = function (preferredLanguages /*, availableLanguages*/) { // the YUI3 way...
		var i, language, result, index;

		availableLanguages = availableLanguages || LnkWcb.util.keySet(languages); // ... plus global language list support

		// check whether the list of available languages contains language; if so return it
		function scan(language) {
			var i;
			for (i = 0; i < availableLanguages.length; i += 1) {
				if (language.toLowerCase() === availableLanguages[i].toLowerCase()) {
					return availableLanguages[i];
				}
			}
		}

		if (typeof preferredLanguages === 'string') {
			preferredLanguages = preferredLanguages.split(/[, ]/);
		}

		for (i = 0; i < preferredLanguages.length; i += 1) {
			language = preferredLanguages[i];
			if (!language || language === "*") {
				continue;
			}
			// check the fallback sequence for one language
			while (language.length > 0) {
				result = scan(language);
				if (result) {
					return result;
				} else {
					index = language.lastIndexOf("-");
					if (index >= 0) {
						language = language.substring(0, index);
						// one-character subtags get cut along with the following subtag
						if (index >= 2 && language.charAt(index - 2) === "-") {
							language = language.substring(0, index - 2);
						}
					} else {
						// nothing available for this language
						break;
					}
				}
			}
		}

		return "";
	}
})();

Copyright © 2010 Linkeo.com All rights reserved.