When we use an English app, many people will immediately check whether there is a Corresponding Chinese version. This shows how important it is to display the most user-friendly language text in your app. For a significant number of apps, if you can localize the text displayed in the UI, you’re basically done localizing the app. In this article, we’ll explore how to localize display text in iOS development. The Demo of this article is written with SwiftUI.
The original post was posted on my blog wwww.fatbobman.com
How text localization works
As a programmer, if you were to consider designing a logic to localize the original text for different languages, I think most people would consider a dictionary (key-value pair) solution. Apple does the same thing. By creating multiple dictionaries for different languages, the system can easily find the localized text (value) corresponding to the original text (key). Such as:
//en
"hello" = "Hello";
Copy the code
//zh
"hello" = "Hello";
Copy the code
This set of methods is the main text localization method adopted in this paper.
System at the time of the compiled code, will be able to localize the operation of the text has carried on the tag, when the app runs in a different language environment (e.g., French), the system will try as far as possible from French text keys to replace the contents of the file to find out the corresponding, if can’t find will continue to find in accordance with the order of the language preference list. For some types, such as LocalizedStringKey, this is done automatically, but for strings, which are most commonly used in code, you need to do this explicitly.
Fortunately, most of SwiftUI’s controls (some of which are currently buggy) prefer the LocalizedStringKey constructor for text types, which greatly relieves developers of manual processing.
Add language
The ability to develop internationally is a must-have feature for modern programming languages and development environments. When we create a project in Xcode, by default the app is developed only for its corresponding Development Language.
So we must first let the project know that we are going to localize the project and choose the appropriate language.
In Project Navigation, click Project and select Info to add languages in Localizations.
Click the + sign to select the language we will add.
Here we are simply telling the project that we will probably localize the languages in the list. But how to localize, localize those files and resources, we need to set them up separately.
When you enable Use Base Internationalization, Xcode will modify your project folder structure. The xib and StoreyBoard files will be moved to the base.lproj folder, and the string elements will be extracted to the project locale folder. This option is for development using Storyboard, so don’t worry if you use SwiftUI.
For the UIKit framework, Xcode will let you choosestoryboard
As used in this articleThe Demo projectFor all SwiftUI architecture, thereforeDon’tHere’s the picture.
Create a text string file
In apple’s development environment, the file type corresponding to the string file (text key-value pair file) we mentioned above is strings. We can create multiple string files in an app, and some string files with names have special meanings.
-
Localizable.strings
UI default corresponding string file. Unless the name of the string file is specified, the app gets the corresponding localized text content from Localizable. Strings
-
InfoPlist.strings
String file corresponding to info.plist. It is usually used for localization of app names, permissions warning prompts and other content.
In Project Navigation, we select New file
Select Strings File for the File type and name it Localizable. Strings
The Localizable. Strings file is not localized. Currently, there is only one file in your project, in which text key pairs are defined. Button to select the language file to generate Localizable. Strings (language list for adding language Settings to the project).
Check both languages on the right
Localizable. Strins in the left Project Navigation will become the following state:
English and Chinese are currently empty files, so we can now create the corresponding text key-value pairs here.
You can download the Demo project here
Actual combat 1: The list name of the Chinese bill form
In this section we try to provide the corresponding Localized Chinese text for ITEM, QUANTITY, UNIT PRICE, and AMOUNT.
Following the key/value pair declaration rule above, we add the following to the Localizable.Strings(Chinses) file:
"ITEM" = "Kind";
"QUANTITY" = "The number";
"UNIT PRICE" = "The price";
"AMOUNT" = "Total";
Copy the code
Open the TableView and add the localization environment configuration in the preview
TableView()
.environmentObject(Order.sampleOrder)
.previewLayout(.sizeThatFits)
.environment(\.locale,Locale(identifier: "zh"))
Copy the code
What changes do we see from the Preview area at this point? Nothing has changed!
The reason is that the keys we set up in the string file are problematic. The corresponding code in TableView for the ITEM we see in app rendering is as follows:
HStack{
Text("Item")
.frame(maxWidth:.infinity)
Text("Quantity")
.frame(maxWidth:.infinity)
Text("Unit Price")
.frame(maxWidth:.infinity)
Text("Amount")
.frame(maxWidth:.infinity)
}
.foregroundStyle(.primary)
.textCase(.uppercase) // Convert to uppercase
Copy the code
Text will use Item as the lookup Key, but we defined it as Item, so we didn’t find the corresponding value. Note: Keys in string files are small capitalization sensitive.
Modify the Chinese file as follows:
"Item" = "Kind";
"Quantity" = "The number";
"Unit Price" = "The price";
"Amount" = "Total";
Copy the code
At this point, in the preview window, we can see the result of The Localization:
Congratulations, you’ve got most of text localization covered by now.
I don’t know if you noticed, so farEnglish
The file was empty,Chinese
We only have localized text for four of the files. Anything we don’t set, the app will display the original text that we set in the code.
When defining in a string file, it is easy to make two mistakes: 1) enter Chinese punctuation incorrectly, and 2) forget the semicolon behind it.
Actual combat 2: The Chinese payment button
In this section we try to combine the text “Pay for 4 drinks” with Chinese culture.
This button is defined in the ButtonGroupView as follows:
Button {
showPayResult.toggle()
} label: {
Text("Pay for \(order.totalQuantity) drinks")}Copy the code
Pay for \(order.totalquantity) drinks how to set the corresponding key in Localizable. Strings file?
For localized Strings that use string interpolation, we need to use string format specifiers, and apple’s official documentation provides a detailed comparison.
In the code, order.totalQuantity corresponds to Int (Swift Int corresponds to Int64 on 64-bit systems), so we need to replace it with % LLD in the key-value pair. Make the following definitions in the Chinese file:
"Pay for %lld drinks" = "Pay for % LLD cup drinks";
Copy the code
So we get what we want. As you try to add or decrease the number of drinks, the number of drinks in the text changes.
Select the correct format specifier for your interpolation, as %d in the example above will be treated as another key and cannot be converted.
Actual combat 3: The program name of the Chinese App
In Xcode projects, we usually configure certain system parameters, such as Bundle identifier, Bundle name, etc., in the info. plist file. If we need to localize some of these configurations, we can use the infoplist.strings mentioned above
We create a string file named Infoplist. strings using the same steps used to create the Localizable. Strings file (don’t forget to localize the created file to make sure both Chinese and English are checked).
Add the following contents to the Chinese and English files of Infolist. strings respectively:
//chinese
"CFBundleDisplayName" = Fat Toot Toot.;
//english
"CFBundleDisplayName" = "FatbobBar";
Copy the code
At this point, install the APP on the simulator or real machine, and the name of the app will display the corresponding text in different languages.
In the last two versions of Xcode, it is not possible to set info.plist directly, usually viewing or modifying the value in Target’s Info
The configuration we need to localize doesn’t have to be in info or info.plist, as long as we localize the key-value pairs in InfoPlist. Strings, the app will take precedence. Normally we’ll do this in InfoPlist. Strings and in addition to the name of the app CFBundleDisplayName, There are CFBundleName, CFBundleShortVersionString, NSHumanReadableCopyright and various application description system permissions, Such as NSAppleMusicUsageDescription, NSCameraUsageDescription, etc. See the official documentation for more information about the info.plist parameter
Practice 4: Localize drink names
Add the following to the Localizable(Chinese) string file
"Orange Juice" = "Orange juice";
"Tea" = "Tea";
"Coffee" = "Coffee";
"Coke" = Happy Water;
"Sprite" = "Heart to heart.";
Copy the code
Check out the definition of beverageModel/Drink.swift
code
View the preview by setting local environment variables, either changing the emulator language to Chinese, or changing App Lanuguage to Chinese in Scheme.
We didn’t get the results we expected when we implemented the app. The name of the drink did not become Chinese. We can find out why by looking at drink. swift: Text does not treat a String as LocalizedStringKey if it is already specified.
Previously in ItemRowView, we displayed the drink name with the following code:
Text(item.drink.name)
.padding(.leading,20)
.frame(maxWidth:.infinity,alignment: .leading)
Copy the code
The name of a Drink is defined as follows in Drink
struct Drink:Identifiable.Hashable.Comparable{
let id = UUID(a)let name:String / / type String
let price:Double
let calories:Double
Copy the code
So the easiest way to do this is to modify the ItemRowView code
Text(LocalizedStringKey(item.drink.name))
.padding(.leading,20)
.frame(maxWidth:.infinity,alignment: .leading)
Copy the code
In some cases, we can only get String data, and we may often do similar conversions
Run it again and you will see that the drink name in the table has been changed to the correct Chinese display
Also modify the code in ItemListView:
/ / will be
Button(drink.name)
/ / to
Button(LocalizedStringKey(drink.name))
Copy the code
The list of drinks added is now displayed properly:
The modified code can normally display the Chinese name of the beverage.
The above method is a good way to solve problems in most cases, but it is not suitable to rely solely on Export Localizations… Generate a project to localize key-value pairs. Currently Xcode15 does not output text constructed using LocalizdStringKey.
In order to more accurately sort the localized text, we can also make a further change to Drink’s comparison function:
/ / will be
lhs.name < rhs.name
/ / to
NSLocalizedString(lhs.name,comment: "") < NSLocalizedString(rhs.name,comment: "")
Copy the code
NSLocalizedString can get the corresponding text value from the given text key
Will the InfoView
var list:String {
order.list.map(\.drink.name).joined(separator: "")}Copy the code
To:
order.list.map{NSLocalizedString($0.drink.name, comment: "")}.joined(separator: "")
Copy the code
Can’t we just do it
Drink
thename
Is defined asLocalizedStringKey
Type?Because the LocalizedStringKey does not support Identifiable, Hashable, Comparable agreement, also did not provide any official LocalizedStringKey converted into a String. Therefore, if we want to define name as LocalizedStringKey, we need to use some special means (through Mirror, which we won’t cover in this article).
Create a string dictionary file
Some problems that don’t exist in Chinese are big problems in other languages. Typical examples are complex numbers. If your app is only in English and needs to deal with fewer nouns, you can probably kill the plural rule in code. Such as:
if cups < = 1 {
cupstring = "cup"
}
else {
cupstring = = "cups"
}
Copy the code
However, this is not good for code maintenance, and it is not flexible for some languages with complex complex complex rules (such as Russian, Arabic, etc.).
To solve the problem of defining rules for complex numbers in different languages, Apple provides an alternative to strings: stringdict dictionary files.
It is a property list file with the extension of the.stringsdict file, and is treated exactly the same as editing any other property list (such as info.plist).
.stringsDict was originally developed to deal with complex numbers, but over the years it has been added to display different text for different values (often used for screen size variations) and for specific platforms (iPhone, iPad, MAC, TVOs).
In the figure above, we have formulated the use separatelyNSStringLocalizedFormatKey
The complex number rule,NSStringVariableWidthRuleType
Variable width rules as wellNSStringDeviceSpecificRuleType
Device-specific content rules
.stringdict has a root node called the Strings Dictionary, under which all our rules are built. We need to first build a Dictionary for each rule. In the figure above, the keys corresponding to the three rules are device % LLD, GDP, and Book % LLD cups. When the program encounters text content that meets these three key definitions, it uses its corresponding rules to generate the correct localized content.
So even though it looks a little different than strings, it actually has the same internal logic.
- We can make any number of rules in it.
- The default corresponding string dictionary file name
Localizable.stringsdict
. .stringdict
The execution priority of the.strings
For example, we are right in both filesGDP
If this parameter is defined, it will only be used.stringdict
Corresponding content
Lay down rules for complex numbers
-
The meaning of quantitative categories depends on the language, and not all languages have the same category.
For example, English only uses the one and other categories for plural forms. Arabic has different plural forms for categories zero, one, two, few, many, and other. Although Russian also uses the many category, the rules in the number many category are different from the Rules in Arabic.
-
All categories except other are optional.
However, if you do not provide rules for all specific language categories, your text may not be syntactically correct. Instead, if you provide a rule for a category that the language does not use, it is ignored and the other format string is used.
-
In a zero, one, two, what the things, other use NSStringFormatValueTypeKey format specifier in the format string is optional. For example, when the number is 1, the one cup is returned, and the corresponding % LLD does not need to be included
How to define complex rules in each language please see the Official UNICODE documentation
Variable width rule
Unlike complex and device rules, the system does not automatically match the return value and requires the user to explicitly annotate it when defining localized text, such as:
let gdp = (NSLocalizedString("GDP",comment: "") as NSString).variantFittingPresentationWidth(25)
Text(gdp) // Return GDP(Billon Dollor)
let gdp = (NSLocalizedString("GDP",comment: "") as NSString).variantFittingPresentationWidth(100)
Text(gdp) // Return anything you want to talk about
Copy the code
When there are no identical numbers, the nearest one is returned.
Its usage scenarios, I feel, are not irreplaceable. After all, there is more participation in the code.
Device specific rule
Currently, the following devices are supported: AppleTV, Apple Watch, iPad, iPhone, iPod, and MAC
The user does not need to intervene in the code, and the system will return the corresponding content based on the user’s hardware device
Actual Combat 5: Reset the payment button
Complete payment buttons with complex rules.
The code for the payment button is in ButtonView:
Button {
showPayResult.toggle()
} label: {
Text("Pay for \(order.totalQuantity) drinks")}Copy the code
We need to set “Pay for \(order. TotalQuantity) drinks”.
Start by creating the Localizable. Stringsdict file
For English, we need to set zero, one, and other. Set the following parameters in English:
In Chinese, just set zero and other
Adjust the order quantity, and the button will return the corresponding localized text according to the different language and different order quantity
In Actual Combat 2, we set key-value pairs for Pay for % LLD drinks in Localizable. Strings, but since.dict has a higher priority, NSStringPluralRuleType is preferred.
Actual Combat 6: Poke me or point me
The add drink button displays different content depending on the device.
For example, we can display TAP on iPhone and iPad, Select on AppleTV, and Click on MAC
Add in Chinese
Add in English
Formatter Formats the output
It’s not enough to just localize the display label. There are also a lot of numbers, dates, currencies, units of measure, names, and so on that need to be localized.
Apple has invested significant resources to provide developers with a complete solution, Formatter.
This year (2021), Apple will make further updates to Formatter that not only improve the ease of calling from Swift, but also allow developers to create bi-directional format conversions through the new FormatStyle protocol.
Formatter is too much to cover in a single article. A few examples in the Demo will give you a basic idea.
Actual Combat 7: Date, currency, percentage
The date of
Text(order.date,style: .date) // Displays the year, month and day
Text(order.date.formatted(.dateTime.weekday())) // Display the week
Copy the code
In the Demo we localized the date display in two ways.
-
Text itself supports formatted output of dates, although this is not very customizable.
-
The new FormatStyle is used to chain the output:
Order.date.formatted (.datetime.weekday ()) will display only the day of the week
currency
- Create the NumberFormatter
private func currencyFormatter(a) -> NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.maximumFractionDigits = 2
if locale.identifier ! = "zh_CN" {
formatter.locale = Locale(identifier: "en-us")}return formatter
}
Copy the code
The Demo only provides the prices of two currencies. If the region of the system is not set in mainland China, the currency will be set to USD.
- Apply Formatter to Text
Text(NSNumber(value: item.amount),formatter:currencyFormatter() )
Copy the code
Since Formatter can only be used with NSObject in Text, you need to convert Double to NSNumber.
FormatStyle currently provides too few configurable items for Currency.
The percentage
Text(order.tax.formatted(.percent))
Copy the code
Use formatStyle directly.
Practice 8: Units of measurement, sequence
calories
Use MeasureMent to define units of energy. A MeasureMent object represents a quantity and unit of MeasureMent. Measurement types provide a programming interface for converting measurements into different units and for calculating the sum or difference between two measurements.
init(name: String.price: Double.calories: Double) {
self.name = String.localizedStringWithFormat(NSLocalizedString(name, comment: name))
self.price = price
self.calories = Measurement<UnitEnergy>(value:calories,unit: .calories) // Set the raw data to calorie
}
Copy the code
The measurement object can also perform data calculation:
var totalCalories:Measurement<UnitEnergy>{
items.keys.map{ drink in
drink.calories * Double(items[drink] ?? 0)
}.reduce(Measurement<UnitEnergy>(value: 0, unit: .calories), +)}Copy the code
Create a Formatter describing MeasureMent
var measureFormatter:MeasurementFormatter{
let formatter = MeasurementFormatter()
formatter.unitStyle = .medium
return formatter
}
Copy the code
Displayed in SwiftUI
Text(order.totalCalories,formatter: measureFormatter)
Copy the code
The sequence
Create hyphenation patterns (punctuation, and or, etc.) that fit different language conventions.
var list:String {
order.list.map{NSLocalizedString($0.drink.name, comment: "")}.formatted(.list(type: .and))
}
Copy the code
other
Use tabName to specify a particular named string file
Multiple string files can be created, and when the file name is not Localizabl, we need to specify the file name, such as Other.strings
Text("Item",tableName: "Other")
Copy the code
TableName also works with.dict
Specify string files in other bundles
If your app uses other bundles that contain multilingual resources, you can specify string files from other bundles
import MultiLanguPackage // ML
Text("some text",bundle:ML.self)
Copy the code
In a Package containing multilingual resources, you can specify bundles using the following code
Text("some text",bundle:Self.self)
Copy the code
Markdown symbol support
Apple announced at WWDC 2021 that some markdown symbols can be used directly in Text. Such as:
Text("**Hello** *\(year)*")
Copy the code
We can also use the markdown symbol in string files
"**Hello** *%lld*" = "** hello ** *% LLD *";Copy the code
In addition, the new AttributedString type brings more creativity to the text.
conclusion
This article was originally part of my series on localization for iOS, but was never finished due to a lot of trivia.
Other topics, such as resource localization, localization debugging, localization preview, localization file editing, Formatter in-depth study, will be discussed together in the future.
I hope this article has been helpful to you.
The original article was posted on my blog www.fatbobman.com
My public number: [Swift Notepad for elbow]
Show Core Data in your app in Spotlight
SheetKit — SwiftUI Modal View Extension Library
Create apps that share data with multiple iCloud users