独立开发之 App 国际化全步骤(六):替换代码中使用的文本

2,026 阅读6分钟

关于我:大厂摸鱼 + 业余独立开发,之后会输出深度技术文章 + 独立开发技巧

我的往期技术文章合集:RickeyBoy - Gitbub

我的独立开发 App:iColors - 设计灵感 配色助手

上一篇:

独立开发之 App 国际化全步骤(一):为什么要做国际化

独立开发之 App 国际化全步骤(二):App 数据翻译

独立开发之 App 国际化全步骤(三):Core Data 模型升级

独立开发之 App 国际化全步骤(四):Core Data 模型解析

独立开发之 App 国际化全步骤(五):提取 App 中文本文案

📡 替换代码中使用的文本

好的,前文我们提取了一些带翻译的文件,具体翻译的过程我们就跳过了。现在假设我们能够通过某种方式得到具体的翻译后的结果。

那么在此基础之上,我们需要先优化一下 Localizable.strings 的声明方式

Localizable.strings 使用专门的 Key

上一部分我们在 Localizable.strings 文件中增加了对应的翻译,如下图所示:

// Localizable (Chinese, Simplified)
"色彩主题" = "色彩主题";
// Localizable (English)
"色彩主题" = "Editor's Pick";

但是这样有一个小问题,就是对于中文(之前的默认语言)来讲,无法区分是否是处理过的翻译。比如我现在在代码中有一个类似的使用之处,我并没有办法一眼识别出,这是否是经过多语言处理过的。

Text("首页")

以及另外一个问题,我们有些时候会存在同样的中文,却因为具体使用场景不同,而需要对应不同的英文。比如同样是"会员",有些可能需要根据使用场景、UI 界面等不同因素,翻译成 "vip" 或者是 "membership"。这样如果直接用中文本身作为 key,就会产生问题。

所以,我们最好的方式还是使用单独的 Key,这样可以更规范和更准确。比如下面这样:

// Localizable (Chinese, Simplified)
"SOME_KEY" = "色彩主题";
// Localizable (English)
"SOME_KEY" = "Editor's Pick";

这样,我们需要手动去替换一下使用就可以了。

Text("SOME_KEY")

至于这里使用的 KEY,只需要保证唯一性就可以了,与此同时如果能有一定的字面意义会更好。具体的生成可以交给 ChatGPT,能够节约不少的时间。

在代码中使用 Key

好的,接下来就是我自己认为最爽的时刻了,那就是替换代码中文本文案的使用。首先,如果是 Text、Button 等系统提供的组件,是可以直接接受 Key 的:

// 直接使用 key
Text("SOME_KEY")
Button("SOME_KEY", role: .cancel) { ... }

我们想想,为什么这里直接使用 "SOME_KEY",展示的是多语言结果,而不是直接将 "SOME_KEY" 作为字符串展示出来呢?

来自 SwiftUI 文档的解释:

When you use the initializer Text("Hello"), SwiftUI creates a LocalizedStringKey for you and uses that to look up a localization of the Hello string. This works because LocalizedStringKey conforms to ExpressibleByStringLiteral.

能做到这一点,本质上是因为这些组件都默认实现了通过 LocalizedStringKey 作为参数进行初始化,SwiftUI 会自动通过 "SOME_KEY" 创建 LocalizedStringKey 实现多语言,而不是使用普通的 String。

extension Text {
    public init(_ key: LocalizedStringKey, tableName: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil)
}

没错,这里的 "SOME_KEY" 会被创建为 LocalizedStringKey 类型,而不是 String 类型!

理解了这一点,相应的我们如果自定义一个 CustomView,就需要使用 LocalizedStringKey 而不是 String

// 声明
struct CustomView: View {
    let title: LocalizedStringKey

    var body: some View {
        Text(title)
    }
}

当然我们也可以手动将 String 转变为 LocalizedStringKey,比如像下面这样声明

// 声明
struct CustomView: View {
    let some_key: String

    var body: some View {
        Text(LocalizedStringKey(some_key))
    }
}

检查遗漏的翻译

好了,当我们替换了所有多语言的使用之后,现在来解决之前遗留的一个问题:如果发现忘记进行多语言的文本文案?

此时我们统一使用 "SOME_KEY" 的好处就体现出来了,我们只需要检查没有直接使用中文字符串的地方即可,因为正确情况下,我们所有的代码中都不会直接出现中文字符串,只会出现 "SOME_KEY"!

下面就是检查脚本,如果有遗漏的翻译,就会被直接打印出来。原理很好理解,我会用注释尽量讲清楚:

import os
import unicodedata
​
project_directory = "xxxxx"# 判断字符串中是否包含中文
def contains_chinese_characters(string):
    for char in string:
        if 'CJK' in unicodedata.name(char, ''):
            return True
    return False# 判断是否是注释内容,如果是注释内容的话可以忽略
def is_comment(line):
    line = line.strip()
    return line.startswith("//") or line.startswith("/*") or ("//" in line and contains_chinese_characters(line.split("//")[1]))
​
# 用于统计结果
violations = []
​
# 遍历所有文件
for root, _, files in os.walk(project_directory):
    for file in files:
        if file.endswith(".swift"):
            file_path = os.path.join(root, file)
            with open(file_path, "r") as f:
                lines = f.readlines()
                for line_number, line in enumerate(lines, start=1):
                    # 如果不是注释,且包含中文,就记录下来
                    if not is_comment(line) and contains_chinese_characters(line):
                        violations.append((file_path, line_number, line.strip()))
​
# 输出
if violations:
    print("Violations:")
    for violation in violations:
        file_path, line_number, line = violation
        print(f"File: {file_path}, Line: {line_number} - String: {line}")
else:
    print("No violations found.")

这样,我们遗漏的部分就能被轻松找出来了。比如会输出:

File: /.../xxxx.swift, Line: 38 - String: AlertToast(type: .regular, title: "保存失败")

查看对应的代码,是我直接使用第三方组件的部分,之前就没有被自动提取出来:

// 弹出失败提醒
AlertToast(type: .regular, title: "保存失败")

Tips:全局替换多余的 LocalizedStringKey

我们之前提到,SwiftUI 能够自动识别出 "SOME_KEY",并帮助我们创建 LocalizedStringKey,从而实现多语言。不需要手动进行额外的创建。这一个优势不仅包括 Text、Button,也包括 navigationTitle、toggle 等,也都是可以自动识别的。

// 没必要
Text(LocalizedStringKey("SOME_KEY"))
// ✅
Text("SOME_KEY")

这样就可以大大简化我们的代码。不过可能有一些开发者,在最开始实现的时候并不知道这一个技巧,或者现在读了文章之后才知道这个技巧,那该怎么办呢,难道要一个一个地方手动修改吗?

当然我们还有更好的办法,就是利用 Xcode 的正则匹配搜索,来进行批量的替换!

TranslateAppStrings7.png

如图所示就可以了,两个正则表达式也很好理解,我直接贴在这里,供大家参考

// Text(LocalizedStringKey("SOME_KEY"))
Text(LocalizedStringKey("(.+?)"))
// Text("SOME_KEY")
Text("$1")

本篇讲解了如何将翻译后的内容嵌入在代码中,实操内容比较多!下一篇会继续讲解如何实现语言切换