Skip to content

v0.6.2 - Expanded i18n coverage and Dutch translation polish#118

Merged
bscott merged 6 commits into
mainfrom
v0.6.2
May 30, 2026
Merged

v0.6.2 - Expanded i18n coverage and Dutch translation polish#118
bscott merged 6 commits into
mainfrom
v0.6.2

Conversation

@bscott

@bscott bscott commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary

Expands UI translation coverage across the app and merges @callmenoodles's Dutch translation polish (#115).

Closes #115 (Dutch translation improvements — merged into this branch)
Addresses #116 (i18n coverage gaps — covers most surfaces; see below)

What's covered

Calendar

  • Month name (now translated server-side via the catalog instead of hardcoded Format("January 2006"))
  • Day abbreviations (Sun–Sat)
  • Today button
  • Export to iCal
  • Copy-confirmation toast in the iCal subscribe button

Subscriptions

  • Status display (Active / Cancelled / Paused / Trial) via new statusLabel template func
  • Schedule display via new scheduleLabel template func (handles both single-interval and "Every N Months" forms)
  • These now translate consistently on the dashboard, subscriptions list, and subscriptions table.

Forms

  • Payment-method, Account, URL, and Notes placeholders

Settings

  • Page subtitle ("Manage your SubTrackr preferences and data")
  • Section descriptions: Appearance, Export Data, Base URL, Calendar Subscription, Data Management, Email Notifications, Pushover, Webhook, Security, Currency, Date Format, Categories, API Keys
  • SMTP username/to-email placeholders
  • Pushover help text snippets
  • Webhook URL and headers placeholders
  • API key generate button, placeholder, and empty state
  • Category creation placeholder

Mobile menu

  • "Add Subscription" button (was hardcoded across calendar, dashboard, analytics, settings)

Locale parity

All four locales rebuilt to 282 keys with full parity:

  • en.json — canonical
  • de.json — German translations added
  • es.json — Spanish translations added
  • nl.json — includes both the new keys and @callmenoodles's polish (Agenda, Aankomende, herstellink, etc.)

Translations follow the existing AI-assisted convention documented in web/locales/README.md — native speakers welcome to refine.

Code changes

  • cmd/server/main.go — new statusLabelFunc, scheduleLabelFunc, monthLabelFunc template funcs registered on both FuncMap sites.
  • internal/handlers/subscription.go — Calendar handler now resolves MonthName through the i18n catalog.
  • internal/handlers/settings.goSettingsHandler now receives the i18n catalog and has an activeLang() helper; api-keys-list.html partial gets Lang so its empty state translates.

Deferred from #116

A few items are not in this PR — they need a separate effort:

  • Full API documentation translation (Settings → API Documentation section). It's large reference material and standard practice keeps it English; documented as English-by-design in web/locales/README.md.
  • Currency display names (every "USD - US Dollar" etc.). Sizable list, low signal-to-noise.
  • Locale-aware number formatting ($1,000.00 → $1.000,00). Architectural change touching every cost render path.

Will follow up on these in a separate issue/PR.

Test plan

  • go build ./... clean
  • go test ./... passes (handlers, i18n, models, service)
  • Smoke test: all four pages (calendar, dashboard, settings, subscriptions) return 200 in both English and Dutch
  • Verified Dutch calendar renders translated month name, day abbreviations, Today, and Export to iCal
  • Manual verification of each settings section in de/es/nl
  • Run playwright i18n-coverage spec (npx playwright test tests/i18n-coverage.spec.js --workers=1)

Greptile Summary

This PR expands i18n coverage across the app (calendar day/month names, subscription status and schedule labels, settings section descriptions, form placeholders, mobile menu) and merges Dutch translation polish from a community contributor. All four locale files (en, de, es, nl) are rebuilt to 282 keys with full parity.

  • New statusLabelFunc and scheduleLabelFunc Go template funcs translate subscription status/schedule consistently across the dashboard, subscriptions list, and table.
  • SettingsHandler now accepts the i18n catalog so the API-keys partial can render its empty state in the active language.
  • Dutch locale receives renamed keys (Agenda, Aankomende, herstellink) and a batch of newly added translation keys alongside the rest.

Confidence Score: 5/5

This PR is safe to merge — all changes are additive i18n wiring with no logic mutations, and all four locale files are complete and in parity.

The changes are purely translational: new template functions delegate to the i18n catalog with key-miss fallbacks, existing handler logic is untouched, and the locale files are verified complete. The two findings are edge-case defensive gaps (plural schedule label and printf format string) that are unreachable with the current four locales — they would only surface if a fifth, partially-translated locale were added.

cmd/server/main.go (scheduleLabelFunc plural branch) and templates/settings.html (printf with catalog format string) both lack key-miss guards that the rest of the codebase applies consistently.

Important Files Changed

Filename Overview
cmd/server/main.go Adds statusLabelFunc and scheduleLabelFunc template funcs; the plural branch of scheduleLabelFunc calls fmt.Sprintf on the raw catalog return value without checking for a key miss, which can produce garbled output.
internal/handlers/settings.go SettingsHandler now takes i18nCatalog; activeLang() helper mirrors the existing SubscriptionHandler pattern; Lang field correctly propagated to all api-keys-list.html render paths.
internal/handlers/subscription.go Calendar handler now resolves month name via i18n catalog with proper key-miss detection (monthName == monthKey fallback).
templates/settings.html Broad i18n coverage added; uses printf with a catalog-sourced format string for the high-cost threshold label, which produces garbled output on a translation miss.
templates/calendar.html Day abbreviations, Today button, Export to iCal, and clipboard copy-confirmation toast are now translated; html/template contextual escaping handles the script-block string values safely.
web/locales/en.json Canonical locale extended with 70+ new keys covering all surfaces touched in this PR; keys are consistent with template usage.
web/locales/nl.json Dutch translation polished (Agenda, Aankomende, herstellink) and extended to match the new canonical key set.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[HTTP Request] --> B{activeLang}
    B -->|lang setting| C[i18nCatalog.T]
    C -->|hit| D[Translated string]
    C -->|miss: returns key| E[Key string]

    D --> F{Template function}
    E --> F

    F -->|statusLabel| G{v != key?}
    G -->|yes| H[Return translation]
    G -->|no| I[Return raw status]

    F -->|scheduleLabel interval≤1| J{v != key?}
    J -->|yes| K[Return translation]
    J -->|no| L[Return raw schedule]

    F -->|scheduleLabel interval>1| M[fmt.Sprintf catalog.T ...]
    M -->|hit: has %d| N[Return 'Every 3 Months']
    M -->|miss: no %d in key| O[⚠️ Garbled output]

    F -->|monthName in subscription.go| P{monthName == monthKey?}
    P -->|yes fallback| Q[Go Format January]
    P -->|no| R[Return translation]
Loading

Fix All in Claude Code

Reviews (2): Last reviewed commit: "Address Greptile PR #118 review" | Re-trigger Greptile

callmenoodles and others added 5 commits May 27, 2026 07:51
Refines nl.json with more natural phrasing:
- Kalender → Agenda (calendar/appointment terminology)
- Komende → Aankomende (upcoming renewals)
- autopay → automatische incasso (form placeholder)
- resetlink → herstellink (auth)
- SMTP labels: noun-compound → prepositional phrasing

Thanks to @callmenoodles for the contribution!
Expands translation coverage across the UI:

- Calendar: month name, day abbreviations, Today button, Export to iCal,
  and the JS copy-confirmation toast now respect the active language.
- Dashboard, subscription list, and subscriptions table: subscription
  Status ("Active" / "Cancelled" / etc.) and schedule labels now
  translate via new statusLabel and scheduleLabel template funcs.
- Subscription form: payment-method, account, URL, and notes placeholders.
- Settings: page subtitle and most section descriptions (Appearance, Export
  Data, Base URL, Calendar Subscription, Data Management, Email, Pushover,
  Webhook, Security, Currency, Date Format, Categories, API Keys), plus
  associated placeholders and helper text.
- Hardcoded "Add Subscription" mobile-menu button across calendar,
  dashboard, analytics, and settings pages.

New keys added to en.json and translated into de/es/nl with the same
AI-assisted pass convention as prior locales (282 keys, full parity).

Also adds settings handler i18n catalog injection so api-keys-list.html
can resolve the empty state via translation.

Addresses #116. Includes Dutch translation polish from PR #115.
@bscott

bscott commented May 30, 2026

Copy link
Copy Markdown
Owner Author

Playwright i18n-coverage spec results:

18 passed (15.8s)

All es/de/nl translations render on dashboard, subscriptions, form/subscription, settings, analytics, and calendar pages. No English sentinels leaked through. (Firefox/Webkit projects skipped — browsers not installed locally; chromium covers all assertions.)

Comment thread internal/handlers/subscription.go
Comment thread internal/handlers/subscription.go
Comment thread cmd/server/main.go Outdated
Comment thread templates/calendar.html Outdated
- Fix dead-code fallback in Calendar handler: catalog.T() returns the key
  on miss, never empty, so the if monthName == "" guard was unreachable.
  Now compares against the key string to properly detect a missing
  translation.
- Remove unused monthLabelFunc template helper: not called from any
  template, since month translation is handled server-side in the
  Calendar handler. Avoids divergent fallback paths.
- Drop printf "%q" in calendar.html JS string interpolations: %q
  doesn't HTML-escape < > /, so a translation containing </script>
  could close the script tag. html/template's contextual auto-escape
  pipeline produces a safely-quoted JS string literal.
@bscott

bscott commented May 30, 2026

Copy link
Copy Markdown
Owner Author

Addressed Greptile's review (243f344):

  1. Dead fallback in Calendar handler — fixed. catalog.T() returns the key on miss, never "". Now compares against the key string to detect a real miss before falling back to firstOfMonth.Format("January").
  2. Unused monthLabelFunc — removed from both FuncMap registrations and its definition. Month translation is server-side in the Calendar handler; no template needed it.
  3. printf "%q" JS string injection risk — dropped both printf "%q" pipes. html/template's contextual auto-escape pipeline now produces the JS string literals ("Gekopieerd!", "Abonneren"), which correctly HTML-escapes < > /.

Verified locally:

  • go build ./... clean
  • Calendar in Dutch renders "Vandaag", "Exporteren naar iCal", day abbrs "Zo Ma Di Wo Do Vr Za", and the JS toast strings as properly-quoted Dutch ("Gekopieerd!", "Abonneren").
  • Playwright i18n-coverage spec: 18/18 chromium tests pass.

@bscott bscott merged commit e458470 into main May 30, 2026
2 checks passed
@bscott bscott deleted the v0.6.2 branch May 30, 2026 01:01
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