feat(l10n): add Simplified and Traditional Chinese localizations - #419
Merged
Conversation
Adds zh-Hans and zh-Hant, bringing the UI to 17 languages. Each variant is translated from the English source rather than character-converted from the other, since conversion produces character-correct but lexically wrong output (软件 -> 軟件 instead of 軟體, 视频 -> 視頻 instead of 影片). Chinese is identified by script rather than region because ISO 639-1 cannot express the distinction at all: bare `zh` would ship one Chinese to every Chinese user regardless of script. Script codes also describe what actually differs between the two files, so one traditional bundle serves TW/HK/MO without labelling itself Taiwan-specific. Verified equivalent to region codes: Bundle.preferredLocalizations resolves zh-Hans-CN, zh-Hant-TW, zh-Hant-HK, zh-Hans-SG and zh-Hant-MO correctly either way, with no fallback to English. zh-Hant follows Taiwan usage, matching both CLDR's expansion of the bare tag and YouTube's own zh-Hant responses, so UI strings stay consistent with the API-sourced content beside them. zh-HK is a separate Apple localization and is left out; adding it later is purely additive. apiLanguageCode needs no Chinese special case. Probing browse FEmusic_home with each code showed YouTube accepts zh-Hans and zh-Hant and scripts the responses correctly, so the bundle code passes through unchanged as it does for every other locale. A test pins this. The probe needed an `--hl` override, added to api-explorer rather than scripted ad hoc. Tests assert script-appropriate vocabulary rather than character shape, so a machine-converted or swapped file fails where a shape check would pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Sertac Ozercan <sozercan@gmail.com>
This was referenced Aug 27, 2026
Yoddikko
pushed a commit
to Yoddikko/kasetPlus
that referenced
this pull request
Aug 27, 2026
…ercan#419) Adapted for the fork. Our catalog already carried zh-Hans/zh-Hant translations (via Crowdin), but the .lproj mirrors were unregistered, there was no ContentLanguage case to select Chinese, and no apiRegionCode mapping. This ports the still-missing pieces from upstream sozercan#419: - register both zh mirrors in Package.swift (were never shipped in SwiftPM builds) - add .simplifiedChinese/.traditionalChinese ContentLanguage cases (region CN/TW) - regenerate zh-Hans/zh-Hant .lproj from our catalog via Scripts/sync-lproj.py - port the APIExplorer `--hl` InnerTube language probe flag README language-count line skipped: the fork tracks languages via Crowdin badges, not a bullet list. Cherry-picked/adapted from upstream 0576fd5. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Yoddikko
added a commit
to Yoddikko/kasetPlus
that referenced
this pull request
Aug 27, 2026
…upstream ledger Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Yoddikko
added a commit
to Yoddikko/kasetPlus
that referenced
this pull request
Aug 27, 2026
sozercan#384/sozercan#415 already-ported Content already present via cherry-picks and prior independent ports; records ancestry so the fork reads 0-behind. No code changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Description
Adds Simplified and Traditional Chinese UI localizations (
zh-Hans,zh-Hant), bringing Kaset to 17 languages.Both variants are translated from the English source independently. Traditional Chinese is not a simplified-to-traditional character conversion — that approach produces character-correct but lexically wrong output (软件 → 軟件 rather than 軟體, 视频 → 視頻 rather than 影片).
Why
zh-Hans/zh-Hantrather thanzh-CN/zh-TWThis is the one naming decision worth flagging, since every existing locale here is a plain ISO 639-1 code.
ISO 639-1 cannot express this distinction:
zhalone would mean shipping one Chinese to every Chinese user regardless of script. So the two-letter pattern has to break either way —zh-CNis not two letters either.Given that, this PR uses script codes because:
zh-Hantdescribes it honestly for all three;zh-TWwould label the same content as Taiwan-specific.zh_CN/zh_TWinherited from their own localization pipelines, and Apple's first-party bundles use the legacy underscore-region form throughout (en_AU.lprojand friends), not just for Chinese.Verified as functionally equivalent, so this is convention rather than correctness:
Bundle.preferredLocalizationsresolveszh-Hans-CN,zh-Hant-TW,zh-Hant-HK,zh-Hans-SGandzh-Hant-MOcorrectly under either scheme, with no fallback to English. Happy to rename if you prefer region codes.zh-Hantfollows Taiwan usage. CLDR expands the bare tag tozh-Hant-TW, and YouTube's ownzh-Hantresponses use Taiwan vocabulary (播放清單), so the UI strings stay consistent with the API-sourced content rendered beside them. Hong Kong (zh-HK) is a separate Apple localization and is deliberately out of scope; adding it later is purely additive.apiLanguageCodeneeds no special caseContentLanguage.apiLanguageCodefeeds the InnerTubehlparameter, and YouTube'shlvocabulary is not obviously the same as Apple's localization identifiers — so this looked like it would need the first-ever divergence between the bundle code and the API code.It does not. Probing
browse FEmusic_homewith each code and counting simplified-only versus traditional-only characters:hlzh-Hanszh-CNzh-Hantzh-TWYouTube accepts both codes and scripts them correctly, so
apiLanguageCodekeeps returninglanguageCodeunchanged, exactly like the other 16 locales. A test pins this so the divergence is not "helpfully" added back later.AI Prompt (Optional)
🤖 AI Prompt Used
The locale-identifier question in the opening prompt was answered by investigation during the work, not assumed: the first hypothesis (that
hlwould needzh-CN/zh-TWwhile bundles usedzh-Hans/zh-Hant) was tested against the live API and disproved, which removed a whole layer of planned complexity.Translations were AI-drafted against a fixed glossary, then mechanically checked (placeholder parity, script purity, cross-key collisions) and reviewed by a native speaker in the running app before submission.
AI Tool: Claude Code
Type of Change
Related Issues
None open for Chinese localization.
Changes Made
Localizable.xcstrings— 564 keys × 2 locales, matching the set every other locale translateszh-Hans.lproj/andzh-Hant.lproj/— regenerated from the catalog, not hand-editedSettingsManager.ContentLanguage—.simplifiedChinese/.traditionalChinese, displaying as 简体中文 / 繁體中文Package.swift— registers both.lprojresourcesSources/APIExplorer/main.swift— adds--hl <code>to override the InnerTubehlparameter, so response localization can be probed directly instead of inferred. This is what produced the table above, and follows AGENTS.md's "improve the API Explorer, don't write one-off scripts" ruleAppLocalizationTests— Smart Shuffle entries for both locales, plus a test asserting script-appropriate vocabulary (播放列表/播放清單, 搜索/搜尋, 视频/影片, 登录/登入). Asserting on wording rather than character shape means a machine-converted or swapped file fails; a character-shape check would pass oneSettingsManagerTests— extends the locale-code ordering test and pinsapiLanguageCodeREADME.md,docs/adr/0013-localization-strategy.md— locale list, and corrects the "ISO 639-1" wording thatzh-Hans/zh-Hantdo not satisfyGlossary
Terms where Simplified and Traditional genuinely diverge, listed so these choices can be challenged directly rather than reverse-engineered from the diff:
Judgment calls worth a second opinion:
Library→ 音乐库/音樂庫 (rather than Apple Music's 资料库);Collection→ 我的内容/我的內容 (「收藏」collided withFavorites);Scrobblingleft untranslated, per Last.fm convention.Testing
swift test --skip KasetUITests, 2950 tests in 231 suitesMechanical checks beyond the suite:
%lld,%@,%d) exact against every source stringzh-Hans, no simplified inzh-Hant.lprojfiles re-read withplutiland compared entry-by-entry against the catalogManual pass on a packaged build, both variants, reviewed by a native speaker. Only 51 of 564 strings are wider than their English source (Traditional is the worst case, e.g. 待播清單 for Queue), and the settings panes, player bar, and queue panel were checked specifically for truncation.
Signed-in surfaces (Library, Playlists, Subscriptions) were not visually checked: a locally built ad-hoc-signed bundle cannot reach the Keychain cookie path. Their strings are covered by the automated parity and vocabulary tests.
Checklist
swiftlint --strict && swiftformat .Additional Notes
The diff is large (~7900 added lines) but almost entirely generated: the catalog and the two
.lprojmirrors account for ~7900 of it. Hand-written code is about 60 lines acrossSettingsManager,Package.swift, api-explorer, and the tests.The catalog diff is pure insertion with zero deletions — existing key order and all 15 current locales are untouched.