//--------------------------------------------------------
//-- Node IoC - Translation - Services - Translator - Drivers - File Driver
//--------------------------------------------------------
import __ from '@absolunet/private-registry';
import deepMerge from 'deepmerge';
import dot from 'dot-object';
import Driver from './Driver';
/**
* File driver to handle translations.
*
* @memberof translation.services.Translator.drivers
* @augments translation.services.Translator.drivers.Driver
* @hideconstructor
*/
class FileDriver extends Driver {
/**
* Class dependencies: <code>['app', 'file', 'helper.string']</code>.
*
* @type {Array<string>}
*/
static get dependencies() {
return (super.dependencies || []).concat(['app', 'file']);
}
/**
* @inheritdoc
* @private
*/
init() {
__(this).set('locale', null);
__(this).set('fallbackLocale', null);
__(this).set('translations', {});
__(this).set('loaded', false);
}
/**
* @inheritdoc
*/
loadTranslations() {
this.ensureTranslationsAreLoaded();
}
/**
* @inheritdoc
*/
translate(string, replacements = {}, count = 1) {
const { locale } = this;
const replacementObject = typeof replacements === 'object' && replacements ? replacements : {};
const countValue = typeof replacements === 'number' ? replacements : count;
return this.parse(this.getTranslationForLocale(string, locale), replacementObject, countValue);
}
/**
* @inheritdoc
*/
addTranslation(key, value, locale = this.locale) {
dot.str(`${key.replace(/\//gu, '')}.${locale}`, value, __(this).get('translations'));
return this;
}
/**
* @inheritdoc
*/
addTranslations({ ...translations }) {
Object.entries(translations).forEach(([key, value]) => {
if (key.includes('/')) {
delete translations[key];
dot.str(key.replace(/\//gu, '.'), value, translations);
}
});
__(this).set('translations', deepMerge(__(this).get('translations'), translations));
return this;
}
/**
* @inheritdoc
*/
setLocale(locale) {
__(this).set('locale', locale);
return this;
}
/**
* @inheritdoc
*/
setFallbackLocale(locale) {
__(this).set('fallbackLocale', locale);
return this;
}
/**
* Ensure that the translations are loaded from configured path.
*
* @returns {translation.services.Translator.drivers.FileDriver} The current driver instance.
*/
ensureTranslationsAreLoaded() {
if (!__(this).get('loaded')) {
if (this.file.exists(this.folder)) {
const translations = this.file.loadRecursivelyInFolder(this.folder);
this.addTranslations(translations);
__(this).set('loaded', true);
}
}
return this;
}
/**
* Get unparsed translation value(s) for given locale.
* It will use fallback locale if needed.
*
* @param {string} key - The translation key.
* @param {string} locale - The locale.
* @returns {string} The translated value.
*/
getTranslationForLocale(key, locale) {
const translations = __(this).get('translations');
const { fallbackLocale } = this;
let translation = dot.pick(`${key}.${locale}`, translations);
if (!translation) {
translation = dot.pick(`${this.defaultNamespace}.${key}.${locale}`, translations);
}
if (!translation && locale !== fallbackLocale) {
return this.getTranslationForLocale(key, fallbackLocale);
}
return translation || key;
}
/**
* Parse given string with the given token replacements.
* Handle manual pluralization based on a given count.
*
* @param {string|Array<string>} string - The value.
* @param {object<string, string>} [replacements] - The token replacement dictionary.
* @param {number} [count] - The count for pluralization.
* @returns {string} The parsed string.
*/
parse(string, replacements = {}, count = 1) {
const values = Array.isArray(string) ? string : [string];
const [noneValue] = values;
const singleValue = values[values.length === 3 ? 1 : 0];
const pluralValue = values[values.length - 1];
const value = count === 0 ? noneValue : count === 1 ? singleValue : pluralValue;
return Object.keys(replacements).reduce((parsed, token) => {
return parsed.replace(new RegExp(this.buildReplacementPattern(token), 'gu'), replacements[token]);
}, value);
}
/**
* Build the replacement pattern from a given token name.
*
* @param {string} token - The token to replace.
* @returns {string} The token pattern.
*/
buildReplacementPattern(token) {
return `\\{\\{${token}\\}\\}`;
}
/**
* The locale.
*
* @type {string}
*/
get locale() {
return __(this).get('locale');
}
/**
* The fallback locale.
*
* @type {string}
*/
get fallbackLocale() {
return __(this).get('fallbackLocale');
}
/**
* The folder in which the translations are stored.
*
* @type {string}
*/
get folder() {
return this.app.langPath();
}
/**
* The default namespace for translations.
*
* @type {string}
*/
get defaultNamespace() {
return 'translations';
}
}
export default FileDriver;