Skip to content

Internationalize auth toasts and settings validation messages - #8

Open
pdlourenco wants to merge 2 commits into
devfrom
fix/i18n-auth-validation
Open

pdlourenco wants to merge 2 commits into
devfrom
fix/i18n-auth-validation

Conversation

@pdlourenco

Copy link
Copy Markdown
Owner

Fixes #4. For review before sending upstream. Branched from dev, independent of #6 and #7.

Problem

Several user-facing strings bypassed Paraglide entirely, so a pt-PT (or any non-English) user saw English text:

  • Auth toasts (src/lib/stores/auth.svelte.ts) — "Login successful", "User created successfully. Please login.", "Profile updated successfully"
  • Settings validation (src/lib/helper/settings-form.helper.ts) — "Currency is required", "Format not valid", Invalid timzone value. (with the user-visible typo), the UK MPG rule, and zod's raw Unit Of Lpg: Invalid option: expected one of "liter"|...

Changes

Schema factory is now locale-aware. createSettingsConfigSchema takes the messages module as its first argument (matching createSettingsOptions(m, locales)), and the schema is built by a buildSettingsConfigSchema(m) factory. Message getters are passed lazily ({ error: () => m.foo() }) so errors follow the locale active at validation time, not schema-construction time — this matters because the locale can change without a full remount.

Enum errors now use a shared settings_error_invalid_option message instead of leaking zod internals.

Existing keys reused where they existed (settings_error_currency_required, settings_error_date_format_invalid, settings_error_timezone_invalid) — the catalog already spelled "timezone" correctly, so wiring it up fixes the typo for free.

Scope note: the issue listed only the three success toasts, but the Login failed: / Registration failed: / Update failed: prefixes are hardcoded in the same way, so I did those too rather than leave the store half-translated.

New keys (8, added to en.json only)

auth_toast_login_success, auth_toast_login_error_prefix, auth_toast_register_success, auth_toast_register_error_prefix, profile_toast_updated, profile_toast_error_prefix, settings_error_invalid_option, settings_error_uk_mpg_requirements

Other locales fall back to English until translated — strictly better than hardcoded, and the same pattern earlier language PRs used.

Verification

  • New tests (src/__tests__/settings-validation.test.ts, 4 passing) covering each localized message, that the enum error no longer contains Invalid option: expected one of, that the timezone message no longer contains timzone, and that messages follow a locale change (asserted against de). CI already runs pnpm run test.
  • pnpm build, pnpm check (5073 files, 0 errors), pnpm lint — all clean.

Pre-existing bug found while testing (not fixed here)

createSettingsConfigSchema throws when called without includeNotificationProcessingSchedule: true:

.omit() cannot be used on object schemas containing refinements

zod v4 rejects .omit() on a schema that already has .refine(). Confirmed present on unmodified dev, so it predates this change; the only caller passes the flag, which is why nobody has hit it — the second overload is effectively dead code that would throw if used. Out of scope here; worth its own issue (fix would be to omit before refining).

Follow-up for the pt-PT PR

The 8 new keys will need pt-PT values in javedh-dev#240 — happy to add them once this lands.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YXazaegVWZdeKziwMzPKPG


Generated by Claude Code

@pdlourenco pdlourenco left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — LGTM ✅ (most thoroughly verified of the four)

