/*!
* 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<String>} 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 "";
}
})();