.translations.json Reference
.translations.json declares one or more translation projects in a repo, each with its own locales directory and source language.
The file is optional. By default the wizard saves your project config to local app storage (see Repo Onboarding). .translations.json only appears at the repo root if you opt in to the Export to repo flow, which is how a team shares its AI prompts. The app never reads the file on its own; the only reader is Import prompts from repo, which takes the two prompt fields and nothing else. The schema below is the source of truth for both shapes.
Minimal example
Section titled “Minimal example”The shape produced by Export to repo:
{ "projects": [ { "name": "web", "localesDir": "apps/web/locales", "sourceLanguage": "en" } ]}Top-level fields
Section titled “Top-level fields”| Field | Required | Type | Description |
|---|---|---|---|
$schema | no | string | Optional advisory schema URL. Validation is by Zod, not the JSON Schema. |
projects | yes | Project[] (≥ 1) | Every translation project the repo ships. |
aiSystemPrompt | no | string (≤ 2000 chars) | Repo-scoped guidance prepended to every AI translation request from this repo. Use it for tone, glossary, brand-name handling. |
aiLanguagePrompts | no | Record<lang, string> (≤ 1000 chars each) | Guidance for one language, sent only with that language’s requests. Keys are locale folder names. |
Unknown top-level fields are preserved silently (forward compatibility — the schema uses passthrough() at every level).
Project fields
Section titled “Project fields”| Field | Required | Default | Notes |
|---|---|---|---|
name | yes | — | Human label, unique within the file. Shown in the UI. |
localesDir | yes | — | Path relative to the repo root (e.g. apps/web/locales). |
sourceLanguage | yes | — | The language used by developers in code (e.g. en). |
fileStructure | no | {lang}/{namespace}.json | MVP supports only this value. |
format.type | no | json | Serialization format. Only json is supported in MVP. |
format.nested | no | false | If true, keys nest into objects. If false, flat with keySeparator. |
format.keySeparator | no | . | Separator for flat keys (matches i18next default). |
plurals.enabled | no | true | Toggle plural awareness in the editor. |
plurals.suffixes | no | i18next CLDR (see below) | The set of plural suffixes the editor recognizes. |
validation.placeholders | no | ["{{name}}", "{name}"] | Placeholder formats checked for round-trip presence. |
validation.allowHtml | no | true | If false, target translations with HTML tags are flagged. |
branchPrefix | no | translations/ | Prefix for new translation branches created by the app. |
languageNames | no | {} | Human labels per language code (e.g. "uk": "Українська"). |
languageBcp47 | no | — | Maps folder names to BCP 47 tags (absent unless set). Use when your folder names aren’t BCP 47 (e.g. ua → uk-UA, cn → zh-CN). The mapped tag is used as the lang attribute on cell textareas to drive correct spell-check. Each value is validated against /^[A-Za-z][A-Za-z0-9]{1,7}(?:-[A-Za-z0-9]{2,8})*$/. |
Unknown per-project fields are preserved silently.
Language autodetection
Section titled “Language autodetection”You do not declare languages in .translations.json. The app scans <localesDir>/* and treats every subfolder matching the BCP 47 pattern as a language:
apps/web/locales/ en/ ← detected uk/ ← detected pt-BR/ ← detected _archive/ ← ignored (doesn't match pattern) README.md ← ignored (not a directory)To add a new language, create the folder. No config change needed.
Default plural suffixes
Section titled “Default plural suffixes”The defaults match i18next CLDR conventions:
["_zero", "_one", "_two", "_few", "_many", "_other"]Override with plurals.suffixes if your codebase uses different conventions.
Multi-project example
Section titled “Multi-project example”{ "$schema": "https://stape.io/schemas/translations-v1.json", "projects": [ { "name": "web", "localesDir": "apps/web/locales", "sourceLanguage": "en", "branchPrefix": "translations/web/" }, { "name": "marketing-site", "localesDir": "apps/marketing/i18n", "sourceLanguage": "en", "languageNames": { "uk": "Українська", "pt-BR": "Português (Brasil)" } } ]}AI prompt and BCP 47 overrides
Section titled “AI prompt and BCP 47 overrides”Combine the top-level aiSystemPrompt with per-project languageBcp47 overrides when your folder names don’t follow BCP 47:
{ "aiSystemPrompt": "Use formal Ukrainian (вживайте «Ви»). Brand name 'Stape' stays in English — never transliterate.", "projects": [ { "name": "web", "localesDir": "apps/web/locales", "sourceLanguage": "en", "languageBcp47": { "ua": "uk-UA", "cn": "zh-CN" } } ]}The bcp47For(project, folder) helper returns the mapped tag, or the folder name itself if no mapping exists. The result drives the lang attribute on cell textareas so the OS picks the right spell-check dictionary.
Validation
Section titled “Validation”The schema is enforced with Zod on app load. Field-level errors surface with JSON-pointer paths (e.g. projects[0].localesDir) so you can jump straight to the offending key. Unknown fields pass through silently.
Bootstrap wizard
Section titled “Bootstrap wizard”The first time you open a repo, the in-app wizard collects the required fields and saves them to local app storage, not to the working tree. Adding a repo never produces stray files in git status.
The wizard also exposes an opt-in Export to repo as .translations.json action. It writes the file into the working tree as an unstaged change; commit and push it through your normal git flow. A committed file does not let a teammate skip the wizard: their app reads only its prompts, and only when they click Import prompts from repo (see Sharing prompts with your team). See Repo onboarding for the full export story.