Como Construir um Webapp Moderno com IA e i18n em 2026

Guia completo para criar webapps multilíngues com Lingui + traduções via IA. Suporte a 17 idiomas automaticamente usando Next.js, Claude e T3 Turbo.

Como Construir um Webapp Moderno com IA e i18n em 2026
Feng LiuFeng Liu
24 de janeiro de 2026

Olha, precisamos falar sobre i18n em 2026.

A maioria dos tutoriais vai te dizer para traduzir strings manualmente, contratar tradutores ou usar alguma API do Google Translate tosca. Mas a questão é a seguinte: você está vivendo na era do Claude Sonnet 4.5. Por que você está traduzindo como se fosse 2019?

Vou te mostrar como construímos um webapp em produção que fala 17 idiomas fluentemente, usando uma arquitetura de i18n de duas partes que realmente faz sentido:

  1. Lingui para a extração, compilação e a mágica em tempo de execução (runtime)
  2. Um pacote de i18n customizado alimentado por LLMs para traduções automatizadas e conscientes do contexto

Nossa stack? Create T3 Turbo com Next.js, tRPC, Drizzle, Postgres, Tailwind e o AI SDK. Se você não está usando isso em 2026, precisamos ter uma conversa diferente.

Vamos construir.


O Problema com o i18n Tradicional

Fluxos de trabalho tradicionais de i18n se parecem com isso:

# Extrair strings
$ lingui extract

# ??? Conseguir traduções de algum jeito ???
# (contratar tradutores, usar serviços duvidosos, chorar)

# Compilar
$ lingui compile

Aquele passo do meio? É um pesadelo. Você está ou:

  • Pagando $$$ para tradutores humanos (lento, caro)
  • Usando APIs de tradução básicas (cegas ao contexto, soam robóticas)
  • Traduzindo manualmente (não escala)

Nós estamos fazendo melhor.


A Arquitetura de Duas Partes

Aqui está o nosso setup:

┌─────────────────────────────────────────────┐
│  Next.js App (Integração Lingui)           │
│  ├─ Extrai strings com macros               │
│  ├─ Componentes Trans/t no seu código       │
│  └─ i18n em runtime com catálogos compilados│
└─────────────────────────────────────────────┘
              ↓ gera arquivos .po
┌─────────────────────────────────────────────┐
│  Pacote @acme/i18n (Tradução via LLM)      │
│  ├─ Lê arquivos .po                         │
│  ├─ Traduz em lote com Claude/GPT-5         │
│  ├─ Consciente do contexto e do produto     │
│  └─ Escreve arquivos .po traduzidos         │
└─────────────────────────────────────────────┘
              ↓ compila para TypeScript
┌─────────────────────────────────────────────┐
│  Catálogos de Mensagens Compilados          │
│  └─ Traduções rápidas e type-safe em runtime│
└─────────────────────────────────────────────┘

Parte 1 (Lingui) cuida da experiência do desenvolvedor (DX). Parte 2 (Pacote Customizado de i18n) cuida da mágica da tradução.

Vamos mergulhar em cada uma.


Diagrama de fluxo técnico: UI web → nuvem de tradução IA → banco de dados, três camadas conectadas por setas

Parte 1: Configurando o Lingui no Next.js

Instalação

No seu monorepo T3 Turbo:

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

Configuração do Lingui

Crie 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 idiomas prontos para uso. Porque não?

Integração com Next.js

Atualize o next.config.js para usar o plugin SWC do Lingui:

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

module.exports = {
  experimental: {
    swcPlugins: [
      [
        "@lingui/swc-plugin",
        {
          // Isso torna seus builds mais rápidos
        },
      ],
    ],
  },
  // ... resto da sua config
};

Setup Server-Side

Crie 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é-cria instâncias i18n para todos os 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")!;
}

Por quê? Server Components não têm React Context. Isso te dá traduções no lado do servidor.

Provider Client-Side

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

Envolva seu app no 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>
  );
}

Usando Traduções no Seu Código

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

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

      {/* Com variáveis */}
      <p>{t`${credits} credits remaining`}</p>
    </div>
  );
}

A sintaxe de macro é a CHAVE. O Lingui extrai isso em tempo de build.


Parte 2: O Pacote de Tradução Alimentado por IA

É aqui que a coisa fica interessante.

Estrutura do Pacote

