i18next#BackendModule TypeScript Examples
The following examples show how to use
i18next#BackendModule.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: i18next-electron-fs-backend.ts From TidGi-Desktop with Mozilla Public License 2.0 | 4 votes |
// Template is found at: https://www.i18next.com/misc/creating-own-plugins#backend;
// also took code from: https://github.com/i18next/i18next-node-fs-backend
export class Backend implements BackendModule {
static type = 'backend';
type = 'backend' as const;
backendOptions: any;
i18nextOptions: any;
mainLog: any;
readCallbacks: any;
rendererLog: any;
services: any;
useOverflow: any;
writeCallbacks: any;
writeQueue: any;
writeQueueOverflow: any;
writeTimeout: any;
constructor(services: any, backendOptions = {}, i18nextOptions = {}) {
this.init(services, backendOptions, i18nextOptions);
this.readCallbacks = {}; // Callbacks after reading a translation
this.writeCallbacks = {}; // Callbacks after writing a missing translation
this.writeTimeout; // A timer that will initate writing missing translations to files
this.writeQueue = []; // An array to hold missing translations before the writeTimeout occurs
this.writeQueueOverflow = []; // An array to hold missing translations while the writeTimeout's items are being written to file
this.useOverflow = false; // If true, we should insert missing translations into the writeQueueOverflow
}
init(services: any, backendOptions: any, i18nextOptions: any) {
if (typeof window !== 'undefined' && typeof window.i18n.i18nextElectronBackend === 'undefined') {
throw new TypeError("'window.i18n.i18nextElectronBackend' is not defined! Be sure you are setting up your BrowserWindow's preload script properly!");
}
this.services = services;
this.backendOptions = {
...defaultOptions,
...backendOptions,
i18nextElectronBackend: typeof window !== 'undefined' ? window.i18n.i18nextElectronBackend : undefined,
};
this.i18nextOptions = i18nextOptions;
// log-related
const logPrepend = '[i18next-electron-fs-backend:';
this.mainLog = `${logPrepend}main]=>`;
this.rendererLog = `${logPrepend}renderer]=>`;
this.setupIpcBindings();
}
// Sets up Ipc bindings so that we can keep any node-specific
// modules; (ie. 'fs') out of the Electron renderer process
setupIpcBindings() {
const { i18nextElectronBackend } = this.backendOptions;
i18nextElectronBackend.onReceive(I18NChannels.readFileResponse, (arguments_: any) => {
// args:
// {
// key
// error
// data
// }
// Don't know why we need this line;
// upon initialization, the i18next library
// ends up in this .on([channel], args) method twice
if (typeof this.readCallbacks[arguments_.key] === 'undefined') {
return;
}
let callback;
if (arguments_.error) {
// Failed to read translation file;
// we pass back a fake "success" response
// so that we create a translation file
callback = this.readCallbacks[arguments_.key].callback;
delete this.readCallbacks[arguments_.key];
if (callback !== null && typeof callback === 'function') {
callback(null, {});
}
} else {
let result;
arguments_.data = arguments_.data.replace(/^\uFEFF/, '');
try {
result = JSON.parse(arguments_.data);
} catch (parseError) {
(parseError as Error).message = `Error parsing '${arguments_.filename}'. Message: '${parseError}'.`;
callback = this.readCallbacks[arguments_.key].callback;
delete this.readCallbacks[arguments_.key];
if (callback !== null && typeof callback === 'function') {
callback(parseError);
}
return;
}
callback = this.readCallbacks[arguments_.key].callback;
delete this.readCallbacks[arguments_.key];
if (callback !== null && typeof callback === 'function') {
callback(null, result);
}
}
});
i18nextElectronBackend.onReceive(I18NChannels.writeFileResponse, (arguments_: any) => {
// args:
// {
// keys
// error
// }
const { keys } = arguments_;
for (const key of keys) {
let callback;
// Write methods don't have any callbacks from what I've seen,
// so this is called more than I thought; but necessary!
if (typeof this.writeCallbacks[key] === 'undefined') {
return;
}
if (arguments_.error) {
callback = this.writeCallbacks[key].callback;
delete this.writeCallbacks[key];
callback(arguments_.error);
} else {
callback = this.writeCallbacks[key].callback;
delete this.writeCallbacks[key];
callback(null, true);
}
}
});
}
// Writes a given translation to file
write(writeQueue: any) {
const { debug, i18nextElectronBackend } = this.backendOptions;
// Group by filename so we can make one request
// for all changes within a given file
const toWork = groupByArray(writeQueue, 'filename');
for (const element of toWork) {
const anonymous = (error: any, data: any) => {
if (error) {
console.error(
`${this.rendererLog} encountered error when trying to read file '{filename}' before writing missing translation ('{key}'/'{fallbackValue}') to file. Please resolve this error so missing translation values can be written to file. Error: '${error}'.`,
);
return;
}
const keySeparator = !!this.i18nextOptions.keySeparator; // Do we have a key separator or not?
const writeKeys = [];
for (let index = 0; index < element.values.length; index++) {
// If we have no key separator set, simply update the translation value
if (!keySeparator) {
data[element.values[index].key] = element.values[index].fallbackValue;
} else {
// Created the nested object structure based on the key separator, and merge that
// into the existing translation data
data = mergeNestedI18NObject(data, element.values[index].key, this.i18nextOptions.keySeparator, element.values[index].fallbackValue);
}
const writeKey = uuid();
if (element.values[index].callback) {
this.writeCallbacks[writeKey] = {
callback: element.values[index].callback,
};
writeKeys.push(writeKey);
}
}
// Send out the message to the ipcMain process
debug ? console.log(`${this.rendererLog} requesting the missing key '${String(writeKeys)}' be written to file '${element.key}'.`) : null;
i18nextElectronBackend.send(I18NChannels.writeFileRequest, {
keys: writeKeys,
filename: element.key,
data,
});
};
this.requestFileRead(element.key, anonymous);
}
}
// Reads a given translation file
requestFileRead(filename: any, callback: any) {
const { i18nextElectronBackend } = this.backendOptions;
// Save the callback for this request so we
// can execute once the ipcRender process returns
// with a value from the ipcMain process
const key = uuid();
this.readCallbacks[key] = {
callback,
};
// Send out the message to the ipcMain process
i18nextElectronBackend.send(I18NChannels.readFileRequest, {
key,
filename,
});
}
// Reads a given translation file
read(language: string, namespace: string, callback: any) {
const { loadPath } = this.backendOptions;
const filename = this.services.interpolator.interpolate(loadPath, {
lng: language,
ns: namespace,
});
this.requestFileRead(filename, (error: any, data: any) => {
if (error) {
return callback(error, false);
} // no retry
callback(null, data);
});
}
// Not implementing at this time
readMulti(languages: string[], namespaces: any, callback: any) {
throw 'Not implemented exception.';
}
// Writes a missing translation to file
create(languages: string[], namespace: string, key: string, fallbackValue: string) {
const { addPath } = this.backendOptions;
let filename;
languages = typeof languages === 'string' ? [languages] : languages;
// Create the missing translation for all languages
for (const language of languages) {
filename = this.services.interpolator.interpolate(addPath, {
lng: language,
ns: namespace,
});
// If we are currently writing missing translations from writeQueue,
// temporarily store the requests in writeQueueOverflow until we are
// done writing to file
if (this.useOverflow) {
this.writeQueueOverflow.push({
filename,
key,
fallbackValue,
});
} else {
this.writeQueue.push({
filename,
key,
fallbackValue,
});
}
}
// Fire up the timeout to process items to write
if (this.writeQueue.length > 0 && !this.useOverflow) {
// Clear out any existing timeout if we are still getting translations to write
if (typeof this.writeTimeout !== 'undefined') {
clearInterval(this.writeTimeout);
}
this.writeTimeout = setInterval(() => {
// Write writeQueue entries, then after,
// fill in any from the writeQueueOverflow
if (this.writeQueue.length > 0) {
this.write(cloneDeep(this.writeQueue));
}
this.writeQueue = cloneDeep(this.writeQueueOverflow);
this.writeQueueOverflow = [];
if (this.writeQueue.length === 0) {
// Clear timer
clearInterval(this.writeTimeout);
delete this.writeTimeout;
this.useOverflow = false;
}
}, 1000);
this.useOverflow = true;
}
}
}