Skip to content

feat(i18n): add Spanish locale with full EN/ES translations#9635

Open
dfliess wants to merge 12 commits into
rilldata:mainfrom
dfliess:i18n-es-locale
Open

feat(i18n): add Spanish locale with full EN/ES translations#9635
dfliess wants to merge 12 commits into
rilldata:mainfrom
dfliess:i18n-es-locale

Conversation

@dfliess

@dfliess dfliess commented Jul 1, 2026

Copy link
Copy Markdown

Adds Spanish (ES) as a second locale to Rill, building on the Paraglide scaffolding from #9570.

What's included

  1. Spanish message cataloges.json with ~1,740 keys, full parity with en.json
  2. Localized web-common — all user-facing strings in shared components (~216 files)
  3. Localized web-admin — all user-facing strings in cloud admin (~231 files)
  4. LanguageSwitcher component — dropdown in the avatar menu to switch EN/ES, with spec and fixtures
  5. Locale utilitiesdocument-locale, luxon-locale, normalize-locale, escape-html, catalog integrity test
  6. User preferred locale — persists the user's language choice server-side (preferred_locale column, migration 0096, proto field, API handler); on next login the saved locale is applied automatically

How it works

  • Locale detection: localStoragepreferredLanguagebaseLocale (web-admin); preferredLanguagebaseLocale (web-local)
  • LanguageSwitcher calls UpdateUserPreferences to save the choice
  • +layout.ts reads preferredLocale from GetCurrentUser and applies it via setLocale
  • All imports use @rilldata/web-common/lib/i18n/gen/messages (matching feat: localization support #9570's structure)

Relation to #9621

This PR supersedes #9621. Rebased on current main (which includes #9570), dropped the framework scaffold (now upstream), kept only translations + language addition as requested in the review.

Test plan

  • go build ./... passes
  • Catalog integrity tests pass (key parity, no empties, parameter match, no duplicates)
  • Manual: switch locale via LanguageSwitcher in avatar menu, verify Spanish renders

dfliess added 6 commits July 1, 2026 10:39
Add es.json with full Spanish translations for all 1,733 user-facing
strings. Update inlang settings to include "es" in the locales array.
Merge Rill's existing organizations_overview_page_title key into both
catalogs.
Replace ~1,700 hard-coded English strings across shared viewer
components, features, and layout with m.key() calls backed by the
EN/ES message catalogs.
Replace hard-coded English strings across web-admin features
(projects, alerts, reports, branches, organizations, etc.) with
m.key() calls backed by the EN/ES message catalogs.
Add LanguageSwitcher dropdown component, locale utility modules
(document-locale, luxon-locale, normalize-locale, escape-html),
i18n init barrel, and catalog integrity test suite.
Add preferred_locale column (migration 0096), proto field, Go
handlers, and frontend plumbing so users can persist their language
choice via the API. On page load the +layout.ts resolver applies
the stored preference before rendering.

Proto generated files regenerated against current main.
Add canvas_tab_group, canvas_add_widget_to_tab,
canvas_add_widget_below_tabs, and edit_publish_merge_deploy_failed
to both EN and ES catalogs. These strings were introduced on main
after the original i18n branch diverged.
Comment thread admin/database/postgres/migrations/0096.sql Outdated
Comment thread web-common/src/components/i18n/LanguageSwitcher.svelte Outdated
Comment thread web-common/src/lib/i18n/locale-utils.ts
Comment thread web-common/src/lib/i18n/__tests__/catalog-integrity.spec.ts Outdated
dfliess added 4 commits July 2, 2026 10:23
Address review: keep the language choice in localStorage only (paraglide
localStorage strategy) instead of persisting it on the user record. Reverts
the preferred_locale column, proto field and admin handlers, and removes the
persistLocale plumbing from LanguageSwitcher/AvatarButton and the initial
locale resolver from the web-admin layout. The manual switcher is now
cloud-only: Rill Developer keeps upstream's browser-language detection and
no longer renders LanguageSwitcher.
Address review: collapse document-locale, escape-html, luxon-locale and
normalize-locale into a single locale-utils module re-exported from the
i18n index. normalize-locale is dropped entirely: its only consumer was
the removed backend preference resolver.
Address review: move the catalog checks from a vitest spec into
scripts/i18n-guard.js and generalize them to every file under messages/:
union of keys across locales with per-file missing-key reporting, per-key
parameter superset validation, duplicate top-level keys, and empty texts.
Messages can be plain strings or variant arrays and variants are checked
for internal consistency (selectors, match branches and placeholders must
resolve to declared inputs/locals). Catalog errors are exact and now fail
the quality pipeline; the hardcoded-string heuristic stays warning-level
until --strict.
…ches

Address review: replace singular/plural key pairs and inline count
conditionals with paraglide variant messages (declarations/selectors/match,
backed by Intl.PluralRules). Converted: dimension filter chips, chat
thinking durations, project status (parse errors, tables/views, compute
units), relative time, user management counts (users, groups, members,
projects, invite/added notifications including a two-selector variant),
env var key errors, canvas color palette, pivot drag labels and pivot
error counts. This also fixes Spanish forms that were grammatically wrong
for count=1 and restores upstream singular forms the extraction had
flattened.

Also repair issues surfaced while verifying: message keys referenced but
missing from the catalogs (github connect pages, workspace onboarding,
dashboards empty state), parameter-name mismatches (billing plan renewal,
invite error notifications, logs connection error), the welcome-message
fragment, the missing second time grain, All time casing, and two
pre-existing type errors caught by tsc-with-whitelist.
@dfliess dfliess requested a review from AdityaHegde July 2, 2026 09:44

@AdityaHegde AdityaHegde left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the quick set of changes. This is a massive PR so it will take time for me to fully review. Here are a few more changes needed.

Comment thread web-common/src/lib/i18n/messages/en.json Outdated
Comment thread web-common/src/lib/i18n/messages/en.json Outdated
Comment thread web-admin/src/features/projects/ProjectPage.svelte Outdated
Comment thread web-common/src/lib/i18n/messages/en.json
Comment thread web-admin/src/features/projects/settings/ProjectVisibilitySettings.svelte Outdated
Comment thread web-admin/src/features/billing/plans/dialog/CancelPlanDialog.svelte Outdated
Comment thread web-admin/src/routes/[organization]/[project]/+page.svelte Outdated
Comment thread web-common/src/features/dashboards/time-series/MetricsTimeSeriesCharts.svelte Outdated
Comment thread web-common/src/lib/time/config.ts Outdated
@nishantmonu51 nishantmonu51 added Type:Feature New feature request Size:XL Very large change: 2,000+ lines labels Jul 2, 2026
…sh Spanish catalog

Review feedback:
- Convert remaining count-based messages to plural variants
  (status_levels_selected, users_already_member, users_failed_invite,
  users_failed_add_groups, users_failed_invite_users, groups_total_count)
- Restore texts changed during extraction (Enter a search term,
  Successfully invited..., lowercase user/users descriptions)
- Restore lost ellipses, aria-labels, grain capitalization and inline
  markup, injecting escaped params via {@html} where markup wraps data
- Type kindTitleMap as Record<ProjectPageKindParam, () => string>, reuse
  translateV1TimeGrain, key TIME_COMPARISON by TimeComparisonOption, and
  use alert_edit for the Edit alert trigger

Fidelity sweep over the full diff:
- Split shared keys that flattened distinct source texts (custom range,
  download PNG, search placeholders, no-results, alert/report created-by
  metadata, branch/subpath labels, learn more, loading tables, delete org
  label, filters-only switch)
- Restore typographic apostrophes, Unicode ellipsis, exact wording, and
  the interleaved GitHub user/repo inline components in retry-auth
- Localize hardcoded Viewer role params; fix duplicated Copy in
  copy-to-clipboard tooltips

Spanish catalog:
- Fix gender/number agreement and impersonal se-constructions; align
  tu/usted with each section's dominant register
- Unify terminology: dashboard (was tablero/panel), Visualizador,
  ranking, minigrafico
- Fix mistranslations and gender-dependent billing messages
@dfliess

dfliess commented Jul 2, 2026

Copy link
Copy Markdown
Author

Thanks a lot for the thorough review, really appreciate the time given the size of this PR.

All comments are addressed in 66de2da. Since most of them were fidelity regressions (lost ellipses/markup/aria-labels, altered texts, flattened plurals), we also swept the entire diff for the same classes of issues and fixed ~35 more occurrences of the same patterns (shared keys flattening distinct source texts, typographic apostrophes, a couple of inline components left dangling outside their sentence, etc., grouped in the commit message). We also did a native-speaker pass over the Spanish catalog.

Happy to adjust anything or split changes out if it makes reviewing easier, glad to collaborate however works best for you.

@dfliess dfliess requested a review from AdityaHegde July 2, 2026 21:37

@AdityaHegde AdityaHegde left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks I did miss some. Splitting PRs will cause more disruptions for other PRs, so a single one is better IMO.

Also please run npm run quality:ci and fix any issues as well.

Allow existing and new Rill users with a <b>@{$userDomain.data}</b>
email address to join this project as a <b>Viewer</b>.
{@html m.common_allow_domain_join_project({
domain: `<b>@${$userDomain.data}</b>`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Missing htmlEscape similar to other places.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done, added escapeHtml here (and to the role param and the few remaining interpolations inside {@html} params across the PR, for consistency).

Allow existing and new Rill users with a <b>@{$userDomain.data}</b>
email address to join this org as a <b>Viewer</b>.
{@html m.settings_allow_domain_description({
domain: `<b>@${$userDomain.data}</b>`,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Another instance of missing html escape.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done, same fix.

…locals

- escapeHtml on the domain/role params of the domain-allowlist labels
  (review feedback), and on the remaining interpolations inside {@html}
  message params (plan name, docs links) for consistency
- remove leftover getMode helper and an unused import flagged by
  npm run quality:ci, which now passes clean
@dfliess

dfliess commented Jul 3, 2026

Copy link
Copy Markdown
Author

Ran npm run quality:ci and fixed the two issues it caught (an unused helper left behind by the extraction and an unused import). It now passes clean, including the TypeScript whitelist check. Changes in ad97ee5.

@dfliess dfliess requested a review from AdityaHegde July 3, 2026 07:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Size:XL Very large change: 2,000+ lines Type:Feature New feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants