Cara Membangun Webapp Modern Berbasis AI & i18n di 2026

Panduan lengkap membangun aplikasi web multibahasa dengan Lingui + terjemahan AI. Dukung 17 bahasa secara otomatis menggunakan Next.js, Claude, dan T3 Turbo.

Cara Membangun Webapp Modern Berbasis AI & i18n di 2026
Feng LiuFeng Liu
24 Januari 2026

Dengar, kita perlu bicara serius soal i18n di tahun 2026.

Kebanyakan tutorial akan menyuruh kamu menerjemahkan string secara manual, menyewa penerjemah, atau menggunakan API Google Translate yang abal-abal. Tapi masalahnya begini: kamu hidup di era Claude Sonnet 4.5. Kenapa cara translate kamu masih kayak tahun 2019?

Saya akan menunjukkan bagaimana kami membangun webapp level produksi yang fasih berbicara dalam 17 bahasa, menggunakan arsitektur i18n dua bagian yang benar-benar masuk akal:

  1. Lingui untuk ekstraksi, kompilasi, dan keajaiban runtime
  2. Package i18n kustom yang ditenagai oleh LLM untuk terjemahan otomatis yang sadar konteks

Tech stack kami? Create T3 Turbo dengan Next.js, tRPC, Drizzle, Postgres, Tailwind, dan AI SDK. Kalau kamu belum pakai ini di 2026, kita perlu ngobrol hal lain deh.

Mari kita bangun.


Masalah dengan i18n Tradisional

Workflow i18n tradisional biasanya terlihat seperti ini:

# Extract strings
$ lingui extract

# ??? Entah gimana caranya dapet terjemahan ???
# (sewa penerjemah, pakai layanan nggak jelas, nangis)

# Compile
$ lingui compile

Langkah tengah itu? Itu mimpi buruk. Kamu pilihannya cuma:

  • Bayar $$$ buat penerjemah manusia (lambat, mahal)
  • Pakai API terjemahan dasar (buta konteks, terdengar kaku kayak robot)
  • Terjemahin manual (nggak bakal scale)

Kita melakukan cara yang lebih baik.


Arsitektur Dua Bagian

Begini setup kami:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  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    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Bagian 1 (Lingui) menangani developer experience. Bagian 2 (Package i18n Kustom) menangani keajaiban terjemahannya.

Mari kita bedah satu per satu.


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

Bagian 1: Setup Lingui di Next.js

Instalasi

Di dalam monorepo T3 Turbo kamu:

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

Konfigurasi Lingui

Buat file 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 bahasa langsung siap pakai. Kenapa enggak?

Integrasi Next.js

Update next.config.js untuk menggunakan plugin SWC Lingui:

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

module.exports = {
  experimental: {
    swcPlugins: [
      [
        "@lingui/swc-plugin",
        {
          // Ini bikin build kamu lebih cepat
        },
      ],
    ],
  },
  // ... sisa config kamu
};

Setup Server-Side

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

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

Kenapa? Server Components tidak punya React Context. Ini memberikanmu terjemahan di sisi server.

Client-Side Provider

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

Bungkus aplikasimu di 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>
  );
}

Menggunakan Terjemahan di Kode Kamu

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

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

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

Sintaks macro adalah KUNCINYA. Lingui mengekstrak ini saat build time.


Bagian 2: Package Terjemahan Bertenaga AI

Di sini bagian serunya.

Struktur Package

Buat packages/i18n/:

packages/i18n/
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ translateWithLLM.ts      # Core LLM translation
โ”‚   โ”œโ”€โ”€ 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"
  }
}

Mesin Terjemahan LLM

Ini bumbu rahasianya - 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, // Lower = more consistent
  });

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

Kenapa ini berhasil:

  • Sadar konteks: LLM tahu apa itu acme.
  • Output terstruktur: Skema Zod memastikan JSON yang valid.
  • Temperature rendah: Terjemahan yang konsisten.
  • Mempertahankan format: HTML dan variabel tetap utuh.

Batch Translation Processor

Buat enhanceTranslations.ts:

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

