@tolgee/core
Advanced tools
Comparing version 4.10.0-rc.f068ae1.0 to 5.0.0-alpha.1
@@ -5,21 +5,14 @@ 'use strict'; | ||
const EventEmitter = () => { | ||
let handlers = []; | ||
const listen = (handler) => { | ||
const handlerWrapper = (e) => { | ||
handler(e); | ||
}; | ||
handlers.push(handlerWrapper); | ||
return { | ||
unsubscribe: () => { | ||
handlers = handlers.filter((i) => handlerWrapper !== i); | ||
}, | ||
}; | ||
}; | ||
const emit = (data) => { | ||
handlers.forEach((handler) => handler({ value: data })); | ||
}; | ||
return Object.freeze({ listen, emit }); | ||
function isPromise(value) { | ||
return Boolean(value && typeof value.then === 'function'); | ||
} | ||
const valueOrPromise = (value, callback) => { | ||
if (isPromise(value)) { | ||
return Promise.resolve(value).then(callback); | ||
} | ||
else { | ||
return callback(value); | ||
} | ||
}; | ||
const missingOptionError = (option) => `Tolgee: You need to specify '${option}' option`; | ||
function isObject(item) { | ||
@@ -51,18 +44,26 @@ return typeof item === 'object' && !Array.isArray(item) && item !== null; | ||
} | ||
function sanitizeUrl(url) { | ||
return url ? url.replace(/\/+$/, '') : url; | ||
} | ||
function incrementInMap(map, value) { | ||
const currNum = map.get(value) || 0; | ||
map.set(value, currNum + 1); | ||
} | ||
function decrementInMap(map, value) { | ||
let currNum = map.get(value) || 1; | ||
currNum -= 1; | ||
if (currNum <= 0) { | ||
map.delete(value); | ||
} | ||
else { | ||
map.set(value, currNum); | ||
} | ||
} | ||
const EventEmitterSelective = (getFallbackNamespaces) => { | ||
const EventEmitter = () => { | ||
let handlers = []; | ||
const listen = (handler) => { | ||
const handlerWrapper = (e) => { | ||
handler(e); | ||
}; | ||
handlers.push(handlerWrapper); | ||
return { | ||
unsubscribe: () => { | ||
handlers = handlers.filter((i) => handlerWrapper !== i); | ||
}, | ||
}; | ||
}; | ||
const emit = (data) => { | ||
handlers.forEach((handler) => handler({ value: data })); | ||
}; | ||
return Object.freeze({ listen, emit }); | ||
}; | ||
const EventEmitterSelective = (getFallbackNs, getDefaultNs) => { | ||
const listeners = new Set(); | ||
@@ -84,4 +85,3 @@ const partialListeners = new Set(); | ||
}, | ||
keys: new Map(), | ||
namespaces: new Map(), | ||
namespaces: new Set(), | ||
}; | ||
@@ -94,48 +94,20 @@ partialListeners.add(handlerWrapper); | ||
subscribeNs: (ns) => { | ||
getFallbackArray(ns).forEach((val) => incrementInMap(handlerWrapper.namespaces, val)); | ||
return result; | ||
}, | ||
unsubscribeNs: (ns) => { | ||
getFallbackArray(ns).forEach((val) => decrementInMap(handlerWrapper.namespaces, val)); | ||
return result; | ||
}, | ||
subscribeKey: (descriptor) => { | ||
const { key, ns } = descriptor; | ||
incrementInMap(handlerWrapper.keys, key); | ||
getFallbackArray(ns).forEach((val) => incrementInMap(handlerWrapper.namespaces, val)); | ||
getFallbackArray(ns).forEach((val) => handlerWrapper.namespaces.add(val)); | ||
if (ns === undefined) { | ||
// subscribing to all namespaces | ||
incrementInMap(handlerWrapper.namespaces, undefined); | ||
// subscribing to default ns | ||
handlerWrapper.namespaces.add(getDefaultNs()); | ||
} | ||
return result; | ||
}, | ||
unsubscribeKey: (descriptor) => { | ||
const { key, ns } = descriptor; | ||
decrementInMap(handlerWrapper.keys, key); | ||
getFallbackArray(ns).forEach((val) => decrementInMap(handlerWrapper.namespaces, val)); | ||
if (ns === undefined) { | ||
// subscribing to all namespaces | ||
decrementInMap(handlerWrapper.namespaces, undefined); | ||
} | ||
return result; | ||
}, | ||
}; | ||
return result; | ||
}; | ||
const namespacesWithFallbacks = (namespaces) => { | ||
if (namespaces.has(undefined)) { | ||
const result = new Set(namespaces.keys()); | ||
result.delete(undefined); | ||
getFallbackNamespaces().forEach((ns) => result.add(ns)); | ||
return result; | ||
} | ||
return namespaces; | ||
}; | ||
const callHandlers = (key, ns) => { | ||
const callHandlers = (ns) => { | ||
// everything is implicitly subscribed to fallbacks | ||
// as it can always fall through to it | ||
const fallbackNamespaces = new Set(getFallbackNs()); | ||
partialListeners.forEach((handler) => { | ||
const handlerNamespaces = namespacesWithFallbacks(handler.namespaces); | ||
const nsMatches = ns === undefined || | ||
(ns === null || ns === void 0 ? void 0 : ns.findIndex((ns) => handlerNamespaces.has(ns))) !== -1; | ||
const keyMatches = key === undefined || handler.keys.has(key) || handler.keys.size === 0; | ||
if (nsMatches && keyMatches) { | ||
(ns === null || ns === void 0 ? void 0 : ns.findIndex((ns) => fallbackNamespaces.has(ns) || handler.namespaces.has(ns))) !== -1; | ||
if (nsMatches) { | ||
handler.fn({ value: undefined }); | ||
@@ -151,31 +123,24 @@ } | ||
} | ||
const queueCopy = queue; | ||
queue = []; | ||
listeners.forEach((handler) => { | ||
handler({ value: undefined }); | ||
}); | ||
const namespaces = new Set(); | ||
let keys = new Set(); | ||
queue.forEach((descriptor) => { | ||
if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.ns) === undefined) { | ||
// when no ns specified, it affets all fallback namespaces | ||
namespaces.add(undefined); | ||
let namespaces = new Set(); | ||
queueCopy.forEach((ns) => { | ||
if (ns === undefined) { | ||
// when no ns specified, it affects all namespaces | ||
namespaces = undefined; | ||
} | ||
else { | ||
descriptor.ns.forEach((ns) => namespaces.add(ns)); | ||
else if (namespaces !== undefined) { | ||
ns.forEach((ns) => namespaces.add(ns)); | ||
} | ||
if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.key) === undefined) { | ||
// when no key specified, it affects all keys | ||
keys = undefined; | ||
} | ||
else if (keys !== undefined) { | ||
keys.add(descriptor.key); | ||
} | ||
}); | ||
const namespacesArray = Array.from(namespacesWithFallbacks(namespaces).keys()); | ||
(keys || [undefined]).forEach((key) => { | ||
callHandlers(key, namespacesArray); | ||
}); | ||
queue = []; | ||
const namespacesArray = namespaces | ||
? Array.from(namespaces.keys()) | ||
: undefined; | ||
callHandlers(namespacesArray); | ||
}; | ||
const emit = (descriptor, delayed) => { | ||
queue.push(descriptor); | ||
const emit = (ns, delayed) => { | ||
queue.push(ns); | ||
if (!delayed) { | ||
@@ -185,5 +150,3 @@ solveQueue(); | ||
else { | ||
Promise.resolve().then(() => { | ||
solveQueue(); | ||
}); | ||
setTimeout(solveQueue, 0); | ||
} | ||
@@ -194,16 +157,15 @@ }; | ||
const Events = (getFallbackNamespaces) => { | ||
const Events = (getFallbackNs, getDefaultNs) => { | ||
const onPendingLanguageChange = EventEmitter(); | ||
const onLanguageChange = EventEmitter(); | ||
const onKeyChange = EventEmitter(); | ||
const onLoadingChange = EventEmitter(); | ||
const onFetchingChange = EventEmitter(); | ||
const onInitialLoaded = EventEmitter(); | ||
const onKeyUpdate = EventEmitterSelective(getFallbackNamespaces); | ||
const onRunningChange = EventEmitter(); | ||
const onCacheChange = EventEmitter(); | ||
const onRunningChange = EventEmitter(); | ||
onInitialLoaded.listen(() => onKeyUpdate.emit()); | ||
onLanguageChange.listen(() => onKeyUpdate.emit()); | ||
const onUpdate = EventEmitterSelective(getFallbackNs, getDefaultNs); | ||
onInitialLoaded.listen(() => onUpdate.emit()); | ||
onLanguageChange.listen(() => onUpdate.emit()); | ||
onCacheChange.listen(({ value }) => { | ||
onKeyUpdate.emit({ ns: [value.namespace], key: value.key }, true); | ||
onUpdate.emit([value.namespace], true); | ||
}); | ||
@@ -226,4 +188,4 @@ const on = (event, handler) => { | ||
return onCacheChange.listen(handler); | ||
case 'keyUpdate': | ||
return onKeyUpdate.listen(handler); | ||
case 'update': | ||
return onUpdate.listen(handler); | ||
} | ||
@@ -234,4 +196,2 @@ }; | ||
onLanguageChange, | ||
onKeyChange, | ||
onKeyUpdate, | ||
onLoadingChange, | ||
@@ -242,2 +202,3 @@ onFetchingChange, | ||
onCacheChange, | ||
onUpdate, | ||
on, | ||
@@ -265,3 +226,5 @@ }); | ||
const decodeCacheKey = (key) => { | ||
const [firstPart, secondPart] = key.split(':'); | ||
const [firstPart, ...rest] = key.split(':'); | ||
// if namespaces contains ":" it won't get lost | ||
const secondPart = rest.join(':'); | ||
return { language: firstPart, namespace: secondPart || '' }; | ||
@@ -334,7 +297,7 @@ }; | ||
if (value !== undefined && value !== null) { | ||
return namespace; | ||
return [namespace]; | ||
} | ||
} | ||
} | ||
return Array.from(new Set(namespaces)); | ||
return unique(namespaces); | ||
} | ||
@@ -382,3 +345,6 @@ function getTranslationFallback(namespaces, languages, key) { | ||
} | ||
function fetchNormal(keyObject) { | ||
/** | ||
* Fetches production data | ||
*/ | ||
function fetchProd(keyObject) { | ||
let dataPromise = undefined; | ||
@@ -394,6 +360,2 @@ if (!dataPromise) { | ||
} | ||
if (!dataPromise) { | ||
// return empty data, so we know it has already been attempted to fetch | ||
dataPromise = Promise.resolve({}); | ||
} | ||
return dataPromise; | ||
@@ -408,8 +370,8 @@ } | ||
console.warn(`Tolgee: Failed to fetch data from dev backend`); | ||
// fallback to normal fetch if dev fails | ||
return fetchNormal(keyObject); | ||
// fallback to prod fetch if dev fails | ||
return fetchProd(keyObject); | ||
}); | ||
} | ||
if (!dataPromise) { | ||
dataPromise = fetchNormal(keyObject); | ||
dataPromise = fetchProd(keyObject); | ||
} | ||
@@ -431,3 +393,3 @@ return dataPromise; | ||
} | ||
const dataPromise = fetchData(keyObject, isDev); | ||
const dataPromise = fetchData(keyObject, isDev) || Promise.resolve(undefined); | ||
asyncRequests.set(cacheKey, dataPromise); | ||
@@ -454,2 +416,6 @@ return { | ||
} | ||
else if (!getRecord(value.keyObject)) { | ||
// if no data exist, put empty object | ||
addRecord(value.keyObject, {}); | ||
} | ||
} | ||
@@ -484,16 +450,5 @@ }); | ||
function isPromise(value) { | ||
return Boolean(value && typeof value.then === 'function'); | ||
} | ||
const valueOrPromise = (value, callback) => { | ||
if (isPromise(value)) { | ||
return Promise.resolve(value).then(callback); | ||
} | ||
else { | ||
return callback(value); | ||
} | ||
}; | ||
const missingOptionError = (option) => `Tolgee: You need to specify '${option}' option`; | ||
const PluginService = (getLanguage, getInitialOptions, getObserverOptions, getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation) => { | ||
const Plugins = (getLanguage, getInitialOptions, getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation) => { | ||
let prepared = false; | ||
let onPrepareQueue = []; | ||
const plugins = { | ||
@@ -513,34 +468,17 @@ ui: undefined, | ||
}; | ||
const onClick = async (event, { keysAndDefaults }) => { | ||
const onClick = async ({ keysAndDefaults, event }) => { | ||
var _a; | ||
const withNs = keysAndDefaults.map(({ key, ns, defaultValue }) => ({ | ||
key, | ||
defaultValue, | ||
ns: getFallbackArray(getTranslationNs({ key, ns, defaultValue })), | ||
translation: getTranslation({ | ||
const withNs = keysAndDefaults.map(({ key, ns, defaultValue }) => { | ||
return { | ||
key, | ||
ns, | ||
}), | ||
})); | ||
(_a = instances.ui) === null || _a === void 0 ? void 0 : _a.handleElementClick(event, withNs); | ||
defaultValue, | ||
ns: getTranslationNs({ key, ns }), | ||
translation: getTranslation({ | ||
key, | ||
ns, | ||
}), | ||
}; | ||
}); | ||
(_a = instances.ui) === null || _a === void 0 ? void 0 : _a.handleElementClick(withNs, event); | ||
}; | ||
const run = (isDev) => { | ||
var _a, _b; | ||
instances.ui = | ||
plugins.ui && | ||
new plugins.ui({ | ||
apiKey: getInitialOptions().apiKey, | ||
apiUrl: getInitialOptions().apiUrl, | ||
highlight, | ||
changeTranslation, | ||
}); | ||
if (!instances.observer) { | ||
instances.observer = (_a = plugins.observer) === null || _a === void 0 ? void 0 : _a.call(plugins, { | ||
translate, | ||
onClick, | ||
options: getObserverOptions(), | ||
}); | ||
} | ||
(_b = instances.observer) === null || _b === void 0 ? void 0 : _b.run({ mouseHighlight: isDev }); | ||
}; | ||
const stop = () => { | ||
@@ -556,3 +494,6 @@ var _a; | ||
const translate = (props) => { | ||
const translation = getTranslation(props); | ||
const translation = getTranslation({ | ||
key: props.key, | ||
ns: props.ns, | ||
}); | ||
return formatTranslation(Object.assign(Object.assign({}, props), { translation, formatEnabled: true })); | ||
@@ -583,5 +524,2 @@ }; | ||
}; | ||
const getLanguageStorage = () => { | ||
return instances.languageStorage; | ||
}; | ||
const setStoredLanguage = (language) => { | ||
@@ -626,2 +564,23 @@ var _a; | ||
}; | ||
const run = (isDev) => { | ||
var _a, _b; | ||
if (!instances.ui && plugins.ui) { | ||
const { apiKey, apiUrl, projectId } = getInitialOptions(); | ||
instances.ui = new plugins.ui({ | ||
apiKey: apiKey, | ||
apiUrl: apiUrl, | ||
projectId, | ||
highlight, | ||
changeTranslation, | ||
}); | ||
} | ||
if (!instances.observer) { | ||
instances.observer = (_a = plugins.observer) === null || _a === void 0 ? void 0 : _a.call(plugins, { | ||
translate, | ||
onClick, | ||
options: getInitialOptions().observerOptions, | ||
}); | ||
} | ||
(_b = instances.observer) === null || _b === void 0 ? void 0 : _b.run({ mouseHighlight: isDev }); | ||
}; | ||
const getDevBackend = () => { | ||
@@ -632,5 +591,7 @@ return instances.devBackend; | ||
var _a; | ||
const { apiKey, apiUrl, projectId } = getInitialOptions(); | ||
return (_a = instances.devBackend) === null || _a === void 0 ? void 0 : _a.getRecord({ | ||
apiKey: getInitialOptions().apiKey, | ||
apiUrl: getInitialOptions().apiUrl, | ||
apiKey, | ||
apiUrl, | ||
projectId, | ||
language, | ||
@@ -656,4 +617,37 @@ namespace, | ||
}; | ||
const formatTranslation = ({ key, translation, defaultValue, noWrap, params, orEmpty, ns, formatEnabled, }) => { | ||
const unwrap = (text) => { | ||
var _a; | ||
if (instances.observer) { | ||
return (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.unwrap(text); | ||
} | ||
return { text, keys: [] }; | ||
}; | ||
const retranslate = () => { | ||
var _a; | ||
(_a = instances.observer) === null || _a === void 0 ? void 0 : _a.retranslate(); | ||
}; | ||
const onPrepare = (callback) => { | ||
onPrepareQueue.push(callback); | ||
}; | ||
function addPlugin(tolgeeInstance, plugin) { | ||
const pluginTools = Object.freeze({ | ||
setFinalFormatter, | ||
addFormatter, | ||
setObserver, | ||
hasObserver, | ||
setUi, | ||
hasUi, | ||
setDevBackend, | ||
addBackend, | ||
setLanguageDetector, | ||
setLanguageStorage, | ||
onPrepare, | ||
}); | ||
plugin(tolgeeInstance, pluginTools); | ||
if (prepared) { | ||
prepare(); | ||
} | ||
} | ||
function formatTranslation({ key, translation, defaultValue, noWrap, params, orEmpty, ns, formatEnabled, }) { | ||
var _a; | ||
const formattableTranslation = translation || defaultValue; | ||
@@ -692,3 +686,6 @@ let result = formattableTranslation || (orEmpty ? '' : key); | ||
return result; | ||
}; | ||
} | ||
function hasDevBackend() { | ||
return Boolean(getDevBackend()); | ||
} | ||
const wrap = (params) => { | ||
@@ -701,30 +698,18 @@ var _a; | ||
}; | ||
const unwrap = (text) => { | ||
var _a; | ||
if (instances.observer) { | ||
return (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.unwrap(text); | ||
function prepare() { | ||
prepared = true; | ||
while (onPrepareQueue.length) { | ||
const queue = onPrepareQueue; | ||
onPrepareQueue = []; | ||
queue.forEach((callback) => callback()); | ||
} | ||
return { text, keys: [] }; | ||
}; | ||
const retranslate = () => { | ||
var _a; | ||
(_a = instances.observer) === null || _a === void 0 ? void 0 : _a.retranslate(); | ||
}; | ||
} | ||
return Object.freeze({ | ||
setFinalFormatter, | ||
addFormatter, | ||
prepare, | ||
addPlugin, | ||
formatTranslation, | ||
setObserver, | ||
hasObserver, | ||
setUi, | ||
hasUi, | ||
addBackend, | ||
setDevBackend, | ||
getDevBackend, | ||
getBackendRecord, | ||
getBackendDevRecord, | ||
setLanguageDetector, | ||
getLanguageDetector, | ||
setLanguageStorage, | ||
getLanguageStorage, | ||
getInitialLanguage, | ||
@@ -736,4 +721,5 @@ setStoredLanguage, | ||
highlight, | ||
unwrap, | ||
wrap, | ||
unwrap, | ||
hasDevBackend, | ||
}); | ||
@@ -760,3 +746,3 @@ }; | ||
const defaultValues$1 = { | ||
const defaultObserverOptions = { | ||
tagAttributes: { | ||
@@ -776,15 +762,19 @@ textarea: ['placeholder'], | ||
}; | ||
const initObserverOptions = (options) => { | ||
return Object.assign(Object.assign({}, defaultValues$1), options); | ||
}; | ||
const defaultValues = { | ||
defaultNs: '', | ||
filesUrlPrefix: 'i18n/', | ||
observerOptions: defaultObserverOptions, | ||
observerType: 'invisible', | ||
}; | ||
const combineOptions = (...states) => { | ||
let result = {}; | ||
states.forEach((state) => { | ||
result = Object.assign(Object.assign(Object.assign({}, result), state), { observerOptions: Object.assign(Object.assign({}, result.observerOptions), state === null || state === void 0 ? void 0 : state.observerOptions) }); | ||
}); | ||
return result; | ||
}; | ||
const initState = (options, previousState) => { | ||
const initialOptions = Object.assign(Object.assign(Object.assign({}, defaultValues), previousState === null || previousState === void 0 ? void 0 : previousState.initialOptions), options); | ||
const initialOptions = combineOptions(defaultValues, previousState === null || previousState === void 0 ? void 0 : previousState.initialOptions, options); | ||
// remove extra '/' from url end | ||
const apiUrl = initialOptions.apiUrl; | ||
initialOptions.apiUrl = apiUrl ? apiUrl.replace(/\/+$/, '') : apiUrl; | ||
initialOptions.apiUrl = sanitizeUrl(initialOptions.apiUrl); | ||
return { | ||
@@ -802,4 +792,3 @@ initialOptions, | ||
let state = initState(); | ||
let observerOptions = initObserverOptions(); | ||
let devCredentials = {}; | ||
let devCredentials = undefined; | ||
function init(options) { | ||
@@ -871,2 +860,3 @@ state = initState(options, state); | ||
...(state.initialOptions.ns || [state.initialOptions.defaultNs]), | ||
...getFallbackArray(state.initialOptions.fallbackNs), | ||
...state.activeNamespaces.keys(), | ||
@@ -885,8 +875,8 @@ ]); | ||
} | ||
function getFallbackNamespaces() { | ||
const defaultNs = state.initialOptions.defaultNs; | ||
const fallbackNs = state.initialOptions.fallbackNs; | ||
const fallbackNamespaces = typeof defaultNs === 'string' ? [defaultNs] : []; | ||
return unique([...fallbackNamespaces, ...getFallbackArray(fallbackNs)]); | ||
function getFallbackNs() { | ||
return getFallbackArray(state.initialOptions.fallbackNs); | ||
} | ||
function getDefaultNs(ns) { | ||
return ns === undefined ? state.initialOptions.defaultNs : ns; | ||
} | ||
function getAvailableLanguages() { | ||
@@ -910,10 +900,9 @@ if (state.initialOptions.availableLanguages) { | ||
function overrideCredentials(credentials) { | ||
devCredentials = credentials; | ||
if (credentials) { | ||
devCredentials = Object.assign(Object.assign({}, credentials), { apiUrl: sanitizeUrl(credentials.apiUrl) }); | ||
} | ||
else { | ||
devCredentials = undefined; | ||
} | ||
} | ||
function setObserverOptions(options) { | ||
observerOptions = initObserverOptions(options); | ||
} | ||
function getObserverOptions() { | ||
return observerOptions; | ||
} | ||
return Object.freeze({ | ||
@@ -934,8 +923,7 @@ init, | ||
getFallbackLangs, | ||
getFallbackNamespaces, | ||
getFallbackNs, | ||
getDefaultNs, | ||
getAvailableLanguages, | ||
withDefaultNs, | ||
overrideCredentials, | ||
setObserverOptions, | ||
getObserverOptions, | ||
}); | ||
@@ -977,7 +965,6 @@ }; | ||
orEmpty: orEmpty, | ||
params: Object.assign(Object.assign({}, rest), params), | ||
}; | ||
return options; | ||
return Object.assign(Object.assign({}, options), { params: Object.assign({}, rest) }); | ||
} | ||
const getTranslateParams = (keyOrProps, ...params) => { | ||
const getTranslateProps = (keyOrProps, ...params) => { | ||
let result = {}; | ||
@@ -1005,7 +992,7 @@ let options; | ||
const Controller = ({ options }) => { | ||
const events = Events(getFallbackNamespaces); | ||
const events = Events(getFallbackNs, getDefaultNs); | ||
const fetchingObserver = ValueObserver(false, () => cache.isFetching(), events.onFetchingChange.emit); | ||
const loadingObserver = ValueObserver(false, () => isLoading(), events.onLoadingChange.emit); | ||
const state = State(events.onLanguageChange, events.onPendingLanguageChange, events.onRunningChange); | ||
const pluginService = PluginService(state.getLanguage, state.getInitialOptions, state.getObserverOptions, state.getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation); | ||
const pluginService = Plugins(state.getLanguage, state.getInitialOptions, state.getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation); | ||
const cache = Cache(events.onCacheChange, pluginService.getBackendRecord, pluginService.getBackendDevRecord, state.withDefaultNs, state.isInitialLoading, fetchingObserver, loadingObserver); | ||
@@ -1015,3 +1002,3 @@ if (options) { | ||
} | ||
events.onKeyUpdate.listen(() => { | ||
events.onUpdate.listen(() => { | ||
if (state.isRunning()) { | ||
@@ -1021,2 +1008,21 @@ pluginService.retranslate(); | ||
}); | ||
function getFallbackNs() { | ||
return state.getFallbackNs(); | ||
} | ||
function getDefaultNs(ns) { | ||
return state.getDefaultNs(ns); | ||
} | ||
// gets all namespaces where translation could be located | ||
// takes (ns|default, fallback ns) | ||
function getDefaultAndFallbackNs(ns) { | ||
return [...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()]; | ||
} | ||
// gets all namespaces which need to be loaded | ||
// takes (ns|default, initial ns, fallback ns, active ns) | ||
function getRequiredNamespaces(ns) { | ||
return [ | ||
...getFallbackArray(ns || getDefaultNs()), | ||
...state.getRequiredNamespaces(), | ||
]; | ||
} | ||
function changeTranslation(descriptor, key, value) { | ||
@@ -1032,5 +1038,2 @@ const keyObject = state.withDefaultNs(descriptor); | ||
} | ||
function getFallbackNamespaces() { | ||
return state.getFallbackNamespaces(); | ||
} | ||
function init(options) { | ||
@@ -1044,5 +1047,3 @@ state.init(options); | ||
function isDev() { | ||
return Boolean(state.getInitialOptions().apiKey && | ||
state.getInitialOptions().apiUrl && | ||
pluginService.getDevBackend()); | ||
return Boolean(state.getInitialOptions().apiKey && state.getInitialOptions().apiUrl); | ||
} | ||
@@ -1059,3 +1060,3 @@ async function addActiveNs(ns, forget) { | ||
const languages = state.getFallbackLangs(lang); | ||
const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces(); | ||
const namespaces = getRequiredNamespaces(ns); | ||
const result = []; | ||
@@ -1077,3 +1078,3 @@ languages.forEach((language) => { | ||
const languages = state.getFallbackLangs(language); | ||
const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces(); | ||
const namespaces = getRequiredNamespaces(ns); | ||
const result = []; | ||
@@ -1111,9 +1112,9 @@ languages.forEach((language) => { | ||
} | ||
function getTranslationNs({ key, ns, }) { | ||
const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getFallbackNamespaces(); | ||
function getTranslationNs({ key, ns }) { | ||
const languages = state.getFallbackLangs(); | ||
const namespaces = getDefaultAndFallbackNs(ns); | ||
return cache.getTranslationNs(namespaces, languages, key); | ||
} | ||
function getTranslation({ key, ns, }) { | ||
const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getFallbackNamespaces(); | ||
function getTranslation({ key, ns }) { | ||
const namespaces = getDefaultAndFallbackNs(ns); | ||
const languages = state.getFallbackLangs(); | ||
@@ -1201,3 +1202,3 @@ return cache.getTranslationFallback(namespaces, languages, key); | ||
// @ts-ignore | ||
const params = getTranslateParams(...args); | ||
const params = getTranslateProps(...args); | ||
const translation = getTranslation(params); | ||
@@ -1211,3 +1212,2 @@ return pluginService.formatTranslation(Object.assign(Object.assign({}, params), { translation })); | ||
addActiveNs, | ||
loadRequiredRecords, | ||
loadRecords, | ||
@@ -1223,19 +1223,7 @@ loadRecord, | ||
const Tolgee = (options) => { | ||
const createTolgee = (options) => { | ||
const controller = Controller({ | ||
options, | ||
}); | ||
const pluginTools = Object.freeze({ | ||
setFinalFormatter: controller.setFinalFormatter, | ||
addFormatter: controller.addFormatter, | ||
setObserver: controller.setObserver, | ||
hasObserver: controller.hasObserver, | ||
setUi: controller.setUi, | ||
hasUi: controller.hasUi, | ||
setDevBackend: controller.setDevBackend, | ||
addBackend: controller.addBackend, | ||
setLanguageDetector: controller.setLanguageDetector, | ||
setLanguageStorage: controller.setLanguageStorage, | ||
overrideCredentials: controller.overrideCredentials, | ||
}); | ||
// restarts tolgee while applying callback | ||
const withRestart = (callback) => { | ||
@@ -1248,65 +1236,351 @@ const wasRunning = controller.isRunning(); | ||
const tolgee = Object.freeze({ | ||
// event listeners | ||
/** | ||
* Listen to tolgee events. | ||
*/ | ||
on: controller.on, | ||
onKeyUpdate: controller.onKeyUpdate.listenSome, | ||
// state | ||
/** | ||
* Listen for specific namespaces changes. | ||
* | ||
* ``` | ||
* const sub = tolgee.onUpdate(handler) | ||
* | ||
* // subscribe to selected namespace | ||
* sub.subscribeNs(['common']) | ||
* | ||
* // unsubscribe | ||
* sub.unsubscribe() | ||
* ``` | ||
*/ | ||
onNsUpdate: controller.onUpdate.listenSome, | ||
/** | ||
* @return current language if set. | ||
*/ | ||
getLanguage: controller.getLanguage, | ||
/** | ||
* `pendingLanguage` represents language which is currently being loaded. | ||
* @return current `pendingLanguage` if set. | ||
*/ | ||
getPendingLanguage: controller.getPendingLanguage, | ||
/** | ||
* Change current language. | ||
* - if not running sets `pendingLanguage`, `language` to the new value | ||
* - if running sets `pendingLanguage` to the value, fetches necessary data and then changes `language` | ||
* | ||
* @return Promise which is resolved when `language` is changed. | ||
*/ | ||
changeLanguage: controller.changeLanguage, | ||
/** | ||
* Temporarily change translation in cache. | ||
* @return object with revert method. | ||
*/ | ||
changeTranslation: controller.changeTranslation, | ||
/** | ||
* Adds namespace(s) list of active namespaces. And if tolgee is running, loads required data. | ||
*/ | ||
addActiveNs: controller.addActiveNs, | ||
/** | ||
* Remove namespace(s) from active namespaces. | ||
* | ||
* Tolgee internally counts how many times was each active namespace added, | ||
* so this method will remove namespace only if the counter goes down to 0. | ||
*/ | ||
removeActiveNs: controller.removeActiveNs, | ||
/** | ||
* Manually load multiple records from `Backend` (or `DevBackend` when in dev mode) | ||
* | ||
* It loads data together and adds them to cache in one operation, to prevent partly loaded state. | ||
*/ | ||
loadRecords: controller.loadRecords, | ||
/** | ||
* Manually load record from `Backend` (or `DevBackend` when in dev mode) | ||
*/ | ||
loadRecord: controller.loadRecord, | ||
/** | ||
* | ||
*/ | ||
addStaticData: controller.addStaticData, | ||
/** | ||
* Get record from cache. | ||
*/ | ||
getRecord: controller.getRecord, | ||
/** | ||
* Get all records from cache. | ||
*/ | ||
getAllRecords: controller.getAllRecords, | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if there are data that need to be fetched. | ||
*/ | ||
isLoaded: controller.isLoaded, | ||
/** | ||
* @return `true` if tolgee is loading initial data (triggered by `run`). | ||
*/ | ||
isInitialLoading: controller.isInitialLoading, | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if tolgee is loading some translations for the first time. | ||
*/ | ||
isLoading: controller.isLoading, | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if tolgee is fetching some translations. | ||
*/ | ||
isFetching: controller.isFetching, | ||
/** | ||
* @return `true` if tolgee is running. | ||
*/ | ||
isRunning: controller.isRunning, | ||
/** | ||
* Changes internal state to running: true and loads initial files. | ||
* Runs runnable plugins mainly Observer if present. | ||
*/ | ||
run: controller.run, | ||
/** | ||
* Changes internal state to running: false and stops runnable plugins. | ||
*/ | ||
stop: controller.stop, | ||
/** | ||
* Returns translated and formatted key. | ||
* If Observer is present and tolgee is running, wraps result to be identifiable in the DOM. | ||
*/ | ||
t: controller.t, | ||
/** | ||
* Highlight keys that match selection. | ||
*/ | ||
highlight: controller.highlight, | ||
/** | ||
* @return current Tolgee options. | ||
*/ | ||
getInitialOptions: controller.getInitialOptions, | ||
/** | ||
* Tolgee is in dev mode if `DevTools` plugin is used and `apiKey` + `apiUrl` are specified. | ||
* @return `true` if tolgee is in dev mode. | ||
*/ | ||
isDev: controller.isDev, | ||
/** | ||
* Wraps translation if there is `Observer` plugin | ||
*/ | ||
wrap: controller.wrap, | ||
/** | ||
* Unwrap translation | ||
*/ | ||
unwrap: controller.unwrap, | ||
// plugins | ||
setObserverOptions: (options) => { | ||
controller.setObserverOptions(options); | ||
return tolgee; | ||
/** | ||
* Override creadentials passed on initialization | ||
*/ | ||
overrideCredentials(credentials) { | ||
withRestart(() => controller.overrideCredentials(credentials)); | ||
}, | ||
use: (plugin) => { | ||
/** | ||
* Add tolgee plugin. | ||
*/ | ||
addPlugin(plugin) { | ||
if (plugin) { | ||
withRestart(() => plugin(tolgee, pluginTools)); | ||
withRestart(() => controller.addPlugin(tolgee, plugin)); | ||
} | ||
return tolgee; | ||
}, | ||
init: (options) => { | ||
withRestart(() => controller.init(options)); | ||
/** | ||
* Updates options after instance creation. Extends existing options, | ||
* so it only changes the fields, that are listed. | ||
* | ||
* When called in running state, tolgee stops and runs again. | ||
*/ | ||
updateOptions(options) { | ||
if (options) { | ||
withRestart(() => controller.init(options)); | ||
} | ||
}, | ||
}); | ||
return tolgee; | ||
}; | ||
/** | ||
* Tolgee chainable constructor. | ||
* | ||
* Usage: | ||
* ``` | ||
* const tolgee = Tolgee().use(...).init(...) | ||
* ``` | ||
*/ | ||
const Tolgee = () => { | ||
const state = { | ||
plugins: [], | ||
options: {}, | ||
}; | ||
const tolgeeChain = Object.freeze({ | ||
use(plugin) { | ||
state.plugins.push(plugin); | ||
return tolgeeChain; | ||
}, | ||
updateDefaults(options) { | ||
state.options = combineOptions(state.options, options); | ||
return tolgeeChain; | ||
}, | ||
init(options) { | ||
const tolgee = createTolgee(combineOptions(state.options, options)); | ||
state.plugins.forEach(tolgee.addPlugin); | ||
return tolgee; | ||
}, | ||
}); | ||
return tolgeeChain; | ||
}; | ||
const ERROR_PARAM_EMPTY = 0, ERROR_UNEXPECTED_CHAR = 1, ERROR_UNEXPECTED_END = 2; | ||
class FormatError extends Error { | ||
constructor(code, index, text) { | ||
let error; | ||
if (code === ERROR_PARAM_EMPTY) { | ||
error = 'Empty parameter'; | ||
} | ||
else if (code === ERROR_UNEXPECTED_CHAR) { | ||
error = 'Unexpected character'; | ||
} | ||
else { | ||
error = 'Unexpected end'; | ||
} | ||
super(`Tolgee parser: ${error} at ${index} in "${text}"`); | ||
this.code = code; | ||
this.index = index; | ||
} | ||
} | ||
function isWhitespace(ch) { | ||
return /\s/.test(ch); | ||
} | ||
const STATE_TEXT = 0, STATE_ESCAPE_MAYBE = 1, STATE_ESCAPE = 2, STATE_PARAM = 3, STATE_PARAM_AFTER = 4; | ||
const END_STATES = new Set([ | ||
STATE_ESCAPE, | ||
STATE_ESCAPE_MAYBE, | ||
STATE_TEXT, | ||
]); | ||
const CHAR_ESCAPE = "'"; | ||
const ESCAPABLE = new Set(['{', '}', CHAR_ESCAPE]); | ||
const isAllowedInParam = (char) => { | ||
return /[0-9a-zA-Z_]/.test(char); | ||
}; | ||
function formatParser(translation) { | ||
let state = STATE_TEXT; | ||
let text = ''; | ||
let param = ''; | ||
let ch = ''; | ||
const texts = []; | ||
const params = []; | ||
let i = 0; | ||
function parsingError(code) { | ||
throw new FormatError(code, i, translation); | ||
} | ||
const addText = () => { | ||
texts.push(text); | ||
text = ''; | ||
}; | ||
const addParamChar = () => { | ||
if (!isAllowedInParam(ch)) { | ||
parsingError(ERROR_UNEXPECTED_CHAR); | ||
} | ||
param += ch; | ||
}; | ||
const addParam = () => { | ||
if (param === '') { | ||
parsingError(ERROR_PARAM_EMPTY); | ||
} | ||
params.push(param); | ||
param = ''; | ||
}; | ||
for (i = 0; i < translation.length; i++) { | ||
ch = translation[i]; | ||
switch (state) { | ||
case STATE_TEXT: | ||
if (ch === CHAR_ESCAPE) { | ||
text += ch; | ||
state = STATE_ESCAPE_MAYBE; | ||
} | ||
else if (ch === '{') { | ||
addText(); | ||
state = STATE_PARAM; | ||
} | ||
else { | ||
text += ch; | ||
state = STATE_TEXT; | ||
} | ||
break; | ||
case STATE_ESCAPE_MAYBE: | ||
if (ESCAPABLE.has(ch)) { | ||
text = text.slice(0, -1) + ch; | ||
state = STATE_ESCAPE; | ||
} | ||
else { | ||
text += ch; | ||
state = STATE_TEXT; | ||
} | ||
break; | ||
case STATE_ESCAPE: | ||
if (ch === CHAR_ESCAPE) { | ||
state = STATE_TEXT; | ||
} | ||
else { | ||
text += ch; | ||
state = STATE_ESCAPE; | ||
} | ||
break; | ||
case STATE_PARAM: | ||
if (ch === '}') { | ||
addParam(); | ||
state = STATE_TEXT; | ||
} | ||
else if (!isWhitespace(ch)) { | ||
addParamChar(); | ||
state = STATE_PARAM; | ||
} | ||
else if (param !== '') { | ||
addParam(); | ||
state = STATE_PARAM_AFTER; | ||
} | ||
break; | ||
case STATE_PARAM_AFTER: | ||
if (ch == '}') { | ||
state = STATE_TEXT; | ||
} | ||
else if (isWhitespace(ch)) { | ||
state = STATE_PARAM_AFTER; | ||
} | ||
else { | ||
parsingError(ERROR_UNEXPECTED_CHAR); | ||
} | ||
} | ||
} | ||
if (!END_STATES.has(state)) { | ||
parsingError(ERROR_UNEXPECTED_END); | ||
} | ||
addText(); | ||
return [texts, params]; | ||
} | ||
function formatter(translation, params) { | ||
const [texts, pars] = formatParser(translation); | ||
const result = [texts[0]]; | ||
for (let i = 1; i < texts.length; i++) { | ||
const parameter = params === null || params === void 0 ? void 0 : params[pars[i - 1]]; | ||
if (parameter === undefined) { | ||
throw new Error(`Missing parameter "${pars[i - 1]}" in "${translation}"`); | ||
} | ||
result.push(String(parameter)); | ||
result.push(texts[i]); | ||
} | ||
return result.join(''); | ||
} | ||
function createFormatSimple() { | ||
return { | ||
format: ({ translation, params }) => formatter(translation, params), | ||
}; | ||
} | ||
const FormatSimple = () => (tolgee, tools) => { | ||
tools.setFinalFormatter(createFormatSimple()); | ||
return tolgee; | ||
}; | ||
const RESTRICTED_ASCENDANT_ATTRIBUTE = 'data-tolgee-restricted'; | ||
const TOLGEE_ATTRIBUTE_NAME = '_tolgee'; | ||
const TOLGEE_HIGHLIGHTER_CLASS = '_tolgee-highlighter'; | ||
const TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE = 'data-tolgee-key-only'; | ||
// needs to be same as in @tolgee/ui package | ||
const DEVTOOLS_ID = '__tolgee_dev_tools'; | ||
exports.DEVTOOLS_ID = DEVTOOLS_ID; | ||
exports.RESTRICTED_ASCENDANT_ATTRIBUTE = RESTRICTED_ASCENDANT_ATTRIBUTE; | ||
exports.TOLGEE_ATTRIBUTE_NAME = TOLGEE_ATTRIBUTE_NAME; | ||
exports.TOLGEE_HIGHLIGHTER_CLASS = TOLGEE_HIGHLIGHTER_CLASS; | ||
exports.TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE = TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE; | ||
exports.FormatSimple = FormatSimple; | ||
exports.Tolgee = Tolgee; | ||
exports.getFallback = getFallback; | ||
exports.getFallbackArray = getFallbackArray; | ||
exports.getTranslateParams = getTranslateParams; | ||
exports.getTranslateProps = getTranslateProps; | ||
//# sourceMappingURL=tolgee.cjs.js.map |
@@ -1,2 +0,2 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e=()=>{let e=[];return Object.freeze({listen:n=>{const t=e=>{n(e)};return e.push(t),{unsubscribe:()=>{e=e.filter((e=>t!==e))}}},emit:n=>{e.forEach((e=>e({value:n})))}})};function n(e){return"string"==typeof e?[e]:Array.isArray(e)?e:void 0}function t(e){return n(e)||[]}function a(e,n){return"object"!=typeof(a=n)||Array.isArray(a)||null===a?t(n):t(null==n?void 0:n[e]);var a}function i(e){return Array.from(new Set(e))}function o(e,n){const t=e.get(n)||0;e.set(n,t+1)}function r(e,n){let t=e.get(n)||1;t-=1,t<=0?e.delete(n):e.set(n,t)}const s=e=>{const n=new Set,a=new Set,i=n=>{if(n.has(void 0)){const t=new Set(n.keys());return t.delete(void 0),e().forEach((e=>t.add(e))),t}return n};let s=[];const g=()=>{if(0===s.length)return;n.forEach((e=>{e({value:void 0})}));const e=new Set;let t=new Set;s.forEach((n=>{void 0===(null==n?void 0:n.ns)?e.add(void 0):n.ns.forEach((n=>e.add(n))),void 0===(null==n?void 0:n.key)?t=void 0:void 0!==t&&t.add(n.key)}));const o=Array.from(i(e).keys());(t||[void 0]).forEach((e=>{((e,n)=>{a.forEach((t=>{const a=i(t.namespaces),o=void 0===n||-1!==(null==n?void 0:n.findIndex((e=>a.has(e)))),r=void 0===e||t.keys.has(e)||0===t.keys.size;o&&r&&t.fn({value:void 0})}))})(e,o)})),s=[]};return Object.freeze({listenSome:e=>{const n={fn:n=>{e(n)},keys:new Map,namespaces:new Map};a.add(n);const i={unsubscribe:()=>{a.delete(n)},subscribeNs:e=>(t(e).forEach((e=>o(n.namespaces,e))),i),unsubscribeNs:e=>(t(e).forEach((e=>r(n.namespaces,e))),i),subscribeKey:e=>{const{key:a,ns:r}=e;return o(n.keys,a),t(r).forEach((e=>o(n.namespaces,e))),void 0===r&&o(n.namespaces,void 0),i},unsubscribeKey:e=>{const{key:a,ns:o}=e;return r(n.keys,a),t(o).forEach((e=>r(n.namespaces,e))),void 0===o&&r(n.namespaces,void 0),i}};return i},listen:e=>{n.add(e);return{unsubscribe:()=>{n.delete(e)}}},emit:(e,n)=>{s.push(e),n?Promise.resolve().then((()=>{g()})):g()}})},g=e=>{const n=new Map;return Object.entries(e).forEach((([e,t])=>{null!=t&&("object"!=typeof t?n.set(e,t):g(t).forEach(((t,a)=>{n.set(e+"."+a,t)})))})),n},c=e=>{const[n,t]=e.split(":");return{language:n,namespace:t||""}},u=({language:e,namespace:n})=>n?`${e}:${n}`:e,l=(e,n,a,i,o,r,s)=>{const l=new Map,d=new Map;let f={},p=0;function v(n,t,a){const i=u(n);d.set(i,{data:g(t),version:a}),e.emit(n)}function b(e,n){v(e,n,p)}function h(e,n=!1){const t=d.get(u(e));return t&&n?t.version===p:Boolean(t)}function m(e){var n;return null===(n=d.get(u(i(e))))||void 0===n?void 0:n.data}function y(e){let t;if(!t){const n=f[u(e)];"function"==typeof n&&(t=n())}return t||(t=n(e)),t||(t=Promise.resolve({})),t}return Object.freeze({addStaticData:function(e){e&&(f=Object.assign(Object.assign({},f),e),Object.entries(e).forEach((([e,n])=>{if("function"!=typeof n){const t=c(e),a=d.get(e);a&&0!==a.version||v(t,n,0)}})))},invalidate:function(){l.clear(),p+=1},addRecord:b,exists:h,getRecord:m,getTranslation:function(e,n){var t;return null===(t=d.get(u(e)))||void 0===t?void 0:t.data.get(n)},getTranslationNs:function(e,n,t){var a;for(const i of e)for(const e of n){const n=null===(a=d.get(u({language:e,namespace:i})))||void 0===a?void 0:a.data.get(t);if(null!=n)return i}return Array.from(new Set(e))},getTranslationFallback:function(e,n,t){var a;for(const i of e)for(const e of n){const n=null===(a=d.get(u({language:e,namespace:i})))||void 0===a?void 0:a.data.get(t);if(null!=n)return n}},changeTranslation:function(n,t,a){var i;const o=null===(i=d.get(u(n)))||void 0===i?void 0:i.data;null==o||o.set(t,a),e.emit(Object.assign(Object.assign({},n),{key:t}))},isFetching:function(e){if(o())return!0;if(void 0===e)return l.size>0;const n=t(e);return Boolean(Array.from(l.keys()).find((e=>n.includes(c(e).namespace))))},isLoading:function(e,n){const a=t(n);return Boolean(o()||Array.from(l.keys()).find((n=>{const t=c(n);return(!a.length||a.includes(t.namespace))&&!h({namespace:t.namespace,language:e})})))},loadRecords:async function(e,n){const t=e.map((e=>{const t=i(e),o=u(t),r=l.get(o);if(r)return{new:!1,promise:r,keyObject:t,cacheKey:o};const s=function(e,n){var t;let i;return n&&(i=null===(t=a(e))||void 0===t?void 0:t.catch((()=>(console.warn("Tolgee: Failed to fetch data from dev backend"),y(e))))),i||(i=y(e)),i}(t,n);return l.set(o,s),{new:!0,promise:s,keyObject:t,cacheKey:o}}));r.notify(),s.notify();const o=await Promise.all(t.map((e=>e.promise)));return t.forEach(((e,n)=>{const t=l.get(e.cacheKey)!==e.promise;if(e.new&&!t){l.delete(e.cacheKey);const t=o[n];t&&b(e.keyObject,t)}})),r.notify(),s.notify(),t.map((e=>m(e.keyObject)))},getAllRecords:function(){return Array.from(d.entries()).map((([e,n])=>Object.assign(Object.assign({},c(e)),{data:n.data})))}})};function d(e){return Boolean(e&&"function"==typeof e.then)}const f=(e,n)=>d(e)?Promise.resolve(e).then(n):n(e),p=e=>`Tolgee: You need to specify '${e}' option`,v=(e,n,a,i,o,r,s)=>{const g={ui:void 0,observer:void 0},c={formatters:[],finalFormatter:void 0,observer:void 0,devBackend:void 0,backends:[],ui:void 0,languageDetector:void 0,languageStorage:void 0},u=async(e,{keysAndDefaults:n})=>{var a;const i=n.map((({key:e,ns:n,defaultValue:a})=>({key:e,defaultValue:a,ns:t(o({key:e,ns:n,defaultValue:a})),translation:r({key:e,ns:n})})));null===(a=c.ui)||void 0===a||a.handleElementClick(e,i)},l=(e,n)=>{var t,a;return(null===(a=null===(t=c.observer)||void 0===t?void 0:t.highlight)||void 0===a?void 0:a.call(t,e,n))||{unhighlight(){}}},p=e=>{const n=r(e);return v(Object.assign(Object.assign({},e),{translation:n,formatEnabled:!0}))},v=({key:n,translation:t,defaultValue:a,noWrap:i,params:o,orEmpty:r,ns:s,formatEnabled:g})=>{var u;const l=t||a;let d=l||(r?"":n);c.observer&&!i&&(d=c.observer.wrap({key:n,translation:d,defaultValue:a,params:o,ns:s}));const f=e(),p=g||!(null===(u=c.observer)||void 0===u?void 0:u.outputNotFormattable);if(l&&f&&p)for(const e of c.formatters)d=e.format({translation:d,language:f,params:o});return c.finalFormatter&&l&&f&&p&&(d=c.finalFormatter.format({translation:d,language:f,params:o})),d};return Object.freeze({setFinalFormatter:e=>{c.finalFormatter=e},addFormatter:e=>{e&&c.formatters.push(e)},formatTranslation:v,setObserver:e=>{g.observer=e},hasObserver:()=>Boolean(g.observer),setUi:e=>{g.ui=(null==e?void 0:e.UI)||e},hasUi:()=>Boolean(g.ui),addBackend:e=>{e&&c.backends.push(e)},setDevBackend:e=>{c.devBackend=e},getDevBackend:()=>c.devBackend,getBackendRecord:({language:e,namespace:n})=>{for(const t of c.backends){const a=t.getRecord({language:e,namespace:n});if(d(a))return null==a?void 0:a.catch((e=>(console.error(e),{})));if(void 0!==a)return a}},getBackendDevRecord:({language:e,namespace:t})=>{var a;return null===(a=c.devBackend)||void 0===a?void 0:a.getRecord({apiKey:n().apiKey,apiUrl:n().apiUrl,language:e,namespace:t})},setLanguageDetector:e=>{c.languageDetector=e},getLanguageDetector:()=>c.languageDetector,setLanguageStorage:e=>{c.languageStorage=e},getLanguageStorage:()=>c.languageStorage,getInitialLanguage:()=>{var e;const n=i(),t=null===(e=c.languageStorage)||void 0===e?void 0:e.getLanguage();return f(t,(e=>n&&!n.includes(e)||!e?(()=>{if(!c.languageDetector)return;const e=i();return c.languageDetector.getLanguage({availableLanguages:e})})():e))},setStoredLanguage:e=>{var n;null===(n=c.languageStorage)||void 0===n||n.setLanguage(e)},run:e=>{var t,i;c.ui=g.ui&&new g.ui({apiKey:n().apiKey,apiUrl:n().apiUrl,highlight:l,changeTranslation:s}),c.observer||(c.observer=null===(t=g.observer)||void 0===t?void 0:t.call(g,{translate:p,onClick:u,options:a()})),null===(i=c.observer)||void 0===i||i.run({mouseHighlight:e})},stop:()=>{var e;c.ui=void 0,null===(e=c.observer)||void 0===e||e.stop()},retranslate:()=>{var e;null===(e=c.observer)||void 0===e||e.retranslate()},highlight:l,wrap:e=>{var n;return c.observer?null===(n=c.observer)||void 0===n?void 0:n.wrap(e):e.translation},unwrap:e=>{var n;return c.observer?null===(n=c.observer)||void 0===n?void 0:n.unwrap(e):{text:e,keys:[]}}})},b=(e,n,t)=>{let a=e;return Object.freeze({init:function(e){a=e},notify:function(){const e=n();a!==e&&t(e),a=e}})},h={tagAttributes:{textarea:["placeholder"],input:["value","placeholder"],img:["alt"],"*":["aria-label","title"]},restrictedElements:["script","style"],highlightKeys:["Alt"],highlightColor:"rgb(255, 0, 0)",highlightWidth:5,inputPrefix:"%-%tolgee:",inputSuffix:"%-%",passToParent:["option","optgroup"]},m=e=>Object.assign(Object.assign({},h),e),y={defaultNs:"",filesUrlPrefix:"i18n/"},O=(e,n)=>{const t=Object.assign(Object.assign(Object.assign({},y),null==n?void 0:n.initialOptions),e),a=t.apiUrl;return t.apiUrl=a?a.replace(/\/+$/,""):a,{initialOptions:t,activeNamespaces:(null==n?void 0:n.activeNamespaces)||new Map,language:null==n?void 0:n.language,pendingLanguage:null==n?void 0:n.language,isInitialLoading:!1,isRunning:!1}};function L(e){var{ns:n,noWrap:t,orEmpty:a,params:i}=e,o=function(e,n){var t={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&n.indexOf(a)<0&&(t[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(a=Object.getOwnPropertySymbols(e);i<a.length;i++)n.indexOf(a[i])<0&&Object.prototype.propertyIsEnumerable.call(e,a[i])&&(t[a[i]]=e[a[i]])}return t}(e,["ns","noWrap","orEmpty","params"]);return{ns:n,noWrap:t,orEmpty:a,params:Object.assign(Object.assign({},o),i)}}const k=(e,...n)=>{let t,a={};return"object"==typeof e?a=e:(a.key=e,"string"==typeof n[0]?(a.defaultValue=n[0],t=n[1]):"object"==typeof n[0]&&(t=n[0])),t&&(a=Object.assign(Object.assign({},L(t)),a)),a},j=({options:n})=>{const o=(n=>{const t=e(),a=e(),i=e(),o=e(),r=e(),g=e(),c=s(n),u=e(),l=e();return g.listen((()=>c.emit())),a.listen((()=>c.emit())),u.listen((({value:e})=>{c.emit({ns:[e.namespace],key:e.key},!0)})),Object.freeze({onPendingLanguageChange:t,onLanguageChange:a,onKeyChange:i,onKeyUpdate:c,onLoadingChange:o,onFetchingChange:r,onInitialLoaded:g,onRunningChange:l,onCacheChange:u,on:(e,n)=>{switch(e){case"pendingLanguage":return t.listen(n);case"language":return a.listen(n);case"loading":return o.listen(n);case"fetching":return r.listen(n);case"initialLoad":return g.listen(n);case"running":return l.listen(n);case"cache":return u.listen(n);case"keyUpdate":return c.listen(n)}}})})((function(){return u.getFallbackNamespaces()})),r=b(!1,(()=>y.isFetching()),o.onFetchingChange.emit),g=b(!1,(()=>R()),o.onLoadingChange.emit),u=((e,n,o)=>{let r=O(),s=m(),g={};function u(){return r.language||r.initialOptions.language}function l(){return Object.assign(Object.assign({},r.initialOptions),g)}return Object.freeze({init:function(e){r=O(e,r)},isRunning:function(){return r.isRunning},setRunning:function(e){r.isRunning!==e&&(r.isRunning=e,o.emit(e))},isInitialLoading:function(){return r.isInitialLoading},setInitialLoading:function(e){r.isInitialLoading=e},getLanguage:u,setLanguage:function(n){r.language!==n&&(r.language=n,e.emit(n))},getPendingLanguage:function(){return r.pendingLanguage||u()},setPendingLanguage:function(e){r.pendingLanguage!==e&&(r.pendingLanguage=e,n.emit(e))},getInitialOptions:l,addActiveNs:function(e){t(e).forEach((e=>{const n=r.activeNamespaces.get(e);void 0!==n?r.activeNamespaces.set(e,n+1):r.activeNamespaces.set(e,1)}))},removeActiveNs:function(e){t(e).forEach((e=>{const n=r.activeNamespaces.get(e);void 0!==n&&n>1?r.activeNamespaces.set(e,n-1):r.activeNamespaces.delete(e)}))},getRequiredNamespaces:function(){return i([...r.initialOptions.ns||[r.initialOptions.defaultNs],...r.activeNamespaces.keys()])},getFallbackLangs:function(e){const n=e||u();return n?i([n,...a(n,r.initialOptions.fallbackLanguage)]):[]},getFallbackNamespaces:function(){const e=r.initialOptions.defaultNs;return i([..."string"==typeof e?[e]:[],...t(r.initialOptions.fallbackNs)])},getAvailableLanguages:function(){if(r.initialOptions.availableLanguages)return r.initialOptions.availableLanguages;if(r.initialOptions.staticData){const e=Object.keys(r.initialOptions.staticData).map((e=>c(e).language));return Array.from(new Set(e))}},withDefaultNs:function(e){return{namespace:void 0===e.namespace?l().defaultNs:e.namespace,language:e.language}},overrideCredentials:function(e){g=e},setObserverOptions:function(e){s=m(e)},getObserverOptions:function(){return s}})})(o.onLanguageChange,o.onPendingLanguageChange,o.onRunningChange),h=v(u.getLanguage,u.getInitialOptions,u.getObserverOptions,u.getAvailableLanguages,(function({key:e,ns:n}){const a=void 0!==n?t(n):u.getFallbackNamespaces(),i=u.getFallbackLangs();return y.getTranslationNs(a,i,e)}),N,L),y=l(o.onCacheChange,h.getBackendRecord,h.getBackendDevRecord,u.withDefaultNs,u.isInitialLoading,r,g);function L(e,n,t){const a=u.withDefaultNs(e),i=y.getTranslation(a,n);return y.changeTranslation(a,n,t),{revert:()=>{y.changeTranslation(a,n,i)}}}function j(e){u.init(e),y.addStaticData(u.getInitialOptions().staticData)}function R(e){return y.isLoading(u.getLanguage(),e)}function E(){return Boolean(u.getInitialOptions().apiKey&&u.getInitialOptions().apiUrl&&h.getDevBackend())}function w(e,n){const a=function(e,n){const a=u.getFallbackLangs(e),i=void 0!==n?t(n):u.getRequiredNamespaces(),o=[];return a.forEach((e=>{i.forEach((n=>{y.exists({language:e,namespace:n},!0)||o.push({language:e,namespace:n})}))})),o}(e,n);if(a.length)return f(T(a),(()=>{}))}function N({key:e,ns:n}){const a=void 0!==n?t(n):u.getFallbackNamespaces(),i=u.getFallbackLangs();return y.getTranslationFallback(a,i,e)}function A(){const e=f(function(){if(u.getLanguage())return;if(!u.getInitialOptions().defaultLanguage)throw new Error(p("defaultLanguage"));const e=h.getInitialLanguage();return f(e,(e=>{const n=e||u.getInitialOptions().defaultLanguage;n&&u.setLanguage(n)}))}(),(()=>w()));if(d(e))return u.setInitialLoading(!0),r.notify(),g.notify(),Promise.resolve(e).then((()=>{u.setInitialLoading(!1),r.notify(),g.notify(),o.onInitialLoaded.emit()}));o.onInitialLoaded.emit()}function T(e){return y.loadRecords(e,E())}n&&j(n),o.onKeyUpdate.listen((()=>{u.isRunning()&&h.retranslate()}));return Object.freeze(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},o),u),h),y),{init:j,changeLanguage:async function(e){u.getPendingLanguage()===e&&u.getLanguage()===e||(u.setPendingLanguage(e),u.isRunning()&&await w(e),e===u.getPendingLanguage()&&(u.setLanguage(e),h.setStoredLanguage(e)))},getTranslation:N,changeTranslation:L,addActiveNs:async function(e,n){n||u.addActiveNs(e),u.isRunning()&&await w(void 0,e)},loadRequiredRecords:w,loadRecords:T,loadRecord:async function(e){return(await T([e]))[0]},isLoading:R,isLoaded:function(e){const n=u.getLanguage();if(!n)return!1;const a=u.getFallbackLangs(n),i=void 0!==e?t(e):u.getRequiredNamespaces(),o=[];return a.forEach((e=>{i.forEach((n=>{y.exists({language:e,namespace:n})||o.push({language:e,namespace:n})}))})),0===o.length},t:(...e)=>{const n=k(...e),t=N(n);return h.formatTranslation(Object.assign(Object.assign({},n),{translation:t}))},isDev:E,run:function(){let e;return(()=>{const e=h.getLanguageDetector();if(e&&!u.getAvailableLanguages())throw new Error(p("availableLanguages"));if(!u.getLanguage()&&!u.getInitialOptions().defaultLanguage)throw e?new Error(p("defaultLanguage")):new Error(p("language"))})(),u.isRunning()||(E()&&y.invalidate(),u.setRunning(!0),h.run(E()),e=A()),Promise.resolve(e)},stop:function(){u.isRunning()&&(h.stop(),u.setRunning(!1))}}))};exports.DEVTOOLS_ID="__tolgee_dev_tools",exports.RESTRICTED_ASCENDANT_ATTRIBUTE="data-tolgee-restricted",exports.TOLGEE_ATTRIBUTE_NAME="_tolgee",exports.TOLGEE_HIGHLIGHTER_CLASS="_tolgee-highlighter",exports.TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE="data-tolgee-key-only",exports.Tolgee=e=>{const n=j({options:e}),t=Object.freeze({setFinalFormatter:n.setFinalFormatter,addFormatter:n.addFormatter,setObserver:n.setObserver,hasObserver:n.hasObserver,setUi:n.setUi,hasUi:n.hasUi,setDevBackend:n.setDevBackend,addBackend:n.addBackend,setLanguageDetector:n.setLanguageDetector,setLanguageStorage:n.setLanguageStorage,overrideCredentials:n.overrideCredentials}),a=e=>{const t=n.isRunning();t&&n.stop(),e(),t&&n.run()},i=Object.freeze({on:n.on,onKeyUpdate:n.onKeyUpdate.listenSome,getLanguage:n.getLanguage,getPendingLanguage:n.getPendingLanguage,changeLanguage:n.changeLanguage,changeTranslation:n.changeTranslation,addActiveNs:n.addActiveNs,removeActiveNs:n.removeActiveNs,loadRecords:n.loadRecords,loadRecord:n.loadRecord,addStaticData:n.addStaticData,getRecord:n.getRecord,getAllRecords:n.getAllRecords,isLoaded:n.isLoaded,isInitialLoading:n.isInitialLoading,isLoading:n.isLoading,isFetching:n.isFetching,isRunning:n.isRunning,run:n.run,stop:n.stop,t:n.t,highlight:n.highlight,getInitialOptions:n.getInitialOptions,isDev:n.isDev,wrap:n.wrap,unwrap:n.unwrap,setObserverOptions:e=>(n.setObserverOptions(e),i),use:e=>(e&&a((()=>e(i,t))),i),init:e=>(a((()=>n.init(e))),i)});return i},exports.getFallback=n,exports.getFallbackArray=t,exports.getTranslateParams=k; | ||
"use strict";function e(e){return Boolean(e&&"function"==typeof e.then)}Object.defineProperty(exports,"__esModule",{value:!0});const n=(n,t)=>e(n)?Promise.resolve(n).then(t):t(n),t=e=>`Tolgee: You need to specify '${e}' option`;function a(e){return"string"==typeof e?[e]:Array.isArray(e)?e:void 0}function i(e){return a(e)||[]}function o(e,n){return"object"!=typeof(t=n)||Array.isArray(t)||null===t?i(n):i(null==n?void 0:n[e]);var t}function r(e){return Array.from(new Set(e))}function s(e){return e?e.replace(/\/+$/,""):e}const c=()=>{let e=[];return Object.freeze({listen:n=>{const t=e=>{n(e)};return e.push(t),{unsubscribe:()=>{e=e.filter((e=>t!==e))}}},emit:n=>{e.forEach((e=>e({value:n})))}})},g=(e,n)=>{const t=new Set,a=new Set;let o=[];const r=()=>{if(0===o.length)return;const n=o;o=[],t.forEach((e=>{e({value:void 0})}));let i=new Set;n.forEach((e=>{void 0===e?i=void 0:void 0!==i&&e.forEach((e=>i.add(e)))}));(n=>{const t=new Set(e());a.forEach((e=>{(void 0===n||-1!==(null==n?void 0:n.findIndex((n=>t.has(n)||e.namespaces.has(n)))))&&e.fn({value:void 0})}))})(i?Array.from(i.keys()):void 0)};return Object.freeze({listenSome:e=>{const t={fn:n=>{e(n)},namespaces:new Set};a.add(t);const o={unsubscribe:()=>{a.delete(t)},subscribeNs:e=>(i(e).forEach((e=>t.namespaces.add(e))),void 0===e&&t.namespaces.add(n()),o)};return o},listen:e=>{t.add(e);return{unsubscribe:()=>{t.delete(e)}}},emit:(e,n)=>{o.push(e),n?setTimeout(r,0):r()}})},u=e=>{const n=new Map;return Object.entries(e).forEach((([e,t])=>{null!=t&&("object"!=typeof t?n.set(e,t):u(t).forEach(((t,a)=>{n.set(e+"."+a,t)})))})),n},l=e=>{const[n,...t]=e.split(":");return{language:n,namespace:t.join(":")||""}},d=({language:e,namespace:n})=>n?`${e}:${n}`:e,f=(e,n,t,a,o,s,c)=>{const g=new Map,f=new Map;let p={},v=0;function h(n,t,a){const i=d(n);f.set(i,{data:u(t),version:a}),e.emit(n)}function b(e,n){h(e,n,v)}function m(e,n=!1){const t=f.get(d(e));return t&&n?t.version===v:Boolean(t)}function O(e){var n;return null===(n=f.get(d(a(e))))||void 0===n?void 0:n.data}function y(e){let t;if(!t){const n=p[d(e)];"function"==typeof n&&(t=n())}return t||(t=n(e)),t}return Object.freeze({addStaticData:function(e){e&&(p=Object.assign(Object.assign({},p),e),Object.entries(e).forEach((([e,n])=>{if("function"!=typeof n){const t=l(e),a=f.get(e);a&&0!==a.version||h(t,n,0)}})))},invalidate:function(){g.clear(),v+=1},addRecord:b,exists:m,getRecord:O,getTranslation:function(e,n){var t;return null===(t=f.get(d(e)))||void 0===t?void 0:t.data.get(n)},getTranslationNs:function(e,n,t){var a;for(const i of e)for(const e of n){const n=null===(a=f.get(d({language:e,namespace:i})))||void 0===a?void 0:a.data.get(t);if(null!=n)return[i]}return r(e)},getTranslationFallback:function(e,n,t){var a;for(const i of e)for(const e of n){const n=null===(a=f.get(d({language:e,namespace:i})))||void 0===a?void 0:a.data.get(t);if(null!=n)return n}},changeTranslation:function(n,t,a){var i;const o=null===(i=f.get(d(n)))||void 0===i?void 0:i.data;null==o||o.set(t,a),e.emit(Object.assign(Object.assign({},n),{key:t}))},isFetching:function(e){if(o())return!0;if(void 0===e)return g.size>0;const n=i(e);return Boolean(Array.from(g.keys()).find((e=>n.includes(l(e).namespace))))},isLoading:function(e,n){const t=i(n);return Boolean(o()||Array.from(g.keys()).find((n=>{const a=l(n);return(!t.length||t.includes(a.namespace))&&!m({namespace:a.namespace,language:e})})))},loadRecords:async function(e,n){const i=e.map((e=>{const i=a(e),o=d(i),r=g.get(o);if(r)return{new:!1,promise:r,keyObject:i,cacheKey:o};const s=function(e,n){var a;let i;return n&&(i=null===(a=t(e))||void 0===a?void 0:a.catch((()=>(console.warn("Tolgee: Failed to fetch data from dev backend"),y(e))))),i||(i=y(e)),i}(i,n)||Promise.resolve(void 0);return g.set(o,s),{new:!0,promise:s,keyObject:i,cacheKey:o}}));s.notify(),c.notify();const o=await Promise.all(i.map((e=>e.promise)));return i.forEach(((e,n)=>{const t=g.get(e.cacheKey)!==e.promise;if(e.new&&!t){g.delete(e.cacheKey);const t=o[n];t?b(e.keyObject,t):O(e.keyObject)||b(e.keyObject,{})}})),s.notify(),c.notify(),i.map((e=>O(e.keyObject)))},getAllRecords:function(){return Array.from(f.entries()).map((([e,n])=>Object.assign(Object.assign({},l(e)),{data:n.data})))}})},p=(t,a,i,o,r,s)=>{let c=!1,g=[];const u={ui:void 0,observer:void 0},l={formatters:[],finalFormatter:void 0,observer:void 0,devBackend:void 0,backends:[],ui:void 0,languageDetector:void 0,languageStorage:void 0},d=async({keysAndDefaults:e,event:n})=>{var t;const a=e.map((({key:e,ns:n,defaultValue:t})=>({key:e,defaultValue:t,ns:o({key:e,ns:n}),translation:r({key:e,ns:n})})));null===(t=l.ui)||void 0===t||t.handleElementClick(a,n)},f=(e,n)=>{var t,a;return(null===(a=null===(t=l.observer)||void 0===t?void 0:t.highlight)||void 0===a?void 0:a.call(t,e,n))||{unhighlight(){}}},p=e=>{const n=r({key:e.key,ns:e.ns});return E(Object.assign(Object.assign({},e),{translation:n,formatEnabled:!0}))},v=e=>{u.observer=e},h=()=>Boolean(u.observer),b=e=>{e&&l.formatters.push(e)},m=e=>{l.finalFormatter=e},O=e=>{u.ui=(null==e?void 0:e.UI)||e},y=()=>Boolean(u.ui),L=e=>{l.languageStorage=e},j=e=>{l.languageDetector=e},k=e=>{e&&l.backends.push(e)},w=e=>{l.devBackend=e},R=()=>l.devBackend,N=e=>{g.push(e)};function E({key:e,translation:n,defaultValue:a,noWrap:i,params:o,orEmpty:r,ns:s,formatEnabled:c}){var g;const u=n||a;let d=u||(r?"":e);l.observer&&!i&&(d=l.observer.wrap({key:e,translation:d,defaultValue:a,params:o,ns:s}));const f=t(),p=c||!(null===(g=l.observer)||void 0===g?void 0:g.outputNotFormattable);if(u&&f&&p)for(const e of l.formatters)d=e.format({translation:d,language:f,params:o});return l.finalFormatter&&u&&f&&p&&(d=l.finalFormatter.format({translation:d,language:f,params:o})),d}function I(){for(c=!0;g.length;){const e=g;g=[],e.forEach((e=>e()))}}return Object.freeze({prepare:I,addPlugin:function(e,n){n(e,Object.freeze({setFinalFormatter:m,addFormatter:b,setObserver:v,hasObserver:h,setUi:O,hasUi:y,setDevBackend:w,addBackend:k,setLanguageDetector:j,setLanguageStorage:L,onPrepare:N})),c&&I()},formatTranslation:E,getDevBackend:R,getBackendRecord:({language:n,namespace:t})=>{for(const a of l.backends){const i=a.getRecord({language:n,namespace:t});if(e(i))return null==i?void 0:i.catch((e=>(console.error(e),{})));if(void 0!==i)return i}},getBackendDevRecord:({language:e,namespace:n})=>{var t;const{apiKey:i,apiUrl:o,projectId:r}=a();return null===(t=l.devBackend)||void 0===t?void 0:t.getRecord({apiKey:i,apiUrl:o,projectId:r,language:e,namespace:n})},getLanguageDetector:()=>l.languageDetector,getInitialLanguage:()=>{var e;const t=i(),a=null===(e=l.languageStorage)||void 0===e?void 0:e.getLanguage();return n(a,(e=>t&&!t.includes(e)||!e?(()=>{if(!l.languageDetector)return;const e=i();return l.languageDetector.getLanguage({availableLanguages:e})})():e))},setStoredLanguage:e=>{var n;null===(n=l.languageStorage)||void 0===n||n.setLanguage(e)},run:e=>{var n,t;if(!l.ui&&u.ui){const{apiKey:e,apiUrl:n,projectId:t}=a();l.ui=new u.ui({apiKey:e,apiUrl:n,projectId:t,highlight:f,changeTranslation:s})}l.observer||(l.observer=null===(n=u.observer)||void 0===n?void 0:n.call(u,{translate:p,onClick:d,options:a().observerOptions})),null===(t=l.observer)||void 0===t||t.run({mouseHighlight:e})},stop:()=>{var e;l.ui=void 0,null===(e=l.observer)||void 0===e||e.stop()},retranslate:()=>{var e;null===(e=l.observer)||void 0===e||e.retranslate()},highlight:f,unwrap:e=>{var n;return l.observer?null===(n=l.observer)||void 0===n?void 0:n.unwrap(e):{text:e,keys:[]}},wrap:e=>{var n;return l.observer?null===(n=l.observer)||void 0===n?void 0:n.wrap(e):e.translation},hasDevBackend:function(){return Boolean(R())}})},v=(e,n,t)=>{let a=e;return Object.freeze({init:function(e){a=e},notify:function(){const e=n();a!==e&&t(e),a=e}})},h={defaultNs:"",observerOptions:{tagAttributes:{textarea:["placeholder"],input:["value","placeholder"],img:["alt"],"*":["aria-label","title"]},restrictedElements:["script","style"],highlightKeys:["Alt"],highlightColor:"rgb(255, 0, 0)",highlightWidth:5,inputPrefix:"%-%tolgee:",inputSuffix:"%-%",passToParent:["option","optgroup"]},observerType:"invisible"},b=(...e)=>{let n={};return e.forEach((e=>{n=Object.assign(Object.assign(Object.assign({},n),e),{observerOptions:Object.assign(Object.assign({},n.observerOptions),null==e?void 0:e.observerOptions)})})),n},m=(e,n)=>{const t=b(h,null==n?void 0:n.initialOptions,e);return t.apiUrl=s(t.apiUrl),{initialOptions:t,activeNamespaces:(null==n?void 0:n.activeNamespaces)||new Map,language:null==n?void 0:n.language,pendingLanguage:null==n?void 0:n.language,isInitialLoading:!1,isRunning:!1}};function O(e){var{ns:n,noWrap:t,orEmpty:a,params:i}=e,o=function(e,n){var t={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&n.indexOf(a)<0&&(t[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(a=Object.getOwnPropertySymbols(e);i<a.length;i++)n.indexOf(a[i])<0&&Object.prototype.propertyIsEnumerable.call(e,a[i])&&(t[a[i]]=e[a[i]])}return t}(e,["ns","noWrap","orEmpty","params"]);const r={ns:n,noWrap:t,orEmpty:a};return Object.assign(Object.assign({},r),{params:Object.assign({},o)})}const y=(e,...n)=>{let t,a={};return"object"==typeof e?a=e:(a.key=e,"string"==typeof n[0]?(a.defaultValue=n[0],t=n[1]):"object"==typeof n[0]&&(t=n[0])),t&&(a=Object.assign(Object.assign({},O(t)),a)),a},L=({options:a})=>{const u=((e,n)=>{const t=c(),a=c(),i=c(),o=c(),r=c(),s=c(),u=c(),l=g(e,n);return r.listen((()=>l.emit())),a.listen((()=>l.emit())),u.listen((({value:e})=>{l.emit([e.namespace],!0)})),Object.freeze({onPendingLanguageChange:t,onLanguageChange:a,onLoadingChange:i,onFetchingChange:o,onInitialLoaded:r,onRunningChange:s,onCacheChange:u,onUpdate:l,on:(e,n)=>{switch(e){case"pendingLanguage":return t.listen(n);case"language":return a.listen(n);case"loading":return i.listen(n);case"fetching":return o.listen(n);case"initialLoad":return r.listen(n);case"running":return s.listen(n);case"cache":return u.listen(n);case"update":return l.listen(n)}}})})(j,k),d=v(!1,(()=>L.isFetching()),u.onFetchingChange.emit),h=v(!1,(()=>I()),u.onLoadingChange.emit),b=((e,n,t)=>{let a,c=m();function g(){return c.language||c.initialOptions.language}function u(){return Object.assign(Object.assign({},c.initialOptions),a)}return Object.freeze({init:function(e){c=m(e,c)},isRunning:function(){return c.isRunning},setRunning:function(e){c.isRunning!==e&&(c.isRunning=e,t.emit(e))},isInitialLoading:function(){return c.isInitialLoading},setInitialLoading:function(e){c.isInitialLoading=e},getLanguage:g,setLanguage:function(n){c.language!==n&&(c.language=n,e.emit(n))},getPendingLanguage:function(){return c.pendingLanguage||g()},setPendingLanguage:function(e){c.pendingLanguage!==e&&(c.pendingLanguage=e,n.emit(e))},getInitialOptions:u,addActiveNs:function(e){i(e).forEach((e=>{const n=c.activeNamespaces.get(e);void 0!==n?c.activeNamespaces.set(e,n+1):c.activeNamespaces.set(e,1)}))},removeActiveNs:function(e){i(e).forEach((e=>{const n=c.activeNamespaces.get(e);void 0!==n&&n>1?c.activeNamespaces.set(e,n-1):c.activeNamespaces.delete(e)}))},getRequiredNamespaces:function(){return r([...c.initialOptions.ns||[c.initialOptions.defaultNs],...i(c.initialOptions.fallbackNs),...c.activeNamespaces.keys()])},getFallbackLangs:function(e){const n=e||g();return n?r([n,...o(n,c.initialOptions.fallbackLanguage)]):[]},getFallbackNs:function(){return i(c.initialOptions.fallbackNs)},getDefaultNs:function(e){return void 0===e?c.initialOptions.defaultNs:e},getAvailableLanguages:function(){if(c.initialOptions.availableLanguages)return c.initialOptions.availableLanguages;if(c.initialOptions.staticData){const e=Object.keys(c.initialOptions.staticData).map((e=>l(e).language));return Array.from(new Set(e))}},withDefaultNs:function(e){return{namespace:void 0===e.namespace?u().defaultNs:e.namespace,language:e.language}},overrideCredentials:function(e){a=e?Object.assign(Object.assign({},e),{apiUrl:s(e.apiUrl)}):void 0}})})(u.onLanguageChange,u.onPendingLanguageChange,u.onRunningChange),O=p(b.getLanguage,b.getInitialOptions,b.getAvailableLanguages,(function({key:e,ns:n}){const t=b.getFallbackLangs(),a=w(n);return L.getTranslationNs(a,t,e)}),F,N),L=f(u.onCacheChange,O.getBackendRecord,O.getBackendDevRecord,b.withDefaultNs,b.isInitialLoading,d,h);function j(){return b.getFallbackNs()}function k(e){return b.getDefaultNs(e)}function w(e){return[...i(k(e)),...j()]}function R(e){return[...i(e||k()),...b.getRequiredNamespaces()]}function N(e,n,t){const a=b.withDefaultNs(e),i=L.getTranslation(a,n);return L.changeTranslation(a,n,t),{revert:()=>{L.changeTranslation(a,n,i)}}}function E(e){b.init(e),L.addStaticData(b.getInitialOptions().staticData)}function I(e){return L.isLoading(b.getLanguage(),e)}function D(){return Boolean(b.getInitialOptions().apiKey&&b.getInitialOptions().apiUrl)}function A(e,t){const a=function(e,n){const t=b.getFallbackLangs(e),a=R(n),i=[];return t.forEach((e=>{a.forEach((n=>{L.exists({language:e,namespace:n},!0)||i.push({language:e,namespace:n})}))})),i}(e,t);if(a.length)return n(P(a),(()=>{}))}function F({key:e,ns:n}){const t=w(n),a=b.getFallbackLangs();return L.getTranslationFallback(t,a,e)}function S(){const a=n(function(){if(b.getLanguage())return;if(!b.getInitialOptions().defaultLanguage)throw new Error(t("defaultLanguage"));const e=O.getInitialLanguage();return n(e,(e=>{const n=e||b.getInitialOptions().defaultLanguage;n&&b.setLanguage(n)}))}(),(()=>A()));if(e(a))return b.setInitialLoading(!0),d.notify(),h.notify(),Promise.resolve(a).then((()=>{b.setInitialLoading(!1),d.notify(),h.notify(),u.onInitialLoaded.emit()}));u.onInitialLoaded.emit()}function P(e){return L.loadRecords(e,D())}a&&E(a),u.onUpdate.listen((()=>{b.isRunning()&&O.retranslate()}));return Object.freeze(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},u),b),O),L),{init:E,changeLanguage:async function(e){b.getPendingLanguage()===e&&b.getLanguage()===e||(b.setPendingLanguage(e),b.isRunning()&&await A(e),e===b.getPendingLanguage()&&(b.setLanguage(e),O.setStoredLanguage(e)))},getTranslation:F,changeTranslation:N,addActiveNs:async function(e,n){n||b.addActiveNs(e),b.isRunning()&&await A(void 0,e)},loadRecords:P,loadRecord:async function(e){return(await P([e]))[0]},isLoading:I,isLoaded:function(e){const n=b.getLanguage();if(!n)return!1;const t=b.getFallbackLangs(n),a=R(e),i=[];return t.forEach((e=>{a.forEach((n=>{L.exists({language:e,namespace:n})||i.push({language:e,namespace:n})}))})),0===i.length},t:(...e)=>{const n=y(...e),t=F(n);return O.formatTranslation(Object.assign(Object.assign({},n),{translation:t}))},isDev:D,run:function(){let e;return(()=>{const e=O.getLanguageDetector();if(e&&!b.getAvailableLanguages())throw new Error(t("availableLanguages"));if(!b.getLanguage()&&!b.getInitialOptions().defaultLanguage)throw e?new Error(t("defaultLanguage")):new Error(t("language"))})(),b.isRunning()||(D()&&L.invalidate(),b.setRunning(!0),O.run(D()),e=S()),Promise.resolve(e)},stop:function(){b.isRunning()&&(O.stop(),b.setRunning(!1))}}))};class j extends Error{constructor(e,n,t){let a;a=0===e?"Empty parameter":1===e?"Unexpected character":"Unexpected end",super(`Tolgee parser: ${a} at ${n} in "${t}"`),this.code=e,this.index=n}}function k(e){return/\s/.test(e)}const w=new Set([2,1,0]),R=new Set(["{","}","'"]);function N(e,n){const[t,a]=function(e){let n=0,t="",a="",i="";const o=[],r=[];let s=0;function c(n){throw new j(n,s,e)}const g=()=>{o.push(t),t=""},u=()=>{""===a&&c(0),r.push(a),a=""};for(s=0;s<e.length;s++)switch(i=e[s],n){case 0:"'"===i?(t+=i,n=1):"{"===i?(g(),n=3):(t+=i,n=0);break;case 1:R.has(i)?(t=t.slice(0,-1)+i,n=2):(t+=i,n=0);break;case 2:"'"===i?n=0:(t+=i,n=2);break;case 3:"}"===i?(u(),n=0):k(i)?""!==a&&(u(),n=4):(/[0-9a-zA-Z_]/.test(i)||c(1),a+=i,n=3);break;case 4:"}"==i?n=0:k(i)?n=4:c(1)}return w.has(n)||c(2),g(),[o,r]}(e),i=[t[0]];for(let o=1;o<t.length;o++){const r=null==n?void 0:n[a[o-1]];if(void 0===r)throw new Error(`Missing parameter "${a[o-1]}" in "${e}"`);i.push(String(r)),i.push(t[o])}return i.join("")}exports.FormatSimple=()=>(e,n)=>(n.setFinalFormatter({format:({translation:e,params:n})=>N(e,n)}),e),exports.Tolgee=()=>{const e={plugins:[],options:{}},n=Object.freeze({use:t=>(e.plugins.push(t),n),updateDefaults:t=>(e.options=b(e.options,t),n),init(n){const t=(e=>{const n=L({options:e}),t=e=>{const t=n.isRunning();t&&n.stop(),e(),t&&n.run()},a=Object.freeze({on:n.on,onNsUpdate:n.onUpdate.listenSome,getLanguage:n.getLanguage,getPendingLanguage:n.getPendingLanguage,changeLanguage:n.changeLanguage,changeTranslation:n.changeTranslation,addActiveNs:n.addActiveNs,removeActiveNs:n.removeActiveNs,loadRecords:n.loadRecords,loadRecord:n.loadRecord,addStaticData:n.addStaticData,getRecord:n.getRecord,getAllRecords:n.getAllRecords,isLoaded:n.isLoaded,isInitialLoading:n.isInitialLoading,isLoading:n.isLoading,isFetching:n.isFetching,isRunning:n.isRunning,run:n.run,stop:n.stop,t:n.t,highlight:n.highlight,getInitialOptions:n.getInitialOptions,isDev:n.isDev,wrap:n.wrap,unwrap:n.unwrap,overrideCredentials(e){t((()=>n.overrideCredentials(e)))},addPlugin(e){e&&t((()=>n.addPlugin(a,e)))},updateOptions(e){e&&t((()=>n.init(e)))}});return a})(b(e.options,n));return e.plugins.forEach(t.addPlugin),t}});return n},exports.getFallback=a,exports.getFallbackArray=i,exports.getTranslateProps=y; | ||
//# sourceMappingURL=tolgee.cjs.min.js.map |
@@ -7,21 +7,14 @@ (function (global, factory) { | ||
const EventEmitter = () => { | ||
let handlers = []; | ||
const listen = (handler) => { | ||
const handlerWrapper = (e) => { | ||
handler(e); | ||
}; | ||
handlers.push(handlerWrapper); | ||
return { | ||
unsubscribe: () => { | ||
handlers = handlers.filter((i) => handlerWrapper !== i); | ||
}, | ||
}; | ||
}; | ||
const emit = (data) => { | ||
handlers.forEach((handler) => handler({ value: data })); | ||
}; | ||
return Object.freeze({ listen, emit }); | ||
function isPromise(value) { | ||
return Boolean(value && typeof value.then === 'function'); | ||
} | ||
const valueOrPromise = (value, callback) => { | ||
if (isPromise(value)) { | ||
return Promise.resolve(value).then(callback); | ||
} | ||
else { | ||
return callback(value); | ||
} | ||
}; | ||
const missingOptionError = (option) => `Tolgee: You need to specify '${option}' option`; | ||
function isObject(item) { | ||
@@ -53,18 +46,26 @@ return typeof item === 'object' && !Array.isArray(item) && item !== null; | ||
} | ||
function sanitizeUrl(url) { | ||
return url ? url.replace(/\/+$/, '') : url; | ||
} | ||
function incrementInMap(map, value) { | ||
const currNum = map.get(value) || 0; | ||
map.set(value, currNum + 1); | ||
} | ||
function decrementInMap(map, value) { | ||
let currNum = map.get(value) || 1; | ||
currNum -= 1; | ||
if (currNum <= 0) { | ||
map.delete(value); | ||
} | ||
else { | ||
map.set(value, currNum); | ||
} | ||
} | ||
const EventEmitterSelective = (getFallbackNamespaces) => { | ||
const EventEmitter = () => { | ||
let handlers = []; | ||
const listen = (handler) => { | ||
const handlerWrapper = (e) => { | ||
handler(e); | ||
}; | ||
handlers.push(handlerWrapper); | ||
return { | ||
unsubscribe: () => { | ||
handlers = handlers.filter((i) => handlerWrapper !== i); | ||
}, | ||
}; | ||
}; | ||
const emit = (data) => { | ||
handlers.forEach((handler) => handler({ value: data })); | ||
}; | ||
return Object.freeze({ listen, emit }); | ||
}; | ||
const EventEmitterSelective = (getFallbackNs, getDefaultNs) => { | ||
const listeners = new Set(); | ||
@@ -86,4 +87,3 @@ const partialListeners = new Set(); | ||
}, | ||
keys: new Map(), | ||
namespaces: new Map(), | ||
namespaces: new Set(), | ||
}; | ||
@@ -96,48 +96,20 @@ partialListeners.add(handlerWrapper); | ||
subscribeNs: (ns) => { | ||
getFallbackArray(ns).forEach((val) => incrementInMap(handlerWrapper.namespaces, val)); | ||
return result; | ||
}, | ||
unsubscribeNs: (ns) => { | ||
getFallbackArray(ns).forEach((val) => decrementInMap(handlerWrapper.namespaces, val)); | ||
return result; | ||
}, | ||
subscribeKey: (descriptor) => { | ||
const { key, ns } = descriptor; | ||
incrementInMap(handlerWrapper.keys, key); | ||
getFallbackArray(ns).forEach((val) => incrementInMap(handlerWrapper.namespaces, val)); | ||
getFallbackArray(ns).forEach((val) => handlerWrapper.namespaces.add(val)); | ||
if (ns === undefined) { | ||
// subscribing to all namespaces | ||
incrementInMap(handlerWrapper.namespaces, undefined); | ||
// subscribing to default ns | ||
handlerWrapper.namespaces.add(getDefaultNs()); | ||
} | ||
return result; | ||
}, | ||
unsubscribeKey: (descriptor) => { | ||
const { key, ns } = descriptor; | ||
decrementInMap(handlerWrapper.keys, key); | ||
getFallbackArray(ns).forEach((val) => decrementInMap(handlerWrapper.namespaces, val)); | ||
if (ns === undefined) { | ||
// subscribing to all namespaces | ||
decrementInMap(handlerWrapper.namespaces, undefined); | ||
} | ||
return result; | ||
}, | ||
}; | ||
return result; | ||
}; | ||
const namespacesWithFallbacks = (namespaces) => { | ||
if (namespaces.has(undefined)) { | ||
const result = new Set(namespaces.keys()); | ||
result.delete(undefined); | ||
getFallbackNamespaces().forEach((ns) => result.add(ns)); | ||
return result; | ||
} | ||
return namespaces; | ||
}; | ||
const callHandlers = (key, ns) => { | ||
const callHandlers = (ns) => { | ||
// everything is implicitly subscribed to fallbacks | ||
// as it can always fall through to it | ||
const fallbackNamespaces = new Set(getFallbackNs()); | ||
partialListeners.forEach((handler) => { | ||
const handlerNamespaces = namespacesWithFallbacks(handler.namespaces); | ||
const nsMatches = ns === undefined || | ||
(ns === null || ns === void 0 ? void 0 : ns.findIndex((ns) => handlerNamespaces.has(ns))) !== -1; | ||
const keyMatches = key === undefined || handler.keys.has(key) || handler.keys.size === 0; | ||
if (nsMatches && keyMatches) { | ||
(ns === null || ns === void 0 ? void 0 : ns.findIndex((ns) => fallbackNamespaces.has(ns) || handler.namespaces.has(ns))) !== -1; | ||
if (nsMatches) { | ||
handler.fn({ value: undefined }); | ||
@@ -153,31 +125,24 @@ } | ||
} | ||
const queueCopy = queue; | ||
queue = []; | ||
listeners.forEach((handler) => { | ||
handler({ value: undefined }); | ||
}); | ||
const namespaces = new Set(); | ||
let keys = new Set(); | ||
queue.forEach((descriptor) => { | ||
if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.ns) === undefined) { | ||
// when no ns specified, it affets all fallback namespaces | ||
namespaces.add(undefined); | ||
let namespaces = new Set(); | ||
queueCopy.forEach((ns) => { | ||
if (ns === undefined) { | ||
// when no ns specified, it affects all namespaces | ||
namespaces = undefined; | ||
} | ||
else { | ||
descriptor.ns.forEach((ns) => namespaces.add(ns)); | ||
else if (namespaces !== undefined) { | ||
ns.forEach((ns) => namespaces.add(ns)); | ||
} | ||
if ((descriptor === null || descriptor === void 0 ? void 0 : descriptor.key) === undefined) { | ||
// when no key specified, it affects all keys | ||
keys = undefined; | ||
} | ||
else if (keys !== undefined) { | ||
keys.add(descriptor.key); | ||
} | ||
}); | ||
const namespacesArray = Array.from(namespacesWithFallbacks(namespaces).keys()); | ||
(keys || [undefined]).forEach((key) => { | ||
callHandlers(key, namespacesArray); | ||
}); | ||
queue = []; | ||
const namespacesArray = namespaces | ||
? Array.from(namespaces.keys()) | ||
: undefined; | ||
callHandlers(namespacesArray); | ||
}; | ||
const emit = (descriptor, delayed) => { | ||
queue.push(descriptor); | ||
const emit = (ns, delayed) => { | ||
queue.push(ns); | ||
if (!delayed) { | ||
@@ -187,5 +152,3 @@ solveQueue(); | ||
else { | ||
Promise.resolve().then(() => { | ||
solveQueue(); | ||
}); | ||
setTimeout(solveQueue, 0); | ||
} | ||
@@ -196,16 +159,15 @@ }; | ||
const Events = (getFallbackNamespaces) => { | ||
const Events = (getFallbackNs, getDefaultNs) => { | ||
const onPendingLanguageChange = EventEmitter(); | ||
const onLanguageChange = EventEmitter(); | ||
const onKeyChange = EventEmitter(); | ||
const onLoadingChange = EventEmitter(); | ||
const onFetchingChange = EventEmitter(); | ||
const onInitialLoaded = EventEmitter(); | ||
const onKeyUpdate = EventEmitterSelective(getFallbackNamespaces); | ||
const onRunningChange = EventEmitter(); | ||
const onCacheChange = EventEmitter(); | ||
const onRunningChange = EventEmitter(); | ||
onInitialLoaded.listen(() => onKeyUpdate.emit()); | ||
onLanguageChange.listen(() => onKeyUpdate.emit()); | ||
const onUpdate = EventEmitterSelective(getFallbackNs, getDefaultNs); | ||
onInitialLoaded.listen(() => onUpdate.emit()); | ||
onLanguageChange.listen(() => onUpdate.emit()); | ||
onCacheChange.listen(({ value }) => { | ||
onKeyUpdate.emit({ ns: [value.namespace], key: value.key }, true); | ||
onUpdate.emit([value.namespace], true); | ||
}); | ||
@@ -228,4 +190,4 @@ const on = (event, handler) => { | ||
return onCacheChange.listen(handler); | ||
case 'keyUpdate': | ||
return onKeyUpdate.listen(handler); | ||
case 'update': | ||
return onUpdate.listen(handler); | ||
} | ||
@@ -236,4 +198,2 @@ }; | ||
onLanguageChange, | ||
onKeyChange, | ||
onKeyUpdate, | ||
onLoadingChange, | ||
@@ -244,2 +204,3 @@ onFetchingChange, | ||
onCacheChange, | ||
onUpdate, | ||
on, | ||
@@ -267,3 +228,5 @@ }); | ||
const decodeCacheKey = (key) => { | ||
const [firstPart, secondPart] = key.split(':'); | ||
const [firstPart, ...rest] = key.split(':'); | ||
// if namespaces contains ":" it won't get lost | ||
const secondPart = rest.join(':'); | ||
return { language: firstPart, namespace: secondPart || '' }; | ||
@@ -336,7 +299,7 @@ }; | ||
if (value !== undefined && value !== null) { | ||
return namespace; | ||
return [namespace]; | ||
} | ||
} | ||
} | ||
return Array.from(new Set(namespaces)); | ||
return unique(namespaces); | ||
} | ||
@@ -384,3 +347,6 @@ function getTranslationFallback(namespaces, languages, key) { | ||
} | ||
function fetchNormal(keyObject) { | ||
/** | ||
* Fetches production data | ||
*/ | ||
function fetchProd(keyObject) { | ||
let dataPromise = undefined; | ||
@@ -396,6 +362,2 @@ if (!dataPromise) { | ||
} | ||
if (!dataPromise) { | ||
// return empty data, so we know it has already been attempted to fetch | ||
dataPromise = Promise.resolve({}); | ||
} | ||
return dataPromise; | ||
@@ -410,8 +372,8 @@ } | ||
console.warn(`Tolgee: Failed to fetch data from dev backend`); | ||
// fallback to normal fetch if dev fails | ||
return fetchNormal(keyObject); | ||
// fallback to prod fetch if dev fails | ||
return fetchProd(keyObject); | ||
}); | ||
} | ||
if (!dataPromise) { | ||
dataPromise = fetchNormal(keyObject); | ||
dataPromise = fetchProd(keyObject); | ||
} | ||
@@ -433,3 +395,3 @@ return dataPromise; | ||
} | ||
const dataPromise = fetchData(keyObject, isDev); | ||
const dataPromise = fetchData(keyObject, isDev) || Promise.resolve(undefined); | ||
asyncRequests.set(cacheKey, dataPromise); | ||
@@ -456,2 +418,6 @@ return { | ||
} | ||
else if (!getRecord(value.keyObject)) { | ||
// if no data exist, put empty object | ||
addRecord(value.keyObject, {}); | ||
} | ||
} | ||
@@ -486,16 +452,5 @@ }); | ||
function isPromise(value) { | ||
return Boolean(value && typeof value.then === 'function'); | ||
} | ||
const valueOrPromise = (value, callback) => { | ||
if (isPromise(value)) { | ||
return Promise.resolve(value).then(callback); | ||
} | ||
else { | ||
return callback(value); | ||
} | ||
}; | ||
const missingOptionError = (option) => `Tolgee: You need to specify '${option}' option`; | ||
const PluginService = (getLanguage, getInitialOptions, getObserverOptions, getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation) => { | ||
const Plugins = (getLanguage, getInitialOptions, getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation) => { | ||
let prepared = false; | ||
let onPrepareQueue = []; | ||
const plugins = { | ||
@@ -515,34 +470,17 @@ ui: undefined, | ||
}; | ||
const onClick = async (event, { keysAndDefaults }) => { | ||
const onClick = async ({ keysAndDefaults, event }) => { | ||
var _a; | ||
const withNs = keysAndDefaults.map(({ key, ns, defaultValue }) => ({ | ||
key, | ||
defaultValue, | ||
ns: getFallbackArray(getTranslationNs({ key, ns, defaultValue })), | ||
translation: getTranslation({ | ||
const withNs = keysAndDefaults.map(({ key, ns, defaultValue }) => { | ||
return { | ||
key, | ||
ns, | ||
}), | ||
})); | ||
(_a = instances.ui) === null || _a === void 0 ? void 0 : _a.handleElementClick(event, withNs); | ||
defaultValue, | ||
ns: getTranslationNs({ key, ns }), | ||
translation: getTranslation({ | ||
key, | ||
ns, | ||
}), | ||
}; | ||
}); | ||
(_a = instances.ui) === null || _a === void 0 ? void 0 : _a.handleElementClick(withNs, event); | ||
}; | ||
const run = (isDev) => { | ||
var _a, _b; | ||
instances.ui = | ||
plugins.ui && | ||
new plugins.ui({ | ||
apiKey: getInitialOptions().apiKey, | ||
apiUrl: getInitialOptions().apiUrl, | ||
highlight, | ||
changeTranslation, | ||
}); | ||
if (!instances.observer) { | ||
instances.observer = (_a = plugins.observer) === null || _a === void 0 ? void 0 : _a.call(plugins, { | ||
translate, | ||
onClick, | ||
options: getObserverOptions(), | ||
}); | ||
} | ||
(_b = instances.observer) === null || _b === void 0 ? void 0 : _b.run({ mouseHighlight: isDev }); | ||
}; | ||
const stop = () => { | ||
@@ -558,3 +496,6 @@ var _a; | ||
const translate = (props) => { | ||
const translation = getTranslation(props); | ||
const translation = getTranslation({ | ||
key: props.key, | ||
ns: props.ns, | ||
}); | ||
return formatTranslation(Object.assign(Object.assign({}, props), { translation, formatEnabled: true })); | ||
@@ -585,5 +526,2 @@ }; | ||
}; | ||
const getLanguageStorage = () => { | ||
return instances.languageStorage; | ||
}; | ||
const setStoredLanguage = (language) => { | ||
@@ -628,2 +566,23 @@ var _a; | ||
}; | ||
const run = (isDev) => { | ||
var _a, _b; | ||
if (!instances.ui && plugins.ui) { | ||
const { apiKey, apiUrl, projectId } = getInitialOptions(); | ||
instances.ui = new plugins.ui({ | ||
apiKey: apiKey, | ||
apiUrl: apiUrl, | ||
projectId, | ||
highlight, | ||
changeTranslation, | ||
}); | ||
} | ||
if (!instances.observer) { | ||
instances.observer = (_a = plugins.observer) === null || _a === void 0 ? void 0 : _a.call(plugins, { | ||
translate, | ||
onClick, | ||
options: getInitialOptions().observerOptions, | ||
}); | ||
} | ||
(_b = instances.observer) === null || _b === void 0 ? void 0 : _b.run({ mouseHighlight: isDev }); | ||
}; | ||
const getDevBackend = () => { | ||
@@ -634,5 +593,7 @@ return instances.devBackend; | ||
var _a; | ||
const { apiKey, apiUrl, projectId } = getInitialOptions(); | ||
return (_a = instances.devBackend) === null || _a === void 0 ? void 0 : _a.getRecord({ | ||
apiKey: getInitialOptions().apiKey, | ||
apiUrl: getInitialOptions().apiUrl, | ||
apiKey, | ||
apiUrl, | ||
projectId, | ||
language, | ||
@@ -658,4 +619,37 @@ namespace, | ||
}; | ||
const formatTranslation = ({ key, translation, defaultValue, noWrap, params, orEmpty, ns, formatEnabled, }) => { | ||
const unwrap = (text) => { | ||
var _a; | ||
if (instances.observer) { | ||
return (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.unwrap(text); | ||
} | ||
return { text, keys: [] }; | ||
}; | ||
const retranslate = () => { | ||
var _a; | ||
(_a = instances.observer) === null || _a === void 0 ? void 0 : _a.retranslate(); | ||
}; | ||
const onPrepare = (callback) => { | ||
onPrepareQueue.push(callback); | ||
}; | ||
function addPlugin(tolgeeInstance, plugin) { | ||
const pluginTools = Object.freeze({ | ||
setFinalFormatter, | ||
addFormatter, | ||
setObserver, | ||
hasObserver, | ||
setUi, | ||
hasUi, | ||
setDevBackend, | ||
addBackend, | ||
setLanguageDetector, | ||
setLanguageStorage, | ||
onPrepare, | ||
}); | ||
plugin(tolgeeInstance, pluginTools); | ||
if (prepared) { | ||
prepare(); | ||
} | ||
} | ||
function formatTranslation({ key, translation, defaultValue, noWrap, params, orEmpty, ns, formatEnabled, }) { | ||
var _a; | ||
const formattableTranslation = translation || defaultValue; | ||
@@ -694,3 +688,6 @@ let result = formattableTranslation || (orEmpty ? '' : key); | ||
return result; | ||
}; | ||
} | ||
function hasDevBackend() { | ||
return Boolean(getDevBackend()); | ||
} | ||
const wrap = (params) => { | ||
@@ -703,30 +700,18 @@ var _a; | ||
}; | ||
const unwrap = (text) => { | ||
var _a; | ||
if (instances.observer) { | ||
return (_a = instances.observer) === null || _a === void 0 ? void 0 : _a.unwrap(text); | ||
function prepare() { | ||
prepared = true; | ||
while (onPrepareQueue.length) { | ||
const queue = onPrepareQueue; | ||
onPrepareQueue = []; | ||
queue.forEach((callback) => callback()); | ||
} | ||
return { text, keys: [] }; | ||
}; | ||
const retranslate = () => { | ||
var _a; | ||
(_a = instances.observer) === null || _a === void 0 ? void 0 : _a.retranslate(); | ||
}; | ||
} | ||
return Object.freeze({ | ||
setFinalFormatter, | ||
addFormatter, | ||
prepare, | ||
addPlugin, | ||
formatTranslation, | ||
setObserver, | ||
hasObserver, | ||
setUi, | ||
hasUi, | ||
addBackend, | ||
setDevBackend, | ||
getDevBackend, | ||
getBackendRecord, | ||
getBackendDevRecord, | ||
setLanguageDetector, | ||
getLanguageDetector, | ||
setLanguageStorage, | ||
getLanguageStorage, | ||
getInitialLanguage, | ||
@@ -738,4 +723,5 @@ setStoredLanguage, | ||
highlight, | ||
unwrap, | ||
wrap, | ||
unwrap, | ||
hasDevBackend, | ||
}); | ||
@@ -762,3 +748,3 @@ }; | ||
const defaultValues$1 = { | ||
const defaultObserverOptions = { | ||
tagAttributes: { | ||
@@ -778,15 +764,19 @@ textarea: ['placeholder'], | ||
}; | ||
const initObserverOptions = (options) => { | ||
return Object.assign(Object.assign({}, defaultValues$1), options); | ||
}; | ||
const defaultValues = { | ||
defaultNs: '', | ||
filesUrlPrefix: 'i18n/', | ||
observerOptions: defaultObserverOptions, | ||
observerType: 'invisible', | ||
}; | ||
const combineOptions = (...states) => { | ||
let result = {}; | ||
states.forEach((state) => { | ||
result = Object.assign(Object.assign(Object.assign({}, result), state), { observerOptions: Object.assign(Object.assign({}, result.observerOptions), state === null || state === void 0 ? void 0 : state.observerOptions) }); | ||
}); | ||
return result; | ||
}; | ||
const initState = (options, previousState) => { | ||
const initialOptions = Object.assign(Object.assign(Object.assign({}, defaultValues), previousState === null || previousState === void 0 ? void 0 : previousState.initialOptions), options); | ||
const initialOptions = combineOptions(defaultValues, previousState === null || previousState === void 0 ? void 0 : previousState.initialOptions, options); | ||
// remove extra '/' from url end | ||
const apiUrl = initialOptions.apiUrl; | ||
initialOptions.apiUrl = apiUrl ? apiUrl.replace(/\/+$/, '') : apiUrl; | ||
initialOptions.apiUrl = sanitizeUrl(initialOptions.apiUrl); | ||
return { | ||
@@ -804,4 +794,3 @@ initialOptions, | ||
let state = initState(); | ||
let observerOptions = initObserverOptions(); | ||
let devCredentials = {}; | ||
let devCredentials = undefined; | ||
function init(options) { | ||
@@ -873,2 +862,3 @@ state = initState(options, state); | ||
...(state.initialOptions.ns || [state.initialOptions.defaultNs]), | ||
...getFallbackArray(state.initialOptions.fallbackNs), | ||
...state.activeNamespaces.keys(), | ||
@@ -887,8 +877,8 @@ ]); | ||
} | ||
function getFallbackNamespaces() { | ||
const defaultNs = state.initialOptions.defaultNs; | ||
const fallbackNs = state.initialOptions.fallbackNs; | ||
const fallbackNamespaces = typeof defaultNs === 'string' ? [defaultNs] : []; | ||
return unique([...fallbackNamespaces, ...getFallbackArray(fallbackNs)]); | ||
function getFallbackNs() { | ||
return getFallbackArray(state.initialOptions.fallbackNs); | ||
} | ||
function getDefaultNs(ns) { | ||
return ns === undefined ? state.initialOptions.defaultNs : ns; | ||
} | ||
function getAvailableLanguages() { | ||
@@ -912,10 +902,9 @@ if (state.initialOptions.availableLanguages) { | ||
function overrideCredentials(credentials) { | ||
devCredentials = credentials; | ||
if (credentials) { | ||
devCredentials = Object.assign(Object.assign({}, credentials), { apiUrl: sanitizeUrl(credentials.apiUrl) }); | ||
} | ||
else { | ||
devCredentials = undefined; | ||
} | ||
} | ||
function setObserverOptions(options) { | ||
observerOptions = initObserverOptions(options); | ||
} | ||
function getObserverOptions() { | ||
return observerOptions; | ||
} | ||
return Object.freeze({ | ||
@@ -936,8 +925,7 @@ init, | ||
getFallbackLangs, | ||
getFallbackNamespaces, | ||
getFallbackNs, | ||
getDefaultNs, | ||
getAvailableLanguages, | ||
withDefaultNs, | ||
overrideCredentials, | ||
setObserverOptions, | ||
getObserverOptions, | ||
}); | ||
@@ -979,7 +967,6 @@ }; | ||
orEmpty: orEmpty, | ||
params: Object.assign(Object.assign({}, rest), params), | ||
}; | ||
return options; | ||
return Object.assign(Object.assign({}, options), { params: Object.assign({}, rest) }); | ||
} | ||
const getTranslateParams = (keyOrProps, ...params) => { | ||
const getTranslateProps = (keyOrProps, ...params) => { | ||
let result = {}; | ||
@@ -1007,7 +994,7 @@ let options; | ||
const Controller = ({ options }) => { | ||
const events = Events(getFallbackNamespaces); | ||
const events = Events(getFallbackNs, getDefaultNs); | ||
const fetchingObserver = ValueObserver(false, () => cache.isFetching(), events.onFetchingChange.emit); | ||
const loadingObserver = ValueObserver(false, () => isLoading(), events.onLoadingChange.emit); | ||
const state = State(events.onLanguageChange, events.onPendingLanguageChange, events.onRunningChange); | ||
const pluginService = PluginService(state.getLanguage, state.getInitialOptions, state.getObserverOptions, state.getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation); | ||
const pluginService = Plugins(state.getLanguage, state.getInitialOptions, state.getAvailableLanguages, getTranslationNs, getTranslation, changeTranslation); | ||
const cache = Cache(events.onCacheChange, pluginService.getBackendRecord, pluginService.getBackendDevRecord, state.withDefaultNs, state.isInitialLoading, fetchingObserver, loadingObserver); | ||
@@ -1017,3 +1004,3 @@ if (options) { | ||
} | ||
events.onKeyUpdate.listen(() => { | ||
events.onUpdate.listen(() => { | ||
if (state.isRunning()) { | ||
@@ -1023,2 +1010,21 @@ pluginService.retranslate(); | ||
}); | ||
function getFallbackNs() { | ||
return state.getFallbackNs(); | ||
} | ||
function getDefaultNs(ns) { | ||
return state.getDefaultNs(ns); | ||
} | ||
// gets all namespaces where translation could be located | ||
// takes (ns|default, fallback ns) | ||
function getDefaultAndFallbackNs(ns) { | ||
return [...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()]; | ||
} | ||
// gets all namespaces which need to be loaded | ||
// takes (ns|default, initial ns, fallback ns, active ns) | ||
function getRequiredNamespaces(ns) { | ||
return [ | ||
...getFallbackArray(ns || getDefaultNs()), | ||
...state.getRequiredNamespaces(), | ||
]; | ||
} | ||
function changeTranslation(descriptor, key, value) { | ||
@@ -1034,5 +1040,2 @@ const keyObject = state.withDefaultNs(descriptor); | ||
} | ||
function getFallbackNamespaces() { | ||
return state.getFallbackNamespaces(); | ||
} | ||
function init(options) { | ||
@@ -1046,5 +1049,3 @@ state.init(options); | ||
function isDev() { | ||
return Boolean(state.getInitialOptions().apiKey && | ||
state.getInitialOptions().apiUrl && | ||
pluginService.getDevBackend()); | ||
return Boolean(state.getInitialOptions().apiKey && state.getInitialOptions().apiUrl); | ||
} | ||
@@ -1061,3 +1062,3 @@ async function addActiveNs(ns, forget) { | ||
const languages = state.getFallbackLangs(lang); | ||
const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces(); | ||
const namespaces = getRequiredNamespaces(ns); | ||
const result = []; | ||
@@ -1079,3 +1080,3 @@ languages.forEach((language) => { | ||
const languages = state.getFallbackLangs(language); | ||
const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces(); | ||
const namespaces = getRequiredNamespaces(ns); | ||
const result = []; | ||
@@ -1113,9 +1114,9 @@ languages.forEach((language) => { | ||
} | ||
function getTranslationNs({ key, ns, }) { | ||
const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getFallbackNamespaces(); | ||
function getTranslationNs({ key, ns }) { | ||
const languages = state.getFallbackLangs(); | ||
const namespaces = getDefaultAndFallbackNs(ns); | ||
return cache.getTranslationNs(namespaces, languages, key); | ||
} | ||
function getTranslation({ key, ns, }) { | ||
const namespaces = ns !== undefined ? getFallbackArray(ns) : state.getFallbackNamespaces(); | ||
function getTranslation({ key, ns }) { | ||
const namespaces = getDefaultAndFallbackNs(ns); | ||
const languages = state.getFallbackLangs(); | ||
@@ -1203,3 +1204,3 @@ return cache.getTranslationFallback(namespaces, languages, key); | ||
// @ts-ignore | ||
const params = getTranslateParams(...args); | ||
const params = getTranslateProps(...args); | ||
const translation = getTranslation(params); | ||
@@ -1213,3 +1214,2 @@ return pluginService.formatTranslation(Object.assign(Object.assign({}, params), { translation })); | ||
addActiveNs, | ||
loadRequiredRecords, | ||
loadRecords, | ||
@@ -1225,19 +1225,7 @@ loadRecord, | ||
const Tolgee = (options) => { | ||
const createTolgee = (options) => { | ||
const controller = Controller({ | ||
options, | ||
}); | ||
const pluginTools = Object.freeze({ | ||
setFinalFormatter: controller.setFinalFormatter, | ||
addFormatter: controller.addFormatter, | ||
setObserver: controller.setObserver, | ||
hasObserver: controller.hasObserver, | ||
setUi: controller.setUi, | ||
hasUi: controller.hasUi, | ||
setDevBackend: controller.setDevBackend, | ||
addBackend: controller.addBackend, | ||
setLanguageDetector: controller.setLanguageDetector, | ||
setLanguageStorage: controller.setLanguageStorage, | ||
overrideCredentials: controller.overrideCredentials, | ||
}); | ||
// restarts tolgee while applying callback | ||
const withRestart = (callback) => { | ||
@@ -1250,65 +1238,351 @@ const wasRunning = controller.isRunning(); | ||
const tolgee = Object.freeze({ | ||
// event listeners | ||
/** | ||
* Listen to tolgee events. | ||
*/ | ||
on: controller.on, | ||
onKeyUpdate: controller.onKeyUpdate.listenSome, | ||
// state | ||
/** | ||
* Listen for specific namespaces changes. | ||
* | ||
* ``` | ||
* const sub = tolgee.onUpdate(handler) | ||
* | ||
* // subscribe to selected namespace | ||
* sub.subscribeNs(['common']) | ||
* | ||
* // unsubscribe | ||
* sub.unsubscribe() | ||
* ``` | ||
*/ | ||
onNsUpdate: controller.onUpdate.listenSome, | ||
/** | ||
* @return current language if set. | ||
*/ | ||
getLanguage: controller.getLanguage, | ||
/** | ||
* `pendingLanguage` represents language which is currently being loaded. | ||
* @return current `pendingLanguage` if set. | ||
*/ | ||
getPendingLanguage: controller.getPendingLanguage, | ||
/** | ||
* Change current language. | ||
* - if not running sets `pendingLanguage`, `language` to the new value | ||
* - if running sets `pendingLanguage` to the value, fetches necessary data and then changes `language` | ||
* | ||
* @return Promise which is resolved when `language` is changed. | ||
*/ | ||
changeLanguage: controller.changeLanguage, | ||
/** | ||
* Temporarily change translation in cache. | ||
* @return object with revert method. | ||
*/ | ||
changeTranslation: controller.changeTranslation, | ||
/** | ||
* Adds namespace(s) list of active namespaces. And if tolgee is running, loads required data. | ||
*/ | ||
addActiveNs: controller.addActiveNs, | ||
/** | ||
* Remove namespace(s) from active namespaces. | ||
* | ||
* Tolgee internally counts how many times was each active namespace added, | ||
* so this method will remove namespace only if the counter goes down to 0. | ||
*/ | ||
removeActiveNs: controller.removeActiveNs, | ||
/** | ||
* Manually load multiple records from `Backend` (or `DevBackend` when in dev mode) | ||
* | ||
* It loads data together and adds them to cache in one operation, to prevent partly loaded state. | ||
*/ | ||
loadRecords: controller.loadRecords, | ||
/** | ||
* Manually load record from `Backend` (or `DevBackend` when in dev mode) | ||
*/ | ||
loadRecord: controller.loadRecord, | ||
/** | ||
* | ||
*/ | ||
addStaticData: controller.addStaticData, | ||
/** | ||
* Get record from cache. | ||
*/ | ||
getRecord: controller.getRecord, | ||
/** | ||
* Get all records from cache. | ||
*/ | ||
getAllRecords: controller.getAllRecords, | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if there are data that need to be fetched. | ||
*/ | ||
isLoaded: controller.isLoaded, | ||
/** | ||
* @return `true` if tolgee is loading initial data (triggered by `run`). | ||
*/ | ||
isInitialLoading: controller.isInitialLoading, | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if tolgee is loading some translations for the first time. | ||
*/ | ||
isLoading: controller.isLoading, | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if tolgee is fetching some translations. | ||
*/ | ||
isFetching: controller.isFetching, | ||
/** | ||
* @return `true` if tolgee is running. | ||
*/ | ||
isRunning: controller.isRunning, | ||
/** | ||
* Changes internal state to running: true and loads initial files. | ||
* Runs runnable plugins mainly Observer if present. | ||
*/ | ||
run: controller.run, | ||
/** | ||
* Changes internal state to running: false and stops runnable plugins. | ||
*/ | ||
stop: controller.stop, | ||
/** | ||
* Returns translated and formatted key. | ||
* If Observer is present and tolgee is running, wraps result to be identifiable in the DOM. | ||
*/ | ||
t: controller.t, | ||
/** | ||
* Highlight keys that match selection. | ||
*/ | ||
highlight: controller.highlight, | ||
/** | ||
* @return current Tolgee options. | ||
*/ | ||
getInitialOptions: controller.getInitialOptions, | ||
/** | ||
* Tolgee is in dev mode if `DevTools` plugin is used and `apiKey` + `apiUrl` are specified. | ||
* @return `true` if tolgee is in dev mode. | ||
*/ | ||
isDev: controller.isDev, | ||
/** | ||
* Wraps translation if there is `Observer` plugin | ||
*/ | ||
wrap: controller.wrap, | ||
/** | ||
* Unwrap translation | ||
*/ | ||
unwrap: controller.unwrap, | ||
// plugins | ||
setObserverOptions: (options) => { | ||
controller.setObserverOptions(options); | ||
return tolgee; | ||
/** | ||
* Override creadentials passed on initialization | ||
*/ | ||
overrideCredentials(credentials) { | ||
withRestart(() => controller.overrideCredentials(credentials)); | ||
}, | ||
use: (plugin) => { | ||
/** | ||
* Add tolgee plugin. | ||
*/ | ||
addPlugin(plugin) { | ||
if (plugin) { | ||
withRestart(() => plugin(tolgee, pluginTools)); | ||
withRestart(() => controller.addPlugin(tolgee, plugin)); | ||
} | ||
return tolgee; | ||
}, | ||
init: (options) => { | ||
withRestart(() => controller.init(options)); | ||
/** | ||
* Updates options after instance creation. Extends existing options, | ||
* so it only changes the fields, that are listed. | ||
* | ||
* When called in running state, tolgee stops and runs again. | ||
*/ | ||
updateOptions(options) { | ||
if (options) { | ||
withRestart(() => controller.init(options)); | ||
} | ||
}, | ||
}); | ||
return tolgee; | ||
}; | ||
/** | ||
* Tolgee chainable constructor. | ||
* | ||
* Usage: | ||
* ``` | ||
* const tolgee = Tolgee().use(...).init(...) | ||
* ``` | ||
*/ | ||
const Tolgee = () => { | ||
const state = { | ||
plugins: [], | ||
options: {}, | ||
}; | ||
const tolgeeChain = Object.freeze({ | ||
use(plugin) { | ||
state.plugins.push(plugin); | ||
return tolgeeChain; | ||
}, | ||
updateDefaults(options) { | ||
state.options = combineOptions(state.options, options); | ||
return tolgeeChain; | ||
}, | ||
init(options) { | ||
const tolgee = createTolgee(combineOptions(state.options, options)); | ||
state.plugins.forEach(tolgee.addPlugin); | ||
return tolgee; | ||
}, | ||
}); | ||
return tolgeeChain; | ||
}; | ||
const ERROR_PARAM_EMPTY = 0, ERROR_UNEXPECTED_CHAR = 1, ERROR_UNEXPECTED_END = 2; | ||
class FormatError extends Error { | ||
constructor(code, index, text) { | ||
let error; | ||
if (code === ERROR_PARAM_EMPTY) { | ||
error = 'Empty parameter'; | ||
} | ||
else if (code === ERROR_UNEXPECTED_CHAR) { | ||
error = 'Unexpected character'; | ||
} | ||
else { | ||
error = 'Unexpected end'; | ||
} | ||
super(`Tolgee parser: ${error} at ${index} in "${text}"`); | ||
this.code = code; | ||
this.index = index; | ||
} | ||
} | ||
function isWhitespace(ch) { | ||
return /\s/.test(ch); | ||
} | ||
const STATE_TEXT = 0, STATE_ESCAPE_MAYBE = 1, STATE_ESCAPE = 2, STATE_PARAM = 3, STATE_PARAM_AFTER = 4; | ||
const END_STATES = new Set([ | ||
STATE_ESCAPE, | ||
STATE_ESCAPE_MAYBE, | ||
STATE_TEXT, | ||
]); | ||
const CHAR_ESCAPE = "'"; | ||
const ESCAPABLE = new Set(['{', '}', CHAR_ESCAPE]); | ||
const isAllowedInParam = (char) => { | ||
return /[0-9a-zA-Z_]/.test(char); | ||
}; | ||
function formatParser(translation) { | ||
let state = STATE_TEXT; | ||
let text = ''; | ||
let param = ''; | ||
let ch = ''; | ||
const texts = []; | ||
const params = []; | ||
let i = 0; | ||
function parsingError(code) { | ||
throw new FormatError(code, i, translation); | ||
} | ||
const addText = () => { | ||
texts.push(text); | ||
text = ''; | ||
}; | ||
const addParamChar = () => { | ||
if (!isAllowedInParam(ch)) { | ||
parsingError(ERROR_UNEXPECTED_CHAR); | ||
} | ||
param += ch; | ||
}; | ||
const addParam = () => { | ||
if (param === '') { | ||
parsingError(ERROR_PARAM_EMPTY); | ||
} | ||
params.push(param); | ||
param = ''; | ||
}; | ||
for (i = 0; i < translation.length; i++) { | ||
ch = translation[i]; | ||
switch (state) { | ||
case STATE_TEXT: | ||
if (ch === CHAR_ESCAPE) { | ||
text += ch; | ||
state = STATE_ESCAPE_MAYBE; | ||
} | ||
else if (ch === '{') { | ||
addText(); | ||
state = STATE_PARAM; | ||
} | ||
else { | ||
text += ch; | ||
state = STATE_TEXT; | ||
} | ||
break; | ||
case STATE_ESCAPE_MAYBE: | ||
if (ESCAPABLE.has(ch)) { | ||
text = text.slice(0, -1) + ch; | ||
state = STATE_ESCAPE; | ||
} | ||
else { | ||
text += ch; | ||
state = STATE_TEXT; | ||
} | ||
break; | ||
case STATE_ESCAPE: | ||
if (ch === CHAR_ESCAPE) { | ||
state = STATE_TEXT; | ||
} | ||
else { | ||
text += ch; | ||
state = STATE_ESCAPE; | ||
} | ||
break; | ||
case STATE_PARAM: | ||
if (ch === '}') { | ||
addParam(); | ||
state = STATE_TEXT; | ||
} | ||
else if (!isWhitespace(ch)) { | ||
addParamChar(); | ||
state = STATE_PARAM; | ||
} | ||
else if (param !== '') { | ||
addParam(); | ||
state = STATE_PARAM_AFTER; | ||
} | ||
break; | ||
case STATE_PARAM_AFTER: | ||
if (ch == '}') { | ||
state = STATE_TEXT; | ||
} | ||
else if (isWhitespace(ch)) { | ||
state = STATE_PARAM_AFTER; | ||
} | ||
else { | ||
parsingError(ERROR_UNEXPECTED_CHAR); | ||
} | ||
} | ||
} | ||
if (!END_STATES.has(state)) { | ||
parsingError(ERROR_UNEXPECTED_END); | ||
} | ||
addText(); | ||
return [texts, params]; | ||
} | ||
function formatter(translation, params) { | ||
const [texts, pars] = formatParser(translation); | ||
const result = [texts[0]]; | ||
for (let i = 1; i < texts.length; i++) { | ||
const parameter = params === null || params === void 0 ? void 0 : params[pars[i - 1]]; | ||
if (parameter === undefined) { | ||
throw new Error(`Missing parameter "${pars[i - 1]}" in "${translation}"`); | ||
} | ||
result.push(String(parameter)); | ||
result.push(texts[i]); | ||
} | ||
return result.join(''); | ||
} | ||
function createFormatSimple() { | ||
return { | ||
format: ({ translation, params }) => formatter(translation, params), | ||
}; | ||
} | ||
const FormatSimple = () => (tolgee, tools) => { | ||
tools.setFinalFormatter(createFormatSimple()); | ||
return tolgee; | ||
}; | ||
const RESTRICTED_ASCENDANT_ATTRIBUTE = 'data-tolgee-restricted'; | ||
const TOLGEE_ATTRIBUTE_NAME = '_tolgee'; | ||
const TOLGEE_HIGHLIGHTER_CLASS = '_tolgee-highlighter'; | ||
const TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE = 'data-tolgee-key-only'; | ||
// needs to be same as in @tolgee/ui package | ||
const DEVTOOLS_ID = '__tolgee_dev_tools'; | ||
exports.DEVTOOLS_ID = DEVTOOLS_ID; | ||
exports.RESTRICTED_ASCENDANT_ATTRIBUTE = RESTRICTED_ASCENDANT_ATTRIBUTE; | ||
exports.TOLGEE_ATTRIBUTE_NAME = TOLGEE_ATTRIBUTE_NAME; | ||
exports.TOLGEE_HIGHLIGHTER_CLASS = TOLGEE_HIGHLIGHTER_CLASS; | ||
exports.TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE = TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE; | ||
exports.FormatSimple = FormatSimple; | ||
exports.Tolgee = Tolgee; | ||
exports.getFallback = getFallback; | ||
exports.getFallbackArray = getFallbackArray; | ||
exports.getTranslateParams = getTranslateParams; | ||
exports.getTranslateProps = getTranslateProps; | ||
@@ -1315,0 +1589,0 @@ Object.defineProperty(exports, '__esModule', { value: true }); |
@@ -1,2 +0,2 @@ | ||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self)["@tolgee/core"]={})}(this,(function(e){"use strict";const n=()=>{let e=[];return Object.freeze({listen:n=>{const t=e=>{n(e)};return e.push(t),{unsubscribe:()=>{e=e.filter((e=>t!==e))}}},emit:n=>{e.forEach((e=>e({value:n})))}})};function t(e){return"string"==typeof e?[e]:Array.isArray(e)?e:void 0}function a(e){return t(e)||[]}function i(e,n){return"object"!=typeof(t=n)||Array.isArray(t)||null===t?a(n):a(null==n?void 0:n[e]);var t}function o(e){return Array.from(new Set(e))}function r(e,n){const t=e.get(n)||0;e.set(n,t+1)}function s(e,n){let t=e.get(n)||1;t-=1,t<=0?e.delete(n):e.set(n,t)}const g=e=>{const n=new Set,t=new Set,i=n=>{if(n.has(void 0)){const t=new Set(n.keys());return t.delete(void 0),e().forEach((e=>t.add(e))),t}return n};let o=[];const g=()=>{if(0===o.length)return;n.forEach((e=>{e({value:void 0})}));const e=new Set;let a=new Set;o.forEach((n=>{void 0===(null==n?void 0:n.ns)?e.add(void 0):n.ns.forEach((n=>e.add(n))),void 0===(null==n?void 0:n.key)?a=void 0:void 0!==a&&a.add(n.key)}));const r=Array.from(i(e).keys());(a||[void 0]).forEach((e=>{((e,n)=>{t.forEach((t=>{const a=i(t.namespaces),o=void 0===n||-1!==(null==n?void 0:n.findIndex((e=>a.has(e)))),r=void 0===e||t.keys.has(e)||0===t.keys.size;o&&r&&t.fn({value:void 0})}))})(e,r)})),o=[]};return Object.freeze({listenSome:e=>{const n={fn:n=>{e(n)},keys:new Map,namespaces:new Map};t.add(n);const i={unsubscribe:()=>{t.delete(n)},subscribeNs:e=>(a(e).forEach((e=>r(n.namespaces,e))),i),unsubscribeNs:e=>(a(e).forEach((e=>s(n.namespaces,e))),i),subscribeKey:e=>{const{key:t,ns:o}=e;return r(n.keys,t),a(o).forEach((e=>r(n.namespaces,e))),void 0===o&&r(n.namespaces,void 0),i},unsubscribeKey:e=>{const{key:t,ns:o}=e;return s(n.keys,t),a(o).forEach((e=>s(n.namespaces,e))),void 0===o&&s(n.namespaces,void 0),i}};return i},listen:e=>{n.add(e);return{unsubscribe:()=>{n.delete(e)}}},emit:(e,n)=>{o.push(e),n?Promise.resolve().then((()=>{g()})):g()}})},c=e=>{const n=new Map;return Object.entries(e).forEach((([e,t])=>{null!=t&&("object"!=typeof t?n.set(e,t):c(t).forEach(((t,a)=>{n.set(e+"."+a,t)})))})),n},l=e=>{const[n,t]=e.split(":");return{language:n,namespace:t||""}},u=({language:e,namespace:n})=>n?`${e}:${n}`:e,d=(e,n,t,i,o,r,s)=>{const g=new Map,d=new Map;let f={},p=0;function v(n,t,a){const i=u(n);d.set(i,{data:c(t),version:a}),e.emit(n)}function b(e,n){v(e,n,p)}function h(e,n=!1){const t=d.get(u(e));return t&&n?t.version===p:Boolean(t)}function m(e){var n;return null===(n=d.get(u(i(e))))||void 0===n?void 0:n.data}function y(e){let t;if(!t){const n=f[u(e)];"function"==typeof n&&(t=n())}return t||(t=n(e)),t||(t=Promise.resolve({})),t}return Object.freeze({addStaticData:function(e){e&&(f=Object.assign(Object.assign({},f),e),Object.entries(e).forEach((([e,n])=>{if("function"!=typeof n){const t=l(e),a=d.get(e);a&&0!==a.version||v(t,n,0)}})))},invalidate:function(){g.clear(),p+=1},addRecord:b,exists:h,getRecord:m,getTranslation:function(e,n){var t;return null===(t=d.get(u(e)))||void 0===t?void 0:t.data.get(n)},getTranslationNs:function(e,n,t){var a;for(const i of e)for(const e of n){const n=null===(a=d.get(u({language:e,namespace:i})))||void 0===a?void 0:a.data.get(t);if(null!=n)return i}return Array.from(new Set(e))},getTranslationFallback:function(e,n,t){var a;for(const i of e)for(const e of n){const n=null===(a=d.get(u({language:e,namespace:i})))||void 0===a?void 0:a.data.get(t);if(null!=n)return n}},changeTranslation:function(n,t,a){var i;const o=null===(i=d.get(u(n)))||void 0===i?void 0:i.data;null==o||o.set(t,a),e.emit(Object.assign(Object.assign({},n),{key:t}))},isFetching:function(e){if(o())return!0;if(void 0===e)return g.size>0;const n=a(e);return Boolean(Array.from(g.keys()).find((e=>n.includes(l(e).namespace))))},isLoading:function(e,n){const t=a(n);return Boolean(o()||Array.from(g.keys()).find((n=>{const a=l(n);return(!t.length||t.includes(a.namespace))&&!h({namespace:a.namespace,language:e})})))},loadRecords:async function(e,n){const a=e.map((e=>{const a=i(e),o=u(a),r=g.get(o);if(r)return{new:!1,promise:r,keyObject:a,cacheKey:o};const s=function(e,n){var a;let i;return n&&(i=null===(a=t(e))||void 0===a?void 0:a.catch((()=>(console.warn("Tolgee: Failed to fetch data from dev backend"),y(e))))),i||(i=y(e)),i}(a,n);return g.set(o,s),{new:!0,promise:s,keyObject:a,cacheKey:o}}));r.notify(),s.notify();const o=await Promise.all(a.map((e=>e.promise)));return a.forEach(((e,n)=>{const t=g.get(e.cacheKey)!==e.promise;if(e.new&&!t){g.delete(e.cacheKey);const t=o[n];t&&b(e.keyObject,t)}})),r.notify(),s.notify(),a.map((e=>m(e.keyObject)))},getAllRecords:function(){return Array.from(d.entries()).map((([e,n])=>Object.assign(Object.assign({},l(e)),{data:n.data})))}})};function f(e){return Boolean(e&&"function"==typeof e.then)}const p=(e,n)=>f(e)?Promise.resolve(e).then(n):n(e),v=e=>`Tolgee: You need to specify '${e}' option`,b=(e,n,t,i,o,r,s)=>{const g={ui:void 0,observer:void 0},c={formatters:[],finalFormatter:void 0,observer:void 0,devBackend:void 0,backends:[],ui:void 0,languageDetector:void 0,languageStorage:void 0},l=async(e,{keysAndDefaults:n})=>{var t;const i=n.map((({key:e,ns:n,defaultValue:t})=>({key:e,defaultValue:t,ns:a(o({key:e,ns:n,defaultValue:t})),translation:r({key:e,ns:n})})));null===(t=c.ui)||void 0===t||t.handleElementClick(e,i)},u=(e,n)=>{var t,a;return(null===(a=null===(t=c.observer)||void 0===t?void 0:t.highlight)||void 0===a?void 0:a.call(t,e,n))||{unhighlight(){}}},d=e=>{const n=r(e);return v(Object.assign(Object.assign({},e),{translation:n,formatEnabled:!0}))},v=({key:n,translation:t,defaultValue:a,noWrap:i,params:o,orEmpty:r,ns:s,formatEnabled:g})=>{var l;const u=t||a;let d=u||(r?"":n);c.observer&&!i&&(d=c.observer.wrap({key:n,translation:d,defaultValue:a,params:o,ns:s}));const f=e(),p=g||!(null===(l=c.observer)||void 0===l?void 0:l.outputNotFormattable);if(u&&f&&p)for(const e of c.formatters)d=e.format({translation:d,language:f,params:o});return c.finalFormatter&&u&&f&&p&&(d=c.finalFormatter.format({translation:d,language:f,params:o})),d};return Object.freeze({setFinalFormatter:e=>{c.finalFormatter=e},addFormatter:e=>{e&&c.formatters.push(e)},formatTranslation:v,setObserver:e=>{g.observer=e},hasObserver:()=>Boolean(g.observer),setUi:e=>{g.ui=(null==e?void 0:e.UI)||e},hasUi:()=>Boolean(g.ui),addBackend:e=>{e&&c.backends.push(e)},setDevBackend:e=>{c.devBackend=e},getDevBackend:()=>c.devBackend,getBackendRecord:({language:e,namespace:n})=>{for(const t of c.backends){const a=t.getRecord({language:e,namespace:n});if(f(a))return null==a?void 0:a.catch((e=>(console.error(e),{})));if(void 0!==a)return a}},getBackendDevRecord:({language:e,namespace:t})=>{var a;return null===(a=c.devBackend)||void 0===a?void 0:a.getRecord({apiKey:n().apiKey,apiUrl:n().apiUrl,language:e,namespace:t})},setLanguageDetector:e=>{c.languageDetector=e},getLanguageDetector:()=>c.languageDetector,setLanguageStorage:e=>{c.languageStorage=e},getLanguageStorage:()=>c.languageStorage,getInitialLanguage:()=>{var e;const n=i(),t=null===(e=c.languageStorage)||void 0===e?void 0:e.getLanguage();return p(t,(e=>n&&!n.includes(e)||!e?(()=>{if(!c.languageDetector)return;const e=i();return c.languageDetector.getLanguage({availableLanguages:e})})():e))},setStoredLanguage:e=>{var n;null===(n=c.languageStorage)||void 0===n||n.setLanguage(e)},run:e=>{var a,i;c.ui=g.ui&&new g.ui({apiKey:n().apiKey,apiUrl:n().apiUrl,highlight:u,changeTranslation:s}),c.observer||(c.observer=null===(a=g.observer)||void 0===a?void 0:a.call(g,{translate:d,onClick:l,options:t()})),null===(i=c.observer)||void 0===i||i.run({mouseHighlight:e})},stop:()=>{var e;c.ui=void 0,null===(e=c.observer)||void 0===e||e.stop()},retranslate:()=>{var e;null===(e=c.observer)||void 0===e||e.retranslate()},highlight:u,wrap:e=>{var n;return c.observer?null===(n=c.observer)||void 0===n?void 0:n.wrap(e):e.translation},unwrap:e=>{var n;return c.observer?null===(n=c.observer)||void 0===n?void 0:n.unwrap(e):{text:e,keys:[]}}})},h=(e,n,t)=>{let a=e;return Object.freeze({init:function(e){a=e},notify:function(){const e=n();a!==e&&t(e),a=e}})},m={tagAttributes:{textarea:["placeholder"],input:["value","placeholder"],img:["alt"],"*":["aria-label","title"]},restrictedElements:["script","style"],highlightKeys:["Alt"],highlightColor:"rgb(255, 0, 0)",highlightWidth:5,inputPrefix:"%-%tolgee:",inputSuffix:"%-%",passToParent:["option","optgroup"]},y=e=>Object.assign(Object.assign({},m),e),O={defaultNs:"",filesUrlPrefix:"i18n/"},L=(e,n)=>{const t=Object.assign(Object.assign(Object.assign({},O),null==n?void 0:n.initialOptions),e),a=t.apiUrl;return t.apiUrl=a?a.replace(/\/+$/,""):a,{initialOptions:t,activeNamespaces:(null==n?void 0:n.activeNamespaces)||new Map,language:null==n?void 0:n.language,pendingLanguage:null==n?void 0:n.language,isInitialLoading:!1,isRunning:!1}};function k(e){var{ns:n,noWrap:t,orEmpty:a,params:i}=e,o=function(e,n){var t={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&n.indexOf(a)<0&&(t[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(a=Object.getOwnPropertySymbols(e);i<a.length;i++)n.indexOf(a[i])<0&&Object.prototype.propertyIsEnumerable.call(e,a[i])&&(t[a[i]]=e[a[i]])}return t}(e,["ns","noWrap","orEmpty","params"]);return{ns:n,noWrap:t,orEmpty:a,params:Object.assign(Object.assign({},o),i)}}const j=(e,...n)=>{let t,a={};return"object"==typeof e?a=e:(a.key=e,"string"==typeof n[0]?(a.defaultValue=n[0],t=n[1]):"object"==typeof n[0]&&(t=n[0])),t&&(a=Object.assign(Object.assign({},k(t)),a)),a},R=({options:e})=>{const t=(e=>{const t=n(),a=n(),i=n(),o=n(),r=n(),s=n(),c=g(e),l=n(),u=n();return s.listen((()=>c.emit())),a.listen((()=>c.emit())),l.listen((({value:e})=>{c.emit({ns:[e.namespace],key:e.key},!0)})),Object.freeze({onPendingLanguageChange:t,onLanguageChange:a,onKeyChange:i,onKeyUpdate:c,onLoadingChange:o,onFetchingChange:r,onInitialLoaded:s,onRunningChange:u,onCacheChange:l,on:(e,n)=>{switch(e){case"pendingLanguage":return t.listen(n);case"language":return a.listen(n);case"loading":return o.listen(n);case"fetching":return r.listen(n);case"initialLoad":return s.listen(n);case"running":return u.listen(n);case"cache":return l.listen(n);case"keyUpdate":return c.listen(n)}}})})((function(){return c.getFallbackNamespaces()})),r=h(!1,(()=>m.isFetching()),t.onFetchingChange.emit),s=h(!1,(()=>R()),t.onLoadingChange.emit),c=((e,n,t)=>{let r=L(),s=y(),g={};function c(){return r.language||r.initialOptions.language}function u(){return Object.assign(Object.assign({},r.initialOptions),g)}return Object.freeze({init:function(e){r=L(e,r)},isRunning:function(){return r.isRunning},setRunning:function(e){r.isRunning!==e&&(r.isRunning=e,t.emit(e))},isInitialLoading:function(){return r.isInitialLoading},setInitialLoading:function(e){r.isInitialLoading=e},getLanguage:c,setLanguage:function(n){r.language!==n&&(r.language=n,e.emit(n))},getPendingLanguage:function(){return r.pendingLanguage||c()},setPendingLanguage:function(e){r.pendingLanguage!==e&&(r.pendingLanguage=e,n.emit(e))},getInitialOptions:u,addActiveNs:function(e){a(e).forEach((e=>{const n=r.activeNamespaces.get(e);void 0!==n?r.activeNamespaces.set(e,n+1):r.activeNamespaces.set(e,1)}))},removeActiveNs:function(e){a(e).forEach((e=>{const n=r.activeNamespaces.get(e);void 0!==n&&n>1?r.activeNamespaces.set(e,n-1):r.activeNamespaces.delete(e)}))},getRequiredNamespaces:function(){return o([...r.initialOptions.ns||[r.initialOptions.defaultNs],...r.activeNamespaces.keys()])},getFallbackLangs:function(e){const n=e||c();return n?o([n,...i(n,r.initialOptions.fallbackLanguage)]):[]},getFallbackNamespaces:function(){const e=r.initialOptions.defaultNs;return o([..."string"==typeof e?[e]:[],...a(r.initialOptions.fallbackNs)])},getAvailableLanguages:function(){if(r.initialOptions.availableLanguages)return r.initialOptions.availableLanguages;if(r.initialOptions.staticData){const e=Object.keys(r.initialOptions.staticData).map((e=>l(e).language));return Array.from(new Set(e))}},withDefaultNs:function(e){return{namespace:void 0===e.namespace?u().defaultNs:e.namespace,language:e.language}},overrideCredentials:function(e){g=e},setObserverOptions:function(e){s=y(e)},getObserverOptions:function(){return s}})})(t.onLanguageChange,t.onPendingLanguageChange,t.onRunningChange),u=b(c.getLanguage,c.getInitialOptions,c.getObserverOptions,c.getAvailableLanguages,(function({key:e,ns:n}){const t=void 0!==n?a(n):c.getFallbackNamespaces(),i=c.getFallbackLangs();return m.getTranslationNs(t,i,e)}),N,O),m=d(t.onCacheChange,u.getBackendRecord,u.getBackendDevRecord,c.withDefaultNs,c.isInitialLoading,r,s);function O(e,n,t){const a=c.withDefaultNs(e),i=m.getTranslation(a,n);return m.changeTranslation(a,n,t),{revert:()=>{m.changeTranslation(a,n,i)}}}function k(e){c.init(e),m.addStaticData(c.getInitialOptions().staticData)}function R(e){return m.isLoading(c.getLanguage(),e)}function E(){return Boolean(c.getInitialOptions().apiKey&&c.getInitialOptions().apiUrl&&u.getDevBackend())}function w(e,n){const t=function(e,n){const t=c.getFallbackLangs(e),i=void 0!==n?a(n):c.getRequiredNamespaces(),o=[];return t.forEach((e=>{i.forEach((n=>{m.exists({language:e,namespace:n},!0)||o.push({language:e,namespace:n})}))})),o}(e,n);if(t.length)return p(A(t),(()=>{}))}function N({key:e,ns:n}){const t=void 0!==n?a(n):c.getFallbackNamespaces(),i=c.getFallbackLangs();return m.getTranslationFallback(t,i,e)}function T(){const e=p(function(){if(c.getLanguage())return;if(!c.getInitialOptions().defaultLanguage)throw new Error(v("defaultLanguage"));const e=u.getInitialLanguage();return p(e,(e=>{const n=e||c.getInitialOptions().defaultLanguage;n&&c.setLanguage(n)}))}(),(()=>w()));if(f(e))return c.setInitialLoading(!0),r.notify(),s.notify(),Promise.resolve(e).then((()=>{c.setInitialLoading(!1),r.notify(),s.notify(),t.onInitialLoaded.emit()}));t.onInitialLoaded.emit()}function A(e){return m.loadRecords(e,E())}e&&k(e),t.onKeyUpdate.listen((()=>{c.isRunning()&&u.retranslate()}));return Object.freeze(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},t),c),u),m),{init:k,changeLanguage:async function(e){c.getPendingLanguage()===e&&c.getLanguage()===e||(c.setPendingLanguage(e),c.isRunning()&&await w(e),e===c.getPendingLanguage()&&(c.setLanguage(e),u.setStoredLanguage(e)))},getTranslation:N,changeTranslation:O,addActiveNs:async function(e,n){n||c.addActiveNs(e),c.isRunning()&&await w(void 0,e)},loadRequiredRecords:w,loadRecords:A,loadRecord:async function(e){return(await A([e]))[0]},isLoading:R,isLoaded:function(e){const n=c.getLanguage();if(!n)return!1;const t=c.getFallbackLangs(n),i=void 0!==e?a(e):c.getRequiredNamespaces(),o=[];return t.forEach((e=>{i.forEach((n=>{m.exists({language:e,namespace:n})||o.push({language:e,namespace:n})}))})),0===o.length},t:(...e)=>{const n=j(...e),t=N(n);return u.formatTranslation(Object.assign(Object.assign({},n),{translation:t}))},isDev:E,run:function(){let e;return(()=>{const e=u.getLanguageDetector();if(e&&!c.getAvailableLanguages())throw new Error(v("availableLanguages"));if(!c.getLanguage()&&!c.getInitialOptions().defaultLanguage)throw e?new Error(v("defaultLanguage")):new Error(v("language"))})(),c.isRunning()||(E()&&m.invalidate(),c.setRunning(!0),u.run(E()),e=T()),Promise.resolve(e)},stop:function(){c.isRunning()&&(u.stop(),c.setRunning(!1))}}))};e.DEVTOOLS_ID="__tolgee_dev_tools",e.RESTRICTED_ASCENDANT_ATTRIBUTE="data-tolgee-restricted",e.TOLGEE_ATTRIBUTE_NAME="_tolgee",e.TOLGEE_HIGHLIGHTER_CLASS="_tolgee-highlighter",e.TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE="data-tolgee-key-only",e.Tolgee=e=>{const n=R({options:e}),t=Object.freeze({setFinalFormatter:n.setFinalFormatter,addFormatter:n.addFormatter,setObserver:n.setObserver,hasObserver:n.hasObserver,setUi:n.setUi,hasUi:n.hasUi,setDevBackend:n.setDevBackend,addBackend:n.addBackend,setLanguageDetector:n.setLanguageDetector,setLanguageStorage:n.setLanguageStorage,overrideCredentials:n.overrideCredentials}),a=e=>{const t=n.isRunning();t&&n.stop(),e(),t&&n.run()},i=Object.freeze({on:n.on,onKeyUpdate:n.onKeyUpdate.listenSome,getLanguage:n.getLanguage,getPendingLanguage:n.getPendingLanguage,changeLanguage:n.changeLanguage,changeTranslation:n.changeTranslation,addActiveNs:n.addActiveNs,removeActiveNs:n.removeActiveNs,loadRecords:n.loadRecords,loadRecord:n.loadRecord,addStaticData:n.addStaticData,getRecord:n.getRecord,getAllRecords:n.getAllRecords,isLoaded:n.isLoaded,isInitialLoading:n.isInitialLoading,isLoading:n.isLoading,isFetching:n.isFetching,isRunning:n.isRunning,run:n.run,stop:n.stop,t:n.t,highlight:n.highlight,getInitialOptions:n.getInitialOptions,isDev:n.isDev,wrap:n.wrap,unwrap:n.unwrap,setObserverOptions:e=>(n.setObserverOptions(e),i),use:e=>(e&&a((()=>e(i,t))),i),init:e=>(a((()=>n.init(e))),i)});return i},e.getFallback=t,e.getFallbackArray=a,e.getTranslateParams=j,Object.defineProperty(e,"__esModule",{value:!0})})); | ||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self)["@tolgee/core"]={})}(this,(function(e){"use strict";function n(e){return Boolean(e&&"function"==typeof e.then)}const t=(e,t)=>n(e)?Promise.resolve(e).then(t):t(e),a=e=>`Tolgee: You need to specify '${e}' option`;function i(e){return"string"==typeof e?[e]:Array.isArray(e)?e:void 0}function o(e){return i(e)||[]}function r(e,n){return"object"!=typeof(t=n)||Array.isArray(t)||null===t?o(n):o(null==n?void 0:n[e]);var t}function s(e){return Array.from(new Set(e))}function c(e){return e?e.replace(/\/+$/,""):e}const g=()=>{let e=[];return Object.freeze({listen:n=>{const t=e=>{n(e)};return e.push(t),{unsubscribe:()=>{e=e.filter((e=>t!==e))}}},emit:n=>{e.forEach((e=>e({value:n})))}})},u=(e,n)=>{const t=new Set,a=new Set;let i=[];const r=()=>{if(0===i.length)return;const n=i;i=[],t.forEach((e=>{e({value:void 0})}));let o=new Set;n.forEach((e=>{void 0===e?o=void 0:void 0!==o&&e.forEach((e=>o.add(e)))}));(n=>{const t=new Set(e());a.forEach((e=>{(void 0===n||-1!==(null==n?void 0:n.findIndex((n=>t.has(n)||e.namespaces.has(n)))))&&e.fn({value:void 0})}))})(o?Array.from(o.keys()):void 0)};return Object.freeze({listenSome:e=>{const t={fn:n=>{e(n)},namespaces:new Set};a.add(t);const i={unsubscribe:()=>{a.delete(t)},subscribeNs:e=>(o(e).forEach((e=>t.namespaces.add(e))),void 0===e&&t.namespaces.add(n()),i)};return i},listen:e=>{t.add(e);return{unsubscribe:()=>{t.delete(e)}}},emit:(e,n)=>{i.push(e),n?setTimeout(r,0):r()}})},l=e=>{const n=new Map;return Object.entries(e).forEach((([e,t])=>{null!=t&&("object"!=typeof t?n.set(e,t):l(t).forEach(((t,a)=>{n.set(e+"."+a,t)})))})),n},d=e=>{const[n,...t]=e.split(":");return{language:n,namespace:t.join(":")||""}},f=({language:e,namespace:n})=>n?`${e}:${n}`:e,p=(e,n,t,a,i,r,c)=>{const g=new Map,u=new Map;let p={},v=0;function h(n,t,a){const i=f(n);u.set(i,{data:l(t),version:a}),e.emit(n)}function b(e,n){h(e,n,v)}function m(e,n=!1){const t=u.get(f(e));return t&&n?t.version===v:Boolean(t)}function y(e){var n;return null===(n=u.get(f(a(e))))||void 0===n?void 0:n.data}function O(e){let t;if(!t){const n=p[f(e)];"function"==typeof n&&(t=n())}return t||(t=n(e)),t}return Object.freeze({addStaticData:function(e){e&&(p=Object.assign(Object.assign({},p),e),Object.entries(e).forEach((([e,n])=>{if("function"!=typeof n){const t=d(e),a=u.get(e);a&&0!==a.version||h(t,n,0)}})))},invalidate:function(){g.clear(),v+=1},addRecord:b,exists:m,getRecord:y,getTranslation:function(e,n){var t;return null===(t=u.get(f(e)))||void 0===t?void 0:t.data.get(n)},getTranslationNs:function(e,n,t){var a;for(const i of e)for(const e of n){const n=null===(a=u.get(f({language:e,namespace:i})))||void 0===a?void 0:a.data.get(t);if(null!=n)return[i]}return s(e)},getTranslationFallback:function(e,n,t){var a;for(const i of e)for(const e of n){const n=null===(a=u.get(f({language:e,namespace:i})))||void 0===a?void 0:a.data.get(t);if(null!=n)return n}},changeTranslation:function(n,t,a){var i;const o=null===(i=u.get(f(n)))||void 0===i?void 0:i.data;null==o||o.set(t,a),e.emit(Object.assign(Object.assign({},n),{key:t}))},isFetching:function(e){if(i())return!0;if(void 0===e)return g.size>0;const n=o(e);return Boolean(Array.from(g.keys()).find((e=>n.includes(d(e).namespace))))},isLoading:function(e,n){const t=o(n);return Boolean(i()||Array.from(g.keys()).find((n=>{const a=d(n);return(!t.length||t.includes(a.namespace))&&!m({namespace:a.namespace,language:e})})))},loadRecords:async function(e,n){const i=e.map((e=>{const i=a(e),o=f(i),r=g.get(o);if(r)return{new:!1,promise:r,keyObject:i,cacheKey:o};const s=function(e,n){var a;let i;return n&&(i=null===(a=t(e))||void 0===a?void 0:a.catch((()=>(console.warn("Tolgee: Failed to fetch data from dev backend"),O(e))))),i||(i=O(e)),i}(i,n)||Promise.resolve(void 0);return g.set(o,s),{new:!0,promise:s,keyObject:i,cacheKey:o}}));r.notify(),c.notify();const o=await Promise.all(i.map((e=>e.promise)));return i.forEach(((e,n)=>{const t=g.get(e.cacheKey)!==e.promise;if(e.new&&!t){g.delete(e.cacheKey);const t=o[n];t?b(e.keyObject,t):y(e.keyObject)||b(e.keyObject,{})}})),r.notify(),c.notify(),i.map((e=>y(e.keyObject)))},getAllRecords:function(){return Array.from(u.entries()).map((([e,n])=>Object.assign(Object.assign({},d(e)),{data:n.data})))}})},v=(e,a,i,o,r,s)=>{let c=!1,g=[];const u={ui:void 0,observer:void 0},l={formatters:[],finalFormatter:void 0,observer:void 0,devBackend:void 0,backends:[],ui:void 0,languageDetector:void 0,languageStorage:void 0},d=async({keysAndDefaults:e,event:n})=>{var t;const a=e.map((({key:e,ns:n,defaultValue:t})=>({key:e,defaultValue:t,ns:o({key:e,ns:n}),translation:r({key:e,ns:n})})));null===(t=l.ui)||void 0===t||t.handleElementClick(a,n)},f=(e,n)=>{var t,a;return(null===(a=null===(t=l.observer)||void 0===t?void 0:t.highlight)||void 0===a?void 0:a.call(t,e,n))||{unhighlight(){}}},p=e=>{const n=r({key:e.key,ns:e.ns});return E(Object.assign(Object.assign({},e),{translation:n,formatEnabled:!0}))},v=e=>{u.observer=e},h=()=>Boolean(u.observer),b=e=>{e&&l.formatters.push(e)},m=e=>{l.finalFormatter=e},y=e=>{u.ui=(null==e?void 0:e.UI)||e},O=()=>Boolean(u.ui),L=e=>{l.languageStorage=e},j=e=>{l.languageDetector=e},k=e=>{e&&l.backends.push(e)},w=e=>{l.devBackend=e},R=()=>l.devBackend,N=e=>{g.push(e)};function E({key:n,translation:t,defaultValue:a,noWrap:i,params:o,orEmpty:r,ns:s,formatEnabled:c}){var g;const u=t||a;let d=u||(r?"":n);l.observer&&!i&&(d=l.observer.wrap({key:n,translation:d,defaultValue:a,params:o,ns:s}));const f=e(),p=c||!(null===(g=l.observer)||void 0===g?void 0:g.outputNotFormattable);if(u&&f&&p)for(const e of l.formatters)d=e.format({translation:d,language:f,params:o});return l.finalFormatter&&u&&f&&p&&(d=l.finalFormatter.format({translation:d,language:f,params:o})),d}function I(){for(c=!0;g.length;){const e=g;g=[],e.forEach((e=>e()))}}return Object.freeze({prepare:I,addPlugin:function(e,n){n(e,Object.freeze({setFinalFormatter:m,addFormatter:b,setObserver:v,hasObserver:h,setUi:y,hasUi:O,setDevBackend:w,addBackend:k,setLanguageDetector:j,setLanguageStorage:L,onPrepare:N})),c&&I()},formatTranslation:E,getDevBackend:R,getBackendRecord:({language:e,namespace:t})=>{for(const a of l.backends){const i=a.getRecord({language:e,namespace:t});if(n(i))return null==i?void 0:i.catch((e=>(console.error(e),{})));if(void 0!==i)return i}},getBackendDevRecord:({language:e,namespace:n})=>{var t;const{apiKey:i,apiUrl:o,projectId:r}=a();return null===(t=l.devBackend)||void 0===t?void 0:t.getRecord({apiKey:i,apiUrl:o,projectId:r,language:e,namespace:n})},getLanguageDetector:()=>l.languageDetector,getInitialLanguage:()=>{var e;const n=i(),a=null===(e=l.languageStorage)||void 0===e?void 0:e.getLanguage();return t(a,(e=>n&&!n.includes(e)||!e?(()=>{if(!l.languageDetector)return;const e=i();return l.languageDetector.getLanguage({availableLanguages:e})})():e))},setStoredLanguage:e=>{var n;null===(n=l.languageStorage)||void 0===n||n.setLanguage(e)},run:e=>{var n,t;if(!l.ui&&u.ui){const{apiKey:e,apiUrl:n,projectId:t}=a();l.ui=new u.ui({apiKey:e,apiUrl:n,projectId:t,highlight:f,changeTranslation:s})}l.observer||(l.observer=null===(n=u.observer)||void 0===n?void 0:n.call(u,{translate:p,onClick:d,options:a().observerOptions})),null===(t=l.observer)||void 0===t||t.run({mouseHighlight:e})},stop:()=>{var e;l.ui=void 0,null===(e=l.observer)||void 0===e||e.stop()},retranslate:()=>{var e;null===(e=l.observer)||void 0===e||e.retranslate()},highlight:f,unwrap:e=>{var n;return l.observer?null===(n=l.observer)||void 0===n?void 0:n.unwrap(e):{text:e,keys:[]}},wrap:e=>{var n;return l.observer?null===(n=l.observer)||void 0===n?void 0:n.wrap(e):e.translation},hasDevBackend:function(){return Boolean(R())}})},h=(e,n,t)=>{let a=e;return Object.freeze({init:function(e){a=e},notify:function(){const e=n();a!==e&&t(e),a=e}})},b={defaultNs:"",observerOptions:{tagAttributes:{textarea:["placeholder"],input:["value","placeholder"],img:["alt"],"*":["aria-label","title"]},restrictedElements:["script","style"],highlightKeys:["Alt"],highlightColor:"rgb(255, 0, 0)",highlightWidth:5,inputPrefix:"%-%tolgee:",inputSuffix:"%-%",passToParent:["option","optgroup"]},observerType:"invisible"},m=(...e)=>{let n={};return e.forEach((e=>{n=Object.assign(Object.assign(Object.assign({},n),e),{observerOptions:Object.assign(Object.assign({},n.observerOptions),null==e?void 0:e.observerOptions)})})),n},y=(e,n)=>{const t=m(b,null==n?void 0:n.initialOptions,e);return t.apiUrl=c(t.apiUrl),{initialOptions:t,activeNamespaces:(null==n?void 0:n.activeNamespaces)||new Map,language:null==n?void 0:n.language,pendingLanguage:null==n?void 0:n.language,isInitialLoading:!1,isRunning:!1}};function O(e){var{ns:n,noWrap:t,orEmpty:a,params:i}=e,o=function(e,n){var t={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&n.indexOf(a)<0&&(t[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(a=Object.getOwnPropertySymbols(e);i<a.length;i++)n.indexOf(a[i])<0&&Object.prototype.propertyIsEnumerable.call(e,a[i])&&(t[a[i]]=e[a[i]])}return t}(e,["ns","noWrap","orEmpty","params"]);const r={ns:n,noWrap:t,orEmpty:a};return Object.assign(Object.assign({},r),{params:Object.assign({},o)})}const L=(e,...n)=>{let t,a={};return"object"==typeof e?a=e:(a.key=e,"string"==typeof n[0]?(a.defaultValue=n[0],t=n[1]):"object"==typeof n[0]&&(t=n[0])),t&&(a=Object.assign(Object.assign({},O(t)),a)),a},j=({options:e})=>{const i=((e,n)=>{const t=g(),a=g(),i=g(),o=g(),r=g(),s=g(),c=g(),l=u(e,n);return r.listen((()=>l.emit())),a.listen((()=>l.emit())),c.listen((({value:e})=>{l.emit([e.namespace],!0)})),Object.freeze({onPendingLanguageChange:t,onLanguageChange:a,onLoadingChange:i,onFetchingChange:o,onInitialLoaded:r,onRunningChange:s,onCacheChange:c,onUpdate:l,on:(e,n)=>{switch(e){case"pendingLanguage":return t.listen(n);case"language":return a.listen(n);case"loading":return i.listen(n);case"fetching":return o.listen(n);case"initialLoad":return r.listen(n);case"running":return s.listen(n);case"cache":return c.listen(n);case"update":return l.listen(n)}}})})(j,k),l=h(!1,(()=>O.isFetching()),i.onFetchingChange.emit),f=h(!1,(()=>I()),i.onLoadingChange.emit),b=((e,n,t)=>{let a,i=y();function g(){return i.language||i.initialOptions.language}function u(){return Object.assign(Object.assign({},i.initialOptions),a)}return Object.freeze({init:function(e){i=y(e,i)},isRunning:function(){return i.isRunning},setRunning:function(e){i.isRunning!==e&&(i.isRunning=e,t.emit(e))},isInitialLoading:function(){return i.isInitialLoading},setInitialLoading:function(e){i.isInitialLoading=e},getLanguage:g,setLanguage:function(n){i.language!==n&&(i.language=n,e.emit(n))},getPendingLanguage:function(){return i.pendingLanguage||g()},setPendingLanguage:function(e){i.pendingLanguage!==e&&(i.pendingLanguage=e,n.emit(e))},getInitialOptions:u,addActiveNs:function(e){o(e).forEach((e=>{const n=i.activeNamespaces.get(e);void 0!==n?i.activeNamespaces.set(e,n+1):i.activeNamespaces.set(e,1)}))},removeActiveNs:function(e){o(e).forEach((e=>{const n=i.activeNamespaces.get(e);void 0!==n&&n>1?i.activeNamespaces.set(e,n-1):i.activeNamespaces.delete(e)}))},getRequiredNamespaces:function(){return s([...i.initialOptions.ns||[i.initialOptions.defaultNs],...o(i.initialOptions.fallbackNs),...i.activeNamespaces.keys()])},getFallbackLangs:function(e){const n=e||g();return n?s([n,...r(n,i.initialOptions.fallbackLanguage)]):[]},getFallbackNs:function(){return o(i.initialOptions.fallbackNs)},getDefaultNs:function(e){return void 0===e?i.initialOptions.defaultNs:e},getAvailableLanguages:function(){if(i.initialOptions.availableLanguages)return i.initialOptions.availableLanguages;if(i.initialOptions.staticData){const e=Object.keys(i.initialOptions.staticData).map((e=>d(e).language));return Array.from(new Set(e))}},withDefaultNs:function(e){return{namespace:void 0===e.namespace?u().defaultNs:e.namespace,language:e.language}},overrideCredentials:function(e){a=e?Object.assign(Object.assign({},e),{apiUrl:c(e.apiUrl)}):void 0}})})(i.onLanguageChange,i.onPendingLanguageChange,i.onRunningChange),m=v(b.getLanguage,b.getInitialOptions,b.getAvailableLanguages,(function({key:e,ns:n}){const t=b.getFallbackLangs(),a=w(n);return O.getTranslationNs(a,t,e)}),F,N),O=p(i.onCacheChange,m.getBackendRecord,m.getBackendDevRecord,b.withDefaultNs,b.isInitialLoading,l,f);function j(){return b.getFallbackNs()}function k(e){return b.getDefaultNs(e)}function w(e){return[...o(k(e)),...j()]}function R(e){return[...o(e||k()),...b.getRequiredNamespaces()]}function N(e,n,t){const a=b.withDefaultNs(e),i=O.getTranslation(a,n);return O.changeTranslation(a,n,t),{revert:()=>{O.changeTranslation(a,n,i)}}}function E(e){b.init(e),O.addStaticData(b.getInitialOptions().staticData)}function I(e){return O.isLoading(b.getLanguage(),e)}function D(){return Boolean(b.getInitialOptions().apiKey&&b.getInitialOptions().apiUrl)}function A(e,n){const a=function(e,n){const t=b.getFallbackLangs(e),a=R(n),i=[];return t.forEach((e=>{a.forEach((n=>{O.exists({language:e,namespace:n},!0)||i.push({language:e,namespace:n})}))})),i}(e,n);if(a.length)return t(P(a),(()=>{}))}function F({key:e,ns:n}){const t=w(n),a=b.getFallbackLangs();return O.getTranslationFallback(t,a,e)}function S(){const e=t(function(){if(b.getLanguage())return;if(!b.getInitialOptions().defaultLanguage)throw new Error(a("defaultLanguage"));const e=m.getInitialLanguage();return t(e,(e=>{const n=e||b.getInitialOptions().defaultLanguage;n&&b.setLanguage(n)}))}(),(()=>A()));if(n(e))return b.setInitialLoading(!0),l.notify(),f.notify(),Promise.resolve(e).then((()=>{b.setInitialLoading(!1),l.notify(),f.notify(),i.onInitialLoaded.emit()}));i.onInitialLoaded.emit()}function P(e){return O.loadRecords(e,D())}e&&E(e),i.onUpdate.listen((()=>{b.isRunning()&&m.retranslate()}));return Object.freeze(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},i),b),m),O),{init:E,changeLanguage:async function(e){b.getPendingLanguage()===e&&b.getLanguage()===e||(b.setPendingLanguage(e),b.isRunning()&&await A(e),e===b.getPendingLanguage()&&(b.setLanguage(e),m.setStoredLanguage(e)))},getTranslation:F,changeTranslation:N,addActiveNs:async function(e,n){n||b.addActiveNs(e),b.isRunning()&&await A(void 0,e)},loadRecords:P,loadRecord:async function(e){return(await P([e]))[0]},isLoading:I,isLoaded:function(e){const n=b.getLanguage();if(!n)return!1;const t=b.getFallbackLangs(n),a=R(e),i=[];return t.forEach((e=>{a.forEach((n=>{O.exists({language:e,namespace:n})||i.push({language:e,namespace:n})}))})),0===i.length},t:(...e)=>{const n=L(...e),t=F(n);return m.formatTranslation(Object.assign(Object.assign({},n),{translation:t}))},isDev:D,run:function(){let e;return(()=>{const e=m.getLanguageDetector();if(e&&!b.getAvailableLanguages())throw new Error(a("availableLanguages"));if(!b.getLanguage()&&!b.getInitialOptions().defaultLanguage)throw e?new Error(a("defaultLanguage")):new Error(a("language"))})(),b.isRunning()||(D()&&O.invalidate(),b.setRunning(!0),m.run(D()),e=S()),Promise.resolve(e)},stop:function(){b.isRunning()&&(m.stop(),b.setRunning(!1))}}))};class k extends Error{constructor(e,n,t){let a;a=0===e?"Empty parameter":1===e?"Unexpected character":"Unexpected end",super(`Tolgee parser: ${a} at ${n} in "${t}"`),this.code=e,this.index=n}}function w(e){return/\s/.test(e)}const R=new Set([2,1,0]),N=new Set(["{","}","'"]);function E(e,n){const[t,a]=function(e){let n=0,t="",a="",i="";const o=[],r=[];let s=0;function c(n){throw new k(n,s,e)}const g=()=>{o.push(t),t=""},u=()=>{""===a&&c(0),r.push(a),a=""};for(s=0;s<e.length;s++)switch(i=e[s],n){case 0:"'"===i?(t+=i,n=1):"{"===i?(g(),n=3):(t+=i,n=0);break;case 1:N.has(i)?(t=t.slice(0,-1)+i,n=2):(t+=i,n=0);break;case 2:"'"===i?n=0:(t+=i,n=2);break;case 3:"}"===i?(u(),n=0):w(i)?""!==a&&(u(),n=4):(/[0-9a-zA-Z_]/.test(i)||c(1),a+=i,n=3);break;case 4:"}"==i?n=0:w(i)?n=4:c(1)}return R.has(n)||c(2),g(),[o,r]}(e),i=[t[0]];for(let o=1;o<t.length;o++){const r=null==n?void 0:n[a[o-1]];if(void 0===r)throw new Error(`Missing parameter "${a[o-1]}" in "${e}"`);i.push(String(r)),i.push(t[o])}return i.join("")}e.FormatSimple=()=>(e,n)=>(n.setFinalFormatter({format:({translation:e,params:n})=>E(e,n)}),e),e.Tolgee=()=>{const e={plugins:[],options:{}},n=Object.freeze({use:t=>(e.plugins.push(t),n),updateDefaults:t=>(e.options=m(e.options,t),n),init(n){const t=(e=>{const n=j({options:e}),t=e=>{const t=n.isRunning();t&&n.stop(),e(),t&&n.run()},a=Object.freeze({on:n.on,onNsUpdate:n.onUpdate.listenSome,getLanguage:n.getLanguage,getPendingLanguage:n.getPendingLanguage,changeLanguage:n.changeLanguage,changeTranslation:n.changeTranslation,addActiveNs:n.addActiveNs,removeActiveNs:n.removeActiveNs,loadRecords:n.loadRecords,loadRecord:n.loadRecord,addStaticData:n.addStaticData,getRecord:n.getRecord,getAllRecords:n.getAllRecords,isLoaded:n.isLoaded,isInitialLoading:n.isInitialLoading,isLoading:n.isLoading,isFetching:n.isFetching,isRunning:n.isRunning,run:n.run,stop:n.stop,t:n.t,highlight:n.highlight,getInitialOptions:n.getInitialOptions,isDev:n.isDev,wrap:n.wrap,unwrap:n.unwrap,overrideCredentials(e){t((()=>n.overrideCredentials(e)))},addPlugin(e){e&&t((()=>n.addPlugin(a,e)))},updateOptions(e){e&&t((()=>n.init(e)))}});return a})(m(e.options,n));return e.plugins.forEach(t.addPlugin),t}});return n},e.getFallback=i,e.getFallbackArray=o,e.getTranslateProps=L,Object.defineProperty(e,"__esModule",{value:!0})})); | ||
//# sourceMappingURL=tolgee.umd.min.js.map |
@@ -1,5 +0,7 @@ | ||
import { CacheDescriptor, CacheDescriptorInternal, CacheDescriptorWithKey, EventEmitterType, FallbackNSTranslation, Options, TranslationsFlat, TranslationValue, TreeTranslationsData, BackendGetRecord, BackendGetDevRecord } from '../../types'; | ||
import { CacheDescriptor, CacheDescriptorInternal, CacheDescriptorWithKey, NsFallback, TranslationsFlat, TranslationValue, TreeTranslationsData, BackendGetRecord, BackendGetDevRecord } from '../../types'; | ||
import { EventEmitterInstance } from '../Events/EventEmitter'; | ||
import { TolgeeStaticData } from '../State/initState'; | ||
import { ValueObserverInstance } from '../ValueObserver'; | ||
export declare const Cache: (onCacheChange: EventEmitterType<CacheDescriptorWithKey>, backendGetRecord: BackendGetRecord, backendGetDevRecord: BackendGetDevRecord, withDefaultNs: (descriptor: CacheDescriptor) => CacheDescriptorInternal, isInitialLoading: () => boolean, fetchingObserver: ValueObserverInstance<boolean>, loadingObserver: ValueObserverInstance<boolean>) => Readonly<{ | ||
addStaticData: (data: Options['staticData']) => void; | ||
export declare const Cache: (onCacheChange: EventEmitterInstance<CacheDescriptorWithKey>, backendGetRecord: BackendGetRecord, backendGetDevRecord: BackendGetDevRecord, withDefaultNs: (descriptor: CacheDescriptor) => CacheDescriptorInternal, isInitialLoading: () => boolean, fetchingObserver: ValueObserverInstance<boolean>, loadingObserver: ValueObserverInstance<boolean>) => Readonly<{ | ||
addStaticData: (data: TolgeeStaticData | undefined) => void; | ||
invalidate: () => void; | ||
@@ -10,7 +12,7 @@ addRecord: (descriptor: CacheDescriptorInternal, data: TreeTranslationsData) => void; | ||
getTranslation: (descriptor: CacheDescriptorInternal, key: string) => TranslationValue; | ||
getTranslationNs: (namespaces: string[], languages: string[], key: string) => string | string[]; | ||
getTranslationNs: (namespaces: string[], languages: string[], key: string) => string[]; | ||
getTranslationFallback: (namespaces: string[], languages: string[], key: string) => string | undefined; | ||
changeTranslation: (descriptor: CacheDescriptorInternal, key: string, value: TranslationValue) => void; | ||
isFetching: (ns?: FallbackNSTranslation) => boolean; | ||
isLoading: (language: string | undefined, ns?: FallbackNSTranslation) => boolean; | ||
isFetching: (ns?: NsFallback) => boolean; | ||
isLoading: (language: string | undefined, ns?: NsFallback) => boolean; | ||
loadRecords: (descriptors: CacheDescriptor[], isDev: boolean) => Promise<TranslationsFlat[]>; | ||
@@ -23,2 +25,2 @@ getAllRecords: () => { | ||
}>; | ||
export declare type CacheType = ReturnType<typeof Cache>; | ||
export declare type CacheInstance = ReturnType<typeof Cache>; |
@@ -1,18 +0,17 @@ | ||
import { CacheDescriptor, FallbackNSTranslation, Options, TFnType, TranslatePropsInternal } from '../types'; | ||
import { CacheDescriptor, NsFallback, TolgeeOptions, TFnType, KeyAndNamespacesInternal } from '../types'; | ||
declare type StateServiceProps = { | ||
options?: Partial<Options>; | ||
options?: Partial<TolgeeOptions>; | ||
}; | ||
export declare const Controller: ({ options }: StateServiceProps) => Readonly<{ | ||
init: (options: Partial<Options>) => void; | ||
init: (options: Partial<TolgeeOptions>) => void; | ||
changeLanguage: (language: string) => Promise<void>; | ||
getTranslation: ({ key, ns, }: Pick<TranslatePropsInternal, 'key' | 'ns'>) => string | undefined; | ||
getTranslation: ({ key, ns }: KeyAndNamespacesInternal) => string | undefined; | ||
changeTranslation: (descriptor: CacheDescriptor, key: string, value: string) => { | ||
revert: () => void; | ||
}; | ||
addActiveNs: (ns: FallbackNSTranslation, forget?: boolean) => Promise<void>; | ||
loadRequiredRecords: (lang?: string, ns?: FallbackNSTranslation) => void | Promise<void>; | ||
addActiveNs: (ns: NsFallback, forget?: boolean) => Promise<void>; | ||
loadRecords: (descriptors: CacheDescriptor[]) => Promise<import("../types").TranslationsFlat[]>; | ||
loadRecord: (descriptor: CacheDescriptor) => Promise<import("../types").TranslationsFlat>; | ||
isLoading: (ns?: FallbackNSTranslation) => boolean; | ||
isLoaded: (ns?: FallbackNSTranslation) => boolean; | ||
isLoading: (ns?: NsFallback) => boolean; | ||
isLoaded: (ns?: NsFallback) => boolean; | ||
t: TFnType<import("../types").DefaultParamType, string>; | ||
@@ -22,5 +21,3 @@ isDev: () => boolean; | ||
stop: () => void; | ||
addStaticData: (data: { | ||
[key: string]: import("../types").TreeTranslationsData | (() => Promise<import("../types").TreeTranslationsData>); | ||
} | undefined) => void; | ||
addStaticData: (data: import("./State/initState").TolgeeStaticData | undefined) => void; | ||
invalidate: () => void; | ||
@@ -30,5 +27,5 @@ addRecord: (descriptor: import("../types").CacheDescriptorInternal, data: import("../types").TreeTranslationsData) => void; | ||
getRecord: (descriptor: CacheDescriptor) => import("../types").TranslationsFlat | undefined; | ||
getTranslationNs: (namespaces: string[], languages: string[], key: string) => string | string[]; | ||
getTranslationNs: (namespaces: string[], languages: string[], key: string) => string[]; | ||
getTranslationFallback: (namespaces: string[], languages: string[], key: string) => string | undefined; | ||
isFetching: (ns?: FallbackNSTranslation) => boolean; | ||
isFetching: (ns?: NsFallback) => boolean; | ||
getAllRecords: () => { | ||
@@ -39,8 +36,59 @@ data: import("../types").TranslationsFlat; | ||
}[]; | ||
setFinalFormatter: (formatter: import("../types").FinalFormatterInterface | undefined) => void; | ||
addFormatter: (formatter: import("../types").FormatterInterface | undefined) => void; | ||
prepare: () => void; | ||
addPlugin: (tolgeeInstance: Readonly<{ | ||
on: import("../types").TolgeeOn<keyof import("../types").EventType>; | ||
onNsUpdate: (handler: import("../types").Listener<undefined>) => import("../types").SubscriptionSelective; | ||
getLanguage: () => string | undefined; | ||
getPendingLanguage: () => string | undefined; | ||
changeLanguage: (language: string) => Promise<void>; | ||
changeTranslation: (descriptor: CacheDescriptor, key: string, value: string) => { | ||
revert: () => void; | ||
}; | ||
addActiveNs: (ns: NsFallback, forget?: boolean) => Promise<void>; | ||
removeActiveNs: (ns: NsFallback) => void; | ||
loadRecords: (descriptors: CacheDescriptor[]) => Promise<import("../types").TranslationsFlat[]>; | ||
loadRecord: (descriptor: CacheDescriptor) => Promise<import("../types").TranslationsFlat>; | ||
addStaticData: (data: import("./State/initState").TolgeeStaticData | undefined) => void; | ||
getRecord: (descriptor: CacheDescriptor) => import("../types").TranslationsFlat | undefined; | ||
getAllRecords: () => { | ||
data: import("../types").TranslationsFlat; | ||
language: string; | ||
namespace: string; | ||
}[]; | ||
isLoaded: (ns?: NsFallback) => boolean; | ||
isInitialLoading: () => boolean; | ||
isLoading: (ns?: NsFallback) => boolean; | ||
isFetching: (ns?: NsFallback) => boolean; | ||
isRunning: () => boolean; | ||
run: () => Promise<void | undefined>; | ||
stop: () => void; | ||
t: TFnType<import("../types").DefaultParamType, string>; | ||
highlight: import("../types").HighlightInterface; | ||
getInitialOptions: () => { | ||
apiUrl?: string | undefined; | ||
apiKey?: string | undefined; | ||
projectId?: string | number | undefined; | ||
language?: string | undefined; | ||
defaultLanguage?: string | undefined; | ||
availableLanguages?: string[] | undefined; | ||
fallbackLanguage?: import("../types").FallbackLanguageOption; | ||
ns?: string[] | undefined; | ||
fallbackNs?: import("../types").FallbackGeneral; | ||
defaultNs: string; | ||
staticData?: import("./State/initState").TolgeeStaticData | undefined; | ||
observerOptions: import("./State/observerOptions").ObserverOptionsInternal; | ||
observerType: "invisible" | "text"; | ||
}; | ||
isDev: () => boolean; | ||
wrap: (params: import("../types").WrapperWrapProps) => string | undefined; | ||
unwrap: (text: string) => import("../types").Unwrapped; | ||
overrideCredentials(credentials: import("../types").DevCredentials): void; | ||
addPlugin(plugin: import("../types").TolgeePlugin | undefined): void; | ||
updateOptions(options?: TolgeeOptions | undefined): void; | ||
}>, plugin: import("../types").TolgeePlugin) => void; | ||
formatTranslation: ({ key, translation, defaultValue, noWrap, params, orEmpty, ns, formatEnabled, }: { | ||
key: string; | ||
defaultValue?: string | undefined; | ||
} & import("../types").TranslateOptions<import("../types").DefaultParamType> & { | ||
params?: import("../types").TranslateParams<import("../types").DefaultParamType> | undefined; | ||
} & import("../types").TranslateOptions & { | ||
translation?: string | undefined; | ||
@@ -50,15 +98,6 @@ } & { | ||
}) => string; | ||
setObserver: (observer: import("../types").ObserverInterface | undefined) => void; | ||
hasObserver: () => boolean; | ||
setUi: (ui: import("../types").UiType | undefined) => void; | ||
hasUi: () => boolean; | ||
addBackend: (backend: import("../types").BackendInterface | undefined) => void; | ||
setDevBackend: (backend: import("../types").BackendDevInterface | undefined) => void; | ||
getDevBackend: () => import("../types").BackendDevInterface | undefined; | ||
getDevBackend: () => import("../types").BackendDevMiddleware | undefined; | ||
getBackendRecord: import("../types").BackendGetRecord; | ||
getBackendDevRecord: import("../types").BackendGetRecord; | ||
setLanguageDetector: (detector: import("../types").LanguageDetectorInterface | undefined) => void; | ||
getLanguageDetector: () => import("../types").LanguageDetectorInterface | undefined; | ||
setLanguageStorage: (storage: import("../types").LanguageStorageInterface | undefined) => void; | ||
getLanguageStorage: () => import("../types").LanguageStorageInterface | undefined; | ||
getLanguageDetector: () => import("../types").LanguageDetectorMiddleware | undefined; | ||
getInitialLanguage: () => string | Promise<string | undefined> | Promise<string | Promise<string | undefined> | undefined> | undefined; | ||
@@ -68,4 +107,5 @@ setStoredLanguage: (language: string) => void; | ||
highlight: import("../types").HighlightInterface; | ||
unwrap: (text: string) => import("../types").Unwrapped; | ||
wrap: (params: import("../types").WrapperWrapProps) => string | undefined; | ||
unwrap: (text: string) => import("../types").Unwrapped; | ||
hasDevBackend: () => boolean; | ||
isRunning: () => boolean; | ||
@@ -82,4 +122,4 @@ setRunning: (value: boolean) => void; | ||
apiKey?: string | undefined; | ||
projectId?: string | number | undefined; | ||
language?: string | undefined; | ||
projectId?: number | undefined; | ||
defaultLanguage?: string | undefined; | ||
@@ -91,64 +131,25 @@ availableLanguages?: string[] | undefined; | ||
defaultNs: string; | ||
filesUrlPrefix: string; | ||
staticData?: { | ||
[key: string]: import("../types").TreeTranslationsData | (() => Promise<import("../types").TreeTranslationsData>); | ||
} | undefined; | ||
staticData?: import("./State/initState").TolgeeStaticData | undefined; | ||
observerOptions: import("./State/observerOptions").ObserverOptionsInternal; | ||
observerType: "invisible" | "text"; | ||
}; | ||
removeActiveNs: (ns: FallbackNSTranslation) => void; | ||
removeActiveNs: (ns: NsFallback) => void; | ||
getRequiredNamespaces: () => string[]; | ||
getFallbackLangs: (lang?: string | undefined) => string[]; | ||
getFallbackNamespaces: () => string[]; | ||
getFallbackNs: () => string[]; | ||
getDefaultNs: (ns?: string | undefined) => string; | ||
getAvailableLanguages: () => string[] | undefined; | ||
withDefaultNs: (descriptor: CacheDescriptor) => import("../types").CacheDescriptorInternal; | ||
overrideCredentials: (credentials: import("../types").DevCredentials) => void; | ||
setObserverOptions: (options: Partial<import("./State/initObserverOptions").ObserverOptions>) => void; | ||
getObserverOptions: () => import("./State/initObserverOptions").ObserverOptions; | ||
onPendingLanguageChange: Readonly<{ | ||
listen: (handler: import("../types").ListenerHandler<string>) => import("../types").Listener; | ||
emit: (data: string) => void; | ||
}>; | ||
onLanguageChange: Readonly<{ | ||
listen: (handler: import("../types").ListenerHandler<string>) => import("../types").Listener; | ||
emit: (data: string) => void; | ||
}>; | ||
onKeyChange: Readonly<{ | ||
listen: (handler: import("../types").ListenerHandler<import("../types").KeyDescriptorInternal>) => import("../types").Listener; | ||
emit: (data: import("../types").KeyDescriptorInternal) => void; | ||
}>; | ||
onKeyUpdate: Readonly<{ | ||
listenSome: (handler: import("../types").ListenerHandler<undefined>) => { | ||
unsubscribe: () => void; | ||
subscribeNs: (ns: FallbackNSTranslation) => any; | ||
unsubscribeNs: (ns: FallbackNSTranslation) => any; | ||
subscribeKey: (descriptor: import("../types").KeyDescriptor) => any; | ||
unsubscribeKey: (descriptor: import("../types").KeyDescriptor) => any; | ||
}; | ||
listen: (handler: import("../types").ListenerHandler<void>) => { | ||
unsubscribe: () => void; | ||
}; | ||
emit: (descriptor?: import("../types").KeyDescriptorInternal | undefined, delayed?: boolean | undefined) => void; | ||
}>; | ||
onLoadingChange: Readonly<{ | ||
listen: (handler: import("../types").ListenerHandler<boolean>) => import("../types").Listener; | ||
emit: (data: boolean) => void; | ||
}>; | ||
onFetchingChange: Readonly<{ | ||
listen: (handler: import("../types").ListenerHandler<boolean>) => import("../types").Listener; | ||
emit: (data: boolean) => void; | ||
}>; | ||
onInitialLoaded: Readonly<{ | ||
listen: (handler: import("../types").ListenerHandler<void>) => import("../types").Listener; | ||
emit: (data: void) => void; | ||
}>; | ||
onRunningChange: Readonly<{ | ||
listen: (handler: import("../types").ListenerHandler<boolean>) => import("../types").Listener; | ||
emit: (data: boolean) => void; | ||
}>; | ||
onCacheChange: Readonly<{ | ||
listen: (handler: import("../types").ListenerHandler<import("../types").CacheDescriptorWithKey>) => import("../types").Listener; | ||
emit: (data: import("../types").CacheDescriptorWithKey) => void; | ||
}>; | ||
on: import("../types").TolgeeOn; | ||
onPendingLanguageChange: import("./Events/EventEmitter").EventEmitterInstance<string>; | ||
onLanguageChange: import("./Events/EventEmitter").EventEmitterInstance<string>; | ||
onLoadingChange: import("./Events/EventEmitter").EventEmitterInstance<boolean>; | ||
onFetchingChange: import("./Events/EventEmitter").EventEmitterInstance<boolean>; | ||
onInitialLoaded: import("./Events/EventEmitter").EventEmitterInstance<void>; | ||
onRunningChange: import("./Events/EventEmitter").EventEmitterInstance<boolean>; | ||
onCacheChange: import("./Events/EventEmitter").EventEmitterInstance<import("../types").CacheDescriptorWithKey>; | ||
onUpdate: import("./Events/EventEmitterSelective").EventEmitterSelectiveInstance; | ||
on: import("../types").TolgeeOn<keyof import("../types").EventType>; | ||
}>; | ||
export declare type StateServiceType = ReturnType<typeof Controller>; | ||
export declare type ControllerInstance = ReturnType<typeof Controller>; | ||
export {}; |
@@ -1,6 +0,6 @@ | ||
import { Listener, ListenerHandler } from '../../types'; | ||
export declare const EventEmitter: <T>() => Readonly<{ | ||
listen: (handler: ListenerHandler<T>) => Listener; | ||
emit: (data: T) => void; | ||
}>; | ||
export declare type EventEmitterType<T> = ReturnType<typeof EventEmitter<T>>; | ||
import { Subscription, Listener } from '../../types'; | ||
export declare const EventEmitter: <T>() => EventEmitterInstance<T>; | ||
export declare type EventEmitterInstance<T> = { | ||
readonly listen: (handler: Listener<T>) => Subscription; | ||
readonly emit: (data: T) => void; | ||
}; |
@@ -1,15 +0,7 @@ | ||
import { FallbackNSTranslation, KeyDescriptor, KeyDescriptorInternal, ListenerHandler } from '../../types'; | ||
export declare const EventEmitterSelective: <T>(getFallbackNamespaces: () => string[]) => Readonly<{ | ||
listenSome: (handler: ListenerHandler<undefined>) => { | ||
unsubscribe: () => void; | ||
subscribeNs: (ns: FallbackNSTranslation) => any; | ||
unsubscribeNs: (ns: FallbackNSTranslation) => any; | ||
subscribeKey: (descriptor: KeyDescriptor) => any; | ||
unsubscribeKey: (descriptor: KeyDescriptor) => any; | ||
}; | ||
listen: (handler: ListenerHandler<T>) => { | ||
unsubscribe: () => void; | ||
}; | ||
emit: (descriptor?: KeyDescriptorInternal, delayed?: boolean) => void; | ||
}>; | ||
export declare type EventEmitterSelectiveType<T> = ReturnType<typeof EventEmitterSelective<T>>; | ||
import { Subscription, Listener, SubscriptionSelective } from '../../types'; | ||
export declare const EventEmitterSelective: (getFallbackNs: () => string[], getDefaultNs: () => string) => EventEmitterSelectiveInstance; | ||
export declare type EventEmitterSelectiveInstance = { | ||
readonly listenSome: (handler: Listener<undefined>) => SubscriptionSelective; | ||
readonly listen: (handler: Listener<undefined>) => Subscription; | ||
readonly emit: (ns?: string[], delayed?: boolean) => void; | ||
}; |
@@ -1,50 +0,13 @@ | ||
import { CacheDescriptorWithKey, KeyDescriptorInternal, ListenerHandler, TolgeeOn } from '../../types'; | ||
export declare const Events: (getFallbackNamespaces: () => string[]) => Readonly<{ | ||
onPendingLanguageChange: Readonly<{ | ||
listen: (handler: ListenerHandler<string>) => import("../../types").Listener; | ||
emit: (data: string) => void; | ||
}>; | ||
onLanguageChange: Readonly<{ | ||
listen: (handler: ListenerHandler<string>) => import("../../types").Listener; | ||
emit: (data: string) => void; | ||
}>; | ||
onKeyChange: Readonly<{ | ||
listen: (handler: ListenerHandler<KeyDescriptorInternal>) => import("../../types").Listener; | ||
emit: (data: KeyDescriptorInternal) => void; | ||
}>; | ||
onKeyUpdate: Readonly<{ | ||
listenSome: (handler: ListenerHandler<undefined>) => { | ||
unsubscribe: () => void; | ||
subscribeNs: (ns: import("../../types").FallbackNSTranslation) => any; | ||
unsubscribeNs: (ns: import("../../types").FallbackNSTranslation) => any; | ||
subscribeKey: (descriptor: import("../../types").KeyDescriptor) => any; | ||
unsubscribeKey: (descriptor: import("../../types").KeyDescriptor) => any; | ||
}; | ||
listen: (handler: ListenerHandler<void>) => { | ||
unsubscribe: () => void; | ||
}; | ||
emit: (descriptor?: KeyDescriptorInternal | undefined, delayed?: boolean | undefined) => void; | ||
}>; | ||
onLoadingChange: Readonly<{ | ||
listen: (handler: ListenerHandler<boolean>) => import("../../types").Listener; | ||
emit: (data: boolean) => void; | ||
}>; | ||
onFetchingChange: Readonly<{ | ||
listen: (handler: ListenerHandler<boolean>) => import("../../types").Listener; | ||
emit: (data: boolean) => void; | ||
}>; | ||
onInitialLoaded: Readonly<{ | ||
listen: (handler: ListenerHandler<void>) => import("../../types").Listener; | ||
emit: (data: void) => void; | ||
}>; | ||
onRunningChange: Readonly<{ | ||
listen: (handler: ListenerHandler<boolean>) => import("../../types").Listener; | ||
emit: (data: boolean) => void; | ||
}>; | ||
onCacheChange: Readonly<{ | ||
listen: (handler: ListenerHandler<CacheDescriptorWithKey>) => import("../../types").Listener; | ||
emit: (data: CacheDescriptorWithKey) => void; | ||
}>; | ||
on: TolgeeOn; | ||
import { CacheDescriptorWithKey, TolgeeOn } from '../../types'; | ||
export declare const Events: (getFallbackNs: () => string[], getDefaultNs: () => string) => Readonly<{ | ||
onPendingLanguageChange: import("./EventEmitter").EventEmitterInstance<string>; | ||
onLanguageChange: import("./EventEmitter").EventEmitterInstance<string>; | ||
onLoadingChange: import("./EventEmitter").EventEmitterInstance<boolean>; | ||
onFetchingChange: import("./EventEmitter").EventEmitterInstance<boolean>; | ||
onInitialLoaded: import("./EventEmitter").EventEmitterInstance<void>; | ||
onRunningChange: import("./EventEmitter").EventEmitterInstance<boolean>; | ||
onCacheChange: import("./EventEmitter").EventEmitterInstance<CacheDescriptorWithKey>; | ||
onUpdate: import("./EventEmitterSelective").EventEmitterSelectiveInstance; | ||
on: TolgeeOn<keyof import("../../types").EventType>; | ||
}>; | ||
export declare type EventServiceType = ReturnType<typeof Events>; | ||
export declare type EventsInstance = ReturnType<typeof Events>; |
@@ -1,10 +0,10 @@ | ||
import { BackendDevInterface, BackendGetRecord, BackendInterface, FormatterInterface, ObserverInterface, TranslatePropsInternal, UiType, FinalFormatterInterface, HighlightInterface, LanguageDetectorInterface, LanguageStorageInterface, Options, ChangeTranslationInterface, WrapperWrapProps, Unwrapped } from '../../types'; | ||
import { ObserverOptions } from '../State/initObserverOptions'; | ||
export declare const PluginService: (getLanguage: () => string | undefined, getInitialOptions: () => Options, getObserverOptions: () => ObserverOptions, getAvailableLanguages: () => string[] | undefined, getTranslationNs: (props: TranslatePropsInternal) => string[] | string, getTranslation: (props: TranslatePropsInternal) => string | undefined, changeTranslation: ChangeTranslationInterface) => Readonly<{ | ||
setFinalFormatter: (formatter: FinalFormatterInterface | undefined) => void; | ||
addFormatter: (formatter: FormatterInterface | undefined) => void; | ||
import { BackendDevMiddleware, BackendGetRecord, TranslatePropsInternal, HighlightInterface, LanguageDetectorMiddleware, ChangeTranslationInterface, WrapperWrapProps, Unwrapped, KeyAndNamespacesInternal, TolgeePlugin, TolgeeInstance, TolgeeOptionsInternal } from '../../types'; | ||
export declare const Plugins: (getLanguage: () => string | undefined, getInitialOptions: () => TolgeeOptionsInternal, getAvailableLanguages: () => string[] | undefined, getTranslationNs: (props: KeyAndNamespacesInternal) => string[], getTranslation: (props: KeyAndNamespacesInternal) => string | undefined, changeTranslation: ChangeTranslationInterface) => Readonly<{ | ||
prepare: () => void; | ||
addPlugin: (tolgeeInstance: TolgeeInstance, plugin: TolgeePlugin) => void; | ||
formatTranslation: ({ key, translation, defaultValue, noWrap, params, orEmpty, ns, formatEnabled, }: { | ||
key: string; | ||
defaultValue?: string | undefined; | ||
} & import("../../types").TranslateOptions<import("../../types").DefaultParamType> & { | ||
params?: import("../../types").TranslateParams<import("../../types").DefaultParamType> | undefined; | ||
} & import("../../types").TranslateOptions & { | ||
translation?: string | undefined; | ||
@@ -14,15 +14,6 @@ } & { | ||
}) => string; | ||
setObserver: (observer: ObserverInterface | undefined) => void; | ||
hasObserver: () => boolean; | ||
setUi: (ui: UiType | undefined) => void; | ||
hasUi: () => boolean; | ||
addBackend: (backend: BackendInterface | undefined) => void; | ||
setDevBackend: (backend: BackendDevInterface | undefined) => void; | ||
getDevBackend: () => BackendDevInterface | undefined; | ||
getDevBackend: () => BackendDevMiddleware | undefined; | ||
getBackendRecord: BackendGetRecord; | ||
getBackendDevRecord: BackendGetRecord; | ||
setLanguageDetector: (detector: LanguageDetectorInterface | undefined) => void; | ||
getLanguageDetector: () => LanguageDetectorInterface | undefined; | ||
setLanguageStorage: (storage: LanguageStorageInterface | undefined) => void; | ||
getLanguageStorage: () => LanguageStorageInterface | undefined; | ||
getLanguageDetector: () => LanguageDetectorMiddleware | undefined; | ||
getInitialLanguage: () => string | Promise<string | undefined> | Promise<string | Promise<string | undefined> | undefined> | undefined; | ||
@@ -34,5 +25,6 @@ setStoredLanguage: (language: string) => void; | ||
highlight: HighlightInterface; | ||
unwrap: (text: string) => Unwrapped; | ||
wrap: (params: WrapperWrapProps) => string | undefined; | ||
unwrap: (text: string) => Unwrapped; | ||
hasDevBackend: () => boolean; | ||
}>; | ||
export declare type PluginServiceType = ReturnType<typeof PluginService>; | ||
export declare type PluginsInstance = ReturnType<typeof Plugins>; |
@@ -1,3 +0,7 @@ | ||
import { FallbackLanguageOption, FallbackNS, TreeTranslationsData } from '../../types'; | ||
export declare type Options = { | ||
import { FallbackGeneral, FallbackLanguageOption, TreeTranslationsData } from '../../types'; | ||
import { ObserverOptions, ObserverOptionsInternal } from './observerOptions'; | ||
export declare type TolgeeStaticData = { | ||
[key: string]: TreeTranslationsData | (() => Promise<TreeTranslationsData>); | ||
}; | ||
export declare type TolgeeOptionsInternal = { | ||
/** | ||
@@ -7,6 +11,15 @@ * Initial language | ||
language?: string; | ||
/** | ||
* Tolgee instance url (e.g. https://app.tolgee.io) | ||
*/ | ||
apiUrl?: string; | ||
/** | ||
* Project API key (PAK) or Personal Access Token (PAT) | ||
*/ | ||
apiKey?: string; | ||
projectId?: number; | ||
/** | ||
* Project id is necessary if you are using PAT | ||
*/ | ||
projectId?: number | string; | ||
/** | ||
* Used when auto detection is not available or is turned off | ||
@@ -31,3 +44,3 @@ */ | ||
*/ | ||
fallbackNs?: FallbackNS; | ||
fallbackNs?: FallbackGeneral; | ||
/** | ||
@@ -38,11 +51,21 @@ * Default namespace when no namespace defined (default: '') | ||
/** | ||
* Prefix used for fetching languages (default: 'i18n/') | ||
* These data go directly to cache or you can specify async | ||
* function which will be used to get the data. Use `:` to add namespace: | ||
* | ||
* ```ts | ||
* { | ||
* 'locale': <translations | async function> | ||
* 'locale:namespace': <translations | async function> | ||
* } | ||
* ``` | ||
*/ | ||
filesUrlPrefix: string; | ||
staticData?: { | ||
[key: string]: TreeTranslationsData | (() => Promise<TreeTranslationsData>); | ||
}; | ||
staticData?: TolgeeStaticData; | ||
observerOptions: ObserverOptionsInternal; | ||
observerType: 'invisible' | 'text'; | ||
}; | ||
export declare type TolgeeOptions = Partial<Omit<TolgeeOptionsInternal, 'observerOptions'>> & { | ||
observerOptions?: ObserverOptions; | ||
}; | ||
export declare type State = { | ||
initialOptions: Options; | ||
initialOptions: TolgeeOptionsInternal; | ||
activeNamespaces: Map<string, number>; | ||
@@ -54,2 +77,3 @@ language: string | undefined; | ||
}; | ||
export declare const initState: (options?: Partial<Options>, previousState?: State) => State; | ||
export declare const combineOptions: <T extends TolgeeOptions>(...states: (T | undefined)[]) => T; | ||
export declare const initState: (options?: Partial<TolgeeOptions>, previousState?: State) => State; |
@@ -1,6 +0,6 @@ | ||
import { CacheDescriptor, CacheDescriptorInternal, DevCredentials, EventEmitterType, FallbackNSTranslation } from '../../types'; | ||
import { ObserverOptions } from './initObserverOptions'; | ||
import { Options } from './initState'; | ||
export declare const State: (onLanguageChange: EventEmitterType<string>, onPendingLanguageChange: EventEmitterType<string>, onRunningChange: EventEmitterType<boolean>) => Readonly<{ | ||
init: (options?: Partial<Options>) => void; | ||
import { CacheDescriptor, CacheDescriptorInternal, DevCredentials, NsFallback, NsType } from '../../types'; | ||
import { EventEmitterInstance } from '../Events/EventEmitter'; | ||
import { TolgeeOptions } from './initState'; | ||
export declare const State: (onLanguageChange: EventEmitterInstance<string>, onPendingLanguageChange: EventEmitterInstance<string>, onRunningChange: EventEmitterInstance<boolean>) => Readonly<{ | ||
init: (options?: Partial<TolgeeOptions>) => void; | ||
isRunning: () => boolean; | ||
@@ -17,4 +17,4 @@ setRunning: (value: boolean) => void; | ||
apiKey?: string | undefined; | ||
projectId?: string | number | undefined; | ||
language?: string | undefined; | ||
projectId?: number | undefined; | ||
defaultLanguage?: string | undefined; | ||
@@ -26,17 +26,16 @@ availableLanguages?: string[] | undefined; | ||
defaultNs: string; | ||
filesUrlPrefix: string; | ||
staticData?: { | ||
[key: string]: import("../../types").TreeTranslationsData | (() => Promise<import("../../types").TreeTranslationsData>); | ||
} | undefined; | ||
staticData?: import("./initState").TolgeeStaticData | undefined; | ||
observerOptions: import("./observerOptions").ObserverOptionsInternal; | ||
observerType: "invisible" | "text"; | ||
}; | ||
addActiveNs: (ns: FallbackNSTranslation) => void; | ||
removeActiveNs: (ns: FallbackNSTranslation) => void; | ||
addActiveNs: (ns: NsFallback) => void; | ||
removeActiveNs: (ns: NsFallback) => void; | ||
getRequiredNamespaces: () => string[]; | ||
getFallbackLangs: (lang?: string) => string[]; | ||
getFallbackNamespaces: () => string[]; | ||
getFallbackNs: () => string[]; | ||
getDefaultNs: (ns?: NsType) => string; | ||
getAvailableLanguages: () => string[] | undefined; | ||
withDefaultNs: (descriptor: CacheDescriptor) => CacheDescriptorInternal; | ||
overrideCredentials: (credentials: DevCredentials) => void; | ||
setObserverOptions: (options: Partial<ObserverOptions>) => void; | ||
getObserverOptions: () => ObserverOptions; | ||
}>; | ||
export declare type StateInstance = ReturnType<typeof State>; |
@@ -1,5 +0,5 @@ | ||
export declare const ValueObserver: <T = any>(initialValue: T, valueGetter: () => T, handler: (value: T) => void) => Readonly<{ | ||
init: (value: T) => void; | ||
notify: () => void; | ||
}>; | ||
export declare type ValueObserverInstance<T> = ReturnType<typeof ValueObserver<T>>; | ||
export declare const ValueObserver: <T = any>(initialValue: T, valueGetter: () => T, handler: (value: T) => void) => ValueObserverInstance<T>; | ||
export declare type ValueObserverInstance<T> = { | ||
readonly init: (value: T) => void; | ||
readonly notify: () => void; | ||
}; |
@@ -0,3 +1,10 @@ | ||
import { FallbackGeneral, FallbackLanguageOption } from './types'; | ||
export declare function isPromise(value: any): boolean; | ||
export declare const valueOrPromise: <T, R>(value: T | Promise<T>, callback: (value: T) => R) => R | Promise<R>; | ||
export declare const missingOptionError: (option: string) => string; | ||
export declare function isObject(item: any): boolean; | ||
export declare function getFallback(value: FallbackGeneral): string[] | undefined; | ||
export declare function getFallbackArray(value: FallbackGeneral): string[]; | ||
export declare function getFallbackFromStruct(language: string, fallbackLanguage: FallbackLanguageOption): string[]; | ||
export declare function unique<T>(arr: T[]): T[]; | ||
export declare function sanitizeUrl(url: string | undefined): string | undefined; |
@@ -0,5 +1,5 @@ | ||
export { getFallback, getFallbackArray } from './helpers'; | ||
export { Tolgee } from './Tolgee'; | ||
export { RESTRICTED_ASCENDANT_ATTRIBUTE, DEVTOOLS_ID, TOLGEE_ATTRIBUTE_NAME, TOLGEE_HIGHLIGHTER_CLASS, TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE, } from './constants'; | ||
export * from './types'; | ||
export { getTranslateParams } from './TranslateParams'; | ||
export { getFallback, getFallbackArray } from './Controller/State/helpers'; | ||
export { getTranslateProps } from './TranslateParams'; | ||
export { FormatSimple } from './FormatSimple/FormatSimple'; |
@@ -1,2 +0,195 @@ | ||
import { Options, TolgeeInstance } from './types'; | ||
export declare const Tolgee: (options?: Partial<Options>) => TolgeeInstance; | ||
import { TolgeeOptions, TolgeePlugin, DevCredentials } from './types'; | ||
declare const createTolgee: (options: TolgeeOptions) => Readonly<{ | ||
/** | ||
* Listen to tolgee events. | ||
*/ | ||
on: import("./types").TolgeeOn<keyof import("./types").EventType>; | ||
/** | ||
* Listen for specific namespaces changes. | ||
* | ||
* ``` | ||
* const sub = tolgee.onUpdate(handler) | ||
* | ||
* // subscribe to selected namespace | ||
* sub.subscribeNs(['common']) | ||
* | ||
* // unsubscribe | ||
* sub.unsubscribe() | ||
* ``` | ||
*/ | ||
onNsUpdate: (handler: import("./types").Listener<undefined>) => import("./types").SubscriptionSelective; | ||
/** | ||
* @return current language if set. | ||
*/ | ||
getLanguage: () => string | undefined; | ||
/** | ||
* `pendingLanguage` represents language which is currently being loaded. | ||
* @return current `pendingLanguage` if set. | ||
*/ | ||
getPendingLanguage: () => string | undefined; | ||
/** | ||
* Change current language. | ||
* - if not running sets `pendingLanguage`, `language` to the new value | ||
* - if running sets `pendingLanguage` to the value, fetches necessary data and then changes `language` | ||
* | ||
* @return Promise which is resolved when `language` is changed. | ||
*/ | ||
changeLanguage: (language: string) => Promise<void>; | ||
/** | ||
* Temporarily change translation in cache. | ||
* @return object with revert method. | ||
*/ | ||
changeTranslation: (descriptor: import("./types").CacheDescriptor, key: string, value: string) => { | ||
revert: () => void; | ||
}; | ||
/** | ||
* Adds namespace(s) list of active namespaces. And if tolgee is running, loads required data. | ||
*/ | ||
addActiveNs: (ns: import("./types").NsFallback, forget?: boolean | undefined) => Promise<void>; | ||
/** | ||
* Remove namespace(s) from active namespaces. | ||
* | ||
* Tolgee internally counts how many times was each active namespace added, | ||
* so this method will remove namespace only if the counter goes down to 0. | ||
*/ | ||
removeActiveNs: (ns: import("./types").NsFallback) => void; | ||
/** | ||
* Manually load multiple records from `Backend` (or `DevBackend` when in dev mode) | ||
* | ||
* It loads data together and adds them to cache in one operation, to prevent partly loaded state. | ||
*/ | ||
loadRecords: (descriptors: import("./types").CacheDescriptor[]) => Promise<import("./types").TranslationsFlat[]>; | ||
/** | ||
* Manually load record from `Backend` (or `DevBackend` when in dev mode) | ||
*/ | ||
loadRecord: (descriptor: import("./types").CacheDescriptor) => Promise<import("./types").TranslationsFlat>; | ||
/** | ||
* | ||
*/ | ||
addStaticData: (data: import("./types").TolgeeStaticData | undefined) => void; | ||
/** | ||
* Get record from cache. | ||
*/ | ||
getRecord: (descriptor: import("./types").CacheDescriptor) => import("./types").TranslationsFlat | undefined; | ||
/** | ||
* Get all records from cache. | ||
*/ | ||
getAllRecords: () => { | ||
data: import("./types").TranslationsFlat; | ||
language: string; | ||
namespace: string; | ||
}[]; | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if there are data that need to be fetched. | ||
*/ | ||
isLoaded: (ns?: import("./types").NsFallback) => boolean; | ||
/** | ||
* @return `true` if tolgee is loading initial data (triggered by `run`). | ||
*/ | ||
isInitialLoading: () => boolean; | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if tolgee is loading some translations for the first time. | ||
*/ | ||
isLoading: (ns?: import("./types").NsFallback) => boolean; | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if tolgee is fetching some translations. | ||
*/ | ||
isFetching: (ns?: import("./types").NsFallback) => boolean; | ||
/** | ||
* @return `true` if tolgee is running. | ||
*/ | ||
isRunning: () => boolean; | ||
/** | ||
* Changes internal state to running: true and loads initial files. | ||
* Runs runnable plugins mainly Observer if present. | ||
*/ | ||
run: () => Promise<void | undefined>; | ||
/** | ||
* Changes internal state to running: false and stops runnable plugins. | ||
*/ | ||
stop: () => void; | ||
/** | ||
* Returns translated and formatted key. | ||
* If Observer is present and tolgee is running, wraps result to be identifiable in the DOM. | ||
*/ | ||
t: import("./types").TFnType<import("./types").DefaultParamType, string>; | ||
/** | ||
* Highlight keys that match selection. | ||
*/ | ||
highlight: import("./types").HighlightInterface; | ||
/** | ||
* @return current Tolgee options. | ||
*/ | ||
getInitialOptions: () => { | ||
apiUrl?: string | undefined; | ||
apiKey?: string | undefined; | ||
projectId?: string | number | undefined; | ||
language?: string | undefined; | ||
defaultLanguage?: string | undefined; | ||
availableLanguages?: string[] | undefined; | ||
fallbackLanguage?: import("./types").FallbackLanguageOption; | ||
ns?: string[] | undefined; | ||
fallbackNs?: import("./types").FallbackGeneral; | ||
defaultNs: string; | ||
staticData?: import("./types").TolgeeStaticData | undefined; | ||
observerOptions: import("./types").ObserverOptionsInternal; | ||
observerType: "invisible" | "text"; | ||
}; | ||
/** | ||
* Tolgee is in dev mode if `DevTools` plugin is used and `apiKey` + `apiUrl` are specified. | ||
* @return `true` if tolgee is in dev mode. | ||
*/ | ||
isDev: () => boolean; | ||
/** | ||
* Wraps translation if there is `Observer` plugin | ||
*/ | ||
wrap: (params: import("./types").WrapperWrapProps) => string | undefined; | ||
/** | ||
* Unwrap translation | ||
*/ | ||
unwrap: (text: string) => import("./types").Unwrapped; | ||
/** | ||
* Override creadentials passed on initialization | ||
*/ | ||
overrideCredentials(credentials: DevCredentials): void; | ||
/** | ||
* Add tolgee plugin. | ||
*/ | ||
addPlugin(plugin: TolgeePlugin | undefined): void; | ||
/** | ||
* Updates options after instance creation. Extends existing options, | ||
* so it only changes the fields, that are listed. | ||
* | ||
* When called in running state, tolgee stops and runs again. | ||
*/ | ||
updateOptions(options?: TolgeeOptions): void; | ||
}>; | ||
export declare type TolgeeInstance = ReturnType<typeof createTolgee>; | ||
export declare type TolgeeChainer = { | ||
/** | ||
* Add plugin, plugins are applied when `init` method is called. | ||
*/ | ||
use: (plugin: TolgeePlugin | undefined) => TolgeeChainer; | ||
/** | ||
* Update default options before tolgee is initialized. | ||
*/ | ||
updateDefaults: (options: TolgeeOptions) => TolgeeChainer; | ||
/** | ||
* Initialize tolgee options and apply plugins | ||
* @returns tolgee instance | ||
*/ | ||
init(options?: TolgeeOptions): TolgeeInstance; | ||
}; | ||
/** | ||
* Tolgee chainable constructor. | ||
* | ||
* Usage: | ||
* ``` | ||
* const tolgee = Tolgee().use(...).init(...) | ||
* ``` | ||
*/ | ||
export declare const Tolgee: () => TolgeeChainer; | ||
export {}; |
import { TFnType, TranslateProps } from './types'; | ||
export declare const getTranslateParams: TFnType<any, TranslateProps<any>>; | ||
export declare const getTranslateProps: TFnType<any, TranslateProps<any>>; |
{ | ||
"name": "@tolgee/core", | ||
"version": "4.10.0-rc.f068ae1.0", | ||
"version": "5.0.0-alpha.1", | ||
"description": "Library providing ability to translate messages directly in context of developed application.", | ||
"main": "./dist/tolgee.cjs.js", | ||
"module": "./dist/tolgee.esm.mjs", | ||
"module": "./dist/tolgee.esm.js", | ||
"types": "./lib/index.d.ts", | ||
@@ -24,3 +24,3 @@ "repository": { | ||
"require": "./dist/tolgee.cjs.js", | ||
"import": "./dist/tolgee.esm.mjs" | ||
"import": "./dist/tolgee.esm.js" | ||
}, | ||
@@ -53,2 +53,3 @@ "directories": { | ||
"concurrently": "7.3.0", | ||
"intl-messageformat": "^9.9.1", | ||
"jest": "^27.2.4", | ||
@@ -66,3 +67,3 @@ "jest-fetch-mock": "^3.0.3", | ||
}, | ||
"gitHead": "1fcb3dce87d60f206cfa264b8e87779f2350efbf" | ||
"gitHead": "9c66dbc9a72cc2874cf24abbe99af5eb6df45d50" | ||
} |
import { Tolgee } from '../Tolgee'; | ||
import { BackendInterface, TolgeePlugin } from '../types'; | ||
import { BackendMiddleware, TolgeePlugin } from '../types'; | ||
@@ -15,3 +15,3 @@ const data = { | ||
const backendNormal: BackendInterface = { | ||
const backendNormal: BackendMiddleware = { | ||
getRecord({ language, namespace = '' }) { | ||
@@ -22,3 +22,3 @@ return data[language]?.[namespace]; | ||
const backendDev: BackendInterface = { | ||
const backendDev: BackendMiddleware = { | ||
getRecord() { | ||
@@ -46,3 +46,3 @@ return Promise.resolve({ cancel: 'Dev' }); | ||
tolgee.stop(); | ||
tolgee.init({ apiUrl: 'asdfasdf', apiKey: 'test' }); | ||
tolgee.overrideCredentials({ apiUrl: 'asdfasdf', apiKey: 'test' }); | ||
await tolgee.run(); | ||
@@ -49,0 +49,0 @@ expect(tolgee.t({ key: 'cancel', ns: 'common' })).toEqual('Dev'); |
@@ -1,3 +0,3 @@ | ||
import { Tolgee } from '../Tolgee'; | ||
import { TolgeeInstance, TolgeePlugin, TreeTranslationsData } from '../types'; | ||
import { Tolgee, TolgeeInstance } from '../Tolgee'; | ||
import { TolgeePlugin, TreeTranslationsData } from '../types'; | ||
import { resolvablePromise } from './testTools'; | ||
@@ -16,3 +16,3 @@ | ||
(tolgee, tools) => { | ||
tolgee.init({ apiKey: 'test', apiUrl: 'test' }); | ||
tolgee.updateOptions({ apiKey: 'test', apiUrl: 'test' }); | ||
tools.setDevBackend({ | ||
@@ -28,2 +28,12 @@ getRecord({ language, namespace }) { | ||
const DevToolsThrow = (): TolgeePlugin => (tolgee, tools) => { | ||
tolgee.updateOptions({ apiKey: 'test', apiUrl: 'test' }); | ||
tools.setDevBackend({ | ||
getRecord() { | ||
return Promise.reject(); | ||
}, | ||
}); | ||
return tolgee; | ||
}; | ||
describe('cache', () => { | ||
@@ -33,3 +43,3 @@ let tolgee: TolgeeInstance; | ||
beforeEach(async () => { | ||
tolgee = Tolgee({ | ||
tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -64,3 +74,3 @@ staticData: { | ||
expect(tolgee.t('test.sub')).toEqual('subtestEn'); | ||
tolgee.use(DevToolsPlugin()); | ||
tolgee.addPlugin(DevToolsPlugin()); | ||
await tolgee.run(); | ||
@@ -77,3 +87,3 @@ expect(tolgee.t('test.sub')).toEqual('en.default'); | ||
expect(tolgee.t('test.sub')).toEqual('subtestEn'); | ||
tolgee.use(DevToolsPlugin()); | ||
tolgee.addPlugin(DevToolsPlugin()); | ||
expect(tolgee.t('test.sub')).toEqual('subtestEn'); | ||
@@ -91,6 +101,6 @@ | ||
it('works with switching to different dev backend', async () => { | ||
tolgee.use(DevToolsPlugin()); | ||
tolgee.addPlugin(DevToolsPlugin()); | ||
await tolgee.run(); | ||
expect(tolgee.t('test.sub')).toEqual('en.default'); | ||
tolgee.use(DevToolsPlugin('.new')); | ||
tolgee.addPlugin(DevToolsPlugin('.new')); | ||
expect(tolgee.t('test.sub')).toEqual('en.default'); | ||
@@ -102,2 +112,14 @@ | ||
it('keeps data when dev backend throws', async () => { | ||
const keyUpdateHandler = jest.fn(); | ||
tolgee.on('update', keyUpdateHandler); | ||
await tolgee.run(); | ||
expect(keyUpdateHandler).toBeCalledTimes(1); | ||
expect(tolgee.t('test.sub')).toEqual('subtestEn'); | ||
tolgee.addPlugin(DevToolsThrow()); | ||
await waitForInitialLoad(tolgee); | ||
expect(keyUpdateHandler).toBeCalledTimes(2); | ||
expect(tolgee.t('test.sub')).toEqual('subtestEn'); | ||
}); | ||
it('updates initial data correctly', async () => { | ||
@@ -111,3 +133,3 @@ await tolgee.run(); | ||
it('ignores new initial data when already in dev mode', async () => { | ||
tolgee.use(DevToolsPlugin()); | ||
tolgee.addPlugin(DevToolsPlugin()); | ||
await tolgee.run(); | ||
@@ -125,3 +147,3 @@ expect(tolgee.t('test.sub')).toEqual('en.default'); | ||
it('fetching works with namespaces', async () => { | ||
tolgee.use(DevToolsPlugin()); | ||
tolgee.addPlugin(DevToolsPlugin()); | ||
const runPromise = tolgee.run(); | ||
@@ -140,5 +162,22 @@ expect(tolgee.isFetching()).toBeTruthy(); | ||
it('works with namespaces containing colon', async () => { | ||
const [promiseEn, resolveEn] = resolvablePromise<TreeTranslationsData>(); | ||
tolgee.updateOptions({ | ||
language: 'en', | ||
staticData: { | ||
'en:common:test': () => promiseEn, | ||
}, | ||
}); | ||
await tolgee.run(); | ||
expect(tolgee.t('test.sub', { ns: 'common:test' })).toEqual('test.sub'); | ||
const nsPromise = tolgee.addActiveNs('common:test'); | ||
expect(tolgee.isLoading('common:test')).toBeTruthy(); | ||
resolveEn({ test: { sub: 'Test' } }); | ||
await nsPromise; | ||
expect(tolgee.t('test.sub', { ns: 'common:test' })).toEqual('Test'); | ||
}); | ||
it("pending requests won't rewrite cache when reinitialized", async () => { | ||
const [promiseEn, resolveEn] = resolvablePromise<TreeTranslationsData>(); | ||
tolgee = Tolgee({ | ||
tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -151,3 +190,3 @@ staticData: { | ||
await Promise.resolve(); | ||
tolgee.use(DevToolsPlugin()); | ||
tolgee.addPlugin(DevToolsPlugin()); | ||
await waitForInitialLoad(tolgee); | ||
@@ -154,0 +193,0 @@ expect(tolgee.t('test.sub')).toEqual('en.default'); |
@@ -14,3 +14,3 @@ import { Tolgee } from '../index'; | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -17,0 +17,0 @@ ns: ['common'], |
@@ -5,3 +5,3 @@ import { Tolgee } from '../index'; | ||
it('emits language change event', async () => { | ||
const tolgee = Tolgee({ language: 'en' }); | ||
const tolgee = Tolgee().init({ language: 'en' }); | ||
const handler = jest.fn((lang) => {}); | ||
@@ -14,3 +14,3 @@ tolgee.on('language', handler); | ||
it('correctly emits translation change listeners', async () => { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -25,4 +25,4 @@ staticData: { | ||
tolgee.onKeyUpdate(helloHandler).subscribeKey({ key: 'hello' }); | ||
tolgee.onKeyUpdate(languageHandler).subscribeKey({ key: 'language' }); | ||
tolgee.onNsUpdate(helloHandler); | ||
tolgee.onNsUpdate(languageHandler); | ||
@@ -29,0 +29,0 @@ tolgee.changeTranslation({ language: 'es' }, 'hello', 'Světe'); |
@@ -14,3 +14,3 @@ import { Tolgee } from '../index'; | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -52,3 +52,3 @@ ns: ['common'], | ||
it("won't start loading when nothing to load", async () => { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -77,3 +77,3 @@ staticData: { | ||
it('emits keyUpdate on initialLoad', async () => { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -83,3 +83,3 @@ staticData: { en: { hello: 'world' } }, | ||
const onKeyChange = jest.fn(); | ||
tolgee.on('keyUpdate', onKeyChange); | ||
tolgee.on('update', onKeyChange); | ||
expect(onKeyChange).toBeCalledTimes(0); | ||
@@ -86,0 +86,0 @@ await tolgee.run(); |
@@ -6,3 +6,3 @@ import { Tolgee, TreeTranslationsData } from '../index'; | ||
it('changes language', async () => { | ||
const tolgee = Tolgee({ language: 'en' }); | ||
const tolgee = Tolgee().init({ language: 'en' }); | ||
expect(tolgee.getLanguage()).toEqual('en'); | ||
@@ -14,3 +14,3 @@ await tolgee.changeLanguage('es'); | ||
it('returns correct translation', async () => { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -29,3 +29,3 @@ staticData: { en: { hello: 'World' }, es: { hello: 'Mundo' } }, | ||
const [promiseEs, resolveEs] = resolvablePromise<TreeTranslationsData>(); | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -59,3 +59,3 @@ staticData: { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'es', | ||
@@ -102,3 +102,3 @@ staticData: { | ||
it('will fallback to default value', () => { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
defaultLanguage: 'en', | ||
@@ -113,6 +113,24 @@ }); | ||
it('will throw error when no language specified', () => { | ||
const tolgee = Tolgee({}); | ||
const tolgee = Tolgee().init({}); | ||
expect(() => tolgee.run()).toThrow(/'language'/); | ||
}); | ||
it('loads fallback languages and namespaces', async () => { | ||
const loadNs = jest.fn(() => Promise.resolve(undefined as any)); | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
fallbackNs: ['fallback'], | ||
staticData: { | ||
en: loadNs, | ||
'en:fallback': loadNs, | ||
cs: loadNs, | ||
'cs:fallback': loadNs, | ||
}, | ||
}); | ||
tolgee.run(); | ||
expect(loadNs).toBeCalledTimes(2); | ||
await tolgee.changeLanguage('cs'); | ||
expect(loadNs).toBeCalledTimes(4); | ||
}); | ||
}); |
@@ -10,3 +10,3 @@ import { Tolgee, TreeTranslationsData } from '../index'; | ||
const onFetchingHandler = jest.fn(() => {}); | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -13,0 +13,0 @@ staticData: { |
@@ -11,5 +11,6 @@ import { Tolgee } from '../index'; | ||
return Tolgee({ | ||
return Tolgee().init({ | ||
language: 'en', | ||
ns: ['common'], | ||
defaultNs: 'common', | ||
staticData: { | ||
@@ -26,3 +27,3 @@ 'en:common': promiseEnCommon, | ||
it('returns correct translation from namespace', () => { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -40,3 +41,3 @@ staticData: { | ||
it('uses defaultNs', async () => { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
@@ -82,5 +83,5 @@ defaultNs: 'common', | ||
const tolgee = tolgeeWithNamespaces(); | ||
await tolgee.run(); | ||
const handler = jest.fn(); | ||
tolgee.on('cache', handler); | ||
await tolgee.run(); | ||
@@ -91,3 +92,3 @@ expect(tolgee.t({ key: 'cancel', ns: 'common' })).toEqual('Cancel'); | ||
await tolgee.addActiveNs('test'); | ||
expect(handler).toBeCalledTimes(1); | ||
expect(handler).toBeCalledTimes(2); | ||
@@ -99,3 +100,3 @@ expect(tolgee.t({ key: 'cancel', ns: 'common' })).toEqual('Cancel'); | ||
await tolgee.changeLanguage('es'); | ||
expect(handler).toBeCalledTimes(2); | ||
expect(handler).toBeCalledTimes(3); | ||
@@ -105,2 +106,24 @@ expect(tolgee.t({ key: 'cancel', ns: 'common' })).toEqual('Cancellar'); | ||
}); | ||
it('add active namespace with forget', async () => { | ||
const tolgee = tolgeeWithNamespaces(); | ||
const handler = jest.fn(); | ||
tolgee.on('cache', handler); | ||
await tolgee.run(); | ||
// test ns is not loaded | ||
expect(tolgee.t({ key: 'test', ns: 'test' })).toEqual('test'); | ||
await tolgee.addActiveNs('test', true); | ||
expect(handler).toBeCalledTimes(2); | ||
// test ns is loaded | ||
expect(tolgee.t({ key: 'test', ns: 'test' })).toEqual('Test'); | ||
await tolgee.changeLanguage('es'); | ||
expect(handler).toBeCalledTimes(3); | ||
// test ns is not loaded for spanish | ||
expect(tolgee.t({ key: 'test', ns: 'test' })).toEqual('test'); | ||
}); | ||
}); |
import { Tolgee } from '../Tolgee'; | ||
import { | ||
FinalFormatterInterface, | ||
FormatterInterface, | ||
FormatterInterfaceFormatParams, | ||
ObserverInterface, | ||
FinalFormatterMiddleware, | ||
FormatterMiddleware, | ||
FormatterMiddlewareFormatParams, | ||
ObserverMiddleware, | ||
TolgeePlugin, | ||
WrapperWrapFunction, | ||
ObserverOptions, | ||
} from '../types'; | ||
const testObserver = | ||
( | ||
outputNotFormattable: boolean, | ||
onCreate?: (options: ObserverOptions) => void | ||
): ObserverInterface => | ||
({ options }) => { | ||
(outputNotFormattable: boolean): ObserverMiddleware => | ||
() => { | ||
const wrap: WrapperWrapFunction = ({ key, translation }) => { | ||
@@ -31,4 +27,2 @@ return `${key}|${translation}`; | ||
onCreate?.(options); | ||
return Object.freeze({ | ||
@@ -45,4 +39,4 @@ wrap, | ||
const testFormatter1: FormatterInterface = { | ||
format: ({ translation }: FormatterInterfaceFormatParams) => { | ||
const testFormatter1: FormatterMiddleware = { | ||
format: ({ translation }: FormatterMiddlewareFormatParams) => { | ||
return `(1${translation})`; | ||
@@ -52,4 +46,4 @@ }, | ||
const testFormatter2: FormatterInterface = { | ||
format: ({ translation }: FormatterInterfaceFormatParams) => { | ||
const testFormatter2: FormatterMiddleware = { | ||
format: ({ translation }: FormatterMiddlewareFormatParams) => { | ||
return `(2${translation})`; | ||
@@ -59,4 +53,4 @@ }, | ||
const testFinalFormatter: FinalFormatterInterface = { | ||
format: ({ translation }: FormatterInterfaceFormatParams) => { | ||
const testFinalFormatter: FinalFormatterMiddleware = { | ||
format: ({ translation }: FormatterMiddlewareFormatParams) => { | ||
return { final: translation }; | ||
@@ -67,8 +61,5 @@ }, | ||
const observerPlugin = | ||
( | ||
outputNotFormattable: boolean, | ||
onCreate?: (options: ObserverOptions) => void | ||
): TolgeePlugin => | ||
(outputNotFormattable: boolean): TolgeePlugin => | ||
(tolgee, tools) => { | ||
tools.setObserver(testObserver(outputNotFormattable, onCreate)); | ||
tools.setObserver(testObserver(outputNotFormattable)); | ||
return tolgee; | ||
@@ -86,11 +77,11 @@ }; | ||
it('wraps and formats translation', () => { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
staticData: { en: { hello: 'world' } }, | ||
}); | ||
tolgee.use(observerPlugin(false)); | ||
tolgee.addPlugin(observerPlugin(false)); | ||
tolgee.run(); | ||
expect(tolgee.t({ key: 'hello' })).toEqual('hello|world'); | ||
tolgee.use(formattersPlugin); | ||
tolgee.addPlugin(formattersPlugin); | ||
expect(tolgee.t({ key: 'hello' })).toEqual({ final: '(2(1hello|world))' }); | ||
@@ -101,11 +92,11 @@ tolgee.stop(); | ||
it("won't format when observer doesn't return formattable text", () => { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
staticData: { en: { hello: 'world' } }, | ||
}); | ||
tolgee.use(observerPlugin(true)); | ||
tolgee.addPlugin(observerPlugin(true)); | ||
tolgee.run(); | ||
expect(tolgee.t({ key: 'hello' })).toEqual('hello|world'); | ||
tolgee.use(formattersPlugin); | ||
tolgee.addPlugin(formattersPlugin); | ||
expect(tolgee.t({ key: 'hello' })).toEqual('hello|world'); | ||
@@ -116,7 +107,7 @@ tolgee.stop(); | ||
it("won't wrap before run", () => { | ||
const tolgee = Tolgee({ | ||
const tolgee = Tolgee().init({ | ||
language: 'en', | ||
staticData: { en: { hello: 'world' } }, | ||
}); | ||
tolgee.use(observerPlugin(false)); | ||
tolgee.addPlugin(observerPlugin(false)); | ||
expect(tolgee.t({ key: 'hello' })).toEqual('world'); | ||
@@ -126,20 +117,2 @@ tolgee.run(); | ||
}); | ||
it('observer recieves options', () => { | ||
const tolgee = Tolgee({ | ||
language: 'en', | ||
staticData: { en: { hello: 'world' } }, | ||
}); | ||
const restrictedElements = ['test']; | ||
tolgee.setObserverOptions({ | ||
restrictedElements, | ||
}); | ||
const onCreate = jest.fn(); | ||
tolgee.use(observerPlugin(false, onCreate)); | ||
tolgee.run(); | ||
expect(onCreate).toBeCalledTimes(1); | ||
expect(onCreate.mock.calls[0][0].restrictedElements).toEqual( | ||
restrictedElements | ||
); | ||
}); | ||
}); |
import { | ||
CacheAsyncRequests, | ||
CacheDescriptor, | ||
CacheDescriptorInternal, | ||
CacheDescriptorWithKey, | ||
EventEmitterType, | ||
FallbackNSTranslation, | ||
Options, | ||
NsFallback, | ||
TranslationsFlat, | ||
@@ -15,3 +12,5 @@ TranslationValue, | ||
} from '../../types'; | ||
import { getFallbackArray } from '../State/helpers'; | ||
import { getFallbackArray, unique } from '../../helpers'; | ||
import { EventEmitterInstance } from '../Events/EventEmitter'; | ||
import { TolgeeStaticData } from '../State/initState'; | ||
import { ValueObserverInstance } from '../ValueObserver'; | ||
@@ -21,2 +20,7 @@ | ||
type CacheAsyncRequests = Map< | ||
string, | ||
Promise<TreeTranslationsData | undefined> | undefined | ||
>; | ||
type CacheRecord = { | ||
@@ -30,3 +34,3 @@ version: number; | ||
export const Cache = ( | ||
onCacheChange: EventEmitterType<CacheDescriptorWithKey>, | ||
onCacheChange: EventEmitterInstance<CacheDescriptorWithKey>, | ||
backendGetRecord: BackendGetRecord, | ||
@@ -41,6 +45,6 @@ backendGetDevRecord: BackendGetDevRecord, | ||
const cache: StateCache = new Map(); | ||
let staticData: NonNullable<Options['staticData']> = {}; | ||
let staticData: NonNullable<TolgeeStaticData> = {}; | ||
let version = 0; | ||
function addStaticData(data: Options['staticData']) { | ||
function addStaticData(data: TolgeeStaticData | undefined) { | ||
if (data) { | ||
@@ -112,7 +116,7 @@ staticData = { ...staticData, ...data }; | ||
if (value !== undefined && value !== null) { | ||
return namespace; | ||
return [namespace]; | ||
} | ||
} | ||
} | ||
return Array.from(new Set(namespaces)); | ||
return unique(namespaces); | ||
} | ||
@@ -148,3 +152,3 @@ | ||
function isFetching(ns?: FallbackNSTranslation) { | ||
function isFetching(ns?: NsFallback) { | ||
if (isInitialLoading()) { | ||
@@ -165,3 +169,3 @@ return true; | ||
function isLoading(language: string | undefined, ns?: FallbackNSTranslation) { | ||
function isLoading(language: string | undefined, ns?: NsFallback) { | ||
const namespaces = getFallbackArray(ns); | ||
@@ -184,3 +188,6 @@ | ||
function fetchNormal(keyObject: CacheDescriptorInternal) { | ||
/** | ||
* Fetches production data | ||
*/ | ||
function fetchProd(keyObject: CacheDescriptorInternal) { | ||
let dataPromise = undefined as | ||
@@ -200,6 +207,2 @@ | Promise<TreeTranslationsData | undefined> | ||
if (!dataPromise) { | ||
// return empty data, so we know it has already been attempted to fetch | ||
dataPromise = Promise.resolve({}); | ||
} | ||
return dataPromise; | ||
@@ -216,4 +219,4 @@ } | ||
console.warn(`Tolgee: Failed to fetch data from dev backend`); | ||
// fallback to normal fetch if dev fails | ||
return fetchNormal(keyObject); | ||
// fallback to prod fetch if dev fails | ||
return fetchProd(keyObject); | ||
}); | ||
@@ -223,3 +226,3 @@ } | ||
if (!dataPromise) { | ||
dataPromise = fetchNormal(keyObject); | ||
dataPromise = fetchProd(keyObject); | ||
} | ||
@@ -244,3 +247,4 @@ | ||
} | ||
const dataPromise = fetchData(keyObject, isDev); | ||
const dataPromise = | ||
fetchData(keyObject, isDev) || Promise.resolve(undefined); | ||
asyncRequests.set(cacheKey, dataPromise); | ||
@@ -269,2 +273,5 @@ return { | ||
addRecord(value.keyObject, data); | ||
} else if (!getRecord(value.keyObject)) { | ||
// if no data exist, put empty object | ||
addRecord(value.keyObject, {}); | ||
} | ||
@@ -306,2 +313,2 @@ } | ||
export type CacheType = ReturnType<typeof Cache>; | ||
export type CacheInstance = ReturnType<typeof Cache>; |
@@ -24,3 +24,5 @@ import { CacheDescriptorInternal, TreeTranslationsData } from '../../types'; | ||
export const decodeCacheKey = (key: string): CacheDescriptorInternal => { | ||
const [firstPart, secondPart] = key.split(':'); | ||
const [firstPart, ...rest] = key.split(':'); | ||
// if namespaces contains ":" it won't get lost | ||
const secondPart = rest.join(':'); | ||
return { language: firstPart, namespace: secondPart || '' }; | ||
@@ -27,0 +29,0 @@ }; |
import { Events } from './Events/Events'; | ||
import { | ||
CacheDescriptor, | ||
FallbackNSTranslation, | ||
Options, | ||
NsFallback, | ||
TolgeeOptions, | ||
TFnType, | ||
TranslatePropsInternal, | ||
NsType, | ||
KeyAndNamespacesInternal, | ||
} from '../types'; | ||
import { Cache } from './Cache/Cache'; | ||
import { getFallbackArray } from './State/helpers'; | ||
import { PluginService } from './Plugins/Plugins'; | ||
import { getFallbackArray } from '../helpers'; | ||
import { Plugins } from './Plugins/Plugins'; | ||
import { ValueObserver } from './ValueObserver'; | ||
import { State } from './State/State'; | ||
import { isPromise, missingOptionError, valueOrPromise } from '../helpers'; | ||
import { getTranslateParams } from '../TranslateParams'; | ||
import { getTranslateProps } from '../TranslateParams'; | ||
type StateServiceProps = { | ||
options?: Partial<Options>; | ||
options?: Partial<TolgeeOptions>; | ||
}; | ||
export const Controller = ({ options }: StateServiceProps) => { | ||
const events = Events(getFallbackNamespaces); | ||
const events = Events(getFallbackNs, getDefaultNs); | ||
const fetchingObserver = ValueObserver<boolean>( | ||
@@ -40,6 +41,5 @@ false, | ||
const pluginService = PluginService( | ||
const pluginService = Plugins( | ||
state.getLanguage, | ||
state.getInitialOptions, | ||
state.getObserverOptions, | ||
state.getAvailableLanguages, | ||
@@ -65,3 +65,3 @@ getTranslationNs, | ||
events.onKeyUpdate.listen(() => { | ||
events.onUpdate.listen(() => { | ||
if (state.isRunning()) { | ||
@@ -72,2 +72,25 @@ pluginService.retranslate(); | ||
function getFallbackNs() { | ||
return state.getFallbackNs(); | ||
} | ||
function getDefaultNs(ns?: NsType) { | ||
return state.getDefaultNs(ns); | ||
} | ||
// gets all namespaces where translation could be located | ||
// takes (ns|default, fallback ns) | ||
function getDefaultAndFallbackNs(ns?: NsType) { | ||
return [...getFallbackArray(getDefaultNs(ns)), ...getFallbackNs()]; | ||
} | ||
// gets all namespaces which need to be loaded | ||
// takes (ns|default, initial ns, fallback ns, active ns) | ||
function getRequiredNamespaces(ns: NsFallback) { | ||
return [ | ||
...getFallbackArray(ns || getDefaultNs()), | ||
...state.getRequiredNamespaces(), | ||
]; | ||
} | ||
function changeTranslation( | ||
@@ -88,7 +111,3 @@ descriptor: CacheDescriptor, | ||
function getFallbackNamespaces() { | ||
return state.getFallbackNamespaces(); | ||
} | ||
function init(options: Partial<Options>) { | ||
function init(options: Partial<TolgeeOptions>) { | ||
state.init(options); | ||
@@ -98,3 +117,3 @@ cache.addStaticData(state.getInitialOptions().staticData); | ||
function isLoading(ns?: FallbackNSTranslation) { | ||
function isLoading(ns?: NsFallback) { | ||
return cache.isLoading(state.getLanguage()!, ns); | ||
@@ -105,9 +124,7 @@ } | ||
return Boolean( | ||
state.getInitialOptions().apiKey && | ||
state.getInitialOptions().apiUrl && | ||
pluginService.getDevBackend() | ||
state.getInitialOptions().apiKey && state.getInitialOptions().apiUrl | ||
); | ||
} | ||
async function addActiveNs(ns: FallbackNSTranslation, forget?: boolean) { | ||
async function addActiveNs(ns: NsFallback, forget?: boolean) { | ||
if (!forget) { | ||
@@ -121,6 +138,5 @@ state.addActiveNs(ns); | ||
function getRequiredRecords(lang?: string, ns?: FallbackNSTranslation) { | ||
function getRequiredRecords(lang?: string, ns?: NsFallback) { | ||
const languages = state.getFallbackLangs(lang); | ||
const namespaces = | ||
ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces(); | ||
const namespaces = getRequiredNamespaces(ns); | ||
const result: CacheDescriptor[] = []; | ||
@@ -137,3 +153,3 @@ languages.forEach((language) => { | ||
function isLoaded(ns?: FallbackNSTranslation) { | ||
function isLoaded(ns?: NsFallback) { | ||
const language = state.getLanguage(); | ||
@@ -144,4 +160,3 @@ if (!language) { | ||
const languages = state.getFallbackLangs(language); | ||
const namespaces = | ||
ns !== undefined ? getFallbackArray(ns) : state.getRequiredNamespaces(); | ||
const namespaces = getRequiredNamespaces(ns); | ||
const result: CacheDescriptor[] = []; | ||
@@ -158,3 +173,3 @@ languages.forEach((language) => { | ||
function loadRequiredRecords(lang?: string, ns?: FallbackNSTranslation) { | ||
function loadRequiredRecords(lang?: string, ns?: NsFallback) { | ||
const descriptors = getRequiredRecords(lang, ns); | ||
@@ -187,18 +202,10 @@ if (descriptors.length) { | ||
function getTranslationNs({ | ||
key, | ||
ns, | ||
}: Pick<TranslatePropsInternal, 'key' | 'ns'>) { | ||
const namespaces = | ||
ns !== undefined ? getFallbackArray(ns) : state.getFallbackNamespaces(); | ||
function getTranslationNs({ key, ns }: KeyAndNamespacesInternal) { | ||
const languages = state.getFallbackLangs(); | ||
const namespaces = getDefaultAndFallbackNs(ns); | ||
return cache.getTranslationNs(namespaces, languages, key); | ||
} | ||
function getTranslation({ | ||
key, | ||
ns, | ||
}: Pick<TranslatePropsInternal, 'key' | 'ns'>) { | ||
const namespaces = | ||
ns !== undefined ? getFallbackArray(ns) : state.getFallbackNamespaces(); | ||
function getTranslation({ key, ns }: KeyAndNamespacesInternal) { | ||
const namespaces = getDefaultAndFallbackNs(ns); | ||
const languages = state.getFallbackLangs(); | ||
@@ -294,3 +301,3 @@ return cache.getTranslationFallback(namespaces, languages, key); | ||
// @ts-ignore | ||
const params = getTranslateParams(...args); | ||
const params = getTranslateProps(...args); | ||
const translation = getTranslation(params); | ||
@@ -310,3 +317,2 @@ return pluginService.formatTranslation({ ...params, translation }); | ||
addActiveNs, | ||
loadRequiredRecords, | ||
loadRecords, | ||
@@ -323,2 +329,2 @@ loadRecord, | ||
export type StateServiceType = ReturnType<typeof Controller>; | ||
export type ControllerInstance = ReturnType<typeof Controller>; |
@@ -1,8 +0,8 @@ | ||
import { Listener, ListenerHandler } from '../../types'; | ||
import { Subscription, Listener } from '../../types'; | ||
export const EventEmitter = <T>() => { | ||
let handlers: ListenerHandler<T>[] = []; | ||
export const EventEmitter = <T>(): EventEmitterInstance<T> => { | ||
let handlers: Listener<T>[] = []; | ||
const listen = (handler: ListenerHandler<T>): Listener => { | ||
const handlerWrapper: ListenerHandler<T> = (e) => { | ||
const listen = (handler: Listener<T>): Subscription => { | ||
const handlerWrapper: Listener<T> = (e) => { | ||
handler(e); | ||
@@ -27,2 +27,5 @@ }; | ||
export type EventEmitterType<T> = ReturnType<typeof EventEmitter<T>>; | ||
export type EventEmitterInstance<T> = { | ||
readonly listen: (handler: Listener<T>) => Subscription; | ||
readonly emit: (data: T) => void; | ||
}; |
import { EventEmitterSelective } from './EventEmitterSelective'; | ||
describe('event emitter selective', () => { | ||
it('handles correctly fallback namespaces', () => { | ||
const emitter = EventEmitterSelective<void>(() => ['a', 'b']); | ||
it('handles correctly default namespace', () => { | ||
const emitter = EventEmitterSelective( | ||
() => [], | ||
() => 'default' | ||
); | ||
const handler = jest.fn(); | ||
const listener = emitter.listenSome(handler); | ||
// subscribe to fallback namespaces | ||
listener.subscribeKey({ key: 'test' }); | ||
// subscribe to default ns | ||
listener.subscribeNs(); | ||
// emmit | ||
emitter.emit({ key: 'test' }); | ||
emitter.emit({ ns: ['a'] }); | ||
emitter.emit(['default']); | ||
// should be ignored | ||
emitter.emit({ ns: ['c'] }); | ||
emitter.emit(['c']); | ||
expect(handler).toBeCalledTimes(2); | ||
}); | ||
it('subscribes to key', () => { | ||
const emitter = EventEmitterSelective<void>(() => ['']); | ||
const handler = jest.fn(); | ||
const listener = emitter.listenSome(handler); | ||
listener.subscribeKey({ key: 'test' }); | ||
listener.subscribeKey({ key: 'abcd' }); | ||
emitter.emit({ key: 'test' }); | ||
emitter.emit({ key: 'youda' }); | ||
expect(handler).toBeCalledTimes(1); | ||
listener.unsubscribe(); | ||
emitter.emit(); | ||
expect(handler).toBeCalledTimes(1); | ||
}); | ||
it('subscribes to key with namespaces', () => { | ||
const emitter = EventEmitterSelective<void>(() => []); | ||
it('unsubscribes', () => { | ||
const emitter = EventEmitterSelective( | ||
() => [], | ||
() => '' | ||
); | ||
const handler = jest.fn(); | ||
const listener = emitter.listenSome(handler); | ||
const listener = emitter.listen(handler); | ||
listener.subscribeKey({ key: 'test', ns: 'common' }); | ||
listener.subscribeKey({ key: 'abcd', ns: ['test', 'abcd'] }); | ||
emitter.emit({ key: 'youda', ns: ['common'] }); | ||
emitter.emit({ key: 'test', ns: ['youda'] }); | ||
expect(handler).toBeCalledTimes(0); | ||
emitter.emit({ key: 'abcd', ns: ['abcd'] }); | ||
emitter.emit({ ns: ['test'] }); | ||
expect(handler).toBeCalledTimes(2); | ||
listener.unsubscribe(); | ||
emitter.emit(); | ||
expect(handler).toBeCalledTimes(2); | ||
}); | ||
it('unsubscribes', () => { | ||
const emitter = EventEmitterSelective<void>(() => []); | ||
const handler = jest.fn(); | ||
const listener = emitter.listenSome(handler); | ||
listener.subscribeKey({ key: 'test', ns: 'common' }); | ||
listener.subscribeKey({ key: 'abcd', ns: ['test', 'abcd'] }); | ||
emitter.emit({ key: 'youda', ns: ['common'] }); | ||
emitter.emit({ key: 'test', ns: ['youda'] }); | ||
expect(handler).toBeCalledTimes(0); | ||
emitter.emit({ key: 'abcd', ns: ['abcd'] }); | ||
emitter.emit({ ns: ['test'] }); | ||
expect(handler).toBeCalledTimes(2); | ||
listener.unsubscribeKey({ key: 'abcd', ns: ['test', 'abcd'] }); | ||
emitter.emit({ key: 'abcd' }); | ||
emitter.emit({ ns: ['test'] }); | ||
listener.unsubscribe(); | ||
emitter.emit(); | ||
expect(handler).toBeCalledTimes(2); | ||
expect(handler).toBeCalledTimes(1); | ||
}); | ||
it('groups events correctly', async () => { | ||
const emitter = EventEmitterSelective<void>(() => ['test', 'opqrst']); | ||
const emitter = EventEmitterSelective( | ||
() => ['test', 'opqrst'], | ||
() => '' | ||
); | ||
const handler = jest.fn(); | ||
@@ -86,8 +48,8 @@ const hanlderAll = jest.fn(); | ||
listener.subscribeKey({ key: 'abcd', ns: ['test'] }); | ||
listener.subscribeNs('test'); | ||
emitter.emit({ key: 'abcd' }, true); | ||
emitter.emit({ ns: ['opqrst'] }, true); | ||
// is fallback should always call handler | ||
emitter.emit(['opqrst'], true); | ||
await Promise.resolve(); | ||
await new Promise((resolve) => setTimeout(resolve)); | ||
@@ -97,4 +59,5 @@ expect(hanlderAll).toBeCalledTimes(1); | ||
emitter.emit({ key: 'youda', ns: ['test'] }, true); | ||
emitter.emit({ key: 'filda', ns: ['test'] }); | ||
// these should be merged together | ||
emitter.emit(['abcd'], true); | ||
emitter.emit(['abcd']); | ||
@@ -109,20 +72,19 @@ expect(hanlderAll).toBeCalledTimes(2); | ||
it('subscribes to ns only', async () => { | ||
const emitter = EventEmitterSelective<void>(() => ['test', 'youda']); | ||
it('always subscribes to fallback ns', async () => { | ||
const emitter = EventEmitterSelective( | ||
() => ['fallback1', 'fallback2'], | ||
() => '' | ||
); | ||
const handler = jest.fn(); | ||
const listener = emitter.listenSome(handler); | ||
listener.subscribeNs(['test']); | ||
emitter.listenSome(handler); | ||
emitter.emit({ key: 'youda' }); | ||
emitter.emit(['fallback1']); | ||
expect(handler).toBeCalledTimes(1); | ||
emitter.emit({ ns: ['test'] }); | ||
emitter.emit(['fallback2']); | ||
expect(handler).toBeCalledTimes(2); | ||
emitter.emit({ ns: ['youda'] }); | ||
emitter.emit(['test']); | ||
expect(handler).toBeCalledTimes(2); | ||
emitter.emit({ key: 'youda', ns: ['youda'] }); | ||
expect(handler).toBeCalledTimes(2); | ||
}); | ||
}); |
@@ -1,38 +0,26 @@ | ||
import { getFallbackArray } from '../State/helpers'; | ||
import { getFallbackArray } from '../../helpers'; | ||
import { | ||
FallbackNSTranslation, | ||
KeyDescriptor, | ||
KeyDescriptorInternal, | ||
ListenerHandler, | ||
ListenerHandlerEvent, | ||
NsFallback, | ||
Subscription, | ||
Listener, | ||
ListenerEvent, | ||
SubscriptionSelective, | ||
NsType, | ||
} from '../../types'; | ||
type NsListType = string; | ||
type HandlerWrapperType = { | ||
fn: ListenerHandler<undefined>; | ||
keys: Map<string, number>; | ||
namespaces: Map<string | undefined, number>; | ||
fn: Listener<undefined>; | ||
namespaces: Set<string>; | ||
}; | ||
function incrementInMap(map: Map<any, number>, value: any) { | ||
const currNum = map.get(value) || 0; | ||
map.set(value, currNum + 1); | ||
} | ||
function decrementInMap(map: Map<any, number>, value: any) { | ||
let currNum = map.get(value) || 1; | ||
currNum -= 1; | ||
if (currNum <= 0) { | ||
map.delete(value); | ||
} else { | ||
map.set(value, currNum); | ||
} | ||
} | ||
export const EventEmitterSelective = <T>( | ||
getFallbackNamespaces: () => string[] | ||
) => { | ||
const listeners: Set<ListenerHandler<T>> = new Set(); | ||
export const EventEmitterSelective = ( | ||
getFallbackNs: () => string[], | ||
getDefaultNs: () => string | ||
): EventEmitterSelectiveInstance => { | ||
const listeners: Set<Listener<undefined>> = new Set(); | ||
const partialListeners: Set<HandlerWrapperType> = new Set(); | ||
const listen = (handler: ListenerHandler<T>) => { | ||
const listen = (handler: Listener<undefined>) => { | ||
listeners.add(handler); | ||
@@ -47,9 +35,8 @@ const result = { | ||
const listenSome = (handler: ListenerHandler<undefined>) => { | ||
const listenSome = (handler: Listener<undefined>) => { | ||
const handlerWrapper = { | ||
fn: (e: ListenerHandlerEvent<undefined>) => { | ||
fn: (e: ListenerEvent<undefined>) => { | ||
handler(e); | ||
}, | ||
keys: new Map<string, number>(), | ||
namespaces: new Map<string | undefined, number>(), | ||
namespaces: new Set<NsListType>(), | ||
}; | ||
@@ -63,38 +50,12 @@ | ||
}, | ||
subscribeNs: (ns: FallbackNSTranslation) => { | ||
subscribeNs: (ns: NsFallback) => { | ||
getFallbackArray(ns).forEach((val) => | ||
incrementInMap(handlerWrapper.namespaces, val) | ||
handlerWrapper.namespaces.add(val) | ||
); | ||
return result; | ||
}, | ||
unsubscribeNs: (ns: FallbackNSTranslation) => { | ||
getFallbackArray(ns).forEach((val) => | ||
decrementInMap(handlerWrapper.namespaces, val) | ||
); | ||
return result; | ||
}, | ||
subscribeKey: (descriptor: KeyDescriptor) => { | ||
const { key, ns } = descriptor; | ||
incrementInMap(handlerWrapper.keys, key); | ||
getFallbackArray(ns).forEach((val) => | ||
incrementInMap(handlerWrapper.namespaces, val) | ||
); | ||
if (ns === undefined) { | ||
// subscribing to all namespaces | ||
incrementInMap(handlerWrapper.namespaces, undefined); | ||
// subscribing to default ns | ||
handlerWrapper.namespaces.add(getDefaultNs()); | ||
} | ||
return result; | ||
}, | ||
unsubscribeKey: (descriptor: KeyDescriptor) => { | ||
const { key, ns } = descriptor; | ||
decrementInMap(handlerWrapper.keys, key); | ||
getFallbackArray(ns).forEach((val) => | ||
decrementInMap(handlerWrapper.namespaces, val) | ||
); | ||
if (ns === undefined) { | ||
// subscribing to all namespaces | ||
decrementInMap(handlerWrapper.namespaces, undefined); | ||
} | ||
return result; | ||
}, | ||
}; | ||
@@ -105,23 +66,15 @@ | ||
const namespacesWithFallbacks = ( | ||
namespaces: Map<string | undefined, number> | Set<string | undefined> | ||
) => { | ||
if (namespaces.has(undefined)) { | ||
const result = new Set(namespaces.keys()); | ||
result.delete(undefined); | ||
getFallbackNamespaces().forEach((ns) => result.add(ns)); | ||
return result as Set<string>; | ||
} | ||
return namespaces as Map<string, number>; | ||
}; | ||
const callHandlers = (ns: Array<string> | undefined) => { | ||
// everything is implicitly subscribed to fallbacks | ||
// as it can always fall through to it | ||
const fallbackNamespaces = new Set(getFallbackNs()); | ||
const callHandlers = (key: string | undefined, ns: string[] | undefined) => { | ||
partialListeners.forEach((handler) => { | ||
const handlerNamespaces = namespacesWithFallbacks(handler.namespaces); | ||
const nsMatches = | ||
ns === undefined || | ||
ns?.findIndex((ns) => handlerNamespaces.has(ns)) !== -1; | ||
const keyMatches = | ||
key === undefined || handler.keys.has(key) || handler.keys.size === 0; | ||
if (nsMatches && keyMatches) { | ||
ns?.findIndex( | ||
(ns) => fallbackNamespaces.has(ns) || handler.namespaces.has(ns!) | ||
) !== -1; | ||
if (nsMatches) { | ||
handler.fn({ value: undefined as any }); | ||
@@ -132,3 +85,3 @@ } | ||
let queue: (KeyDescriptorInternal | undefined)[] = []; | ||
let queue: (string[] | undefined)[] = []; | ||
// merge events in queue into one event | ||
@@ -139,2 +92,5 @@ const solveQueue = () => { | ||
} | ||
const queueCopy = queue; | ||
queue = []; | ||
listeners.forEach((handler) => { | ||
@@ -144,34 +100,26 @@ handler({ value: undefined as any }); | ||
const namespaces = new Set<string | undefined>(); | ||
let keys: Set<string> | undefined = new Set<string>(); | ||
queue.forEach((descriptor) => { | ||
if (descriptor?.ns === undefined) { | ||
// when no ns specified, it affets all fallback namespaces | ||
namespaces.add(undefined); | ||
} else { | ||
descriptor.ns.forEach((ns) => namespaces.add(ns)); | ||
let namespaces: Set<NsType> | undefined = new Set<NsType>(); | ||
queueCopy.forEach((ns) => { | ||
if (ns === undefined) { | ||
// when no ns specified, it affects all namespaces | ||
namespaces = undefined; | ||
} else if (namespaces !== undefined) { | ||
ns.forEach((ns) => namespaces!.add(ns)); | ||
} | ||
if (descriptor?.key === undefined) { | ||
// when no key specified, it affects all keys | ||
keys = undefined; | ||
} else if (keys !== undefined) { | ||
keys.add(descriptor.key); | ||
} | ||
}); | ||
const namespacesArray = Array.from( | ||
namespacesWithFallbacks(namespaces).keys() | ||
); | ||
(keys || [undefined]).forEach((key) => { | ||
callHandlers(key, namespacesArray); | ||
}); | ||
queue = []; | ||
const namespacesArray = namespaces | ||
? Array.from(namespaces.keys()) | ||
: undefined; | ||
callHandlers(namespacesArray); | ||
}; | ||
const emit = (descriptor?: KeyDescriptorInternal, delayed?: boolean) => { | ||
queue.push(descriptor); | ||
const emit = (ns?: string[], delayed?: boolean) => { | ||
queue.push(ns); | ||
if (!delayed) { | ||
solveQueue(); | ||
} else { | ||
Promise.resolve().then(() => { | ||
solveQueue(); | ||
}); | ||
setTimeout(solveQueue, 0); | ||
} | ||
@@ -183,4 +131,6 @@ }; | ||
export type EventEmitterSelectiveType<T> = ReturnType< | ||
typeof EventEmitterSelective<T> | ||
>; | ||
export type EventEmitterSelectiveInstance = { | ||
readonly listenSome: (handler: Listener<undefined>) => SubscriptionSelective; | ||
readonly listen: (handler: Listener<undefined>) => Subscription; | ||
readonly emit: (ns?: string[], delayed?: boolean) => void; | ||
}; |
import { EventEmitter } from './EventEmitter'; | ||
import { EventEmitterSelective } from './EventEmitterSelective'; | ||
import { | ||
CacheDescriptorWithKey, | ||
KeyDescriptorInternal, | ||
ListenerHandler, | ||
TolgeeOn, | ||
} from '../../types'; | ||
import { CacheDescriptorWithKey, TolgeeOn } from '../../types'; | ||
export const Events = (getFallbackNamespaces: () => string[]) => { | ||
export const Events = ( | ||
getFallbackNs: () => string[], | ||
getDefaultNs: () => string | ||
) => { | ||
const onPendingLanguageChange = EventEmitter<string>(); | ||
const onLanguageChange = EventEmitter<string>(); | ||
const onKeyChange = EventEmitter<KeyDescriptorInternal>(); | ||
const onLoadingChange = EventEmitter<boolean>(); | ||
const onFetchingChange = EventEmitter<boolean>(); | ||
const onInitialLoaded = EventEmitter<void>(); | ||
const onKeyUpdate = EventEmitterSelective<void>(getFallbackNamespaces); | ||
const onRunningChange = EventEmitter<boolean>(); | ||
const onCacheChange = EventEmitter<CacheDescriptorWithKey>(); | ||
const onRunningChange = EventEmitter<boolean>(); | ||
const onUpdate = EventEmitterSelective(getFallbackNs, getDefaultNs); | ||
onInitialLoaded.listen(() => onKeyUpdate.emit()); | ||
onLanguageChange.listen(() => onKeyUpdate.emit()); | ||
onInitialLoaded.listen(() => onUpdate.emit()); | ||
onLanguageChange.listen(() => onUpdate.emit()); | ||
onCacheChange.listen(({ value }) => { | ||
onKeyUpdate.emit({ ns: [value.namespace], key: value.key }, true); | ||
onUpdate.emit([value.namespace], true); | ||
}); | ||
@@ -30,21 +27,17 @@ | ||
case 'pendingLanguage': | ||
return onPendingLanguageChange.listen( | ||
handler as ListenerHandler<string> | ||
); | ||
return onPendingLanguageChange.listen(handler as any); | ||
case 'language': | ||
return onLanguageChange.listen(handler as ListenerHandler<string>); | ||
return onLanguageChange.listen(handler as any); | ||
case 'loading': | ||
return onLoadingChange.listen(handler as ListenerHandler<boolean>); | ||
return onLoadingChange.listen(handler as any); | ||
case 'fetching': | ||
return onFetchingChange.listen(handler as ListenerHandler<boolean>); | ||
return onFetchingChange.listen(handler as any); | ||
case 'initialLoad': | ||
return onInitialLoaded.listen(handler as ListenerHandler<void>); | ||
return onInitialLoaded.listen(handler as any); | ||
case 'running': | ||
return onRunningChange.listen(handler as ListenerHandler<boolean>); | ||
return onRunningChange.listen(handler as any); | ||
case 'cache': | ||
return onCacheChange.listen( | ||
handler as ListenerHandler<CacheDescriptorWithKey> | ||
); | ||
case 'keyUpdate': | ||
return onKeyUpdate.listen(handler as ListenerHandler<void>); | ||
return onCacheChange.listen(handler as any); | ||
case 'update': | ||
return onUpdate.listen(handler as any); | ||
} | ||
@@ -56,4 +49,2 @@ }; | ||
onLanguageChange, | ||
onKeyChange, | ||
onKeyUpdate, | ||
onLoadingChange, | ||
@@ -64,2 +55,3 @@ onFetchingChange, | ||
onCacheChange, | ||
onUpdate, | ||
on, | ||
@@ -69,2 +61,2 @@ }); | ||
export type EventServiceType = ReturnType<typeof Events>; | ||
export type EventsInstance = ReturnType<typeof Events>; |
import { isPromise, valueOrPromise } from '../../helpers'; | ||
import { | ||
BackendDevInterface, | ||
BackendDevMiddleware, | ||
BackendGetRecord, | ||
BackendInterface, | ||
FormatterInterface, | ||
ObserverInterface, | ||
BackendMiddleware, | ||
FormatterMiddleware, | ||
ObserverMiddleware, | ||
TranslatePropsInternal, | ||
TranslationOnClick, | ||
UiInterface, | ||
UiLibInterface, | ||
UiMiddleware, | ||
UiLibMiddleware, | ||
UiType, | ||
FinalFormatterInterface, | ||
FinalFormatterMiddleware, | ||
HighlightInterface, | ||
UiConstructor, | ||
UiKeyOption, | ||
LanguageDetectorInterface, | ||
LanguageStorageInterface, | ||
Options, | ||
LanguageDetectorMiddleware, | ||
LanguageStorageMiddleware, | ||
ChangeTranslationInterface, | ||
WrapperWrapProps, | ||
Unwrapped, | ||
KeyAndNamespacesInternal, | ||
TolgeePlugin, | ||
TolgeeInstance, | ||
TolgeeOptionsInternal, | ||
} from '../../types'; | ||
import { getFallbackArray } from '../State/helpers'; | ||
import { ObserverOptions } from '../State/initObserverOptions'; | ||
export const PluginService = ( | ||
export const Plugins = ( | ||
getLanguage: () => string | undefined, | ||
getInitialOptions: () => Options, | ||
getObserverOptions: () => ObserverOptions, | ||
getInitialOptions: () => TolgeeOptionsInternal, | ||
getAvailableLanguages: () => string[] | undefined, | ||
getTranslationNs: (props: TranslatePropsInternal) => string[] | string, | ||
getTranslation: (props: TranslatePropsInternal) => string | undefined, | ||
getTranslationNs: (props: KeyAndNamespacesInternal) => string[], | ||
getTranslation: (props: KeyAndNamespacesInternal) => string | undefined, | ||
changeTranslation: ChangeTranslationInterface | ||
) => { | ||
let prepared = false; | ||
let onPrepareQueue: (() => void)[] = []; | ||
const plugins = { | ||
ui: undefined as UiConstructor | undefined, | ||
observer: undefined as ObserverInterface | undefined, | ||
observer: undefined as ObserverMiddleware | undefined, | ||
}; | ||
const instances = { | ||
formatters: [] as FormatterInterface[], | ||
finalFormatter: undefined as FinalFormatterInterface | undefined, | ||
observer: undefined as ReturnType<ObserverInterface> | undefined, | ||
devBackend: undefined as BackendDevInterface | undefined, | ||
backends: [] as BackendInterface[], | ||
ui: undefined as UiInterface | undefined, | ||
languageDetector: undefined as LanguageDetectorInterface | undefined, | ||
languageStorage: undefined as LanguageStorageInterface | undefined, | ||
formatters: [] as FormatterMiddleware[], | ||
finalFormatter: undefined as FinalFormatterMiddleware | undefined, | ||
observer: undefined as ReturnType<ObserverMiddleware> | undefined, | ||
devBackend: undefined as BackendDevMiddleware | undefined, | ||
backends: [] as BackendMiddleware[], | ||
ui: undefined as UiMiddleware | undefined, | ||
languageDetector: undefined as LanguageDetectorMiddleware | undefined, | ||
languageStorage: undefined as LanguageStorageMiddleware | undefined, | ||
}; | ||
const onClick: TranslationOnClick = async (event, { keysAndDefaults }) => { | ||
const onClick: TranslationOnClick = async ({ keysAndDefaults, event }) => { | ||
const withNs: UiKeyOption[] = keysAndDefaults.map( | ||
({ key, ns, defaultValue }) => ({ | ||
key, | ||
defaultValue, | ||
ns: getFallbackArray(getTranslationNs({ key, ns, defaultValue })), | ||
translation: getTranslation({ | ||
({ key, ns, defaultValue }) => { | ||
return { | ||
key, | ||
ns, | ||
}), | ||
}) | ||
defaultValue, | ||
ns: getTranslationNs({ key, ns }), | ||
translation: getTranslation({ | ||
key, | ||
ns, | ||
}), | ||
}; | ||
} | ||
); | ||
instances.ui?.handleElementClick(event, withNs); | ||
instances.ui?.handleElementClick(withNs, event); | ||
}; | ||
const run = (isDev: boolean) => { | ||
instances.ui = | ||
plugins.ui && | ||
new plugins.ui({ | ||
apiKey: getInitialOptions().apiKey!, | ||
apiUrl: getInitialOptions().apiUrl!, | ||
highlight, | ||
changeTranslation, | ||
}); | ||
if (!instances.observer) { | ||
instances.observer = plugins.observer?.({ | ||
translate, | ||
onClick, | ||
options: getObserverOptions(), | ||
}); | ||
} | ||
instances.observer?.run({ mouseHighlight: isDev }); | ||
}; | ||
const stop = () => { | ||
@@ -96,7 +81,10 @@ instances.ui = undefined; | ||
const translate = (props: TranslatePropsInternal) => { | ||
const translation = getTranslation(props); | ||
const translation = getTranslation({ | ||
key: props.key, | ||
ns: props.ns, | ||
}); | ||
return formatTranslation({ ...props, translation, formatEnabled: true }); | ||
}; | ||
const setObserver = (observer: ObserverInterface | undefined) => { | ||
const setObserver = (observer: ObserverMiddleware | undefined) => { | ||
plugins.observer = observer; | ||
@@ -109,3 +97,3 @@ }; | ||
const addFormatter = (formatter: FormatterInterface | undefined) => { | ||
const addFormatter = (formatter: FormatterMiddleware | undefined) => { | ||
if (formatter) { | ||
@@ -117,3 +105,3 @@ instances.formatters.push(formatter); | ||
const setFinalFormatter = ( | ||
formatter: FinalFormatterInterface | undefined | ||
formatter: FinalFormatterMiddleware | undefined | ||
) => { | ||
@@ -124,3 +112,3 @@ instances.finalFormatter = formatter; | ||
const setUi = (ui: UiType | undefined) => { | ||
plugins.ui = (ui as UiLibInterface)?.UI || ui; | ||
plugins.ui = (ui as UiLibMiddleware)?.UI || ui; | ||
}; | ||
@@ -133,3 +121,3 @@ | ||
const setLanguageStorage = ( | ||
storage: LanguageStorageInterface | undefined | ||
storage: LanguageStorageMiddleware | undefined | ||
) => { | ||
@@ -139,6 +127,2 @@ instances.languageStorage = storage; | ||
const getLanguageStorage = () => { | ||
return instances.languageStorage; | ||
}; | ||
const setStoredLanguage = (language: string) => { | ||
@@ -149,3 +133,3 @@ instances.languageStorage?.setLanguage(language); | ||
const setLanguageDetector = ( | ||
detector: LanguageDetectorInterface | undefined | ||
detector: LanguageDetectorMiddleware | undefined | ||
) => { | ||
@@ -186,3 +170,3 @@ instances.languageDetector = detector; | ||
const addBackend = (backend: BackendInterface | undefined) => { | ||
const addBackend = (backend: BackendMiddleware | undefined) => { | ||
if (backend) { | ||
@@ -193,6 +177,27 @@ instances.backends.push(backend); | ||
const setDevBackend = (backend: BackendDevInterface | undefined) => { | ||
const setDevBackend = (backend: BackendDevMiddleware | undefined) => { | ||
instances.devBackend = backend; | ||
}; | ||
const run = (isDev: boolean) => { | ||
if (!instances.ui && plugins.ui) { | ||
const { apiKey, apiUrl, projectId } = getInitialOptions(); | ||
instances.ui = new plugins.ui({ | ||
apiKey: apiKey!, | ||
apiUrl: apiUrl!, | ||
projectId, | ||
highlight, | ||
changeTranslation, | ||
}); | ||
} | ||
if (!instances.observer) { | ||
instances.observer = plugins.observer?.({ | ||
translate, | ||
onClick, | ||
options: getInitialOptions().observerOptions, | ||
}); | ||
} | ||
instances.observer?.run({ mouseHighlight: isDev }); | ||
}; | ||
const getDevBackend = () => { | ||
@@ -203,5 +208,7 @@ return instances.devBackend; | ||
const getBackendDevRecord: BackendGetRecord = ({ language, namespace }) => { | ||
const { apiKey, apiUrl, projectId } = getInitialOptions(); | ||
return instances.devBackend?.getRecord({ | ||
apiKey: getInitialOptions().apiKey, | ||
apiUrl: getInitialOptions().apiUrl, | ||
apiKey, | ||
apiUrl, | ||
projectId, | ||
language, | ||
@@ -229,3 +236,38 @@ namespace, | ||
const formatTranslation = ({ | ||
const unwrap = (text: string): Unwrapped => { | ||
if (instances.observer) { | ||
return instances.observer?.unwrap(text); | ||
} | ||
return { text, keys: [] }; | ||
}; | ||
const retranslate = () => { | ||
instances.observer?.retranslate(); | ||
}; | ||
const onPrepare = (callback: () => void) => { | ||
onPrepareQueue.push(callback); | ||
}; | ||
function addPlugin(tolgeeInstance: TolgeeInstance, plugin: TolgeePlugin) { | ||
const pluginTools = Object.freeze({ | ||
setFinalFormatter, | ||
addFormatter, | ||
setObserver, | ||
hasObserver, | ||
setUi, | ||
hasUi, | ||
setDevBackend, | ||
addBackend, | ||
setLanguageDetector, | ||
setLanguageStorage, | ||
onPrepare, | ||
}); | ||
plugin(tolgeeInstance, pluginTools); | ||
if (prepared) { | ||
prepare(); | ||
} | ||
} | ||
function formatTranslation({ | ||
key, | ||
@@ -239,3 +281,3 @@ translation, | ||
formatEnabled, | ||
}: TranslatePropsInternal & { formatEnabled?: boolean }) => { | ||
}: TranslatePropsInternal & { formatEnabled?: boolean }) { | ||
const formattableTranslation = translation || defaultValue; | ||
@@ -279,4 +321,8 @@ let result = formattableTranslation || (orEmpty ? '' : key); | ||
return result; | ||
}; | ||
} | ||
function hasDevBackend() { | ||
return Boolean(getDevBackend()); | ||
} | ||
const wrap = (params: WrapperWrapProps) => { | ||
@@ -289,30 +335,19 @@ if (instances.observer) { | ||
const unwrap = (text: string): Unwrapped => { | ||
if (instances.observer) { | ||
return instances.observer?.unwrap(text); | ||
function prepare() { | ||
prepared = true; | ||
while (onPrepareQueue.length) { | ||
const queue = onPrepareQueue; | ||
onPrepareQueue = []; | ||
queue.forEach((callback) => callback()); | ||
} | ||
return { text, keys: [] }; | ||
}; | ||
} | ||
const retranslate = () => { | ||
instances.observer?.retranslate(); | ||
}; | ||
return Object.freeze({ | ||
setFinalFormatter, | ||
addFormatter, | ||
prepare, | ||
addPlugin, | ||
formatTranslation, | ||
setObserver, | ||
hasObserver, | ||
setUi, | ||
hasUi, | ||
addBackend, | ||
setDevBackend, | ||
getDevBackend, | ||
getBackendRecord, | ||
getBackendDevRecord, | ||
setLanguageDetector, | ||
getLanguageDetector, | ||
setLanguageStorage, | ||
getLanguageStorage, | ||
getInitialLanguage, | ||
@@ -324,7 +359,8 @@ setStoredLanguage, | ||
highlight, | ||
unwrap, | ||
wrap, | ||
unwrap, | ||
hasDevBackend, | ||
}); | ||
}; | ||
export type PluginServiceType = ReturnType<typeof PluginService>; | ||
export type PluginsInstance = ReturnType<typeof Plugins>; |
import { | ||
FallbackGeneral, | ||
FallbackLanguageOption, | ||
FallbackNS, | ||
TreeTranslationsData, | ||
} from '../../types'; | ||
import { sanitizeUrl } from '../../helpers'; | ||
import { | ||
defaultObserverOptions, | ||
ObserverOptions, | ||
ObserverOptionsInternal, | ||
} from './observerOptions'; | ||
export type Options = { | ||
export type TolgeeStaticData = { | ||
[key: string]: TreeTranslationsData | (() => Promise<TreeTranslationsData>); | ||
}; | ||
export type TolgeeOptionsInternal = { | ||
/** | ||
@@ -12,9 +22,23 @@ * Initial language | ||
language?: string; | ||
/** | ||
* Tolgee instance url (e.g. https://app.tolgee.io) | ||
*/ | ||
apiUrl?: string; | ||
/** | ||
* Project API key (PAK) or Personal Access Token (PAT) | ||
*/ | ||
apiKey?: string; | ||
projectId?: number; | ||
/** | ||
* Project id is necessary if you are using PAT | ||
*/ | ||
projectId?: number | string; | ||
/** | ||
* Used when auto detection is not available or is turned off | ||
*/ | ||
defaultLanguage?: string; | ||
/** | ||
@@ -25,2 +49,3 @@ * Languages which can be used for language detection | ||
availableLanguages?: string[]; | ||
/** | ||
@@ -30,2 +55,3 @@ * Language which is used when no translation is available for current one | ||
fallbackLanguage?: FallbackLanguageOption; | ||
/** | ||
@@ -35,6 +61,8 @@ * Namespaces which should be always fetched | ||
ns?: string[]; | ||
/** | ||
* Namespaces to be used to find translation when no explicit namespace set. | ||
*/ | ||
fallbackNs?: FallbackNS; | ||
fallbackNs?: FallbackGeneral; | ||
/** | ||
@@ -44,13 +72,29 @@ * Default namespace when no namespace defined (default: '') | ||
defaultNs: string; | ||
/** | ||
* Prefix used for fetching languages (default: 'i18n/') | ||
* These data go directly to cache or you can specify async | ||
* function which will be used to get the data. Use `:` to add namespace: | ||
* | ||
* ```ts | ||
* { | ||
* 'locale': <translations | async function> | ||
* 'locale:namespace': <translations | async function> | ||
* } | ||
* ``` | ||
*/ | ||
filesUrlPrefix: string; | ||
staticData?: { | ||
[key: string]: TreeTranslationsData | (() => Promise<TreeTranslationsData>); | ||
}; | ||
staticData?: TolgeeStaticData; | ||
observerOptions: ObserverOptionsInternal; | ||
observerType: 'invisible' | 'text'; | ||
}; | ||
export type TolgeeOptions = Partial< | ||
Omit<TolgeeOptionsInternal, 'observerOptions'> | ||
> & { | ||
observerOptions?: ObserverOptions; | ||
}; | ||
export type State = { | ||
initialOptions: Options; | ||
initialOptions: TolgeeOptionsInternal; | ||
activeNamespaces: Map<string, number>; | ||
@@ -63,20 +107,37 @@ language: string | undefined; | ||
const defaultValues: Options = { | ||
const defaultValues: TolgeeOptionsInternal = { | ||
defaultNs: '', | ||
filesUrlPrefix: 'i18n/', | ||
observerOptions: defaultObserverOptions, | ||
observerType: 'invisible', | ||
}; | ||
export const combineOptions = <T extends TolgeeOptions>( | ||
...states: (T | undefined)[] | ||
) => { | ||
let result = {} as T; | ||
states.forEach((state) => { | ||
result = { | ||
...result, | ||
...state, | ||
observerOptions: { | ||
...result.observerOptions, | ||
...state?.observerOptions, | ||
}, | ||
}; | ||
}); | ||
return result; | ||
}; | ||
export const initState = ( | ||
options?: Partial<Options>, | ||
options?: Partial<TolgeeOptions>, | ||
previousState?: State | ||
): State => { | ||
const initialOptions = { | ||
...defaultValues, | ||
...previousState?.initialOptions, | ||
...options, | ||
}; | ||
const initialOptions = combineOptions( | ||
defaultValues, | ||
previousState?.initialOptions, | ||
options | ||
) as TolgeeOptionsInternal; | ||
// remove extra '/' from url end | ||
const apiUrl = initialOptions.apiUrl; | ||
initialOptions.apiUrl = apiUrl ? apiUrl.replace(/\/+$/, '') : apiUrl; | ||
initialOptions.apiUrl = sanitizeUrl(initialOptions.apiUrl); | ||
@@ -83,0 +144,0 @@ return { |
@@ -5,20 +5,25 @@ import { | ||
DevCredentials, | ||
EventEmitterType, | ||
FallbackNSTranslation, | ||
NsFallback, | ||
NsType, | ||
} from '../../types'; | ||
import { decodeCacheKey } from '../Cache/helpers'; | ||
import { getFallbackArray, getFallbackFromStruct, unique } from './helpers'; | ||
import { initObserverOptions, ObserverOptions } from './initObserverOptions'; | ||
import { initState, Options } from './initState'; | ||
import { EventEmitterInstance } from '../Events/EventEmitter'; | ||
import { | ||
getFallbackArray, | ||
getFallbackFromStruct, | ||
sanitizeUrl, | ||
unique, | ||
} from '../../helpers'; | ||
import { initState, TolgeeOptions } from './initState'; | ||
export const State = ( | ||
onLanguageChange: EventEmitterType<string>, | ||
onPendingLanguageChange: EventEmitterType<string>, | ||
onRunningChange: EventEmitterType<boolean> | ||
onLanguageChange: EventEmitterInstance<string>, | ||
onPendingLanguageChange: EventEmitterInstance<string>, | ||
onRunningChange: EventEmitterInstance<boolean> | ||
) => { | ||
let state = initState(); | ||
let observerOptions = initObserverOptions(); | ||
let devCredentials: DevCredentials = {}; | ||
let devCredentials: DevCredentials = undefined; | ||
function init(options?: Partial<Options>) { | ||
function init(options?: Partial<TolgeeOptions>) { | ||
state = initState(options, state); | ||
@@ -72,3 +77,3 @@ } | ||
function addActiveNs(ns: FallbackNSTranslation) { | ||
function addActiveNs(ns: NsFallback) { | ||
const namespaces = getFallbackArray(ns); | ||
@@ -85,3 +90,3 @@ namespaces.forEach((namespace) => { | ||
function removeActiveNs(ns: FallbackNSTranslation) { | ||
function removeActiveNs(ns: NsFallback) { | ||
const namespaces = getFallbackArray(ns); | ||
@@ -101,2 +106,3 @@ namespaces.forEach((namespace) => { | ||
...(state.initialOptions.ns || [state.initialOptions.defaultNs]), | ||
...getFallbackArray(state.initialOptions.fallbackNs), | ||
...state.activeNamespaces.keys(), | ||
@@ -117,9 +123,10 @@ ]); | ||
function getFallbackNamespaces() { | ||
const defaultNs = state.initialOptions.defaultNs; | ||
const fallbackNs = state.initialOptions.fallbackNs; | ||
const fallbackNamespaces = typeof defaultNs === 'string' ? [defaultNs] : []; | ||
return unique([...fallbackNamespaces, ...getFallbackArray(fallbackNs)]); | ||
function getFallbackNs() { | ||
return getFallbackArray(state.initialOptions.fallbackNs); | ||
} | ||
function getDefaultNs(ns?: NsType) { | ||
return ns === undefined ? state.initialOptions.defaultNs : ns; | ||
} | ||
function getAvailableLanguages() { | ||
@@ -147,13 +154,12 @@ if (state.initialOptions.availableLanguages) { | ||
function overrideCredentials(credentials: DevCredentials) { | ||
devCredentials = credentials; | ||
if (credentials) { | ||
devCredentials = { | ||
...credentials, | ||
apiUrl: sanitizeUrl(credentials.apiUrl), | ||
}; | ||
} else { | ||
devCredentials = undefined; | ||
} | ||
} | ||
function setObserverOptions(options: Partial<ObserverOptions>) { | ||
observerOptions = initObserverOptions(options); | ||
} | ||
function getObserverOptions() { | ||
return observerOptions; | ||
} | ||
return Object.freeze({ | ||
@@ -174,9 +180,10 @@ init, | ||
getFallbackLangs, | ||
getFallbackNamespaces, | ||
getFallbackNs, | ||
getDefaultNs, | ||
getAvailableLanguages, | ||
withDefaultNs, | ||
overrideCredentials, | ||
setObserverOptions, | ||
getObserverOptions, | ||
}); | ||
}; | ||
export type StateInstance = ReturnType<typeof State>; |
@@ -5,3 +5,3 @@ export const ValueObserver = <T = any>( | ||
handler: (value: T) => void | ||
) => { | ||
): ValueObserverInstance<T> => { | ||
let previousValue: T = initialValue; | ||
@@ -24,2 +24,5 @@ function init(value: T) { | ||
export type ValueObserverInstance<T> = ReturnType<typeof ValueObserver<T>>; | ||
export type ValueObserverInstance<T> = { | ||
readonly init: (value: T) => void; | ||
readonly notify: () => void; | ||
}; |
@@ -0,1 +1,7 @@ | ||
import { | ||
FallbackGeneral, | ||
FallbackLanguageObject, | ||
FallbackLanguageOption, | ||
} from './types'; | ||
export function isPromise(value: any) { | ||
@@ -18,1 +24,40 @@ return Boolean(value && typeof value.then === 'function'); | ||
`Tolgee: You need to specify '${option}' option`; | ||
export function isObject(item: any) { | ||
return typeof item === 'object' && !Array.isArray(item) && item !== null; | ||
} | ||
export function getFallback(value: FallbackGeneral): string[] | undefined { | ||
if (typeof value === 'string') { | ||
return [value]; | ||
} | ||
if (Array.isArray(value)) { | ||
return value; | ||
} | ||
return undefined; | ||
} | ||
export function getFallbackArray(value: FallbackGeneral): string[] { | ||
return getFallback(value) || []; | ||
} | ||
export function getFallbackFromStruct( | ||
language: string, | ||
fallbackLanguage: FallbackLanguageOption | ||
) { | ||
if (isObject(fallbackLanguage)) { | ||
return getFallbackArray( | ||
(fallbackLanguage as FallbackLanguageObject)?.[language] | ||
); | ||
} else { | ||
return getFallbackArray(fallbackLanguage as FallbackGeneral); | ||
} | ||
} | ||
export function unique<T>(arr: T[]) { | ||
return Array.from(new Set(arr)); | ||
} | ||
export function sanitizeUrl(url: string | undefined) { | ||
return url ? url.replace(/\/+$/, '') : url; | ||
} |
@@ -0,11 +1,5 @@ | ||
export { getFallback, getFallbackArray } from './helpers'; | ||
export { Tolgee } from './Tolgee'; | ||
export { | ||
RESTRICTED_ASCENDANT_ATTRIBUTE, | ||
DEVTOOLS_ID, | ||
TOLGEE_ATTRIBUTE_NAME, | ||
TOLGEE_HIGHLIGHTER_CLASS, | ||
TOLGEE_WRAPPED_ONLY_DATA_ATTRIBUTE, | ||
} from './constants'; | ||
export * from './types'; | ||
export { getTranslateParams } from './TranslateParams'; | ||
export { getFallback, getFallbackArray } from './Controller/State/helpers'; | ||
export { getTranslateProps } from './TranslateParams'; | ||
export { FormatSimple } from './FormatSimple/FormatSimple'; |
import { Controller } from './Controller/Controller'; | ||
import { | ||
Options, | ||
TolgeeInstance, | ||
TolgeePlugin, | ||
ObserverOptions, | ||
} from './types'; | ||
import { combineOptions } from './Controller/State/initState'; | ||
import { TolgeeOptions, TolgeePlugin, DevCredentials } from './types'; | ||
export const Tolgee = (options?: Partial<Options>): TolgeeInstance => { | ||
const createTolgee = (options: TolgeeOptions) => { | ||
const controller = Controller({ | ||
@@ -14,16 +10,3 @@ options, | ||
const pluginTools = Object.freeze({ | ||
setFinalFormatter: controller.setFinalFormatter, | ||
addFormatter: controller.addFormatter, | ||
setObserver: controller.setObserver, | ||
hasObserver: controller.hasObserver, | ||
setUi: controller.setUi, | ||
hasUi: controller.hasUi, | ||
setDevBackend: controller.setDevBackend, | ||
addBackend: controller.addBackend, | ||
setLanguageDetector: controller.setLanguageDetector, | ||
setLanguageStorage: controller.setLanguageStorage, | ||
overrideCredentials: controller.overrideCredentials, | ||
}); | ||
// restarts tolgee while applying callback | ||
const withRestart = (callback: () => void) => { | ||
@@ -36,47 +19,186 @@ const wasRunning = controller.isRunning(); | ||
const tolgee: TolgeeInstance = Object.freeze({ | ||
// event listeners | ||
const tolgee = Object.freeze({ | ||
/** | ||
* Listen to tolgee events. | ||
*/ | ||
on: controller.on, | ||
onKeyUpdate: controller.onKeyUpdate.listenSome, | ||
// state | ||
/** | ||
* Listen for specific namespaces changes. | ||
* | ||
* ``` | ||
* const sub = tolgee.onUpdate(handler) | ||
* | ||
* // subscribe to selected namespace | ||
* sub.subscribeNs(['common']) | ||
* | ||
* // unsubscribe | ||
* sub.unsubscribe() | ||
* ``` | ||
*/ | ||
onNsUpdate: controller.onUpdate.listenSome, | ||
/** | ||
* @return current language if set. | ||
*/ | ||
getLanguage: controller.getLanguage, | ||
/** | ||
* `pendingLanguage` represents language which is currently being loaded. | ||
* @return current `pendingLanguage` if set. | ||
*/ | ||
getPendingLanguage: controller.getPendingLanguage, | ||
/** | ||
* Change current language. | ||
* - if not running sets `pendingLanguage`, `language` to the new value | ||
* - if running sets `pendingLanguage` to the value, fetches necessary data and then changes `language` | ||
* | ||
* @return Promise which is resolved when `language` is changed. | ||
*/ | ||
changeLanguage: controller.changeLanguage, | ||
/** | ||
* Temporarily change translation in cache. | ||
* @return object with revert method. | ||
*/ | ||
changeTranslation: controller.changeTranslation, | ||
/** | ||
* Adds namespace(s) list of active namespaces. And if tolgee is running, loads required data. | ||
*/ | ||
addActiveNs: controller.addActiveNs, | ||
/** | ||
* Remove namespace(s) from active namespaces. | ||
* | ||
* Tolgee internally counts how many times was each active namespace added, | ||
* so this method will remove namespace only if the counter goes down to 0. | ||
*/ | ||
removeActiveNs: controller.removeActiveNs, | ||
/** | ||
* Manually load multiple records from `Backend` (or `DevBackend` when in dev mode) | ||
* | ||
* It loads data together and adds them to cache in one operation, to prevent partly loaded state. | ||
*/ | ||
loadRecords: controller.loadRecords, | ||
/** | ||
* Manually load record from `Backend` (or `DevBackend` when in dev mode) | ||
*/ | ||
loadRecord: controller.loadRecord, | ||
/** | ||
* | ||
*/ | ||
addStaticData: controller.addStaticData, | ||
/** | ||
* Get record from cache. | ||
*/ | ||
getRecord: controller.getRecord, | ||
/** | ||
* Get all records from cache. | ||
*/ | ||
getAllRecords: controller.getAllRecords, | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if there are data that need to be fetched. | ||
*/ | ||
isLoaded: controller.isLoaded, | ||
/** | ||
* @return `true` if tolgee is loading initial data (triggered by `run`). | ||
*/ | ||
isInitialLoading: controller.isInitialLoading, | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if tolgee is loading some translations for the first time. | ||
*/ | ||
isLoading: controller.isLoading, | ||
/** | ||
* @param ns optional list of namespaces that you are interested in | ||
* @return `true` if tolgee is fetching some translations. | ||
*/ | ||
isFetching: controller.isFetching, | ||
/** | ||
* @return `true` if tolgee is running. | ||
*/ | ||
isRunning: controller.isRunning, | ||
/** | ||
* Changes internal state to running: true and loads initial files. | ||
* Runs runnable plugins mainly Observer if present. | ||
*/ | ||
run: controller.run, | ||
/** | ||
* Changes internal state to running: false and stops runnable plugins. | ||
*/ | ||
stop: controller.stop, | ||
/** | ||
* Returns translated and formatted key. | ||
* If Observer is present and tolgee is running, wraps result to be identifiable in the DOM. | ||
*/ | ||
t: controller.t, | ||
/** | ||
* Highlight keys that match selection. | ||
*/ | ||
highlight: controller.highlight, | ||
/** | ||
* @return current Tolgee options. | ||
*/ | ||
getInitialOptions: controller.getInitialOptions, | ||
/** | ||
* Tolgee is in dev mode if `DevTools` plugin is used and `apiKey` + `apiUrl` are specified. | ||
* @return `true` if tolgee is in dev mode. | ||
*/ | ||
isDev: controller.isDev, | ||
/** | ||
* Wraps translation if there is `Observer` plugin | ||
*/ | ||
wrap: controller.wrap, | ||
/** | ||
* Unwrap translation | ||
*/ | ||
unwrap: controller.unwrap, | ||
// plugins | ||
setObserverOptions: (options: Partial<ObserverOptions>) => { | ||
controller.setObserverOptions(options); | ||
return tolgee; | ||
/** | ||
* Override creadentials passed on initialization | ||
*/ | ||
overrideCredentials(credentials: DevCredentials) { | ||
withRestart(() => controller.overrideCredentials(credentials)); | ||
}, | ||
use: (plugin: TolgeePlugin | undefined) => { | ||
/** | ||
* Add tolgee plugin. | ||
*/ | ||
addPlugin(plugin: TolgeePlugin | undefined) { | ||
if (plugin) { | ||
withRestart(() => plugin(tolgee, pluginTools)); | ||
withRestart(() => controller.addPlugin(tolgee, plugin)); | ||
} | ||
return tolgee; | ||
}, | ||
init: (options: Partial<Options>) => { | ||
withRestart(() => controller.init(options)); | ||
return tolgee; | ||
/** | ||
* Updates options after instance creation. Extends existing options, | ||
* so it only changes the fields, that are listed. | ||
* | ||
* When called in running state, tolgee stops and runs again. | ||
*/ | ||
updateOptions(options?: TolgeeOptions) { | ||
if (options) { | ||
withRestart(() => controller.init(options)); | ||
} | ||
}, | ||
@@ -87,1 +209,53 @@ }); | ||
}; | ||
export type TolgeeInstance = ReturnType<typeof createTolgee>; | ||
export type TolgeeChainer = { | ||
/** | ||
* Add plugin, plugins are applied when `init` method is called. | ||
*/ | ||
use: (plugin: TolgeePlugin | undefined) => TolgeeChainer; | ||
/** | ||
* Update default options before tolgee is initialized. | ||
*/ | ||
updateDefaults: (options: TolgeeOptions) => TolgeeChainer; | ||
/** | ||
* Initialize tolgee options and apply plugins | ||
* @returns tolgee instance | ||
*/ | ||
init(options?: TolgeeOptions): TolgeeInstance; | ||
}; | ||
/** | ||
* Tolgee chainable constructor. | ||
* | ||
* Usage: | ||
* ``` | ||
* const tolgee = Tolgee().use(...).init(...) | ||
* ``` | ||
*/ | ||
export const Tolgee = (): TolgeeChainer => { | ||
const state = { | ||
plugins: [] as (TolgeePlugin | undefined)[], | ||
options: {} as TolgeeOptions, | ||
}; | ||
const tolgeeChain = Object.freeze({ | ||
use(plugin: TolgeePlugin | undefined) { | ||
state.plugins.push(plugin); | ||
return tolgeeChain; | ||
}, | ||
updateDefaults(options: TolgeeOptions) { | ||
state.options = combineOptions(state.options, options); | ||
return tolgeeChain; | ||
}, | ||
init(options?: TolgeeOptions) { | ||
const tolgee = createTolgee(combineOptions(state.options, options)); | ||
state.plugins.forEach(tolgee.addPlugin); | ||
return tolgee; | ||
}, | ||
}); | ||
return tolgeeChain; | ||
}; |
@@ -1,2 +0,2 @@ | ||
import { getTranslateParams } from './TranslateParams'; | ||
import { getTranslateProps } from './TranslateParams'; | ||
import { TranslateProps } from './types'; | ||
@@ -8,10 +8,10 @@ | ||
noWrap: true, | ||
ns: [], | ||
ns: 'test', | ||
orEmpty: true, | ||
params: { yo: 'yo', ns: '(this is param not namespace)' }, | ||
params: { yo: 'yo' }, | ||
}; | ||
describe('getTranslateParams', () => { | ||
describe('getTranslateProps', () => { | ||
it('manages regular params', () => { | ||
const result = getTranslateParams(testParams); | ||
const result = getTranslateProps(testParams); | ||
expect(result).toEqual(testParams); | ||
@@ -21,3 +21,3 @@ }); | ||
it('manages key with default value', () => { | ||
const result = getTranslateParams('test', 'Test'); | ||
const result = getTranslateProps('test', 'Test'); | ||
expect(result).toEqual({ key: 'test', defaultValue: 'Test' }); | ||
@@ -27,3 +27,3 @@ }); | ||
it('manages key with options', () => { | ||
const result = getTranslateParams('test', { noWrap: true, yo: 'yo' }); | ||
const result = getTranslateProps('test', { noWrap: true, yo: 'yo' }); | ||
expect(result).toEqual({ key: 'test', noWrap: true, params: { yo: 'yo' } }); | ||
@@ -33,10 +33,7 @@ }); | ||
it('manages key default and options', () => { | ||
const result = getTranslateParams('test', 'Test', { | ||
const result = getTranslateProps('test', 'Test', { | ||
noWrap: true, | ||
ns: [], | ||
ns: 'test', | ||
orEmpty: true, | ||
yo: 'yo', | ||
params: { | ||
ns: '(this is param not namespace)', | ||
}, | ||
}); | ||
@@ -43,0 +40,0 @@ expect(result).toEqual(testParams); |
@@ -14,16 +14,17 @@ import { | ||
...rest | ||
}: CombinedOptions<any>): Partial<TranslateProps> { | ||
const options: Required<TranslateOptions<any>> = { | ||
}: Partial<TranslateProps>): Partial<TranslateProps> { | ||
const options: Required<TranslateOptions> = { | ||
ns: ns!, | ||
noWrap: noWrap!, | ||
orEmpty: orEmpty!, | ||
}; | ||
return { | ||
...options, | ||
params: { | ||
...rest, | ||
...params, | ||
}, | ||
}; | ||
return options; | ||
} | ||
export const getTranslateParams: TFnType<any, TranslateProps<any>> = ( | ||
export const getTranslateProps: TFnType<any, TranslateProps<any>> = ( | ||
keyOrProps, | ||
@@ -30,0 +31,0 @@ ...params |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
663065
81
9214
16
1