terriajs
Version:
Geospatial data visualization platform.
190 lines (172 loc) • 6.94 kB
text/typescript
import i18next, { ReactOptions, TFunction } from "i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import HttpApi, { RequestCallback } from "i18next-http-backend";
import { initReactI18next } from "react-i18next";
import isDefined from "../Core/isDefined";
export interface I18nBackendOptions {
/**
* A few overrides that would be useful from a TerriaMap. The
* i18next-http-backend library is still in its early stages, so the
* documentation is sparse - the types are a quick glean from upstream
* `i18next-http-backend` source
* */
crossDomain?: boolean;
loadPath?: string;
parse?: (
data: any,
languages: string | [string],
namespaces: string
) => { [key: string]: any };
request?: (
options: any,
url: string,
payload: any,
callback: RequestCallback
) => void;
}
export interface I18nStartOptions {
backend?: I18nBackendOptions;
skipInit?: boolean; // skip initialising i18next. Used in CI
}
export interface LanguageConfiguration {
enabled: boolean;
debug: boolean;
react: ReactOptions;
languages: object;
fallbackLanguage: string;
changeLanguageOnStartWhen: string[];
lookupCookie?: string;
/** Base URL for override namespace translation files. If set, this makes up the base URL for translation override files. Should end in /
*
* For example, if `overridesBaseUrl = "test/path/"`, then the full path for translation override files will be `"test/path/{{lng}}.json"`
**/
overridesBaseUrl?: string;
}
const defaultLanguageConfiguration = {
enabled: false,
debug: false,
react: {
useSuspense: false
},
languages: {
en: "english"
},
fallbackLanguage: "en",
changeLanguageOnStartWhen: [
"querystring",
"localStorage",
"navigator",
"htmlTag"
],
lookupCookie: "i18next"
};
class Internationalization {
static initLanguage(
languageConfiguration: LanguageConfiguration | undefined,
/**
* i18nOptions is explicitly a separate option from `languageConfiguration`,
* as `languageConfiguration` can be serialised, but `i18nOptions` may have
* some functions that are passed in from a TerriaMap
*/
i18StartOptions: I18nStartOptions | undefined,
terriajsResourcesBaseUrl: string
): Promise<TFunction> {
const languageConfig = Object.assign(
defaultLanguageConfiguration,
languageConfiguration
);
/**
* initialization of the language with i18next
*
* @param {Object} languageConfiguration configuration read from config.json
* @param {Boolean} languageConfiguration.enabled is GUI language switching enabled
* @param {Object} languageConfiguration.languages the languages to be used, example `{en: "english"}
* @param {String} languageConfiguration.fallbackLanguage the language to be used on startup
* @param {Array} languageConfiguration.changeLanguageOnStartWhen
* @param {String} languageConfiguration.lookupCookie name of the cookie that handles i18n
*/
return i18next
.use(HttpApi)
.use(LanguageDetector)
.use(initReactI18next)
.init({
// use i18next-json-v3 as weblate still doesn't support v4
compatibilityJSON: "v3",
debug: languageConfig.debug,
react: languageConfig.react,
fallbackLng: languageConfig.fallbackLanguage,
// whitelist: Object.keys(languageConfig.languages),
// deprecated
supportedLngs: Object.keys(languageConfig.languages),
// to allow en-US when only en is on the whitelist - nonExplicitWhitelist must be set to true
/**
* for anyone else confused as I was, `nonExplicitSupportedLngs` &&
* `load: "languageOnly"` seems functionally equivalent but perhaps this
* will change in the future
*
* > also in my mind I conflated the config surrounding this a little
* > bit with `load: "languageOnly"` - but upon looking at the source in
* > i18next this confirms that they both intend to do similar things:
*
* https://github.com/i18next/i18next/blob/80a38100d21a7e7c1f9cb2acff5f709063027b9f/src/LanguageUtils.js#L78-L80
*
* */
nonExplicitSupportedLngs: true,
// to not look into a folder like /locals/en-US/... when en-US is detected, use load: "languageOnly" to avoid using Country-Code in path
load: "languageOnly",
// send not translated keys to endpoint
saveMissing: false,
// allow loading of internal trnaslation files and backend files
partialBundledLanguages: true,
/*
This setting adds a posibility for users to override translations using their own translation json file stored in
`TerriaMap/wwwroot/languages/{{lng}}/languageOverrides.json`
It will first look in defaultNS for translation and then check the fallbackNS
*/
ns: ["translation", "languageOverrides"],
defaultNS: "languageOverrides",
fallbackNS: "translation",
backend: Object.assign(
{
// Loads translation files from either a TerriaMap's languages assets or from TerriaJS' assets
// Always load "translation" namespace from TerriaJS assets, and load "languageOverrides" namespace from the TerriaMap
loadPath: function loadPath(
[_lng]: string[],
[namespace]: string[]
) {
if (namespace === "translation")
return `${terriajsResourcesBaseUrl}languages/{{lng}}/{{ns}}.json`;
// Apply languageConfig.overridesBaseUrl to path for "languageOverrides" namespace if defined
if (
namespace === "languageOverrides" &&
isDefined(languageConfig.overridesBaseUrl)
) {
return `${languageConfig.overridesBaseUrl}{{lng}}.json`;
}
return "languages/{{lng}}/{{ns}}.json";
},
crossDomain: false
},
{ ...i18StartOptions?.backend }
),
detection: {
// order and from where user language should be detected
order: languageConfig.changeLanguageOnStartWhen,
// keys or params to lookup language from
lookupQuerystring: "lng",
lookupCookie: languageConfig.lookupCookie,
lookupLocalStorage: "i18nextLng",
// cache user language on
caches: ["localStorage"],
excludeCacheFor: ["cimode"] // languages to not persist (cookie, localStorage)
// optional expire and domain for set cookie
// cookieMinutes: 10,
// cookieDomain: "myDomain",
},
interpolation: {
escapeValue: false // not needed for react as it escapes by default and not needed in node
}
});
}
}
export default Internationalization;