Conversation
Adds zh-Hant as the fifth UI language alongside ko/en/ja/es.
- AppLanguage gains `case zhHant = "zh-Hant"`. The raw value doubles as the
persisted save format and as the `displayLocale` identifier, so relative
dates in the popover follow the app language.
- `apiCodes` uses the lowercase PokéAPI code ("zh-hant"); the API returns
`language.name` in lowercase, so an uppercase tag would silently fall back
to the English species name.
- The system-language guess only matches Traditional variants
(zh-Hant / zh-TW / zh-HK / zh-MO). Simplified users (zh-Hans / zh-CN /
zh-SG) keep the English fallback rather than being shown a script they did
not ask for; a bare "zh" is not assumed to be Traditional either.
- `systemDefault` now delegates to a pure `resolve(preferredLanguages:)` so
every branch can be exercised without depending on the machine's locale.
- All 207 `t(...)` call sites and the 25 nature names get their zh-Hant
column. The existing ko/en/ja/es columns are untouched.
The language picker in Settings is driven by `AppLanguage.allCases`, so it
picks the new entry up with no UI change.
`langCodes` was a literal list that has to be edited by hand every time a language is added. Forgetting it is silent: the new language resolves no species name and falls back to English, which looks like a data gap rather than a missing line of code. Deriving the set from `AppLanguage.apiCodes` makes the enum the single source, matching the provider-extension convention in CONTRIBUTING.md. The resulting set is a superset of the previous list, so lookups are unchanged for the existing languages.
The argument count of `t(...)` is compiler-enforced, so a missing string
cannot ship. These tests cover what the compiler cannot see:
- raw value / display locale, including that the relative-time formatter
produces Traditional ("小時") and not Simplified ("小时")
- the Traditional/Simplified matching table, including script tags winning
over region (zh-Hant-CN is Traditional, zh-Hans-TW is not)
- every branch of `resolve(preferredLanguages:)`, injected rather than read
from the host machine
- that the client keeps an API code for every language, iterating
`allCases` so reverting to a literal list fails the suite
- script leakage: every sampled string carries Han characters and no
Hangul or kana, which is what a copy-pasted column looks like
- that interpolations survived translation
Both guards were verified by injecting the defect they are meant to catch.
Adds README.zh-Hant.md and links it from the language row of the other three. The file is named after the enum's raw value (zh-Hant) rather than a region, so the docs and the language identifier stay in sync. Also corrects the "KO / EN / JA" claim in the feature table: Spanish was already shipping and was never reflected there. It now reads KO / EN / JA / ES / ZH-Hant.
There was a problem hiding this comment.
Pull request overview
Adds Traditional Chinese (zh-Hant) as a new UI language end-to-end (UI strings, language selection, system-language inference, and PokéAPI name resolution), plus targeted tests and documentation updates to ensure the new locale behaves correctly and doesn’t regress existing languages.
Changes:
- Extends
AppLanguagewithzhHant(persisted raw valuezh-Hant), adds Traditional-Chinese matching logic, and updates nature-name localization. - Expands the
L.t(...)localization helper to include a zh-Hant column across UI strings. - Adds
TraditionalChineseLocalizationTestsand a newREADME.zh-Hant.md, and links the new README from existing language READMEs.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| Tests/PokeTokenBarTests/TraditionalChineseTests.swift | Adds coverage for zh-Hant persistence/locale behavior, system-language resolution, PokéAPI code invariants, and script leakage checks. |
| Sources/PokeTokenBar/Core/PokeAPIClient.swift | Derives supported PokéAPI language codes from AppLanguage.apiCodes (single source) and uses a Set for filtering. |
| Sources/PokeTokenBar/Core/Localization.swift | Adds zh-Hant as the 5th argument to all localized UI strings via t(...). |
| Sources/PokeTokenBar/Core/CompanionModel.swift | Adds zhHant, language matching (resolve, isTraditionalChinese), updated labels, and zh-Hant nature names. |
| README.zh-Hant.md | New Traditional Chinese README. |
| README.md | Links Traditional Chinese README and updates supported-language mention. |
| README.ko.md | Links Traditional Chinese README and updates supported-language mention. |
| README.ja.md | Links Traditional Chinese README and updates supported-language mention. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
`apiCodes` listed both "zh-hant" and "zh-Hant" while the comment directly above it states that an uppercase tag never matches a PokéAPI response. The uppercase entry contradicted its own comment, and it was carried into `PokeAPIClient.langCodes` as a permanently unmatchable filter key. Keeps only the lowercase code the API actually returns, and pins it with an assertion so the variant does not come back.
|
Hi @squall — apologies for how long this has sat. It needs a rewrite rather than a rebase, and I'd rather tell you that clearly than leave it drifting. This branch was written when the app had four languages: it changes If you're still up for it, what's needed is to redo it against the current signature. Please base it on The parts specific to Traditional Chinese looked good on the read-through: matching zh-Hant/TW/HK/MO while deliberately not matching Simplified is the right call, and persisting the raw value as Entirely understandable if you'd rather not redo it — say so and I'll close it with thanks rather than leave it open indefinitely. |
|
Following up as promised: #234 (German) is merged, so If you take this on, Also carrying over the one substantive point from the automated review on this PR: the comment above And the earlier offer stands — if you'd rather not redo it, just say so and I'll close it with thanks. |
Summary
Adds Traditional Chinese (
zh-Hant) as the fifth UI language, alongside ko / en / ja / es.AppLanguagegainscase zhHant = "zh-Hant". The raw value doubles as the persisted save format and as thedisplayLocaleidentifier, so the popover's relative dates follow the app language.t(...)call sites and the 25 nature names get their zh-Hant column. The existing ko/en/ja/es columns are untouched — I verified this mechanically by re-parsing both revisions and diffing the first four arguments of every call (0 differences).zh-hantdata at runtime, so no name table is bundled.zh-Hant,zh-TW,zh-HK,zh-MO). Simplified users (zh-Hans,zh-CN,zh-SG) keep the English fallback rather than being shown a script they did not ask for, and a barezhis not assumed to be Traditional.README.zh-Hant.md, linked from the language row of the other three.Why
zh-Hantrather thanzh-TW: the script subtag is region-neutral, so one entry serves TW/HK/MO; it matches PokéAPI's ownzh-hantcode, keeping one concept end to end; and it stays consistent with the region-neutralko/en/ja/esentries. A futurezh-HKfor Hong Kong vocabulary can be added later without renaming anything. The wording itself leans Taiwan Mandarin.One change worth a second look
PokeAPIClient.langCodeswas a literal array that has to be edited by hand for every new language, and forgetting it is silent — the new language resolves no species name and quietly falls back to English, which reads like a data gap rather than a missing line of code. I changed it to derive fromAppLanguage.apiCodes, which also meant widening it fromprivate lettostatic letso a test can assert the invariant. The resulting set is a superset of the old list, so lookups are unchanged for the existing languages. It is an isolated commit (0904bc8) — happy to drop or split it if you would rather keep that line explicit.Type of change
UI changes
No layout changes. The Settings language picker is
.menustyle and driven byAppLanguage.allCases, so it gains one entry with no change to the view code. Every other screen keeps its layout with Traditional Chinese text.PerformanceTests.testLocalizedAlertBubbleFitsDefaultPanelalready iteratesAppLanguage.allCases, so the new alert strings are verified to fit the floating pet's speech bubble without truncating.Checklist
swift buildandswift testpass locally — 686 tests, and./scripts/test-gate.shreports 89.93% line coverage on the logic coreOn the tests
The argument count of
t(...)is compiler-enforced, so a missing string cannot ship.TraditionalChineseTestscovers what the compiler cannot see: the Traditional/Simplified matching table (including script tags winning over region —zh-Hant-CNis Traditional,zh-Hans-TWis not), every branch ofresolve(preferredLanguages:)with the locale injected rather than read from the host machine, that the relative-time formatter produces 小時 and not 小时, that the client keeps an API code for every language, that interpolations survived translation, and script leakage (every sampled string carries Han characters and no Hangul or kana — which is what a copy-pasted column looks like). Both guards were checked by injecting the defect they are meant to catch.