"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _utils = require("./utils"); var utils = _interopRequireWildcard(_utils); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Simple module to localize the React interface using the same syntax * used in the ReactNativeLocalization module * (https://github.com/stefalda/ReactNativeLocalization) * * Originally developed by Stefano Falda (stefano.falda@gmail.com) * * It uses a call to the Navigator/Browser object to get the current interface language, * then display the correct language strings or the default language (the first * one if a match is not found). * * How to use: * Check the instructions at: * https://github.com/stefalda/localized-strings */ var placeholderReplaceRegex = /(\{[\d|\w]+\})/; var placeholderReferenceRegex = /(\$ref\{[\w|.]+\})/; var LocalizedStrings = function () { /** * Constructor used to provide the strings objects in various language and the optional callback to get * the interface language * @param {*} props - the strings object * @param {Function} options.customLanguageInterface - the optional method to use to get the InterfaceLanguage * @param {Boolean} options.pseudo - convert all strings to pseudo, helpful when implementing * @param {Boolean} options.pseudoMultipleLanguages - add 40% to pseudo, helps with translations in the future * @param {Boolean} options.logsEnabled - Enable/Disable console.log outputs (default=true) */ function LocalizedStrings(props, options) { _classCallCheck(this, LocalizedStrings); // Compatibility fix with previous version if (typeof options === "function") { /* eslint-disable no-param-reassign */ options = { customLanguageInterface: options }; /* eslint-enable */ } this._opts = _extends({}, { customLanguageInterface: utils.getInterfaceLanguage, pseudo: false, pseudoMultipleLanguages: false, logsEnabled: true }, options); this._interfaceLanguage = this._opts.customLanguageInterface(); this._language = this._interfaceLanguage; this.setContent(props); } /** * Set the strings objects based on the parameter passed in the constructor * @param {*} props */ _createClass(LocalizedStrings, [{ key: "setContent", value: function setContent(props) { var _this = this; var _Object$keys = Object.keys(props), _Object$keys2 = _slicedToArray(_Object$keys, 1), defaultLang = _Object$keys2[0]; this._defaultLanguage = defaultLang; this._defaultLanguageFirstLevelKeys = []; // Store locally the passed strings this._props = props; utils.validateTranslationKeys(Object.keys(props[this._defaultLanguage])); // Store first level keys (for identifying missing translations) Object.keys(this._props[this._defaultLanguage]).forEach(function (key) { if (typeof _this._props[_this._defaultLanguage][key] === "string") { _this._defaultLanguageFirstLevelKeys.push(key); } }); // Set language to its default value (the interface) this.setLanguage(this._interfaceLanguage); // Developermode with pseudo if (this._opts.pseudo) { this._pseudoAllValues(this._props); } } /** * Replace all strings to pseudo value * @param {Object} obj - Loopable object */ }, { key: "_pseudoAllValues", value: function _pseudoAllValues(obj) { var _this2 = this; Object.keys(obj).forEach(function (property) { if (_typeof(obj[property]) === "object") { _this2._pseudoAllValues(obj[property]); } else if (typeof obj[property] === "string") { if (obj[property].indexOf("[") === 0 && obj[property].lastIndexOf("]") === obj[property].length - 1) { // already psuedo fixed return; } // @TODO must be a way to get regex to find all replaceble strings except our replacement variables var strArr = obj[property].split(" "); for (var i = 0; i < strArr.length; i += 1) { if (strArr[i].match(placeholderReplaceRegex)) { // we want to keep this string, includes specials } else if (strArr[i].match(placeholderReferenceRegex)) { // we want to keep this string, includes specials } else { var len = strArr[i].length; if (_this2._opts.pseudoMultipleLanguages) { len = parseInt(len * 1.4, 10); // add length with 40% } strArr[i] = utils.randomPseudo(len); } } obj[property] = "[" + strArr.join(" ") + "]"; // eslint-disable-line no-param-reassign } }); } /** * Can be used from ouside the class to force a particular language * indipendently from the interface one * @param {*} language */ }, { key: "setLanguage", value: function setLanguage(language) { var _this3 = this; // Check if exists a translation for the current language or if the default // should be used var bestLanguage = utils.getBestMatchingLanguage(language, this._props); var defaultLanguage = Object.keys(this._props)[0]; this._language = bestLanguage; // Associate the language object to the this object if (this._props[bestLanguage]) { // delete default propery values to identify missing translations for (var i = 0; i < this._defaultLanguageFirstLevelKeys.length; i += 1) { delete this[this._defaultLanguageFirstLevelKeys[i]]; } var localizedStrings = _extends({}, this._props[this._language]); Object.keys(localizedStrings).forEach(function (key) { _this3[key] = localizedStrings[key]; }); // Now add any string missing from the translation but existing in the default language if (defaultLanguage !== this._language) { localizedStrings = this._props[defaultLanguage]; this._fallbackValues(localizedStrings, this); } } } /** * Load fallback values for missing translations * @param {*} defaultStrings * @param {*} strings */ }, { key: "_fallbackValues", value: function _fallbackValues(defaultStrings, strings) { var _this4 = this; Object.keys(defaultStrings).forEach(function (key) { if (Object.prototype.hasOwnProperty.call(defaultStrings, key) && !strings[key] && strings[key] !== "") { strings[key] = defaultStrings[key]; // eslint-disable-line no-param-reassign if (_this4._opts.logsEnabled) { console.log("\uD83D\uDEA7 \uD83D\uDC77 key '" + key + "' not found in localizedStrings for language " + _this4._language + " \uD83D\uDEA7"); } } else if (typeof strings[key] !== "string") { // It's an object _this4._fallbackValues(defaultStrings[key], strings[key]); } }); } /** * The current language displayed (could differ from the interface language * if it has been forced manually and a matching translation has been found) */ }, { key: "getLanguage", value: function getLanguage() { return this._language; } /** * The current interface language (could differ from the language displayed) */ }, { key: "getInterfaceLanguage", value: function getInterfaceLanguage() { return this._interfaceLanguage; } /** * Return an array containing the available languages passed as props in the constructor */ }, { key: "getAvailableLanguages", value: function getAvailableLanguages() { var _this5 = this; if (!this._availableLanguages) { this._availableLanguages = []; Object.keys(this._props).forEach(function (key) { _this5._availableLanguages.push(key); }); } return this._availableLanguages; } // Format the passed string replacing the numbered or tokenized placeholders // eg. 1: I'd like some {0} and {1}, or just {0} // eg. 2: I'd like some {bread} and {butter}, or just {bread} // eg. 3: I'd like some $ref{bread} and $ref{butter}, or just $ref{bread} // Use example: // eg. 1: strings.formatString(strings.question, strings.bread, strings.butter) // eg. 2: strings.formatString(strings.question, { bread: strings.bread, butter: strings.butter }) // eg. 3: strings.formatString(strings.question) }, { key: "formatString", value: function formatString(str) { var _this6 = this; for (var _len = arguments.length, valuesForPlaceholders = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { valuesForPlaceholders[_key - 1] = arguments[_key]; } var input = str || ""; if (typeof input === "string") { input = this.getString(str, null, true) || input; } var ref = input.split(placeholderReferenceRegex).filter(function (textPart) { return !!textPart; }).map(function (textPart) { if (textPart.match(placeholderReferenceRegex)) { var matchedKey = textPart.slice(5, -1); var referenceValue = _this6.getString(matchedKey); if (referenceValue) return referenceValue; if (_this6._opts.logsEnabled) { console.log("No Localization ref found for '" + textPart + "' in string '" + str + "'"); } // lets print it another way so next replacer doesn't find it return "$ref(id:" + matchedKey + ")"; } return textPart; }).join(""); return ref.split(placeholderReplaceRegex).filter(function (textPart) { return !!textPart; }).map(function (textPart) { if (textPart.match(placeholderReplaceRegex)) { var matchedKey = textPart.slice(1, -1); var valueForPlaceholder = valuesForPlaceholders[matchedKey]; // If no value found, check if working with an object instead if (valueForPlaceholder === undefined) { var valueFromObjectPlaceholder = valuesForPlaceholders[0][matchedKey]; if (valueFromObjectPlaceholder !== undefined) { valueForPlaceholder = valueFromObjectPlaceholder; } else { // If value still isn't found, then it must have been undefined/null return valueForPlaceholder; } } return valueForPlaceholder; } return textPart; }).join(""); } // Return a string with the passed key in a different language or defalt if not set // We allow deep . notation for finding strings }, { key: "getString", value: function getString(key, language) { var omitWarning = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; try { var current = this._props[language || this._language]; var paths = key.split("."); for (var i = 0; i < paths.length; i += 1) { if (current[paths[i]] === undefined) { throw Error(paths[i]); } current = current[paths[i]]; } return current; } catch (ex) { if (!omitWarning && this._opts.logsEnabled) { console.log("No localization found for key '" + key + "' and language '" + language + "', failed on " + ex.message); } } return null; } /** * The current props (locale object) */ }, { key: "getContent", value: function getContent() { return this._props; } }]); return LocalizedStrings; }(); exports.default = LocalizedStrings;