Comment Construire une Webapp Moderne i18n Propulsée par l'IA en 2026

Guide complet pour créer des webapps multilingues avec Lingui et l'IA. Gérez automatiquement 17 langues grâce à Next.js, Claude et T3 Turbo.

Comment Construire une Webapp Moderne i18n Propulsée par l'IA en 2026
Feng LiuFeng Liu
24 janvier 2026

Bon, il faut qu'on parle d'i18n en 2026.

La plupart des tutoriels vous diront de traduire vos chaînes manuellement, d'embaucher des traducteurs ou d'utiliser une API Google Translate bancale. Mais voilà le truc : vous vivez à l'ère de Claude Sonnet 4.5. Pourquoi traduisez-vous encore comme en 2019 ?

Je vais vous montrer comment nous avons construit une webapp en production qui parle couramment 17 langues, en utilisant une architecture i18n en deux parties qui tient vraiment la route :

  1. Lingui pour l'extraction, la compilation et la magie du runtime
  2. Un package i18n personnalisé propulsé par des LLM pour des traductions automatisées et contextuelles

Notre stack ? Create T3 Turbo avec Next.js, tRPC, Drizzle, Postgres, Tailwind et le AI SDK. Si vous n'utilisez pas ça en 2026, nous devons avoir une conversation différente.

Allez, on construit.


Le problème avec l'i18n traditionnel

Les workflows i18n traditionnels ressemblent à ça :

# Extraire les chaînes
$ lingui extract

# ??? Obtenir les traductions d'une manière ou d'une autre ???
# (embaucher des traducteurs, utiliser des services douteux, pleurer un bon coup)

# Compiler
$ lingui compile

Cette étape intermédiaire ? C'est un cauchemar. Vous êtes soit en train de :

  • Payer des fortunes pour des traducteurs humains (lent, cher)
  • Utiliser des API de traduction basiques (aveugles au contexte, rendu robotique)
  • Traduire manuellement (ne passe pas à l'échelle)

On fait mieux que ça.


L'architecture en deux parties

Voici notre configuration :

┌─────────────────────────────────────────────┐
│  Next.js App (Intégration Lingui)          │
│  ├─ Extraction des chaînes avec macros      │
│  ├─ Composants Trans/t dans votre code      │
│  └─ i18n Runtime avec catalogues compilés   │
└─────────────────────────────────────────────┘
              ↓ génère des fichiers .po
┌─────────────────────────────────────────────┐
│  @acme/i18n Package (Traduction LLM)      │
│  ├─ Lit les fichiers .po                    │
│  ├─ Traduction par lots avec Claude/GPT-5   │
│  ├─ Contextuel, spécifique au produit       │
│  └─ Écrit les fichiers .po traduits         │
└─────────────────────────────────────────────┘
              ↓ compile vers TypeScript
┌─────────────────────────────────────────────┐
│  Catalogues de messages compilés            │
│  └─ Traductions runtime rapides et typées   │
└─────────────────────────────────────────────┘

La partie 1 (Lingui) gère l'expérience développeur (DX). La partie 2 (Package i18n personnalisé) gère la magie de la traduction.

Plongeons dans les détails.


Technical flow diagram: web UI → AI translation cloud → database, three tiers connected by arrows

Partie 1 : Configurer Lingui dans Next.js

Installation

Dans votre monorepo T3 Turbo :

# Dans apps/nextjs
pnpm add @lingui/core @lingui/react @lingui/macro
pnpm add -D @lingui/cli @lingui/swc-plugin

Configuration Lingui

Créez apps/nextjs/lingui.config.ts :

import type { LinguiConfig } from "@lingui/conf";

const config: LinguiConfig = {
  locales: [
    "en", "zh_CN", "zh_TW", "ja", "ko",
    "de", "fr", "es", "pt", "ar", "it",
    "ru", "tr", "th", "id", "vi", "hi"
  ],
  sourceLocale: "en",
  fallbackLocales: {
    default: "en"
  },
  catalogs: [
    {
      path: "<rootDir>/src/locales/{locale}/messages",
      include: ["src"],
    },
  ],
};

export default config;

17 langues prêtes à l'emploi. Et pourquoi pas ?

Intégration Next.js

Mettez à jour next.config.js pour utiliser le plugin SWC de Lingui :

const linguiConfig = require("./lingui.config");

module.exports = {
  experimental: {
    swcPlugins: [
      [
        "@lingui/swc-plugin",
        {
          // Cela rend vos builds plus rapides
        },
      ],
    ],
  },
  // ... reste de votre config
};

Configuration Côté Serveur

Créez src/utils/i18n/appRouterI18n.ts :

import { setupI18n } from "@lingui/core";
import { allMessages } from "./initLingui";

const locales = ["en", "zh_CN", "zh_TW", /* ... */] as const;

const instances = new Map<string, ReturnType<typeof setupI18n>>();

// Pré-création des instances i18n pour toutes les locales
locales.forEach((locale) => {
  const i18n = setupI18n({
    locale,
    messages: { [locale]: allMessages[locale] },
  });
  instances.set(locale, i18n);
});

export function getI18nInstance(locale: string) {
  return instances.get(locale) ?? instances.get("en")!;
}

Pourquoi ? Les Server Components n'ont pas accès au Contexte React. Ceci vous donne des traductions côté serveur.

Provider Côté Client

Créez src/providers/LinguiClientProvider.tsx :

"use client";

import { I18nProvider } from "@lingui/react";
import { setupI18n } from "@lingui/core";
import { useEffect, useState } from "react";

export function LinguiClientProvider({
  children,
  locale,
  messages
}: {
  children: React.ReactNode;
  locale: string;
  messages: any;
}) {
  const [i18n] = useState(() =>
    setupI18n({
      locale,
      messages: { [locale]: messages },
    })
  );

  useEffect(() => {
    i18n.load(locale, messages);
    i18n.activate(locale);
  }, [locale, messages, i18n]);

  return <I18nProvider i18n={i18n}>{children}</I18nProvider>;
}

Enveloppez votre app dans layout.tsx :

import { LinguiClientProvider } from "@/providers/LinguiClientProvider";
import { getLocale } from "@/utils/i18n/localeDetection";
import { allMessages } from "@/utils/i18n/initLingui";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const locale = getLocale();

  return (
    <html lang={locale}>
      <body>
        <LinguiClientProvider locale={locale} messages={allMessages[locale]}>
          {children}
        </LinguiClientProvider>
      </body>
    </html>
  );
}