Crie packages/i18n/:

packages/i18n/
├── package.json
├── src/
│   ├── translateWithLLM.ts      # Tradução core com LLM
│   ├── enhanceTranslations.ts   # Processador em lote
│   └── 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"
  }
}

O Motor de Tradução LLM

Aqui está o ingrediente secreto - 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, // Mais baixo = mais consistente
  });

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

Por que isso funciona:

  • Consciente do contexto: O LLM sabe o que é a "acme".
  • Saída estruturada: O schema do Zod garante um JSON válido.
  • Baixa temperatura: Traduções consistentes.
  • Preserva formatação: HTML e variáveis permanecem intactos.

Processador de Tradução em Lote

Crie enhanceTranslations.ts:

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

const BATCH_SIZE = 30; // Traduz 30 strings por vez
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"));

  // Encontra itens não traduzidos
  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}...`);

  // Processa em lotes
  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);

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

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

      // Salva progresso
      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}`);
      // Continua com o próximo lote
    }
  }

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

Processamento em lote previne limites de tokens e economiza custos.

O Script de Tradução

Crie 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() {
  // Passo 1: Extrair strings do código
  console.log("📝 Extracting strings...");
  await execAsync("pnpm run lingui:extract --clean");

  // Passo 2: Auto-traduzir strings faltantes
  console.log("\n🤖 Translating with AI...");
  const catalogPath = "./src/locales";

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

  // Passo 3: Compilar para TypeScript
  console.log("\n⚡ Compiling catalogs...");
  await execAsync("npx lingui compile --typescript");

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

main().catch(console.error);

Adicione ao package.json:

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

Rodando Seu Pipeline de i18n

# Um comando para governar todos
$ 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.

É isso. Adicione uma nova string no seu código, rode pnpm i18n, boom - traduzido para 17 idiomas.


Antes/depois tela dividida: esquerda mostra desenvolvedor estressado com papéis de tradução e conta de $1000

Troca de Idioma (Locale Switching)

Não esqueça da parte de UX. Aqui está um seletor de locale:

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

A implementação do hook:

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

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

export function useLocaleSwitcher() {
  const switchLocale = (locale: string) => {
    setUserLocale(locale);
    window.location.reload(); // Força recarregamento para aplicar locale
  };

  return { switchLocale };
}

Armazene a preferência em um 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 ano
  });
}

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

Avançado: Traduções Type-Safe

Quer segurança de tipos? O Lingui te cobre:

// Ao invés disso:
t`Hello ${name}`

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

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

Sua IDE vai autocompletar as chaves de tradução. Lindo.


Considerações de Performance

1. Compile em Tempo de Build

O Lingui compila traduções para JSON minificado. Sem overhead de parsing em runtime.

// Saída compilada (minificada):
export const messages = JSON.parse('{"ICt8/V":["视频"],"..."}');

2. Pré-carregue Catálogos no Servidor

Carregue todos os catálogos uma vez na inicialização (veja appRouterI18n.ts acima). Sem I/O de arquivo a cada requisição.

3. Tamanho do Bundle do Cliente

Envie apenas o locale ativo para o cliente:

<LinguiClientProvider
  locale={locale}
  messages={allMessages[locale]} // Apenas um locale
>

4. Otimização de Custo de LLM

  • Traduções em lote: 30 strings por chamada de API
  • Cache de traduções: Não re-traduza strings inalteradas
  • Use modelos mais baratos: GPT-4o-mini para idiomas não críticos

Nosso custo? ~$2-3 para 800+ strings × 16 idiomas. Centavos comparado a tradutores humanos.


A Integração Full Stack

Vamos ver como isso joga com o resto do T3 Turbo:

tRPC com 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 }) => {
      // Erros podem ser traduzidos também!
      if (!ctx.session?.user) {
        throw new TRPCError({
          code: "UNAUTHORIZED",
          message: ctx.i18n._(msg`You must be logged in`),
        });
      }

      // ... lógica de assinatura
    }),
});

Passe a instância 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,
  };
};

Banco de Dados com Drizzle

Armazene a preferência de locale do usuário:

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

Integração com AI SDK

Traduza respostas de IA em tempo real:

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

Melhores Práticas que Aprendemos

1. Sempre Use Macros

// ❌ Ruim: Tradução em runtime (não extraída)
const text = t("Hello world");

