Internationaliser une mini‑boutique

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

Faites tourner une petite « mini‑boutique » entièrement locale et traduite — sans services externes, sans routage, sans frameworks UI. Vous utiliserez de bout en bout les fonctionnalités React de GT 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 « boutique » monopage avec une grille de produits et un panier en mémoire simple
  • Sélecteur de langue et libellés de navigation partagés
  • Nombres, devise et pluralisation correctement internationalisés
  • Optionnel : stockage local des traductions pour les builds de production

Liens utilisés dans ce tutoriel


Installez et enveloppez 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 dans les 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"]}> {/* Active l'espagnol et le français */}
      <App />
    </GTProvider>
  </StrictMode>
);

Vous pouvez éventuellement ajouter un gt.config.json dès maintenant (utile plus tard pour la CI 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 utilisée). Pour afficher 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’application

Nous allons coder en dur un petit tableau de produits et tout conserver 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 circum-auriculaire à réduction de bruit avec autonomie de 30h',
    price: 199.99,
    currency: 'USD',
    inStock: true,
    addedAt: new Date().toISOString()
  },
  {
    id: 'p-2',
    name: 'Mug isotherme',
    description: 'Acier inoxydable à double paroi isolante (355 ml)',
    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écoder ces chaînes via 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 Devise

Utilisez <T> pour la traduction JSX. Encapsulez le contenu dynamique à l’aide de 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é le : <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 gestion du pluriel 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 espaces réservés des 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 }}
    />
  );
}

Assembler le tout

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

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

Comment trouvez-vous ce guide ?