gristlabs_grist-core/app/server/localization.ts
Jarosław Sadziński 5219932a1f (core) i18
Summary:
Adding initial work for localization support.

Summary in https://grist.quip.com/OtZKA6RHdQ6T/Internationalization-and-Localization

Test Plan: Not yet

Reviewers: paulfitz

Reviewed By: paulfitz

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D3633
2022-09-29 18:02:09 +02:00

76 lines
2.7 KiB
TypeScript

import {lstatSync, readdirSync} from 'fs';
import {createInstance, i18n} from 'i18next';
import i18fsBackend from 'i18next-fs-backend';
import {LanguageDetector} from 'i18next-http-middleware';
import path from 'path';
export function setupLocale(appRoot: string): i18n {
// We are using custom instance and leave the global object intact.
const instance = createInstance();
// By default locales are located in the appRoot folder, unless the environment variable
// GRIST_LOCALES_DIR is set.
const localeDir = process.env.GRIST_LOCALES_DIR || path.join(appRoot, 'static', 'locales');
const supportedNamespaces: Set<string> = new Set();
const supportedLngs: Set<string> = new Set(readdirSync(localeDir).map((fileName) => {
const fullPath = path.join(localeDir, fileName);
const isDirectory = lstatSync(fullPath).isDirectory();
if (isDirectory) {
return "";
}
const baseName = path.basename(fileName, '.json');
const lang = baseName.split('.')[0];
const namespace = baseName.split('.')[1];
if (!lang || !namespace) {
throw new Error("Unrecognized resource file " + fileName);
}
supportedNamespaces.add(namespace);
return lang;
}).filter((lang) => lang));
if (!supportedLngs.has('en') || !supportedNamespaces.has('core')) {
throw new Error("Missing core English language file");
}
// Initialize localization filesystem plugin that will read the locale files from the localeDir.
instance.use(i18fsBackend);
// Initialize localization language detector plugin that will read the language from the request.
instance.use(LanguageDetector);
let errorDuringLoad: Error | undefined;
instance.init({
// Load all files synchronously.
initImmediate: false,
preload: [...supportedLngs],
supportedLngs: [...supportedLngs],
defaultNS: 'core',
ns: [...supportedNamespaces],
fallbackLng: 'en',
backend: {
loadPath: `${localeDir}/{{lng}}.{{ns}}.json`
},
}, (err: any) => {
if (err) {
errorDuringLoad = err;
}
}).catch((err: any) => {
// This should not happen, the promise should be resolved synchronously, without
// any errors reported.
console.error("i18next failed unexpectedly", err);
});
if (errorDuringLoad) {
throw errorDuringLoad;
}
return instance;
}
export function readLoadedLngs(instance?: i18n): readonly string[] {
if (!instance) { return []; }
return instance?.options.preload || ['en'];
}
export function readLoadedNamespaces(instance?: i18n): readonly string[] {
if (!instance) { return []; }
if (Array.isArray(instance?.options.ns)) {
return instance.options.ns;
}
return instance?.options.ns ? [instance.options.ns as string] : ['core'];
}