Skip to main content

Internationalization (i18n) Implementation Guide

Our application supports multiple languages by utilizing a dynamic dictionary system. This system loads JSON dictionaries corresponding to the user's locale. This guide will walk you through the code implementation of our i18n setup.

Look how work the i18n middleware

Dictionary Loader Implementation​

The dictionary.ts file is responsible for importing the appropriate language dictionaries.

File: dictionary.ts​

import "server-only";
import type { Locale } from "i18n.config";

const dictionaries = {
en: () => import("../dictionaries/en.json").then((module) => module.default),
fr: () => import("../dictionaries/fr.json").then((module) => module.default),
nl: () => import("../dictionaries/nl.json").then((module) => module.default),
};

export const getDictionary = async (locale: Locale) => dictionaries[locale]?.() ?? dictionaries.en();

The getDictionary function is an asynchronous operation that retrieves the dictionary based on the provided locale.

Internationalization Configuration​

The i18n.config.ts file defines the default and supported locales.

File: i18n.config.ts​

export const i18n = {
defaultLocale: "en",
locales: ["en", "fr", "nl"],
} as const;

export type Locale = (typeof i18n)["locales"][number];

The Locale type is a union of supported locale strings, ensuring that we only use valid locale identifiers in our application.

Using getDictionary in Server Components​

The getDictionary function is intended for use in server components due to its asynchronous nature.

Example Server Component: RegisterPage​

import React from "react";
import { Logo, H2 } from "@pxs/ui";
import { FormUserAuthSignUp } from "@/app/[lang]/components/auth/form-user-auth-signup";
import LocaleSwitcher from "@/app/[lang]/components/locale-switcher";
import { getDictionary } from "@/lib/dictionary";
import { Locale } from "i18n.config";

export default async function RegisterPage({ params: { lang } }: { params: { lang: Locale } }) {
const { pages } = await getDictionary(lang);
// Component rendering using pages...
}

This server component fetches the dictionary asynchronously before rendering, ensuring that the user sees the content in their selected language.

Passing i18n Data to Client Components​

It is possible to pass the dictionary data to child components that are client-side.

Propagating i18n Data to Child Components​

<FormUserAuthSignUp registerTranslation={pages.register} lang={lang} />

Here, the FormUserAuthSignUp component receives the necessary translations as props, enabling the use of localized strings within the client component.

Conclusion​

The code snippets above illustrate how our application handles internationalization by dynamically loading language dictionaries and providing localized content. This setup allows us to ensure that all users receive a UI tailored to their language preferences, improving overall accessibility and user experience.

Client-Side Internationalization and Type Safety

In our application, we maintain type safety for internationalization through TypeScript. We define translation types corresponding to our language dictionaries, ensuring that our components receive the correct structure of translations.

Child Component: FormUserAuthSignUp​

The FormUserAuthSignUp component is a client-side component that utilizes the translations provided from the server component. It includes form handling and leverages the zod library for schema validation.

File: FormUserAuthSignUp.tsx​

"use client";

import * as React from "react";
import { useRouter } from "next/navigation";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";

import api from "@/lib/api";
import {
// ...UI components...
} from "@pxs/ui";
import RegisterTranslation from "@/lib/types/Registertranslation.type";
import { Locale } from "i18n.config";

// Define the component props, including registerTranslation for type safety
interface UserAuthFormProps extends React.HTMLAttributes<HTMLDivElement> {
// ...props definitions...
registerTranslation: RegisterTranslation;
lang: Locale;
}

// Schema for form validation using zod
const formSchema = z.object({
// ...schema definition...
});

export function FormUserAuthSignUp({ className, registerTranslation, lang, ...props }: UserAuthFormProps) {
// Form handling logic
// ...

return (
// JSX code utilizing the registerTranslation props for displaying text
// ...
);
}

This component expects registerTranslation of the type RegisterTranslation to ensure that the translations provided to the form are correctly typed.

Translation Type Definition: RegisterTranslation​

The RegisterTranslation type defines the shape of our translation object for the registration form, ensuring that all necessary strings are provided and are of the correct type.

File: RegisterTranslation.type.ts​

type RegisterTranslation = {
// ...translation keys and types...
};

export default RegisterTranslation;

By defining a type for our translations, we ensure that the component receives the correct structure of translation data, providing compile-time checks and enhancing the developer experience.

Language Dictionary Sample: en.json​

Here's a snippet from the en.json dictionary file that corresponds to the RegisterTranslation type.

{
"register": {
"registertitle": "Sign In to your account",
// ...other translation strings...
}
}

Each key in the dictionary corresponds to a property in the RegisterTranslation type, ensuring consistency between our localization files and the types used in our components.

Conclusion​

Implementing client-side internationalization with TypeScript types provides a robust solution for handling translations. It ensures that components like FormUserAuthSignUp receive correctly structured and typed translation data. This setup helps prevent runtime errors related to localization and improves maintainability and scalability of the internationalization system within our React application.