简单尝试
- 首先先创建一个叫
Loc
的iOS
空项目,创建好后目录结构如下:
- 创建一个
Language
的文件夹在Loc
目录下, 然后在Language
目录下创建一个叫Localizable
的String File(Legacy)
文件, 创建好后,我们show in finder
可以看到Xcode
自动帮我们创建了一个叫en.lproj
的文件夹
这是因为项目默认选择的语言是英文,可以在下图中查看,如果你设置的是中文,生成的就是
zh-Hans.lproj
文件夹- 添加支持新的语言, 比如我们这个项目要同时支持简体中文和英文,这时我们只需要在下图点+按钮选择简体中文就好
完成后可以看到Language->Localizable
中有两个配置文件,分别对应英文和简体中文。 Xcode
的Localizations
中也可以看到有英文和简体中文的配置了
- 在配置文件中添加对应字符串,我们在
Localizable(English)
中添加"personal_name" = "英文-san zhang";
,在Localizable(Chinese, Simplified) 中添加"personal_name" = "简体中文-张三";
- 现在我们可以使用了, 将ContentView中代码改成如下:
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text(NSLocalizedString("personal_name", comment: ""))
}
.padding()
}
}
那么运行就会得到下图结果,左边英文,右边中文
我们看到会根据不同的系统语言去相应的配置中去取对应的内容,这里我们的项目就简单支持多语言了。
NSLocalizedString
- 上面我们简单在配置文件中为不同语言配置了内容,然后
NSLocalizedString
会根据不同语言加载不同配置文件中的内容,那么NSLocalizedString
做了什么呢?
/// Returns the localized version of a string.
///
/// - parameter key: An identifying value used to reference a localized string.
/// Don't use the empty string as a key. Values keyed by the empty string will
/// not be localized.
/// - parameter tableName: The name of the table containing the localized string
/// identified by `key`. This is the prefix of the strings file—a file with
/// the `.strings` extension—containing the localized values. If `tableName`
/// is `nil` or the empty string, the `Localizable` table is used.
/// - parameter bundle: The bundle containing the table's strings file. The main
/// bundle is used by default.
/// - parameter value: A user-visible string to return when the localized string
/// for `key` cannot be found in the table. If `value` is the empty string,
/// `key` would be returned instead.
/// - parameter comment: A note to the translator describing the context where
/// the localized string is presented to the user.
///
/// - returns: A localized version of the string designated by `key` in the
/// table identified by `tableName`. If the localized string for `key` cannot
/// be found within the table, `value` is returned. However, `key` is returned
/// instead when `value` is the empty string.
....
@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *)
public func NSLocalizedString(_ key: String, tableName: String? = nil, bundle: Bundle = Bundle.main, value: String = "", comment: String) -> String
- 上面是NSLocalizedString的API, 注释有很多,我这里只保留了参数的注释。
这里说明以下几点:
- 如果找不到对应
key
的value
, 就会从value
参数中取,如果value
参数中也去不到,就会将key
作为value
来显示 - 如果
tableName
没有设置,就会从Localizable
table里查找 - 默认使用
main bundle
里的配置资源,如果在其它bundle
,这里bundle
不能省略,需要设置对应的bundle
,比如语言配置可以在一个package
里,这时就需要将bundle
设置为这个package
的bundle
了 - 不能使用空字符串作
key
-
comment
是对应说明
优化
- 有了对
NSLocalizedString
的了解,我们就可以做一些优化。比如我们可以String
加上一个分类,来优化我们取值, 具体做法: 首先我们新建一个String+Localizing
的文件来给String
添加一个fetch
的方法:
import Foundation
public extension String {
/// - Parameter localizedStringKey: Key for the localized string.
/// - Returns: The localized string associated with the key.
static func fetch(_ localizedStringKey: String) -> String {
return NSLocalizedString(
localizedStringKey,
comment: ""
)
}
}
这样我们就可以在ContentView
使用了,使用String.fetch("personal_name")
了和NSLocalizedString("personal_name", comment: "")
效果是一样的
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text(NSLocalizedString("personal_name", comment: ""))
Text(String.fetch("personal_name"))
}
.padding()
}
}
- 这样直接使用
key
值我们很容易写错,所以我们可以定义一个枚举,把这些key
值定义成枚举里的变量, 比如我们新建一个叫Localizable.swift
的文件,在这个文件里我们定义一个Localizable
的枚举,然后定义一个叫personalName
的静态变量如下:
public enum Localizable {
/// ...
public static let personalName = "personal_name"
}
这样我们就可以使用String.fetch(Localizable.personalName)
取获取文本了,和上面两种使用效果是一样的
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text(NSLocalizedString("personal_name", comment: ""))
Text(String.fetch("personal_name"))
Text(String.fetch(Localizable.personalName))
}
.padding()
}
}
- 既然
public func NSLocalizedString(_ key: String, tableName: String? = nil, bundle: Bundle = Bundle.main, value: String = "", comment: String) -> String
支持传递tableName
,那么我们也可以制作多个table
将我们的文本根据一定的规则进行分类,这样可以将文本分散到不同的table
中,防止所有文本都存放到一个table
里面导致table
内容过多.
比如,我们新建一个LocalizableGeneral.strings
的文件
不过这次我们看到Xcode
左侧文件导航里并没有像Localizable
那样帮我们分成Localizable(English)
和Localizable(Chinese, Simplified)
两个文件,我们show in finder
中看到只在en.lproj
目录下多了一个LocalizableGeneral.strings
文件,而zh-Hans.lproj
中并没有
查看Xcode
的配置看到也是只在English
下多了一个文件,而中文下还是1个
不过这没关系,我们可以把en.lproj
目录下的LocalizableGeneral.strings
复制一份到zh-Hans.lproj
目录下,然后将LocalizableGeneral.strings
拖动到Xcode LocalizableGeneral
文件下,这样Xcode 自动帮我们分成了LocalizableGeneral(English)
和 LocalizableGeneral(Chinese,Simplified)
然后我们分别在LocalizableGeneral(English)
和 LocalizableGeneral(Chinese,Simplified)
定义好"personal_name_general"
对应的文本
然后我们再创建一个LocalizableGeneral.swift
的文件,内容如下:
public enum LocalizableGeneral {
/// ...
public static let personalNameGeneral = "personal_name_general"
}
然后在String
的分类中再添加一个fetchGeneral
的方法,在文件中添加localizableGeneralTableName
变量名,注意这个变量对应内容要和创建的LocalizableGeneral
文件名一致, static func fetch(_ localizedStringKey: String) -> String
中的实现没有添加tableName
的原因是,如果不添加默认会到Localizable
中查找
import Foundation
private let localizableTableName = "Localizable"
private let localizableGeneralTableName = "LocalizableGeneral"
public extension String {
/// - Parameter localizedStringKey: Key for the localized string.
/// - Returns: The localized string associated with the key.
static func fetch(_ localizedStringKey: String) -> String {
return NSLocalizedString(
localizedStringKey,
comment: ""
)
}
static func fetchGeneral(_ localizedStringKey: String) -> String {
return NSLocalizedString(
localizedStringKey,
tableName: localizableGeneralTableName,
comment: ""
)
}
}
这样我们就可以在ContentView
中使用了
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text(NSLocalizedString("personal_name", comment: ""))
Text(String.fetch("personal_name"))
Text(String.fetch(Localizable.personalName))
Text(String.fetchGeneral(LocalizableGeneral.personalNameGeneral))
}
.padding()
}
}
运行得到如下结果:
- 除了上面这种,我门还可以把文本分散到
Localizable
的分类中。比如,我们创建一个LocalizableError.strings
的配置文件,方式同上面LocalizableGeneral.strings
文件一样, 然后再创建一个LocalizableError.swift
文件,对应文件内容如下:
不过这次我们要修改一下fetch
方法,因为目前fetch
方法只会去Localizable
配置文件中去找,但我们配置在LocalizableError
中,这样就会导致照不到,fetch
方法具体修改如下:
/// 首先去Localizable配置文件中根据localizedStringKey 查找,如果找不到就会使用value的值,而value的值就会去LocalizableError这个配置文件中去找
static func fetch(_ localizedStringKey: String) -> String {
return NSLocalizedString(
localizedStringKey,
value: NSLocalizedString(
localizedStringKey,
tableName: localizableErrorTableName,
comment: ""),
comment: ""
)
}
这样我们可以在ContentView
中使用了
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text(NSLocalizedString("personal_name", comment: ""))
Text(String.fetch("personal_name"))
Text(String.fetch(Localizable.personalName))
Text(String.fetchGeneral(LocalizableGeneral.personalNameGeneral))
Text(String.fetch(Localizable.personalNameError))
}
.padding()
}
}
运行得到下面结果:
app 名 和 申请权限提示的配置
- 除了上面文本的显示,多语言还涉及到不同语言环境显示的app 名称也不一样,另外还有访问权限的提示也不一样。这两个问题都是在Info.plist文件中设置的,解决这两个问题就需要我们创建一个名为InfoPlist.strings的文件,
注意:名字一定要是InfoPlist,别的名字Xcode不认
。
名字的话我们在不同语言配置文件中设置CFBundleDisplayName key的value就好,权限的话也是一样,根据不同的语言环境给相应权限的key设置value。 下面我们设置的是简体中文和英文环境下,app 名字 和 网络请求提示的配置。