Skip to content

feat(watchos): system parity (preferences/keychain/locale) + i18n runtime locale selection + t() interpolation fix - #6201

Merged
proggeramlug merged 11 commits into
mainfrom
timestable/watchos-system-parity
Jul 9, 2026
Merged

feat(watchos): system parity (preferences/keychain/locale) + i18n runtime locale selection + t() interpolation fix#6201
proggeramlug merged 11 commits into
mainfrom
timestable/watchos-system-parity

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What

watchOS system parity, plus three cross-platform fixes surfaced while building a real standalone watch app (a kids' times-table trainer) against Perry.

The watchOS backend had preferences, keychain, and getLocale stubbed. This makes them real by porting the iOS implementations, then fixes three issues the app exposed:

  1. preferences / keychain / getLocale — real NSUserDefaults / SecItem / NSLocale backing in a new crates/perry-ui-watchos/src/system.rs (six empty stubs removed from lib.rs). ILP32-hardened for arm64_32 (true runtime extern signatures, zero-extending pointer casts, usize for NSUInteger).
  2. Runtime locale detection on watchOS/tvOS/visionOS — widened the #[cfg] gates in crates/perry-runtime/src/i18n.rs (the Apple detector was gated to macOS/iOS only, so a German watch rendered English).
  3. Button styling/repaint in PerryWatchApp.swiftCommonModifiers' only stored property was nodeId, so SwiftUI value-diffed it as unchanged and never re-ran its body on version bumps; attribute-only mutations (bg/fg/font) never repainted, and styled buttons needed a structural rebuild to show. Fixed by threading bridge.version through the modifier and rendering styled buttons as .plain with the style applied to the label.
  4. t() {param} interpolation (was broken on every platform) — closed-shape object literals lower to Expr::New on synthesized __AnonShape_* classes before the i18n transform runs, so extract_params (which only matched Expr::Object) saw no params and emitted the literal {name}. Fixed by mapping anon-shape classes to their ordered field names.
  5. Bundle localizationbundle_for_watchos now writes CFBundleDevelopmentRegion + CFBundleLocalizations and emits <locale>.lproj/Localizable.strings (mirrors iOS/visionOS), so NSBundle language negotiation actually sees the app's locales.
  6. Runtime translation selection — the surprise: t() translations were baked to the default locale at compile time on all platforms (perry_i18n_init/LOCALE_INDEX were dead code). Added perry_i18n_locale_index_for (lazy detect+match+cache); the I18nString lowering now resolves every locale's row and branches at runtime when they differ, keeping the compile-time fast path when identical.

Verification

cargo +nightly check -Z build-std clean on watchos-sim, watchos (arm64), and arm64_32. perry-dispatch (5/5) and perry-api-manifest incl. stub_inventory (37/37, no count change). Two new e2e i18n tests. Verified end-to-end on the German watch simulator: strings render in German with {param} interpolation; prefs persist across relaunch.

Caveats

  • Keychain returns undefined on the simulator (unsigned sim bundles lack entitlements, securityd -34018) — same pre-existing iOS-sim behavior; works on signed device builds.
  • Plural forms don't yet honor the runtime locale (pre-existing "first cut" in the lowering).

Note for reviewers

Touches bundle_for_watchos in bundle_apple.rs, which also changes in the App Groups PR (#watchos-appgroups) — trivial rebase (localization writes before codesign).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added watchOS localization support with locale-aware string output and localized Localizable.strings generation in app bundles.
    • Added watchOS system access for preferences, keychain storage, and current locale lookup.
    • Enhanced i18n interpolation to correctly resolve params from closed-shape objects and choose the right locale at runtime.
  • Bug Fixes
    • Fixed localized strings to match the active locale, including when switching locales dynamically.
    • Improved watchOS UI updates so button styling/appearance stays in sync with tree changes.

Ralph Kuepper added 7 commits July 4, 2026 22:16
Intl.ListFormat with locale "es"/"es-*" and type "unit" must join with the
Spanish connectors from CLDR:
  - long:  "foo, bar y baz"   (last boundary " y ", pair " y ")
  - short: "foo, bar, baz"    (comma-joined for 3+, pair still " y ")
  - narrow: "foo bar baz"     (space-joined, same as the base — unchanged)

list_separators was hardcoded to the en-US patterns, so es lists came out
comma-joined ("foo, bar" instead of "foo y bar").

Thread the resolved locale through list_format_parts / list_separators and
add the es unit branch. The single DurationFormat caller passes "en-US"
explicitly to preserve its existing (locale-agnostic) output.

Fixes test262 intl402/ListFormat/prototype/{format,formatToParts}/
{es-es-long,es-es-short}.js (4 cases); es-es-narrow stays passing.
…ults + SecItem + NSLocale impls

Replace the empty perry_system_preferences_set/get,
perry_system_keychain_save/get/delete and perry_system_get_locale stubs
in perry-ui-watchos with real implementations in a new src/system.rs,
ported from perry-ui-ios (ffi/camera.rs preferences,
ffi/security_notifications.rs keychain + locale). Foundation and
Security.framework are already on the watch link line; exported extern
signatures match what the stubs declared, so no dispatch changes.

arm64_32 (ILP32) note: the runtime externs are declared with the real
perry-runtime signatures (js_string_from_bytes(ptr, len: u32) -> *mut u8)
and pointers only widen to i64 via zero-extending casts at the NaN-box
boundary; NSStringEncoding is passed as usize (NSUInteger is 32-bit on
arm64_32). Checked for aarch64-apple-watchos-sim, aarch64-apple-watchos
and arm64_32-apple-watchos.
detect_apple_locale (NSBundle preferredLocalizations with a
CFLocaleCopyCurrent fallback) was gated to macos+ios, so the other three
Apple targets silently fell through to LANG/LC_* env vars that GUI apps
never have, and perry/i18n apps always rendered locale index 0. Both
APIs exist on every Apple OS; widen the two cfg gates.

No link-line change needed: the non-game-loop watch path links with
swiftc which pulls in libobjc implicitly (verified: _objc_getClass /
_sel_registerName bind from libobjc.A.dylib in a linked simulator app),
and the game-loop path already passes -lobjc explicitly.
… repaint on attribute-only mutations

Two gaps in the SwiftUI tree renderer:

1. buttonView rendered Button(nodeText) only. The default watchOS button
   style paints its own opaque chrome over anything CommonModifiers
   hangs outside the Button, so font size/weight, buttonSetTextColor,
   widgetSetBackgroundColor and setCornerRadius never showed. When a
   node carries any of those, render a .plain-style button and style
   the label directly (same fontSize > 0 / fontWeight >= 0 fallback
   logic as textView), full-width with a stock-like pill fallback so
   styled buttons keep filling their cell; unstyled buttons keep the
   stock system path unchanged. CommonModifiers skips fg/bg/cornerRadius
   for button nodes so nothing double-applies.

2. CommonModifiers' only stored property was nodeId, which compares
   equal across render passes, so SwiftUI never re-ran its body on tree
   version bumps: attribute-only mutations (bg/fg color, corner radius,
   opacity, frame...) were recorded in the node model (with_node_mut
   does bump the version) but only became visible after a structural
   rebuild (e.g. widgetSetHidden toggle). Thread bridge.version through
   the modifier so its value changes every bump and body re-runs.
…wering

t("Day streak: {days}", { days: 5 }) printed the raw "{days}"
placeholder for EVERY params object. Root cause: closed-shape object
literals are rewritten during HIR lowering — before the i18n transform
runs — into Expr::New on a synthesized __AnonShape_<hash> class whose
constructor takes the field values positionally (perry-hir
lower/context.rs::synthesize_anon_shape_class). extract_params only
recognized Expr::Object, so it returned no params, and codegen's
fragment plan (dyn_extern_i18n) deliberately emits the literal {name}
text for placeholders missing from lowered_params.

Fix: apply_i18n builds a map of every __AnonShape_* class to its
ordered field names (declaration order == constructor arg order, same
across modules thanks to content-addressed names) and extract_params
zips those names with the New args to recover the name→value pairs.
Also covers the localizable perry/ui widget path (Text/Button/...),
which shares extract_params.

New e2e test: literal / variable / property-get / multi-param string
forms all interpolate.
The watch bundler wrote no CFBundleLocalizations /
CFBundleDevelopmentRegion and no .lproj directories, so
NSBundle.mainBundle.preferredLocalizations — the primary source in
perry_runtime::i18n::detect_apple_locale — negotiated against an
unlocalized bundle and always answered the development language. A
German watch (or per-app language override) still rendered the default
locale.

Mirror the visionOS bundler: bundle_for_watchos now takes the i18n
table + config, declares CFBundleDevelopmentRegion (default_locale)
and CFBundleLocalizations (all configured locales) in Info.plist, and
emits <locale>.lproj/Localizable.strings via the shared
write_lproj_localized_strings helper — the .lproj directories are what
NSBundle actually scans during language negotiation.
t() / localized-widget strings were resolved at COMPILE TIME against
the default locale (I18nString lowering used default_locale_idx only),
so a German device still rendered English on every platform. The
runtime's locale plumbing (perry_i18n_init, LOCALE_INDEX,
detect_apple_locale) existed but nothing in emitted code ever called
it — dead code since it landed.

Wire it up end to end:

- runtime: new perry_i18n_locale_index_for(locales_header, default_idx)
  — lazily detects the system locale on first call (NSBundle/CFLocale
  on Apple, Win32 / Android props / env vars elsewhere), matches it
  against the comma-separated configured locale list, caches the row
  index in LOCALE_INDEX. An explicit perry_i18n_set_locale_index or
  perry_i18n_init beats the lazy path (LOCALE_RESOLVED flag).

- codegen: I18nLowerCtx carries the locale codes; the I18nString
  lowering resolves EVERY locale's template row. Keys translated
  identically everywhere (and single-locale builds) keep the old
  compile-time fast path with zero runtime cost; keys that differ emit
  one perry_i18n_locale_index_for call + per-locale branches that each
  build that row's fragment plan (params are lowered exactly once, up
  front, so side effects don't duplicate across branches). Template
  emission is factored into emit_i18n_template.

Verified on the watch simulator (device language German): a
[i18n] en/de/fr app renders 'Gut gemacht!' / 'Serie: 5' with
interpolation, while an English host still renders the en rows.
The e2e test asserts the multi-locale switch compiles, runs, and
interpolates without pinning the host language.
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 832fa181-d0f7-4e95-8286-7cf745d6124f

📥 Commits

Reviewing files that changed from the base of the PR and between 46d80fc and b784bc2.

📒 Files selected for processing (4)
  • crates/perry-codegen/src/expr/dyn_extern_i18n.rs
  • crates/perry-ui-watchos/src/lib.rs
  • crates/perry-ui-watchos/src/system.rs
  • crates/perry/tests/i18n_interpolation_params.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • crates/perry/tests/i18n_interpolation_params.rs
  • crates/perry-codegen/src/expr/dyn_extern_i18n.rs
  • crates/perry-ui-watchos/src/lib.rs
  • crates/perry-ui-watchos/src/system.rs

📝 Walkthrough

Walkthrough

Adds locale-aware i18n lowering and runtime locale selection, anon-shape param reconstruction in the transform pass, watchOS system FFI and bundle localization output, and SwiftUI watchOS button/modifier updates.

Changes

Multi-locale i18n interpolation and runtime resolution

Layer / File(s) Summary
I18nLowerCtx locale_codes field and runtime FFI declaration
crates/perry-codegen/src/expr/mod.rs, crates/perry-codegen/src/runtime_decls/mod.rs, crates/perry-codegen/src/codegen/mod.rs
Adds locale_codes to I18nLowerCtx, declares perry_i18n_locale_index_for, and wires locale codes into ctx construction.
I18nString codegen lowering with locale branching
crates/perry-codegen/src/expr/dyn_extern_i18n.rs
Adds template emission helpers and reworks Expr::I18nString lowering to resolve per-locale templates and branch at runtime by locale index.
Runtime lazy locale resolution and Apple platform expansion
crates/perry-runtime/src/i18n.rs
Introduces LOCALE_RESOLVED, updates locale index handling and init, and expands Apple cfg targets to watchOS/tvOS/visionOS.
watchOS bundle localization output
crates/perry/src/commands/compile/bundle_apple.rs, crates/perry/src/commands/compile/run_pipeline.rs
Extends watchOS bundling to emit localization plist entries and per-locale .lproj strings, and forwards i18n inputs from the pipeline.
i18n interpolation integration tests
crates/perry/tests/i18n_interpolation_params.rs
Adds end-to-end tests for object-literal interpolation and multi-locale runtime selection.

Anon-shape params support in i18n transform pass

Layer / File(s) Summary
Anon-shape field mapping and threading through replacement pass
crates/perry-transform/src/i18n.rs
Builds anon-shape field-order mapping, extends params extraction for Expr::New, and threads the mapping through recursive replacement traversal.

watchOS system APIs and UI updates

Layer / File(s) Summary
Move system stubs out of lib.rs
crates/perry-ui-watchos/src/lib.rs
Declares the new system module and removes the inlined watchOS preferences, keychain, and locale stubs.
system.rs preferences, keychain, and locale FFI implementations
crates/perry-ui-watchos/src/system.rs
Implements watchOS preferences, keychain, and locale FFI using NSUserDefaults, Security.framework, and NSLocale.
SwiftUI button styling and modifier version tracking
crates/perry-ui-watchos/swift/PerryWatchApp.swift
Adds version tracking to CommonModifiers and reworks buttonView to render styled labels when node styling is present.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Codegen
  participant Runtime
  participant EmitI18nTemplate
  Codegen->>Codegen: resolve per-locale templates from table
  Codegen->>Runtime: call perry_i18n_locale_index_for
  Runtime-->>Codegen: locale index
  Codegen->>EmitI18nTemplate: emit selected locale template
  EmitI18nTemplate-->>Codegen: boxed nan-string result
Loading
sequenceDiagram
  participant JSRuntime
  participant SystemRS as system.rs
  participant SecItem as Security.framework
  JSRuntime->>SystemRS: perry_system_keychain_save(key, value)
  SystemRS->>SecItem: SecItemUpdate(query)
  SecItem-->>SystemRS: errSecItemNotFound
  SystemRS->>SecItem: SecItemAdd(query)
  SecItem-->>SystemRS: status
Loading

Possibly related PRs

  • PerryTS/perry#5426: Touches the same crates/perry-runtime/src/i18n.rs initialization path that this PR updates for locale resolution state.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed It clearly summarizes the main changes: watchOS parity, runtime locale selection, and i18n interpolation fixes.
Description check ✅ Passed It covers the summary, concrete changes, and verification; the template is followed in substance, though headings are renamed and some optional sections are omitted.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch timestable/watchos-system-parity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
crates/perry-ui-watchos/swift/PerryWatchApp.swift (1)

316-327: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

styledButtonText duplicates textView font logic.

The font fallback logic (fontSize > 0 → fontWeight >= 0 gate) is identical to textView (lines 219–233). Extracting a shared helper would prevent these from diverging over time.

♻️ Optional refactor: shared font helper
+ /// Shared font logic for textView and styledButtonText.
+ `@ViewBuilder` func styledText(
+    _ t: String, fontSize: Double, fontWeight: Double
+ ) -> some View {
+    if fontSize > 0 {
+        if fontWeight >= 0 {
+            Text(t).font(.system(size: fontSize, weight: swiftWeight(fontWeight)))
+        } else {
+            Text(t).font(.system(size: fontSize))
+        }
+    } else {
+        Text(t)
+    }
+ }

Then textView becomes styledText(nodeText, fontSize: ..., fontWeight: ...) and styledButtonText delegates to the same helper.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-ui-watchos/swift/PerryWatchApp.swift` around lines 316 - 327,
`styledButtonText` is duplicating the same font fallback rules already used by
`textView`, so extract that shared font-building logic into one helper and have
both `textView` and `styledButtonText` delegate to it. Keep the `fontSize > 0`
and `fontWeight >= 0` behavior in the shared helper, and update both call sites
to use the new helper so the logic stays consistent and won’t drift.
crates/perry-runtime/src/intl/list_relative_plural.rs (1)

126-159: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding unit tests for the new Spanish separator branch.

list_separators is a pure function with no I/O, making it cheap to unit-test directly (e.g. asserting the tuple returned for ("es", "unit", "long"), ("es-MX", "unit", "short"), and the narrow-falls-through-to-base case). This locks in the CLDR-derived behavior described in the comment and guards against regressions when more locales are added later.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-runtime/src/intl/list_relative_plural.rs` around lines 126 -
159, Add unit tests for the new Spanish branch in list_separators to lock in the
CLDR behavior. Cover the es and es-* cases for list_type "unit" with style
"long" and "short", and verify the "narrow" case falls through to the base
separator behavior. Keep the tests focused on the pure list_separators function
so future locale changes don’t regress this logic.
crates/perry/tests/i18n_interpolation_params.rs (1)

106-179: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test accepts either locale row — doesn't actually verify runtime locale selection picked the right one.

The comment acknowledges the host's language can't be pinned, but the assertion accepting either "Day streak: 9" or "Serie: 9 Tage" means this test would still pass even if locale detection always fell back to the default locale and never actually selected "de" — it only proves the runtime-switch codegen compiles and interpolates params correctly for whichever branch happens to run.

Consider setting LANG/LC_ALL on the child process (via Command::env(...)) when spawning run, rather than relying on the host's ambient locale. That pins only the compiled binary's environment (not the host machine, respecting the stated constraint) and would let the test deterministically assert the "de" row is selected.

♻️ Sketch of a more deterministic assertion
-    let run = Command::new(&output).output().expect("run compiled binary");
+    let run = Command::new(&output)
+        .env("LANG", "de_DE.UTF-8")
+        .env("LC_ALL", "de_DE.UTF-8")
+        .output()
+        .expect("run compiled binary");

Effectiveness may depend on how the runtime reads the OS locale on the CI platform (env-var based on Linux vs. NSLocale-based on Apple targets per the PR description), so this is worth confirming before tightening the assertion to require the "de" row specifically.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/tests/i18n_interpolation_params.rs` around lines 106 - 179, The
test in t_multi_locale_runtime_selection_interpolates is too permissive because
it accepts either locale row, so it never proves runtime locale selection
actually chooses the non-default branch. Update the run invocation using
Command::env to set the child process locale (for example LANG/LC_ALL) before
executing the compiled binary, and then tighten the assertion to expect the
intended locale row with {days} interpolated. Use the existing
t_multi_locale_runtime_selection_interpolates test and its Command::new(&output)
spawn point to locate the change.
crates/perry-transform/src/i18n.rs (1)

501-528: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider guarding against field/arg count mismatch in extract_params.

zip silently truncates if field_names and args differ in length, dropping params without any signal. Given the anon-shape synthesis contract this should never happen, but a debug assertion would catch HIR-lowering regressions early instead of producing silently incorrect interpolation.

♻️ Proposed debug assertion
         Expr::New {
             class_name, args, ..
         } => match anon_shapes.get(class_name) {
             Some(field_names) => {
+                debug_assert_eq!(
+                    field_names.len(),
+                    args.len(),
+                    "anon-shape field count mismatch for {}: {} fields, {} args",
+                    class_name,
+                    field_names.len(),
+                    args.len()
+                );
                 field_names
                     .iter()
                     .zip(args.iter())
                     .map(|(name, value)| (name.clone(), Box::new(value.clone())))
                     .collect()
             }
             None => Vec::new(),
         },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-transform/src/i18n.rs` around lines 501 - 528, Add a debug-time
guard in extract_params for the Expr::New branch so mismatched anon-shape
field_names and args lengths are caught instead of silently truncated by zip.
Use the anon_shapes lookup and the class_name/args path to assert the counts
match before mapping, and keep the existing field-name-to-arg pairing logic
unchanged when they do.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-codegen/src/expr/dyn_extern_i18n.rs`:
- Around line 234-279: The literal accumulation logic in the template parser
inside the byte-scanning loop is incorrectly using `buf.push(b as char)`, which
can mangle non-ASCII text when placeholders are present. Update the parser in
`dyn_extern_i18n.rs` so it buffers raw bytes for each literal segment and only
converts to UTF-8 once when flushing into `Part::Lit`, while keeping the
existing `Part::Param` handling and escape cases (`{{`, `}}`) intact.

In `@crates/perry-ui-watchos/src/system.rs`:
- Around line 184-206: The successful keychain lookup path in
perry_system_keychain_get is leaking the returned CFData from
SecItemCopyMatching. After converting the bytes with js_string_from_bytes and
before returning, explicitly release the result object when status is 0 and
result is non-null so the owned Core Foundation object does not accumulate on
repeated reads.

---

Nitpick comments:
In `@crates/perry-runtime/src/intl/list_relative_plural.rs`:
- Around line 126-159: Add unit tests for the new Spanish branch in
list_separators to lock in the CLDR behavior. Cover the es and es-* cases for
list_type "unit" with style "long" and "short", and verify the "narrow" case
falls through to the base separator behavior. Keep the tests focused on the pure
list_separators function so future locale changes don’t regress this logic.

In `@crates/perry-transform/src/i18n.rs`:
- Around line 501-528: Add a debug-time guard in extract_params for the
Expr::New branch so mismatched anon-shape field_names and args lengths are
caught instead of silently truncated by zip. Use the anon_shapes lookup and the
class_name/args path to assert the counts match before mapping, and keep the
existing field-name-to-arg pairing logic unchanged when they do.

In `@crates/perry-ui-watchos/swift/PerryWatchApp.swift`:
- Around line 316-327: `styledButtonText` is duplicating the same font fallback
rules already used by `textView`, so extract that shared font-building logic
into one helper and have both `textView` and `styledButtonText` delegate to it.
Keep the `fontSize > 0` and `fontWeight >= 0` behavior in the shared helper, and
update both call sites to use the new helper so the logic stays consistent and
won’t drift.

In `@crates/perry/tests/i18n_interpolation_params.rs`:
- Around line 106-179: The test in t_multi_locale_runtime_selection_interpolates
is too permissive because it accepts either locale row, so it never proves
runtime locale selection actually chooses the non-default branch. Update the run
invocation using Command::env to set the child process locale (for example
LANG/LC_ALL) before executing the compiled binary, and then tighten the
assertion to expect the intended locale row with {days} interpolated. Use the
existing t_multi_locale_runtime_selection_interpolates test and its
Command::new(&output) spawn point to locate the change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f6efd531-f171-489e-84a4-3ea47b29be19

📥 Commits

Reviewing files that changed from the base of the PR and between 01a7f6f and 43de037.

📒 Files selected for processing (14)
  • crates/perry-codegen/src/codegen/mod.rs
  • crates/perry-codegen/src/expr/dyn_extern_i18n.rs
  • crates/perry-codegen/src/expr/mod.rs
  • crates/perry-codegen/src/runtime_decls/mod.rs
  • crates/perry-runtime/src/i18n.rs
  • crates/perry-runtime/src/intl/duration_format.rs
  • crates/perry-runtime/src/intl/list_relative_plural.rs
  • crates/perry-transform/src/i18n.rs
  • crates/perry-ui-watchos/src/lib.rs
  • crates/perry-ui-watchos/src/system.rs
  • crates/perry-ui-watchos/swift/PerryWatchApp.swift
  • crates/perry/src/commands/compile/bundle_apple.rs
  • crates/perry/src/commands/compile/run_pipeline.rs
  • crates/perry/tests/i18n_interpolation_params.rs

Comment thread crates/perry-codegen/src/expr/dyn_extern_i18n.rs Outdated
Comment thread crates/perry-ui-watchos/src/system.rs
Ralph Küpper added 2 commits July 9, 2026 20:55
…; release keychain CFData

- emit_i18n_template buffered literal bytes as `b as char`, re-encoding each
  byte as a Unicode scalar and mangling non-ASCII text around placeholders
  (e.g. `für {name}`). Buffer raw bytes, decode once per fragment. New
  non-ASCII case in the interpolation regression test.
- keychain_get leaked the +1 CFData from SecItemCopyMatching/kSecReturnData;
  release it after copying the bytes.

Addresses CodeRabbit review on #6201.
@proggeramlug
proggeramlug merged commit c78903c into main Jul 9, 2026
25 checks passed
@proggeramlug
proggeramlug deleted the timestable/watchos-system-parity branch July 9, 2026 19:54
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