refactor(i18n): settle all 24 divergences; write the overrides down - #594
Merged
Conversation
Slice 4 of five. Shared is the source and web is an overlay, so a string can only
exist twice if somebody chose that. Twenty-four keys still did. Each was decided
on its own evidence rather than by a rule.
**Fourteen were dead in shared, not divergent.** Website SEO and brand copy for
pages the app does not have — `authors.seoDesc`, `books.seoDesc`, `contact.seoDesc`,
`contact.seoTitle`, `footer.description`, `nav.brandTitle`, `nav.aboutTextStack`,
three `bookDetail.faq*` — plus `home.hero.*` and `home.seo.content`, which mobile
never referenced because it has no home screen (Library is the front door), and
`vocabulary.deleteConfirm`. Deleted from shared; web keeps them as its own. This is
also the correction to what I said earlier: mobile was not showing an abandoned
hero, it was carrying a string nothing rendered.
**One key was serving two different destinations.** `tutor.signIn.cta` is the
button on the signed-out tutor screen. Web's goes to `/books` and says "Browse
books"; mobile's goes to sign-in and says "Sign in". Each label matches its own
handler — so this was never a mislabelled button, it was one name for two actions.
Web's is now `tutor.signIn.ctaBrowse` and shared's `cta` says "Sign in". Neither is
an override any more.
**One was real drift.** `reader.ask.indexFailed`: "Preparation failed." on web,
"Could not index this book." on mobile. Same state, two messages, and the mobile
one tells the reader what happened. Web now inherits it — a production copy change,
visible as a fixture line.
**Eight are deliberate and now written down** in `overrides.test.ts`, which asserts
**set equality** against the register — so removing an override, or one quietly
becoming a duplicate again, fails too. The reasons live in the test because JSON has
no comments and a reason nothing enforces is not a reason. They are: brand
(`contact.responseBody`), different jobs (`librarian.openBook` — web interpolates a
title, mobile uses the string as a bare a11y label), different affordance in view
(`library.actions.addToCollectionEmpty`), short phone labels (three
`library.sort.*`), a "+" baked into mobile copy (`library.collections.new`, flagged
as a smell), and one mechanism difference (`reader.vocab.tapAgainToStudy`, `{{n}}`
vs `{n}` — to be retired by giving the shared t() a vars argument, not by editing
the string).
The fixture diffs are the review, and they are small: web has two changed values
and one added key; **mobile has fourteen removals and not one changed string.**
786 web + 449 shared + 385 mobile tests green; both typechecks clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011rgEMvYYi4Egj99dVtvm6E
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.
Slice 4 of five. Shared is the source and web is an overlay, so a string can only exist twice if somebody chose that. Twenty-four keys still did. Each was decided on its own evidence rather than by a rule.
Fourteen were dead in shared, not divergent
Website SEO and brand copy for pages the app does not have —
authors.seoDesc,books.seoDesc,contact.seoDesc,contact.seoTitle,footer.description,nav.brandTitle,nav.aboutTextStack, threebookDetail.faq*— plushome.hero.*andhome.seo.content, which mobile never referenced because it has no home screen (Library is the front door), andvocabulary.deleteConfirm.Deleted from shared; web keeps them as its own.
This is also a correction to something I said earlier in this work: mobile was not showing an abandoned hero. It was carrying a string nothing rendered.
One key was serving two different destinations
tutor.signIn.ctais the button on the signed-out tutor screen./booksEach label matches its own handler — so this was never a mislabelled button, it was one name for two actions. Web's is now
tutor.signIn.ctaBrowseand shared'sctasays "Sign in". Neither is an override any more.One was real drift
reader.ask.indexFailed: "Preparation failed." on web, "Could not index this book." on mobile. Same state, two messages, and the mobile one tells the reader what happened. Web now inherits it — a production copy change, visible as a fixture line.Eight are deliberate and now written down
overrides.test.tsasserts set equality against the register — so removing an override, or one quietly becoming a duplicate again, fails too. The reasons live in the test because JSON has no comments and a reason nothing enforces is not a reason.contact.responseBody— brand: the website is "TextStack Reader", the app is "TextStack"librarian.openBook— different jobs: web interpolates a title, mobile uses the string as a bare a11y labellibrary.actions.addToCollectionEmpty— different affordance in viewlibrary.sort.added/.progress/.recent— short labels for a phonelibrary.collections.new— mobile bakes a+into the copy (flagged as a smell)reader.vocab.tapAgainToStudy— a mechanism difference,{{n}}vs{n}; to be retired by giving the sharedt()a vars argument, not by editing the stringVerification
The fixture diffs are the review, and they are small: web has two changed values and one added key; mobile has fourteen removals and not one changed string.
🤖 Generated with Claude Code
https://claude.ai/code/session_011rgEMvYYi4Egj99dVtvm6E