Chaînes partagées

Comment internationaliser les chaînes utilisées dans plusieurs composants et fichiers

Les chaînes partagées sont des valeurs textuelles utilisées à plusieurs endroits dans votre application — comme des libellés de navigation, des messages de formulaire ou des données de configuration. Plutôt que de dupliquer la logique de traduction partout, utilisez msg pour marquer les chaînes à traduire et useMessages pour les décoder.

Problème du contenu partagé

Prenons cette configuration de navigation utilisée dans l’ensemble de votre application :

// navData.ts
export const navData = [
  {
    label: 'Accueil',
    description: 'La page d\'accueil',
    href: '/'
  },
  {
    label: 'À propos', 
    description: 'Informations sur l\'entreprise',
    href: '/about'
  }
];

Pour internationaliser cela, vous devrez généralement :

  1. Le transformer en une fonction qui accepte une fonction de traduction
  2. Mettre à jour chaque usage pour appeler la fonction avec t
  3. Gérer la complexité à l’échelle de votre base de code

Cela ajoute de la maintenance et rend votre code moins lisible. La fonction msg résout ce problème en vous permettant de baliser les chaînes à traduire directement sur place, puis de les décoder au besoin.

Démarrage rapide

Utilisez msg pour baliser les chaînes et useMessages pour les décoder :

// navData.ts - Marquer les chaînes pour la traduction
import { msg } from 'gt-react';

export const navData = [
  {
    label: msg('Accueil'),
    description: msg('La page d\'accueil'), 
    href: '/'
  },
  {
    label: msg('À propos'),
    description: msg('Informations sur l\'entreprise'),
    href: '/about'
  }
];
// Utilisation du composant - Décoder les chaînes marquées
import { useMessages } from 'gt-react';
import { navData } from './navData';

function Navigation() {
  const m = useMessages();
  
  return (
    <nav>
      {navData.map((item) => (
        <a key={item.href} href={item.href} title={m(item.description)}>
          {m(item.label)}
        </a>
      ))}
    </nav>
  );
}

Fonctionnement des chaînes partagées

Le système de chaînes partagées fonctionne en deux phases :

  1. Phase de marquage : msg encode des chaînes avec des métadonnées de traduction
  2. Phase de décodage : useMessages décode et traduit les chaînes
// msg() encode la chaîne avec des métadonnées
const encoded = msg('Hello, world!');
console.log(encoded); // "Hello, world!:eyIkX2hhc2giOiJkMjA3MDliZGExNjNlZmM2In0="

// useMessages() décode et traduit
const m = useMessages();
const translated = m(encoded); // "Hello, world!" dans la langue de l'utilisateur

Les encoded strings générées par msg ne peuvent pas être utilisées directement : elles doivent être décodées avec useMessages.

Composants

Utilisez le hook useMessages :

import { useMessages } from 'gt-react';

const encodedString = msg('Bonjour le monde !');

function MyComponent() {
  const m = useMessages();
  return <div>{m(encodedString)}</div>;
}

Récupérer les chaînes d’origine avec decodeMsg

Il arrive que vous ayez besoin d’accéder à la chaîne d’origine sans traduction, par exemple pour la journalisation, le débogage ou des comparaisons. Utilisez decodeMsg pour extraire le texte d’origine :

import { decodeMsg } from 'gt-react';

const encoded = msg('Bonjour, le monde !');
const original = decodeMsg(encoded); // "Bonjour, le monde !" (original)
const translated = m(encoded); // "Bonjour, le monde !" (dans la langue de l'utilisateur)

// Utile pour les logs ou le débogage
console.log('Chaîne originale :', decodeMsg(encoded));
console.log('Chaîne traduite :', m(encoded));

Cas d’utilisation de decodeMsg

  • Développement et débogage : Consigner les chaînes d’origine pour le diagnostic
  • Gestion du secours : Utiliser le texte d’origine lorsque les traductions échouent
  • Comparaison de chaînes : Comparer à des valeurs d’origine connues
  • Analytique : Suivre l’utilisation des chaînes d’origine
// Exemple : Gestion du secours
function getDisplayText(encodedStr) {
  const m = useMessages();
  try {
    return m(encodedStr);
  } catch (error) {
    console.warn('Échec de la traduction, utilisation de l\'original :', decodeMsg(encodedStr));
    return decodeMsg(encodedStr);
  }
}

Utilisation des variables

Pour les chaînes contenant du contenu dynamique, utilisez des espaces réservés et passez des variables :

// Marquer une chaîne avec des variables
const items = 100;
export const pricing = [
  {
    name: 'De base',
    price: 100,
    description: msg('Le forfait de base inclut {items} éléments', { items })
  }
];
// Utilisation dans le composant
function PricingCard() {
  const m = useMessages();
  
  return (
    <div>
      <h3>{pricing[0].name}</h3>
      <p>{m(pricing[0].description)}</p>
    </div>
  );
}

Format de message ICU

Pour un formatage avancé, utilisez la syntaxe ICU :

const count = 10;
const message = msg('Il y a {count, plural, =0 {aucun article} =1 {un article} other {{count} articles}} dans le panier', { count });

Pour en savoir plus sur le format de message ICU, consultez la documentation Unicode.

Exemples

Configuration de la navigation

