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关联的字符串。
  • commentgenstrings用于生成.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"
}
}
  1. 仅当self是字符串并且符合RawRepresentable时才扩展扩展名-枚举默认情况下符合此协议。

使用这种方法,我们必须仅在枚举中指定表名。 计算将在协议扩展中完成。

UIKit组件

有时我们通过Interface Builder在UIKit组件(如UILabelUIButton等)中添加字符串。 默认情况下,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 fileThere 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

如果打开新文件,则应具有以下内容:

现在,我们可以添加所有句子以进行多元化。 该文件具有特定的语法,其结果将如下所示:

  1. 要本地化的字符串的键-两个%d是我们稍后将设置的值的占位符。
  2. 本地化字符串的格式。 %#@@是占位符键的语法。
  3. 占位符键。 对于点2的每个占位符,我们必须重复步骤3–4–5–6。在此示例中,我们有两个占位符: to_benum_files
  4. 密钥的类型始终为NSStringPluralRuleType
  5. 输入中接收到的值的类型。 在这种情况下,我们有一个整数(%d)。 您可以在此处找到格式列表。
  6. 字符串的变体。 我们可以使用: zeroonetwofewmany other 。 每种语言可能具有使用这些键的不同复数规则。 您可以在此处阅读有关语言复数规则的更多详细信息。

然后,我们可以像这样使用这个复数字符串:

 let countFiles = 1 
let formatString = "pending_files_%d_%d".localized()
 let result = String(format: formatString, countFiles, countFiles) // There is one file 

localized 的方法 来自 前面解释 String 扩展。

为了便于说明,我们将此字符串与两个占位符一起使用。 占位符的使用没有限制。

结论

这些是我经常在辅助项目中组织本地化的方法。 随时添加提示与您的提示。 谢谢!