#
Setup i18next
Most of the applications that forms the Workleap platform are either already bilingual or will be in the future. To help feature teams with localized resources, Squide provides a native plugin designed to adapt the i18next library for federated applications.
The examples in this guide load all the resources from single localized resources files. For a real Workleap application, you probably want to spread the resources into multiple files and load the files with a i18next backend plugin.
#
Setup the host application
Let's start by configuring the host application. First, open a terminal at the root of the host application and install the following packages:
pnpm add @squide/i18next i18next i18next-browser-languagedetector react-i18next
yarn add @squide/i18next i18next i18next-browser-languagedetector react-i18next
npm install @squide/i18next i18next i18next-browser-languagedetector react-i18next
While you can use any package manager to develop an application with Squide, it is highly recommended that you use PNPM as the guides has been developed and tested with PNPM.
#
Register the i18nextPlugin
Then, update the host application boostrapping code to register an instance of the i18nextplugin with the FireflyRuntime instance:
import { createRoot } from "react-dom/client";
import { ConsoleLogger, RuntimeContext, FireflyRuntime, registerRemoteModules, type RemoteDefinition } from "@squide/firefly";
import { App } from "./App.tsx";
import { registerHost } from "./register.tsx";
import { registerShell } from "@sample/shell";
import { i18nextPlugin } from "@sample/i18next";
const Remotes: RemoteDefinition[] = [
{ url: name: "remote1" }
];
// In this example:
// - The supported languages are "en-US" and "fr-CA"
// - The fallback language is "en-US"
// - The URL querystring parameter to detect the current language is "language"
const i18nextPlugin = new i18nextPlugin(["en-US", "fr-CA"], "en-US", "language");
// Always detect the user language early on.
i18nextPlugin.detectUserLanguage();
const runtime = new FireflyRuntime({
plugins: [i18nextPlugin]
loggers: [new ConsoleLogger()]
});
await registerLocalModules([registerShell, registerHost], runtime);
await registerRemoteModules(Remotes, runtime);
const root = createRoot(document.getElementById("root")!);
root.render(
<RuntimeContext.Provider value={runtime}>
<App />
</RuntimeContext.Provider>
);
In the previous code sample, upon creating an i18nextPlugin
instance, the user language is automatically detected using the plugin.detectUserLanguage
function. Applications should always detect the user language at bootstrapping, even if the current language is expected to be overriden by a preferred language setting once the user session has been loaded.
#
Define the localized resources
Next, create the localized resource files for the en-US
and fr-CA
locales:
{
"HomePage": {
"bodyText": "Hello from the Home page!"
}
}
{
"HomePage": {
"bodyText": "Bonjour depuis la page d'accueil!"
}
}
#
Register an i18next instance
Then, update the local module register function to create and register an instance of i18next
with the i18nextPlugin
plugin instance:
import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { getI18nextPlugin } from "@squide/i18next";
import { HomePage } from "./HomePage.tsx";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import resourcesEn from "./locales/en-US/resources.json";
import resourcesFr from "./locales/fr-CA/resources.json";
export const registerHost: ModuleRegisterFunction<FireflyRuntime> = runtime => {
const i18nextPlugin = getI18nextPlugin(runtime);
const i18nextInstance = i18n
.createInstance()
.use(initReactI18next);
i18nextInstance.init({
// Create the instance with the language that has been detected earlier in the bootstrapping code.
lng: i18nextPlugin.currentLanguage,
resources: {
"en-US": resourcesEn,
"fr-CA": resourcesFr
}
});
// Will associate the instance with the "host" key.
i18nextPlugin.registerInstance("host", i18nextInstance);
// --------
runtime.registerRoute({
index: true,
element: <HomePage />
});
};
In the previous code sample, notice that the i18next
instance has been initialized with the current language of the i18nextPlugin
instance by providing the lng
option. If the user language has been detected during bootstrapping, the i18next
instance will be initialized with the user language which has been deduced from either a ?language
querystring parameter or the user navigator language settings. Otherwise, the application instance will be initialized with the fallback language.
#
Localize the home page resources
Then, update the HomePage
component to use the newly created localized resource:
import { useI18nextInstance } from "@squide/i18next";
import { useTranslation } from "react-i18next";
export function HomePage() {
// Must be the same instance key that has been used to register the i18next instance previously in the "register" function.
const i18nextInstance = useI18nextInstance("host");
const { t } = useTranslation("HomePage", { i18n: i18nextInstance });
return (
<div>{t("bodyText")}</div>
);
}
#
Update the webpack configurations
Finally, update the webpack development and build configurations to activate the i18next
feature:
// @ts-check
import { defineDevHostConfig } from "@squide/firefly-webpack-configs";
import { swcConfig } from "./swc.dev.js";
export default defineDevHostConfig(swcConfig, 8080, [], {
features: {
i18next: true
},
sharedDependencies: {
"@sample/shared": {
singleton: true,
eager: true
}
}
});
// @ts-check
import { defineBuildHostConfig } from "@squide/firefly-webpack-configs";
import { swcConfig } from "./swc.build.js";
export default defineBuildHostConfig(swcConfig, [], {
features: {
i18next: true
},
sharedDependencies: {
"@sample/shared": {
singleton: true,
eager: true
}
}
});
#
Setup a remote module
First, open a terminal at the root of the remote module application and install the following packages:
pnpm add @squide/i18next i18next i18next-browser-languagedetector react-i18next
yarn add @squide/i18next i18next i18next-browser-languagedetector react-i18next
npm install @squide/i18next i18next i18next-browser-languagedetector react-i18next
#
Define the localized resources
Then, create the localized resource files for the en-US
and fr-CA
locales:
{
"navigationItems": {
"page": "Remote/Page - en-US"
},
"Page": {
"bodyText": "Hello from Remote/Page!"
}
}
{
"navigationItems": {
"page": "Remote/Page - fr-CA"
},
"Page": {
"bodyText": "Bonjour depuis Remote/Page!"
}
}
Notice that this time, a standard navigationItems
namespace has been added to the resource files. The resources in the navigationItems
namespace will be used later on to localize the navigation items labels.
#
Register an i18next instance
Then, update the local module register function to create and register an instance of i18next
with the i18nextPlugin
plugin instance:
import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { getI18nextPlugin } from "@squide/i18next";
import { Page } from "./Page.tsx";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import resourcesEn from "./locales/en-US/resources.json";
import resourcesFr from "./locales/fr-CA/resources.json";
export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
const i18nextPlugin = getI18nextPlugin(runtime);
const i18nextInstance = i18n
.createInstance()
.use(initReactI18next);
i18nextInstance.init({
// Create the instance with the language that has been detected earlier in the host application bootstrapping code.
lng: i18nextPlugin.currentLanguage,
resources: {
"en-US": resourcesEn,
"fr-CA": resourcesFr
}
});
// Will associate the instance with the "remote-module" key.
i18nextPlugin.registerInstance("remote-module", i18nextInstance);
// --------
runtime.registerRoute({
path: "/remote/page",
element: <Page />
});
runtime.registerNavigationItem({
$label: "Remote/Page",
to: "/remote/page"
});
}
#
Localize the navigation item labels
Then, localize the navigation items labels using the I18nextNavigationItemLabel component. Since for this example, the resources are in the navigationItems
namespace, there's no need to specify a namespace
property on the components as it will be inferred:
import type { ModuleRegisterFunction, FireflyRuntime } from "@squide/firefly";
import { getI18nextPlugin, I18nextNavigationItemLabel } from "@squide/i18next";
import { Page } from "./Page.tsx";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import resourcesEn from "./locales/en-US/resources.json";
import resourcesFr from "./locales/fr-CA/resources.json";
export const register: ModuleRegisterFunction<FireflyRuntime> = runtime => {
const i18nextPlugin = getI18nextPlugin(runtime);
const i18nextInstance = i18n
.createInstance()
.use(initReactI18next);
i18nextInstance.init({
// Create the instance with the language that has been detected earlier in the host application bootstrapping code.
lng: i18nextPlugin.currentLanguage,
load: "currentOnly",
resources: {
"en-US": resourcesEn,
"fr-CA": resourcesFr
}
});
// Will associate the instance with the "remote-module" key.
i18nextPlugin.registerInstance("remote-module", i18nextInstance);
// --------
runtime.registerRoute({
path: "/remote/page",
element: <Page />
});
runtime.registerNavigationItem({
$label: <I18nextNavigationItemLabel i18next={i18nextInstance} resourceKey="page" />,
to: "/remote/page"
});
}
#
Localize the page resources
Then, update the HomePage
component to use the newly created localized resource:
import { useI18nextInstance } from "@squide/i18next";
import { useTranslation } from "react-i18next";
export function Page() {
// Must be the same instance key that has been used to register the i18next instance previously in the "register" function.
const i18nextInstance = useI18nextInstance("remote-module");
const { t } = useTranslation("Page", { i18n: useI18nextInstance });
return (
<div>{t("bodyText")}</div>
);
}
#
Update the webpack configurations
Finally, update the webpack development and build configurations to activate the i18next
feature:
// @ts-check
import { defineDevRemoteModuleConfig } from "@squide/firefly-webpack-configs";
import { swcConfig } from "./swc.dev.js";
export default defineDevRemoteModuleConfig(swcConfig, "remote1", 8081, {
features: {
i18next: true
},
sharedDependencies: {
"@sample/shared": {
singleton: true
}
}
});
// @ts-check
import { defineBuildRemoteModuleConfig } from "@squide/firefly-webpack-configs";
import { swcConfig } from "./swc.build.js";
export default defineBuildRemoteModuleConfig(swcConfig, "remote1", {
features: {
i18next: true
},
sharedDependencies: {
"@sample/shared": {
singleton: true
}
}
});
#
Setup a local module
Follow the same steps as for a
#
Integrate a backend language setting
For many applications, the displayed language is expected to be derived from an application specific user "preferred language" setting stored in a database on the backend. Therefore, the frontend remains unaware of this setting value until the user session is loaded.
Hence, the strategy to select the displayed language should be as follow:
Utilize the language detected at bootstrapping for anonymous users (with the
detectUserLanguage
function).Upon user authentication and session loading, if a "preferred language" setting is available from the session data, update the displayed language to reflect this preference.
To implement this strategy, use the useChangeLanguage hook and the onLoadProtectedData handler of the AppRouter component:
import { AppRouter } from "@squide/firefly";
import { useChangeLanguage, useI18nextInstance } from "@squide/i18next";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
async function fetchProtectedData(changeLanguage: (language: string) => void, setIsSessionLoaded: (isLoaded: boolean) => void, signal: AbortSignal) {
const response = await fetch("/api/session", {
signal
});
if (response.ok) {
const session = await response.json();
// When the session has been retrieved, change the displayed language to match
// the preferred language setting.
changeLanguage(session.preferredLanguage);
setIsSessionLoaded(true);
}
}
export function App() {
const [isSessionLoaded, setIsSessionLoaded] = useState(false);
const i18nextInstance = useI18nextInstance("host");
const { t } = useTranslation("App", { i18n: useI18nextInstance });
const changeLanguage = useChangeLanguage();
const handleLoadProtectedData = useCallback((signal: AbortSignal) => {
return fetchProtectedData(changeLanguage, setIsSessionLoaded, signal);
}, [changeLanguage]);
return (
<AppRouter
fallbackElement={<div>{t("loadingMessage")}</div>}
errorElement={<div>{t("errorMessage")}</div>}
waitForMsw={false}
onLoadProtectedData={handleLoadProtectedData}
isProtectedDataLoaded={isSessionLoaded}
>
{(routes, providerProps) => (
<RouterProvider router={createBrowserRouter(routes)} {...providerProps} />
)}
</AppRouter>
);
}
#
Use the Trans component
The Trans component is valuable for scenarios that involve interpolation to render a localized resource. To use the Trans
component with Squide, pair the component with an i18next
instance retrieved from useI18nextInstance hook:
import { useI18nextInstance } from "@squide/i18next";
import { Trans, useTranslation } from "react-i18next";
const instance = useI18nextInstance("an-instance-key");
const { t } = useTranslation("a-namespace", { i18n: instance });
return (
<Trans
i18n={instance}
i18nKey="a-key"
t={t}
/>
);
The Trans
component can also be used without the t
function by including a namespace to the i18nKey
property value:
import { useI18nextInstance } from "@squide/i18next";
import { Trans, useTranslation } from "react-i18next";
const instance = useI18nextInstance("an-instance-key");
return (
<Trans
i18n={instance}
i18nKey="a-namespace:a-key"
/>
);
#
Try it 🚀
Start the development servers using the dev
script. The homepage and the navigation items should render the english (en-US
) resources. Then append ?language=fr-CA
to the URL. The homepage and the navigation items should now render the french (fr-CA
) resources.
#
Troubleshoot issues
If you are experiencing issues with this guide:
- Open the DevTools console. You'll find a log entry for each
i18next
instance that is being registered and another log everytime the language is changed:[squide] Registered a new i18next instance with key "remote-module": ...
[squide] The language has been changed to "fr-CA".
- Refer to a working example on GitHub.
- Refer to the troubleshooting page.