AI-powered i18n for React, Next.js, and Astro.
Write your app in one language, run the CLI, and generate translated UI and docs.
Overview • Supported frameworks • Quickstart • Translate docs • Packages • Configuration • Development
Tyndale is a Bun monorepo with three publishable packages:
tyndale— CLI for extracting, translating, validating, and initializing projectstyndale-react— React components and hooks for runtime translationtyndale-next— Next.js adapter for locale routing, providers, and static generation helpers
It is built for a zero-key workflow: wrap JSX or .astro templates with <T>, mark plain strings with msg() or useTranslation(), then let the CLI generate locale files for your app. Tyndale can also translate MDX/Markdown documentation with translate-docs.
- React
- Vite + React
- Next.js
- Astro components (
.astro)
- Starlight
- Docusaurus
- VitePress
- MkDocs
- Nextra
- Zero-key JSX and
.astrostring extraction for app translation - AI-powered translation using your configured provider
- Incremental app translation based on deltas
- Rich formatting support for variables, plurals, numbers, currency, and dates
- First-class Next.js support with middleware and server providers
- Docs translation for Starlight, Docusaurus, VitePress, MkDocs, and Nextra
- CI-friendly validation with
tyndale validate
npm install tyndale-react
npm install -D tyndaleIf you are using Next.js, also install the adapter:
npm install tyndale-nextnpx tyndale initThis creates tyndale.config.json, updates .gitignore, and scaffolds middleware for Next.js projects when needed.
npx tyndale authimport { T, useTranslation, Var, Num } from 'tyndale-react';
export function Welcome({ userName, count }: { userName: string; count: number }) {
const t = useTranslation();
return (
<div>
<T>
<h1>Hello <Var name="user">{userName}</Var></h1>
<p>You have <Num value={count} /> items.</p>
</T>
<input placeholder={t('Search products...')} />
</div>
);
}npx tyndale translateTip
translate auto-runs extraction first, then translates only changed strings. Run npx tyndale extract by itself when you want to inspect the extracted manifest before making translation calls.
If you want to translate .astro pages/components, Tyndale supports that too.
-
If your Astro project does not already render React components, add Astro's React integration first.
npx astro add react
tyndale-reactexports React components such as<T>,<Var>, and<Num>, so Astro needs the React integration to render them. See Astro's official@astrojs/reactguide: https://docs.astro.build/en/guides/integrations-guide/react/ -
Initialize Tyndale and keep
.astroinextensions(added bytyndale initby default).{ "extensions": [".ts", ".tsx", ".js", ".jsx", ".astro"] } -
Wrap translatable Astro content.
--- import { T, Var, Num } from 'tyndale-react'; const userName = 'Ada'; const count = 3; --- <T> <h1>Hello <Var name="user">{userName}</Var></h1> <p>You have <Num name="count" value={count} /> items.</p> </T>
-
Run translations as usual.
npx tyndale translate
// middleware.ts
import { createTyndaleMiddleware } from 'tyndale-next/middleware';
export default createTyndaleMiddleware();
export const config = {
matcher: ['/((?!api|_next|_tyndale|.*\\..*).*)'],
};// app/[locale]/layout.tsx
import { getDirection, TyndaleServerProvider } from 'tyndale-next/server';
export default async function RootLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
return (
<html lang={locale} dir={getDirection(locale)}>
<body>
<TyndaleServerProvider locale={locale}>
{children}
</TyndaleServerProvider>
</body>
</html>
);
}Tyndale does not stop at app strings. translate-docs translates MDX and Markdown documentation, detects supported docs frameworks, preserves imports and code fences, and retries invalid outputs when validation fails.
npx tyndale translate-docs setup
npx tyndale translate-docsSupported frameworks: Starlight, Docusaurus, VitePress, MkDocs, Nextra.
translate-docs writes .tyndale-docs-state.json at the project root to track source document hashes. Commit it so fresh clones can skip unchanged docs instead of retranslating everything.
| Package | Purpose |
|---|---|
tyndale |
CLI for init, auth, extract, translate, translate-docs, validate, and model |
tyndale-react |
Runtime components and hooks such as <T>, useTranslation(), msg(), and useDictionary(filenameKey) |
tyndale-next |
Next.js helpers including middleware, config integration, server/client providers, and static locale params |
apps/website |
Astro + Starlight documentation site for the project |
| Command | Description |
|---|---|
tyndale init |
Create tyndale.config.json, update .gitignore, and scaffold Next.js middleware when applicable |
tyndale auth |
Configure AI provider credentials |
tyndale extract |
Extract translatable source strings without translating them; useful for inspection and review |
tyndale translate |
Auto-extract, then translate changed app strings for configured locales |
tyndale translate-docs |
Translate MDX/Markdown docs for a supported documentation framework |
tyndale translate-docs setup |
Detect a docs framework and write the docs config |
tyndale validate |
Validate locale files without making AI calls |
tyndale model |
Change the configured AI model |
tyndale init creates a starter config. A typical setup looks like this:
{
"defaultLocale": "en",
"locales": ["es", "fr", "ja"],
"source": ["src", "app"],
"extensions": [".ts", ".tsx", ".js", ".jsx", ".astro"],
"output": "public/_tyndale",
"translate": {
"tokenBudget": 50000,
"concurrency": 8
},
"localeAliases": {
"pt-BR": "pt"
},
"dictionaries": {
"include": ["src/dictionaries/*.json"],
"format": "key-value"
},
"pi": {
"model": "claude-sonnet-4-20250514",
"thinkingLevel": "low"
},
"docs": {
"framework": "starlight",
"contentDir": "src/content/docs"
}
}Important
defaultLocale must not appear in locales. defaultLocale is the source language; locales contains only target locales.
Key fields:
translate.tokenBudget— token budget per translation batchtranslate.concurrency— max parallel translation sessionslocaleAliases— map variant locale codes to canonical onesdictionaries— include key-value translation files alongside JSX translationsdocs— configure docs framework detection and content directory
This repository uses Bun workspaces.
# install dependencies
bun install
# run all tests
bun test
# type-check the workspace
bun run typecheck
# build publishable packages
bun run build:packages
# run the documentation site locally
bun --cwd apps/website devCI runs the same core checks on pushes and pull requests.