Add locale mechanism for Work System auto-filed issue text - #1695
Open
takanorinishida wants to merge 3 commits into
Open
Add locale mechanism for Work System auto-filed issue text#1695takanorinishida wants to merge 3 commits into
takanorinishida wants to merge 3 commits into
Conversation
WorkSweep.ts and ReminderRouter.hook.ts wrote every issue body string
in English, hardcoded inline. Non-English LifeOS users had no way to
get issue text in their own language without hand-patching SYSTEM code.
Adds hooks/lib/work-strings.ts: a key-value locale bundle with a
built-in EN default (a byte-for-byte port of the strings that used to
be inline) and key-level fallback to a user-supplied
USER/CONFIG/locales/<lang>.json. Never throws — a missing, malformed,
or partial locale file degrades gracefully to English for whatever
keys it doesn't cover.
WORK.ISSUE_LANGUAGE in config.yaml selects the locale (default "en",
unchanged output). Labels (Type:*, Status:*, Priority:*, Property:*,
Agent:*) and the bracketed title prefixes ([Goal], [Sweep],
[Project-Check], [BPE], [Reminder], [Research], [Queue], [Native]) are
classification tokens that dedup logic and the Pulse kanban badge
depend on — they stay English regardless of locale.
USER/CONFIG/locales/<lang>.json is documented as a new INTERFACE path
in SystemUserBoundary.md (schema ships in SYSTEM via the EN bundle,
values are USER data), matching the existing USER/CONFIG/*.json
precedent (memory-review.json). Resolved via LifeosConfig.ts's
paiUserDir(), never a literal LIFEOS/USER/ path.
Reminder trigger phrases and relative-date parsing in
ReminderRouter.hook.ts are untouched — still English-only by design.
A non-English request routes fine if it uses an English trigger verb
("remind me to <anything> tomorrow"); only the resulting issue body
language and free-text subject change. Documented as a known
limitation, not a partial implementation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hand-writing a USER/CONFIG/locales/<lang>.json against the key set in hooks/lib/work-strings.ts's EN bundle means keeping two things in sync by memory. This CLI removes that: it generates a full scaffold (every key present, English placeholders) so a locale file is always valid JSON with complete coverage from the moment it's created, and a --check mode reports missing/extra keys against the current EN bundle so drift (e.g. after a LifeOS update adds a key) is visible instead of silent. InitWorkLocale.ts <lang> scaffold InitWorkLocale.ts <lang> --force overwrite InitWorkLocale.ts <lang> --check report drift, don't write InitWorkLocale.ts --list enumerate installed locales + coverage % <lang> is validated against a BCP-47-ish regex before it ever becomes part of a filesystem path (it's used directly as a filename), with a resolved-path containment check as a second layer. Zero LLM calls — deterministic file generation only, matching the existing WorkSweep.ts / BootstrapLabels.ts pattern (gh + fs, nothing else). Doesn't write WORK.ISSUE_LANGUAGE itself; that's left for the principal to set after reviewing the translation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
sweepGoals() and sweepBpeCadence() hardcoded "Agent:kai" instead of using getDAName() like every other label-building site in this file (sweepSessions, sweepProjectChecks). Any repo whose labels.yml doesn't have an Agent:kai entry — i.e. every principal who isn't named "kai" — silently drops the Agent label via filterLabels()'s filter-to-existing-labels behavior, with no error surfaced anywhere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Jul 30, 2026
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
WorkSweep.tsandReminderRouter.hook.tswrite every GitHub issue body they auto-file with hardcoded English strings. There was no way to get issue text in another language without hand-patching SYSTEM code.This PR adds a locale mechanism, all opt-in and English-unchanged by default:
hooks/lib/work-strings.ts(new) — at(lang, key, vars)helper with a built-inENbundle (a byte-for-byte port of the strings that used to be inline) and key-level fallback: a locale only needs to cover the keys it has a translation for; anything missing — or the whole locale file missing/malformed — falls back toEN. Never throws.hooks/lib/work-config.ts— addsWORK.ISSUE_LANGUAGE(default"en") to the existing config loader, following the sameextractScalar/fail-open pattern already used forCAPTURE_NATIVEetc.WorkSweep.ts/ReminderRouter.hook.ts— the four sub-sweep bodies and the reminder/research/queue body now route throught(). Control flow, dedup logic, and label construction are untouched.LIFEOS/TOOLS/InitWorkLocale.ts(new) — a deterministic scaffold/audit CLI (no LLM calls, matchingWorkSweep.ts's owngh+fs-only style):InitWorkLocale.ts <lang>writes a full locale file (every key present, English placeholders — valid and equivalent toenoutput the moment it's created),--checkreports missing/extra keys against the currentENbundle,--listshows installed locales + translation coverage. The<lang>argument is validated against a BCP-47-ish regex before it ever becomes part of a filesystem path (it's used directly as a filename), plus a resolved-path containment check as a second layer.WorkSystem.md, and a new INTERFACE row inSystemUserBoundary.mdforUSER/CONFIG/locales/<lang>.json(schema ships in SYSTEM via theENbundle, values are USER data — same shape as the existingUSER/CONFIG/memory-review.jsonprecedent). Locale files resolve viaLifeosConfig.ts'spaiUserDir(), never a literalLIFEOS/USER/...path.sweepGoals()andsweepBpeCadence()hardcoded"Agent:kai"instead ofgetDAName()like every other label site in the file, so any principal whoselabels.ymldoesn't have anAgent:kaientry (i.e. anyone who isn't named "kai") silently loses the Agent label viafilterLabels()'s filter-to-existing behavior.What stays English regardless of locale, by design: labels (
Type:*,Status:*,Priority:*,Property:*,Agent:*) and the bracketed title prefixes ([Goal],[Sweep],[Project-Check],[BPE],[Native],[Reminder],[Research],[Queue]) — these are classification tokens that dedup logic and the Pulse kanban badge depend on, not prose.Known limitations (documented in
WorkSystem.md, apply to every non-English locale alike):ReminderRouter.hook.ts's trigger phrases (remind me to …) and relative-date parsing (tomorrow,next friday) are intentionally left English-only in this PR — translating those would mean feeding locale-supplied regexes into a hook that runs on every prompt, which felt like a separate, more debatable change from "translate the output text." A non-English request still works and still gets theDue:date if it uses an English trigger verb, e.g.remind me to <anything, any language> tomorrow.classifyType()'s English keyword classification and the lack of ICU-style plural rules (interpolation is{name}literal substitution only) are the other two known gaps, called out in the docs with a suggested workaround (phrase around the count).Testing evidence
Fidelity check (en output unchanged): wrote a standalone script that reconstructs the original hardcoded body/title-building logic side by side with the new
t()-based version atlang="en", fed both identical synthetic inputs across all 4 sub-sweeps (native/algorithm session capture, with/without a stated goal, project-check, goal, BPE never-run/overdue) and bothReminderRoutercases (with/without a due date), and diffed. All 11 cases matched byte-for-byte.Real end-to-end on a private Work System repo (repo name redacted; same setup
WorkSweep.ts/ReminderRouter.hook.tsalready target):gh issue viewon the created issue confirmed: fully Japanese body, English labels including the correctedAgent:<DA name>(confirming the bugfix), and the English[BPE]/[bpe-audit-due]title tokens intact. Same pattern verified forReminderRouter.hook.tswithremind me to <Japanese text> tomorrow— Japanese body, correctDue:date, English labels. All test issues closed after inspection.Also verified:
WorkSweep.ts --dry-runruns produce 0 new issues (tag-based dedup for goals/BPE/sessions is untouched and still works with locale-translated bodies)ReminderRouter.hook.tssystemd --usertimer entrypoint (com.lifeos.worksweep.service) exitsstatus=0/SUCCESSwitherrors:[]de, one key translated) proved key-level fallback isn't Japanese-specific — the untranslated key fell back toen, and the presence ofde.jsondidn't affecten/jaoutputbun build --target=bunpasses clean on all four changed/new TypeScript files../../evil,ja/../x) toInitWorkLocale.tsare rejected before any file write🤖 Generated with Claude Code