Skip to content

feat: add Brazilian Portuguese (pt) UI language - #215

Merged
chattymin merged 5 commits into
chattymin:mainfrom
williansaez:claude/portuguese-translation-dlqezu
Aug 23, 2026
Merged

chattymin merged 5 commits into
chattymin:mainfrom
williansaez:claude/portuguese-translation-dlqezu

Conversation

@williansaez

Copy link
Copy Markdown
Contributor

Summary

Adds Brazilian Portuguese (pt) as a fifth UI language, following #159 (Spanish) and #185 (French) as templates.

  • AppLanguage gains a .pt case (native label "Português"), and systemDefault maps the pt system locale to it.
  • L.t(...) is extended to five languages and all 212 UI strings in Localization.swift are translated, in the informal "você" register that matches the app's playful companion tone.
  • PokemonNature.name(_:) gains 25 Portuguese names.
  • Adds LocalizationInterpolationTests, a parity guard asserting every language keeps its \(...) placeholders.

Every ko/en/ja/es string is byte-for-byte unchanged: the new argument was appended mechanically and the result verified by stripping the fifth argument back off and diffing against main.

Translation decisions worth a reviewer's attention

Pokémon species names deliberately stay English. PokéAPI has no pt language, and the core series has never shipped a Portuguese localisation, so Brazilian players use the English names anyway — AppLanguage.resolveName's English fallback is the expected result here, not a gap. "pt" is still listed in apiCodes/langCodes so that if PokéAPI ever adds it, it is picked up with no branch changes.

Nature names are not official, unlike the other four languages. With no Portuguese core-series localisation there is no official set to copy. They are adjectives agreeing with the feminine "natureza" (as the Spanish ones agree with "naturaleza"), picked so all 25 stay distinct.

"Shiny" is kept untranslated, on purpose. Pokémon GO's pt-BR does use an official "Brilhante", but "shiny" is overwhelmingly what the Brazilian community says and this app's audience is Brazilian developers. Flagging it explicitly because it diverges from the Spanish decision to adopt the official "Variocolor" — happy to switch the whole cluster (Amuleto Brilhante, ✨ Pokémon Brilhante!, …) if you prefer consistency with es.

Elsewhere the translation does follow official pt-BR franchise terminology where it exists: "Bolsa" for Bag, "chocar" for hatch, "Incubadora"/"Incubando", "Doce Raro" for Rare Candy, "Estágio" for evolution stage, "companheiro" for companion, and the TCG's "Comum/Incomum/Raro".

Alert copy is length-bounded. notifBody and every window label it embeds are kept no longer than the Spanish equivalent, so the floating-pet speech bubble guard in PerformanceTests keeps holding.

Overlap with #185. That PR proposes an equivalent interpolation-parity guard under the same filename. Whichever lands first, the other copy can simply be dropped — the guard is not specific to either language.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • Other:

UI changes

No files under Sources/PokeTokenBar/UI/ are touched. The user-visible change is entirely driven by Localization.swift:

Before After
Settings → Language offers 한국어 / English / 日本語 / Español. A system locale of pt falls back to English. A fifth entry, Português, is offered, and a pt system locale selects it on a fresh install. Selecting it renders every screen (popover, Pokédex, catch log, Bag, Shop, Settings, floating-pet bubbles, system notifications) in Brazilian Portuguese. Pokémon species names stay English, as described above.

Checklist

  • swift build and swift test pass locally
  • PR title and description are written in English
  • UI changes are described above (before/after — images optional)
  • No copyrighted assets, secrets, or private tooling references are committed (see CONTRIBUTING)
  • Tests were added or updated for this change

On the first box, honestly: this was authored on Linux, where the macOS-only target cannot be built, so swift build / swift test were not run locally. What was verified instead, mechanically: the ko/en/ja/es round-trip diff described above, \(...) interpolation parity across all five languages, that all 25 nature names are distinct and non-empty, and that every L member called from the new test matches its real signature. CI is the first real compile — please treat a red run as mine to fix.

