iOS 支持多语言

简单尝试

  • 首先先创建一个叫LociOS 空项目,创建好后目录结构如下:
  • 创建一个Language的文件夹在Loc目录下, 然后在Language目录下创建一个叫 LocalizableString File(Legacy)文件, 创建好后,我们show in finder可以看到Xcode 自动帮我们创建了一个叫 en.lproj的文件夹


这是因为项目默认选择的语言是英文,可以在下图中查看,如果你设置的是中文,生成的就是zh-Hans.lproj文件夹

  • 添加支持新的语言, 比如我们这个项目要同时支持简体中文和英文,这时我们只需要在下图点+按钮选择简体中文就好

完成后可以看到Language->Localizable中有两个配置文件,分别对应英文和简体中文。 XcodeLocalizations 中也可以看到有英文和简体中文的配置了

  • 在配置文件中添加对应字符串,我们在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, 注释有很多,我这里只保留了参数的注释。
    这里说明以下几点:
  1. 如果找不到对应keyvalue, 就会从value参数中取,如果 value参数中也去不到,就会将key作为value来显示
  2. 如果tableName没有设置,就会从Localizable table里查找
  3. 默认使用main bundle里的配置资源,如果在其它bundle,这里bundle不能省略,需要设置对应的bundle,比如语言配置可以在一个package里,这时就需要将bundle设置为这个packagebundle
  4. 不能使用空字符串作key
  5. 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()
    }
}
image.png
  • 既然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)

或者: 我们直接选中文件,然后在右边的 Show the File inspector 中找到 Localization 栏, 把对应需要支持的 Chinese,Simplified 选上,这样Xcode 会自动帮我们生成对应的文件了

选中后

然后我们分别在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 名字 和 网络请求提示的配置。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • APP语言本地化,即语言国际化。指的是根据用户操作系统的语言设置,自动将APP的语言设置为和操作系统一致的语言环境...
    喔牛慢慢爬阅读 5,769评论 0 11
  • 欢迎访问我的博客muhlenXi,该文章出自我的博客。 版权声明:本文为muhlenXi原创文章,转载请注明出处,...
    卖码维生阅读 3,307评论 0 12
  • ** 原文发表在:https://www.xiaolei0808.com/2016/04/24/Localized...
    金小白先生阅读 17,621评论 16 90
  • 根据当前设备语言自动切换显示。 几个涉及到多语言本地化设置的: 1.应用名称 2.文字 3.图片、素材 4.Sto...
    齐玉婷阅读 3,565评论 2 3
  • 相关问题 国际化官网链接 Infoplist.strings为什么可以替换info.plist的名称?什么时机替换...
    wxkkkkk阅读 2,598评论 0 1