Swift的应用程式本地化提示
原始链接:https://marcosantadev.com/app-localization-tips-swift/
介绍
如果我们想在AppStore中吸引尽可能多的用户,则应该使用不同的语言本地化我们的Apps。 有时可能会增加我们代码的复杂性。 因此,我想分享一些技巧来改善我们的本地化处理。
祝您阅读愉快!
入门
在查看技巧之前,我们需要一个启用了本地化的项目。
第一步是在我们的项目中添加一个名为Localizable.strings
的新文件。 如果您不知道该怎么做,请观看以下视频:
该文件的语法为:
"" = "";
本地化字符串的示例可能是这样的:
"loading_data" = "Loading Data...";
"data_loaded" = "Data Loaded!";
启用本地化功能后,我们准备查看一些技巧来改善我们的本地化处理。
字符串扩展
如果要以编程方式使用本地化的字符串,则应使用功能NSLocalizedString
:
func NSLocalizedString(_ key: String, tableName: String? = default, bundle: Bundle = default, value: String = default, comment: String) -> String
-
key
:本地化字符串的data_loaded
,例如“Getting Started
”示例中的data_loaded
。 -
tableName
:要在其中搜索字符串的文件的名称。 表名是在其中搜索字符串的.strings
文件的名称。 如果我们忽略它,则默认值为Localizable
。 在“Getting Started
,我们只是为了说明而介绍了如何添加Localizable.string
。 我们可能会添加几个.strings
文件以干净的方式组织我们的字符串。 例如,如果我们创建一个文件Login.strings
其中将包含登录页面的本地化字符串),则可以将值Login
传递给tableName
以使用Login.strings
的字符串。 -
bundle
:包含strings
文件的包。 默认情况下,它是主要的。 -
value
:默认字符串,显示该函数是否找不到与key
关联的字符串。 -
comment
:genstrings
用于生成.strings
文件的字符串-您可以在此处找到有关该工具genstrings
更多详细信息。
NSLocalizedString
用法的一个示例可能是这样的:
let string = NSLocalizedString("data_loaded", comment: "") // Data Loaded!
上面的示例没有错,这很干净。 但是,我认为我们可以改进这种方法。 我们可以使用String
扩展来重构它:
extension String {
func localized(bundle: Bundle = .main, tableName: String = "Localizable") -> String {
return NSLocalizedString(self, tableName: tableName, value: "**\(self)**", comment: "")
}
}
如果找不到该字符串,则显示 ****
进行调试。
我们可以这样使用新的扩展名:
"data_loaded".localized() // Data Loaded!
"hello_world".localized() // **hello_world**
表名称处理
正如我们在String Extension
所看到的,我们可以有几个表名称来组织我们的字符串。
如果您 在整个项目中 仅使用 Localizable.strings
文件,建议您拆分子文件以清理字符串。
让我们考虑一下,我们有一个文件DataLoader.strings
,其中包含:
"loading_data" = "Loading Data...";
"data_loaded" = "Data Loaded!";
在此示例中,我们将有一个新的表名称DataLoader
。 然后,如果要使用这些字符串,则每次在String extension
localized
方法中插入表名时,都应插入:
"data_loaded".localized(tableName: "DataLoader") // Data Loaded!
这可能不是一个干净的方法,因为我们应该避免每次都插入表名。 我们可以使用每个表名称的枚举来清洁此方法,以存储我们的字符串。 这样,我们将拥有一种类型安全的方法来管理本地化的字符串,而不会将我们的代码库与硬编码的字符串弄混。
我们可以开始创建一个新的枚举DataLoaderStrings
,其中将包含DataLoaderStrings
所有字符串:
enum DataLoaderStrings: String {
case loadingData = "loading_data"
case dataLoaded = "data_loaded"
}
每个 case
都与我们的 strings
文件 的特定键相关联 。
然后,我们可以添加一个使用特定表名本地化这些字符串的方法:
enum DataLoaderStrings: String {
case loadingData = "loading_data"
case dataLoaded = "data_loaded"
var localized: String {
return self.rawValue.localized(tableName: "DataLoader")
}
}
通过这种方法,我们可以像这样使用本地化的字符串:
let string = DataLoaderStrings.loadingData.localized // Loading Data...
我们可以使用协议扩展来改进此方法:
protocol Localizable {
var tableName: String { get }
var localized: String { get }
}
// 1
extension Localizable where Self: RawRepresentable, Self.RawValue == String {
var localized: String {
return rawValue.localized(tableName: tableName)
}
}
enum DataLoaderStrings: String, Localizable {
case loadingData = "loading_data"
case dataLoaded = "data_loaded"
var tableName: String {
return "DataLoader"
}
}
- 仅当
self
是字符串并且符合RawRepresentable
时才扩展扩展名-枚举默认情况下符合此协议。
使用这种方法,我们必须仅在枚举中指定表名。 计算将在协议扩展中完成。
UIKit组件
有时我们通过Interface Builder在UIKit
组件(如UILabel
, UIButton
等)中添加字符串。 默认情况下,Interface Builder不允许我们在图形界面中本地化字符串。
例如,如果我们有一个UILabel
,并且在Interface Builder中设置了如下文本:
我们将无法本地化“数据已加载!”文本。
我们可以解决用UILocalizedLabel
UILabel
问题。 然后,我们可以在awakeFromNib
方法中以编程方式本地化文本:
final class UILocalizedLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
text = text?.localized()
}
}
localized
的方法 来自 前面解释 的 String
扩展。
使用这种方法,在Interface Builder中,我们必须将UILabel
对象类更新为UILocalizedLabel
:
最后,我们必须将标签文本替换为要本地化的字符串的键:
为了说明起见,我们使用了UILabel
。 我们可以将这种方法用于任何UIKit
组件,例如UIButton
:
final class UILocalizedButton: UIButton {
override func awakeFromNib() {
super.awakeFromNib()
let title = self.title(for: .normal)?.localized()
setTitle(title, for: .normal)
}
}
UITextField
:
final class UILocalizedTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
text = text?.localized()
}
}
等等…
注意
正如我们在Table Names Handling
所看到的,我们应尽可能使用表名称来清理.strings
文件。 不幸的是,在上面的示例中,我们无法在这些自定义UIKit
组件中设置表名。 我们可以轻松解决。
首先,我们必须在类中添加注释IBDesignable
:
@IBDesignable final class UILocalizedLabel: UILabel {
//...
}
然后,我们必须在我们的自定义类中添加带有注解IBInspectable
的新属性:
@IBInspectable var tableName: String?
最后,我们可以在此属性处添加didSet
观察器以使用新的表名值:
@IBInspectable var tableName: String? {
didSet {
guard let tableName = tableName else { return }
text = text?.localized(tableName: tableName)
}
}
因为 tableName
是可选的 String
所以 我们在 guard
内部使用了可选的绑定 。
最后,我们的UILocalizedLabel
应该是这样的:
@IBDesignable final class UILocalizedLabel: UILabel {
@IBInspectable var tableName: String? {
didSet {
guard let tableName = tableName else { return }
text = text?.localized(tableName: tableName)
}
}
}
通过这种方法,我们在接口构建器中为自定义UILabel
组件添加了一个新字段,可以在其中设置tableName
的值:
多元化
有时我们需要根据输入值处理具有单数和复数变体的字符串。 例如,我们可能There is one file
而There are 2 files
。
我们可以使用两个单独的字符串处理这些变体,并在if / else语句中使用它们:
if numFiles == 1 {
return "There is one file"
} else if numFiles > 1 {
return "There are \(numFiles) files"
}
这种方法不是很好,因为它增加了我们管理代码变体的代码库的复杂性。 幸运的是,Xcode提供了更好的解决方案来处理多元化。
首先,我们必须在项目中添加一个新文件Localizable.stringsdict
。 我们可以按照Localizable.strings
的相同步骤添加它,唯一的区别是我们必须添加一个新的Property List
文件而不是Strings File
。
Property List
文件 的默认扩展名 是 .plist
。 我们必须将其重命名为 stringsdict
。
为了便于说明,我们使用表名称Localizable
。 我们可以为任何表名添加复数形式,例如DataLoader.stringsdict
。
如果打开新文件,则应具有以下内容:
现在,我们可以添加所有句子以进行多元化。 该文件具有特定的语法,其结果将如下所示:
- 要本地化的字符串的键-两个
%d
是我们稍后将设置的值的占位符。 - 本地化字符串的格式。
%#@@
是占位符键的语法。 - 占位符键。 对于点2的每个占位符,我们必须重复步骤3–4–5–6。在此示例中,我们有两个占位符:
to_be
和num_files
。 - 密钥的类型始终为
NSStringPluralRuleType
。 - 输入中接收到的值的类型。 在这种情况下,我们有一个整数(%d)。 您可以在此处找到格式列表。
- 字符串的变体。 我们可以使用:
zero
,one
,two
,few
,many
other
。 每种语言可能具有使用这些键的不同复数规则。 您可以在此处阅读有关语言复数规则的更多详细信息。
然后,我们可以像这样使用这个复数字符串:
let countFiles = 1
let formatString = "pending_files_%d_%d".localized()
let result = String(format: formatString, countFiles, countFiles) // There is one file
localized
的方法 来自 前面解释 的 String
扩展。
为了便于说明,我们将此字符串与两个占位符一起使用。 占位符的使用没有限制。
结论
这些是我经常在辅助项目中组织本地化的方法。 随时添加提示与您的提示。 谢谢!