Xcode 15 introduced the String Catalog (.xcstrings) as Apple's new opinionated format for iOS and macOS localization. It is a single JSON file that holds every locale, every translation state, every plural variant, and every device-specific override under one tree. The legacy .strings format — flat, one-locale-per-file, no state tracking — is not going anywhere quickly: it is still what SwiftUI's LocalizedStringKey ultimately reads, what hundreds of CI scripts grep, and what most third-party translation tools speak fluently. Going from catalog to legacy strings is the conversion every team needs when they want to adopt .xcstrings for editing but still ship .strings for compatibility.
i18n-convert reads the catalog's sourceLanguage, walks each entry's localizations map, and emits a flat .strings file using the source-language stringUnit.value as the displayed text. Per-entry comment fields become C-style /* */ comments above the corresponding key, so translator notes survive the conversion. Two pieces of data are intentionally dropped: per-entry state (translated / new / needs-review) and per-entry extractionState. The CLI surfaces these as INFO warnings and requires --force because they have no representation in the flat .strings format. Entries are emitted in alphabetical order for deterministic diffs.
Command
i18n-convert simple.xcstrings --to ios-strings --force -o Localizable.strings
Input
{
"sourceLanguage" : "en",
"strings" : {
"greeting" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hello"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "こんにちは"
}
}
}
},
"farewell" : {
"comment" : "Said when leaving",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Goodbye"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "さようなら"
}
}
}
},
"app_name" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "MyApp"
}
}
}
}
},
"version" : "1.0"
}
Output
"app_name" = "MyApp";
/* Said when leaving */
"farewell" = "Goodbye";
"greeting" = "Hello";