const BATCH_SIZE = 30; // Translate 30 strings at a time
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"));

  // Find untranslated 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}...`);

  // Process 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 file
      translations.forEach((translation, index) => {
        const item = batch[index];
        if (item) {
          item.msgstr = [translation.msgstr];
        }
      });

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

      // Save progress
      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}`);
      // Continue with next batch
    }
  }

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

Pemrosesan batch mencegah limit token dan menghemat biaya.

Skrip Terjemahan

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

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

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

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

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

main().catch(console.error);

Tambahkan ke package.json:

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

Menjalankan Pipeline i18n Kamu

# Satu perintah untuk mengurus semuanya
$ 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.

Itu saja. Tambahkan string baru di kodemu, jalankan pnpm i18n, boom - langsung diterjemahkan ke 17 bahasa.


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

Locale Switching

Jangan lupakan bagian UX-nya. Ini contoh locale switcher:

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

Implementasi hook-nya:

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

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

export function useLocaleSwitcher() {
  const switchLocale = (locale: string) => {
    setUserLocale(locale);
    window.location.reload(); // Force reload to apply locale
  };

  return { switchLocale };
}

Simpan preferensi di 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 year
  });
}

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

Lanjutan: Type-Safe Translations

Mau type safety? Lingui sudah menyediakannya:

// Instead of this:
t`Hello ${name}`

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

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

IDE kamu akan melakukan autocomplete untuk kunci terjemahan. Cakep.


Pertimbangan Performa

1. Compile saat Build Time

Lingui mengompilasi terjemahan menjadi JSON yang sudah diminifikasi. Tidak ada overhead parsing saat runtime.

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

2. Pre-load Server Catalogs

Muat semua katalog sekali saat startup (lihat appRouterI18n.ts di atas). Tidak ada file I/O di setiap request.

3. Ukuran Bundle Client

Hanya kirim locale yang aktif ke client:

<LinguiClientProvider
  locale={locale}
  messages={allMessages[locale]} // Only one locale
>

4. Optimasi Biaya LLM

  • Batch translations: 30 string per panggilan API
  • Cache translations: Jangan terjemahkan ulang string yang tidak berubah
  • Gunakan model yang lebih murah: GPT-4o-mini untuk bahasa yang tidak terlalu kritis

Biaya kami? ~$2-3 untuk 800+ string ร— 16 bahasa. Receh banget kalau dibandingin sama penerjemah manusia.


Integrasi Full Tech Stack

Mari kita lihat bagaimana ini bermain dengan bagian lain dari T3 Turbo:

tRPC dengan 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 can be translated too!
      if (!ctx.session?.user) {
        throw new TRPCError({
          code: "UNAUTHORIZED",
          message: ctx.i18n._(msg`You must be logged in`),
        });
      }

      // ... subscription logic
    }),
});

Oper instance i18n 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 dengan Drizzle

Simpan preferensi locale user:

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

Integrasi AI SDK

Terjemahkan respons AI secara 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 yang Kami Pelajari

1. Selalu Gunakan Macros

// โŒ Bad: Runtime translation (not extracted)
const text = t("Hello world");

// โœ… Good: Macro (extracted at build time)
const text = t`Hello world`;

2. Konteks adalah Segalanya

Tambahkan komentar untuk penerjemah (atau AI):

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

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

Lingui mengekstrak ini sebagai catatan penerjemah.

3. Tangani Plural dengan Benar

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

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

Bahasa yang berbeda punya aturan jamak yang berbeda. Lingui menangani ini.

4. Format Tanggal/Angka

Gunakan Intl APIs:

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

Untuk bahasa Arab, tangani arah teks:

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

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

Tambahkan ke config Tailwind:

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

Gunakan class direksional:

<div className="ms-4"> {/* margin-start, works for both LTR/RTL */}

Checklist Deployment

Sebelum kamu ship:

  • Jalankan pnpm i18n untuk memastikan semua terjemahan up to date
  • Tes setiap locale di mode produksi
  • Verifikasi persistensi cookie locale
  • Cek layout RTL untuk bahasa Arab
  • Tes UX locale switcher
  • Tambahkan tag hreflang untuk SEO
  • Setup routing berbasis locale jika diperlukan
  • Monitor biaya terjemahan LLM

Hasilnya

Setelah mengimplementasikan sistem ini:

  • 17 bahasa didukung langsung
  • ~850 string diterjemahkan secara otomatis
  • $2-3 total biaya untuk terjemahan penuh
  • Siklus update 2 menit saat menambahkan string baru
  • Nol pekerjaan terjemahan manual
  • Terjemahan berkualitas tinggi yang sadar konteks

Bandingkan itu dengan:

  • Penerjemah manusia: $0.10-0.30 per kata = $1,000+
  • Layanan tradisional: Masih mahal, masih lambat
  • Kerja manual: Nggak bakal scale

Kenapa Ini Penting di 2026

Dengar, web itu global. Kalau kamu cuma merilis dalam bahasa Inggris di tahun 2026, kamu meninggalkan 90% dunia di belakang.

Tapi i18n tradisional itu menyakitkan. Pendekatan ini membuatnya jadi sepele:

  1. Tulis kode dengan macro Trans/t (cuma butuh 2 detik)
  2. Jalankan pnpm i18n (otomatis)
  3. Rilis ke dunia (cuan)

Kombinasi dari developer experience Lingui + terjemahan bertenaga LLM adalah game-changer. Kamu mendapatkan:

  • Terjemahan yang type-safe
  • Runtime tanpa overhead
  • Ekstraksi otomatis
  • Terjemahan AI yang sadar konteks
  • Biaya receh per bahasa
  • Scale tanpa batas

Melangkah Lebih Jauh

Mau level up? Coba ini:

Terjemahan Konten Dinamis

Simpan terjemahan di database kamu:

// 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-translate saat disimpan:

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

export const blogRouter = createTRPCRouter({
  create: protectedProcedure
    .input(z.object({ title: z.string() }))
    .mutation(async ({ input }) => {
      // Translate to all languages
      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),
      });
    }),
});

Terjemahan dari User

Biarkan user mengirimkan saran terjemahan yang lebih baik:

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

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

A/B Testing Terjemahan

Tes terjemahan mana yang konversinya lebih baik:

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

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

Kodenya

Semua ini adalah kode produksi dari aplikasi nyata. Implementasi lengkapnya ada di monorepo kami:

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

Pemikiran Terakhir

Membangun aplikasi AI multibahasa di tahun 2026 itu nggak susah lagi. Alat-alatnya sudah ada:

  • Lingui untuk ekstraksi dan runtime
  • Claude/GPT untuk terjemahan sadar konteks
  • T3 Turbo untuk DX terbaik di kelasnya

Berhenti bayar ribuan dolar untuk terjemahan. Berhenti membatasi aplikasimu cuma buat bahasa Inggris.

Bangun secara global. Rilis dengan cepat. Gunakan AI.

Begitulah cara kita melakukannya di tahun 2026.


Ada pertanyaan? Masalah? Temukan saya di Twitter atau cek dokumentasi Lingui dan dokumentasi AI SDK.

Sekarang pergi dan rilis aplikasi multibahasa itu. Dunia sudah menunggu.

Bagikan ini

Feng Liu

Feng Liu

shenjian8628@gmail.com