fix(i18n): stop xcstrings from diffing on every Xcode build#832
Open
jbergler wants to merge 3 commits into
Open
fix(i18n): stop xcstrings from diffing on every Xcode build#832jbergler wants to merge 3 commits into
jbergler wants to merge 3 commits into
Conversation
Xcode rewrites String Catalogs (reformats + reorders keys) on essentially every local build via SWIFT_EMIT_LOC_STRINGS — confirmed this doesn't reproduce under plain `xcodebuild` CLI, only Xcode.app's own build/run path. Xcode's key order isn't alphabetical or locale-collated (looks like Swift's hash-seeded Dictionary order), so no external tool can match it byte-for-byte — that part isn't fixable. What was fixable: the repo's own catalog writers (seed-string-catalog.py, translate-de.py, translate-it.py) used plain json.dumps(indent=2), a different format from Xcode's own, so every commit compounded the noise on top of Xcode's unavoidable reordering. Adds Tools/xcstrings_format.py: a stable canonical writer (sorted keys, Xcode's " : " spacing and no-trailing-newline convention, and its non-collapsing empty-object formatting) used by all three catalog-writing scripts, plus a CLI to canonicalize any catalog on demand — collapses a build-triggered working-tree diff back down to real changes for a quick sanity check. Tools/git-hooks/pre-commit re-applies it to staged catalogs so committed history stays diff-friendly regardless of what Xcode did locally (opt in: `git config core.hooksPath Tools/git-hooks`). Also lands the real backlog the canonicalizer surfaced: 198 strings added to Swift source across past PRs (several merged without a local Xcode compile, per project convention) had never been synced into Strand/Resources/Localizable.xcstrings until this build did it. Documents the whole thing in CONTRIBUTING.md so the next person who sees a scary full-file diff after pressing Run knows it's expected, not a bug. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0181ZawDbdJ5dpNtYegkL53s
Code review on the previous commit caught two inaccuracies: it claimed the writer matches Xcode's own serialization byte-for-byte (it can't — Xcode's key order isn't reproducible, as the xcstrings_format.py module docstring already correctly says), and its documented run command (`python3 -m unittest Tools.test_xcstrings_format -v` from repo root) fails with ModuleNotFoundError since Tools isn't a package. Matches the one invocation that actually works. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0181ZawDbdJ5dpNtYegkL53s
…mmit CI's i18n-coverage gate (Tools/i18n_audit.py --ci) treats full de/es/fr/pt-PT translation coverage as a standing invariant (the ryanbr#453 backlog is closed), and correctly failed this PR: the 221 strings the earlier commit synced in from Xcode's extraction had no translations yet, which is real, separate work — not something to wave through alongside a formatting fix, and not something to auto-generate here (see the project's own caution against machine-translated "slop", ryanbr#543). Restores all four catalogs to their pre-PR content (verified byte-for-byte identical via JSON comparison against origin/main), reformatted only through Tools/xcstrings_format.py. Confirmed `i18n_audit.py --ci` is green again and the 42 Python unit tests still pass. The 221-string backlog itself is separate follow-up work, to be landed with real translations. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0181ZawDbdJ5dpNtYegkL53s
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.xcstringsString Catalogs show a full-file diff after nearly every local Xcode build/run: Xcode auto-syncs catalogs against source (SWIFT_EMIT_LOC_STRINGS: YES) via its own IDE build path only (confirmedxcodebuildCLI never triggers it), rewriting the whole file in its own formatting and key order every time.Dictionaryorder) and can't be reproduced byte-for-byte by outside tooling — that part is inherent and not fixable from here.seed-string-catalog.py,translate-de.py,translate-it.py) each wrote catalogs via plainjson.dumps(indent=2), a format different from Xcode's own — compounding noise on top of Xcode's unavoidable reordering.Tools/xcstrings_format.py: a stable, deterministic canonical writer (sorted keys, Xcode's" : "spacing and no-trailing-newline convention, and its non-collapsing empty-object formatting), used by all three catalog-writing scripts, plus a CLI to canonicalize any catalog on demand — collapses a build-triggered working-tree diff back to real changes for a quick "did anything actually change" check.Tools/git-hooks/pre-committo re-canonicalize any staged.xcstringsfile automatically (opt in viagit config core.hooksPath Tools/git-hooks), so committed history stays diff-friendly regardless of what Xcode's local rewrite left behind.docs/CONTRIBUTING.md.Not in this PR: an earlier version of this PR also landed ~221 strings that Xcode's extraction had synced in from Swift source but that had never made it into the catalogs (several PRs were merged without a local Xcode compile, per project convention). CI's i18n-coverage gate correctly failed on that — full de/es/fr/pt-PT translation coverage is a standing invariant here, and those 221 strings had no translations yet. That's real, separate work, not something to auto-generate alongside a formatting fix, so it's been dropped from this PR: all four catalogs are now reformatted-only, byte-for-byte identical in content to
main. The backlog itself is left for separate follow-up work with real translations.Test plan
cd Tools && python3 -m unittest test_xcstrings_format -v— 8/8 passcd Tools && python3 -m unittest test_i18n_audit -v— 34/34 pass (untouched, still green)python3 Tools/i18n_audit.py --ci origin/main— green (this is what CI runs; confirmed locally after dropping the untranslated backlog)mainversions — this PR only changes formatting.xcstringsfile is normalized and re-staged before commitxcodebuildCLI builds (macOSStrandand iOSNOOPiOSschemes) never touch.xcstringsfiles, including after adding a newString(localized:)call to source — only Xcode.app's own build/run path does