Hoe bouw je een moderne i18n AI-webapp in 2026

De complete gids voor het bouwen van meertalige webapps met Lingui + AI-vertalingen. Ondersteun automatisch 17 talen met Next.js, Claude en T3 Turbo.

Hoe bouw je een moderne i18n AI-webapp in 2026
Feng LiuFeng Liu
24 januari 2026

Luister, we moeten het echt even hebben over i18n in 2026.

De meeste tutorials zullen je vertellen dat je strings handmatig moet vertalen, vertalers moet inhuren of een krakkemikkige Google Translate API moet gebruiken. Maar hier is het ding: je leeft in het Claude Sonnet 4.5-tijdperk. Waarom vertaal je alsof het 2019 is?

Ik ga je laten zien hoe we een productie-webapp hebben gebouwd die vloeiend 17 talen spreekt, gebruikmakend van een tweedelige i18n-architectuur die daadwerkelijk logisch is:

  1. Lingui voor de extractie, compilatie en runtime-magie
  2. Een custom i18n package aangedreven door LLM's voor geautomatiseerde, context-bewuste vertalingen

Onze stack? Create T3 Turbo met Next.js, tRPC, Drizzle, Postgres, Tailwind en de AI SDK. Als je dit in 2026 niet gebruikt, moeten we een heel ander gesprek voeren.

Laten we gaan bouwen.


Het Probleem Met Traditionele i18n

Traditionele i18n-workflows zien er zo uit:

# Strings extraheren
$ lingui extract

# ??? Op de een of andere manier vertalingen krijgen ???
# (vertalers inhuren, vage diensten gebruiken, huilen)

# Compileren
$ lingui compile

Die middelste stap? Dat is een nachtmerrie. Je bent ofwel:

  • $$$ aan het betalen voor menselijke vertalers (traag, duur)
  • Simpele vertaal-API's aan het gebruiken (geen context, klinkt robotachtig)
  • Handmatig aan het vertalen (schaalt niet)

Wij doen het beter.


De Tweedelige Architectuur

Hier is onze setup:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Next.js App (Lingui Integration)          โ”‚
โ”‚  โ”œโ”€ Extract strings with macros             โ”‚
โ”‚  โ”œโ”€ Trans/t components in your code         โ”‚
โ”‚  โ””โ”€ Runtime i18n with compiled catalogs     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
              โ†“ generates .po files
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  @acme/i18n Package (LLM Translation)     โ”‚
โ”‚  โ”œโ”€ Reads .po files                         โ”‚
โ”‚  โ”œโ”€ Batch translates with Claude/GPT-5      โ”‚
โ”‚  โ”œโ”€ Context-aware, product-specific         โ”‚
โ”‚  โ””โ”€ Writes translated .po files             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
              โ†“ compiles to TypeScript
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Compiled Message Catalogs                  โ”‚
โ”‚  โ””โ”€ Fast, type-safe runtime translations    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Deel 1 (Lingui) regelt de developer experience. Deel 2 (Custom i18n Package) regelt de vertaalmagie.

Laten we in beide duiken.


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

Deel 1: Lingui Opzetten in Next.js

Installatie

In je T3 Turbo monorepo:

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

Lingui Config

Maak apps/nextjs/lingui.config.ts aan:

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 talen direct uit de doos. Waarom ook niet?

Next.js Integratie

Update next.config.js om Lingui's SWC plugin te gebruiken:

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

module.exports = {
  experimental: {
    swcPlugins: [
      [
        "@lingui/swc-plugin",
        {
          // Dit maakt je builds sneller
        },
      ],
    ],
  },
  // ... rest van je config
};

Server-Side Setup

Maak src/utils/i18n/appRouterI18n.ts aan:

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>>();

// Pre-create i18n instances voor alle 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")!;
}

Waarom? Server Components hebben geen React Context. Dit geeft je server-side vertalingen.

Client-Side Provider

Maak src/providers/LinguiClientProvider.tsx aan:

"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>;
}

Wrap je app in 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>
  );
}

Vertalingen Gebruiken in Je Code

