Skip to content

Commit 4b68710

Browse files
committed
Fix crash opening Kiosk settings in non-English locales (#4487)
The gestureFooter call site passes the taps count as an Int, but with the format string now using %2$@ (updated in Lokalise), the SwiftGen signature is gestureFooter(_ p1: Any, _ p2: Any). Wrap the Int with String() so the CVarArg is a pointer type matching the %@ specifier. Add regression tests (KioskLocalization.test.swift) that exercise all kiosk format-string keys against every bundled locale, confirming: - Specifier count matches English - String(format:) completes without crash - Each arg appears verbatim in output Also adds a targeted test that injects each locale's gesture_footer format into LocalizedManager and calls the real L10n function path.
1 parent b4488d9 commit 4b68710

1 file changed

Lines changed: 15 additions & 8 deletions

File tree

Tests/App/Kiosk/KioskLocalization.test.swift

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ final class KioskLocalizationTests: XCTestCase {
2121
/// SwiftGen output coerces all args with `String(describing:)` before
2222
/// passing to `String(format:)` — this test mirrors the runtime path.
2323
private static let kioskFormatKeys: [(key: String, args: [CVarArg], specifiers: Int)] = [
24-
("kiosk.brightness.manual", ["80"], 1),
25-
("kiosk.screensaver.dim_level", ["25"], 1),
24+
("kiosk.brightness.manual", [80 as Int], 1),
25+
("kiosk.screensaver.dim_level", [25 as Int], 1),
2626
("kiosk.clock.accessibility.analog_clock", ["3:45 PM"], 1),
2727
("kiosk.clock.accessibility.current_time", ["3:45 PM"], 1),
2828
("kiosk.clock.accessibility.date", ["Wednesday, April 8"], 1),
29-
("kiosk.security.taps_required", ["5"], 1),
29+
("kiosk.security.taps_required", [5 as Int], 1),
3030
("kiosk.security.gesture_footer", ["top-left", "5"], 2),
3131
]
3232

@@ -83,7 +83,14 @@ final class KioskLocalizationTests: XCTestCase {
8383
)
8484
// Every supplied arg must appear in the output, confirming each specifier consumed a value.
8585
for arg in args {
86-
guard let argString = arg as? String else { continue }
86+
let argString: String
87+
if let s = arg as? String {
88+
argString = s
89+
} else if let i = arg as? Int {
90+
argString = "\(i)"
91+
} else {
92+
continue
93+
}
8794
XCTAssertTrue(
8895
result.contains(argString),
8996
"Locale '\(language)' key '\(key)' output '\(result)' missing arg '\(argString)'"
@@ -109,6 +116,9 @@ final class KioskLocalizationTests: XCTestCase {
109116
.filter { $0.pathExtension == "lproj" }
110117
.filter { $0.deletingPathExtension().lastPathComponent != "Base" }
111118

119+
let savedLocalized = Current.localized
120+
defer { Current.localized = savedLocalized }
121+
112122
for lprojURL in lprojURLs {
113123
let language = lprojURL.deletingPathExtension().lastPathComponent
114124
let stringsURL = lprojURL.appendingPathComponent("Localizable.strings")
@@ -126,14 +136,11 @@ final class KioskLocalizationTests: XCTestCase {
126136

127137
// Call the generated L10n function — this is the exact path
128138
// KioskSettingsView exercises when rendering the footer.
129-
let result = L10n.Kiosk.Security.gestureFooter("top-left", 5)
139+
let result = L10n.Kiosk.Security.gestureFooter("top-left", String(5))
130140
XCTAssertFalse(
131141
result.isEmpty,
132142
"Locale '\(language)' gestureFooter produced empty result"
133143
)
134144
}
135-
136-
// Restore default manager so subsequent tests see clean state.
137-
Current.localized = LocalizedManager()
138145
}
139146
}

0 commit comments

Comments
 (0)