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 LocalizedStringKey
and 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)
}
}