Table des matières

Traductions

Documentation i18next Documentation react-i18next Documentation Intl

SmartCommon intègre i18next pour gérer les traductions de votre application.

Configuration rapide

Fichiers de traduction

Placez vos fichiers JSON dans public/locales/ :

public/
└── locales/
    ├── en.json
    └── fr.json

Structure d'un fichier

// public/locales/fr.json

{
  "common": {
    "save": "Enregistrer",
    "cancel": "Annuler",
    "delete": "Supprimer",
    "loading": "Chargement..."
  },
  "login": {
    "title": "Connexion",
    "email": "Adresse email",
    "password": "Mot de passe",
    "submit": "Se connecter",
    "error": "Identifiants incorrects"
  },
  "home": {
    "welcome": "Bienvenue {{name}} !",
    "items_count": "{{count}} élément",
    "items_count_plural": "{{count}} éléments"
  }
}
// public/locales/en.json

{
  "common": {
    "save": "Save",
    "cancel": "Cancel",
    "delete": "Delete",
    "loading": "Loading..."
  },
  "login": {
    "title": "Login",
    "email": "Email address",
    "password": "Password",
    "submit": "Sign in",
    "error": "Invalid credentials"
  },
  "home": {
    "welcome": "Welcome {{name}}!",
    "items_count": "{{count}} item",
    "items_count_plural": "{{count}} items"
  }
}

Configuration du Provider

// appConfig.js

export const config = {
  // Stocker la langue dans l'état global
  globalState: {
    reducers: {
      settings: { lng: "fr" },
    },
  },

  // Persister les settings
  storage: {
    local: ["settings"],
  },
};

Utilisation avec useIntl

Hook useIntl

SmartCommon expose le hook useIntl qui simplifie l'utilisation des traductions :

import { useIntl } from '@cap-rel/smartcommon';

const MyComponent = () => {
  const { t, lng, setLng, formatDate, formatNumber } = useIntl();

  return (
    <div>
      <h1>{t('login.title')}</h1>

      {/* Avec interpolation */}
      <p>{t('home.welcome', { name: 'Jean' })}</p>

      {/* Pluriel automatique */}
      <p>{t('home.items_count', { count: 5 })}</p>

      {/* Formatage de date */}
      <p>{formatDate(new Date())}</p>

      {/* Formatage de nombre */}
      <p>{formatNumber(1234.56)}</p>

      {/* Changer la langue */}
      <button onClick={() => setLng('en')}>English</button>
      <button onClick={() => setLng('fr')}>Français</button>
    </div>
  );
};

Propriétés de useIntl

Propriété Type Description
t function Fonction de traduction
lng string Langue actuelle
setLng function Changer la langue
formatDate function Formater une date selon la locale
formatNumber function Formater un nombre selon la locale
formatCurrency function Formater une devise

Exemple complet : Page de login

// src/components/pages/public/LoginPage/index.jsx

import { useApi, useGlobalStates, useForm, useNavigation, useIntl } from '@cap-rel/smartcommon';
import { Form, Input, Button } from '@cap-rel/smartcommon';
import { z } from 'zod';

export const LoginPage = () => {
  const { t } = useIntl();
  const api = useApi();
  const navigate = useNavigation();
  const [, setSession] = useGlobalStates('session');

  const schema = z.object({
    email: z.string().email(t('login.error_email')),
    password: z.string().min(1, t('login.error_password')),
  });

  const form = useForm({ schema });

  const handleSubmit = async (data) => {
    const response = await api.public.post('login', { json: data });

    if (response.success) {
      setSession(response.data);
      navigate('/');
    }
  };

  return (
    <div className="fixed inset-0 bg-white flex justify-center items-center p-10">
      <Form form={form} onSubmit={handleSubmit} className="flex flex-col gap-6 w-full max-w-sm">
        <h1 className="text-2xl font-bold text-center">
          {t('login.title')}
        </h1>

        <Input
          name="email"
          type="email"
          label={t('login.email')}
          placeholder={t('login.email_placeholder')}
        />

        <Input
          name="password"
          type="password"
          label={t('login.password')}
          placeholder="●●●●●●●●"
        />

        <Button type="submit" loading={form.formState.isSubmitting}>
          {t('login.submit')}
        </Button>
      </Form>
    </div>
  );
};