Ran the full verification myself on this branch:

  • Test suite: 29/29 passing (25 existing + the 4 new ones) — reproduced locally. (First run failed in my sandbox, but that was my environment's inlang-plugin setup, not the PR — after fixing my local paraglide compile the suite is green.)
  • svelte-check: 0 errors across the workspace, so the signature change is consistent — and I confirmed the only production caller is src/routes/(app)/settings/+page.svelte:42, updated in this diff.
  • The locale-following test is a real assertion: I checked de.json and it does contain settings_error_currency_required, so the de comparison isn't trivially passing through the English fallback. Nice that the test restores the locale in a finally.
  • The claimed pre-existing .omit()-after-.refine() bug reproduces exactly: z.object({...}).refine(...).omit(...) throws .omit() cannot be used on object schemas containing refinements on this zod version. Confirmed it predates this PR and only the dead second-overload path hits it. Please do file it as its own issue so it doesn't get lost in this PR's description — the "omit before refine" fix suggestion is right.

Design points I specifically like: lazy { error: () => m.foo() } getters (correctly makes messages follow the locale at validation time — and the new test proves it), reusing the three existing settings_error_* keys instead of minting duplicates (which kills the "timzone" typo for free), and replacing raw zod enum internals with settings_error_invalid_option.

Scope expansion to the error-prefix toasts is justified — leaving the store half-hardcoded would have been worse.

Coordination notes: (1) disjoint-hunk overlap with #7 in settings-form.helper.ts and with #10 in en.json — both should merge cleanly regardless of order; (2) the 8 new keys need pt-PT values in upstream javedh-dev#240 once this lands, as the description already notes.

No changes requested.


Generated by Claude Code

claude added 2 commits August 24, 2026 14:11
Several user-facing strings bypassed Paraglide and always rendered in
English:

- Login, registration and profile toasts in the auth store, including
  the "Login failed:"/"Registration failed:"/"Update failed:" prefixes.
- Settings validation messages produced by the zod schema: the required
  currency, the date format and timezone refinements, the UK MPG rule,
  and the raw "Invalid option: expected one of ..." text emitted for
  enum fields.

The schema is now built through a factory that takes the messages module,
with message getters called lazily so errors follow the locale that is
active when the form is validated. The existing catalog keys are reused
where they already existed; wiring settings_error_timezone_invalid also
resolves the "timzone" typo that was visible to users.

Adds tests covering the validation messages, including that they follow a
locale change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXazaegVWZdeKziwMzPKPG
The pt-PT catalog is now upstream, so the keys introduced here would
otherwise fall back to English for Portuguese users.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXazaegVWZdeKziwMzPKPG
@pdlourenco
pdlourenco force-pushed the fix/i18n-auth-validation branch from 2df39ae to 34e5641 Compare August 24, 2026 14:17

Copy link
Copy Markdown
Owner Author

pt-PT strings for review

The pt-PT catalog is now upstream (javedh-dev#240 merged), so I rebased this branch onto c54ac97 and added Portuguese values for the 8 new keys — without them these strings would fall back to English for pt-PT users. Pushed as 34e5641.

Key English Português (pt-PT)
auth_toast_login_success Login successful Sessão iniciada com sucesso
auth_toast_login_error_prefix Login failed: Falha ao iniciar sessão:
auth_toast_register_success User created successfully. Please login. Conta criada com sucesso. Inicie sessão.
auth_toast_register_error_prefix Registration failed: Falha ao criar a conta:
profile_toast_updated Profile updated successfully Perfil atualizado com sucesso
profile_toast_error_prefix Update failed: Falha ao atualizar:
settings_error_invalid_option Invalid option selected Opção inválida
settings_error_uk_mpg_requirements UK MPG calculation requires unit of distance to be miles and unit of volume to be litres. O cálculo em MPG britânico requer a distância em milhas e o volume em litros.

Judgement calls worth a second opinion

  • settings_error_invalid_option — shortened rather than translated literally ("Opção selecionada inválida"). Its siblings in the same validation family are already terse: Formato inválido, Fuso horário inválido, so consistency beat literalness.
  • settings_error_uk_mpg_requirements — reuses the established MPG britânico from settings_mileage_format_uk_mpg, plus milhas / litros to match common_mile / common_litre.
  • Error prefixes — the catalog has two competing patterns: Erro ao guardar: for save failures and Falha ao … for operation failures. I used Falha ao since the English reads "… failed:", but these could be Erro ao iniciar sessão: etc. if the other pattern is preferred.

Catalog state

Only en (source) and pt-PT carry these keys; the other ten locales alias to English via Paraglide's base-locale fallback, which is the same pattern previous language PRs used. No catalog is left partially populated.

Verification after the rebase

  • 819/819 keys in source order, placeholders identical across both catalogs
  • pnpm build — pt-PT variants present in the compiled output (Opção inválida confirmed in the emitted module)
  • pnpm test — 36 passing across 6 files
  • pnpm lint / pnpm check — clean
  • Merges cleanly onto upstream dev @ c54ac97

🤖 Generated with Claude Code

https://claude.ai/code/session_01YXazaegVWZdeKziwMzPKPG


Generated by Claude Code

}
return false;
} catch (err: any) {
console.error('Registration error:', err);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about this string? Shouldn't it be localized as well or are we only translating UI and not console?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deliberate: UI only, not console. console.error('Login error:', err) and its sibling on line 93 are developer diagnostics, not user-facing text.

That matches the existing convention rather than being a decision I made for this PR — I checked: 27 console.* calls across src/ (excluding generated Paraglide output), and 0 of them are localized. The server-side logger.info/error calls follow the same rule.

Three reasons it's the right split:

  1. Greppability. An English console string can be matched back to source. If a pt-PT user pastes Erro de início de sessão: into a bug report, the maintainer can't grep for it — and neither can they read it.
  2. Catalog bloat. Every translator in every locale would translate strings no user ever reads.
  3. The user-facing half of the same failure already is localized — the toast.error(...) two lines below each console.error. So on a failed login the person sees Portuguese, and the developer opening DevTools sees English. That's the intended division, and both branches of this function do it.

Related gap worth knowing about (not fixed here)

The localized toast interpolates a server-supplied string:

toast.error(`${m.auth_toast_login_error_prefix()}${err.response?.data?.message || err.message}`)

err.response?.data?.message comes from the API and is always English ('Invalid username or password', 'Username already exists', …). So a pt-PT user sees a half-translated toast: Falha ao iniciar sessão: Invalid username or password.

Fixing that properly means either localizing API error responses (the server would need per-request locale awareness) or mapping known server error codes to catalog keys on the client. Both are a design change well beyond this PR — worth its own issue if you want it. Flagging it so the half-English toast isn't mistaken for an oversight in this change.

Leaving both threads open for you to close, since this is an answer rather than a code change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YXazaegVWZdeKziwMzPKPG


Generated by Claude Code

} catch (err: any) {
this.user = null;
this.isLoggedIn = false;
console.error('Login error:', err);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below comment on missing translation for console errors

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answered on the line-93 thread: console diagnostics stay English by design (27 console.* calls in src/, 0 localized; server logger.* likewise), while the user-facing toast.error two lines below is localized. Same reasoning applies to this one.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YXazaegVWZdeKziwMzPKPG


Generated by Claude Code

@pdlourenco pdlourenco left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review of the update — LGTM ✅

Re-verified after the force-push:

  • Rebase is content-identical: the rebased commit's patch differs from the previously reviewed 2df39ae only in the blob-index line — no code drift smuggled in with the rebase onto the post-javedh-dev#240 dev.
  • New commit (pt-PT values for the 8 keys) — reviewed as European Portuguese, all approved:
    • "Sessão iniciada com sucesso" / "Falha ao iniciar sessão: " — matches the catalog's "Iniciar sessão" terminology exactly;
    • "Conta criada com sucesso. Inicie sessão." — matches the "Criar conta" button rather than the English "User created", which is the better localization;
    • "Falha ao …: " prefixes are consistent with the existing error-prefix pattern;
    • "Opção inválida" follows the "Formato inválido" convention;
    • "O cálculo em MPG britânico requer a distância em milhas e o volume em litros." — consistent with the "MPG britânico (milhas por galão imperial)" label, correct AO90, natural phrasing.
  • Integrity: 818/818 key parity between en.json and pt-PT.json, zero placeholder mismatches (scripted check).
  • Suite on the new head: 36/36 passing — the 29 from before plus the date-validation tests that arrived via the rebase, all green together.

Nice touch appending the keys in the same order as en.json. Since javedh-dev#240 merged upstream and dev is synced, updating both catalogs in this PR is exactly right; the other ten locales fall back to English for these 8 keys as before.

One thing still outstanding from the previous review: the pre-existing zod .omit()-after-.refine() bug (confirmed reproducible) deserves its own issue so it isn't lost when this PR merges.

No changes requested.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Thanks — both reviews addressed. Nothing to change in the code; two items to close out.

The zod .omit()-after-.refine() bug — already filed

Raised in both of your reviews: it's #9, filed before this comment, with the standalone repro, confirmation it's present on unmodified upstream dev (4b92734), the "omit before refine" fix sketch, and a note that it's deliberately excluded from this PR to keep the scope on i18n. It won't be lost when this merges.

The half-translated toast — also filed

The gap I flagged in the line-93 thread (localized prefix + English server message, e.g. Falha ao iniciar sessão: Invalid username or password) is now #12. Quantified there: 25 distinct AppError messages on the server and 14 client sites surfacing response?.data?.message — so it reaches most CRUD failure paths via entity-service.ts's extractApiError, not just the auth store. Three candidate approaches are laid out; it's an API-contract decision rather than a string swap, so it needs a design call before anyone writes code.

Small reconciliation on the key count

You reported 818/818 and my checks said 819/819 — both correct, different conventions. json.load() counts the $schema entry:

raw len (incl. $schema): 819 819
message keys only      : 818 818
parity: True

Noting it only so a future check landing on 819 isn't mistaken for a stray key.

Status

No code changes made in response to this review. Both review threads left open for you to close, since they were questions rather than change requests. Branch unchanged at 34e5641; still merges cleanly onto upstream dev @ c54ac97.

Holding this PR (along with #7 and #10) until upstream javedh-dev#247 gets a response — #6 goes first as the smallest, least ambiguous change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YXazaegVWZdeKziwMzPKPG


Generated by Claude Code

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.

Auth toasts and settings validation messages are not internationalized (plus "timzone" typo)

2 participants