@mongez/localization
Version:
A simple i18n localization handler for browsers and nodejs
230 lines (229 loc) • 8.38 kB
JavaScript
import {merge,flatten,set,get}from'@mongez/reinforcements';import {getLocalizationConfigurations}from'./config.js';import {plainConverter}from'./converters.js';import {getCountKey,formatCount}from'./count-rules.js';import {localizationEvents}from'./events.js';import {getPlaceholderPattern}from'./placeholder-pattern-config.js';/**
* Current locale code
*/
let currentLocaleCode = "en";
/**
* Current converter
*/
let currentConverter = plainConverter;
/**
* all keywords for all locale codes
*/
let translationsList = {};
/**
* Current fall back locale code
*/
let fallbackLocaleCode = "en";
/**
* Get current locale code
*/
function getCurrentLocaleCode() {
return currentLocaleCode;
}
/**
* Get fallback locale code
*/
function getFallbackLocaleCode() {
return fallbackLocaleCode;
}
/**
* Set current converter
*/
function setConverter(converter) {
currentConverter = converter;
}
/**
* Get the locale code used in translation, this allows to get the locale code on the fly
*/
function getTranslationLocaleCode() {
return (getLocalizationConfigurations().translationLocalCode || currentLocaleCode);
}
/**
* Set current locale code
*/
function setCurrentLocaleCode(localeCode) {
const oldLocaleCode = currentLocaleCode;
currentLocaleCode = localeCode;
localizationEvents.triggerChange("localeCode", localeCode, oldLocaleCode);
}
/**
* Add keywords
*/
function extend(localeCode, keywords) {
translationsList[localeCode] = merge(translationsList[localeCode] || {}, keywords);
}
/**
* Create a grouped translations based on keyword, each keyword contains list of locale codes and beside it its corresponding translation
*
* @example
* {
* home: {
* en: "Home",
* ar: "الرئيسية"
* }
* }
*
* Also it could have nested grouped translations
*
* @example
* {
* general: {
* home: {
* en: "Home",
* ar: "الرئيسية"
* }
* }
*/
function groupedTranslations(groupKey, groupedTranslations) {
if (typeof groupKey !== "string" && !groupedTranslations) {
groupedTranslations = groupKey;
groupKey = undefined;
}
// now we need to loop over the grouped translations
// we have two cases here
// first one we have a group key
// second one we don't have a group key
// as values are always objects until the last level
// we need to create a recursive function to loop over the object
// output of the flatten object will be something like:
// general.home.en: "Home"
// general.home.ar: "الرئيسية"
const object = flatten(groupKey && typeof groupKey === "string"
? { [groupKey]: groupedTranslations }
: groupedTranslations);
// now the locale codes are the last dot in each key
// now we will loop over the object and add each key to the translations list
for (const key in object) {
const keyword = key.split(".");
const localeCode = keyword.pop();
set(translationsList, localeCode + "." + keyword.join("."), object[key]);
}
}
/**
* Override the entire translations list
*/
function setTranslationsList(translations) {
translationsList = translations;
}
/**
* Get the entire translations list
*/
function getTranslationsList() {
return translationsList;
}
/**
* Get the keywords list of the given locale code
*/
function getKeywordsListOf(localeCode) {
return translationsList[localeCode] || null;
}
/**
* Set fallback locale code, if the keyword does not exist on current locale code,
* then check it in the faLLBACK locale code instead
*/
function setFallbackLocaleCode(fallbackLocale) {
const oldFallback = fallbackLocaleCode;
fallbackLocaleCode = fallbackLocale;
localizationEvents.triggerChange("fallback", fallbackLocale, oldFallback);
}
/**
* Translate the given keyword in current locale code
*/
function trans(keyword, placeholders, converter = currentConverter) {
return transFrom(getTranslationLocaleCode(), keyword, placeholders, converter);
}
/**
* Translate using the default converter
*/
function plainTrans(keyword, placeholders) {
return transFrom(getTranslationLocaleCode(), keyword, placeholders, plainConverter);
}
/**
* Translate the given keyword for the given locale code
* Please note this method accepts dot notation syntax
*/
function transFrom(localeCode, keyword, placeholders, converter = currentConverter) {
let translation;
if (typeof keyword === "object") {
translation = keyword[localeCode] || keyword[fallbackLocaleCode];
}
else {
// Check if we have a count in placeholders
if (placeholders?.count !== undefined) {
const count = Number(placeholders.count);
// Try current locale with its count rules
const currentCountKey = getCountKey(count, localeCode);
translation = get(translationsList, `${localeCode}.${keyword}${currentCountKey}`);
// If not found and we have a fallback, try the fallback locale with its own count rules
if (!translation && fallbackLocaleCode) {
const fallbackCountKey = getCountKey(count, fallbackLocaleCode);
translation = get(translationsList, `${fallbackLocaleCode}.${keyword}${fallbackCountKey}`);
}
// If still not found, try other variants in order:
// 1. Current locale _other
// 2. Fallback locale _other
// 3. Current locale base
// 4. Fallback locale base
if (!translation) {
translation = get(translationsList, `${localeCode}.${keyword}_other`) ||
(fallbackLocaleCode && get(translationsList, `${fallbackLocaleCode}.${keyword}_other`)) ||
get(translationsList, `${localeCode}.${keyword}`) ||
(fallbackLocaleCode && get(translationsList, `${fallbackLocaleCode}.${keyword}`));
}
// Format the count value according to configuration
if (translation && placeholders.count !== undefined) {
placeholders = { ...placeholders, count: formatCount(count) };
}
}
else {
// No count, just get regular translation
translation =
get(translationsList, `${localeCode}.${keyword}`) ||
(fallbackLocaleCode
? get(translationsList, `${fallbackLocaleCode}.${keyword}`)
: null);
}
}
if (!translation)
return keyword;
return placeholders
? converter(translation, placeholders, getPlaceholderPattern())
: translation;
}
/**
* Get a translation object with automatic translation using object syntax
* Please note this does not support nested objects, only keywords and their translations
* i.e
* const translations = transObject({
* name: {
* en: 'name',
* ar: 'الاسم'
* }
* });
*
* Usage: translations.name // returns the name in current locale code
* If the keyword does not exist on current locale code, then it will check it in the fallback locale code
*
* If keyword is "p", then it will return a function that accepts keyword and its placeholders
* If keyword is "plain", then the converter used in translation will be the plain converter
*/
function transObject(translations) {
// use proxy
return new Proxy(translations, {
get(target, key) {
if (key === "p") {
return function (keyword, placeholders) {
return transFrom(currentLocaleCode, target[keyword], placeholders, currentConverter);
};
}
if (key === "plain") {
return function (keyword, placeholders) {
return transFrom(currentLocaleCode, target[keyword], placeholders, plainConverter);
};
}
if (!target[key])
return transFrom(fallbackLocaleCode, key);
return transFrom(currentLocaleCode, target[key]);
},
});
}export{extend,getCurrentLocaleCode,getFallbackLocaleCode,getKeywordsListOf,getTranslationLocaleCode,getTranslationsList,groupedTranslations,plainTrans,setConverter,setCurrentLocaleCode,setFallbackLocaleCode,setTranslationsList,trans,transFrom,transObject};//# sourceMappingURL=translator.js.map