A compléter
L'internationalisation du site cartes.gouv.fr est réalisée avec la librairie i18nifty.
Les chaînes sont habituellement regroupées par un composant, ou parfois par un groupe de composants d'un même contexte. Elles sont stockées dans un fichier dédié à côté du ou des fichiers où elles sont utilisées. Elles sont ainsi en de très nombreux endroits du code source mais au plus proche de leur utilisation réelle.
Important
Convention de nommage : [nom du composant].locale.tsx
Voici un exemple d'utilisation avec le composant Contact
(pages/contact/Contact.tsx) :
-
Créer un fichier
Contact.locale.tsx
dans pages/contact -
Déclarer toutes les clés des textes dans ce fichier :
// Contact.tsx
import { JSX } from "react";
import { declareComponentKeys } from "../i18n/i18n";
const { i18n } = declareComponentKeys<
| "title"
| "mandatory_fields"
| "form.error_title"
| { K: "form.explanation"; P: { docsLinkProps: RegisteredLinkProps }; R: JSX.Element }
| "form.email_contact"
| "form.email_contact_hint"
| "form.email_contact_mandatory_error"
| "documentation"
| { K: "warning"; R: ReactNode }
| { K: "field_required"; P: { name: string } }
>()("Contact");
export type I18n = typeof i18n;
Note
- K : la clé (obligatoire)
- P : le type des paramètres de la fonction (optionnel,
undefined
par défaut) - R : le type de la valeur retournée par la fonction (optionnel,
string
par défaut)
Pour un texte simple, il suffit de déclarer la clé, par exemple "mandatory_fields" ou "title".
Pour retourner une chaîne mais avec des interpolations, il suffit de déclarer la clé et le type des paramètres, par exemple "field_required".
Pour retourner autre chose que du texte mais sans forcément avoir de paramètres, il suffit de déclarer la clé et le type de retour, par exemple "warning".
Le cas maxi, pour retourner autre chose que du texte avec des interpolations, il faut déclarer les trois K, P et R, par exemple "form.explanation"
- Ajouter le type retourné
I18n
dedeclareComponentKeys
àComponentKey
dans le fichieri18n/types.ts
(qui est une union de toutes les déclaration de clés) :
// i18n/types.ts
export type ComponentKey =
| import("../pages/contact/Contact").I18n
| import("../config/navItems").I18n
| ...
- Ajouter les textes traduits pour chaque langue (dans le fichier
.locale.tsx
)
// Contact.locale.tsx
import type { Translations } from "../../../i18n/types";
export const ContactFrTranslations: Translations<"fr">["Contact"] = {
title: "Nous écrire",
mandatory_fields: "Sauf mention contraire “(optionnel)” dans le label, tous les champs sont obligatoires.",
"form.error_title": "Votre message n'a pas pu être envoyé",
"form.explanation": ({ docsLinkProps }) => (
<>
{"Vous n'avez pas trouvé la réponse à votre question dans "}
<a {...docsLinkProps}>{"l'aide en ligne"}</a>
{" ? Vous souhaitez la configuration d'un espace de travail pour vos besoins ? Utilisez ce formulaire pour nous contacter."}
</>
),
"form.email_contact": "Votre email",
"form.email_contact_hint": "Format attendu : [email protected]",
"form.email_contact_mandatory_error": "Veuillez saisir une adresse email",
documentation: "Documentation",
warning: <Alert severity="error" title="Une erreur est survenue" className={fr.cx("fr-my-3w")} />,
field_required: ({ name }) => `Champ ${name} est obligatoire`,
};
export const ContactEnTranslations: Translations<"en">["Contact"] = {
title: "Contact us",
mandatory_fields: "All fields are mandatory unless label states “optional”",
"form.error_title": "Your message could not be sent",
"form.explanation": ({ docsLinkProps }) => (
<>
{"You did not find the answer to your question in "}
<a {...docsLinkProps}>{"our documentation"}</a>
{"? Do you want to configure a workspace for your needs? Use this form to contact us."}
</>
),
"form.email_contact": "Email",
"form.email_contact_hint": "Expected format: [email protected]",
"form.email_contact_mandatory_error": "Enter an email address",
documentation: undefined,
warning: undefined,
field_required: undefined,
};
Important
Garder une homogénéité des noms de ces variables : [nom du composant][langue]Translations
Note
Même si tous les textes ne sont pas traduits dans chaque langue, il faut quand-même déclarer la clé pour toutes les langues en la mettant à undefined
, comme c'est le cas de "documentation"
- Importer et ajouter les textes de traduction de chaque composant dans les fichiers d'agrégation par langue :
// i18n/languages/fr.tsx et i18n/languages/en.tsx
import type { Translations } from "../i18n/types";
import { navItemsFrTranslations } from "../../config/navItems";
import { ContactFrTranslations } from "../../pages/contact/Contact";
export const translations: Translations<"fr"> = {
Contact: ContactFrTranslations,
navItems: navItemsFrTranslations,
};
- Utiliser les textes de traduction :
// Contact.tsx
import { useTranslation } from "../i18n/i18n";
const Contact = () => {
const { t } = useTranslation({ Contact });
...
return (
<AppLayout documentTitle={t("title")}>
<div className={fr.cx("fr-grid-row")}>
<div className={fr.cx("fr-col-12", "fr-col-md-8")}>
<h1>{t("title")}</h1>
...
</div>
</div>
</AppLayout>
);
};