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 to share defaults with teammates. The schema below is the source of truth for both shapes.

The shape produced by Export to repo (and the shape an imported .translations.json must conform to):

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

Unknown top-level fields are preserved silently (forward compatibility — the schema uses passthrough() at every level).


FieldRequiredDefaultNotes
nameyesHuman label, unique within the file. Shown in the UI.
localesDiryesPath relative to the repo root (e.g. apps/web/locales).
sourceLanguageyesThe 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": "Українська").
languageBcp47noMaps folder names to BCP 47 tags (absent unless set). Use when your folder names aren’t BCP 47 (e.g. uauk-UA, cnzh-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.

If your team wants to share project defaults — so a teammate’s first run can skip the wizard — the wizard 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. See Repo onboarding for the full export story.