// ✅ Bom: Macro (extraída em tempo de build)
const text = t`Hello world`;

2. Contexto é Tudo

Adicione comentários para os tradutores:

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

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

O Lingui extrai isso como notas para o tradutor.

3. Lide com Plurais Corretamente

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

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

Idiomas diferentes têm regras de plural diferentes. O Lingui cuida disso.

4. Formatação de Data/Número

Use 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. Suporte a RTL

Para Árabe, lide com a direção:

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

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

Adicione à config do Tailwind:

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

Use classes direcionais:

<div className="ms-4"> {/* margin-start, funciona para ambos LTR/RTL */}

Checklist de Deploy

Antes de shippar:

  • Rode pnpm i18n para garantir que todas as traduções estão atualizadas
  • Teste cada locale em modo de produção
  • Verifique a persistência do cookie de locale
  • Cheque o layout RTL para Árabe
  • Teste a UX do seletor de locale
  • Adicione tags hreflang para SEO
  • Configure roteamento baseado em locale se necessário
  • Monitore os custos de tradução do LLM

Os Resultados

Depois de implementar esse sistema:

  • 17 idiomas suportados prontos para uso
  • ~850 strings traduzidas automaticamente
  • $2-3 de custo total para tradução completa
  • Ciclo de atualização de 2 minutos ao adicionar novas strings
  • Zero trabalho manual de tradução
  • Traduções de alta qualidade, conscientes do contexto

Compare isso com:

  • Tradutores humanos: $0.10-0.30 por palavra = $1,000+
  • Serviços tradicionais: Ainda caros, ainda lentos
  • Trabalho manual: Não escala

Por Que Isso Importa em 2026

Olha, a web é global. Se você está entregando apenas em inglês em 2026, você está deixando 90% do mundo para trás.

Mas o i18n tradicional é doloroso. Essa abordagem torna isso trivial:

  1. Escreva código com macros Trans/t (leva 2 segundos)
  2. Rode pnpm i18n (automatizado)
  3. Entregue para o mundo (lucro)

A combinação da experiência de desenvolvedor do Lingui + traduções alimentadas por LLM é um divisor de águas. Você ganha:

  • Traduções type-safe
  • Runtime com zero overhead
  • Extração automática
  • Traduções de IA conscientes do contexto
  • Centavos por idioma
  • Escala infinitamente

Indo Além

Quer subir de nível? Tente:

Tradução de Conteúdo Dinâmico

Armazene traduções no seu banco de dados:

// 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-traduza ao salvar:

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

export const blogRouter = createTRPCRouter({
  create: protectedProcedure
    .input(z.object({ title: z.string() }))
    .mutation(async ({ input }) => {
      // Traduz para todos os idiomas
      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),
      });
    }),
});

Traduções Fornecidas por Usuários

Deixe os usuários enviarem traduções melhores:

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

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

Teste A/B de Traduções

Teste quais traduções convertem melhor:

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

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

O Código

Tudo isso é código de produção de um app real. A implementação completa está no nosso monorepo:

t3-acme-app/
├── apps/nextjs/
│   ├── lingui.config.ts
│   ├── src/
│   │   ├── locales/           # Catálogos compilados
│   │   ├── utils/i18n/        # Utilitários de i18n
│   │   └── providers/         # LinguiClientProvider
│   └── script/i18n.ts         # Script de tradução
└── packages/i18n/
    └── src/
        ├── translateWithLLM.ts
        ├── enhanceTranslations.ts
        └── utils.ts

Pensamentos Finais

Construir um app de IA multilíngue em 2026 não é mais difícil. As ferramentas estão aqui:

  • Lingui para extração e runtime
  • Claude/GPT para tradução consciente do contexto
  • T3 Turbo para a melhor DX do mercado

Pare de pagar milhares de dólares por traduções. Pare de limitar seu app ao inglês.

Construa globalmente. Shipe rápido. Use IA.

É assim que fazemos em 2026.


Perguntas? Problemas? Me encontre no Twitter ou confira a documentação do Lingui e a documentação do AI SDK.

Agora vá shippar esse app multilíngue. O mundo está esperando.

Compartilhar

Feng Liu

Feng Liu

shenjian8628@gmail.com

Como Construir um Webapp Moderno com IA e i18n em 2026 | Feng Liu