Utiliser les traductions dans votre code

Dans les Server Components :

import { msg } from "@lingui/core/macro";
import { getI18nInstance } from "@/utils/i18n/appRouterI18n";

export async function generateMetadata({ params }) {
  const locale = getLocale();
  const i18n = getI18nInstance(locale);

  return {
    title: i18n._(msg`Pricing Plans | acme`),
    description: i18n._(msg`Choose the perfect plan for you`),
  };
}

Dans les Client Components :

"use client";

import { Trans, useLingui } from "@lingui/react/macro";

export function PricingCard() {
  const { t } = useLingui();

  return (
    <div>
      <h1><Trans>Pricing Plans</Trans></h1>
      <p>{t`Ultimate entertainment experience`}</p>

      {/* Avec variables */}
      <p>{t`${credits} credits remaining`}</p>
    </div>
  );
}

La syntaxe macro est la CLÉ. Lingui extrait ces éléments au moment du build.


Partie 2 : Le package de traduction propulsé par l'IA

C'est là que ça devient croustillant.

Structure du Package

Créez packages/i18n/ :

packages/i18n/
├── package.json
├── src/
│   ├── translateWithLLM.ts      # Cœur de la traduction LLM
│   ├── enhanceTranslations.ts   # Processeur par lots
│   └── utils.ts                  # Utilitaires

package.json

{
  "name": "@acme/i18n",
  "version": "0.1.0",
  "dependencies": {
    "@acme/ai": "workspace:*",
    "openai": "^4.77.3",
    "pofile": "^1.1.4",
    "zod": "^3.23.8"
  }
}

Le moteur de traduction LLM

Voici la recette secrète - translateWithLLM.ts :

import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { z } from "zod";

const translationSchema = z.object({
  translations: z.array(
    z.object({
      msgid: z.string(),
      msgstr: z.string(),
    })
  ),
});