Sélecteur de langue

Composant simple

import { useIntl } from '@cap-rel/smartcommon';

const LanguageSelector = () => {
  const { lng, setLng } = useIntl();

  const languages = [
    { code: 'fr', label: 'Français', flag: '🇫🇷' },
    { code: 'en', label: 'English', flag: '🇬🇧' },
  ];

  return (
    <div className="flex gap-2">
      {languages.map((lang) => (
        <button
          key={lang.code}
          onClick={() => setLng(lang.code)}
          className={`px-3 py-1 rounded ${
            lng === lang.code
              ? 'bg-primary text-white'
              : 'bg-gray-200'
          }`}
        >
          {lang.flag} {lang.label}
        </button>
      ))}
    </div>
  );
};

Avec Select SmartCommon

import { useIntl } from '@cap-rel/smartcommon';
import { Select } from '@cap-rel/smartcommon';

const LanguageSelect = () => {
  const { lng, setLng } = useIntl();

  return (
    <Select
      value={lng}
      onChange={(e) => setLng(e.target.value)}
      options={[
        { value: 'fr', label: 'Français' },
        { value: 'en', label: 'English' },
        { value: 'es', label: 'Español' },
      ]}
    />
  );
};

Formatage avancé

Dates

import { useIntl } from '@cap-rel/smartcommon';

const DateDisplay = ({ date }) => {
  const { formatDate } = useIntl();

  return (
    <div>
      {/* Format par défaut */}
      <p>{formatDate(date)}</p>

      {/* Format personnalisé */}
      <p>{formatDate(date, { dateStyle: 'full' })}</p>
      <p>{formatDate(date, { dateStyle: 'long', timeStyle: 'short' })}</p>
    </div>
  );
};

Nombres et devises

import { useIntl } from '@cap-rel/smartcommon';

const PriceDisplay = ({ amount }) => {
  const { formatNumber, formatCurrency } = useIntl();

  return (
    <div>
      {/* Nombre simple */}
      <p>{formatNumber(1234567.89)}</p>
      {/* fr: 1 234 567,89 */}
      {/* en: 1,234,567.89 */}

      {/* Devise */}
      <p>{formatCurrency(amount, 'EUR')}</p>
      {/* fr: 1 234,56 € */}
      {/* en: €1,234.56 */}
    </div>
  );
};

Interpolation et pluriels

Interpolation

// fr.json
{
  "greeting": "Bonjour {{name}}, vous avez {{count}} messages"
}

// Utilisation
t('greeting', { name: 'Jean', count: 5 })
// "Bonjour Jean, vous avez 5 messages"

Pluriels

// fr.json
{
  "item": "{{count}} article",
  "item_plural": "{{count}} articles",
  "item_zero": "Aucun article"
}

// Utilisation
t('item', { count: 0 })  // "Aucun article"
t('item', { count: 1 })  // "1 article"
t('item', { count: 5 })  // "5 articles"

Organisation avancée

Namespaces

Pour les grandes applications, organisez par namespace :

public/
└── locales/
    ├── fr/
    │   ├── common.json
    │   ├── login.json
    │   └── dashboard.json
    └── en/
        ├── common.json
        ├── login.json
        └── dashboard.json
// Utilisation avec namespace
t('login:title')
t('dashboard:stats.revenue')

Prefix automatique

Pour éviter de répéter le namespace :

import { useTranslation } from 'react-i18next';

const LoginPage = () => {
  // Utiliser un préfixe
  const { t } = useTranslation('login');

  return (
    <div>
      <h1>{t('title')}</h1>           {/* login.title */}
      <p>{t('description')}</p>        {/* login.description */}
    </div>
  );
};

Configuration manuelle (sans SmartCommon)

Si vous n'utilisez pas SmartCommon, voici la configuration manuelle :

// src/i18n/index.js

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import HttpBackend from "i18next-http-backend";

i18n
  .use(HttpBackend)
  .use(initReactI18next)
  .init({
    interpolation: {
      escapeValue: false,
    },
    backend: {
      loadPath: "/locales/{{lng}}.json",
    },
  });

export { i18n };

Voir aussi