{"id":49458286,"url":"https://github.com/ogrodev/tyndale","last_synced_at":"2026-04-30T08:04:06.925Z","repository":{"id":350787176,"uuid":"1208273247","full_name":"ogrodev/tyndale","owner":"ogrodev","description":"AI-powered i18n for React and Next.js","archived":false,"fork":false,"pushed_at":"2026-04-23T00:50:50.000Z","size":1124,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-23T02:20:53.289Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://tyndale-website.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ogrodev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-12T03:48:25.000Z","updated_at":"2026-04-21T17:53:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ogrodev/tyndale","commit_stats":null,"previous_names":["ogrodev/tyndale"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ogrodev/tyndale","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogrodev%2Ftyndale","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogrodev%2Ftyndale/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogrodev%2Ftyndale/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogrodev%2Ftyndale/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ogrodev","download_url":"https://codeload.github.com/ogrodev/tyndale/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ogrodev%2Ftyndale/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32458241,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T22:27:22.272Z","status":"online","status_checked_at":"2026-04-30T02:00:05.929Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-30T08:04:02.251Z","updated_at":"2026-04-30T08:04:06.916Z","avatar_url":"https://github.com/ogrodev.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- prettier-ignore --\u003e\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./apps/website/public/favicon.svg\" alt=\"Tyndale logo\" width=\"72\" height=\"72\" /\u003e\n\n# Tyndale\n\n[![Build Status](https://img.shields.io/github/actions/workflow/status/ogrodev/tyndale/ci.yml?style=flat-square\u0026label=CI)](https://github.com/ogrodev/tyndale/actions/workflows/ci.yml)\n[![npm version](https://img.shields.io/npm/v/tyndale?style=flat-square)](https://www.npmjs.com/package/tyndale)\n![Node version](https://img.shields.io/badge/Node.js-\u003e=20-3c873a?style=flat-square)\n[![TypeScript](https://img.shields.io/badge/TypeScript-blue?style=flat-square\u0026logo=typescript\u0026logoColor=white)](https://www.typescriptlang.org)\n[![License](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)](LICENSE)\n\n**Translate your app with the AI subscription you already pay for.**  \nTyndale wraps your strings, hashes them, and only sends what changed to your model.\n\nWebsite: https://tyndale.dev\n\n[Website](https://tyndale.dev) • [Why Tyndale](#why-tyndale) • [Quickstart](#quickstart) • [Supported frameworks](#supported-frameworks) • [Translate docs](#translate-documentation) • [Packages](#packages) • [Configuration](#configuration) • [Development](#development)\n\n\u003c/div\u003e\n\n## Why Tyndale\n\nIf you already pay for Claude, ChatGPT, or another AI assistant, you have everything you need to translate your app. Tyndale runs on top of that subscription. There is no separate translation API to sign up for, no per-word billing, no extra invoice at the end of the month.\n\nThe piece most i18n setups get wrong is how they handle change. Every time you tweak a string, the naive workflow is to ask an agent to re-read your locale files, compare them to source, and figure out what to update. That burns thousands of tokens on work the tool should already know how to do.\n\nTyndale fingerprints every translatable string with a content hash. When you run `translate`, it compares hashes against the manifest, sends only the new and changed entries to the model, and removes stale ones. Unchanged strings cost zero tokens. The same hashing applies to MDX and Markdown docs, with state stored in `.tyndale-docs-state.json` so a fresh clone skips work that was already done.\n\nThe setup is meant to be boring. Drop a prompt into your coding agent, let it pick the right packages and wire the framework, then start marking strings.\n\n## Quickstart\n\n### Let your agent do the integration\n\nPaste this into Claude Code, Cursor, or whichever agent you use:\n\n```text\nRead https://raw.githubusercontent.com/ogrodev/tyndale/main/skills/setup-tyndale/SKILL.md and use it to set up Tyndale in this project. Detect whether this codebase is React, Next.js, Astro, or a supported docs framework, install the right published packages, wire the framework correctly, and run the necessary Tyndale setup steps so I do not need to make the integration choices myself.\n```\n\nIf you'd rather do it by hand, the steps below cover the same ground.\n\n### 1. Install\n\n```bash\nnpm install tyndale-react\nnpm install -D tyndale\n```\n\nFor Next.js, also install the adapter:\n\n```bash\nnpm install tyndale-next\n```\n\n### 2. Initialize your project\n\n```bash\nnpx tyndale init\n```\n\nThis writes `tyndale.config.json`, updates `.gitignore`, and scaffolds Next.js middleware when needed.\n\n### 3. Sign in to your AI provider\n\n```bash\nnpx tyndale auth\n```\n\nOAuth providers open a browser and reuse your existing subscription. Providers without OAuth fall back to an API key. Either way, the credentials live on your machine.\n[Supported Providers](https://github.com/badlogic/pi-mono/tree/main/packages/ai#supported-providers)\n\n### 4. Mark translatable UI\n\n```tsx\nimport { T, useTranslation, Var, Num } from \"tyndale-react\";\n\nexport function Welcome({\n  userName,\n  count,\n}: {\n  userName: string;\n  count: number;\n}) {\n  const t = useTranslation();\n\n  return (\n    \u003cdiv\u003e\n      \u003cT\u003e\n        \u003ch1\u003e\n          Hello \u003cVar name=\"user\"\u003e{userName}\u003c/Var\u003e\n        \u003c/h1\u003e\n        \u003cp\u003e\n          You have \u003cNum value={count} /\u003e items.\n        \u003c/p\u003e\n      \u003c/T\u003e\n\n      \u003cinput placeholder={t(\"Search products...\")} /\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n### 5. Generate translations\n\n```bash\nnpx tyndale translate\n```\n\n\u003e [!TIP]\n\u003e `translate` extracts first, then sends only the changed hashes to your model. Run `npx tyndale extract` on its own if you want to inspect the manifest before any translation calls happen.\n\n### 6. Astro applications\n\nIf your `.astro` files need translating, Tyndale handles them too.\n\n1. If your Astro project does not already render React components, add Astro's React integration first.\n\n   ```bash\n   npx astro add react\n   ```\n\n   `tyndale-react` exports React components such as `\u003cT\u003e`, `\u003cVar\u003e`, and `\u003cNum\u003e`, so Astro needs the React integration to render them. See Astro's official `@astrojs/react` guide: https://docs.astro.build/en/guides/integrations-guide/react/\n\n2. Initialize Tyndale and keep `.astro` in `extensions` (added by `tyndale init` by default).\n\n   ```json\n   {\n     \"extensions\": [\".ts\", \".tsx\", \".js\", \".jsx\", \".astro\"]\n   }\n   ```\n\n3. Wrap translatable Astro content.\n\n   ```astro\n   ---\n   import { T, Var, Num } from 'tyndale-react';\n   const userName = 'Ada';\n   const count = 3;\n   ---\n\n   \u003cT\u003e\n     \u003ch1\u003eHello \u003cVar name=\"user\"\u003e{userName}\u003c/Var\u003e\u003c/h1\u003e\n     \u003cp\u003eYou have \u003cNum name=\"count\" value={count} /\u003e items.\u003c/p\u003e\n   \u003c/T\u003e\n   ```\n\n4. Run translations as usual.\n\n   ```bash\n   npx tyndale translate\n   ```\n\n### 7. For Next.js, verify the middleware\n\n```ts\n// middleware.ts\nimport { createTyndaleMiddleware } from \"tyndale-next/middleware\";\n\nexport default createTyndaleMiddleware();\n\nexport const config = {\n  matcher: [\"/((?!api|_next|_tyndale|.*\\\\..*).*)\"],\n};\n```\n\n### 8. Add the Next.js provider\n\n```tsx\n// app/[locale]/layout.tsx\nimport { getDirection, TyndaleServerProvider } from \"tyndale-next/server\";\n\nexport default async function RootLayout({\n  children,\n  params,\n}: {\n  children: React.ReactNode;\n  params: Promise\u003c{ locale: string }\u003e;\n}) {\n  const { locale } = await params;\n\n  return (\n    \u003chtml lang={locale} dir={getDirection(locale)}\u003e\n      \u003cbody\u003e\n        \u003cTyndaleServerProvider locale={locale}\u003e\n          {children}\n        \u003c/TyndaleServerProvider\u003e\n      \u003c/body\u003e\n    \u003c/html\u003e\n  );\n}\n```\n\n## Supported frameworks\n\nApp translation (`tyndale extract` / `tyndale translate`):\n\n- React\n- Vite + React\n- Next.js\n- Astro components (`.astro`)\n\nDocumentation translation (`tyndale translate-docs`):\n\n- Starlight\n- Docusaurus\n- VitePress\n- MkDocs\n- Nextra\n\n## Translate documentation\n\n`translate-docs` works on MDX and Markdown the same way `translate` works on UI strings. It detects the docs framework, preserves imports and code fences, retries when validation rejects an output, and tracks source hashes in `.tyndale-docs-state.json` so unchanged pages stay free.\n\n```bash\nnpx tyndale translate-docs setup\nnpx tyndale translate-docs\n```\n\nCommit `.tyndale-docs-state.json` so collaborators and CI inherit the same state and don't retranslate pages that nobody touched.\n\n## Features\n\n- Zero-key JSX and `.astro` extraction\n- OAuth login that reuses the AI subscription you already have\n- Content-hashed deltas so unchanged strings cost zero tokens\n- Variables, plurals, numbers, currency, and dates handled by the runtime\n- First-class Next.js support with middleware and server providers\n- Docs translation for Starlight, Docusaurus, VitePress, MkDocs, and Nextra\n- CI-friendly checking with `tyndale validate`\n\n## Packages\n\n| Package                                     | Purpose                                                                                                     |\n| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |\n| [`tyndale`](./packages/tyndale)             | CLI for `init`, `auth`, `extract`, `translate`, `translate-docs`, `validate`, and `model`                   |\n| [`tyndale-react`](./packages/tyndale-react) | Runtime components and hooks such as `\u003cT\u003e`, `useTranslation()`, `msg()`, and `useDictionary(filenameKey)`   |\n| [`tyndale-next`](./packages/tyndale-next)   | Next.js helpers including middleware, config integration, server/client providers, and static locale params |\n| [`apps/website`](./apps/website)            | Astro + Starlight documentation site for the project                                                        |\n\n## CLI overview\n\n| Command                        | Description                                                                                        |\n| ------------------------------ | -------------------------------------------------------------------------------------------------- |\n| `tyndale init`                 | Create `tyndale.config.json`, update `.gitignore`, and scaffold Next.js middleware when applicable |\n| `tyndale auth`                 | Sign in to your AI provider (OAuth or API key)                                                     |\n| `tyndale extract`              | Extract translatable source strings without translating them                                       |\n| `tyndale translate`            | Auto-extract, then translate only the changed hashes                                               |\n| `tyndale translate-docs`       | Translate MDX/Markdown docs for a supported documentation framework                                |\n| `tyndale translate-docs setup` | Detect a docs framework and write the `docs` config                                                |\n| `tyndale validate`             | Validate locale files without making AI calls                                                      |\n| `tyndale model`                | Change the configured AI model                                                                     |\n\n## Configuration\n\n`tyndale init` writes a starter config. A typical setup looks like this:\n\n```json\n{\n  \"defaultLocale\": \"en\",\n  \"locales\": [\"es\", \"fr\", \"ja\"],\n  \"source\": [\"src\", \"app\"],\n  \"extensions\": [\".ts\", \".tsx\", \".js\", \".jsx\", \".astro\"],\n  \"output\": \"public/_tyndale\",\n  \"translate\": {\n    \"tokenBudget\": 50000,\n    \"concurrency\": 8\n  },\n  \"localeAliases\": {\n    \"pt-BR\": \"pt\"\n  },\n  \"dictionaries\": {\n    \"include\": [\"src/dictionaries/*.json\"],\n    \"format\": \"key-value\"\n  },\n  \"pi\": {\n    \"model\": \"claude-sonnet-4-20250514\",\n    \"thinkingLevel\": \"low\"\n  },\n  \"docs\": {\n    \"framework\": \"starlight\",\n    \"contentDir\": \"src/content/docs\"\n  }\n}\n```\n\n\u003e [!IMPORTANT]\n\u003e `defaultLocale` must not appear in `locales`. `defaultLocale` is the source language; `locales` contains only target locales.\n\nKey fields:\n\n- `translate.tokenBudget` — token budget per translation batch\n- `translate.concurrency` — max parallel translation sessions\n- `localeAliases` — map variant locale codes to canonical ones\n- `dictionaries` — include key-value translation files alongside JSX translations\n- `docs` — configure docs framework detection and content directory\n\n## Development\n\nThis repository uses Bun workspaces.\n\n```bash\n# install dependencies\nbun install\n\n# run all tests\nbun test\n\n# type-check the workspace\nbun run typecheck\n\n# build publishable packages\nbun run build:packages\n\n# run the documentation site locally\nbun --cwd apps/website dev\n```\n\nCI runs the same core checks on pushes and pull requests.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fogrodev%2Ftyndale","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fogrodev%2Ftyndale","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fogrodev%2Ftyndale/lists"}