In 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`),
  };
}

In 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>

      {/* Met variabelen */}
      <p>{t`${credits} credits remaining`}</p>
    </div>
  );
}

De macro syntax is CRUCIAAL. Lingui extraheert deze tijdens build time.


Deel 2: Het AI-Aangedreven Vertaal Package

Hier wordt het pas echt interessant.

Package Structuur

Maak packages/i18n/ aan:

packages/i18n/
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ translateWithLLM.ts      # Core LLM vertaling
โ”‚   โ”œโ”€โ”€ enhanceTranslations.ts   # Batch processor
โ”‚   โ””โ”€โ”€ utils.ts                  # Helpers

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"
  }
}

De LLM Vertaal Engine

Hier is het geheime ingrediรซnt - 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, // Lager = consistenter
  });

  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",
    // ... enz
  };
  return names[locale] ?? locale;
}

Waarom dit werkt:

  • Context-bewust: De LLM weet wat acme is
  • Gestructureerde output: Zod schema garandeert valide JSON
  • Lage temperatuur: Consistente vertalingen
  • Behoudt opmaak: HTML en variabelen blijven intact

Batch Translation Processor

Maak enhanceTranslations.ts aan:

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

const BATCH_SIZE = 30; // Vertaal 30 strings per keer
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"));

  // Vind onvertaalde items
  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}...`);

  // Verwerk in batches
  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);

      // Update PO bestand
      translations.forEach((translation, index) => {
        const item = batch[index];
        if (item) {
          item.msgstr = [translation.msgstr];
        }
      });

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

      // Voortgang opslaan
      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}`);
      // Ga door met volgende batch
    }
  }

  console.log(`โœ“ ${locale}: Translation complete!`);
}

Batch processing voorkomt token-limieten en bespaart kosten.

Het Vertaalscript

Maak apps/nextjs/script/i18n.ts aan:

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() {
  // Stap 1: Strings extraheren uit code
  console.log("๐Ÿ“ Extracting strings...");
  await execAsync("pnpm run lingui:extract --clean");

  // Stap 2: Auto-vertaal ontbrekende strings
  console.log("\n๐Ÿค– Translating with AI...");
  const catalogPath = "./src/locales";

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

  // Stap 3: Compileer naar TypeScript
  console.log("\nโšก Compiling catalogs...");
  await execAsync("npx lingui compile --typescript");

  console.log("\nโœ… Done! All translations updated.");
}

main().catch(console.error);

Voeg toe aan package.json:

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

Je i18n Pipeline Draaien

# Eรฉn commando om alles te regelen
$ 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.

Dat is het. Voeg een nieuwe string toe in je code, draai pnpm i18n, boem - vertaald in 17 talen.


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

Locale Switching

Vergeet de UX niet. Hier is een locale switcher:

"use client";

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

const LOCALES = {
  en: "English",
  zh_CN: "็ฎ€ไฝ“ไธญๆ–‡",
  zh_TW: "็น้ซ”ไธญๆ–‡",
  ja: "ๆ—ฅๆœฌ่ชž",
  ko: "ํ•œ๊ตญ์–ด",
  // ... enz
};

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>
  );
}

De hook implementatie:

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

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

export function useLocaleSwitcher() {
  const switchLocale = (locale: string) => {
    setUserLocale(locale);
    window.location.reload(); // Forceer reload om locale toe te passen
  };

  return { switchLocale };
}

Sla de voorkeur op in een 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 jaar
  });
}

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

Geavanceerd: Type-Safe Vertalingen

Wil je type safety? Lingui regelt het:

// In plaats van dit:
t`Hello ${name}`

// Gebruik msg descriptor:
import { msg } from "@lingui/core/macro";

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

Je IDE zal vertaalsleutels automatisch aanvullen. Prachtig.


Performance Overwegingen

1. Compileer tijdens Build Time

Lingui compileert vertalingen naar geminimaliseerde JSON. Geen runtime parsing overhead.

// Gecompileerde output (geminimaliseerd):
export const messages = JSON.parse('{"ICt8/V":["่ง†้ข‘"],"..."}');

2. Pre-load Server Catalogs

Laad alle catalogs รฉรฉn keer bij het opstarten (zie appRouterI18n.ts hierboven). Geen file I/O bij elk request.

3. Client Bundle Grootte

Stuur alleen de actieve locale naar de client:

<LinguiClientProvider
  locale={locale}
  messages={allMessages[locale]} // Slechts รฉรฉn locale
>

4. LLM Kostenoptimalisatie

  • Batch vertalingen: 30 strings per API call
  • Cache vertalingen: Vertaal ongewijzigde strings niet opnieuw
  • Gebruik goedkopere modellen: GPT-4o-mini voor niet-kritieke talen

Onze kosten? ~$2-3 voor 800+ strings ร— 16 talen. Wisselgeld vergeleken met menselijke vertalers.


De Volledige Tech Stack Integratie

Laten we kijken hoe dit samenwerkt met de rest van T3 Turbo:

tRPC met 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 }) => {
      // Errors kunnen ook vertaald worden!
      if (!ctx.session?.user) {
        throw new TRPCError({
          code: "UNAUTHORIZED",
          message: ctx.i18n._(msg`You must be logged in`),
        });
      }

      // ... subscription logica
    }),
});

Geef i18n instance door via context:

// 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,
  };
};

Database met Drizzle

Sla de locale-voorkeur van de gebruiker op:

// 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"),
  // ... andere velden
});

AI SDK Integratie

Vertaal AI-antwoorden on-the-fly:

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 };
}

Best Practices Die We Hebben Geleerd

1. Gebruik Altijd Macro's

// โŒ Fout: Runtime vertaling (niet geรซxtraheerd)
const text = t("Hello world");

// โœ… Goed: Macro (geรซxtraheerd tijdens build time)
const text = t`Hello world`;

2. Context is Alles

Voeg comments toe voor vertalers (of de AI):

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

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

Lingui extraheert deze als notities voor de vertaler.

3. Ga Goed Om Met Meervoudsvormen

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

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

Verschillende talen hebben verschillende regels voor meervoud. Lingui regelt dit.

4. Datum/Nummer Formattering

Gebruik Intl API's:

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. RTL Ondersteuning

Voor Arabisch, regel de schrijfrichting:

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

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

Voeg toe aan Tailwind config:

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

Gebruik directionele classes:

<div className="ms-4"> {/* margin-start, werkt voor zowel LTR/RTL */}

Deployment Checklist

Voordat je shipt:

  • Draai pnpm i18n om te zorgen dat alle vertalingen up-to-date zijn
  • Test elke locale in productie-modus
  • Verifieer of de locale-cookie blijft bestaan
  • Check RTL-layout voor Arabisch
  • Test de UX van de locale switcher
  • Voeg hreflang tags toe voor SEO
  • Stel locale-based routing in indien nodig
  • Monitor de kosten van LLM-vertalingen

De Resultaten

Na het implementeren van dit systeem:

  • 17 talen ondersteund direct uit de doos
  • ~850 strings automatisch vertaald
  • $2-3 totale kosten voor volledige vertaling
  • 2 minuten update-cyclus bij het toevoegen van nieuwe strings
  • Nul handmatig vertaalwerk
  • Context-bewuste, hoge kwaliteit vertalingen

Vergelijk dat eens met:

  • Menselijke vertalers: $0.10-0.30 per woord = $1.000+
  • Traditionele diensten: Nog steeds duur, nog steeds traag
  • Handmatig werk: Schaalt niet

Waarom Dit Belangrijk is in 2026

Kijk, het web is wereldwijd. Als je in 2026 alleen Engels shipt, laat je 90% van de wereld links liggen.

Maar traditionele i18n is pijnlijk. Deze aanpak maakt het triviaal:

  1. Schrijf code met Trans/t macro's (kost 2 seconden)
  2. Draai pnpm i18n (geautomatiseerd)
  3. Ship naar de wereld (profit)

De combinatie van Lingui's developer experience + LLM-aangedreven vertalingen is een gamechanger. Je krijgt:

  • Type-safe vertalingen
  • Zero-overhead runtime
  • Automatische extractie
  • Context-bewuste AI-vertalingen
  • Centen per taal
  • Schaalt oneindig

Verder Gaan

Wil je nog een stap verder? Probeer dit:

Dynamische Content Vertaling

Sla vertalingen op in je database:

// 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"),
  // ... enz
});

Auto-vertaal bij opslaan:

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

export const blogRouter = createTRPCRouter({
  create: protectedProcedure
    .input(z.object({ title: z.string() }))
    .mutation(async ({ input }) => {
      // Vertaal naar alle talen
      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),
      });
    }),
});

Door Gebruikers Aangedragen Vertalingen

Laat gebruikers betere vertalingen indienen:

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);

      // Breng maintainers op de hoogte
      await sendEmail({
        to: "i18n@acme.com",
        subject: `New translation suggestion for ${input.locale}`,
        body: `"${input.msgid}" โ†’ "${input.suggestion}"`,
      });
    }),
});

A/B Testen van Vertalingen

Test welke vertalingen beter converteren:

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

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

De Code

Dit alles is productie-code van een echte app. De volledige implementatie staat in onze monorepo:

t3-acme-app/
โ”œโ”€โ”€ apps/nextjs/
โ”‚   โ”œโ”€โ”€ lingui.config.ts
โ”‚   โ”œโ”€โ”€ src/
โ”‚   โ”‚   โ”œโ”€โ”€ locales/           # Compiled catalogs
โ”‚   โ”‚   โ”œโ”€โ”€ utils/i18n/        # i18n utilities
โ”‚   โ”‚   โ””โ”€โ”€ providers/         # LinguiClientProvider
โ”‚   โ””โ”€โ”€ script/i18n.ts         # Translation script
โ””โ”€โ”€ packages/i18n/
    โ””โ”€โ”€ src/
        โ”œโ”€โ”€ translateWithLLM.ts
        โ”œโ”€โ”€ enhanceTranslations.ts
        โ””โ”€โ”€ utils.ts

Laatste Gedachten

Een meertalige AI-app bouwen in 2026 is niet moeilijk meer. De tools zijn er:

  • Lingui voor extractie en runtime
  • Claude/GPT voor context-bewuste vertaling
  • T3 Turbo voor de beste DX in de game

Stop met het betalen van duizenden euro's voor vertalingen. Stop met het beperken van je app tot alleen Engels.

Bouw wereldwijd. Ship snel. Gebruik AI.

Zo doen we dat in 2026.


Vragen? Issues? Vind me op Twitter of check de Lingui docs en AI SDK docs.

Ga nu die meertalige app shippen. De wereld wacht op je.

Deel dit

Feng Liu

Feng Liu

shenjian8628@gmail.com

Hoe bouw je een moderne i18n AI-webapp in 2026 | Feng Liu