Skip to content

feat(i18n): add Brazilian Portuguese (pt-BR) interface language - #79

Merged
diwenne merged 1 commit into
diwenne:mainfrom
in100tiva:feat/i18n-pt-br
Oct 5, 2026
Merged

diwenne merged 1 commit into
diwenne:mainfrom
in100tiva:feat/i18n-pt-br

Conversation

@in100tiva

Copy link
Copy Markdown

What

Adds Brazilian Portuguese (pt-BR) as a third interface language, next to English and Traditional Chinese.

Brazil is one of Instagram's largest markets and comment-to-DM is a very common growth tactic for Brazilian creators, agencies and small businesses. Most of them run ManyChat today, and a Portuguese interface makes OpenReply usable for teams that don't work in English.

How

It follows the existing localization design in docs/localization.md exactly, with no new mechanism:

File Change
lib/i18n/pt-BR.json New. 448 entries, the same English source keys as zh-TW.json, same order.
lib/i18n/index.ts Locale becomes "en" | "zh-TW" | "pt-BR"; isLocale accepts pt-BR; t() looks the message up in a small catalogs map instead of the zh-TW ternary.
components/language-switcher.tsx Adds the Português (Brasil) option (with lang="pt-BR").
__tests__/i18n.test.ts Existing zh-TW tests untouched; adds pt-BR coverage (see below).
__tests__/i18n-actions.test.ts Adds "renders Brazilian Portuguese from the saved cookie".
docs/localization.md, README.md Mention the new language and the rule that catalogs share the same keys.

Unchanged on purpose: the cookie name and flags, the server action, the provider, English as the fallback, and everything outside the interface-translation scope (marketing pages, legal pages, auth emails, campaign content, the worker). No new dependency, no schema change.

Keeping the catalogs in sync

The catalogs could drift as new UI copy lands, so this PR adds two guards:

  1. Compile time: ptBRCatalog satisfies Record<MessageKey, string>. A key that exists in zh-TW.json but is missing from pt-BR.json becomes a typecheck error, not an undefined at render time.
  2. Test time: keeps the Brazilian Portuguese catalog on exactly the same keys compares the two key sets, which also catches a stale extra key after copy is removed.

The existing test has complete, plain-text translations with matching interpolation fields now runs over both catalogs. So every pt-BR entry is non-empty, plain text, and keeps exactly the same {placeholders} as its English key.

Translation notes

  • Natural Brazilian Portuguese, the way SaaS products in Brazil usually word it: campaign → campanha, keyword → palavra-chave, inbox → caixa de entrada, workspace → espaço de trabalho, follow gate → trava de seguir, tracked link → link rastreado.
  • Words Brazilian Instagram users already use as-is stay in English: DM, reel, story, CTR, webhook.
  • Fragments that are concatenated in the UI keep their leading and trailing spaces and punctuation (for example " (capped at {count})" → " (máximo de {count})", and "You: " → "Você: ").
  • Short weekday labels are Seg/Ter/Qua/Qui/Sex/Sáb/Dom.

Checks

Check Result
npm test ✅ 312 passed, 15 skipped
npm run lint ✅ clean
npm run typecheck ⚠️ One error, pre-existing on main and unrelated to this PR: lib/queue/dm-worker.ts(855,7): Type 'string | null' is not assignable to type 'string' (introduced by 257e38d; CI on main is red for the same reason). Nothing else is reported, including in the i18n files.
npm run build ⚠️ Blocked on main by the same dm-worker.ts type error. I built and ran this same i18n change on top of 086c155 (the last green main), in a self-hosted Docker deployment.

Manual verification (self-hosted instance)

  • With the openreply-locale=pt-BR cookie, the sign-in page is server-rendered in Portuguese ("Entrar", "Enviar um link mágico por e-mail") and the localized wrapper carries lang="pt-BR".
  • The language switcher lists Português (Brasil) next to the existing options.

I have not yet walked through the full browser checklist in docs/localization.md (the draft-survives-switch and narrow-viewport steps). I'll report back here once I have.

Maintenance

I'm happy to keep pt-BR.json up to date as new UI copy lands. Feel free to ping me on any PR that adds keys.

Adds a third interface language next to English and Traditional Chinese.
The pt-BR catalog translates the same 448 English source keys as zh-TW,
keeps every {placeholder} intact and is wired through the existing
locale cookie, server action and provider — no new dependency, schema
or worker change.

A `satisfies` check makes a missing pt-BR key a compile error, and a
test pins the pt-BR key set to the zh-TW one so the catalogs cannot
drift apart.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PEPFq6M4tCRdtCgXbnRt5a
@vercel

vercel Bot commented Sep 30, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the diwenne's projects Team on Vercel.

A member of the Team first needs to authorize it.

@diwenne
diwenne merged commit dca8713 into diwenne:main Oct 5, 2026
1 check failed
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