Skip to content

feat: add French (fr) UI language - #185

Merged
chattymin merged 2 commits into
chattymin:mainfrom
tchapuis:feat/french-ui-language
Aug 23, 2026
Merged

chattymin merged 2 commits into
chattymin:mainfrom
tchapuis:feat/french-ui-language

Conversation

@tchapuis

Copy link
Copy Markdown
Contributor

Add French as a fifth UI language, following the architecture guide in #149 and the Spanish PR (#159) as the template.

  • AppLanguage gains a .fr case (apiCodes ["fr"], native label "Français"), and systemDefault maps the fr system locale to it.
  • L.t(...) is extended to five languages; every UI string in Localization.swift is translated to French, using the informal "tu" register to match the app's playful companion tone.
  • PokemonNature.name(_:) returns the 25 official French nature names, verified against PokéAPI's fr names data rather than invented.
  • PokeAPIClient.langCodes now includes "fr" so French Pokémon species names are fetched (otherwise they fall back to English).
  • Weekly limit labels use the compact "Hebdo" so French alert copy stays within the floating-pet speech bubble (PerformanceTests bubble-fit guard).
  • Add LocalizationInterpolationTests: a parity guard asserting every language keeps the same (...) interpolations, since the compiler only enforces the argument count, not that a translation kept its placeholders.

Refs #149

@chattymin

Copy link
Copy Markdown
Owner

Thanks @tchapuis — this is careful work, and I checked it rather than taking it on trust:

  • All 25 French nature names match PokéAPI's fr data exactly (fetched all 25 and compared; zero mismatches).
  • Of 208 t(...) strings, only 6 have French identical to English — Collection, Maintenance, Notifications, \(m) min, Pokédex, Rare — and all six are genuinely the same word in French. No untranslated leftovers.
  • swift test on your head: 677 tests, 0 failures.

LocalizationInterpolationTests is the part I'm happiest about. It iterates allCases, so it retroactively guards the four existing languages too — that outlives the French itself.

Apologies for the churn: main has moved a fair bit since you opened this, and the branch now conflicts.

Rebase — one conflict, plus five strings that need French

d16c3ea (#158, representative Pokémon) landed and added UI strings, so Localization.swift conflicts in a single hunk. Rebasing resolves that hunk but leaves five four-argument t(...) calls needing a French argument:

t("대표 포켓몬", "Representative Pokémon", "代表ポケモン", "Pokémon representativo")
t("현재 포켓몬 따라가기", "Follow current companion", "現在のポケモンに合わせる", "Seguir al compañero actual")
t("도감에서 선택…", "Choose in Pokédex…", "図鑑で選ぶ…", "Elegir en la Pokédex…")
t("대표로 설정", "Set as representative", "代表ポケモンに設定", "Establecer como representante")
t("대표", "Representative", "代表", "Representante")   // badge — keep it short

You can't miss one: after your change t takes five arguments, so anything left at four fails to compile. These are popover/settings labels rather than speech-bubble copy, so the bubble-fit guard doesn't constrain their length.

One more thing, in the same rebase

The commit carries Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>. This repo keeps AI attribution out of the history, and a squash merge carries the branch commit message through to main — so please drop that trailer while you're amending.

I have maintainer_can_modify here and am happy to do the rebase myself if you'd rather not. But the remaining work is really five translations, and you have a better ear for the register you've used throughout than I do. Either way is fine — just say which.

Add French as a fifth UI language, following the architecture guide in chattymin#149
and the Spanish PR (chattymin#159) as the template.

- AppLanguage gains a .fr case (apiCodes ["fr"], native label "Français"), and
  systemDefault maps the fr system locale to it.
- L.t(...) is extended to five languages; every UI string in Localization.swift
  is translated to French, using the informal "tu" register to match the app's
  playful companion tone.
- PokemonNature.name(_:) returns the 25 official French nature names, verified
  against PokéAPI's fr `names` data rather than invented.
- PokeAPIClient.langCodes now includes "fr" so French Pokémon species names are
  fetched (otherwise they fall back to English).
- Weekly limit labels use the compact "Hebdo" so French alert copy stays within
  the floating-pet speech bubble (PerformanceTests bubble-fit guard).
- Add LocalizationInterpolationTests: a parity guard asserting every language
  keeps the same \(...) interpolations, since the compiler only enforces the
  argument count, not that a translation kept its placeholders.

Refs chattymin#149
@tchapuis
tchapuis force-pushed the feat/french-ui-language branch from 563011d to 0919cee Compare August 20, 2026 14:42
@tchapuis

Copy link
Copy Markdown
Contributor Author

Thank you for you message @chattymin !

I just rebased my work and fixed everything you said. Hope it's good now !

Resolve Localization.swift by keeping the French column and adding fr for the
strings that landed on main after this branch was opened: limitsAccount, and the
customScanProviderLabel / customScanRoots* group.
@chattymin
chattymin merged commit 136e062 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
#185 made `L.t` five-argument at 14:45:33; #210 merged four-argument
Antigravity strings 104 seconds later. GitHub reported the merge as
clean because the two changes never touch the same lines, so `main`
landed with 24 "missing argument for parameter #5" errors.

Adds `fr` to the three Antigravity limit strings. No behaviour change.
chattymin added a commit that referenced this pull request Aug 23, 2026
* feat: add Brazilian Portuguese (pt) UI language

Add Portuguese as a fifth UI language, following the Spanish PR (#159) and the
French PR (#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. #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 23, 2026
Brings in upstream features and integrates them with the fork's iOS/OpenCode Go work:
- Antigravity 2.0 / IDE official rate-limit tracking (chattymin#210, chattymin#219, chattymin#220)
- French (fr) UI language (chattymin#185) — added fr strings to fork-only labels
- Per-provider additional scan folders (chattymin#177/chattymin#187)
- Account-labeled official limits (chattymin#199), swift.org Swift 6.3 toolchains (chattymin#216),
  claude-fable-5 pricing (chattymin#198)

Conflicts resolved in Localization, UsageStore, PopoverView, and their tests by
keeping both the fork's OpenCode Go limits and upstream's Antigravity limits, and
by giving fork-only localized labels their French values.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
Add French as a fifth UI language, following the architecture guide in chattymin#149
and the Spanish PR (chattymin#159) as the template.

- AppLanguage gains a .fr case (apiCodes ["fr"], native label "Français"), and
  systemDefault maps the fr system locale to it.
- L.t(...) is extended to five languages; every UI string in Localization.swift
  is translated to French, using the informal "tu" register to match the app's
  playful companion tone.
- PokemonNature.name(_:) returns the 25 official French nature names, verified
  against PokéAPI's fr `names` data rather than invented.
- PokeAPIClient.langCodes now includes "fr" so French Pokémon species names are
  fetched (otherwise they fall back to English).
- Weekly limit labels use the compact "Hebdo" so French alert copy stays within
  the floating-pet speech bubble (PerformanceTests bubble-fit guard).
- Add LocalizationInterpolationTests: a parity guard asserting every language
  keeps the same \(...) interpolations, since the compiler only enforces the
  argument count, not that a translation kept its placeholders.

Refs chattymin#149

Co-authored-by: Patrick.Park <pdm001125@naver.com>
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>
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