Skip to content

Localize dynamic strings, enum names and UIKit text (2/3) - #761

Draft
zersoft wants to merge 11 commits into
loopandlearn:devfrom
zerloop:feat/localization-pr2
Draft

zersoft wants to merge 11 commits into
loopandlearn:devfrom
zerloop:feat/localization-pr2

Conversation

@zersoft

@zersoft zersoft commented Sep 27, 2026 •

Copy link
Copy Markdown

Summary

Second of three localization PRs. Builds on #760 (this branch contains its commits; merge that first). Covers what the String Catalog could not reach through SwiftUI literals alone: reusable views, enum display names, string constants, remote-command messages and the UIKit main screen. Opened as a draft while the Turkish copy is reviewed by a native speaker on a TestFlight build.

Changes

  • Reusable views that only ever receive literals take LocalizedStringKey (QuickPickSectionHeader, InfoBanner, alarm editor sections, BGPicker, StatCard, onboarding header/footer, …), so their ~130 call sites localize without change. Section footers that were split across "a " + "b" literals are single literals again.
  • Enum display names (InfoType.name, tab names, appearance/prediction/contact modes, Trio commands, treatment types, alarm blurbs, settings screen titles) go through String(localized:); SettingsLeaf localizes at init so Menu search matches the Turkish labels; AlarmSelectionView reuses AlarmType.displayName.
  • String constants, ternaries, ~90 remote-command/status/error messages, error descriptions and the main screen (minutes-ago, LOW/HIGH, Not Looping, alerts, chart pills, OTP countdown) go through String(localized:); server response bodies stay %@ arguments.
  • A Nightscout status comparison against the English literal "Checking..." was replaced by a shared constant so the onboarding pill keeps its checking state in every language (caught in review, with a test that runs in a Turkish test host).
  • Tooling: Scripts/localization/localized_apis.txt names the APIs whose literal arguments are keys; scan_keys.py collects them (labeled/unlabeled, ternary branches, Name(label: entries, opt-in reviewed --interpolations heuristic) and scannedLiteralsExistInCatalog enforces the same rule in the test target. A lint rejects raw literal ternaries in String positions.
  • Catalog: 548 → 992 keys, all with Turkish values.

Still English (deliberately, PR 3)

UNMutableNotificationContent bodies, BackgroundAlertManager messages, AlarmManager.sendNotification and the Live Activity extension / Shared display names, Info.plist usage descriptions.

Testing

  • Tests target: 115 passed, 0 failed (iOS 27 simulator running in tr-TR).
  • SwiftFormat --lint clean.
  • Simulator in Turkish: onboarding first screen fully Turkish (screenshot in the fork's dev build notes).

🤖 Generated with Claude Code

zersoft and others added 11 commits September 27, 2026 01:59
Adds LoopFollow/Resources/Localizable.xcstrings (source language en) and
registers tr in knownRegions. Keys come from xcodebuild
-exportLocalizations plus a scan of SwiftUI LocalizedStringKey call
sites (Toggle, Picker, Section, Label, navigationTitle, ...) that the
extractor does not emit; those are marked extractionState "manual".
No translations yet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 345c02582e5878ba12922177d89edbf96ef836f7)
Fails until every translatable key has a Turkish value; also checks
printf placeholders and stale keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 38286cfcb1ae0da53444786b2cc326e75318f23b)
AlarmType.rawValue and the day/night option raw values are persisted, so
they must stay English. Add AlarmType.displayName and make
DayNightDisplayable.displayName an explicit localized switch; route the
tile, editor title, list and add-sheet search, notification title and
default alarm name through them. Tests pin the raw values.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit ca2276f80102049c02f5e88cc3b3829725e0c3dc)
Fills the tr locale of Localizable.xcstrings for every extracted key,
marks units, symbols and product names as do-not-translate, and adds
two small scripts for listing and applying translations from TSV.

Two call sites are adjusted so they can be localized at all: the
"minute(s) ago" plural suffix becomes a plural variation on a single
"%lld minutes ago" key, and three alarm-editor footers that joined two
literals with "+" (which selects the non-localized Text overload) are
single literals again.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 0f417a115c1b09b3444112935a19b5ee8185b231)
…er don't-translate set

- AlarmSettingsView's day/night footer joined three literals with "+",
  which selects the non-localized Text overload; merge into one literal
  and translate the whole footer. Drop the dead fragment and "(" keys.
- Add a source lint test that fails on Text("a" + "b") patterns.
- Require positional indexes (%1$@, %2$@) in every multi-placeholder
  translation so argument order can be changed safely; update the five
  affected Turkish values.
- Keep shouldTranslate=false only for units, symbols, bare specifiers and
  hashtags; ordinary terms that happen to match in Turkish get an explicit
  translation so other languages can translate them.
- Verify the English plural variation at runtime from en.lproj.
- Fix "Snoozed until %@" (argument is a time, not a date) and refine
  "Proactive Low" and the "Remote" title.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… consistency test

Scripts/localization/localized_apis.txt names the SwiftUI/Foundation APIs
whose first string-literal argument is a catalog key. scan_keys.py adds
missing keys to the catalog (or reports them with --check) and the new
scannedLiteralsExistInCatalog test enforces the same rule in the test
target. Literals followed by "+" are String concatenation and are
ignored by both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rals

QuickPickSectionHeader, QuickSelectButton, InfoBanner, NavigationRow-free
helpers, LoadingButtonView, ErrorMessageView, OnboardingStepHeader,
HKQuantityInputView, AlarmEnumMenuPicker, CommandButtonView, BGPicker,
StatCard, the alarm editor sections (AlarmBGSection, AlarmBGLimitSection,
AlarmStepperSection), the welcome primary label and the onboarding footer
title now take LocalizedStringKey, so their call sites localize without
change. InfoBanner texts that were split across "+" literals are single
literals again; the quick-pick info texts are localized at their source.

The key scanner and the matching test now collect labeled literal
arguments inside each listed call (skipping systemImage/comment/icon
style labels, nested calls and arrays), so these keys are covered.
Adds the Turkish values for the 188 new keys.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ch titles

All displayName/name/title/label/blurb bodies go through String(localized:).
SettingsLeaf localizes its title at init so Menu search matches the
Turkish labels on screen (a localizedTitle: initializer covers titles that
are already localized). AlarmSelectionView reuses AlarmType.displayName
instead of its own copy of the 24 names. The key scanner and test now
honor "Name(label:" entries, so String(localized:) literals are collected
only from their localized: argument.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every user-facing String literal assigned to a variable, chosen by a
ternary or reported as a remote-command status/error goes through
String(localized:); server response bodies stay interpolated arguments.
The meal confirmation and the scanned-settings prompts are whole
sentences instead of concatenated fragments. Adds a lint that rejects
raw literal ternaries in String positions, ternary-branch support in
the key scanner/test, and an opt-in heuristic (--interpolations) that
turns interpolated String(localized:) literals into printf-style keys
for review.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Minutes-ago, LOW/HIGH, Not Looping, loading and setup-button strings,
UIKit alerts, the log search placeholder, the QR scanner title, the
treatment pill details and the suspend/resume chart pills go through
String(localized:). The OTP countdown becomes a single interpolated key
instead of concatenation. Double amounts keep their compact formatting
via an explicit %g specifier.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ago, remaining English sources

- NightscoutSettingsViewModel compared the (now localized) status text with
  the English literal "Checking...", so the onboarding pill lost its
  checking state in Turkish; the value is a shared constant compared by
  identity of lookup, with a test that runs in the tr host.
- The main screen's minutes-ago text lives in MinAgoTask, not the
  calendar title; it is now "%@ min ago" (the duration can be "4:35").
- Sensor-age footer key added; chart pill labels (Bolus, Carbs, SMB,
  BG Check, Sensor Start, Override, Temp Target), Nightscout friendly
  status and auth level, token-provisioning and Nightscout/Loop APNS/
  override-preset error descriptions, TIR titles, info-row color footers,
  onboarding fast-drop explanation and the update alerts go through
  String(localized:).
- Property initializers drop the explicit String type so SwiftFormat does
  not rewrite them to .init(localized:), which the scanner cannot see;
  empty String(localized: "") artifacts and the "(%@)" OTP wrapper key are
  gone; the meal bolus line matches its siblings; "Est. A1C:" is the
  single key.
- scan_keys.py refuses to add a heuristic key that differs from an
  existing key only by specifier types.
- Turkish: "OK (%@)" → "Bağlı (%@)", clearer time-window messages.

Co-Authored-By: Claude Fable 5.1 <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.

1 participant