I’m wondering how to fix problems when working with String Localization in Swift Packages in SwiftUI?
Currently facing problems:
- When importing the package into another project, the localized strings from the Swift Package are not showing as localized.
- Inside the package, strings are only localized in the preview when set directly, like this:
Text("String Example", bundle: .module)
However, when strings are called from variables, they are not localized.
To demonstrate the problems, I created a package named TestPackage
.
The TestPackage
code is as follows:
import PackageDescription
let package = Package(
name: "TestPackage",
defaultLocalization: "en",
platforms: [
.iOS(.v16)
],
products: [
.library(
name: "TestPackage",
targets: ["TestPackage"]
),
],
targets: [
.target(
name: "TestPackage",
resources: [.process("Localizable.xcstrings")] // also tried [.copy("Localizable.xcstrings")]
)
]
)
After that, in my Sources > TestPackage
folder, I created a SwiftUIViewForPreviews.swift
file to test the localized strings and a Strings
struct for holding an example string:
import SwiftUI
struct SwiftUIViewForPreviews: View {
let stringFromCurrentView = "String Example"
var body: some View {
VStack {
Text("String Example", bundle: .module) // Works in Preview
Text(stringFromCurrentView) // Localization doesn't work
Text(Strings.stringOneLocalized) // Localization doesn't work
}
}
}
#Preview {
SwiftUIViewForPreviews().environment(.locale, .init(identifier: "de"))
}
public struct Strings {
public static let stringOneLocalized = String(localized: "String Example", bundle: .module)
}
When running the preview, I only see the Text("String Example", bundle: .module)
string localized. For Text(stringFromCurrentView)
and Text(Strings.stringOneLocalized)
, localization does not work.
I also tried to set up strings the old way using .lproj
instead of using a Localizable Strings Catalog, but I faced the same problems.
To test how our package will work inside of a project, I created a project named ProjectForLocalizationPackageTesting
and attached the TestPackage
to it. I created a ContentView
to test how the localized string will show up in German localization, but in the preview, the string Text("String Example")
was not localized. I also tested it in the simulator with App Language and App Region set to German/Germany, but it still displayed the English variant in a simulator.
import SwiftUI
import TestPackage
struct ContentView: View {
var body: some View {
VStack {
Text("String Example") // Localization doesn't work
Text(Strings.stringOneLocalized) // Localization doesn't work
Text("String Example", bundle: .module) // Call doesn't work due to internal protection level
}
}
}
#Preview {
ContentView()
.environment(.locale, .init(identifier: "de"))
}
I tried the approach of adding a special extension and when adding it to a Package SwiftUIViewForPreviews View, in a Preview we start to see our strings localised, :
extension Text {
init(_ key: LocalizedStringKey, comment: String = "") {
self.init(key, bundle: .module)
}
}
But when we import our package to another project, our strings are shown again as not localised, even with the extension added to our Package.
Adding this code into our ContentView in our Project also didn’t help
extension Text {
init(_ key: LocalizedStringKey, comment: String = "") {
#if SWIFT_PACKAGE
self.init(key, bundle: .module) // For Swift Package
#else
self.init(key, bundle: Bundle.main) // For regular Xcode project
#endif
}
}
The questions are:
- How to correctly set up a Swift Package to support string localization to be able to see correct localisations for strings?
- How can we localize strings declared outside of a Text view in SwiftUI?
- How do I correctly configure a Swift Package in a project to display localized strings from the package where we have strings Localized?
The question is specific to working with swift packages, Dynamic Text LocalizedStringKey approach Text(LocalizedStringKey(stringFromCurrentView)) didn’t help to solve the problem. Any help appreciated.
Xcode version: Version 16.0, Swift, SwiftUI
2
Answers
Question 1
Your description of the package setup is the correct way to add localization to the package.
When the package is built, the resources from the packes will be collected into a Bundle that will be accessible within the package by using
Bundle.module
.Anywhere you want to use the string in the package, you need to pass
Bundle.module
otherwise it will default to looking inBundle.main
which is the bundle for app, not the package.Question 2
That code doesn’t localize the string because
stringFromCurrentView
is of typeString
, so when passing to theText
it’ll call the init that takes a string and does no localizationThat’s opposed to creating
Text
directly with a string literal, which will call theText.init
that takes aLocalizedStringKey
which can be initialized with a string literal.If the code above was update so the property was a
LocalizedStringKey
, it should work.String(localized:...)
should work when being run in an app. It doesn’t work in the preview because the view modifier.environment(.locale, ...)
is only used by other SwiftUI code.String(localized:...)
is defined in Foundation and won’t use the environment value since it doesn’t known about SwiftUI.Question 3
Correct is a matter of personal preference. I’ve used the approach similar to the
struct Strings
containing properties or functions if the string can take values. That’s also what the code from tools like SwiftGen generate.The important thing to remember for any solution is that it’s the combination of the string key and bundle (also tableName but it’s not applicable here) that allows a localized string to be looked up.
Using
LocalizedStringResource
In your package, declare your localizable strings with
LocalizedStringResource
instead ofString(localized:)
. Do not forget to include thebundle
parameter.Then in your app, import your package and everything works fine:
Using a public reference to the package itself
In your package, create a public value that refers to itself:
Then in your app, import your package, so you can access its
module
and use it in your views, i.e. yourText
:However I’m not sure this last solution is secure.