Skip to content

fix(coach): failure and refusal messages in the app's language - #351

Merged
DuarteSantos8 merged 1 commit into
DuarteSantos8:mainfrom
kurktchiev:gh/coach-errors-i18n
Oct 7, 2026
Merged

DuarteSantos8 merged 1 commit into
DuarteSantos8:mainfrom
kurktchiev:gh/coach-errors-i18n

Conversation

@kurktchiev

Copy link
Copy Markdown
Contributor

A lifter using the app in any language but English read English whenever a Coach job failed or
the server refused one. Three places:

  1. JOB_ERRORS and BYOK_ERRORS in lib/coach-api.js were English literals that never went
    through t(). The chat's error bubble ("The Coach took too long and gave up.") and the toast
    on a phone with its own key were English. Every line is now a getter over t(), looked up
    when it is read. The module loads in English before any pack has arrived, so a map
    translated at load time would never follow a language switch. The lookup is still
    JOB_ERRORS[cls], so jobErrorText is unchanged.
  2. A refused request (review, plan, refine, debrief) answers { error: <English words>, code: <class> }. CoachChat's composer, its quick actions and CoachIntake toasted
    e.message, the English words. They now show JOB_ERRORS[e.data?.code] first, and the
    server's words only when the class has no line: shared, which routes.js words verbatim on
    purpose because it names who can fix it. JOB_ERRORS also gains a line for unprivileged,
    the one refusal class it had none for, in the same words as USER_ERROR.unprivileged.
  3. The guard in front of every Coach user route answered 503 with { error } and no class, so
    on a switched-off Coach even the change above fell through to English. It sends code: 'off'
    now, the class enqueue already throws for the same state. openapi.yaml's CoachOff
    documents it, and website/api.html is regenerated. build-api-docs.mjs has no --check on
    main yet (that is build(docs): build-api-docs gains --check, and CI runs it #342); a second run gives the same file.

15 new keys, in all 16 packs, in a new section at the end of each ("AI Coach: why a job failed or
was refused"). Busy, consent and no-workout already had keys, because the phone and demo Coach
throw them through t(). pt-BR overrides 12 of the 15; the other 3 ("not set up on this
instance", "not installed properly on this instance", "switched off on this instance for safety
reasons") read the same in both variants and inherit from pt. That moves the pt-BR inheritance
fingerprint: pt-br-inheritance-fingerprint.mjs --list differs from before only by those 3
keys (738 → 741 inherited, 965 → 977 overrides). The new lines were translated with Claude, and
no native speaker has checked them in any pack.

Tests, all passing:

  • lib/coach-api.i18n.test.js, new (22). For each of the 16 packs, through setLang, every
    JOB_ERRORS and BYOK_ERRORS class has an entry, it is not the English text, and the map
    returns it. A language picked after the module loaded changes the text, and switching back
    gives English (both voices, and the provider detail still appended on a phone). It also reads
    api/coach/routes.js: The guard's 503 names a class with a line here, every class
    USER_ERROR words has a line except shared, and the unprivileged line matches the
    server's words. 21 of the 22 fail against the old coach-api.js and routes.js.
  • views/CoachChat.refusal.test.jsx, new (4). The composer and a quick action toast the app's
    line for a refusal class; shared keeps the server's words; a plain network error still
    shows its message. The two class cases fail against the old CoachChat.jsx.
  • CoachIntake.test.jsx +1 (34) and api/test/routes.test.js +1 (7), each failing against the
    old code. The routes test checks all seven guarded user routes, with the Coach switched off
    and with no provider connected.
  • Also run: pt-br-locale.test.js (4), locale-coverage (2), i18n-core (33), coach.test.js (67),
    coach-demo (35), coach-local (11), CoachChat.test.jsx (12), CoachChat.fast-failure (4),
    CoachChat.demo-failure (1), api openapi-routes (3) and jobs (25). check-locales.mjs: 16
    locales, 1,718 keys each. check-source-strings.mjs: 1,357 strings.

Overlaps:

🤖 Generated with Claude Code

A lifter using the app in any language but English read English
whenever a Coach job failed or the server refused one.

- JOB_ERRORS and BYOK_ERRORS in lib/coach-api.js were English literals
  that never went through t(), so the chat's error bubble ("The Coach
  took too long and gave up.") and the toast on a phone with its own key
  were English. Every line is now a getter over t(), looked up when it
  is read: the module loads in English before any pack arrives, so text
  fixed at load time would never follow a language switch.
- A refused request (review, plan, refine, debrief) answers
  { error: <English words>, code: <class> }. The composer, the quick
  actions and the intake toasted e.message, the English words. They now
  show JOB_ERRORS[e.data?.code] first and the server's words only when
  the class has no line ('shared', which the server words verbatim on
  purpose). JOB_ERRORS gains the one refusal class it had no line for,
  'unprivileged', in the server's own words.
- The guard in front of every Coach user route answered 503 with the
  words and no class, so a switched-off Coach toasted English even
  after the change above. It sends code 'off' now, the class enqueue
  already uses for the same state. openapi.yaml documents it and
  website/api.html is regenerated.

15 new keys, in all 16 packs, in a new section at the end of each.
pt-BR overrides 12 of them; the other 3 read the same in both variants
and inherit from pt, so the pt-BR inheritance fingerprint moves. Its
--list output differs from before only by those 3 keys.

Tests: coach-api.i18n.test.js is new (22): every line in every pack,
a language switch after load, and pins against routes.js's refusal
classes. CoachChat.refusal.test.jsx is new (4). CoachIntake.test.jsx
gains one (34) and routes.test.js one (7). Against the old code 21, 2,
1 and 1 of them fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@DuarteSantos8
DuarteSantos8 merged commit 97b73c6 into DuarteSantos8:main Oct 7, 2026
2 of 4 checks passed
@DuarteSantos8

Copy link
Copy Markdown
Owner

Merged and released in v1.3.10, thanks @kurktchiev!

https://github.com/DuarteSantos8/openGym/releases/tag/v1.3.10

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants