Skip to content
Snippets Groups Projects
Select Git revision
  • 569000adaf06d354d5e46c714ed43d87e8e325e4
  • main default protected
  • drip-server-timing
  • compress-middleware
  • v2.11.0
  • v2.10.0
  • v2.9.2
  • v2.9.1
  • v2.9.0
  • v2.8.0
  • v2.7.0
  • v2.6.0
  • v2.5.6
  • v2.5.5
  • v2.5.4
  • v2.5.3
  • v2.5.2
  • v2.5.1
  • v2.5.0
  • v2.4.2
  • v2.4.1
  • v2.4.0
  • v2.3.0
  • v2.2.2
24 results

index.html

Blame
  • translations.mjs 6.99 KiB
    /**
     * Copyright schukai GmbH and contributors 2022. All Rights Reserved.
     * Node module: @schukai/monster
     * This file is licensed under the AGPLv3 License.
     * License text available at https://www.gnu.org/licenses/agpl-3.0.en.html
     */
    
    import { getLinkedObjects,hasObjectLink} from "../dom/attributes.mjs";
    import {ATTRIBUTE_OBJECTLINK} from "../dom/constants.mjs";
    import {getDocument} from "../dom/util.mjs";
    import {Base} from "../types/base.mjs";
    import {isObject, isString} from "../types/is.mjs";
    import {validateInteger, validateObject, validateString} from "../types/validate.mjs";
    import {Locale, parseLocale} from "./locale.mjs";
    import {translationsLinkSymbol} from "./provider.mjs";
    
    
    export {Translations, getDocumentTranslations};
    
    /**
     * With this class you can manage translations and access the keys.
     *
     * @externalExample ../../example/i18n/translations.mjs
     * @license AGPLv3
     * @since 1.13.0
     * @copyright schukai GmbH
     * @memberOf Monster.I18n
     * @see https://datatracker.ietf.org/doc/html/rfc3066
     */
    class Translations extends Base {
        /**
         *
         * @param {Locale} locale
         */
        constructor(locale) {
            super();
    
            if (locale instanceof Locale) {
                this.locale = locale;
            } else {
                this.locale = parseLocale(validateString(locale));
            }
    
            this.storage = new Map();
        }
    
        /**
         * Fetches a text using the specified key.
         * If no suitable key is found, `defaultText` is taken.
         *
         * @param {string} key
         * @param {string|undefined} defaultText
         * @return {string}
         * @throws {Error} key not found
         */
        getText(key, defaultText) {
            if (!this.storage.has(key)) {
                if (defaultText === undefined) {
                    throw new Error(`key ${key} not found`);
                }
    
                return validateString(defaultText);
            }
    
            let r = this.storage.get(key);
            if (isObject(r)) {
                return this.getPluralRuleText(key, "other", defaultText);
            }
    
            return this.storage.get(key);
        }
    
        /**
         * A number `count` can be passed to this method. In addition to a number, one of the keywords can also be passed directly.
         * "zero", "one", "two", "few", "many" and "other". Remember: not every language has all rules.
         *
         * The appropriate text for this number is then selected. If no suitable key is found, `defaultText` is taken.
         *
         * @param {string} key
         * @param {integer|count} count
         * @param {string|undefined} defaultText
         * @return {string}
         */
        getPluralRuleText(key, count, defaultText) {
            if (!this.storage.has(key)) {
                return validateString(defaultText);
            }
    
            let r = validateObject(this.storage.get(key));
    
            let keyword;
            if (isString(count)) {
                keyword = count.toLocaleString();
            } else {
                count = validateInteger(count);
                if (count === 0) {
                    // special handlig for zero count
                    if (r.hasOwnProperty("zero")) {
                        return validateString(r["zero"]);
                    }
                }
    
                keyword = new Intl.PluralRules(this.locale.toString()).select(validateInteger(count));
            }
    
            if (r.hasOwnProperty(keyword)) {
                return validateString(r[keyword]);
            }
    
            if (r.hasOwnProperty(DEFAULT_KEY)) {
                return validateString(r[DEFAULT_KEY]);
            }
    
            return validateString(defaultText);
        }
    
        /**
         * Set a text for a key
         *
         * ```
         * translations.setText("text1", "Make my day!");
         * // plural rules
         * translations.setText("text6", {
         *     "zero": "There are no files on Disk.",
         *     "one": "There is one file on Disk.",
         *     "other": "There are files on Disk."
         *     "default": "There are files on Disk."
         * });
         * ```
         *
         * @param {string} key
         * @param {string|object} text
         * @return {Translations}
         * @throws {TypeError} value is not a string or object
         */
        setText(key, text) {
            if (isString(text) || isObject(text)) {
                this.storage.set(validateString(key), text);
                return this;
            }
    
            throw new TypeError("value is not a string or object");
        }
    
        /**
         * This method can be used to transfer overlays from an object. The keys are transferred, and the values are entered
         * as text.
         *
         * The values can either be character strings or, in the case of texts with plural forms, objects. The plural forms
         * must be stored as text via a standard key "zero", "one", "two", "few", "many" and "other".
         *
         * Additionally, the key default can be specified, which will be used if no other key fits.
         *
         * In some languages, like for example in German, there is no own more number at the value 0. In these languages,
         * the function applies additionally zero.
         *
         * ```
         * translations.assignTranslations({
         *   "text1": "Make my day!",
         *   "text2": "I'll be back!",
         *   "text6": {
         *     "zero": "There are no files on Disk.",
         *     "one": "There is one file on Disk.",
         *     "other": "There are files on Disk."
         *     "default": "There are files on Disk."
         * });
         * ```
         *
         * @param {object} translations
         * @return {Translations}
         */
        assignTranslations(translations) {
            validateObject(translations);
            
            if (translations instanceof Translations) {
                translations.storage.forEach((v, k) => {
                    this.setText(k, v);
                });
                return this;
            }
    
            for (const [k, v] of Object.entries(translations)) {
                this.setText(k, v);
            }
    
            return this;
        }
    }
    
    /**
     * Returns the translations for the current document.
     *
     * @param {HTMLElement|undefined} [element] - Element to search for translations. Default: element with objectlink @schukai/monster/i18n/translations@@link.
     * @returns {Translations}
     * @throws {Error} Element is not an HTMLElement.
     * @throws {Error} Cannot find element with translations. Add a translations object to the document.
     * @throws {Error} This element has no translations.
     * @throws {Error} Missing translations.
     */
    function getDocumentTranslations(element) {
    
        const d = getDocument()
    
        if (!(element instanceof HTMLElement)) {
            element = d.querySelector('['+ATTRIBUTE_OBJECTLINK+'~="' + translationsLinkSymbol.toString() + '"]');
            if (element === null) {
                throw new Error("Cannot find element with translations. Add a translations object to the document.");
            }
        }
    
        if (!(element instanceof HTMLElement)) {
            throw new Error("Element is not an HTMLElement.");
        }
    
        if (!hasObjectLink(element, translationsLinkSymbol)) {
            throw new Error("This element has no translations.");
        }
        
        let obj = getLinkedObjects(element, translationsLinkSymbol);
        
        for (const t of obj) {
            if (t instanceof Translations) {
                return t;
            }
        }
    
        throw new Error("Missing translations.");
    
    }