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

@@ -5,8 +5,11 @@
*/
import { mkdirSync } from "node:fs";
import { join } from "node:path";
import { mkdir } from "node:fs/promises";
import { dirname } from "node:path";
import {
APPLE_INFO_PLIST,
APPLE_L10N_SWIFT,
APPLE_XCSTRINGS,
kmpValuesDir,
KMP_RESOURCES,
@@ -14,6 +17,7 @@ import {
} from "../config";
import { renderAndroid, type ParsedAndroid } from "../lib/android-xml";
import { fromCanonical, type Flavor } from "../lib/placeholders";
import { renderSwiftAccessors } from "../lib/swift-accessors";
import { renderXcstrings, type XcCatalog, type XcEntry } from "../lib/xcstrings";
import { targetsOf, type StringEntry, type StringsFile } from "../types";
@@ -111,6 +115,10 @@ export async function generate() {
await Bun.write(APPLE_XCSTRINGS, renderXcstrings(buildXcstrings(doc)));
console.log(`Wrote ${APPLE_XCSTRINGS}`);
await mkdir(dirname(APPLE_L10N_SWIFT), { recursive: true });
await Bun.write(APPLE_L10N_SWIFT, renderSwiftAccessors(doc));
console.log(`Wrote ${APPLE_L10N_SWIFT}`);
await syncInfoPlistLocalizations(doc.supportedLanguages);
for (const lang of doc.supportedLanguages) {