export async function translateWithLLM(
  messages: Array<{ msgid: string; msgstr: string }>,
  targetLocale: string,
  options?: { model?: string }
) {
  const prompt = `You are a professional translator for acme, an AI-powered creative platform.

Translate the following strings from English to ${getLanguageName(targetLocale)}.

CONTEXT:
- acme is a platform for AI chat, image generation, and creative content
- Keep brand names unchanged (acme, Claude, etc.)
- Preserve HTML tags, variables like {count}, and placeholders
- Adapt culturally where appropriate
- Maintain tone: friendly, creative, engaging

STRINGS TO TRANSLATE:
${JSON.stringify(messages, null, 2)}

Return a JSON object with this structure:
{
  "translations": [
    { "msgid": "original", "msgstr": "translation" },
    ...
  ]
}`;

  const result = await generateText({
    model: openai(options?.model ?? "gpt-4o"),
    prompt,
    temperature: 0.3, // Plus bas = plus cohérent
  });

  const parsed = translationSchema.parse(JSON.parse(result.text));
  return parsed.translations;
}

function getLanguageName(locale: string): string {
  const names: Record<string, string> = {
    zh_CN: "Simplified Chinese",
    zh_TW: "Traditional Chinese",
    ja: "Japanese",
    ko: "Korean",
    de: "German",
    fr: "French",
    es: "Spanish",
    pt: "Portuguese",
    ar: "Arabic",
    // ... etc
  };
  return names[locale] ?? locale;
}

Pourquoi ça marche :

  • Conscient du contexte : Le LLM sait ce qu'est "acme".
  • Sortie structurée : Le schéma Zod garantit un JSON valide.
  • Température basse : Traductions cohérentes.
  • Préservation du format : Le HTML et les variables restent intacts.

Processeur de traduction par lots

Créez enhanceTranslations.ts :

import fs from "fs";
import path from "path";
import pofile from "pofile";
import { translateWithLLM } from "./translateWithLLM";

const BATCH_SIZE = 30; // Traduire 30 chaînes à la fois
const DELAY_MS = 1000; // Rate limiting

export async function enhanceTranslations(
  locale: string,
  catalogPath: string
) {
  const poPath = path.join(catalogPath, locale, "messages.po");
  const po = pofile.parse(fs.readFileSync(poPath, "utf-8"));

  // Trouver les éléments non traduits
  const untranslated = po.items.filter(
    (item) => item.msgid && (!item.msgstr || item.msgstr[0] === "")
  );

  if (untranslated.length === 0) {
    console.log(`✓ ${locale}: All strings translated`);
    return;
  }

  console.log(`Translating ${untranslated.length} strings for ${locale}...`);

  // Traiter par lots
  for (let i = 0; i < untranslated.length; i += BATCH_SIZE) {
    const batch = untranslated.slice(i, i + BATCH_SIZE);
    const messages = batch.map((item) => ({
      msgid: item.msgid,
      msgstr: item.msgstr?.[0] ?? "",
    }));

    try {
      const translations = await translateWithLLM(messages, locale);

      // Mettre à jour le fichier PO
      translations.forEach((translation, index) => {
        const item = batch[index];
        if (item) {
          item.msgstr = [translation.msgstr];
        }
      });

      console.log(`  ${i + batch.length}/${untranslated.length} translated`);

      // Sauvegarder la progression
      fs.writeFileSync(poPath, po.toString());

      // Rate limiting
      if (i + BATCH_SIZE < untranslated.length) {
        await new Promise((resolve) => setTimeout(resolve, DELAY_MS));
      }
    } catch (error) {
      console.error(`  Error translating batch: ${error}`);
      // Continuer avec le lot suivant
    }
  }

  console.log(`✓ ${locale}: Translation complete!`);
}

Le traitement par lots évite les limites de tokens et réduit les coûts.

Le script de traduction

Créez apps/nextjs/script/i18n.ts :

import { enhanceTranslations } from "@acme/i18n";
import { exec } from "child_process";
import { promisify } from "util";

const execAsync = promisify(exec);

const LOCALES = [
  "zh_CN", "zh_TW", "ja", "ko", "de",
  "fr", "es", "pt", "ar", "it", "ru"
];

async function main() {
  // Étape 1 : Extraire les chaînes du code
  console.log("📝 Extracting strings...");
  await execAsync("pnpm run lingui:extract --clean");

  // Étape 2 : Auto-traduire les chaînes manquantes
  console.log("\n🤖 Translating with AI...");
  const catalogPath = "./src/locales";

  for (const locale of LOCALES) {
    await enhanceTranslations(locale, catalogPath);
  }

  // Étape 3 : Compiler vers TypeScript
  console.log("\n⚡ Compiling catalogs...");
  await execAsync("npx lingui compile --typescript");

  console.log("\n✅ Done! All translations updated.");
}

