Skip to content

feat: multi-language support (pt/es) + internationalize remaining strings#376

Open
opastorello wants to merge 2 commits into
ArnasDon:mainfrom
opastorello:feat/i18n-full
Open

feat: multi-language support (pt/es) + internationalize remaining strings#376
opastorello wants to merge 2 commits into
ArnasDon:mainfrom
opastorello:feat/i18n-full

Conversation

@opastorello

@opastorello opastorello commented Jul 12, 2026

Copy link
Copy Markdown

Summary

Ship full multi-language support: add pt/es catalogs, internationalize the remaining hardcoded strings, and fix several i18n leaks (some visible even in English). The locale stays a single global value driven by NEXT_PUBLIC_APP_LOCALE; English remains the default and fallback.

What changed

  • New catalogs: messages/pt.json (pt-BR) and messages/es.json (es-419), key-for-key identical to en.json (1683 leaf keys each; ICU/interpolation arguments preserved).
  • ~250 hardcoded strings internationalized across ~30 files: Notifications, AI agent (page + Playground + Usage), Signup, Forgot password, Join/invite, Flows header, interactive message builder/preview, flow & automation template galleries, theme picker, and scattered toasts/placeholders/aria-labels.
  • Bug fixes (raw keys shown even in English): missing Settings.sections.quick-replies; Overview role chip scope 'roles' -> 'Settings.roles'; scopesHint <code className> -> attribute-less <code>.
  • Base UI Select triggers: 14 bare <SelectValue /> now render the selected option's translated label instead of the raw enum value.
  • API scope descriptions moved to Settings.apiKeys.scopeDesc.*.

Data files (flow/automation templates, themes) keep English name/description as seed fallback; galleries resolve display copy via useTranslations keyed by each item's stable id.

Test plan

  • npm run typecheck clean.
  • npm run lint — 0 errors.
  • npm run build succeeds.
  • en/pt/es catalogs verified key-for-key identical; ICU argument parity checked per-string (0 mismatches); all useTranslations scopes resolve to a catalog namespace (guards against raw-key leaks).
  • Deployed with NEXT_PUBLIC_APP_LOCALE=pt and spot-checked the converted screens.

Related

Closes #375. Also resolves #173 (make UI text translatable without fork conflicts).

Make the whole UI follow NEXT_PUBLIC_APP_LOCALE and ship two extra
locales.

- Add messages/pt.json (Brazilian Portuguese) and messages/es.json
  (Latin American Spanish); en/pt/es stay key-for-key identical.
- Internationalize ~250 previously hardcoded English strings across
  ~30 files (Notifications, AI agent, Signup, Forgot password, Join,
  Flows header, interactive message builder, flow/automation template
  galleries, theme picker, and scattered toasts/placeholders/aria).
- Fix pre-existing i18n leaks that showed raw keys even in English:
  missing Settings.sections.quick-replies label; Overview role chip
  using useTranslations('roles') instead of 'Settings.roles'; the
  API-key scopesHint message embedding <code className> (next-intl
  rich tags must be attribute-less).
- Fix Base UI Select triggers that rendered the raw enum value while
  options were translated — 14 <SelectValue /> now show the selected
  option's label.
- Internationalize API scope descriptions (Settings.apiKeys.scopeDesc).

Data files (flow/automation templates, themes) keep their English
name/description as seed fallback; galleries resolve display copy via
useTranslations keyed by each item's stable id.

typecheck, lint (0 errors), and production build pass.
@opastorello opastorello requested a review from ArnasDon as a code owner July 12, 2026 19:32
@ivanmercedes

Copy link
Copy Markdown
Contributor

Great work on this!

One small note: this PR also appears to resolve issue #173, so it might be worth adding Closes #173

@opastorello

Copy link
Copy Markdown
Author

Thanks @ivanmercedes! Good catch — updated the description to reference #173. This PR follows exactly that shape: English stays the default and fallback, and adding a locale is just one new messages/<locale>.json file, so forks can translate without fighting merge conflicts.

@ArnasDon

Copy link
Copy Markdown
Owner

Reviewed the branch locally — key parity (1683 keys × en/es/pt), ICU argument parity, typecheck, and lint (0 errors) all check out, and request.ts loads the new catalogs correctly. A couple of small things worth improving:

1. Missing t in some useCallback deps. A few converted callbacks now call t() but don't list it in their dependency array, unlike the other files in this PR — it adds new react-hooks/exhaustive-deps warnings:

  • members-tab.tsx (load, ~L177)
  • whatsapp-config.tsx (~L166)
  • quick-replies-manager.tsx (save L113, remove L125)

Harmless with a fixed global locale, but worth aligning for consistency (or intentionally leaving them all out).

2. Two pre-existing raw-key leaks not covered (optional, since this PR is cleaning up i18n leaks anyway):

  • automation-builder.tsx:1157t("delete", { defaultValue: "Delete" })
  • contact-detail-view.tsx:471t("tabs.tags", { fallback: "Tags" })

next-intl ignores defaultValue/fallback, and neither key exists, so both render the raw key path. Adding Automations.builder.delete and Contacts.detailView.tabs.tags (× 3 locales) would fix them.

Neither blocks merge — otherwise LGTM.

- members-tab, whatsapp-config, quick-replies-manager: include t in
  useCallback dependency arrays for consistency with the rest of the PR
- add Automations.builder.delete and Contacts.detailView.tabs.tags to
  en/pt/es (next-intl ignores defaultValue/fallback options, so both
  rendered raw key paths)
@opastorello

Copy link
Copy Markdown
Author

Thanks for the thorough review @ArnasDon! Both points addressed in d539518:

  1. useCallback deps — added t to the dependency arrays in members-tab.tsx, whatsapp-config.tsx, and quick-replies-manager.tsx (both save and remove), matching the convention used elsewhere in the PR. The three exhaustive-deps warnings are gone.
  2. Raw-key leaks — added Automations.builder.delete and Contacts.detailView.tabs.tags to all three catalogs (en/pt/es, now 1685 keys each, still in full parity) and dropped the ignored defaultValue/fallback options from the call sites.

typecheck and lint (0 errors) still clean.

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

Labels

None yet

Projects

None yet

3 participants