mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
Add localization/ — one strings.json is the source of truth for every
user-facing string, and a Bun CLI generates the platform files:
loc migrate rebuild strings.json from existing platform files (one-time)
loc validate structural checks (plural `other`, arg refs, coverage)
loc generate emit .xcstrings (Apple) + per-language strings.xml (KMP)
Canonical `{name}` placeholders are converted to each platform's positional
printf tokens using declared arg types. Plurals use CLDR categories and emit
native forms: xcstrings plural variations and Compose <plurals> blocks.
Migration folds the KMP `transfer_file_count_one`/`_other` pair into a single
plural key, so `transferFileCountResource` now returns the plural resource and
callers resolve it with pluralStringResource.
Localization
Single source of truth for every user-facing string, in strings.json.
A Bun CLI generates the platform-native files from it:
| Target | Output | Notes |
|---|---|---|
apple |
apple/VniDrop/Resources/Localizable.xcstrings |
one catalog, all languages nested |
kmp |
shared/src/commonMain/composeResources/values[-lang]/strings.xml |
one file per language |
Workflow
cd localization
bun run src/cli.ts validate # structural checks (run before committing)
bun run src/cli.ts generate # regenerate .xcstrings + strings.xml from strings.json
bun run src/cli.ts migrate # one-time: rebuild strings.json from existing platform files
Never edit the generated .xcstrings / strings.xml by hand — edit strings.json and
regenerate. Regenerated output is deterministic (sorted keys), so diffs stay small.
strings.json format
{
"sourceLanguage": "en",
"supportedLanguages": ["en", "fr"],
"strings": {
"send_title": {
"context": "Send tab — screen title.",
"translations": { "en": "Send", "fr": "Envoyer" }
},
"send_selected_files_count": {
"context": "Send flow — number of files chosen before creating a transfer.",
"targets": ["kmp", "apple"],
"args": [{ "name": "count", "type": "int" }],
"plural": {
"en": { "one": "{count} file selected", "other": "{count} files selected" },
"fr": { "one": "{count} fichier sélectionné", "other": "{count} fichiers sélectionnés" }
}
}
}
}
Fields
context(required) — where the string appears and its purpose. Emitted as the.xcstringscomment and an XML comment; also the note translators see.targets(optional) —["kmp", "apple"]. Omit to mean all targets.args(optional) — ordered list of{ name, type },type∈string | int | double. Referenced in text as{name}.translations— flat text per language. Mutually exclusive withplural.plural— per language, per CLDR category (zero,one,two,few,many,other).otheris always required.
Placeholders
Write named tokens {count}, {name} in text. The generator converts them to the right
positional token per platform, using the declared type:
| type | Apple | Android/KMP |
|---|---|---|
string |
%N$@ |
%N$s |
int |
%N$d |
%N$d |
double |
%N$f |
%N$f |
A literal % in text is emitted as %% whenever the string has args.
Adding a language
Add its code to supportedLanguages, fill in translations / plural for each key, then
generate. KMP gets a new values-<lang>/strings.xml; Apple gets the language inside the
single catalog. validate warns about any key still missing that language.
Migration notes (from the initial import)
- Apple keys that were literal English strings (
"%@ · %@") were imported verbatim — rename them to semantic keys and update the Swift call sites. - Arg names default to
arg1,arg2… (a lone int arg becomescount). Rename for clarity; keep the{token}in text in sync. - Folding
transfer_file_count_one/_otherinto the plural keytransfer_file_countrequires switching the KMP call site fromRes.string.transfer_file_count_oneto the Compose plural API (pluralStringResource(Res.plurals.transfer_file_count, count, count)), and the Apple side to automatic plural inflection.