main().catch(console.error);

Ajoutez ceci à package.json :

{
  "scripts": {
    "i18n": "tsx script/i18n.ts",
    "lingui:extract": "lingui extract",
    "lingui:compile": "lingui compile --typescript"
  }
}

Exécuter votre pipeline i18n

# Une seule commande pour tout gérer
$ pnpm run i18n

📝 Extracting strings...
Catalog statistics for src/locales/{locale}/messages:
┌──────────┬─────────────┬─────────┐
│ Language │ Total count │ Missing │
├──────────┼─────────────┼─────────┤
│ en       │         847 │       0 │
│ zh_CN    │         847 │     123 │
│ ja       │         847 │      89 │
└──────────┴─────────────┴─────────┘

🤖 Translating with AI...
Translating 123 strings for zh_CN...
  30/123 translated
  60/123 translated
  90/123 translated
  123/123 translated
✓ zh_CN: Translation complete!

⚡ Compiling catalogs...
✅ Done! All translations updated.

C'est tout. Ajoutez une nouvelle chaîne dans votre code, lancez pnpm i18n, et boum — traduit en 17 langues.


Before/after split screen: left shows stressed developer with translation papers and $1000 bill

Changement de locale

N'oubliez pas l'UX. Voici un sélecteur de langue :

"use client";

import { useLocaleSwitcher } from "@/hooks/useLocaleSwitcher";
import { useLocale } from "@/hooks/useLocale";

const LOCALES = {
  en: "English",
  zh_CN: "简体中文",
  zh_TW: "繁體中文",
  ja: "日本語",
  ko: "한국어",
  // ... etc
};

export function LocaleSelector() {
  const currentLocale = useLocale();
  const { switchLocale } = useLocaleSwitcher();

  return (
    <select
      value={currentLocale}
      onChange={(e) => switchLocale(e.target.value)}
    >
      {Object.entries(LOCALES).map(([code, name]) => (
        <option key={code} value={code}>
          {name}
        </option>
      ))}
    </select>
  );
}

L'implémentation du hook :

// hooks/useLocaleSwitcher.tsx
"use client";

import { setUserLocale } from "@/utils/i18n/localeDetection";

export function useLocaleSwitcher() {
  const switchLocale = (locale: string) => {
    setUserLocale(locale);
    window.location.reload(); // Force le rechargement pour appliquer la locale
  };

  return { switchLocale };
}

Stockez la préférence dans un cookie :

// utils/i18n/localeDetection.ts
import { cookies } from "next/headers";

export function setUserLocale(locale: string) {
  cookies().set("NEXT_LOCALE", locale, {
    maxAge: 365 * 24 * 60 * 60, // 1 an
  });
}

export function getLocale(): string {
  const cookieStore = cookies();
  return cookieStore.get("NEXT_LOCALE")?.value ?? "en";
}

Avancé : Traductions Type-Safe

Vous voulez du type-safety ? Lingui a ce qu'il vous faut :

// Au lieu de ça :
t`Hello ${name}`

// Utilisez un descripteur msg :
import { msg } from "@lingui/core/macro";

const greeting = msg`Hello ${name}`;
const translated = i18n._(greeting);

Votre IDE autocomplétera les clés de traduction. Magnifique.


Considérations de performance

1. Compiler au Build Time

Lingui compile les traductions en JSON minifié. Pas de surcharge de parsing au runtime.

// Sortie compilée (minifiée) :
export const messages = JSON.parse('{"ICt8/V":["视频"],"..."}');

2. Pré-charger les catalogues serveur

Chargez tous les catalogues une seule fois au démarrage (voir appRouterI18n.ts ci-dessus). Pas d'E/S fichier à chaque requête.

3. Taille du bundle client

N'envoyez que la locale active au client :

<LinguiClientProvider
  locale={locale}
  messages={allMessages[locale]} // Une seule locale
>

4. Optimisation des coûts LLM

  • Traductions par lots : 30 chaînes par appel API
  • Mise en cache : Ne pas re-traduire les chaînes inchangées
  • Utiliser des modèles moins chers : GPT-4o-mini pour les langues non critiques

Notre coût ? ~2-3 $ pour 800+ chaînes × 16 langues. Des centimes comparé aux traducteurs humains.


L'intégration complète de la stack

Voyons comment cela s'articule avec le reste de T3 Turbo :

tRPC avec i18n

