Skip to content
All posts

Branchlate· Part 1 of 2

Translations that follow Git branches

The developer syncs their branch, the business fixes copy in the app, CI checks. Nobody waits for anybody, and nobody overwrites anybody's work.

4 min readLire en français

A translated application does not hard-code its text. It uses keys, and one file per language maps each key to its text. The code says “show this key”, the French file answers “Commander”, the English one “Place order”.

The code
<Button>
{t("checkout.submit")}
</Button>
The language files
en.json"checkout.submit": "Place order"fr.json"checkout.submit": "Commander"
The screen
enPlace orderfrCommander
The code only holds keys; each language has its own file. Changing a text means changing one line of a file, not the code.

In most teams, these files live badly. Developers change them in the code; the business fixes copy in a spreadsheet or a separate tool; someone copies it over by hand, and the two drift apart. Every feature ends with the same question: “is the copy ready?”.

Why follow Git branches

Code lives in branches: each feature has its own, reviewed then merged. Copy should follow the same road, since it changes with the code that displays it. Branchlate starts from that simple idea: one Git branch, one translation branch.

The business works on the feature's branch, before it is merged, without waiting for a release. And translations reach the product with the code that uses them, in the same review.

mainfeature/checkoutBranchlate update needed validated
  1. DevA branch, a new keyA key added in English, then branchlate sync. Branchlate creates the same branch from the files; French is missing: red dot.
  2. BusinessTranslates and validates, without touching codeIn the web app, or right on the product through the Chrome extension. The validated text turns green.
  3. DevPulls the validated copyA second sync brings the text back into the files. The developer commits it like any other change.
  4. CIChecks before the mergebranchlate status fails if a language is incomplete, or if the files are out of sync.
  5. DevMerge, then clean upTranslations reach main through Git, with the code. The translation branch can go.
One feature, from first commit to merge. Dashed: the branch's copy in Branchlate, born at the first sync and gone after the merge. Everyone works in their own tool, and Git stays the only road to main.

The challenge. Translations live in two places at once: in the repository's files, which developers change, and in Branchlate, where the business edits them. Both change at the same time, and one side's work must never erase the other's.

Who wins?

Git cannot see Branchlate's database, and the database cannot see Git's history: a real three-way merge, as Git does for code, is impossible. So the rule lives in each text's state: has a human touched it, and did they validate it?

The file and Branchlate both changed: who wins?

  • No doten.jsonBranchlatethe code wins

    Nobody touched it in Branchlate: it belongs to the code.

    —

  • Update neededen.jsonBranchlateBranchlate wins, the file is rewritten

    It is in the review flow: it is not overwritten silently.

    An explicit option lets the code win, and flags the text for review.

  • Validateden.jsonBranchlateBranchlate wins, the file is rewritten

    A human validated it: it is protected.

    Only a forced sync, confirmed key by key, overrides it.

Git cannot see the database, the database cannot see Git's history: no three-way merge is possible. So the rule lives in each value's state. The list of keys always comes from the reference language's file.

What the developer sees

One command, in the terminal or in CI. It detects the branch, sends the files, and rewrites the ones that changed. A second one is the safety net before the merge.

~/app · feature/checkout
$ branchlate sync🌿 Branch createdfr  ⬇ Local key addedcheckout.submit  null✅ Project synced successfully. $ branchlate statusen 100% validatedfr 99% validated- checkout.submit$ echo $?1
The CLI, output simplified. It sends the files as they are; the server reads them and sends back the new version of each. One parser per format, hence stable diffs.

What I take from it

  • Git stays the road to production: translations travel with the code, in the same review.
  • Every text knows who owns it: the code, or the person who validated it.
  • CI says no before a missing string reaches production.

Maxime Burri

Working on something similar? Let's talk.

Send an email