Skip to content

build(i18n): give each language a flat file so a translation diff is reviewable - #2215

Merged
datlechin merged 1 commit into
mainfrom
feat/localization-workflow
Aug 19, 2026
Merged

build(i18n): give each language a flat file so a translation diff is reviewable#2215
datlechin merged 1 commit into
mainfrom
feat/localization-workflow

Conversation

@datlechin

Copy link
Copy Markdown
Member

Localizable.xcstrings interleaves every language inside every key, so a translator changing fifty Vietnamese strings edits a 109,196-line file next to Turkish and Chinese they never touched. That is the real reason translation contributions stall, and the app ships four non-English languages sitting at 3,827 of 3,829 strings each with no route for anyone outside the project to move them.

scripts/localization.py gives one flat file per language:

scripts/localization.py status              # coverage per language
scripts/localization.py export vi           # Localization/mac.vi.json
scripts/localization.py import vi           # merge back
scripts/localization.py verify              # round-trip is byte exact

--target ios covers the iPhone catalog, which is separate.

Why this is not Crowdin

Crowdin was the obvious answer and I could not verify it works here. Its Apple String Catalog support is an exporter: the store page describes it as generating .xcstrings files from Crowdin project strings, which is the opposite direction from ingesting one as a source file. I could not confirm .xcstrings is accepted as a source format before this session's search budget ran out, so shipping a crowdin.yml would have been a guess. Weblate is already ruled out: it cannot read String Catalogs at all.

This needs no service, no account and no decision, and it does not block adopting a platform later.

Why the exported file is not committed

Because it turned out not to be necessary. The 109k-line problem is caused by reformatting the catalog, not by the format itself. Once a merge is byte exact for everything it did not translate, one changed string produces a two-line diff in the catalog, which is perfectly reviewable. Verified: changing a single Vietnamese string gives 1 file changed, 1 insertion(+), 1 deletion(-).

So Localization/ is ignored, the catalog stays the single source of truth, and the repository does not gain four megabytes of duplicated strings that can drift.

Byte-exactness needs three Xcode quirks reproduced, all covered by verify: the key separator is " : " rather than ": ", an empty object is written across three lines rather than as {}, and there is no trailing newline. Keys keep the catalog's own order; sorting them looks tidier and rewrites the whole file.

Two bugs the round-trip test caught

Both were mine, and both would have corrupted translations silently.

.strip() on the merged value destroyed meaningful whitespace. It was meant to detect a blank translation, but it also rewrote the stored string: "\n\n… (%d characters not shown)" came back as "… (%d characters not shown)" in all four languages. Blankness is now tested against the stripped form while the raw value is stored.

Re-importing an unedited file flipped needs_review to translated on 14 strings. State belongs to whoever reviewed the string, not to whoever last ran the script, and rewriting it also meant a no-op round-trip produced a diff, defeating the point. The merge now writes state only when the value actually changed, and the export surfaces state read-only so a translator can find what still needs attention.

Verified on all six language/catalog pairs: export then import with no edits leaves both catalogs byte identical.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin
datlechin merged commit 6c245a1 into main Aug 19, 2026
9 checks passed
@datlechin
datlechin deleted the feat/localization-workflow branch August 19, 2026 02:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant