Internationaliser une mini-boutique

Un tutoriel React pratique qui internationalise une boutique simple à l'aide des composants, hooks et chaînes partagées de GT React

Lancez une petite « mini-boutique » entièrement locale et traduite — sans services externes, sans routage, sans frameworks d’UI. Vous utiliserez de bout en bout les fonctionnalités essentielles de GT React et verrez comment elles s’assemblent dans une interface simple et réaliste.

Prérequis : React, bases de JavaScript/TypeScript

Ce que vous allez construire

  • Une page « boutique » avec une grille de produits et un panier en mémoire simple
  • Un sélecteur de langue et des libellés de navigation partagés
  • Des nombres, des devises et une pluralisation correctement internationalisés
  • Optionnel : stockage local des traductions pour les builds de production

Liens utilisés dans ce tutoriel


Installez et intégrez votre application

Installez les packages et enveloppez votre application avec le provider.

npm i gt-react
npm i --save-dev gtx-cli
yarn add gt-react
yarn add --dev gtx-cli
bun add gt-react
bun add --dev gtx-cli
pnpm add gt-react
pnpm add --save-dev gtx-cli

Optionnel : projet de démarrage (Vite)

Si vous partez de zéro, générez une application Vite React + TypeScript, puis installez les packages GT :

npm create vite@latest mini-shop -- --template react-ts
cd mini-shop
npm i gt-react
npm i --save-dev gtx-cli

Ajoutez ensuite les fichiers des sections ci‑dessous (p. ex. src/main.tsx, src/App.tsx, src/components/*, src/data.ts, src/nav.ts).

Créez une configuration minimale du provider.

src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { GTProvider } from 'gt-react'; // Voir : /docs/react/api/components/gtprovider
import App from './App';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <GTProvider locales={["es", "fr"]}> {/* Activer l'espagnol et le français */}
      <App />
    </GTProvider>
  </StrictMode>
);

Ajoutez éventuellement un fichier gt.config.json maintenant (utile plus tard pour l’intégration continue et le stockage local) :

gt.config.json
{
  "defaultLocale": "en",
  "locales": ["es", "fr"]
}

Clés d’API de développement

Vous pouvez suivre ce tutoriel sans clés (la langue par défaut sera affichée). Pour voir les traductions en temps réel et tester le changement de langue en développement, ajoutez des clés de développement.

En savoir plus dans Production vs Development.

.env.local
VITE_GT_API_KEY="your-dev-api-key"
VITE_GT_PROJECT_ID="your-project-id"
.env.local
REACT_APP_GT_API_KEY="your-dev-api-key"
REACT_APP_GT_PROJECT_ID="your-project-id"

Données d’amorçage et structure de l’app

Nous allons coder en dur un petit tableau de produits et tout garder côté client. Pas de serveur, pas de routage.

src/data.ts
export type Product = {
  id: string;
  name: string;
  description: string;
  price: number;
  currency: 'USD' | 'EUR';
  inStock: boolean;
  addedAt: string; // ISO date string
};

export const products: Product[] = [
  {
    id: 'p-1',
    name: 'Casque sans fil',
    description: 'Design supra-auriculaire à réduction de bruit avec batterie 30h',
    price: 199.99,
    currency: 'USD',
    inStock: true,
    addedAt: new Date().toISOString()
  },
  {
    id: 'p-2',
    name: 'Mug de voyage',
    description: 'Acier inoxydable à double paroi isolante (12oz)',
    price: 24.5,
    currency: 'USD',
    inStock: false,
    addedAt: new Date().toISOString()
  }
];

Libellés de navigation partagés avec msg et useMessages

Utilisez msg pour baliser des chaînes partagées dans la configuration, puis décodez-les avec useMessages au moment du rendu.

src/nav.ts
import { msg } from 'gt-react'; // Voir : /docs/react/api/strings/msg

export const nav = [
  { label: msg('Accueil'), href: '#' },
  { label: msg('Produits'), href: '#products' },
  { label: msg('Panier'), href: '#cart' }
];
src/components/Header.tsx
import { LocaleSelector, T } from 'gt-react';
import { useMessages } from 'gt-react'; // Voir : /docs/react/api/strings/useMessages
import { nav } from '../nav';

export default function Header() {
  const m = useMessages();
  return (
    <header style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
      <T><h1>Mini Shop</h1></T> {/* Voir : /docs/react/api/components/t */}
      <nav style={{ display: 'flex', gap: 12 }}>
        {nav.map(item => (
          <a key={item.href} href={item.href} title={m(item.label)}>
            {m(item.label)}
          </a>
        ))}
      </nav>
      <div style={{ marginLeft: 'auto' }}>
        <LocaleSelector /> {/* Voir : /docs/react/api/components/localeSelector */}
      </div>
    </header>
  );
}

Fiches produit avec <T>, Variables, Branch et Currency