// server/api/routers/user.ts
import { createTRPCRouter, publicProcedure } from "../trpc";
import { msg } from "@lingui/core/macro";

export const userRouter = createTRPCRouter({
  subscribe: publicProcedure
    .mutation(async ({ ctx }) => {
      // Les erreurs peuvent aussi être traduites !
      if (!ctx.session?.user) {
        throw new TRPCError({
          code: "UNAUTHORIZED",
          message: ctx.i18n._(msg`You must be logged in`),
        });
      }

      // ... logique d'abonnement
    }),
});

Passez l'instance i18n via le contexte :

// server/api/trpc.ts
import { getI18nInstance } from "@/utils/i18n/appRouterI18n";

export const createTRPCContext = async (opts: CreateNextContextOptions) => {
  const locale = getLocale();
  const i18n = getI18nInstance(locale);

  return {
    session: await getServerAuthSession(),
    i18n,
    locale,
  };
};

Base de données avec Drizzle

Stockez la préférence de locale de l'utilisateur :

// packages/db/schema/user.ts
import { pgTable, text, varchar } from "drizzle-orm/pg-core";

export const users = pgTable("user", {
  id: varchar("id", { length: 255 }).primaryKey(),
  locale: varchar("locale", { length: 10 }).default("en"),
  // ... autres champs
});

Intégration AI SDK

Traduisez les réponses de l'IA à la volée :

import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { useLingui } from "@lingui/react/macro";

export function useAIChat() {
  const { i18n } = useLingui();

  const chat = async (prompt: string) => {
    const systemPrompt = i18n._(msg`You are a helpful AI assistant for acme.`);

    return generateText({
      model: openai("gpt-4"),
      messages: [
        { role: "system", content: systemPrompt },
        { role: "user", content: prompt },
      ],
    });
  };

  return { chat };
}

Les meilleures pratiques que nous avons apprises

1. Toujours utiliser des macros

// ❌ Mauvais : Traduction runtime (non extraite)
const text = t("Hello world");

// ✅ Bon : Macro (extraite au build time)
const text = t`Hello world`;

2. Le contexte est roi

Ajoutez des commentaires pour les traducteurs :

// i18n: This appears in the pricing table header
<Trans>Monthly</Trans>

// i18n: Button to submit payment form
<button>{t`Subscribe Now`}</button>

Lingui extrait ces éléments comme notes pour le traducteur.

3. Gérer les pluriels correctement

import { Plural } from "@lingui/react/macro";

<Plural
  value={count}
  one="# credit remaining"
  other="# credits remaining"
/>

Différentes langues ont différentes règles de pluriel. Lingui gère ça.

4. Formatage Date/Nombre

Utilisez les API Intl :

const date = new Intl.DateTimeFormat(locale, {
  dateStyle: "long",
}).format(new Date());

const price = new Intl.NumberFormat(locale, {
  style: "currency",
  currency: "USD",
}).format(29.99);

5. Support RTL

Pour l'arabe, gérez la direction :

export default function RootLayout({ children }) {
  const locale = getLocale();
  const direction = locale === "ar" ? "rtl" : "ltr";

  return (
    <html lang={locale} dir={direction}>
      <body>{children}</body>
    </html>
  );
}

Ajoutez à la config Tailwind :

module.exports = {
  plugins: [
    require('tailwindcss-rtl'),
  ],
};

Utilisez des classes directionnelles :

<div className="ms-4"> {/* margin-start, fonctionne pour LTR et RTL */}

Checklist de déploiement

Avant de shipper :

  • Lancez pnpm i18n pour vous assurer que toutes les traductions sont à jour
  • Testez chaque locale en mode production
  • Vérifiez la persistance du cookie de locale
  • Vérifiez la mise en page RTL pour l'arabe
  • Testez l'UX du sélecteur de langue
  • Ajoutez les balises hreflang pour le SEO
  • Configurez le routage basé sur la locale si nécessaire
  • Surveillez les coûts de traduction LLM

Les résultats

Après avoir implémenté ce système :

  • 17 langues supportées prêtes à l'emploi
  • ~850 chaînes traduites automatiquement
  • 2-3 $ de coût total pour une traduction complète
  • Cycle de mise à jour de 2 minutes lors de l'ajout de nouvelles chaînes
  • Zéro travail de traduction manuel
  • Traductions contextuelles de haute qualité

