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.

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:
- Lingui voor de extractie, compilatie en runtime-magie
- 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.

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.

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 i18nom 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:
- Schrijf code met Trans/t macro's (kost 2 seconden)
- Draai
pnpm i18n(geautomatiseerd) - 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
shenjian8628@gmail.com