swift-localize syncs translations from Google Sheets directly into your Xcode project's .xcstrings String Catalogs.
It is the Swift Package successor to the deprecated fastlane-plugin-localize Ruby plugin: same Google Sheets workflow, same service-account auth, but emits modern String Catalogs (.xcstrings) instead of .strings + .stringsdict.
Google Sheets is the source of truth: new keys appear in the app, renamed keys replace old ones, and removed keys are deleted from the catalog.
- One
.xcstringsString Catalog per Google Sheet tab. - Plural variations (
one|...,other|..., …) are mapped to xcstrings plural variations — no morestringsdict. - Cross-platform format placeholders (
%s,%1s,%2s) are remapped to Apple form (%@,%1$@,%2$@). - Optional tab allow-list and configurable identifier column header (for re-using a sheet across platforms).
- Diff logging per tab: added / removed / updated keys.
- Pure Swift, no fastlane / Ruby dependency.
- macOS 14+
- Swift 6.2 toolchain (or compatible with this package)
- A Google Cloud service account with access to the Google Sheets API
- Authenticate with Google using a service account JSON key (PKCS#8 RSA, signed JWT → OAuth2 token).
- Read spreadsheet tab metadata and (optionally) filter by
tabs. - For each tab, fetch values, parse rows, transform placeholders / plurals.
- Write the result to
<localizationPath>/<tabName>.xcstrings, overwriting in place.
Empty tabs (no parseable rows) remove an existing catalog file rather than leaving a stale one behind.
Each sheet tab maps to one .xcstrings file. The header row must contain:
- An identifier column (default
Identifier iOS, configurable). - One column per BCP-47 short language code (
de,en,fr, …) — long codes likeen-USare ignored. - A
Kommentarcolumn. Everything to the right ofKommentaris ignored.
Example:
| Identifier iOS | de | en | fr | Kommentar | Identifier Android |
|---|---|---|---|---|---|
| welcome_title | Willkommen | Welcome | Bienvenue | Main screen title | welcome_title |
| button_ok | OK | OK | OK | btn_ok |
- Rows where the identifier is empty,
NR,TBD, or starts with//are skipped. - Individual language cells that are empty,
NR, orTBDare omitted for that language only.
Authors write platform-neutral placeholders in the sheet; the CLI rewrites them on import:
| Sheet value | Written to .xcstrings |
|---|---|
Hello %s |
Hello %@ |
Mario ate a %2s %1s |
Mario ate a %2$@ %1$@ |
Pi: %.2f, Count: %d, Name: %@ |
unchanged |
Plural cells follow the legacy plugin's format:
one|%d artist
other|%d artists
Categories accepted (case-insensitive): zero, one, two, few, many, other. The other category is required; cells missing it are written as a single string. Plural cells become proper xcstrings plural variations:
"artists.count": {
"localizations": {
"en": {
"variations": {
"plural": {
"one": { "stringUnit": { "state": "translated", "value": "%d artist" } },
"other": { "stringUnit": { "state": "translated", "value": "%d artists" } }
}
}
}
}
}- Open the Google Cloud Console and create/select a project.
- Enable the Google Sheets API.
- Create a service account.
- Create and download a JSON key file.
- Share your target Google Sheet with the service account email (Viewer permission is enough).
Save the JSON key locally, for example as ./credentials/google_drive_credentials.json.
Place it next to your .xcodeproj:
{
"credentialsPath": "./credentials/google_drive_credentials.json",
"spreadsheetId": "YOUR_SPREADSHEET_ID",
"localizationPath": "./MyApp",
"sourceLanguage": "de",
"tabs": ["Common", "Onboarding"],
"identifierColumn": "Identifier iOS"
}All paths are resolved relative to the config file's location, so the CLI can be invoked from any working directory.
| Key | Required | Description |
|---|---|---|
credentialsPath |
Yes | Path to Google service account JSON key file. |
spreadsheetId |
Yes | Spreadsheet ID from the Google Sheets URL. |
localizationPath |
Yes | Directory inside your Xcode project where .xcstrings files live. Each tab is written as <localizationPath>/<tabName>.xcstrings. |
sourceLanguage |
Yes | BCP-47 language code used as sourceLanguage in every catalog (e.g. de). |
tabs |
No | Allow-list of sheet tab titles. Omit or use [] to import all tabs. Tabs missing from the spreadsheet log a warning. |
identifierColumn |
No | Header name of the identifier column. Defaults to Identifier iOS. Useful for sheets shared with Android/Web (Identifier Android, Identifier Web). |
From the package root:
swift run swift-localize-cli --config /path/to/localize.jsonDefault (looks for ./localize.json in the current directory):
swift run swift-localize-cliShow help:
swift run swift-localize-cli --helpBuild a release binary:
swift build -c releaseBinary location:
.build/release/swift-localize-cli
Add the package to your Package.swift:
dependencies: [
.package(url: "<repository-url>", from: "0.1.0")
],
targets: [
.target(
name: "YourTarget",
dependencies: [
.product(name: "swift-localize", package: "swift-localize")
]
)
]Then call it from your code:
import swift_localize
let config = try LocalizerConfig.load(from: "./localize.json")
let localizer = Localizer(config: config)
try await localizer.run()swift-localize keeps the same Google Sheet conventions ("Sheet Language") and replaces the rest:
| Old fastlane option | New behavior |
|---|---|
service_account_path |
credentialsPath |
sheet_id |
spreadsheetId |
localization_path |
localizationPath |
tabs |
tabs (allow-list, optional) |
identifier_name |
identifierColumn (optional, default Identifier iOS) |
language_titles |
Auto-detected from header row (any valid ISO short code). |
default_language |
Removed — xcstrings handles fallback to sourceLanguage at runtime. |
base_language |
Removed — .xcstrings is a single file; the sourceLanguage field replaces Base.lproj. |
comment_example_language |
Removed — the Kommentar column is written verbatim as the entry's comment. |
support_objc, code_generation_path, support_spm |
Removed — Xcode 15+ auto-generates symbols from .xcstrings. |
platform (android/web) |
Removed — Swift-only scope. |
.strings + .stringsdict output |
Replaced with .xcstrings (plurals → variations.plural). |
Run tests (requires full Xcode for Swift Testing macro plugins):
swift test