claude and others added 5 commits August 21, 2026 20:33
Add Portuguese as a fifth UI language, following the Spanish PR (chattymin#159) and the
French PR (chattymin#185) as the template.

- AppLanguage gains a .pt case (native label "Português"), and systemDefault
  maps the pt system locale to it.
- L.t(...) is extended to five languages; every UI string in Localization.swift
  is translated to Brazilian Portuguese, using the informal "você" register to
  match the app's playful companion tone.
- PokemonNature.name(_:) gains 25 Portuguese names. Unlike ko/en/ja/es these are
  NOT official: the core series has never shipped a Portuguese localisation, so
  there is no official set to copy. They are adjectives agreeing with the
  feminine "natureza", picked so all 25 stay distinct.
- Pokémon species names deliberately stay English: PokéAPI has no `pt` language,
  and Brazilian players use the English names anyway (no localised core games),
  so AppLanguage.resolveName's English fallback is the expected result rather
  than a gap. "pt" is still listed in apiCodes/langCodes so the day PokéAPI adds
  it, it is picked up with no branch changes.
- Alert copy (notifBody + every window label it embeds) is kept no longer than
  the Spanish equivalent so it keeps fitting the floating-pet speech bubble
  guarded by PerformanceTests.
- Add LocalizationInterpolationTests: a parity guard asserting every language
  keeps its \(...) placeholders, since the compiler only enforces t(...)'s
  argument count, not that a translation kept its interpolations. chattymin#185 proposes
  an equivalent guard — whichever lands first, the other copy can be dropped.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SMZ2PCf9Fby5HVjZUtzEsk
Two native pt-BR reviews (franchise terminology + macOS UI copy) against the
EN source and the accepted ES translation. No ko/en/ja/es string is touched.

Franchise terminology, corrected against official pt-BR sources:
- Bag: "Mochila" -> "Bolsa". Niantic's pt-BR help centre calls the item
  inventory "Bolsa"; "Mochila" has no franchise precedent. ES already used the
  official term and pt had drifted from it for no reason.
- hatch: "eclodir" -> "chocar" (Niantic pt-BR: "Incubadoras e chocar Ovos de
  Pokémon"). "Eclodir" is correct Portuguese but is a calque of the Spanish
  "eclosionar" and is not what Brazilian players say. "Incubando" stays --
  "Incubadora" is the official term.
- Natures: 7 of 25 replaced. Three were meaning errors -- notably lonely
  "Solitária", which in pt-BR means preferring to be alone, the opposite of
  さみしがり; it is now "Carente". The others trade bookish or low-frequency
  words for the register the rest of the app uses.
- "Shiny" is kept untranslated as a deliberate choice: Pokémon GO pt-BR does
  have an official "Brilhante", but "shiny" is what the Brazilian community
  says, and this app's audience is Brazilian developers. Flagging it because a
  reviewer will reasonably compare it against the ES decision to adopt the
  official "Variocolor".

UI copy fixes:
- Four sentences did not say what the English said, or were ungrammatical:
  shinyCharm's description had a transitive verb with no object, useNeedsPokemon
  inverted what the item is used on, limitRefreshReauthNeeded was not idiomatic,
  and notifCandyBody put a noun phrase in an English-style pre-nominal slot that
  breaks in Portuguese ("limite de tokens Sessão de 5 horas do Claude").
- "Atualizar" covered both refresh and update, which sit next to each other in
  the popover; updateButton is now "Instalar". checkNowButton is "Buscar agora"
  to match the "Buscar atualizações" label beside it.
- macOS pt-BR conventions: quit -> "Encerrar", launchAtLogin -> "Abrir ao
  iniciar sessão", statusFocus -> "modo foco" (the system feature's name).
- dexRaising "Criando" was ambiguous (raise vs. create) in an 8pt uppercase
  badge -> "Treinando".

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SMZ2PCf9Fby5HVjZUtzEsk
Every comment this branch introduces now carries an English block followed by
the Korean one. The repository's code comments are Korean, but CONTRIBUTING and
CLAUDE.md put English first for anything a contributor reads, and these three
spots explain non-obvious decisions an outside reviewer has to follow:
why AppLanguage.pt maps to a PokéAPI code that does not exist yet, why the pt
nature column is not official like the other four, and why "pt" is listed in
PokeAPIClient.langCodes despite collecting nothing today.

Assertion messages in the new test are English rather than bilingual — they are
read from CI output, not from the source.

No behaviour change; the test still runs the same 44 assertions.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SMZ2PCf9Fby5HVjZUtzEsk
main added six UI strings in Localization.swift while this branch changed
`t(...)` from four arguments to five, so every call site conflicted.

Resolved mechanically rather than by hand: the pt column was re-applied to
main's file by matching each call's (ko, en, ja, es) tuple, not by line number,
so none of main's edits could be dropped by a stale offset. Verified by
stripping the fifth argument back off the resolved file and diffing against
`upstream/main` — byte-identical. The six new strings were then translated:
`limitsAccount`, `customScanProviderLabel`, `customScanRootsLabel`,
`customScanRootsHint`, `customScanRootsPlaceholder`, `customScanRootsMatches`.

Two of them interpolate, so `LocalizationInterpolationTests` was extended to
cover `limitsAccount` and `customScanRootsMatches` — otherwise the guard would
have silently stopped short of the newest strings, which is the exact failure
mode it exists to prevent.

Interpolation parity across all five languages re-verified after the merge.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SMZ2PCf9Fby5HVjZUtzEsk
Localization gained a French column upstream while this branch was open, so every
t() call needed a sixth argument rather than a fifth. Rebuilt the file from main
and appended the Portuguese column, keeping the other five verbatim; the same for
the 25 nature name tuples. Verified mechanically: 221 calls, all six arguments,
the first five byte-identical to main, the Portuguese identical to this branch.

The two Antigravity limit-group strings that landed after this branch last took
upstream had no Portuguese yet — added as Modelos Gemini and Modelos Claude e GPT.

Kept this branch's LocalizationInterpolationTests, which covers the same ground as
main's and adds a case proving the placeholder guard fails when a translation
drops one.
@chattymin
chattymin merged commit 1ecf87f into chattymin:main Aug 23, 2026
2 checks passed
@chattymin

Copy link
Copy Markdown
Owner

Thank you for your contribution!

chattymin added a commit that referenced this pull request Aug 23, 2026
Brazilian Portuguese shipped in #215 but the card still listed five languages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
LuceteYang added a commit to LuceteYang/PikaTokenBar that referenced this pull request Aug 24, 2026
Resolve the Localization.swift conflict: main widened `t()` to six
languages (adding fr/pt in chattymin#215), while this branch added the five
animation-quality keys with the old four-argument form.

Keep main's six-argument signature and supply fr/pt translations for
every animation key (Animation / Animação, Économie d'énergie /
Economia de bateria, Équilibré / Equilibrado, Fluide / Fluido, and the
battery hint).
LeeTaek added a commit to LeeTaek/PokeTokenBar that referenced this pull request Aug 24, 2026
Resolves conflicts with three changes that landed after this branch was cut.

**French (chattymin#185) and Brazilian Portuguese (chattymin#215) UI languages.** `L.t(...)` now
takes six arguments, so every Pokédex-entry string here gains `fr` and `pt`
values. `AppLanguage.apiCodes` keeps the new `.fr`/`.pt` cases and applies this
branch's lowercase `ja-hrkt` fix to `.ja`. `PokeAPIClient.langCodes` keeps the
"union of apiCodes" invariant that chattymin#215 documented — the dead `"ja-Hrkt"` entry
is corrected in place rather than dropped, so the lockstep rule still holds.

**Representative Pokémon (chattymin#158)** was built on the cell-selection model this
branch removes: a tap selected a cell and the footer offered a pin action. With
taps now opening the detail view there is no selection to hang it on, so the
action moves to the detail header, next to the shiny toggle — a species-level
action on the species screen. That also frees the 332pt footer row, which
`RepresentativeToggleLayoutTests` documents as too tight for the pin action plus
species text in longer locales. The cell keeps the representative accent
background (state, not action); the selection highlight is gone with selection
itself. `RepresentativeFooterButton` is renamed to `RepresentativeToggleButton`
since it no longer lives in a footer.

**Swift 6.3 SwiftUI isolation guard (chattymin#216).** The new views here get explicit
`@MainActor`. `FlavorCacheKey` is split across lines because the guard matches
`": App"` as a substring and a one-line `let language: AppLanguage` tripped it.

Portuguese also motivated a behavior change agreed for this branch: PokéAPI has
no `pt` language at all, so a language whose entry list comes back completely
empty now falls back to English with an on-screen notice. Per-version gaps still
drop out silently — only a wholly empty language falls back, and English itself
never does.
lukemonaghan pushed a commit to lukemonaghan/PokeTokenBar that referenced this pull request Aug 24, 2026
* feat: add Brazilian Portuguese (pt) UI language

Add Portuguese as a fifth UI language, following the Spanish PR (chattymin#159) and the
French PR (chattymin#185) as the template.

- AppLanguage gains a .pt case (native label "Português"), and systemDefault
  maps the pt system locale to it.
- L.t(...) is extended to five languages; every UI string in Localization.swift
  is translated to Brazilian Portuguese, using the informal "você" register to
  match the app's playful companion tone.
- PokemonNature.name(_:) gains 25 Portuguese names. Unlike ko/en/ja/es these are
  NOT official: the core series has never shipped a Portuguese localisation, so
  there is no official set to copy. They are adjectives agreeing with the
  feminine "natureza", picked so all 25 stay distinct.
- Pokémon species names deliberately stay English: PokéAPI has no `pt` language,
  and Brazilian players use the English names anyway (no localised core games),
  so AppLanguage.resolveName's English fallback is the expected result rather
  than a gap. "pt" is still listed in apiCodes/langCodes so the day PokéAPI adds
  it, it is picked up with no branch changes.
- Alert copy (notifBody + every window label it embeds) is kept no longer than
  the Spanish equivalent so it keeps fitting the floating-pet speech bubble
  guarded by PerformanceTests.
- Add LocalizationInterpolationTests: a parity guard asserting every language
  keeps its \(...) placeholders, since the compiler only enforces t(...)'s
  argument count, not that a translation kept its interpolations. chattymin#185 proposes
  an equivalent guard — whichever lands first, the other copy can be dropped.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SMZ2PCf9Fby5HVjZUtzEsk

* fix(pt): apply native-reviewer feedback on the Portuguese copy

Two native pt-BR reviews (franchise terminology + macOS UI copy) against the
EN source and the accepted ES translation. No ko/en/ja/es string is touched.

Franchise terminology, corrected against official pt-BR sources:
- Bag: "Mochila" -> "Bolsa". Niantic's pt-BR help centre calls the item
  inventory "Bolsa"; "Mochila" has no franchise precedent. ES already used the
  official term and pt had drifted from it for no reason.
- hatch: "eclodir" -> "chocar" (Niantic pt-BR: "Incubadoras e chocar Ovos de
  Pokémon"). "Eclodir" is correct Portuguese but is a calque of the Spanish
  "eclosionar" and is not what Brazilian players say. "Incubando" stays --
  "Incubadora" is the official term.
- Natures: 7 of 25 replaced. Three were meaning errors -- notably lonely
  "Solitária", which in pt-BR means preferring to be alone, the opposite of
  さみしがり; it is now "Carente". The others trade bookish or low-frequency
  words for the register the rest of the app uses.
- "Shiny" is kept untranslated as a deliberate choice: Pokémon GO pt-BR does
  have an official "Brilhante", but "shiny" is what the Brazilian community
  says, and this app's audience is Brazilian developers. Flagging it because a
  reviewer will reasonably compare it against the ES decision to adopt the
  official "Variocolor".

UI copy fixes:
- Four sentences did not say what the English said, or were ungrammatical:
  shinyCharm's description had a transitive verb with no object, useNeedsPokemon
  inverted what the item is used on, limitRefreshReauthNeeded was not idiomatic,
  and notifCandyBody put a noun phrase in an English-style pre-nominal slot that
  breaks in Portuguese ("limite de tokens Sessão de 5 horas do Claude").
- "Atualizar" covered both refresh and update, which sit next to each other in
  the popover; updateButton is now "Instalar". checkNowButton is "Buscar agora"
  to match the "Buscar atualizações" label beside it.
- macOS pt-BR conventions: quit -> "Encerrar", launchAtLogin -> "Abrir ao
  iniciar sessão", statusFocus -> "modo foco" (the system feature's name).
- dexRaising "Criando" was ambiguous (raise vs. create) in an 8pt uppercase
  badge -> "Treinando".

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SMZ2PCf9Fby5HVjZUtzEsk

* docs(pt): make the comments added by this branch bilingual

Every comment this branch introduces now carries an English block followed by
the Korean one. The repository's code comments are Korean, but CONTRIBUTING and
CLAUDE.md put English first for anything a contributor reads, and these three
spots explain non-obvious decisions an outside reviewer has to follow:
why AppLanguage.pt maps to a PokéAPI code that does not exist yet, why the pt
nature column is not official like the other four, and why "pt" is listed in
PokeAPIClient.langCodes despite collecting nothing today.

Assertion messages in the new test are English rather than bilingual — they are
read from CI output, not from the source.

No behaviour change; the test still runs the same 44 assertions.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SMZ2PCf9Fby5HVjZUtzEsk

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Patrick.Park <pdm001125@naver.com>
cte13 pushed a commit to cte13/PokeTokenBar-iOS that referenced this pull request Aug 25, 2026
Brings in upstream work since the last sync:
- Brazilian Portuguese (pt) UI language (chattymin#215) — added pt strings to fork-only labels
- Pi Agent usage tracking (chattymin#189)
- Keep Antigravity automatic poll out of the Keychain (chattymin#221)
- Route Antigravity session notice through the localization table (chattymin#222)
- Docs for representative pin / Antigravity limits / scan folders (chattymin#223)
- Release bump to v2.5.2

Conflicts resolved in Localization (added pt to fork-only OpenCode Go / phone /
widget labels and re-added `monthly`) and defect-log.md (kept both new entries).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants