UNPKG

laravel-inertia-react-translator

Version:
142 lines (141 loc) 6.81 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); class Translator { //--- Properties -------------------------------------------------------------------------------------------------- /** * The dictionary can be an object or a function that returns an object. */ dictionary = {}; //--- Constructor ------------------------------------------------------------------------------------------------- constructor(dictionary = {}) { if (dictionary) this.setDictionary(dictionary); } //--- Public API -------------------------------------------------------------------------------------------------- setDictionary(dictionary) { this.dictionary = dictionary; } getDictionary() { return typeof this.dictionary === "function" ? this.dictionary() : this.dictionary; } //--- Translation methods ------------------------------------------------------------------------------------------ translate(key, replace) { let translation = this.#getTranslationFromDictionary(key, this.getDictionary()); //if we have a translation or a set of replacements, we can proceed if (translation || replace) return this.#replacePlaceholders(translation ?? key, this.#normalizeReplaceObject(replace)); //if we got here, we have no translation and no replacements, so we check if we have a composed key if (!this.#isComposedKey(key)) return key; //if we have a composed key, we split it into its parts and try to translate it const composedKey = this.#splitComposedKey(key); //if we have a count, we try to translate the plural form if (composedKey.count !== null && !isNaN(composedKey.count)) return this.translatePlural(composedKey.key, composedKey.count, composedKey.replace); //if we have a replacement object, try to translate the key with the replacements (recursive call!) if (composedKey.replace) return this.translate(composedKey.key, composedKey.replace); //if we got here, we have a composed key without a count or replacements, so we return the key return composedKey.key; } translatePlural(key, count, replace) { let translation = this.#getTranslationFromDictionary(key, this.getDictionary()); if (!translation) return key; translation = this.#pickPluralForm(count, translation.split("|")); if (!translation) return key; let replaceObject = this.#normalizeReplaceObject(replace); let defaultReplaceObject = { count: count, value: count }; return this.#replacePlaceholders(translation, { ...defaultReplaceObject, ...(typeof replaceObject === "object" ? replaceObject : {}), }); } //--- Private helpers --------------------------------------------------------------------------------------------- /** * Pick the correct plural form for a given count. The forms are given as an array of strings (exploded by |). * The count is used to find the right plural form. Following options are supported: * - {0} zero|{1} one|[2-10] few|[11-99] many|[100-*] other * - one|zero or many */ #pickPluralForm(count, forms) { if (!Array.isArray(forms) || forms.length === 0) return null; if (forms.length === 1) return forms[0]; //use the default plural forms, equivalent to ({1} one|[0, 2-*] zero or many) let translated = count === 1 ? forms[0] : forms[1]; for (let i = 0; i < forms.length; i++) { //check for a specific plural form: {count} form let exactNumber = forms[i].match(/\{(\d+)\}\s?(.*)/); //match {n} and the rest of the string if (exactNumber !== null && count === parseInt(exactNumber[1])) { return exactNumber[2]; } //check for a plural range in any of the forms: [n - m] / [n - *] / [n, m] / [n, *] let range = forms[i].match(/\s*\[\s*(\d+)\s*(?:-|,)\s*(\d*\*?)\s*\]\s*(.*)/); if (range !== null) { const rangeStart = parseInt(range[1]); const rangeEnd = range[2] === "*" ? Infinity : parseInt(range[2]); if (count >= rangeStart && count <= rangeEnd) { return range[3]; } } } return translated; } #getTranslationFromDictionary(key, dictionary) { if (!key) return ""; const result = key .split(".") .reduce((t, i) => (t && typeof t === "object" ? t[i] ?? null : null), dictionary); return typeof result === "string" ? result : null; } #replacePlaceholders(translation, replace) { if (!replace) return translation; Object.keys(replace).forEach((key) => { if (typeof replace === "object" && replace !== null && key in replace) { translation = translation.replaceAll(`:${key}`, String(replace[key])); } }); return translation; } //normalize the replacement object to a key-value object #normalizeReplaceObject(replace) { if (!replace) return null; let replaceObject = replace; // if replace is a number, we assume it's: count OR value if (typeof replaceObject === "number") replaceObject = { count: replace, value: replace }; // if replace is a string, we assume it's: value, name if (typeof replaceObject === "string") replaceObject = { value: replace, name: replace }; return typeof replaceObject === "object" ? replaceObject : null; } #isComposedKey(key) { return typeof key === "string" && (key.includes("[") || key.includes("{")); } //given a key in the form of 'a.b[1]{replace: value}', split it into //the key 'a.b', the plural count 1, and the replacement object {replace: value} #splitComposedKey(composedKey) { // Updated regex to correctly handle missing parts const match = composedKey.match(/^(.+?)(?:\[(\d+)])?(?:{(.+?)})?$/); // Return null if no match if (!match) return { key: composedKey, count: null, replace: null }; const key = match[1]; const count = match[2] ? parseInt(match[2], 10) : null; const rawArgs = match[3]; // Parse replacement arguments if provided const replace = rawArgs ? Object.fromEntries(rawArgs.split(",").map((pair) => pair.split(":").map((s) => s.trim()))) : null; return { key, count, replace }; } } exports.default = Translator;