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.

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:
- Lingui untuk ekstraksi, kompilasi, dan keajaiban runtime
- 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.

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.

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 i18nuntuk 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:
- Tulis kode dengan macro Trans/t (cuma butuh 2 detik)
- Jalankan
pnpm i18n(otomatis) - 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
shenjian8628@gmail.com