You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(i18n-ko): core — add ko to UiLocale model #3975
UiLocale will ship ['zh-CN','zh-TW','en'] after #3853. ko/ko-KR system languages fall back to en. There is no type-safe slot for Korean, so catalog entries cannot be added.
Desired outcome
UI_LOCALES includes ko, isUiLocale/isUiLocalePreference accept ko
resolveSystemUiLocale recognizes ko, ko-KR, ko_KR (case-insensitive, ko prefix) and maps auto → ko correctly
uiLocaleToIntlLocale maps ko → ko-KR
Existing zh-CN/zh-TW/en behavior unchanged; persisted auto preference preserved
Tests cover resolveSystemUiLocale(['ko','ko-KR','en']) and resolveUiLocale('auto', ko); missing ko entry in any UiCatalog<T> fails typecheck
Alternatives or workarounds
Keep English fallback for Korean systems.
Scope
packages/core/src/ui-locale.ts + tests (desktop-locale-authority, e2e-fixture-locale). Reuses #3853 pattern for zh-TW. No catalog content in this slice.
Delivery note (2026-08-27 audit): This slice must not land alone with Partial<Record<UiLocale,T>> widening. Per notes/pr-3853-feature-audit.md — widening would make the already-enabled zh-CN/zh-TW optional as well and force every getter to add undefined fallback/assertion, then require a second global tighten. Instead, keep dictionaries off the enabled union in the domain slices (#3977-#3980) and enable koatomically (or on a stacked integration branch), as UiCatalog<T> = Record<UiLocale,T> requires all catalogs to carry the key at once. See audit § Global follow-up.
npm --workspace @maka/core test green with new cases
This issue does not merge to main alone — it lands together with the catalog slices via atomic vertical or stacked integration branch, keeping UiCatalog strict
One thing I'd like to settle before starting, since it affects the other five slices too.
UiCatalog<T> is Record<UiLocale, T>, so adding ko to UI_LOCALES makes every catalog require a ko entry. I count 38 of them — 34 declared with satisfies UiCatalog<T> and 4 annotated directly — across apps/desktop, packages/ui, packages/cli and packages/core. The acceptance criteria name that as intended ("missing ko entry in any UiCatalog<T> fails typecheck"), which is the right property to want, but it does mean #3975 on its own lands main with a failing typecheck until the catalog slices follow. Branch protection requires a passing test check, so the PR would sit red on its own.
How would you like to sequence it? A few options I can see:
stack all six on one integration branch and merge them together
widen UiCatalog so ko is optional first, then tighten it in a final PR once the catalogs are filled
Happy to go with whichever you prefer — I just don't want to be the one who puts red CI on main. If the third option is the one you want, I can fold the widening into #3975 so it stays a single self-contained change.
On the translation slices: I'd rather not claim one yet. #3977 alone is around 2,900 lines of copy, and I'd like to run the app in ko after this slice lands before committing to wording at that volume — tone is easier to get right against a real screen than against a diff.
Hi @heeoneie — thanks so much for catching this so early and for counting the 38 catalogs! You're totally right — Record<UiLocale, T> makes a pure core-only #3975 red on its own, and we definitely don't want you to be the one to put red CI on main.
Let's go with option 3 — widen then tighten — it's the kindest to reviewers and keeps everything green:
Please feel free to fold the widening right into feat(i18n-ko): core — add ko to UiLocale model #3975 so it stays self-contained. Something like Partial<Record<UiLocale, T>> & Record<'en', T> (with en still required) is perfect — missing ko will just fall back to en for a couple of weeks, which is exactly the expected behavior for a not-yet-shipped locale, so no risk.
No need to stack everything on an integration branch or land core last — widen-then-tighten keeps the history clean and reviewable.
Also totally understand holding off on claiming #3977 — ~2,900 lines is a lot to word-smith from a diff! Please take your time, run it in ko after #3975 lands, and just comment I'd like to take #3977 (or whichever slice you prefer) whenever you're ready. No rush at all.
Just for heads-up: we're settling the shared contract as ['zh-CN','zh-TW','en','ko'] (following #3853's region-aware shape), so #3975 will be built on ['zh-CN','zh-TW','en'] — we'll update the description to match, you don't need to rework anything beyond the widening.
Thanks again for the super careful analysis and for offering to carry the Korean copy — really appreciate you!
Hi @heeoneie — quick correction to my last message, sorry for the whiplash!
After your note I had a deeper feature audit run on #3853 (head 13428763b), and it flagged the Partial<Record<UiLocale,T>> widen-then-tighten we just agreed on. The audit's point is fair: that widening wouldn't just make ko optional — it would also make the already-shipped zh-CN/zh-TW optional, and force every UiCatalog getter to add an undefined fallback/assertion, then require a second global tighten. That's more risk than the small-PR benefit is worth.
Updated guidance per the audit: let's not widen. Instead, please keep the ko dictionaries off the enabled union in your domain slices (#3977-#3980) — you can prepare them on branches that don't yet add ko to UI_LOCALES — and then enable ko atomically (either one atomic vertical that adds UI_LOCALES + all catalogs together with clearly separated commits like #3853, or a stacked integration branch). The type stays strict Record<UiLocale,T> the whole time.
I know that's the opposite of what I just said — really sorry for the flip-flop! Your counting of the 38 catalogs was spot-on and is exactly why the audit prefers the atomic/stacked shape over the widening. If you'd still prefer to run ko live before committing wording, the stacked branch still lets you do that locally without putting an optional type on main.
I've updated the issue body to reflect this — let me know if the new shape works for you, or if you'd like to keep #3975 parked until #3853 lands and then do the atomic enable together. Again, sorry for the change and thanks for your patience!
No whiplash — the audit is right, and #389's reasoning goes further than the type shape: the timeout/rate/auth/5xx/network classification in redaction.ts is already duplicated between generalizedErrorMessage and generalizedErrorMessageChinese — identical branches, different strings — and ko would have made that a third copy on main (a fourth at #3853's head). Better to collapse it to "classify once → typed locale catalog" before another locale exists, not after.
I've read #3853 and @AcerYue0's contract question, so I'm working from ['zh-CN','zh-TW','en'], not the ['zh','en'] still in the #3975 body. Parking behind #3853 makes sense — adding ko is one line in UI_LOCALES once it lands.
On the classifier: I'd rather not open that PR right now. It touches the same file #3853 is actively reworking, and dropping a refactor there while it's blocked would just cost @AcerYue0 another rebase. Two options I'm happy with:
No whiplash — the audit is right, and #389's reasoning goes further than the type shape: the timeout/rate/auth/5xx/network classification in redaction.ts is already duplicated between generalizedErrorMessage and generalizedErrorMessageChinese — identical branches, different strings — and ko would have made that a third copy on main (a fourth at #3853's head). Better to collapse it to "classify once → typed locale catalog" before another locale exists, not after.
I've read #3853 and @AcerYue0's contract question, so I'm working from ['zh-CN','zh-TW','en'], not the ['zh','en'] still in the #3975 body. Parking behind #3853 makes sense — adding ko is one line in UI_LOCALES once it lands.
On the classifier: I'd rather not open that PR right now. It touches the same file #3853 is actively reworking, and dropping a refactor there while it's blocked would just cost @AcerYue0 another rebase. Two options I'm happy with:
Not claiming a translation slice until #3853 lands.
Agreed. This looks better tracked as a separate issue rather than expanding #3975’s scope.
I’ll open a follow-up issue for collapsing the duplicated error classification into a single classifier + typed locale catalog, and mark it as blocked by #3853 for now. That should avoid forcing another rebase on @AcerYue0 while keeping the refactor visible and scoped separately.
Once #3853 lands, we can handle the classifier refactor there before adding another locale path. No need to claim a translation slice yet.
@Astro-Han I see this picked up good first issue and help wanted yesterday — could I be assigned, or would you rather it went to someone new?
I raised the Record<UiLocale, T> coupling on this issue and worked the sequencing out with @me2seeks above, so I have been treating it as mine and deliberately have not opened anything while #3853 is in flight. It is unassigned, though, and with the new labels I would rather not have a first-time contributor start the same work and collide with it.
The blocker did move this morning: @AcerYue0 merged main into #3853 at 03:59 UTC and requested review. It still reports a conflict, so I am not starting yet — the contract here depends on whether UI_LOCALES lands as ['zh-CN','zh-TW','en'], and I would rather build on the shape that actually merges than guess at it.
The labels are still on the issue, so if you would rather shape this differently, just say. I would mainly like to avoid two people discovering the same UiCatalog problem independently.
Thanks — you are assigned now, and #3975 is reserved for you.
Please keep it parked until #3853 has merged, then refresh from main and coordinate the atomic/stacked enablement with the catalog slices. UiCatalog stays strict; please don't open a standalone PR that enables ko on main.
I've removed the good first issue and help wanted labels so nobody else starts overlapping work. Thanks for being careful about the dependency and collision risk.
#3853 has landed, so I picked this up. The branch is local only — no PR, since this issue says not to enable ko on main standalone.
Contract confirmed.UI_LOCALES is ['zh-CN','zh-TW','en'] at ui-locale.ts:23 on main (c0229b0). The ['zh','en'] in the issue body is stale — zh now survives only as a legacy persisted value that normalizeUiLocalePreference folds into zh-CN.
The catalog count moved: 38 → 114.
workspace
ko-induced errors
catalogs needing ko
files
@maka/core
4
3
3
@maka/ui
24
13
13
maka-agent (packages/cli)
1
0 (test loop)
1
@maka/desktop
142
98
41
total
171
114
58
61 of desktop's 98 sit in provider-display-copy.ts alone (per-provider literals), so sizing slices by file count will mislead. Happy to post the full per-catalog list if it helps the split.
Core does not build with ko at all, and that changes the stacking. Three core catalogs require a ko entry before dist exists: redaction.ts (GENERALIZED_ERROR_COPY), relative-time.ts (JUST_NOW), and tool-quiet-preview.ts (STRINGS_BY_LOCALE). With UiCatalog<T> total and widening ruled out, #3975 as scoped cannot be green on its own.
Two of those three are not error copy, so they read as outside #3978's scope. If relative-time.ts and tool-quiet-preview.ts come into #3975, core is self-contained green and stays green at every point of a stacked branch, with only redaction.ts left to #3978. That's my suggestion, but it's your call.
Seven sites typecheck cannot see. These compile with ko and silently serve English:
renderer/settings/daily-review-settings-page.tsx:42, e2e/fixtures.ts:205,428 — hardcoded unions instead of UiLocale
main/e2e-fixture.ts:121parseLocaleFlag — no ko branch, returns null
.storybook/preview.tsx:44,105 and stories — no ko in the locale global
The parseLocaleFlag one is the same class @Astro-Han caught for zh-TW in #3853. None of the six slices currently owns these, so they ship as English-inside-ko unless they get a home.
UI_LOCALE_PREFERENCES is fine — it is consumed as z.enum(UI_LOCALE_PREFERENCES), so the settings schema follows automatically.
Two corrections to the issue text.
uiLocaleToIntlLocale is alive (ui-locale.ts:167, 27 references across 10 files), so I added ko → ko-KR as specified. Its return type had to widen from UiLocale to string, since ko-KR is not a UiLocale; I checked all 27 call sites and they pass straight into Intl.*, toLocaleString or localeCompare, and relative-time.ts's cache is already keyed { locale: string }. The existing test asserted uiLocaleToIntlLocale(locale) === locale; ko is the first locale to break that identity, so I pinned the tag table and added a new Intl.Locale(tag).baseName === tag canonicality check instead. Worth recording: the review suggestion to delete this function as a no-op identity stops applying once ko lands.
The acceptance criterion npm --workspace @maka/core test no longer exists on main — that script was split into build + test:dist, orchestrated from the root by run-workspace-tests-parallel.mjs.
What is on the branch.ui-locale.ts (UI_LOCALES, isUiLocale, resolveSystemUiLocale with /^ko(?:[-.]|$)/iu reusing the existing trim and _→- path, uiLocaleToIntlLocale) plus tests: ko / ko-KR / ko_KR / KO-Kr / ko_KR.UTF-8 / ko-Kore-KR, order respect (['en','ko'] → en, ['ko','en'] → ko), kok-IN → en, resolveUiLocale('auto','ko'), persisted auto preserved, zh-CN/zh-TW/en unchanged. 29 green, compiled scope-only for the reason above, no widening. To produce the table I probed with temporary placeholders in the three core catalogs (core was 842/842 green in that state) and reverted them — the branch diff is two files.
Questions. Atomic vertical or stacked branch? And are you happy for relative-time.ts and tool-quiet-preview.ts to come into #3975 so core stays green throughout?
The seven type-invisible sites look like their own tracking issue rather than scope creep here — I can open one and link it back, unless you would rather fold them into one of the existing slices.
@me2seeks@scs0209 — flagging an overlap before either of us spends more time on it.
#4515 also implements this issue. On feat/i18n-ko-native-e2e-3980, packages/core/src/ui-locale.ts has UI_LOCALES = ['zh-CN', 'zh-TW', 'en', 'ko'], ko in the isUiLocale guard, the ko branch in resolveSystemUiLocale, and uiLocaleToIntlLocale mapping ko → ko-KR. That is #3975's entire scope.
We converged almost exactly. The system-locale regex is character-identical on both sides (/^ko(?:[-.]|$)/iu), and we both arrived at ko → ko-KR independently — worth recording, since the earlier review suggestion to drop uiLocaleToIntlLocale as an identity no-op stops applying once ko exists.
Differences, and I think the first is mine to give up:
uiLocaleToIntlLocale return type. I widened it to string; feat(i18n-ko): native surfaces and E2E fixtures #4515 returns the literal union 'zh-CN' | 'zh-TW' | 'en' | 'ko-KR'. Every current consumer passes the result straight into Intl/toLocaleString/localeCompare, all of which take string, so neither choice breaks a call site — but the union keeps the set of tags we actually emit visible in the signature, which string throws away. I'd rather take feat(i18n-ko): native surfaces and E2E fixtures #4515's signature than mine.
UI_LOCALES ordering (...'en', 'ko' vs ...'ko', 'en'). No behavioural difference — resolveSystemUiLocale iterates the input languages, not UI_LOCALES, so ordering only affects anything that maps over the array (a language picker's display order) and the union order. Happy either way.
Test coverage. As of that branch's current head, __tests__/ui-locale.test.ts covers ko-KR, ko_KR, ['en-US', 'ko-KR'] → en, resolveUiLocale('auto', 'ko') and the ko → ko-KR mapping. Two cases I have are not pinned anywhere yet: the reverse order case (['ko', 'en'] → ko, so list order is asserted in both directions) and kok-IN → en. The second is the one I would not want to lose — both our patterns already reject Konkani, but nothing asserts it, and a later simplification to /^ko/i would break it silently. Whichever branch carries the core commit, I would like at least that case to come with it.
So the sequencing question is the real one, and it is the same one I asked on 6 Sep:
Is #4515 the atomic vertical? If it is, #3975 belongs inside it as a commit rather than as its own branch — I'll rebase what I have onto it and hand the core commit over. If it isn't, then #4515 currently also carries #3976–#3979 territory (packages/ui, desktop locales, cli), which are assigned to other people, and we should decide what stays in it.
One observation rather than a claim: #4515 shows merge conflicts at the moment. I suspect that is #3853 landing after the branch was cut — worth a look either way, @scs0209.
Also still open from my last comment, and #3978 is waiting on it (cc @ggbdpq): do relative-time.ts and tool-quiet-preview.ts come into #3975 so core stays green throughout, leaving only redaction.ts to #3978?
@heeoneie Thanks for catching the overlap early — agreed we should not duplicate the core work.
To answer the sequencing question directly: #4515 is not the full atomic vertical for #3975–#3979. It was opened for #3980 (desktop native surfaces + E2E fixtures). Core ui-locale changes landed on that branch only because enabling ko there requires the #3975 contract, and catalogs outside the assigned renderer slices currently use English stubs. It should not own #3976–#3979.
Given you are already assigned to #3975, I think the cleanest split is:
Happy to take your return-type preference either way — I also prefer the literal union ('zh-CN' | 'zh-TW' | 'en' | 'ko-KR') over widening to string.
Please keep the kok-IN → en case (and the bidirectional order asserts if you want). I am fine adopting those into whichever branch carries the core commit.
Also confirmed on the conflict note: #4515 is currently CONFLICTING against main after #3853 landed — I will refresh that regardless.
For relative-time.ts / tool-quiet-preview.ts: I am fine with those coming into #3975 if that is what keeps core green; I will follow whatever you and @me2seeks decide there.
Happy to adjust if maintainers want a different split.
@scs0209 — thank you, and I appreciate you answering the scope question directly rather than letting it sit.
Agreed on all of it: #3975 keeps the core commit, #4515 stays scoped to #3980, the literal union wins over string, and I will carry kok-IN → en plus the bidirectional order asserts in the core tests.
One practical follow-on. You said you would treat the ui-locale.ts and core-test pieces on #4515 as temporary until #3975 is the source — but there is no source to point at yet, because I have kept the work local rather than open a PR. So unless @me2seeks would rather I held off, I will open #3975 as a draft PR marked do-not-merge: it cannot land, so it does not enable ko on main, but it gives you, @ggbdpq and @xiechimon one branch to rebase onto instead of three private copies of the same core change. Expect its CI to be red for exactly the reason described below — that is the point of the atomic/stacked plan, not a regression.
@me2seeks — that leaves one open question, and it is now the only thing blocking the other slices. Core does not compile with ko until three catalogs carry a ko entry: redaction.ts, relative-time.ts and tool-quiet-preview.ts. If relative-time.ts and tool-quiet-preview.ts come into #3975, core is self-contained and green at every point of a stacked branch, and only redaction.ts is left to #3978. @scs0209 has said he is fine with that shape and @ggbdpq is waiting on it. Is that what you want?
Thanks everyone. I am making the delivery decision now so the Korean work can proceed on one clear contract.
UiCatalog<T> remains strict. We will not widen it to an optional catalog type, because ko in UI_LOCALES means Korean is a supported product locale, not a partially enabled preview.
Please include all three core catalogs required by the new locale in feat(i18n-ko): core — add ko to UiLocale model #3975: redaction.ts, relative-time.ts, and tool-quiet-preview.ts, together with ui-locale.ts and the locale tests. Keep the literal Intl-tag union, kok-IN → en, and both system-language ordering assertions.
The renderer, shared UI, CLI, and native/E2E slices should prepare their Korean copy on top of that base. We will merge only one maintainer-owned integration branch after every strict catalog is complete and the full validation is green.
Please also assign the type-invisible locale paths before integration, especially the E2E/Storybook locale globals and hardcoded locale branches, so ko cannot compile while silently rendering English.
@scs0209@ggbdpq@xiechimon — that is the branch to rebase onto. It is heeoneie:feat/i18n-ko-core-ui-locale, rebased onto 93a8dd7.
It currently carries ui-locale.ts and the locale tests only. The three core catalogs (redaction.ts, relative-time.ts, tool-quiet-preview.ts) land on it next, per @me2seeks's delivery decision — its typecheck is red until then, and the four remaining errors are exactly those three catalogs' missing ko entries.
One note for anyone cutting a slice branch: packages/core/src/model-metadata.generated.ts is gitignored, and a stale copy produces four unrelated provider-registry.ts errors. npm run sync:model-metadata right after rebasing clears it.
The catalog count has also moved — 114 when I counted on 6 Sep, 126 as of 93a8dd7.
Correction to my earlier note in this thread: the stack base has moved.
I pointed at heeoneie:feat/i18n-ko-core-ui-locale above. That is superseded — @me2seeks opened #5100 as the official base inside apache/maka (branch stack/i18n-ko-core), recreating the reviewed core layer from #5011 at 36a4377 as a single commit on current main. @jackwener verified its combined patch ID matches the #5011 source range exactly.
Because that branch now lives in apache/maka, slice PRs can retarget their base to it directly instead of carrying temporary core commits. @scs0209 has already done exactly that on #4515 — rebased onto stack/i18n-ko-core, retargeted the PR base, and dropped the temporary core commits. That is the shape to copy.
#5011 stays open as the canonical reviewed source and the public record of the core work, but it is no longer the thing to stack on. #5100 is draft-only and does not merge by itself, so the delivery model is unchanged: only the complete, green stack is eligible for merge.
Maintainer decision update — this supersedes my 2026-09-08 atomic-delivery decision.
We accept progressive Korean rollout. We will use the shared English-fallback catalog path so untranslated Korean copy can render in English while each translation slice is completed. This means Korean can become selectable / follow the system before every surface is translated; that mixed-language period is an accepted product behavior for this rollout.
The delivery sequence is:
Make the fallback path safe for every build/typecheck surface affected by enabling ko, with English required and explicit coverage checks so fallback cannot hide missing copy in the already shipped Chinese locales.
Merge the green core enablement PR to main as its own squash commit.
Retarget and merge each ready translation PR separately to main; each PR keeps its own squash commit and author/co-author attribution. The temporary integration branch is for cross-slice visual/E2E validation, not the final all-in-one squash.
Please do not add verbatim English ko blocks just to satisfy the old strict type. Keep real Korean copy and let the shared resolver provide the fallback. Maintainers will own the integration and cross-slice coordination. #3975 remains the core locale change; the broader Korean completion/release audit stays tracked separately.
Activity
heeoneie commentedon Aug 27, 2026
I'd like to take #3975.
One thing I'd like to settle before starting, since it affects the other five slices too.
UiCatalog<T>isRecord<UiLocale, T>, so addingkotoUI_LOCALESmakes every catalog require akoentry. I count 38 of them — 34 declared withsatisfies UiCatalog<T>and 4 annotated directly — acrossapps/desktop,packages/ui,packages/cliandpackages/core. The acceptance criteria name that as intended ("missing ko entry in anyUiCatalog<T>fails typecheck"), which is the right property to want, but it does mean #3975 on its own landsmainwith a failing typecheck until the catalog slices follow. Branch protection requires a passingtestcheck, so the PR would sit red on its own.How would you like to sequence it? A few options I can see:
UiCatalogsokois optional first, then tighten it in a final PR once the catalogs are filledHappy to go with whichever you prefer — I just don't want to be the one who puts red CI on
main. If the third option is the one you want, I can fold the widening into #3975 so it stays a single self-contained change.On the translation slices: I'd rather not claim one yet. #3977 alone is around 2,900 lines of copy, and I'd like to run the app in
koafter this slice lands before committing to wording at that volume — tone is easier to get right against a real screen than against a diff.me2seeks commentedon Aug 27, 2026
Hi @heeoneie — thanks so much for catching this so early and for counting the 38 catalogs! You're totally right —
Record<UiLocale, T>makes a purecore-only #3975 red on its own, and we definitely don't want you to be the one to put red CI onmain.Let's go with option 3 — widen then tighten — it's the kindest to reviewers and keeps everything green:
Partial<Record<UiLocale, T>> & Record<'en', T>(withenstill required) is perfect — missingkowill just fall back toenfor a couple of weeks, which is exactly the expected behavior for a not-yet-shipped locale, so no risk.tightenPR to bringUiCatalog<T>back to strictRecord<UiLocale, T>once all catalogs are filled.Also totally understand holding off on claiming #3977 — ~2,900 lines is a lot to word-smith from a diff! Please take your time, run it in
koafter #3975 lands, and just commentI'd like to take #3977(or whichever slice you prefer) whenever you're ready. No rush at all.Just for heads-up: we're settling the shared contract as
['zh-CN','zh-TW','en','ko'](following #3853's region-aware shape), so #3975 will be built on['zh-CN','zh-TW','en']— we'll update the description to match, you don't need to rework anything beyond the widening.Thanks again for the super careful analysis and for offering to carry the Korean copy — really appreciate you!
me2seeks commentedon Aug 27, 2026
Hi @heeoneie — quick correction to my last message, sorry for the whiplash!
After your note I had a deeper feature audit run on #3853 (head
13428763b), and it flagged thePartial<Record<UiLocale,T>>widen-then-tighten we just agreed on. The audit's point is fair: that widening wouldn't just makekooptional — it would also make the already-shippedzh-CN/zh-TWoptional, and force everyUiCataloggetter to add anundefinedfallback/assertion, then require a second global tighten. That's more risk than the small-PR benefit is worth.Updated guidance per the audit: let's not widen. Instead, please keep the
kodictionaries off the enabled union in your domain slices (#3977-#3980) — you can prepare them on branches that don't yet addkotoUI_LOCALES— and then enablekoatomically (either one atomic vertical that addsUI_LOCALES+ all catalogs together with clearly separated commits like #3853, or a stacked integration branch). The type stays strictRecord<UiLocale,T>the whole time.I know that's the opposite of what I just said — really sorry for the flip-flop! Your counting of the 38 catalogs was spot-on and is exactly why the audit prefers the atomic/stacked shape over the widening. If you'd still prefer to run
kolive before committing wording, the stacked branch still lets you do that locally without putting an optional type onmain.I've updated the issue body to reflect this — let me know if the new shape works for you, or if you'd like to keep #3975 parked until #3853 lands and then do the atomic enable together. Again, sorry for the change and thanks for your patience!
heeoneie commentedon Aug 27, 2026
No whiplash — the audit is right, and #389's reasoning goes further than the type shape: the timeout/rate/auth/5xx/network classification in
redaction.tsis already duplicated betweengeneralizedErrorMessageandgeneralizedErrorMessageChinese— identical branches, different strings — andkowould have made that a third copy onmain(a fourth at #3853's head). Better to collapse it to "classify once → typed locale catalog" before another locale exists, not after.I've read #3853 and @AcerYue0's contract question, so I'm working from
['zh-CN','zh-TW','en'], not the['zh','en']still in the #3975 body. Parking behind #3853 makes sense — addingkois one line inUI_LOCALESonce it lands.On the classifier: I'd rather not open that PR right now. It touches the same file #3853 is actively reworking, and dropping a refactor there while it's blocked would just cost @AcerYue0 another rebase. Two options I'm happy with:
main(two copies today) if you'd prefer feat(i18n): add Traditional Chinese locale #3853 rebase onto it rather than carry a thirdYour call — whichever costs @AcerYue0 less.
Not claiming a translation slice until #3853 lands.
me2seeks commentedon Aug 27, 2026
Agreed. This looks better tracked as a separate issue rather than expanding #3975’s scope.
I’ll open a follow-up issue for collapsing the duplicated error classification into a single classifier + typed locale catalog, and mark it as blocked by #3853 for now. That should avoid forcing another rebase on @AcerYue0 while keeping the refactor visible and scoped separately.
Once #3853 lands, we can handle the classifier refactor there before adding another locale path. No need to claim a translation slice yet.
heeoneie commentedon Aug 30, 2026
@Astro-Han I see this picked up
good first issueandhelp wantedyesterday — could I be assigned, or would you rather it went to someone new?I raised the
Record<UiLocale, T>coupling on this issue and worked the sequencing out with @me2seeks above, so I have been treating it as mine and deliberately have not opened anything while #3853 is in flight. It is unassigned, though, and with the new labels I would rather not have a first-time contributor start the same work and collide with it.The blocker did move this morning: @AcerYue0 merged
maininto #3853 at 03:59 UTC and requested review. It still reports a conflict, so I am not starting yet — the contract here depends on whetherUI_LOCALESlands as['zh-CN','zh-TW','en'], and I would rather build on the shape that actually merges than guess at it.The labels are still on the issue, so if you would rather shape this differently, just say. I would mainly like to avoid two people discovering the same
UiCatalogproblem independently.heeoneie commentedon Aug 30, 2026
take
me2seeks commentedon Aug 30, 2026
Thanks — you are assigned now, and #3975 is reserved for you.
Please keep it parked until #3853 has merged, then refresh from
mainand coordinate the atomic/stacked enablement with the catalog slices.UiCatalogstays strict; please don't open a standalone PR that enableskoonmain.I've removed the
good first issueandhelp wantedlabels so nobody else starts overlapping work. Thanks for being careful about the dependency and collision risk.2 remaining items
heeoneie commentedon Sep 6, 2026
#3853 has landed, so I picked this up. The branch is local only — no PR, since this issue says not to enable ko on main standalone.
Contract confirmed.
UI_LOCALESis['zh-CN','zh-TW','en']at ui-locale.ts:23 on main (c0229b0). The['zh','en']in the issue body is stale —zhnow survives only as a legacy persisted value thatnormalizeUiLocalePreferencefolds intozh-CN.The catalog count moved: 38 → 114.
61 of desktop's 98 sit in
provider-display-copy.tsalone (per-provider literals), so sizing slices by file count will mislead. Happy to post the full per-catalog list if it helps the split.Core does not build with ko at all, and that changes the stacking. Three core catalogs require a ko entry before
distexists:redaction.ts(GENERALIZED_ERROR_COPY),relative-time.ts(JUST_NOW), andtool-quiet-preview.ts(STRINGS_BY_LOCALE). WithUiCatalog<T>total and widening ruled out, #3975 as scoped cannot be green on its own.Two of those three are not error copy, so they read as outside #3978's scope. If
relative-time.tsandtool-quiet-preview.tscome into #3975, core is self-contained green and stays green at every point of a stacked branch, with onlyredaction.tsleft to #3978. That's my suggestion, but it's your call.Seven sites typecheck cannot see. These compile with ko and silently serve English:
main/browser-message-box.ts:351,renderer/features/connection-settings/subscription-result-message.ts:36,42,47,renderer/workhub-surface.tsx:825,893— locale ternary / if-chainsrenderer/settings/daily-review-settings-page.tsx:42,e2e/fixtures.ts:205,428— hardcoded unions instead ofUiLocalemain/e2e-fixture.ts:121parseLocaleFlag— no ko branch, returns null.storybook/preview.tsx:44,105and stories — no ko in the locale globalThe
parseLocaleFlagone is the same class @Astro-Han caught for zh-TW in #3853. None of the six slices currently owns these, so they ship as English-inside-ko unless they get a home.UI_LOCALE_PREFERENCESis fine — it is consumed asz.enum(UI_LOCALE_PREFERENCES), so the settings schema follows automatically.Two corrections to the issue text.
uiLocaleToIntlLocaleis alive (ui-locale.ts:167, 27 references across 10 files), so I added ko → ko-KR as specified. Its return type had to widen fromUiLocaletostring, sinceko-KRis not aUiLocale; I checked all 27 call sites and they pass straight intoIntl.*,toLocaleStringorlocaleCompare, and relative-time.ts's cache is already keyed{ locale: string }. The existing test asserteduiLocaleToIntlLocale(locale) === locale; ko is the first locale to break that identity, so I pinned the tag table and added anew Intl.Locale(tag).baseName === tagcanonicality check instead. Worth recording: the review suggestion to delete this function as a no-op identity stops applying once ko lands.The acceptance criterion
npm --workspace @maka/core testno longer exists on main — that script was split intobuild+test:dist, orchestrated from the root byrun-workspace-tests-parallel.mjs.What is on the branch.
ui-locale.ts(UI_LOCALES,isUiLocale,resolveSystemUiLocalewith/^ko(?:[-.]|$)/iureusing the existing trim and_→-path,uiLocaleToIntlLocale) plus tests: ko / ko-KR / ko_KR / KO-Kr / ko_KR.UTF-8 / ko-Kore-KR, order respect (['en','ko']→ en,['ko','en']→ ko),kok-IN→ en,resolveUiLocale('auto','ko'), persisted auto preserved, zh-CN/zh-TW/en unchanged. 29 green, compiled scope-only for the reason above, no widening. To produce the table I probed with temporary placeholders in the three core catalogs (core was 842/842 green in that state) and reverted them — the branch diff is two files.Questions. Atomic vertical or stacked branch? And are you happy for
relative-time.tsandtool-quiet-preview.tsto come into #3975 so core stays green throughout?The seven type-invisible sites look like their own tracking issue rather than scope creep here — I can open one and link it back, unless you would rather fold them into one of the existing slices.
heeoneie commentedon Sep 8, 2026
@me2seeks @scs0209 — flagging an overlap before either of us spends more time on it.
#4515 also implements this issue. On
feat/i18n-ko-native-e2e-3980,packages/core/src/ui-locale.tshasUI_LOCALES = ['zh-CN', 'zh-TW', 'en', 'ko'],koin theisUiLocaleguard, thekobranch inresolveSystemUiLocale, anduiLocaleToIntlLocalemappingko → ko-KR. That is #3975's entire scope.We converged almost exactly. The system-locale regex is character-identical on both sides (
/^ko(?:[-.]|$)/iu), and we both arrived atko → ko-KRindependently — worth recording, since the earlier review suggestion to dropuiLocaleToIntlLocaleas an identity no-op stops applying once ko exists.Differences, and I think the first is mine to give up:
uiLocaleToIntlLocalereturn type. I widened it tostring; feat(i18n-ko): native surfaces and E2E fixtures #4515 returns the literal union'zh-CN' | 'zh-TW' | 'en' | 'ko-KR'. Every current consumer passes the result straight intoIntl/toLocaleString/localeCompare, all of which takestring, so neither choice breaks a call site — but the union keeps the set of tags we actually emit visible in the signature, whichstringthrows away. I'd rather take feat(i18n-ko): native surfaces and E2E fixtures #4515's signature than mine.UI_LOCALESordering (...'en', 'ko'vs...'ko', 'en'). No behavioural difference —resolveSystemUiLocaleiterates the inputlanguages, notUI_LOCALES, so ordering only affects anything that maps over the array (a language picker's display order) and the union order. Happy either way.__tests__/ui-locale.test.tscoversko-KR,ko_KR,['en-US', 'ko-KR'] → en,resolveUiLocale('auto', 'ko')and theko → ko-KRmapping. Two cases I have are not pinned anywhere yet: the reverse order case (['ko', 'en'] → ko, so list order is asserted in both directions) andkok-IN → en. The second is the one I would not want to lose — both our patterns already reject Konkani, but nothing asserts it, and a later simplification to/^ko/iwould break it silently. Whichever branch carries the core commit, I would like at least that case to come with it.So the sequencing question is the real one, and it is the same one I asked on 6 Sep:
Is #4515 the atomic vertical? If it is, #3975 belongs inside it as a commit rather than as its own branch — I'll rebase what I have onto it and hand the core commit over. If it isn't, then #4515 currently also carries #3976–#3979 territory (packages/ui, desktop locales, cli), which are assigned to other people, and we should decide what stays in it.
One observation rather than a claim: #4515 shows merge conflicts at the moment. I suspect that is #3853 landing after the branch was cut — worth a look either way, @scs0209.
Also still open from my last comment, and #3978 is waiting on it (cc @ggbdpq): do
relative-time.tsandtool-quiet-preview.tscome into #3975 so core stays green throughout, leaving onlyredaction.tsto #3978?scs0209 commentedon Sep 8, 2026
@heeoneie Thanks for catching the overlap early — agreed we should not duplicate the core work.
To answer the sequencing question directly: #4515 is not the full atomic vertical for #3975–#3979. It was opened for #3980 (desktop native surfaces + E2E fixtures). Core
ui-localechanges landed on that branch only because enablingkothere requires the #3975 contract, and catalogs outside the assigned renderer slices currently use English stubs. It should not own #3976–#3979.Given you are already assigned to #3975, I think the cleanest split is:
UiCatalog, no standalonekoenable onmainbeyond the agreed atomic/stacked plan).ui-locale.ts/ core-test pieces on feat(i18n-ko): native surfaces and E2E fixtures #4515 as temporary until your feat(i18n-ko): core — add ko to UiLocale model #3975 work is the source of truth, then rebase feat(i18n-ko): native surfaces and E2E fixtures #4515 onto that (or onto the integration stack once sequencing is settled).'zh-CN' | 'zh-TW' | 'en' | 'ko-KR') over widening tostring.kok-IN → encase (and the bidirectional order asserts if you want). I am fine adopting those into whichever branch carries the core commit.Also confirmed on the conflict note: #4515 is currently
CONFLICTINGagainstmainafter #3853 landed — I will refresh that regardless.For
relative-time.ts/tool-quiet-preview.ts: I am fine with those coming into #3975 if that is what keeps core green; I will follow whatever you and @me2seeks decide there.Happy to adjust if maintainers want a different split.
heeoneie commentedon Sep 8, 2026
@scs0209 — thank you, and I appreciate you answering the scope question directly rather than letting it sit.
Agreed on all of it: #3975 keeps the core commit, #4515 stays scoped to #3980, the literal union wins over
string, and I will carrykok-IN → enplus the bidirectional order asserts in the core tests.One practical follow-on. You said you would treat the
ui-locale.tsand core-test pieces on #4515 as temporary until #3975 is the source — but there is no source to point at yet, because I have kept the work local rather than open a PR. So unless @me2seeks would rather I held off, I will open #3975 as a draft PR marked do-not-merge: it cannot land, so it does not enable ko on main, but it gives you, @ggbdpq and @xiechimon one branch to rebase onto instead of three private copies of the same core change. Expect its CI to be red for exactly the reason described below — that is the point of the atomic/stacked plan, not a regression.@me2seeks — that leaves one open question, and it is now the only thing blocking the other slices. Core does not compile with ko until three catalogs carry a ko entry:
redaction.ts,relative-time.tsandtool-quiet-preview.ts. Ifrelative-time.tsandtool-quiet-preview.tscome into #3975, core is self-contained and green at every point of a stacked branch, and onlyredaction.tsis left to #3978. @scs0209 has said he is fine with that shape and @ggbdpq is waiting on it. Is that what you want?me2seeks commentedon Sep 8, 2026
Thanks everyone. I am making the delivery decision now so the Korean work can proceed on one clear contract.
UiCatalog<T>remains strict. We will not widen it to an optional catalog type, becausekoinUI_LOCALESmeans Korean is a supported product locale, not a partially enabled preview.mainby itself.redaction.ts,relative-time.ts, andtool-quiet-preview.ts, together withui-locale.tsand the locale tests. Keep the literal Intl-tag union,kok-IN → en, and both system-language ordering assertions.Please also assign the type-invisible locale paths before integration, especially the E2E/Storybook locale globals and hardcoded locale branches, so
kocannot compile while silently rendering English.feat(i18n-ko): core — add ko to UiLocale model
heeoneie commentedon Sep 9, 2026
The shared core base is open as a draft: #5011.
@scs0209 @ggbdpq @xiechimon — that is the branch to rebase onto. It is
heeoneie:feat/i18n-ko-core-ui-locale, rebased onto 93a8dd7.It currently carries
ui-locale.tsand the locale tests only. The three core catalogs (redaction.ts,relative-time.ts,tool-quiet-preview.ts) land on it next, per @me2seeks's delivery decision — its typecheck is red until then, and the four remaining errors are exactly those three catalogs' missingkoentries.One note for anyone cutting a slice branch:
packages/core/src/model-metadata.generated.tsis gitignored, and a stale copy produces four unrelatedprovider-registry.tserrors.npm run sync:model-metadataright after rebasing clears it.The catalog count has also moved — 114 when I counted on 6 Sep, 126 as of 93a8dd7.
scs0209 commentedon Sep 9, 2026
@heeoneie Already rebased #4515 onto #5011 (
feat/i18n-ko-core-ui-locale/93a8dd7).feat(i18n-ko): core — add ko to the three UI copy catalogs
feat(i18n-ko): core — add ko to UiLocale model
heeoneie commentedon Sep 9, 2026
Correction to my earlier note in this thread: the stack base has moved.
I pointed at
heeoneie:feat/i18n-ko-core-ui-localeabove. That is superseded — @me2seeks opened #5100 as the official base insideapache/maka(branchstack/i18n-ko-core), recreating the reviewed core layer from #5011 at 36a4377 as a single commit on currentmain. @jackwener verified its combined patch ID matches the #5011 source range exactly.Because that branch now lives in
apache/maka, slice PRs can retarget their base to it directly instead of carrying temporary core commits. @scs0209 has already done exactly that on #4515 — rebased ontostack/i18n-ko-core, retargeted the PR base, and dropped the temporary core commits. That is the shape to copy.@ggbdpq @xiechimon — please build on #5100 rather than my fork branch.
#5011 stays open as the canonical reviewed source and the public record of the core work, but it is no longer the thing to stack on. #5100 is draft-only and does not merge by itself, so the delivery model is unchanged: only the complete, green stack is eligible for merge.
feat(cli): add Korean catalog for TUI
feat(i18n-ko): core — add ko to UiLocale model
feat(i18n-ko): core — add ko to the three UI copy catalogs
me2seeks commentedon Sep 29, 2026
Maintainer decision update — this supersedes my 2026-09-08 atomic-delivery decision.
We accept progressive Korean rollout. We will use the shared English-fallback catalog path so untranslated Korean copy can render in English while each translation slice is completed. This means Korean can become selectable / follow the system before every surface is translated; that mixed-language period is an accepted product behavior for this rollout.
The delivery sequence is:
ko, with English required and explicit coverage checks so fallback cannot hide missing copy in the already shipped Chinese locales.mainas its own squash commit.main; each PR keeps its own squash commit and author/co-author attribution. The temporary integration branch is for cross-slice visual/E2E validation, not the final all-in-one squash.Please do not add verbatim English
koblocks just to satisfy the old strict type. Keep real Korean copy and let the shared resolver provide the fallback. Maintainers will own the integration and cross-slice coordination. #3975 remains the core locale change; the broader Korean completion/release audit stays tracked separately.简体中文
维护者更新决定:本条替代我在 2026-09-08 发布的原子交付决定。
我们接受韩语渐进式上线。使用共享的英文回退目录机制,在各翻译分片完成前,缺少的韩语文案显示英文。这意味着韩语可能在所有界面翻译完成前就可手动选择或随系统启用;本轮接受这段中英混合的过渡期。
交付顺序:先让所有受
ko启用影响的构建和类型检查路径安全支持回退;英文仍必须完整,并保留简繁中文完整性检查,避免回退掩盖已有语言漏译。然后核心启用 PR 单独 squash 到main;各翻译 PR 准备好后分别 squash 合入,各自保留提交及作者/共同作者归属。临时集成分支用于跨分片界面和 E2E 验证,不作为最终整体 squash 的来源。请不要为了满足旧严格类型而复制英文作为
ko占位;保留真实韩语,由共享解析器提供回退。跨分片集成由维护者负责。#3975 继续跟踪核心语言模型;整体韩语完成度和发布审计继续单独跟踪。