Skip to content

.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.

The shape produced by Export to repo:

{
"projects": [
{
"name": "web",
"localesDir": "apps/web/locales",
"sourceLanguage": "en"
}
]
}

FieldRequiredTypeDescription
$schemanostringOptional advisory schema URL. Validation is by Zod, not the JSON Schema.
projectsyesProject[] (≥ 1)Every translation project the repo ships.
aiSystemPromptnostring (≤ 2000 chars)Repo-scoped guidance prepended to every AI translation request from this repo. Use it for tone, glossary, brand-name handling.
aiLanguagePromptsnoRecord<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).


FieldRequiredDefaultNotes
nameyes—Human label, unique within the file. Shown in the UI.
localesDiryes—Path relative to the repo root (e.g. apps/web/locales).
sourceLanguageyes—The language used by developers in code (e.g. en).
fileStructureno{lang}/{namespace}.jsonMVP supports only this value.
format.typenojsonSerialization format. Only json is supported in MVP.
format.nestednofalseIf true, keys nest into objects. If false, flat with keySeparator.
format.keySeparatorno.Separator for flat keys (matches i18next default).
plurals.enablednotrueToggle plural awareness in the editor.
plurals.suffixesnoi18next CLDR (see below)The set of plural suffixes the editor recognizes.
validation.placeholdersno["{{name}}", "{name}"]Placeholder formats checked for round-trip presence.
validation.allowHtmlnotrueIf false, target translations with HTML tags are flagged.
branchPrefixnotranslations/Prefix for new translation branches created by the app.
languageNamesno{}Human labels per language code (e.g. "uk": "Українська").
languageBcp47no—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.


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.


The defaults match i18next CLDR conventions:

["_zero", "_one", "_two", "_few", "_many", "_other"]

Override with plurals.suffixes if your codebase uses different conventions.


{
"$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)"
}
}
]
}

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.


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.


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.