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/cliThe 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:
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:
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 }:
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:
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 extractThis 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 generateThis 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 --yesNon-interactive runs that need to write translated markdown files must pass --yes.
7. Remove unused keys
npx bt purgeThis 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:
1? Purge unused key "home.oldTitle"? (y/N) y2? Purge unused key "sidebar.legacy"? (y/N) nType 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 --yesTo preview what would be removed without writing any changes:
npx bt purge --dry-runDynamic 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:
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
| Command | Flag | Description |
|---|---|---|
bt extract | Scan for bt: true calls, add keys to source locale, rewrite calls | |
--config <path> | Path to config file (default: auto-detected) | |
--dry-run | Preview changes without writing | |
--max-length <n> | Max segment length for generated key names | |
bt generate | Translate source locale into all target locale files | |
--config <path> | Path to config file | |
--dry-run | Preview changes without writing | |
--yes, -y | Skip confirmation for markdown file writes | |
bt purge | Remove unused translation keys from all locale files | |
--config <path> | Path to config file | |
--dry-run | Preview which keys would be removed without writing | |
--yes, -y | Remove all unused keys without prompting |
Examples
Full working examples are in the GitHub repo: