225 lines
6.3 KiB
JavaScript
225 lines
6.3 KiB
JavaScript
import * as utils from './utils.js';
|
|
import baseLogger from './logger.js';
|
|
import EventEmitter from './EventEmitter.js';
|
|
|
|
function remove(arr, what) {
|
|
let found = arr.indexOf(what);
|
|
|
|
while (found !== -1) {
|
|
arr.splice(found, 1);
|
|
found = arr.indexOf(what);
|
|
}
|
|
}
|
|
|
|
class Connector extends EventEmitter {
|
|
constructor(backend, store, services, options = {}) {
|
|
super();
|
|
if (utils.isIE10) {
|
|
EventEmitter.call(this); // <=IE10 fix (unable to call parent constructor)
|
|
}
|
|
|
|
this.backend = backend;
|
|
this.store = store;
|
|
this.services = services;
|
|
this.languageUtils = services.languageUtils;
|
|
this.options = options;
|
|
this.logger = baseLogger.create('backendConnector');
|
|
|
|
this.state = {};
|
|
this.queue = [];
|
|
|
|
if (this.backend && this.backend.init) {
|
|
this.backend.init(services, options.backend, options);
|
|
}
|
|
}
|
|
|
|
queueLoad(languages, namespaces, options, callback) {
|
|
// find what needs to be loaded
|
|
const toLoad = [];
|
|
const pending = [];
|
|
const toLoadLanguages = [];
|
|
const toLoadNamespaces = [];
|
|
|
|
languages.forEach(lng => {
|
|
let hasAllNamespaces = true;
|
|
|
|
namespaces.forEach(ns => {
|
|
const name = `${lng}|${ns}`;
|
|
|
|
if (!options.reload && this.store.hasResourceBundle(lng, ns)) {
|
|
this.state[name] = 2; // loaded
|
|
} else if (this.state[name] < 0) {
|
|
// nothing to do for err
|
|
} else if (this.state[name] === 1) {
|
|
if (pending.indexOf(name) < 0) pending.push(name);
|
|
} else {
|
|
this.state[name] = 1; // pending
|
|
|
|
hasAllNamespaces = false;
|
|
|
|
if (pending.indexOf(name) < 0) pending.push(name);
|
|
if (toLoad.indexOf(name) < 0) toLoad.push(name);
|
|
if (toLoadNamespaces.indexOf(ns) < 0) toLoadNamespaces.push(ns);
|
|
}
|
|
});
|
|
|
|
if (!hasAllNamespaces) toLoadLanguages.push(lng);
|
|
});
|
|
|
|
if (toLoad.length || pending.length) {
|
|
this.queue.push({
|
|
pending,
|
|
loaded: {},
|
|
errors: [],
|
|
callback,
|
|
});
|
|
}
|
|
|
|
return {
|
|
toLoad,
|
|
pending,
|
|
toLoadLanguages,
|
|
toLoadNamespaces,
|
|
};
|
|
}
|
|
|
|
loaded(name, err, data) {
|
|
const s = name.split('|');
|
|
const lng = s[0];
|
|
const ns = s[1];
|
|
|
|
if (err) this.emit('failedLoading', lng, ns, err);
|
|
|
|
if (data) {
|
|
this.store.addResourceBundle(lng, ns, data);
|
|
}
|
|
|
|
// set loaded
|
|
this.state[name] = err ? -1 : 2;
|
|
|
|
// consolidated loading done in this run - only emit once for a loaded namespace
|
|
const loaded = {};
|
|
|
|
// callback if ready
|
|
this.queue.forEach(q => {
|
|
utils.pushPath(q.loaded, [lng], ns);
|
|
remove(q.pending, name);
|
|
|
|
if (err) q.errors.push(err);
|
|
|
|
if (q.pending.length === 0 && !q.done) {
|
|
// only do once per loaded -> this.emit('loaded', q.loaded);
|
|
Object.keys(q.loaded).forEach(l => {
|
|
if (!loaded[l]) loaded[l] = [];
|
|
if (q.loaded[l].length) {
|
|
q.loaded[l].forEach(ns => {
|
|
if (loaded[l].indexOf(ns) < 0) loaded[l].push(ns);
|
|
});
|
|
}
|
|
});
|
|
|
|
/* eslint no-param-reassign: 0 */
|
|
q.done = true;
|
|
if (q.errors.length) {
|
|
q.callback(q.errors);
|
|
} else {
|
|
q.callback();
|
|
}
|
|
}
|
|
});
|
|
|
|
// emit consolidated loaded event
|
|
this.emit('loaded', loaded);
|
|
|
|
// remove done load requests
|
|
this.queue = this.queue.filter(q => !q.done);
|
|
}
|
|
|
|
read(lng, ns, fcName, tried = 0, wait = 350, callback) {
|
|
if (!lng.length) return callback(null, {}); // noting to load
|
|
|
|
return this.backend[fcName](lng, ns, (err, data) => {
|
|
if (err && data /* = retryFlag */ && tried < 5) {
|
|
setTimeout(() => {
|
|
this.read.call(this, lng, ns, fcName, tried + 1, wait * 2, callback);
|
|
}, wait);
|
|
return;
|
|
}
|
|
callback(err, data);
|
|
});
|
|
}
|
|
|
|
/* eslint consistent-return: 0 */
|
|
prepareLoading(languages, namespaces, options = {}, callback) {
|
|
if (!this.backend) {
|
|
this.logger.warn('No backend was added via i18next.use. Will not load resources.');
|
|
return callback && callback();
|
|
}
|
|
|
|
if (typeof languages === 'string') languages = this.languageUtils.toResolveHierarchy(languages);
|
|
if (typeof namespaces === 'string') namespaces = [namespaces];
|
|
|
|
const toLoad = this.queueLoad(languages, namespaces, options, callback);
|
|
if (!toLoad.toLoad.length) {
|
|
if (!toLoad.pending.length) callback(); // nothing to load and no pendings...callback now
|
|
return null; // pendings will trigger callback
|
|
}
|
|
|
|
toLoad.toLoad.forEach(name => {
|
|
this.loadOne(name);
|
|
});
|
|
}
|
|
|
|
load(languages, namespaces, callback) {
|
|
this.prepareLoading(languages, namespaces, {}, callback);
|
|
}
|
|
|
|
reload(languages, namespaces, callback) {
|
|
this.prepareLoading(languages, namespaces, { reload: true }, callback);
|
|
}
|
|
|
|
loadOne(name, prefix = '') {
|
|
const s = name.split('|');
|
|
const lng = s[0];
|
|
const ns = s[1];
|
|
|
|
this.read(lng, ns, 'read', undefined, undefined, (err, data) => {
|
|
if (err) this.logger.warn(`${prefix}loading namespace ${ns} for language ${lng} failed`, err);
|
|
if (!err && data)
|
|
this.logger.log(`${prefix}loaded namespace ${ns} for language ${lng}`, data);
|
|
|
|
this.loaded(name, err, data);
|
|
});
|
|
}
|
|
|
|
saveMissing(languages, namespace, key, fallbackValue, isUpdate, options = {}) {
|
|
if (
|
|
this.services.utils &&
|
|
this.services.utils.hasLoadedNamespace &&
|
|
!this.services.utils.hasLoadedNamespace(namespace)
|
|
) {
|
|
this.logger.warn(
|
|
`did not save key "${key}" as the namespace "${namespace}" was not yet loaded`,
|
|
'This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
// ignore non valid keys
|
|
if (key === undefined || key === null || key === '') return;
|
|
|
|
if (this.backend && this.backend.create) {
|
|
this.backend.create(languages, namespace, key, fallbackValue, null /* unused callback */, {
|
|
...options,
|
|
isUpdate,
|
|
});
|
|
}
|
|
|
|
// write to store to avoid resending
|
|
if (!languages || !languages[0]) return;
|
|
this.store.addResource(languages[0], namespace, key, fallbackValue);
|
|
}
|
|
}
|
|
|
|
export default Connector;
|