feat(apple): generate type-safe L10n accessors and migrate all key literals

Replaces every stringly-typed localization key in the Apple app with
compile-time-checked accessors generated from localization/strings.json.
A mistyped key is now a build error instead of a silent fallback to the
raw key at runtime. The runtime path is unchanged: plain keys are
String.LocalizationValue constants resolved with String(localized:) and
Apple's String Catalog still does the lookup; keys with arguments become
typed, named functions applying args through String(format:).

Generator: new renderSwiftAccessors emits apple/VniDrop/Generated/L10n.swift,
wired into generate. Renamed generic arg1/arg2 tokens on four keys to
semantic names (receiver, transferName, deviceId) and updated their context
notes; positional output is unchanged so .xcstrings (bar the 4 comments)
and the Android XML regenerate identical.

Migration: every key-carrying value flipped to String.LocalizationValue
end to end, resolved only at the leaf. Zero key literals and zero
LocalizedStringKey remain in app or test code. macOS build passes; iOS
test run pending.
This commit is contained in:
2026-07-23 17:12:43 +02:00
parent 5939489432
commit ef42875ddb
28 changed files with 4465 additions and 395 deletions

View File

@@ -25,26 +25,26 @@ struct SettingsScreen: View {
Form {
Section {
NavigationLink(value: SettingsSection.preferences) {
SettingsRow(icon: "person.crop.circle", title: String(localized: "preferences_title"), value: model.state.username)
SettingsRow(icon: "person.crop.circle", title: String(localized: L10n.Preferences.title), value: model.state.username)
}
NavigationLink(value: SettingsSection.appearance) {
SettingsRow(icon: "sun.max", title: String(localized: "appearance_title"), value: themeModeLabel(model.state.themeMode))
SettingsRow(icon: "sun.max", title: String(localized: L10n.Appearance.title), value: themeModeLabel(model.state.themeMode))
}
}
Section {
NavigationLink(value: SettingsSection.notifications) {
SettingsRow(icon: "bell", title: String(localized: "notifications_title"), value: nil)
SettingsRow(icon: "bell", title: String(localized: L10n.Notifications.title), value: nil)
}
NavigationLink(value: SettingsSection.storage) {
SettingsRow(icon: "internaldrive", title: String(localized: "storage_title"), value: nil)
SettingsRow(icon: "internaldrive", title: String(localized: L10n.Storage.title), value: nil)
}
NavigationLink(value: SettingsSection.about) {
SettingsRow(icon: "info.circle", title: String(localized: "about_title"), value: nil)
SettingsRow(icon: "info.circle", title: String(localized: L10n.About.title), value: nil)
}
}
}
.formStyle(.grouped)
.navigationTitle(Text(LocalizedStringKey("settings_title")))
.navigationTitle(Text(String(localized: L10n.Settings.title)))
.navigationDestination(for: SettingsSection.self) { section in
sectionForm(section)
}
@@ -57,7 +57,7 @@ struct SettingsScreen: View {
SettingsSectionContent(model: model, section: section)
}
.formStyle(.grouped)
.navigationTitle(Text(LocalizedStringKey(section.titleKey)))
.navigationTitle(Text(String(localized: section.titleKey)))
if section == .about {
content
@@ -69,7 +69,7 @@ struct SettingsScreen: View {
Button {
showBugReport = true
} label: {
Label(String(localized: "about_bug_report"), systemImage: "ladybug")
Label(String(localized: L10n.About.bugReport), systemImage: "ladybug")
}
}
}
@@ -130,8 +130,8 @@ struct SettingsRow: View {
func themeModeLabel(_ mode: ThemeMode) -> String {
switch mode {
case .system: return String(localized: "appearance_system_mode")
case .light: return String(localized: "appearance_light_mode")
case .dark: return String(localized: "appearance_dark_mode")
case .system: return String(localized: L10n.Appearance.systemMode)
case .light: return String(localized: L10n.Appearance.lightMode)
case .dark: return String(localized: L10n.Appearance.darkMode)
}
}