A real-world case study of tstlai in production. This very website you're reading is powered by tstlai for automatic AI translation. Here's exactly how we built it.
Try it now: Visit /es, /fr, /de, /ja, or try /ar for RTL Arabic — tstlai automatically sets dir="rtl"!
ZaguanLabs needed to support multiple languages across their entire marketing site without:
The solution? Use tstlai to automatically translate the entire site on-the-fly, with intelligent caching to keep costs low and performance high.
Our integration uses four key files that work together:
src/lib/translator.tsTranslator factoryCreates and caches Tstlai instances per locale with Redis caching and brand term exclusions.
src/proxy.tsMiddlewareHandles locale detection from browser headers and URL routing using tstlai's language support API.
src/app/[locale]/layout.tsxLayout wrapperInjects the AutoTranslate component for non-English locales with streaming enabled.
src/lib/session-token.tsSecurity tokensGenerates cryptographic session tokens for secure, dynamic API endpoints.
src/app/api/[sessionToken]/*/route.tsDynamic API routesTranslation endpoints with session-based paths that prevent unauthorized access.
We create a singleton factory that returns cached Tstlai instances per locale. This ensures we don't create multiple instances for the same language.
// src/lib/translator.ts
import 'server-only';
import { Tstlai } from 'tstlai';
const instances = new Map<string, Tstlai>();
export function getTranslator(locale: string = 'en') {
if (!instances.has(locale)) {
instances.set(
locale,
new Tstlai({
targetLang: locale,
sourceLang: 'en',
provider: {
type: 'openai',
apiKey: process.env.OPENAI_API_KEY,
model: 'zaguanai/gemini-flash-latest', // Google Gemini Flash - fast and efficient for translation
baseUrl: 'https://api.zaguanai.com/v1',
},
cache: {
type: process.env.REDIS_URL ? 'redis' : 'memory',
connectionString: process.env.REDIS_URL,
keyPrefix: 'zaguanlabs:',
},
// Keep brand names untranslated
excludedTerms: [
'Zaguan', 'ZaguanLabs', 'Zaguán AI',
'tstlai', 'chaTTY', 'RayLM', 'LuminaryChat'
],
translationContext: 'Tech startup website for AI developer tools and SDKs',
}),
);
}
return instances.get(locale)!;
}Key insight: We use Google's Gemini 2.5 Flash model via the Zaguán OpenAI-compatible API (accessible at https://api.zaguanai.com/v1). This model supports many languages with excellent quality and low latency for fast response times, making it ideal for real-time website translation with high accuracy and consistency across all content.
Brand protection: The excludedTerms option ensures brand names like "ZaguanLabs" and "tstlai" are never translated, maintaining brand consistency across all languages.
We use tstlai's exported language support utilities to validate locales and handle browser language negotiation.
// src/proxy.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';
import { isLanguageSupported, SHORT_CODE_DEFAULTS } from 'tstlai';
// Use tstlai's short codes for browser negotiation
const NEGOTIATION_LOCALES = Object.keys(SHORT_CODE_DEFAULTS);
export const DEFAULT_LOCALE = 'en';
function getLocale(request: NextRequest): string {
const headers = {
'accept-language': request.headers.get('accept-language') || ''
};
const languages = new Negotiator({ headers }).languages();
return match(languages, NEGOTIATION_LOCALES, DEFAULT_LOCALE);
}
export function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
const segments = pathname.split('/');
const potentialLocale = segments[1];
// Validate locale using tstlai's language support
if (potentialLocale && /^[a-z]{2}$/.test(potentialLocale)) {
if (isLanguageSupported(potentialLocale)) {
return; // Valid locale, continue
}
// Unsupported language - redirect to English
segments[1] = 'en';
return NextResponse.redirect(new URL(segments.join('/'), request.url));
}
// No locale in URL, detect from browser
const locale = getLocale(request);
return NextResponse.redirect(new URL(`/${locale}${pathname}`, request.url));
}This is where the magic happens. We add a single component to our locale layout, and the entire app translates automatically. No changes to any page components needed.
// src/app/[locale]/layout.tsx
import { AutoTranslate } from 'tstlai/next';
import { getTranslator } from '@/lib/translator';
import { generateSessionToken } from '@/lib/session-token';
interface LocaleLayoutProps {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}
export default async function LocaleLayout({
children,
params
}: LocaleLayoutProps) {
const { locale } = await params;
const translator = getTranslator(locale);
const dir = translator.getDir(); // 'ltr' or 'rtl'
// Generate a unique session token for secure API access
const sessionToken = locale !== 'en' ? generateSessionToken() : null;
return (
<div dir={dir} lang={locale}>
{children}
{/* AutoTranslate with dynamic, secure endpoints */}
{locale !== 'en' && sessionToken && (
<AutoTranslate
targetLang={locale}
stream={true}
endpoint={`/api/${sessionToken}/translate`}
streamEndpoint={`/api/${sessionToken}/stream`}
streamBuffer={500}
/>
)}
</div>
);
}Secure by default! Each page render generates a unique session token that becomes part of the API path. This prevents attackers from guessing or abusing your translation endpoints. The AutoTranslate component scans the DOM, extracts text nodes, and streams translations back. The getDir() method automatically returns 'rtl' for Arabic, Hebrew, Persian, and other RTL languages.
We use Next.js dynamic routes with session tokens in the path. Each request validates the token before processing, preventing unauthorized access to the translation API.
// src/app/api/[sessionToken]/stream/route.ts
import { createNextStreamingRouteHandler } from 'tstlai/next';
import { getTranslator } from '@/lib/translator';
import { validateSessionToken } from '@/lib/session-token';
interface RouteParams {
params: Promise<{ sessionToken: string }>;
}
export const POST = async (req: Request, { params }: RouteParams) => {
const { sessionToken } = await params;
// Validate session token before processing
if (!validateSessionToken(sessionToken)) {
return new Response(JSON.stringify({ error: 'Invalid session' }), {
status: 401,
headers: { 'Content-Type': 'application/json' },
});
}
const body = await req.json();
const { targetLang } = body;
const translator = getTranslator(targetLang || 'en');
const handler = createNextStreamingRouteHandler(translator);
return handler(new Request(req.url, {
method: 'POST',
headers: req.headers,
body: JSON.stringify(body),
}));
};Why dynamic routes? Static endpoints like /api/translate can be discovered and abused. With session tokens in the path, attackers would need to guess a cryptographically random string to access the API.
The streaming endpoint with a 500ms buffer provides a much smoother experience than waiting for all translations to complete. Users see content appearing progressively.
Memory caching works for development, but Redis ensures translations persist across deployments and server restarts. Most page loads hit the cache with ~50ms latency.
Adding brand names to excludedTerms from the start prevents awkward translations like "ZaguanLabs" becoming "LaboratoriosZaguan".
Setting translationContext to describe your site helps the AI make better word choices. "Tech startup website" produces different translations than "children's educational content".
Public translation APIs can be abused to run up your AI costs. Using session-based dynamic routes means attackers can't discover or guess your endpoints. Each page render gets a unique, cryptographically secure path.