// config/navigation.ts
import { msg } from 'gt-react';

export const mainNav = [
  {
    label: msg('Accueil'),
    href: '/',
    icon: 'home'
  },
  {
    label: msg('Produits'),
    href: '/products', 
    icon: 'package'
  },
  {
    label: msg('À propos de nous'),
    href: '/about',
    icon: 'info'
  }
];

export const footerLinks = [
  {
    title: msg('Entreprise'),
    links: [
      { label: msg('À propos'), href: '/about' },
      { label: msg('Carrières'), href: '/careers' },
      { label: msg('Contact'), href: '/contact' }
    ]
  },
  {
    title: msg('Assistance'), 
    links: [
      { label: msg('Centre d\'aide'), href: '/help' },
      { label: msg('Documentation'), href: '/docs' },
      { label: msg('Référence de l\'API'), href: '/api' }
    ]
  }
];
// components/Navigation.tsx
import { useMessages } from 'gt-react';
import { mainNav } from '../config/navigation';

function Navigation() {
  const m = useMessages();
  
  return (
    <nav>
      {mainNav.map((item) => (
        <a key={item.href} href={item.href}>
          <Icon name={item.icon} />
          {m(item.label)}
        </a>
      ))}
    </nav>
  );
}

Configuration du formulaire

// config/forms.ts
import { msg } from 'gt-react';

export const formMessages = {
  placeholders: {
    email: msg('Saisissez votre adresse e-mail'),
    password: msg('Saisissez votre mot de passe'),
    message: msg('Tapez votre message ici...')
  },
  actions: {
    send: msg('Envoyer le message'),
    save: msg('Enregistrer les modifications'),
    cancel: msg('Annuler')
  },
  validation: {
    required: msg('Ce champ est obligatoire'),
    email: msg('Veuillez saisir une adresse e-mail valide'),
    minLength: msg('Doit contenir au moins {min} caractères', { min: 8 }),
    maxLength: msg('Ne peut pas dépasser {max} caractères', { max: 100 })
  },
  success: {
    saved: msg('Modifications enregistrées avec succès'),
    sent: msg('Message envoyé avec succès'),
    updated: msg('Profil mis à jour')
  },
  errors: {
    network: msg('Erreur réseau - veuillez réessayer'),
    server: msg('Erreur serveur - veuillez contacter le support'),
    timeout: msg('Délai d\'attente dépassé - veuillez réessayer')
  }
};
// components/ContactForm.tsx
import { useMessages } from 'gt-react';
import { formMessages } from '../config/forms';

function ContactForm() {
  const m = useMessages();
  const [errors, setErrors] = useState({});
  
  return (
    <form>
      <input 
        type="email"
        placeholder={m(formMessages.placeholders.email)}
        required
      />
      {errors.email && <span>{m(formMessages.validation.email)}</span>}
      
      <button type="submit">
        {m(formMessages.actions.send)}
      </button>
    </form>
  );
}

Génération de contenu dynamique

// utils/productData.ts
import { msg } from 'gt-react';

function mockProducts() {
  return [
    { name: 'iPhone 15', company: 'Apple', category: 'Électronique' },
    { name: 'Galaxy S24', company: 'Samsung', category: 'Électronique' }
  ];
}

export function getProductData() {
  const products = mockProducts();
  
  return products.map(product => ({
    ...product,
    description: msg('{name} est un produit {category} de {company}', {
      name: product.name,
      category: product.category,
      company: product.company
    })
  }));
}
// components/ProductList.tsx
import { useMessages } from 'gt-react';
import { getProductData } from '../utils/productData';

function ProductList() {
  const m = useMessages();
  const products = getProductData();
  
  return (
    <div>
      {products.map(product => (
        <div key={product.name}>
          <h3>{product.name}</h3>
          <p>{m(product.description)}</p>
        </div>
      ))}
    </div>
  );
}

Problèmes fréquents

Utiliser directement des chaînes encodées

N’utilisez jamais directement la sortie de msg :

// ❌ Incorrect - chaîne encodée utilisée directement
const encoded = msg('Hello, world!');
return <div>{encoded}</div>; // Affiche la chaîne encodée, pas la traduction

// ✅ Correct - décoder d'abord la chaîne  
const encoded = msg('Hello, world!');
const m = useMessages();
return <div>{m(encoded)}</div>; // Affiche la traduction appropriée

Contenu dynamique dans msg()

Les chaînes doivent être connues à la compilation :

// ❌ Incorrect - littéral de modèle dynamique
const name = 'John';
const message = msg(`Hello, ${name}`); // Erreur au moment de la compilation

// ✅ Correct - utiliser variables  
const name = 'John';
const message = msg('Hello, {name}', { name });

Oubli du décodage

Chaque chaîne msg doit être décodée :

// ❌ Décodage manquant
const config = {
  title: msg('Tableau de bord'),
  subtitle: msg('Bienvenue')
};

// Plus tard dans le composant - oublié de décoder
return <h1>{config.title}</h1>; // Affiche la chaîne encodée

// ✅ Correct - décoder lors de l'utilisation
const m = useMessages();
return <h1>{m(config.title)}</h1>; // Affiche le titre traduit

Prochaines étapes

Que pensez-vous de ce guide ?

Chaînes partagées