Source: Localization.js

(function() {
    const _store = {};
    const _loaded = {};

    let _lang = 'en';
    let _readFun = () => {};

    /** Localization class */
    Help4.Localization = class {
        /**
         * @param {string} lang
         * @returns {Help4.Localization}
         */
        static setLanguage(lang) {
            _lang = lang;  // XRAY-1137
            return this;
        }

        /** @returns {string} */
        static getLanguage() {
            return _lang;
        }

        /**
         * @param {Function} fun
         * @returns {Help4.Localization}
         */
        static setReadFunction(fun) {
            _readFun = fun;
            return this;
        }

        /**
         * @returns {Help4.Promise<*>|*}
         */
        static init() {
            if (_loaded[_lang]) return Help4.Promise.resolve();
            _loaded[_lang] = true;
            return _readFun(_lang);
        }

        /**
         * @param {string} key
         * @returns {string}
         */
        static getText(key) {
            const text = _store[key]?.[_lang];
            return typeof text === 'string' ? text : `[${key}]`;
        }

        /**
         * @param {string} data
         * @returns {Object}
         */
        static parseSAP(data) {
            /** @type {string[]} */
            const texts = data.split('\n');

            for (const text of texts) {
                const c0 = text.trim().charAt(0);
                if (c0 && c0 !== '#') {
                    const index = text.indexOf('=');
                    const key = text.substring(0, index).trim();
                    const value = text.substring(index + 1).trim();

                    if (!_store[key]) _store[key] = {};
                    _store[key][_lang] = value;
                }
            }

            return _store;
        }

        /**
         * @param {string} data
         * @returns {Object}
         */
        static parseSEN(data) {
            /** @type {string[]} */
            const texts =  data
            .replace(/\r\n/g, '\n')
            .split('\n');

            for (const text of texts) {
                const c0 = text.trim().charAt(0);
                if (!c0 || c0 === '#') continue;

                // key value can be delimited by tab or space; choose 1st one
                const indexTab = text.indexOf('\t');
                const indexSpace = text.indexOf(' ');
                let index = null;
                if (indexTab >= 0 && (indexSpace < 0 || indexTab < indexSpace)) {
                    index = indexTab;
                } else if (indexSpace >= 0) {
                    index = indexSpace;
                } else {
                    continue;
                }

                let key = text.substring(0, index).trim();
                if (key.endsWith(';')) key = key.substring(0, key.length - 1);  // trainer localization files: <key>; <value>

                let value = text.substring(index + 1).trim();
                if (value.startsWith('"') && value.endsWith('"')) value = value.substring(1, value.length - 1);  // some trainer localization files

                // replace placeholders
                index = value.indexOf('@{');
                while (index >= 0) {
                    const end = value.indexOf('}', index);
                    /** @type {string} */
                    const placeholderKey = value.substring(index + 2, end);
                    /** @type {string} */
                    const placeholderValue = this.getText(placeholderKey) || `[${placeholderKey}]`;

                    value = value.substring(0, index) + placeholderValue + value.substring(end + 1);
                    index = value.indexOf('@{');
                }

                if (!_store[key]) _store[key] = {};
                _store[key][_lang] = value;
            }

            return _store;
        }
    }
})();