i18nextPlugin

A plugin to faciliate the integration of i18next in a modular application.

Reference

const plugin = new i18nextPlugin(runtime, supportedLanguages: [], fallbackLanguage, queryStringKey, options?: { detection? })

Parameters

  • runtime: A runtime instance.
  • supportedLanguages: An array of languages supported by the application.
  • fallbackLanguage: The language to default to if none of the detected user's languages match any supported language.
  • queryStringKey: The querystring parameter lookup when detecting the user's language.
  • options: An optional object literal of options:

Usage

Register the plugin

import { i18nextPlugin } from "@squide/i18next";
import { FireflyRuntime } from "@squide/firefly";

const runtime = new FireflyRuntime({
    plugins: [x => {
        const i18nextPlugin = new i18nextPlugin(x, ["en-US", "fr-CA"], "en-US", "language");
        i18nextPlugin.detectUserLanguage();

        return i18nextPlugin;
    }]
});

Retrieve the plugin instance

import { i18nextPlugin, i18nextPluginName } from "@squide/i18next";

const plugin = runtime.getPlugin(i18nextPluginName) as i18nextPlugin;

Prefer using getI18nextPlugin when possible
../geti18nextplugin/

Register a i18next instance

import { i18nextPlugin, i18nextPluginName } from "@squide/i18next";
import i18n from "./i18next";
import resourcesEn from "./locales/en.json";
import resourcesFr from "./locales/fr.json";

const instance = i18n.createInstance({
    resources: {
        "en-US": resourcesEn,
        "fr-CA": resourcesFr
    }
});

const plugin = runtime.getPlugin(i18nextPluginName) as i18nextPlugin;

plugin.registerInstance("an-instance-key", instance);

Lazy-load resources per language

Static resources land in the initial chunk for every supported language. To ship only the active language, provide a loadResources function when registering the instance. The plugin calls it with a language and expects a promise resolving to a map of namespace to resource bundle, the same shape as a single language entry of the i18next resources option:

import { getI18nextPlugin, type LoadResourcesFunction } from "@squide/i18next";
import i18n from "i18next";

export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
    const plugin = getI18nextPlugin(runtime);

    // Each dynamic import becomes a chunk, only the active language is downloaded.
    const loadResources: LoadResourcesFunction = async language => {
        const module = await import(`./locales/${language}.json`, { with: { type: "json" } });

        return module.default;
    };

    const instance = i18n.createInstance();

    instance.init({
        lng: plugin.currentLanguage,
        // A lazy instance must be initialized with an empty "resources" object: i18next then initializes
        // synchronously and creates the store that the plugin fills with the loaded bundles.
        resources: {}
    });

    plugin.registerInstance("an-instance-key", instance, {
        loadResources
    });
};

A lazy instance must be registered from a module's register function: once the modules are registered, registerInstance throws.

Lazy-load the resources
../../../integrations/setup-i18next/#lazy-load-the-resources

Retrieve a i18next instance

import { i18nextPlugin, i18nextPluginName } from "@squide/i18next";

const plugin = runtime.getPlugin(i18nextPluginName) as i18nextPlugin;

// If no instance match the specified key, an error will be thrown.
const instance = plugin.getInstance("an-instance-key");

Detect the user language

Whenever a plugin instance is created, the user's language should always be detected immediatly using the detectUserLanguage function.

import { i18nextPlugin } from "@squide/i18next";
import { FireflyRuntime } from "@squide/firefly";

const runtime = new FireflyRuntime({
    plugins: [x => {
        const i18nextPlugin = new i18nextPlugin(x, ["en-US", "fr-CA"], "en-US", "language");

        // If no detected languages match any of the supported languages, the fallback language will be applied.
        i18nextPlugin.detectUserLanguage();

        return i18nextPlugin;
    }]
});

Retrieve the current language

import { i18nextPlugin, i18nextPluginName } from "@squide/i18next";

const plugin = runtime.getPlugin(i18nextPluginName) as i18nextPlugin;

// If the language hasn't been changed nor detected before getting the current language, an error will be thrown.
const language = plugin.currentLanguage;

Change the current language

import { i18nextPlugin, i18nextPluginName } from "@squide/i18next";

const plugin = runtime.getPlugin(i18nextPluginName) as i18nextPlugin;

// If the language isn't included in the "supportedLanguages" array, an error will be thrown.
plugin.changeLanguage("fr-CA");

With lazy instances, the returned promise resolves once the resources of the new language are loaded and the language is switched. It rejects with an I18nextResourcesLoadError when a load fails.

Listen for language changes

import { i18nextPlugin, i18nextPluginName } from "@squide/i18next";

const plugin = runtime.getPlugin(i18nextPluginName) as i18nextPlugin;

const listener = () => {
    console.log("The language changed to", plugin.currentLanguage);
};

plugin.registerLanguageChangedListener(listener);

// When the listener is not needed anymore.
plugin.removeLanguageChangedListener(listener);

Handle a failed resources load

A failed load never blocks the rendering of the application: the affected instance renders the resource keys, as the plugin doesn't load the fallbackLng resources. Every failure is:

  • Logged with the runtime logger.
  • Dispatched on the event bus as an I18nextResourcesLoadFailedEvent, with a { key, language, error } payload.
  • Rejected from the changeLanguage promise as an I18nextResourcesLoadError, exposing the key of the instance, the language and the cause. The language is left unchanged.
import { I18nextResourcesLoadFailedEvent, isI18nextResourcesLoadError } from "@squide/i18next";
import { useEventBusListener } from "@squide/firefly";

useEventBusListener(I18nextResourcesLoadFailedEvent, ({ key, language, error }) => {
    console.error(`The "${language}" resources of the "${key}" instance failed to load.`, error);
});

try {
    await plugin.changeLanguage("fr-CA");
} catch (error: unknown) {
    if (isI18nextResourcesLoadError(error)) {
        console.error(`The "${error.language}" resources of the "${error.key}" instance failed to load.`, error.cause);
    }
}

Change the language detection order

By default, the detection of the user's language is done first from the specified URL querystring parameter (?language in this example), then from the user's navigator language settings. The detection order can be changed by specifying a new value for the order detection option:

const plugin = new i18nextPlugin(["en-US", "fr-CA"], "en-US", "language", {
    detection: {
        // Change the detection order to lookup the user browser default languages before the querystring parameter.
        order: ["navigator", "querystring"]
    }
});

Add an additional detection source

const plugin = new i18nextPlugin(["en-US", "fr-CA"], "en-US", "language", {
    detection: {
        order: [
            "querystring",
            // Will look for a language in the local storage before detecting the language from the user browser defaults.
            "localStorage",
            "navigator",
        ],
        lookupLocalStorage: "my-local-storage-key"
    }
});