/**
* @summary i18n
* @version 3.0.0
* @since 3.0.0
* @author Arian Khosravi<arian.khosravi@aofl.com>
*/
/**
* @memberof module:@aofl/i18n
* @private
* @type {RegExp}
*/
const REPLACE_REGEX = /%%?r(\d+)(?:%|::.*?%%)/g;
/**
* @memberof module:@aofl/i18n
* @private
* @type {RegExp}
*/
const CONDITIONAL_REPLACE_REGEX = /%c(\d+)%/g;
/**
* Provides translation capability.
*
* @memberof module:@aofl/i18n
*/
class I18n {
/**
* Creates an instance of I18n.
*
* @param {Object} [translations={}] - translations map
*/
constructor(translations = {}, lang = 'en-US') {
this.translations = translations;
this.lang = lang;
}
static getDecorateOutput(id, str) {
return window.aoflDevtools && window.aoflDevtools.showI18nIds? `<span id="${id}" class="aofl-i18n-string" title="${id}" style="position: relative;">${str}<span style="font-size: 12px; color: #000; position: absolute; left: 0; bottom: 0; transform: translateY(60%); white-space: pre; background: yellow; line-height: 1; padding: 0 2px; font-weight: 400; z-index: 1;">${id.replace('<', '<').replace('>', '>')}</span></span>`: str;
}
/**
* Lazy-load the translation map
*
* @param {String} lang
* @return {Promise}
*/
getTranslationMap(lang) {
if (typeof this.translations !== 'undefined' && typeof this.translations[lang] === 'function') {
return this.translations[lang]()
.then((langModule) => langModule.default);
}
return Promise.resolve({});
}
/**
* Language translation function.
*
* @param {String} id
* @param {String} str
* @return {String}
*/
async __(id, str) {
const languageMap = await this.getTranslationMap(this.lang);
let out = str;
if (typeof languageMap !== 'undefined' && typeof languageMap[id] === 'object' &&
typeof languageMap[id].text === 'string') {
out = languageMap[id].text;
}
return I18n.getDecorateOutput(id, out);
}
/**
* Replace function. When invoked it will replace %r(number)% with the number matching the index
* of the arguments passed to the _r function.
*
* @param {String} _str
* @param {*} args
* @return {String}
*/
async _r(_str, ...args) {
let str = await Promise.resolve(_str);
/* istanbul ignore next */
if (typeof str === 'object' && Array.isArray(str.strings) && str.strings.length) {
str = str.strings[0];
}
let matches = REPLACE_REGEX.exec(str);
let pivot = 0;
let out = '';
while (matches !== null) {
const match = matches[0];
const argIndex = matches[1] - 1;
const offset = match.length;
out += str.slice(pivot, matches.index) + args[argIndex];
pivot = matches.index + offset;
matches = REPLACE_REGEX.exec(str);
}
out += str.slice(pivot);
return out;
}
/**
* Conditional translation function. When invoked it finds the correct string based on the labels
* specified in ...args.
*
* @param {*} id
* @param {*} str
* @param {*} args
* @return {String}
*/
async _c(id, str, ...args) {
let out = '';
const languageMap = await this.getTranslationMap(this.lang);
/* istanbul ignore else */
if (typeof languageMap !== 'undefined' && typeof languageMap === 'object') {
const idParts = [];
for (let i = 0; i < args.length; i = i + 2) {
const nextI = i + 1;
/* istanbul ignore next */
if (nextI >= args.length) continue;
let key = args[nextI];
if (typeof args[i][key] === 'undefined') {
key = '%other%';
}
idParts.push(key);
}
id += '-' + idParts.join('^^');
if (typeof languageMap[id] === 'object' && typeof languageMap[id].text === 'string') {
str = languageMap[id].text;
}
}
// when language map is not matched we still need to replace %c#% in the default text
let matches = CONDITIONAL_REPLACE_REGEX.exec(str);
let pivot = 0;
while (matches !== null) {
const match = matches[0];
const argIndex = (matches[1] - 1) * 2;
let argCountIndex = args[argIndex + 1];
const offset = match.length;
if (typeof args[argIndex][argCountIndex] === 'undefined') {
argCountIndex = '%other%';
}
out += str.slice(pivot, matches.index) + args[argIndex][argCountIndex];
pivot = matches.index + offset;
matches = CONDITIONAL_REPLACE_REGEX.exec(str);
}
out += str.slice(pivot);
return I18n.getDecorateOutput(id, out);
}
}
export {
I18n
};