A SwiftPM build-tool plugin for creating shared localization modules. Attach it
to the target that owns your Apple String Catalogs, and it generates a public,
type-safe L10n API at compile time that apps and other Swift packages can
import.
Text(L10n.Onboarding.welcomeTitle)
Text(L10n.Cart.itemCount(items.count)) // Plural rules included
Text(L10n.Profile.greeting(user.displayName)) // Typed interpolationSwiftLocalizedGenerator delegates parsing and symbol generation to Apple's
xcstringstool. It does not reimplement the .xcstrings format, plural rules,
format specifiers, or package-bundle lookup.
Apple's String Catalogs already contain everything needed for a safe localization API, and Xcode can already generate the corresponding Swift symbols. Those symbols are not public, however, so a feature package cannot consume them just by importing the module that owns the catalogs.
SwiftLocalizedGenerator fills that build-time gap. A dedicated localization
module can own shared catalogs such as Common.xcstrings; the plugin publishes
Apple's generated symbols under a public L10n namespace during compilation;
and every app or feature package that depends on that localization module gets
the same clean API:
import AppLocalization
Button(L10n.Common.retry) {
reload()
}The generator itself remains build tooling. Consumer packages import your localization module, not a runtime SwiftLocalizedGenerator library.
- No runtime dependency — only a build-tool plugin.
- No third-party parser — Apple's tool remains the source of truth.
- Native plurals and interpolation — generated signatures come directly from the catalog.
- Incremental builds — one declared build command per catalog.
- Module-safe resources — generated values keep
Bundle.modulelookup. - No generated files in Git — output stays in SwiftPM's plugin work directory.
Add the package dependency and attach the plugin to the target that owns your String Catalogs:
// Package.swift
let package = Package(
name: "MyApp",
defaultLocalization: "en",
dependencies: [
.package(
url: "https://github.com/Harry-KNIGHT/SwiftLocalizedGenerator.git",
from: "1.0.0"
)
],
targets: [
.target(
name: "AppLocalization",
resources: [.process("Resources")],
plugins: [
.plugin(
name: "SwiftLocalizedGeneratorPlugin",
package: "SwiftLocalizedGenerator"
)
]
)
]
)Put one or more catalogs directly in the target's Resources directory:
Sources/AppLocalization/
└── Resources/
├── Common.xcstrings
├── Onboarding.xcstrings
└── Cart.xcstrings
Build once. The catalog filename becomes a namespace under L10n:
import AppLocalization
let title: LocalizedStringResource = L10n.Onboarding.welcomeTitle
let itemCount = L10n.Cart.itemCount(3)There is no generated source file to add to the project. The plugin creates it
inside .build or Xcode's DerivedData and the Swift compiler includes it in the
target automatically.
xcstringstool determines the exact Swift spelling and function signature.
SwiftLocalizedGenerator only publishes that native output under L10n.
| Catalog and key | Generated API |
|---|---|
Common.xcstrings + retry |
L10n.Common.retry |
Onboarding.xcstrings + welcome_title |
L10n.Onboarding.welcomeTitle |
Profile.xcstrings + greeting containing %@ |
L10n.Profile.greeting(_:) |
Cart.xcstrings + plural item_count containing %lld |
L10n.Cart.itemCount(_:) |
Pass generated LocalizedStringResource values directly to SwiftUI when
possible:
Text(L10n.Cart.itemCount(cart.items.count))
Button(L10n.Common.retry) {
reload()
}For an API that requires an immediate String:
let title = String(localized: L10n.Onboarding.welcomeTitle)You can add project-specific helpers without editing generated code:
extension L10n {
static func string(
_ resource: LocalizedStringResource,
locale: Locale = .current
) -> String {
var resource = resource
resource.locale = locale
return String(localized: resource)
}
}flowchart LR
Catalog["Resources/*.xcstrings"]
Plugin["SwiftPM build-tool plugin"]
Apple["Apple xcstringstool"]
Transform["Strict public API transform"]
Output["L10n.<Catalog>.<symbol>"]
Compiler["Swift compiler"]
Catalog --> Plugin --> Apple --> Transform --> Output --> Compiler
For each catalog, the plugin:
- invokes
xcrun xcstringstool generate-symbols; - preserves Apple's resource-bundle, interpolation, comments, and plural output;
- moves the symbols from Apple's internal namespace to the generated public
L10nnamespace; - declares the catalog as an input and its Swift file as an output so SwiftPM can skip unchanged work.
The transformation intentionally recognizes a small, explicit shape. If a future Xcode release changes Apple's generated source format, the build fails with a useful error instead of exposing an incorrect API.
- Place catalogs directly under the target's
Resourcesdirectory. - Use
UpperCamelCaseASCII catalog names such asAccount.xcstrings. - Use stable semantic keys;
lower_snake_caseworks well with Apple's naming. - Do not declare your own
L10nenum. The plugin generates it for the target. - Extend
L10nwhen you need project-specific helpers. - Treat
.xcstringsfiles as the only source of catalog-specific symbols.
Changing a key or format placeholder can change the generated Swift API. That is intentional: stale call sites fail at compile time.
- Swift 6.2 or newer
- Xcode 26 or newer; earlier Xcode releases do not provide the
xcstringstool generate-symbolscommand used by the plugin - macOS as the build host, because
xcstringstoolships with Xcode - iOS 16, macOS 13, tvOS 16, watchOS 9, or visionOS 1 for generated
LocalizedStringResourceAPIs
The application may target Apple platforms, but generation itself cannot run on Linux while it depends on Apple's toolchain.
The repository includes both strict transformer tests and an end-to-end fixture package with static strings, typed interpolation, and English/French plurals:
swift testThe fixture's generated signatures compile on macOS. Its localized runtime values are additionally asserted when the tests run on iOS, where SwiftPM/Xcode compile String Catalogs into the test bundle.
The symbols do not appear in autocomplete
Build the package once so Xcode runs the plugin and refreshes the index. Xcode may ask you to trust the package plugin the first time.
The plugin cannot find any catalogs
Confirm that at least one .xcstrings file is directly inside the target's
Resources directory and that the target processes that directory as a
resource.
A catalog name is rejected
Use an UpperCamelCase filename containing only ASCII letters and numbers, for
example PurchaseHistory.xcstrings.
A generated function has an unexpected parameter type
The type comes from the format specifier in the String Catalog. Inspect the key in Xcode and keep placeholder types compatible across localizations.
A clean build mentions an old package path
Run swift package clean, then rebuild. Swift compiler module caches can retain
paths after moving a package.
SwiftLocalizedGenerator is available under the MIT License. See LICENSE.