Localized SwiftUI Views in a Swift Package

A strings file contains the translations of localized user-facing strings for one language with optional comments. The syntax for each string in a strings file is a key-value pair in which key is the identifier for looking up the value that contains the translation.
/* A friendly greeting. */
"Hello, World!" = "Hallo, Welt!";
Initializers for several SwiftUI types – such as Text, Toggle, Picker and others – implicitly lookup a localized string when you provide a string literal.
Text("Hello, World!") // you might expect that it will show "Hallo, Welt!"
This implicit lookup won't work if you use those SwiftUI types in a Swift package !
If you initialize a SwiftUI Text view with a string literal, the view uses the init(_:tableName:bundle:comment:) initializer, which interprets the string as a localization key and searches for the key in the table you specify or in the default table if you don’t specify one. More importantly it will use Bundle.main if you don't specify one.
Text("Hello, World!") // Shows "Hello, World!" as it searches the default table in the main bundle.
When building your Swift package, Xcode treats each target as a Swift module. If a target includes resources, Xcode creates a resource bundle and an internal static extension on Bundle to access each module. You have to use this extension Bundle.module to locate package resources.
Text("Hello, World!", bundle: .module) // Shows "Hallo, Welt!" :)
Not all SwiftUI types have such flexible initializers. Button view has an initializer expecting a LocalizedStringKeyand will lookup the text in the Localizable.strings file through Bundle.main without the option to specify a different bundle.
Then you have to use other initializers (if possible)
Button(action: { print("Label shows 'Hallo, Welt!'") }, label: {
Text("Hello, World!", bundle: .module)
})
or you lookup the localized text directly
Bundle.module.localizedString(forKey: "Hello, World!", value: nil, table: nil) // returns "Hallo, Welt!"
The statement takes a lot of space which can be changed by introducing an extension on Bundle
extension Bundle {
func localizedString(forKey key: String) -> String {
self.localizedString(forKey: key, value: nil, table: nil)
}
}
and an extension on String
extension String {
var localizedString: String {
Bundle.module.localizedString(forKey: self)
}
}
to get a localized string concisely.
Button("Hello, World!".localizedString) { print("Label shows 'Hallo, Welt!'") }
An extra tip for people who toy with the idea to generate a binary framework (xcframework) from a Swift Package: It is necessary to abstract the bundle access as Xcode won't create the internal static extension Bundle.module for XCFrameworks. You can do this with the following code snippet
import Foundation
class BundleLocator {}
extension Bundle {
static var myModule: Bundle {
#if SWIFT_PACKAGE
return Bundle.module
#else
return Bundle(for: BundleLocator.self)
#endif
}
func localizedString(forKey key: String) -> String {
self.localizedString(forKey: key, value: nil, table: nil)
}
}
extension String {
var localizedString: String {
Bundle.myModule.localizedString(forKey: self)
}
}



