Organizing translation files: naming, layout, and why structure decides your merge conflicts
Translation files rarely get architecture review the way application code does, but the way you organize them has real consequences: how readable your diffs are, how badly translators working in parallel collide with each other, and how easy it is to tell whether a language is missing a string. Most of these problems are decided the day you pick a file layout, not later.
One file per language, or one per feature
There are two common layouts. The first is one file per language: en.json holds every string in English, ja.json holds every string in Japanese, and so on. The second is one file per feature or screen, each containing every language for that feature — checkout.json with an en and ja key inside, for instance.
One-file-per-language is simpler to hand to a translator: they get exactly one file with everything they need to translate, and nothing else. One-file-per-feature keeps related strings together and scales better as an app grows, since a change to the checkout flow only touches checkout.json rather than requiring an edit in every language file. Larger projects with many languages tend to move toward per-feature files precisely because it isolates changes; smaller projects with two or three languages often don't feel the pain either way.
Naming conventions with language tags
Whichever layout you choose, name files with a standard language tag rather than an invented abbreviation. BCP 47 tags (en, ja, pt-BR, zh-Hant) are what most tooling, browsers, and platforms already expect, so a file named ja.json or pt-BR.json is unambiguous to both humans and software. A file named japanese.json or jp.json works until it meets a library that expects the standard tag and doesn't recognize yours.
The region subtag matters when a language has real regional variants with different vocabulary or spelling, like pt-BR versus pt-PT. Don't add a region subtag out of habit for languages that don't need one — it just adds a layer nobody consults.
locales/ en.json ja.json pt-BR.json zh-Hant.json
Where the source language lives
Every project has one language that strings are originally written in — usually the language the product is designed in. That source file deserves a clear, unambiguous position: developers add and edit keys there first, and every other language file is understood as a translation of it, not an independent document.
Treating the source file as just another language file, with no special status, tends to produce drift: keys added to one non-source file that don't exist anywhere else, or two language files disagreeing about what a key even means because neither is treated as the reference.
Stable key order makes diffs readable
Many translation file formats don't require any particular key order, which means a tool or editor is free to re-sort the whole file alphabetically, or reorder it however it wants, every time it saves. When that happens, a change that added one string produces a diff touching every line in the file, because the surrounding lines all moved.
Keeping key order stable — new keys appended at the end, or grouped by feature in a fixed sequence, rather than resorted on every save — means a diff shows only the lines that actually changed. This is a small discipline that pays off entirely in code review: a reviewer can tell at a glance whether a change added, removed, or edited a string, instead of scanning a wall of moved lines for the one that matters.
- Pick one ordering rule (alphabetical, or feature-grouped) and stick to it deliberately
- Configure any auto-formatting tool to preserve that order rather than re-sorting
- Append new keys rather than inserting them in the middle where possible
How structure shapes merge conflicts
When several people translate into different languages at the same time, one-file-per-language naturally avoids conflicts between translators, since each person edits a different file. But it doesn't protect against a developer adding a key to the source file and a translator editing the same file for an unrelated string at the same time.
One-file-per-feature has the opposite trade-off: two translators working on the same feature in different languages might touch the same file if all languages live together, but a developer's key addition and a translator's wording change are more likely to land in files scoped narrowly enough that they rarely overlap. Neither layout eliminates conflicts entirely — the goal is picking the one where the conflicts you'll actually have are the ones that are easy to resolve.