2016-01-11 02:25:30 +01:00
|
|
|
/*
|
|
|
|
* Lightweight clientside i18n implementation.
|
|
|
|
* Caution: Only available after DOM loaded because we need to detect the language
|
2016-09-15 09:31:57 +02:00
|
|
|
*
|
2016-01-11 02:25:30 +01:00
|
|
|
* For non-i18n stub implementation, see framework/javascript/src/i18nx.js
|
2016-09-15 09:31:57 +02:00
|
|
|
*
|
2016-01-11 02:25:30 +01:00
|
|
|
* Based on jQuery i18n plugin: 1.0.0 Feb-10-2008
|
2016-09-15 09:31:57 +02:00
|
|
|
*
|
2016-01-11 02:25:30 +01:00
|
|
|
* Dual licensed under the MIT and GPL licenses:
|
|
|
|
* http://www.opensource.org/licenses/mit-license.php
|
|
|
|
* http://www.gnu.org/licenses/gpl.html
|
|
|
|
*
|
|
|
|
* Based on 'javascript i18n that almost doesn't suck' by markos
|
|
|
|
* http://markos.gaivo.net/blog/?p=100
|
|
|
|
*/
|
|
|
|
|
|
|
|
class i18n {
|
|
|
|
constructor() {
|
|
|
|
this.defaultLocale = 'en_US';
|
2016-09-15 09:31:57 +02:00
|
|
|
this.currentLocale = this.detectLocale();
|
2016-01-11 02:25:30 +01:00
|
|
|
this.lang = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set locale in long format, e.g. "de_AT" for Austrian German.
|
|
|
|
*
|
|
|
|
* @param string locale
|
|
|
|
*/
|
|
|
|
setLocale(locale) {
|
|
|
|
this.currentLocale = locale;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get locale in long format. Falls back to i18n.defaut_locale.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
getLocale() {
|
|
|
|
return this.currentLocale !== null ? this.currentLocale : this.defaultLocale;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-09-15 09:31:57 +02:00
|
|
|
* The actual translation function. Looks the given string up in the
|
|
|
|
* dictionary and returns the translation if one exists. If a translation
|
2016-01-11 02:25:30 +01:00
|
|
|
* is not found, returns the original word.
|
|
|
|
*
|
|
|
|
* @param string entity - A "long" locale format, e.g. "de_DE" (Required)
|
|
|
|
* @param string fallbackString - (Required)
|
|
|
|
* @param int priority - (not used)
|
|
|
|
* @param string context - Give translators context for the string
|
|
|
|
* @return string : Translated word
|
|
|
|
*/
|
|
|
|
_t(entity, fallbackString, priority, context) {
|
|
|
|
const langName = this.getLocale().replace(/_[\w]+/i, '');
|
|
|
|
const defaultlangName = this.defaultLocale.replace(/_[\w]+/i, '');
|
|
|
|
|
|
|
|
if (this.lang && this.lang[this.getLocale()] && this.lang[this.getLocale()][entity]) {
|
|
|
|
return this.lang[this.getLocale()][entity];
|
|
|
|
} else if (this.lang && this.lang[langName] && this.lang[langName][entity]) {
|
|
|
|
return this.lang[langName][entity];
|
|
|
|
} else if (this.lang && this.lang[this.defaultLocale] && this.lang[this.defaultLocale][entity]) {
|
|
|
|
return this.lang[this.defaultLocale][entity];
|
|
|
|
} else if (this.lang && this.lang[defaultlangName] && this.lang[defaultlangName][entity]) {
|
2016-09-15 09:31:57 +02:00
|
|
|
return this.lang[defaultlangName][entity];
|
2016-01-11 02:25:30 +01:00
|
|
|
} else if(fallbackString) {
|
|
|
|
return fallbackString;
|
|
|
|
} else {
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add entities to a dictionary. If a dictionary doesn't
|
|
|
|
* exist for this locale, its automatically created.
|
|
|
|
* Existing entities are overwritten.
|
2016-09-15 09:31:57 +02:00
|
|
|
*
|
2016-01-11 02:25:30 +01:00
|
|
|
* @param string locale
|
|
|
|
* @param Object dict
|
|
|
|
*/
|
|
|
|
addDictionary(locale, dict) {
|
|
|
|
if (typeof this.lang[locale] === 'undefined') {
|
|
|
|
this.lang[locale] = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let entity in dict) {
|
|
|
|
this.lang[locale][entity] = dict[entity];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get dictionary for a specific locale.
|
2016-09-15 09:31:57 +02:00
|
|
|
*
|
2016-01-11 02:25:30 +01:00
|
|
|
* @param string locale
|
|
|
|
*/
|
|
|
|
getDictionary(locale) {
|
|
|
|
return this.lang[locale];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string str - The string to strip.
|
|
|
|
* @return string result - Stripped string.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
stripStr(str) {
|
|
|
|
return str.replace(/^\s*/, '').replace(/\s*$/, '');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string str - The multi-line string to strip.
|
|
|
|
* @return string result - Stripped string.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
stripStrML(str) {
|
|
|
|
// Split because m flag doesn't exist before JS1.5 and we need to
|
|
|
|
// strip newlines anyway
|
|
|
|
var parts = str.split('\n');
|
|
|
|
|
|
|
|
for (let i = 0; i < parts.length; i += 1) {
|
|
|
|
parts[i] = stripStr(parts[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't join with empty strings, because it "concats" words
|
|
|
|
// And strip again
|
|
|
|
return stripStr(parts.join(' '));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Substitutes %s with parameters
|
|
|
|
* given in list. %%s is used to escape %s.
|
2016-09-15 09:31:57 +02:00
|
|
|
*
|
2016-01-11 02:25:30 +01:00
|
|
|
* @param string s - The string to perform the substitutions on.
|
|
|
|
* @return string - The new string with substitutions made.
|
|
|
|
*/
|
|
|
|
sprintf(s, ...params) {
|
|
|
|
if (params.length === 0) {
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
const regx = new RegExp('(.?)(%s)', 'g');
|
|
|
|
|
|
|
|
let i = 0;
|
|
|
|
|
|
|
|
return s.replace(regx, function (match, subMatch1, subMatch2, offset, string) {
|
|
|
|
// skip %%s
|
|
|
|
if (subMatch1 === '%') {
|
2016-09-15 09:31:57 +02:00
|
|
|
return match;
|
2016-01-11 02:25:30 +01:00
|
|
|
}
|
|
|
|
|
2016-03-03 00:36:33 +01:00
|
|
|
return subMatch1 + params[i++];
|
2016-01-11 02:25:30 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-09-15 09:31:57 +02:00
|
|
|
* Substitutes variables with a list of injections.
|
|
|
|
*
|
2016-01-11 02:25:30 +01:00
|
|
|
* @param string s - The string to perform the substitutions on.
|
|
|
|
* @param object map - An object with the substitions map e.g. {var: value}.
|
|
|
|
* @return string - The new string with substitutions made.
|
|
|
|
*/
|
|
|
|
inject(s, map) {
|
|
|
|
const regx = new RegExp('\{([A-Za-z0-9_]*)\}', 'g');
|
|
|
|
|
|
|
|
return s.replace(regx, function (match, key, offset, string) {
|
|
|
|
return (map[key]) ? map[key] : match;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Detect document language settings by looking at <meta> tags.
|
|
|
|
* If no match is found, returns this.defaultLocale.
|
2016-09-15 09:31:57 +02:00
|
|
|
*
|
2016-01-11 02:25:30 +01:00
|
|
|
* @todo get by <html lang=''> - needs modification of SSViewer
|
2016-09-15 09:31:57 +02:00
|
|
|
*
|
2016-01-11 02:25:30 +01:00
|
|
|
* @return string - Locale in mixed lowercase/uppercase format suitable
|
|
|
|
* for usage in i18n.lang arrays (e.g. 'en_US').
|
|
|
|
*/
|
|
|
|
detectLocale() {
|
|
|
|
var rawLocale;
|
|
|
|
var detectedLocale;
|
|
|
|
|
|
|
|
// Get by container tag
|
2016-10-05 03:30:19 +02:00
|
|
|
rawLocale = document.body.getAttribute('lang');
|
2016-01-11 02:25:30 +01:00
|
|
|
|
|
|
|
// Get by meta
|
|
|
|
if (!rawLocale) {
|
|
|
|
var metas = document.getElementsByTagName('meta');
|
|
|
|
|
|
|
|
for (var i=0; i<metas.length; i++) {
|
|
|
|
if (metas[i].attributes['http-equiv'] && metas[i].attributes['http-equiv'].nodeValue.toLowerCase() == 'content-language') {
|
|
|
|
rawLocale = metas[i].attributes['content'].nodeValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fallback to default locale
|
|
|
|
if (!rawLocale) {
|
|
|
|
rawLocale = this.defaultLocale;
|
|
|
|
}
|
|
|
|
|
|
|
|
var rawLocaleParts = rawLocale.match(/([^-|_]*)[-|_](.*)/);
|
|
|
|
// Get locale (e.g. 'en_US') from common name (e.g. 'en')
|
|
|
|
// by looking at i18n.lang tables
|
|
|
|
if (rawLocale.length == 2) {
|
|
|
|
for (let compareLocale in i18n.lang) {
|
|
|
|
if (compareLocale.substr(0,2).toLowerCase() == rawLocale.toLowerCase()) {
|
|
|
|
detectedLocale = compareLocale;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (rawLocaleParts) {
|
|
|
|
detectedLocale = rawLocaleParts[1].toLowerCase() + '_' + rawLocaleParts[2].toUpperCase();
|
|
|
|
}
|
|
|
|
|
|
|
|
return detectedLocale;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Attach an event listener to the given object.
|
|
|
|
* Modeled after behaviour.js, but externalized
|
|
|
|
* to keep the i18n library standalone for now.
|
|
|
|
*/
|
|
|
|
addEvent(obj, evType, fn, useCapture) {
|
|
|
|
if (obj.addEventListener) {
|
|
|
|
obj.addEventListener(evType, fn, useCapture);
|
|
|
|
return true;
|
|
|
|
} else if (obj.attachEvent) {
|
|
|
|
return obj.attachEvent('on' + evType, fn);
|
|
|
|
} else {
|
|
|
|
console.log('Handler could not be attached');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let _i18n = new i18n();
|
|
|
|
|
|
|
|
// This module has to support legacy loading...
|
|
|
|
window.ss = typeof window.ss !== 'undefined' ? window.ss : {};
|
|
|
|
window.ss.i18n = window.i18n = _i18n;
|
|
|
|
|
|
|
|
export default _i18n;
|