Skip to content

Spec: Text ‐ Localization

Matt Carroll edited this page Feb 24, 2024 · 1 revision

Localization

SwiftUI is not the only UI framework developed by Apple to build natively on Apple platforms. UIKit and AppKit are older frameworks, but still widely used. They can even be integrated inside SwiftUI. Some localization constructs are shared between these frameworks and since SwftUI is the youngest of the family, there are some limitations or awkward edge cases when using some of them. They will be noted where relevant.

Supporting multiple languages in your app is a good overview of the tools available for localization. Look at Xcode's localization documentation for more information and examples.

Localizable initializers

String literal

If you initialize a text view with a string literal, the view uses the init(_:tableName:bundle:comment:) initializer, which interprets the string as a localization key and looks up its localization value for the app's locale in the given table. If the locale is not supported, the table is not found or there is no value for the key, its value is displayed in the UI as is.

Text("pencil") // displays "pencil" if no localization is found

Variable

If you intialize a text view with a variable value, the view uses the init(_:) initializer, which doesn’t localize the string. However, you can request localization by creating a LocalizedStringKey instance first, which triggers the init(_:tableName:bundle:comment:) initializer instead:

let writingImplement = "pencil"

Text(writingImplement) // displays "pencil" for all locales

Text(LocalizedStringKey(writingImplement)) // uses localization

Verbatim

To explicitly bypass localization for a string literal, use the init(verbatim:) initializer.

Text(verbatim: "pencil") // displays "pencil" for all locales

LocalizedStringResource

LocalizedStringResource can be used to specify the Text initializer parameters outside of UI code, for example:

struct SomeController {
	static var title = LocalizedStringResource(
		"pencil",
		table: "MyTable",
		comment: "my comment",
	)
}

// these display the same thing
Text(SomeController.title)
Text("pencil", table: "MyTable", comment: "my comment")

Note: One of the uses for a LocalizedStringResource is "to provide localizable strings with lookups you defer to a later time". In Swift this refers to the passing of localizable strings to other processes, which may have different locales from the process where they originated from. It's unclear if this capability can be useful in the context of the swift_ui package.

String

String, in the context of localization, has initializers of the form init(localized:...) that take similar parameters to the String literal initializer. For example: init(localized:defaultValue:options:table:bundle:locale:comment:).

The new parameters are:

  • defaultValue: can be used to specify the localized value for the default locale. Useful when the value of localized isn't intended to be displayed.
  • options: used to specify formatting options.
  • locale: can be used to explicitly specify a locale.
Text(LocalizedStringKey(String(localized: "pencil")))

Note: we need to use LocalizedStringKey because String is not a string literal (see variable section).

AttributedString

AttributedString can be used to associate style and attributes to runs of text. Like String, it has various initializers of the type init(localized:options:table:bundle:locale:comment:), with the parameters playing the same roles.

let attributed = AttributedString(localized: "pencil")
Text(attributed)

Note: With AttributedString the localized value can only be displayed in the language currently set in the system and cannot be specified as a specific language, even setting the locale attribute does not work. This is one of the incompatibilities I was referring to before.

Remarks

String and AttributedString are awkward to work with in swiftUI (localization-wise, at least), and don't seem to be completely supported. A more through investigation is needed to discover their limits in swiftUI, but this goes outside of the Text specification. Besides, all the features offered by them can be achieved with the Text view and string literals directly, albeit with a different API.

This needs to be investigated further.

String catalogs

In the initializers mentioned untile now there is the table parameter. This refers to the name of the file that contains the localization values for the Text's string. There are various file formats available to create these tables: Strings and Stringsdict files (legacy), and String catalogs. The latter are the recommended format for new projects in Xcode 15 (example here). They can be auto-generated for swiftUI code from Xcode using the build setting SWIFT_EMIT_LOC_STRINGS = YES.

A String catalog is a json file with the .xcstrings extension, and the usually only one is needed (named Localizable). If more are needed (for a better organization or to split a large one) the convention is to name them LocalizableXXX (eg. LocalizableFoo, LocalizableBar), but any name is acceptable. For example, you could have one catalog per feature: LocalizableSettings, LocalizableAuthentication, LocalizableHome ecc. The same file contains the localization for its keys in all the supported locales. Xcode renders them with an interactive UI:

Screenshot 2024-01-12 at 23 59 57

Another parameter of the initializers is bundle. It is used to lookup localizations in a bundle that is external the application. This functionality is not useful for the swift_ui package.

Automatic grammar agreement

For some supported languages, swiftUI can automatically choose the correct gender, plural form or pronoun for a sentence, without the need to specify anything in the string catalogs. In the example, note that the displayed string has been adapted ("grandi" instead of "grande", "insalate" instead of "insalata"), just by adding the inflect: true attribute.

let quantity = 2
let size = LocalizedStringResource("large") // grande in italian
let food = LocalizedStringResource("salad") // insalata in italian

Text("Add ^[\(quantity) \(size) \(food)](inflect: true) to your order")
// Displays "Aggiungi 2 grandi insalate al tuo ordine" in italian

See the WWDC video for more info.

Note: There are other mechanisms like this (Term of address, Inflection rule, Morphology, Inflection concept), that affect all localizable strings, even outside the Text view, that need their own specification. It's unclear how we can achieve something similar in the swift_ui package.

Clone this wiki locally