Better TranslateHome
GitHub
Getting Started
  • Introduction
  • Mission
  • Installation
  • CLI
  • Skills
  • RTL
  • Changelog
Adapters
  • Core
  • React
  • Expo
  • Astro
  • MD & MDX
  • Next.js
  • TanStack Router

CLI

Use @better-translate/cli when you want Better Translate to build and update locale files for you.

You do not need the CLI to use the runtime packages. It is optional.

1. Install the package

npm install -D @better-translate/cli

The CLI stays provider-agnostic. Your app installs and configures the AI SDK provider package directly.

2. Create a source locale file

Create src/messages/en.json:

json
1{2  "home": {3    "title": "Hello",4    "description": "Welcome to the app"5  }6}

You can also start with an empty {} and let bt extract populate it.

3. Create the CLI config

Create better-translate.config.ts:

ts
1import { createOllama } from "ollama-ai-provider-v2";2import { defineConfig } from "@better-translate/cli/config";34const ollama = createOllama({5  baseURL: process.env.OLLAMA_BASE_URL ?? "http://localhost:11434/api",6});78export default defineConfig({9  sourceLocale: "en",10  locales: ["es", "fr"],11  model: ollama("qwen3:4b"),12  messages: {13    entry: "./src/messages/en.json",14  },15});

If you use Ollama, install ollama-ai-provider-v2. If you use a hosted provider, install that provider package instead, such as @ai-sdk/openai, @ai-sdk/anthropic, or @ai-sdk/moonshotai.

The default Ollama API URL is local: http://localhost:11434/api.

This works the same with hosted providers such as OpenAI, Anthropic, and Moonshot AI. The CLI does not bundle provider helpers anymore.

4. Mark strings in your code

Instead of naming translation keys by hand, write the source text directly and add { bt: true }:

ts
1import { t } from "@better-translate/core";23export function navLabel() {4  return t("Home", { bt: true });5}

At runtime, { bt: true } returns the string unchanged. The CLI will replace these calls with proper keys on the next extract.

You can also pass other options like params — they are preserved after extraction:

ts
1// You write:2t("Hello world", { bt: true })3t("Hello {name}", { bt: true, params: { name: "" } })45// After bt extract rewrites the file:6t("components.nav.helloWorld")7t("components.nav.helloName", { params: { name: "" } })

The key namespace comes from the source file path (components/nav.tsx → components.nav). bt: true is always removed on rewrite.

5. Extract source keys

npx bt extract

This scans for t(..., { bt: true }) calls, adds the missing keys to your source locale file, and rewrites the calls to plain strict keys.

The CLI automatically finds better-translate.config.ts in your project root. The --config flag is only needed if your config file is in a different location.

6. Run the generator

npx bt generate

This creates the target locale files next to your source file.

If markdown.rootDir is enabled and the run would create or overwrite translated .md or .mdx files, the CLI asks for confirmation before making changes. Use --yes or -y to skip the prompt:

npx bt generate --yes

Non-interactive runs that need to write translated markdown files must pass --yes.

7. Remove unused keys

npx bt purge

This scans your codebase for translation keys that are no longer referenced in any t("...") call and removes them from all locale files. It prompts you to confirm each key individually before removing it:

tsx
1? Purge unused key "home.oldTitle"? (y/N) y2? Purge unused key "sidebar.legacy"? (y/N) n

Type y to remove a key from every locale file, or n (or just Enter) to keep it.

To remove all unused keys at once without prompting:

npx bt purge --yes

To preview what would be removed without writing any changes:

npx bt purge --dry-run

Dynamic keys: If your code uses a dynamic key like t(`section.${id}`), the CLI cannot statically resolve it. It will warn you, protect any keys sharing the detected prefix, or mark the key as unsafe and skip it rather than silently deleting something that might still be in use.

8. Use the generated files in your app

After the files exist, import them into your @better-translate/core config just like any hand-written locale file.

Markdown

If you also want localized markdown generation, add the markdown.rootDir option:

ts
1import { createOllama } from "ollama-ai-provider-v2";2import { defineConfig } from "@better-translate/cli/config";34const ollama = createOllama({5  baseURL: process.env.OLLAMA_BASE_URL ?? "http://localhost:11434/api",6});78export default defineConfig({9  sourceLocale: "en",10  locales: ["es", "fr"],11  model: ollama("qwen3:4b"),12  messages: {13    entry: "./src/messages/en.json",14  },15  markdown: {16    rootDir: "./content/docs",17  },18});

Command reference

CommandFlagDescription
bt extractScan for bt: true calls, add keys to source locale, rewrite calls
--config <path>Path to config file (default: auto-detected)
--dry-runPreview changes without writing
--max-length <n>Max segment length for generated key names
bt generateTranslate source locale into all target locale files
--config <path>Path to config file
--dry-runPreview changes without writing
--yes, -ySkip confirmation for markdown file writes
bt purgeRemove unused translation keys from all locale files
--config <path>Path to config file
--dry-runPreview which keys would be removed without writing
--yes, -yRemove all unused keys without prompting

Examples

Full working examples are in the GitHub repo:

  • nextjs-example
  • react-vite-example
  • core-elysia-example