Internationalization and localization are two different concepts, the former makes your app support other languages and the latter adds languages to the app.
Internationalization
To make an app support different languages easily, some configurations must be made first.
To identify the texts that are not localized, open the “Run options” menu ⌥ + ⌘ + r
and check “Show non localized strings”.
This will uppercase every text that has not been localized, with some exceptions:
- Those texts that are obtained from the model (user data) won’t be localized, take a look at the translated words and phrases in the screenshot below.
- Texts that are calculated programmatically, instead of being hardcoded directly, take a look at the word “Filter”.
// ...
private var placeholder: String
init(placeholder: String, /* ... */) {
self.placeholder = placeholder
// ...
}
var body: some View {
VStack(alignment: .leading) {
HStack {
TextField(placeholder, text: $searchString)
// ...
Localizable.strings
Initially, I added all the strings in English, now I am going to support another language, Spanish.
First of all, add a Localizable.strings
file to your project.
In the main directory of your project, run in Terminal:
find . -name "*.swift" | xargs genstrings -SwiftUI -o ~/Desktop/
This command will generate another Localizable.strings
file and it will place it in the desktop, just copy/paste its content into the Localizable.strings
of the project.
Run the app again, the texts are not uppercased anymore. That means that is localized!
Errors
Some texts may not be included in the Localizable.strings
file, for example, the word “Filter” should not be included in it as it was not uppercased in the previous step.
The solution is to use LocalizedStringKey
instead of String
to pass strings as parameters that must be translated.
// ...
private var placeholder: LocalizedStringKey
init(placeholder: LocalizedStringKey, /* ... */) {
self.placeholder = placeholder
// ...
}
var body: some View {
VStack(alignment: .leading) {
HStack {
TextField(placeholder, text: $searchString)
// ...
Now, run again the app and check that the word is not uppercased anymore. Xcode logs should print:
[strings] ERROR: Filter not found in table Localizable of bundle CFBundle
To solve this, the only solution is to add manually the string to the Localizable.strings
file:
"Filter" = "Filter";
Plurals
A string went unnoticed because it was uppercased before the Show non-localized strings
option was checked:
var numberOfTranslationsString: String {
String(format: "%d translations", translations.count).localizedUppercase
}
It wasn’t included in the Localizable.strings
file but it did not appear in the Xcode logs either. Doing this:
/// Text that expresses the number of translations that contains the spreadsheet.
var numberOfTranslationsString: String {
String(format: NSLocalizedString("%d translations", comment: ""), translations.count)
}
The localizedUppercase
has been removed but, running the app, the string is still in capital letters. At least, the system is noticing that the string is not localized, thanks to the NSLocalizedString
.
But what does this string have to do with plurals? Well, each spreadsheet can have a different number of translations and the text is not the same for 1 than for 2 or more, a lot of languages work like this:
1 TRANSLATION
2 TRANSLATIONS // As you see, there is a difference, not only on the number.
To support plurals, add a stringsdict
file:
Open it as an XML file:
It must contain this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>`StringKey`</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@VARIABLE@</string>
<key>VARIABLE</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string></string>
<key>zero</key>
<string></string>
<key>one</key>
<string></string>
<key>two</key>
<string></string>
<key>few</key>
<string></string>
<key>many</key>
<string></string>
<key>other</key>
<string></string>
</dict>
</dict>
</dict>
</plist>
After replacing StringKey
with the actual key, in this case is %d translations
, adding d
as the string for NSStringFormatValueTypeKey
…
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
… and adding a string for the cases zero
, one
, and other
…
<key>zero</key>
<string>No translations</string>
<key>one</key>
<string>1 translation</string>
<key>other</key>
<string>%d translations</string>
This is the result! 😁
At this point, the app is fully internationalized.
Localization
Go to Project > Info tab > Localizations > + > Language
, in my case, I added Spanish, so it looks like this:
By unfolding Localizable.strings
, both languages appear. Opening the second language file and replacing the texts will localize the app!
"Delete all imported translations" = "Borrar todas las traducciones importadas"; // For example
stringsdict
For plurals is a bit different. The Localizable.stringsdict
file must be selected to open its File inspector, you can use the shortcut ⌥ + ⌘ + 0
. By selecting Localize
, Xcode will prompt which language must be supported, after selecting one, it should look like this:
As done in Localizable.strings
, the texts must be replaced to match the new language. But the key StringKey
must remain the same.
Accessibility
In the previous section, where I talked about accessibility, some accessibility labels had this format:
.accessibilityLabel("\(translation.translationInput) in \(translation.translationFrom), \(translation.translationOutput) in \(translation.translationTo) . Level \(translation.level).")
This kind of strings, must be added with placeholders to the Localizable.strings
file and everything should work fine:
"%@ in %@, %@ in %@ . Level %d." = "%@ en %@, %@ en %@ . Nivel %d.";
Always remember to translate all the accessibilityLabel
and accesibilityHint
😁