@seasketch/geoprocessing
Version:
Geoprocessing and reporting framework for SeaSketch 2.0
143 lines (131 loc) • 5.24 kB
text/typescript
import { createInstance } from "i18next";
import { initReactI18next } from "react-i18next";
import extraTerms from "./extraTerms.json" with { type: "json" };
import languages from "./languages.json" with { type: "json" };
const defaultLang = "en";
/**
* Returns an instance of i18n that lazy loads translations using passed langPath,
* defaults to './lang' which gp projects will have by default.
* JSON language modules are bundled using Vite's glob import.
*
* Each call to this function returns an independent instance allowing
* for multiple instances of i18n to be used in the same application.
* Because GP cand GP projects contain a component library with multiple
* report clients potentially loaded in storybook this is needed. In
* production there will typically be only one report client loaded at one time
* but this allows for the possibility of multiple. Note that i18nProvider
* must be used with createInstance to load translations.
* @returns i18n instance
*/
export function createI18nAsyncInstance(
/** i18n language paths relative to this directory */
options: {
/** path to main language translations. If baseLangPath present, then will merge with and override base resources */
langPath?: string;
/** path to extra base language translations (for gp projects), which langPath strings merge with and override */
baseLangPath?: string;
} = {},
) {
const { langPath = "./lang", baseLangPath = "./baseLang" } = options;
const instance = createInstance();
const baseLangModules = import.meta.glob(`./baseLang/*/translation.json`, {
query: "raw",
});
const langModules = import.meta.glob(`./lang/*/translation.json`, {
query: "raw",
});
instance
.use({
type: "backend",
read(
language: string,
namespace: string,
callback: (errorValue: unknown, translations: null | any) => void,
) {
const curLanguage = ((language: string) => {
// language switcher sends zh when zh-Hans is selected, but we need zh-Hans
if (language === "zh") return "zh-Hans";
return languages.find(
(curLang) => curLang.code.toLowerCase() === language.toLowerCase(),
)?.code;
})(language);
const isDefault =
language.toLowerCase() === "en" || /en-/i.test(language);
const langToLoad = isDefault ? defaultLang : curLanguage;
(async () => {
// Load translations
let baseLangResources = {};
try {
if (isDefault) {
// Do not load if default language, let components render strings directly
baseLangResources = {};
} else {
const baseLangToLoadPath = `${baseLangPath}/${langToLoad}/${namespace}.json`;
const curModule = baseLangModules[baseLangToLoadPath];
baseLangResources = JSON.parse(
((await curModule()) as unknown as any).default,
);
}
} catch {
console.info(`Warning: failed to find base lang resource.`);
}
// console.log("language baseLangResources", baseLangResources);
let langResources = {};
if (langPath !== undefined) {
try {
if (isDefault) {
// Do not load if default language, let components render strings directly
langResources = {};
} else {
const langToLoadPath = `${langPath}/${langToLoad}/${namespace}.json`;
console.log("language langToLoadPath", langToLoadPath);
const curModule = langModules[langToLoadPath];
langResources = JSON.parse(
((await curModule()) as unknown as any).default,
);
}
} catch {
console.info(`Warning: failed to find lang resource.`);
}
}
// console.log("language langResources", langResources);
// console.log("language extraTerms", extraTerms);
// Return merged translations
if (isDefault) {
// merge in extraTerms if english
callback(null, {
...baseLangResources,
...langResources,
...extraTerms,
});
} else {
// otherwise extra terms should already be translated in langResources
callback(null, {
...baseLangResources,
...langResources,
});
}
})();
},
})
.use(initReactI18next) // passes i18n down to react-i18next
.init({
debug: false,
lng: defaultLang,
fallbackLng: defaultLang,
cleanCode: true,
keySeparator: false,
nsSeparator: false,
interpolation: {
escapeValue: false,
},
react: {
// Using suspense for language content leads to a lot of flashing and
// re-rendering in unexpected places. It can even lead to weird issues
// like poorly placed popups as positioning is determined in a suspended
// state. Just avoiding for now.
useSuspense: false,
},
});
return instance;
}