Comparez ça à :

  • Traducteurs humains : 0,10-0,30 $ par mot = 1 000 $ et plus
  • Services traditionnels : Toujours chers, toujours lents
  • Travail manuel : Ne passe pas à l'échelle

Pourquoi c'est important en 2026

Écoutez, le web est mondial. Si vous ne shippez qu'en anglais en 2026, vous laissez 90% du monde sur le carreau.

Mais l'i18n traditionnel est douloureux. Cette approche rend la chose triviale :

  1. Écrivez du code avec les macros Trans/t (prend 2 secondes)
  2. Lancez pnpm i18n (automatisé)
  3. Shippez au monde entier (profit)

La combinaison de l'expérience développeur de Lingui + les traductions propulsées par LLM change la donne. Vous obtenez :

  • Des traductions type-safe
  • Zéro surcharge au runtime
  • Extraction automatique
  • Traductions IA conscientes du contexte
  • Quelques centimes par langue
  • Évolutivité infinie

Aller plus loin

Vous voulez passer au niveau supérieur ? Essayez :

Traduction de contenu dynamique

Stockez les traductions dans votre base de données :

// packages/db/schema/content.ts
export const blogPosts = pgTable("blog_post", {
  id: varchar("id", { length: 255 }).primaryKey(),
  titleEn: text("title_en"),
  titleZhCn: text("title_zh_cn"),
  titleJa: text("title_ja"),
  // ... etc
});

Auto-traduisez à la sauvegarde :

import { translateWithLLM } from "@acme/i18n";

export const blogRouter = createTRPCRouter({
  create: protectedProcedure
    .input(z.object({ title: z.string() }))
    .mutation(async ({ input }) => {
      // Traduire dans toutes les langues
      const translations = await Promise.all(
        LOCALES.map(async (locale) => {
          const result = await translateWithLLM(
            [{ msgid: input.title, msgstr: "" }],
            locale
          );
          return [locale, result[0].msgstr];
        })
      );

      await db.insert(blogPosts).values({
        id: generateId(),
        titleEn: input.title,
        ...Object.fromEntries(translations),
      });
    }),
});

Traductions fournies par les utilisateurs

Laissez les utilisateurs soumettre de meilleures traductions :

export const i18nRouter = createTRPCRouter({
  suggestTranslation: publicProcedure
    .input(z.object({
      msgid: z.string(),
      locale: z.string(),
      suggestion: z.string(),
    }))
    .mutation(async ({ input }) => {
      await db.insert(translationSuggestions).values(input);

      // Notifier les mainteneurs
      await sendEmail({
        to: "i18n@acme.com",
        subject: `New translation suggestion for ${input.locale}`,
        body: `"${input.msgid}" → "${input.suggestion}"`,
      });
    }),
});

A/B Testing des traductions

Testez quelles traductions convertissent le mieux :

const variant = await abTest.getVariant("pricing-cta", locale);

const ctaText = variant === "A"
  ? t`Start Your Free Trial`
  : t`Try acme Free`;

Le Code

Tout ceci est du code de production d'une vraie application. L'implémentation complète est dans notre monorepo :

t3-acme-app/
├── apps/nextjs/
│   ├── lingui.config.ts
│   ├── src/
│   │   ├── locales/           # Catalogues compilés
│   │   ├── utils/i18n/        # Utilitaires i18n
│   │   └── providers/         # LinguiClientProvider
│   └── script/i18n.ts         # Script de traduction
└── packages/i18n/
    └── src/
        ├── translateWithLLM.ts
        ├── enhanceTranslations.ts
        └── utils.ts

Dernières pensées

Construire une app IA multilingue en 2026 n'est plus difficile. Les outils sont là :

  • Lingui pour l'extraction et le runtime
  • Claude/GPT pour la traduction contextuelle
  • T3 Turbo pour la meilleure DX du marché

Arrêtez de payer des milliers de dollars pour des traductions. Arrêtez de limiter votre app à l'anglais.

Construisez globalement. Shippez vite. Utilisez l'IA.

C'est comme ça qu'on fait en 2026.


Des questions ? Des problèmes ? Retrouvez-moi sur Twitter ou consultez la doc Lingui et la doc AI SDK.

Maintenant, allez shipper cette app multilingue. Le monde attend.

Partager ceci

Feng Liu

Feng Liu

shenjian8628@gmail.com

Comment Construire une Webapp Moderne i18n Propulsée par l'IA en 2026 | Feng Liu