Utilisez <T> pour la traduction JSX. Enveloppez le contenu dynamique avec des composants de variables comme <Var>, <Num>, <Currency> et <DateTime>. Gérez l’état des stocks via <Branch>.

src/components/ProductCard.tsx
import { T, Var, Num, Currency, DateTime, Branch } from 'gt-react';
import type { Product } from '../data';

export default function ProductCard({ product, onAdd }: { product: Product; onAdd: () => void; }) {
  return (
    <div style={{ border: '1px solid #ddd', padding: 12, borderRadius: 8 }}>
      <T>
        <h3><Var>{product.name}</Var></h3>
        <p><Var>{product.description}</Var></p>
        <p>
          Prix : <Currency currency={product.currency}>{product.price}</Currency>
        </p>
        <p>
          Ajouté : <DateTime options={{ dateStyle: 'medium', timeStyle: 'short' }}>{product.addedAt}</DateTime>
        </p>
        <Branch
          branch={product.inStock}
          true={<p>En stock</p>}
          false={<p style={{ color: 'tomato' }}>Rupture de stock</p>}
        />
        <button onClick={onAdd} disabled={!product.inStock}>
          Ajouter au panier
        </button>
      </T>
    </div>
  );
}

Panier avec pluralisation et totaux

Utilisez <Plural> pour exprimer « X articles dans le panier » et <Currency> pour les totaux. À combiner avec <T>, <Var> et <Num>.

src/components/Cart.tsx
import { T, Plural, Var, Num, Currency } from 'gt-react';
import type { Product } from '../data';

export default function Cart({ items, onClear }: { items: Product[]; onClear: () => void; }) {
  const total = items.reduce((sum, p) => sum + p.price, 0);
  const itemCount = items.length;
  return (
    <div style={{ borderTop: '1px solid #eee', paddingTop: 12 }}>
      <T>
        <h2>Panier</h2>
        <Plural
          n={itemCount}
          zero={<p>Votre panier est vide</p>}
          one={<p>Vous avez <Num>{itemCount}</Num> article</p>}
          other={<p>Vous avez <Num>{itemCount}</Num> articles</p>}
        />
        {items.map((p) => (
          <p key={p.id}>
            <Var>{p.name}</Var> — <Currency currency={p.currency}>{p.price}</Currency>
          </p>
        ))}
        <p>
          Total : <Currency currency={items[0]?.currency || 'USD'}>{total}</Currency>
        </p>
        <button onClick={onClear} disabled={itemCount === 0}>Vider le panier</button>
      </T>
    </div>
  );
}

Attributs et espaces réservés avec useGT

Utilisez useGT pour traduire des chaînes simples, comme les placeholders de champs de saisie et les libellés ARIA.

src/components/Search.tsx
import { useGT } from 'gt-react';

export default function Search({ onQuery }: { onQuery: (q: string) => void; }) {
  const t = useGT();
  return (
    <input
      type="search"
      placeholder={t('Rechercher des produits...')}
      aria-label={t('Champ de recherche')}
      onChange={(e) => onQuery(e.target.value)}
      style={{ padding: 8, width: '100%', maxWidth: 320 }}
    />
  );
}

Rassembler le tout

Une application monopage avec panier en mémoire et un simple filtre de recherche.

src/App.tsx
import { useMemo, useState } from 'react';
import Header from './components/Header';
import Search from './components/Search';
import ProductCard from './components/ProductCard';
import Cart from './components/Cart';
import { products } from './data';

export default function App() {
  const [query, setQuery] = useState('');
  const [cart, setCart] = useState<string[]>([]);

  const filtered = useMemo(() => {
    const q = query.toLowerCase();
    return products.filter(p =>
      p.name.toLowerCase().includes(q) || p.description.toLowerCase().includes(q)
    );
  }, [query]);

  const items = products.filter(p => cart.includes(p.id));

  return (
    <div style={{ margin: '24px auto', maxWidth: 960, padding: 16 }}>
      <Header />
      <div style={{ margin: '16px 0' }}>
        <Search onQuery={setQuery} />
      </div>
      <section id="products" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 16 }}>
        {filtered.map(p => (
          <ProductCard
            key={p.id}
            product={p}
            onAdd={() => setCart(c => (p.inStock ? [...new Set([...c, p.id])] : c))}
          />
        ))}
      </section>
      <section id="cart" style={{ marginTop: 24 }}>
        <Cart
          items={items}
          onClear={() => setCart([])}
        />
      </section>
    </div>
  );
}

Exécuter en local

Ajoutez un script de développement simple à votre package.json, puis démarrez l’application.

package.json
{
  "scripts": {
    "dev": "vite"
  }
}

Exécutez :

npm run dev
package.json
{
  "scripts": {
    "start": "react-scripts start"
  }
}

Exécutez :

npm start

Ce que vous avez appris

Prochaines étapes

Que pensez-vous de ce guide ?

Internationaliser une mini-boutique