UNPKG

@particular.cloud/i18n-js

Version:

The i18n javascript and typescript sdk of particular.cloud

1,171 lines (1,149 loc) 52.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var texts = require('@particular.cloud/texts'); var socket_ioClient = require('socket.io-client'); var fetch = require('isomorphic-unfetch'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } /** * used to reset the config */ var defaultConfig = Object.freeze({ initialized: false, langCodeOrLocale: undefined, token: '', onPickStringFromArray: 'pickRandom', onLeftOverTemplateSyntax: 'warn', onKeyNotFound: 'warn', onError: 'warn', enableWebsocket: false, activeLanguagesOnly: false, onPublish: undefined, onChangeDefaultLanguage: undefined, onChangeLanguage: undefined, onChangeConfig: undefined, updateStrategy: 'never', keyExpiresIn: 86400000, customCache: undefined, defaultLanguage: undefined, acceptLanguage: undefined, _useLocalhost: false, _fetch: undefined, }); var config = __assign({}, defaultConfig); var cache = { records: {}, put: function (key, language, value) { if (!this.records[language]) { this.records[language] = {}; } this.records[language][key] = { value: value, expiresAt: config.updateStrategy === 'never' ? undefined : new Date(Date.now() + config.keyExpiresIn), }; }, /** * Note: Use function retrieveForLangCodeOrLocale to retrieve a value from cache for a langCodeOrLocale * @return {TextValue | undefined, boolean} value undefined means not found, empty string means found (string value) */ retrieve: function (key, language) { var languageRecord = this.records[language]; if (!languageRecord) { return [undefined, false]; } var item = languageRecord[key]; if (!item) { return [undefined, false]; } if (item.expiresAt && item.expiresAt < new Date()) { delete this.records[language][key]; return [item.value, true]; } return [item.value, false]; }, keys: function () { var _this = this; var tuples = []; var locales = Object.keys(this.records); locales.forEach(function (locale) { var keys = Object.keys(_this.records[locale]); keys.forEach(function (key) { tuples.push([locale, key]); }); }); return tuples; }, storeAll: function (texts) { var _this = this; texts.forEach(function (text) { _this.put(text.key, text.locale, text.value); }); }, }; function isLocale(locale) { return locale.includes('-'); } /** * retrieve a value from the cache * based on specified langCodeOrLocale * and fallback to default locale if langCodeOrLocale is a langCode */ function retrieveLangCodeOrLocale(key, langCodeOrLocale, languages) { // let's see if we have a cached value for the specified langCode or locale var _a = cache.retrieve(key, langCodeOrLocale), values = _a[0], expired = _a[1]; // undefined means not found (!empty string is possible string value) if (values !== undefined) { // return direct match return [values, expired]; } // if we have a langCode, try find a locale match and try again if (!isLocale(langCodeOrLocale)) { // we have a langCode, so let's find a locale var defaultLanguage = languages.find(function (l) { return l.language.langCode === langCodeOrLocale && l.isDefault; }); if (defaultLanguage) { return cache.retrieve(key, defaultLanguage.language.locale); } } // no match for either langCode or locale return [undefined, false]; } /** * selectLanguage optimistically selects a language based on the provided paramenters. * The source return value marks the "confidence" of the selected language. * @param activeLanguagesOnly as per ParticularConfig * @param language explicitly provided language * @param acceptLanguage as per ParticularConfig * @param defaultLanguage as per ParticularConfig * @param projectLanguages from @particular.cloud/texts * @param currentLangCodeOrLocale as stored from prior t (translation) and fetchT calls * @returns */ function selectLanguage(activeLanguagesOnly, language, acceptLanguage, defaultLanguage, projectLanguages, currentLangCodeOrLocale) { if (projectLanguages === void 0) { projectLanguages = []; } // specific language always wins if (language) { // we don't check if in project languages here // if user specifcly asks for langauge, we go to Particular.Cloud servers // we can always throw later if needed return [language, 'explicitLanguage']; } // no language specified var noProjectLanguages = !projectLanguages.length; if (!language && !defaultLanguage && !acceptLanguage && noProjectLanguages) { if (currentLangCodeOrLocale) { // better than none return [currentLangCodeOrLocale, 'currentGlobal']; } return ['', 'none']; } // no language specified but projectLanguages are available if (!language && !defaultLanguage && !acceptLanguage && (projectLanguages === null || projectLanguages === void 0 ? void 0 : projectLanguages.length)) { if (currentLangCodeOrLocale) { // better than random return [currentLangCodeOrLocale, 'currentGlobal']; } // pick one of the project languages, preferrebly English var english = projectLanguages.find(function (l) { return l.language.langCode === 'en' && l.isDefault; }); if (english) { return [english.language.locale, 'random']; } var anyDefaultLanguage = projectLanguages.find(function (l) { return l.isDefault; }); if (anyDefaultLanguage) { return [anyDefaultLanguage.language.locale, 'random']; } if (activeLanguagesOnly) { return ['', 'none']; } return [projectLanguages[0].language.locale, 'random']; } // acceptLanguage wins if (acceptLanguage) { var acceptedLanguages = parseAcceptHeader(acceptLanguage); if (noProjectLanguages) { return [acceptedLanguages[0], 'acceptLanguage']; } var language_1; var _loop_1 = function (langCodeOrlocale) { language_1 = projectLanguages.find(function (l) { return l.language.langCode === langCodeOrlocale || l.language.locale === langCodeOrlocale; }); if (language_1) { return { value: [language_1.language.locale, 'acceptLanguage'] }; } }; for (var _i = 0, acceptedLanguages_1 = acceptedLanguages; _i < acceptedLanguages_1.length; _i++) { var langCodeOrlocale = acceptedLanguages_1[_i]; var state_1 = _loop_1(langCodeOrlocale); if (typeof state_1 === "object") return state_1.value; } } // fallback to default language if (defaultLanguage) { // we don't check if in project languages here // if user specifcly asks for langauge, we go to Particular.Cloud servers // we can always throw later if needed return [defaultLanguage, 'defaultLanguage']; } if (currentLangCodeOrLocale) { // better than none return [currentLangCodeOrLocale, 'currentGlobal']; } return ['', 'none']; } function parseAcceptHeader(acceptLanguage) { if (acceptLanguage === '*') { return []; } var trimmedAcceptHeader = acceptLanguage.replace(new RegExp(/\s/g), ''); var entries = trimmedAcceptHeader.split(','); // parse entries into computable units var qValueExp = new RegExp(/q=(.*)/); var parsedEntries = entries.map(function (entry, index) { var tuple = entry.split(';'); if (!tuple.length) { return ['', -1]; } var weight = (entries.length - index) / parseFloat(entries.length.toFixed(2)); if (tuple.length === 1) { return [tuple[0], weight]; } try { var qValues = tuple[1].match(qValueExp); if (!qValues || !qValues.length) { return [tuple[0], weight]; } var qValue = qValues.length === 1 ? qValues[0] : qValues[1]; weight = parseFloat(qValue); return [tuple[0], weight]; } catch (error) { return [tuple[0], weight]; } }); // remove invalid var filteredEntries = parsedEntries.filter(function (_a) { var q = _a[1]; return q !== -1; }); // priotize var sortedEntries = filteredEntries.sort(function (_a, _b) { var q1 = _a[1]; var q2 = _b[1]; return q2 - q1; }); // return locales / languageCodes return sortedEntries.map(function (_a) { var langCodeOrLocale = _a[0]; return langCodeOrLocale; }); } function pickRandomFromArray(array) { return array[Math.floor(Math.random() * array.length)]; } function populateValues(template, values) { return template.replace(regex, function (unpopulatedStr, key) { return values[key.trim()] || unpopulatedStr; }); } var regex = /{(.*?)}/g; function warnForTemplateSyntax(template) { if (regex.test(template)) { console.warn("Particular.Cloud i18n: unpopulated template string: ".concat(template)); } } function throwForTemplateSyntax(template) { if (regex.test(template)) { throw Error("Particular.Cloud i18n: unpopulated template string: ".concat(template)); } } function throwOnParticularTextsMissing() { // added this case to prevent error on client-side if bundler agressively removed @particular.cloud/texts from bundle throw Error('Particular.Cloud i18n: @particular.cloud/texts is missing. Please run "npm install" again or clean your application cache.'); } function handleError(error, onError, message) { if (message === void 0) { message = ''; } if (onError === 'warn') { console.error("Particular.Cloud i18n: ".concat(message), error); } if (onError === 'throw') { if ((error && error.message.includes('i18n')) || message.includes('i18n')) { throw error ? Error(error.message) : Error(message); } if (!error) { throw Error("Particular.Cloud i18n: ".concat(message)); } error.message = "Particular.Cloud i18n:".concat(message ? " (".concat(message, ")") : '', " ").concat(error.message); throw error; } } function internalConfigToConfig(internalConfig) { return __assign({}, internalConfig); } var getHost$1 = function () { return config._useLocalhost ? 'http://localhost:3001/api/v1' : 'https://backend.particular.cloud/api/v1'; }; var getHeaders = function (includeAcceptLanguage) { if (includeAcceptLanguage === void 0) { includeAcceptLanguage = false; } var headers = { Authorization: config.token, Accept: 'application/json', 'Content-Type': 'application/json', }; if (includeAcceptLanguage && config.acceptLanguage) { headers['Accept-Language'] = config.acceptLanguage; } return headers; }; var getSearchQuery = function (includeDefaultLanguage) { if (includeDefaultLanguage === void 0) { includeDefaultLanguage = false; } var searchParams = new URLSearchParams(); if (includeDefaultLanguage && config.defaultLanguage) { searchParams.append('defaultLanguage', config.defaultLanguage); } // generate placeholder as option in rest call so we dont have to store palceholder for all lang if (config.onKeyNotFound === 'generatePlaceholder') { searchParams.append('withPlaceholder', 'true'); } if (config.activeLanguagesOnly) { searchParams.append('active', 'true'); } return searchParams.toString(); }; var withFetch = function () { return (config._fetch ? config._fetch : fetch__default['default']); }; var handleNoTokenError = function () { return handleError(null, config.onError, 'no token provided. Please call init and pass your Particular.Cloud API token'); }; /** * fetchTexts: fetch all texts (for one language or all languages) of your Particular.Cloud project */ var fetchTexts = function (language) { return __awaiter(void 0, void 0, void 0, function () { var url, f, res, result, texts, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!config.token) { handleNoTokenError(); return [2 /*return*/, []]; } url = language ? "".concat(getHost$1(), "/texts/").concat(language, "?").concat(getSearchQuery()) : "".concat(getHost$1(), "/texts/?").concat(getSearchQuery()); _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); f = withFetch(); return [4 /*yield*/, f(url, { headers: getHeaders() })]; case 2: res = _a.sent(); return [4 /*yield*/, res.json()]; case 3: result = _a.sent(); if (result.error) { handleError(null, config.onError, result.error.message); return [2 /*return*/, []]; } texts = result; return [2 /*return*/, texts]; case 4: error_1 = _a.sent(); handleError(error_1, config.onError, 'fetchTexts'); return [2 /*return*/, []]; case 5: return [2 /*return*/]; } }); }); }; /** * fetchText: fetch one text from your Particular.Cloud project */ var fetchText = function (key, language) { return __awaiter(void 0, void 0, void 0, function () { var url, f, res, result, text, error_2; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!config.token) { handleNoTokenError(); return [2 /*return*/, undefined]; } url = "".concat(getHost$1(), "/texts/").concat(language, "/").concat(key, "?").concat(getSearchQuery(true)); _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); f = withFetch(); return [4 /*yield*/, f(url, { headers: getHeaders(true) })]; case 2: res = _a.sent(); return [4 /*yield*/, res.json()]; case 3: result = _a.sent(); if (result.error) { handleError(null, config.onError, result.error.message); return [2 /*return*/, undefined]; } text = result; return [2 /*return*/, text.value]; case 4: error_2 = _a.sent(); handleError(error_2, config.onError, 'fetchText'); return [2 /*return*/, undefined]; case 5: return [2 /*return*/]; } }); }); }; /** * queryTexts: fetch texts from your Particular.Cloud project based on a query */ var queryTexts = function (query) { return __awaiter(void 0, void 0, void 0, function () { var url, f, res, result, texts, error_3; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!config.token) { handleNoTokenError(); return [2 /*return*/, []]; } _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); url = "".concat(getHost$1(), "/texts?").concat(getSearchQuery(true)); f = withFetch(); return [4 /*yield*/, f(url, { method: 'POST', headers: getHeaders(true), body: JSON.stringify(query), })]; case 2: res = _a.sent(); return [4 /*yield*/, res.json()]; case 3: result = _a.sent(); if (result.error) { handleError(null, config.onError, result.error.message); return [2 /*return*/, []]; } texts = result; return [2 /*return*/, texts]; case 4: error_3 = _a.sent(); handleError(error_3, config.onError, 'queryTexts'); return [2 /*return*/, []]; case 5: return [2 /*return*/]; } }); }); }; /** * addTextKey: create a new key in your Particular.Cloud project. * Optional: initialize the key with a text value in one of the supported lanuages of your project */ var addTextKey = function (key, locale, value) { return __awaiter(void 0, void 0, void 0, function () { var url, f, res, result, error_4; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!config.token) { handleNoTokenError(); return [2 /*return*/]; } url = "".concat(getHost$1(), "/texts/").concat(key); _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); f = withFetch(); return [4 /*yield*/, f(url, { method: 'POST', headers: { Authorization: config.token, Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ locale: locale, value: value, }), })]; case 2: res = _a.sent(); return [4 /*yield*/, res.json()]; case 3: result = _a.sent(); if (result.error) { handleError(null, config.onError, result.error.message); } return [3 /*break*/, 5]; case 4: error_4 = _a.sent(); handleError(error_4, config.onError, 'addTextKey'); return [3 /*break*/, 5]; case 5: return [2 /*return*/]; } }); }); }; /** * fetchLanguages: fetch languages of your Particular.Cloud project */ var fetchProjectLanguages = function (filter) { if (filter === void 0) { filter = 'active'; } return __awaiter(void 0, void 0, void 0, function () { var url, f, res, result, error_5; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!config.token) { handleNoTokenError(); return [2 /*return*/, []]; } url = "".concat(getHost$1(), "/languages/project?filter=").concat(filter); _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); f = withFetch(); return [4 /*yield*/, f(url, { headers: { Authorization: config.token, Accept: 'application/json', 'Content-Type': 'application/json', }, })]; case 2: res = _a.sent(); return [4 /*yield*/, res.json()]; case 3: result = _a.sent(); if (result.error) { handleError(null, config.onError, result.error.message); return [2 /*return*/, []]; } return [2 /*return*/, result]; case 4: error_5 = _a.sent(); handleError(error_5, config.onError, 'fetchLanguages'); return [2 /*return*/, []]; case 5: return [2 /*return*/]; } }); }); }; /** * fetchAllLanguages: fetch all languages supported by Particular.Cloud */ var fetchAllLanguages = function () { return __awaiter(void 0, void 0, void 0, function () { var url, f, res, result, error_6; return __generator(this, function (_a) { switch (_a.label) { case 0: url = "".concat(getHost$1(), "/languages?"); _a.label = 1; case 1: _a.trys.push([1, 4, , 5]); f = withFetch(); return [4 /*yield*/, f(url, { headers: { Authorization: config.token, Accept: 'application/json', 'Content-Type': 'application/json', }, })]; case 2: res = _a.sent(); return [4 /*yield*/, res.json()]; case 3: result = _a.sent(); if (result.error) { handleError(null, config.onError, result.error.message); return [2 /*return*/, []]; } return [2 /*return*/, result]; case 4: error_6 = _a.sent(); handleError(error_6, config.onError, 'fetchAllLanguages'); return [2 /*return*/, []]; case 5: return [2 /*return*/]; } }); }); }; var client; var getHost = function () { return (config._useLocalhost ? 'http://localhost:3001' : 'https://backend.particular.cloud'); }; function retrieveKeysFromCache() { return cache.keys(); } function connect() { var _this = this; if (client && client.connected) { return; } // see https://socket.io/docs/v3/client-initialization/#Options var socketPath = "".concat(getHost(), "/apps"); client = socket_ioClient.io(socketPath, { path: '/wss', query: { projectToken: config.token }, forceNew: true, }); client.on('connect', function () { console.log('Particular.Cloud i18n: webSocket client connected'); }); client.on('disconnect', function (reason) { if (reason === 'io server disconnect') { // the disconnection was initiated by the server, you need to reconnect manually console.error("Particular.Cloud i18n: websocket server disconnected connection: ".concat(reason)); handleError(new Error(reason), config.onError, 'ws error'); } // else the socket will automatically try to reconnect }); client.on('PROJECT_PUBLISHED', function () { return __awaiter(_this, void 0, void 0, function () { var query, texts, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 3, , 4]); query = retrieveKeysFromCache(); if (!query) return [3 /*break*/, 2]; return [4 /*yield*/, queryTexts(query)]; case 1: texts = _a.sent(); cache.storeAll(texts); _a.label = 2; case 2: if (config.onPublish) { config.onPublish(); } return [3 /*break*/, 4]; case 3: error_1 = _a.sent(); handleError(error_1, config.onError, 'ws error'); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); }); client.connect(); } /** * initalize the in-memory cache */ function initMemory() { if (!texts.texts) { throwOnParticularTextsMissing(); } var locales = Object.keys(texts.texts); locales.forEach(function (locale) { var record = texts.texts[locale]; Object.keys(record).forEach(function (key) { var value = record[key]; cache.put(key, locale, value); }); }); } function configure(configData) { // set flag to indicate that the package has been configured config.initialized = true; // update by given config data Object.keys(configData).forEach(function (key) { config[key] = configData[key]; }); var requiresToken = config.updateStrategy === 'fetchOnExpired' || config.enableWebsocket; if (requiresToken && !config.token) { throw Error('Particular.Cloud i18n: token must be set for fetchOnExpired and enableWebsocket'); } if (config.acceptLanguage && !config.defaultLanguage) { throw Error('Particular.Cloud i18n: defaultLanguage must be defined if acceptLanguage is used'); } if (config.defaultLanguage) { if (!texts.languages) { throwOnParticularTextsMissing(); } var language = texts.languages.find(function (l) { return l.language.langCode === config.defaultLanguage || l.language.locale === config.defaultLanguage; }); if (!language) { console.warn("Particular.Cloud i18n: defaultLanguage ".concat(config.defaultLanguage, " specified but not present in @particular.cloud/texts project data. Make sure your defaultLanguage is present in your Particular.Cloud project and update your local data by running: npx particular.cloud texts.")); } else if (!language.isActive && config.activeLanguagesOnly) { throw Error("Particular.Cloud i18n: defaultLanguage ".concat(config.defaultLanguage, " specified but not active in @particular.cloud/texts project data. Make sure your defaultLanguage is active in your Particular.Cloud project and update your local data by running: npx particular.cloud texts.")); } } if (config.customCache) { var customCache = config.customCache; if (!(customCache === null || customCache === void 0 ? void 0 : customCache.put) && !(customCache === null || customCache === void 0 ? void 0 : customCache.retrieve)) { throw Error('Particular.Cloud i18n: customCache must implement "put" and "retrieve" functions'); } } // trigger callback functions if (config.defaultLanguage && config.onChangeDefaultLanguage) { config.onChangeDefaultLanguage(config.defaultLanguage); } var langCodeOrLocale = selectLanguage(config.activeLanguagesOnly, undefined, config.acceptLanguage, config.defaultLanguage, texts.languages, undefined)[0]; if (langCodeOrLocale) { config.langCodeOrLocale = langCodeOrLocale; if (config.onChangeLanguage) { config.onChangeLanguage(config.langCodeOrLocale); } } if (config.enableWebsocket) { connect(); } if (config.onChangeConfig) { // triggered for both init and addToConfig config.onChangeConfig(internalConfigToConfig(config)); } } /** * init: set your Particular.Cloud project token and add options to your ParticularConfig (resets all prior configurations) */ function init(configData) { // reset config to default (in-case init has been called before) Object.keys(defaultConfig).forEach(function (key) { config[key] = defaultConfig[key]; }); configure(configData || {}); // load texts from @particular.cloud/texts into in-memory cache initMemory(); } /** * addToConfig: add options to your ParticularConfig without resetting any prior configurations */ function addToConfig(configData) { configure(configData); } /** * isInitialized: returns true if init or addToConfig has been called before */ function isInitialized() { return config.initialized; } function retrieveFromCache(key, language) { return __awaiter(this, void 0, void 0, function () { var _a, textValue, expired, retrieveVal; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = retrieveLangCodeOrLocale(key, language, texts.languages), textValue = _a[0], expired = _a[1]; // undefined means not found (!empty string is possible string value) if (textValue !== undefined) { return [2 /*return*/, [textValue, expired]]; } // no custom cache, return as not found if (!config.customCache) { return [2 /*return*/, [undefined, false]]; } return [4 /*yield*/, config.customCache.retrieve(key, language)]; case 1: retrieveVal = _b.sent(); // check if customCache implements RetrieveValue if (Array.isArray(retrieveVal) && retrieveVal.length === 2 && typeof retrieveVal[1] === 'boolean') { return [2 /*return*/, retrieveVal]; } return [2 /*return*/, [retrieveVal, false]]; } }); }); } function storeInCache(key, language, value) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: cache.put(key, language, value); if (!config.customCache) return [3 /*break*/, 2]; return [4 /*yield*/, config.customCache.put(key, language, value)]; case 1: _a.sent(); _a.label = 2; case 2: return [2 /*return*/]; } }); }); } var reqQuery = []; var timeout; var requestIsOff = false; var resolvePromise; var queryPromise; function queue(key, language) { return __awaiter(this, void 0, void 0, function () { var inQuery, texts, text; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: // collect all queries until timeout triggers if (timeout) { clearTimeout(timeout); } inQuery = reqQuery.find(function (_a) { var l = _a[0], k = _a[1]; return k === key && l === language; }); // reset queue if request is off and current query is not in queue if (requestIsOff && !inQuery) { queryPromise = undefined; requestIsOff = false; reqQuery = []; } if (!inQuery) { // push only if not already in queue reqQuery.push([language, key]); } if (!queryPromise) { queryPromise = new Promise(function (resolve) { resolvePromise = resolve; }); } timeout = setTimeout(function () { return __awaiter(_this, void 0, void 0, function () { var texts; return __generator(this, function (_a) { switch (_a.label) { case 0: requestIsOff = true; return [4 /*yield*/, queryTexts(reqQuery)]; case 1: texts = _a.sent(); resolvePromise(texts); return [2 /*return*/]; } }); }); }); return [4 /*yield*/, queryPromise]; case 1: texts = _a.sent(); text = texts.find(function (p) { return p.key === key; }); return [2 /*return*/, text || undefined]; } }); }); } function query(key, language, source) { if (source === void 0) { source = 'none'; } return __awaiter(this, void 0, void 0, function () { var hasAcceptLanguage, languageSelectedAtRandom, weShouldFetchFromServer, _a, cachedValue, expired, text; return __generator(this, function (_b) { switch (_b.label) { case 0: hasAcceptLanguage = config.acceptLanguage; languageSelectedAtRandom = source === 'random'; weShouldFetchFromServer = hasAcceptLanguage && languageSelectedAtRandom; if (!(language && !weShouldFetchFromServer)) return [3 /*break*/, 2]; return [4 /*yield*/, retrieveFromCache(key, language)]; case 1: _a = _b.sent(), cachedValue = _a[0], expired = _a[1]; if (cachedValue !== undefined) { if (expired) { // if expired, fetch new one but return stale value immediatly (no await) queue(key, language).then(function (text) { if (text) { storeInCache(text.key, text.locale, text.value); } }); } // stale value return [2 /*return*/, [cachedValue, language]]; } _b.label = 2; case 2: return [4 /*yield*/, queue(key, weShouldFetchFromServer ? undefined : language)]; case 3: text = _b.sent(); if (text) { storeInCache(text.key, text.locale, text.value); } return [2 /*return*/, text ? [text.value, text.locale] : undefined]; } }); }); } /** * getLangCodeOrLocale: returns the currently used global languageCode or locale * Changes based on new t (translation) calls * and based on succesfull queries to Particular.Cloud servers */ function getLangCodeOrLocale() { return config.langCodeOrLocale; } /** * setGlobalLangCodeOrLocale: switches the currently used global languageCode or locale * (for internal use only) */ function setGlobalLangCodeOrLocale(langCodeOrLocale) { config.langCodeOrLocale = langCodeOrLocale; if (config.onChangeLanguage) { config.onChangeLanguage(langCodeOrLocale); } } /** * setDefaultLanguage: switch the default language * Use undefined to delete the default language */ function setDefaultLanguage(language) { if (!language && config.acceptLanguage) { throw Error('Particular.Cloud i18n: defaultLanguage must be defined if acceptLanguage is used'); } config.defaultLanguage = language; if (config.onChangeDefaultLanguage) { config.onChangeDefaultLanguage(language); } if (config.onChangeConfig) { // default language is part of config, so this is a config change config.onChangeConfig(internalConfigToConfig(config)); } } /** * getDefaultLanguage: returns the current default language */ function getDefaultLanguage() { return config.defaultLanguage; } /** * setAcceptLanguage: set the accepted language (in Accept-Lanuage HTTP header format) * Use undefined to delete the accept-language */ function setAcceptLanguage(acceptLanguage) { config.acceptLanguage = acceptLanguage; } /** * getAcceptLanguage: returns the current accept-language */ function getAcceptLanguage() { return config.acceptLanguage; } function getFromArray(array) { if (config.onPickStringFromArray === 'pickFirst') { return array[0]; } if (config.onPickStringFromArray === 'pickRandom') { return pickRandomFromArray(array); } throw Error("Particular.Cloud i18n: onPickStringFromArray has wrong value ".concat(config.onPickStringFromArray)); } function handleLeftOverTemplateSyntax(string) { if (config.onLeftOverTemplateSyntax === 'ignore') { return; } if (config.onLeftOverTemplateSyntax === 'warn') { warnForTemplateSyntax(string); return; } if (config.onLeftOverTemplateSyntax === 'throw') { throwForTemplateSyntax(string); return; } throw Error("Particular.Cloud i18n: onLeftOverTemplateSyntax has wrong value ".concat(config.throwForTemplateSyntax)); } function handleNoKey() { throw Error('Particular.Cloud i18n: t called without providing a key'); } function handleValueUndefined(key) { if (config.onKeyNotFound === 'warn') { console.warn("Particular.Cloud i18n: no values found for key ".concat(key)); } if (config.onKeyNotFound === 'throw') { throw Error("Particular.Cloud i18n: no values found for key ".concat(key)); } } /** * parseValue: helper function to populate one text with a set of given template values */ function parseValue$1(key, value, values) { // empty string is a valid value if (value === undefined) { handleValueUndefined(key); return ''; } var template = Array.isArray(value) ? getFromArray(value) : value; var result = template; if (values) { var stringValues_1 = {}; Object.entries(values).forEach(function (_a) { var k = _a[0], val = _a[1]; stringValues_1[k] = Array.isArray(val) ? getFromArray(val) : val; }); result = populateValues(template, stringValues_1); } handleLeftOverTemplateSyntax(result); return result; } function throwOnLanguageMissing(language, defaultLanguage, acceptLanguage) { if (!language && !defaultLanguage && !acceptLanguage) { throw Error('Particular.Cloud i18n: t (translate) or fetchT called without language, default or accept language. Please provide a language.'); } if (acceptLanguage && !defaultLanguage) { throw Error('Particular.Cloud i18n: t (translate) or fetchT called with Accept-Language header and without defaultLanguage. Please provide a fallback defaultLanguage.'); } } /** * fetchT (translate/text): query and populate your localized application text */ function fetchT(_a) { var key = _a.key, language = _a.language, values = _a.values, isolate = _a.isolate; return __awaiter(this, void 0, void 0, function () { var _b, langCodeOrLocale, source, text, textValue, textLocale; return __generator(this, function (_c) { switch (_c.label) { case 0: if (!key) { handleNoKey(); } if (!config.token) { handleError(null, config.onError, 'Particular.Cloud i18n: fetchT called without providing an API token. Please call the init function first and pass your Particular.Cloud read-access token.'); } _b = selectLanguage(config.activeLanguagesOnly, language, config.acceptLanguage, config.defaultLanguage, texts.languages, config.langCodeOrLocale), langCodeOrLocale = _b[0], source = _b[1]; if (!langCodeOrLocale) { throwOnLanguageMissing(language, config.defaultLanguage, config.acceptLanguage); } return [4 /*yield*/, query(key, langCodeOrLocale, source)]; case 1: text = _c.sent(); textValue = text ? text[0] : undefined; textLocale = text ? text[1] : undefined; if (textLocale && !isolate) { setGlobalLangCodeOrLocale(textLocale); } return [2 /*return*/, parseValue$1(key, textValue, values)]; } }); }); } function tFetchHelper(_a, cb) { var key = _a.key, language = _a.language, values = _a.values, isolate = _a.isolate; // fetchT takes care of setGlobalLangCodeOrLocale fetchT({ key: key, language: language, values: values, isolate: isolate }).then(function (fetchedValue) { // and if cb is defined, return the fetched & parsed value if (cb) { cb(fetchedValue); } }); } /** * t (translate/text): query and populate your localized application text * the callback is only triggered if the text is not found in memory or has expired */ function t(_a, cb) { var key = _a.key, language = _a.language, values = _a.values, isolate = _a.isolate; if (!key) { handleNoKey(); } var _b = selectLanguage(config.activeLanguagesOnly, language, config.acceptLanguage, config.defaultLanguage, texts.languages, config.langCodeOrLocale), langCodeOrLocale = _b[0], source = _b[1]; if (!langCodeOrLocale) { throwOnLanguageMissing(language, config.defaultLanguage, config.acceptLanguage); } var hasAcceptLanguage = !!config.acceptLanguage; var betterLanguageCouldBeOnServer = source === 'random' || source === 'acceptLanguage'; var weShouldFetchFromServer = hasAcceptLanguage && betterLanguageCouldBeOnServer && config.updateStrategy === 'fetchOnExpired'; var useOptimisticResult = source !== 'random'; if (weShouldFetchFromServer) { tFetchHelper({ key: key, language: language, values: values, isolate: isolate }, cb); if (useOptimisticResult) { // langCodeOrLocale is string as we would have thrown earlier otherwise var value_1 = retrieveLangCodeOrLocale(key, langCodeOrLocale, texts.languages)[0]; // if we fetch new value async, globalLangCodeOrLocal will be updated again // if we dont fetch or are confident in the current cached value, we set globalLangCodeOrLocale already if (langCodeOrLocale && (config.updateStrategy === 'never' || !weShouldFetchFromServer)) { setGlobalLangCodeOrLocale(langCodeOrLocale); } return parseValue$1(key, value_1, values); } return ''; } // fetch best fitting value from local memory if (source === 'acceptLanguage' && config.acceptLanguage) { var acceptedLanguages = parseAcceptHeader(config.acceptLanguage); for (var _i = 0, acceptedLanguages_1 = acceptedLanguages; _i < acceptedLanguages_1.length; _i++) { var accepted = acceptedLanguages_1[_i]; var value_2 = retrieveLangCodeOrLocale(key, accepted, texts.languages)[0]; // we dont have to check for updateStrategy since otherwise we would have run into case "weShouldFetchFromServer" if (value_2) { setGlobalLangCodeOrLocale(accepted); return parseValue$1(key, value_2, values); } } } // langCodeOrLocale is string as we would have thrown earlier otherwise var _c = retrieveLangCodeOrLocale(key, langCodeOrLocale, texts.languages), value = _c[0], expired = _c[1]; // if value has expired or not found in cache and user wants to update from Particular.Cloud if ((expired || value === undefined) && config.updateStrategy === 'fetchOnExpired') { // then fetch new value, store in cache tFetchHelper({ key: key, language: language, values: values, isolate: isolate }, cb); } // if we fetch new value async, globalLangCodeOrLocal will be updated again // if we dont fetch or are confident in the current cached value, we set globalLangCodeOrLocale already if (langCodeOrLocale && (config.updateStrategy === 'never' || !weShouldFetchFromServer)) { setGlobalLangCodeOrLocale(langCodeOrLocale); } return parseValue$1(key, value, values); } function parseValue(value, stringOnly) { if (Array.isArray(value) && stringOnly) { if (!value.length) { return ''; } else if (config.onPickStringFromArray === 'pickFirst') { return value[0]; } else { return pickRandomFromArray(value); } } return value; } /** * fetchTranslationRecord: fetch the application text for specific language from your Particular.Cloud project * @param locale the locale of the language. If acceptLanguage parameter is provided, locale is used as the default/fallback locale * @param stringOnly if set to true, every value will be of type string. Arrays will be resolved based on your config settings. */ function fetchTranslationRecord(locale, stringOnly) { if (stringOnly === void 0) { stringOnly = false; } return __awaiter(this, void 0, void 0, function () { var texts, records; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, fetchTexts(locale)]; case 1: