本文以及相关东西和代码旨在为已上线的iOS项目供给一种快速支撑多言语的解决计划。因为案牍显现是经过hook完结的,因而对App的功能有一定影响;除了特别场景的案牍显现需求手动支撑外,其他使命均已完结主动化。

本文中的部分脚本代码依据 ChatGPT4.0 和 Github Copilot 完结,案牍翻译经过 Cloud Translation API 进行。若条件允许,建议进行人工二次承认以保证翻译质量。

关于大多数场景下,App向用户展现的案牍或资料一般来自接口下发或本地代码内置(hardcode)。本文将要点介绍hardcode中的国际化解决计划。

全体的解决思路如下,以供咱们参阅:

iOS多语言解决方案全面指南

一、本地hardcode案牍搜集及翻译

1、案牍搜集

1.1、源码查找搜集

搜集案牍最简略的办法便是全局代码查找。关于一个工程办理良好的项目,这个过程或许相对简略。可是,关于包括195个模块的 京喜特价 这样的项目,特别是那些以二进制办法集成到主工程的项目,这将耗费大量的时刻。

为了解决这个问题,咱们能够运用一些主动化东西来辅佐咱们完结案牍搜集的作业。

Step1、正则查找替换

运用正则表达式查找并替换客户端hardcode案牍:

Objective-C 代码中:查找条件里输入 (@"[^"]*[\u4E00-\u9FA5]+["\n]*?")
替换内容里输入 NSLocalizedString($0, nil) 
​
Swift 代码中:查找条件里输入("[^"]*[\u4E00-\u9FA5]+["\n]*?")底部挑选过滤出 .swift 文件
替换内容里输入 NSLocalizedString($0, comment: "") 

正则查找查询:

iOS多语言解决方案全面指南

替换作用如下:

iOS多语言解决方案全面指南

在本文中,咱们供给了一种运用正则表达式查找并替换客户端hardcode案牍的办法。这种办法在 Objective-C 代码中是有用的。可是,在 Swift 代码中,您需求运用不同的正则表达式进行查找。详细操作如下:

  1. 在查找条件中输入 ("[^"]*[\u4E00-\u9FA5]+[^"\n]*?")
  2. 在底部的文件挑选输入 .swift 进行过滤。

这样,您就能够在 Swift 代码中找到一切包括中文字符的字符串字面量,并运用 NSLocalizedString 进行替换,以完结多言语支撑

iOS多语言解决方案全面指南

替换前后对比:

// 替换前:
@objc func log() {
  print("Swift 中")
  print("Swift En")
  
  print("是为 中")
}
​
// 替换后:
@objc func log() {
  print(NSLocalizedString("Swift 中", comment: "") )
  print("Swift En")
  
  print(NSLocalizedString("是为 中", comment: "") )
}
Step2、生成言语包

搜集客户端hardcode中的中文并生成国际化言语包:

# 1、在项目的根途径下
➜  HDTranslateModule git:(main) ✗ tree -L 1
.
├── Assets
├── Example
├── HDTranslateModule
├── HDTranslateModule.podspec
├── LICENSE
├── README.md
└── _Pods.xcodeproj -> Example/Pods/Pods.xcodeproj
​
4 directories, 3 files
​
# 2、创立 en.lproj 多言语文件夹,保存多言语包
➜  HDTranslateModule git:(main) ✗ mkdir en.lproj
​
# genstrings东西用于从Objective-C或Swift源代码文件中提取本地化字符串。
# 3、因为我的测验代码在 Example/HDTranslateModule 中, 履行如下指令 从 .m  或许 .swift 文件中找到 本地化字符串
➜  HDTranslateModule git:(main) ✗ find Example/HDTranslateModule ( -name "*.m" -o -name "*.swift" ) -print0 | xargs -0 genstrings -o en.lproj

成功后,会在 en.lproj 途径下生成 Localizable.strings 文件:

/* No comment provided by engineer. */
"Swift 中" = "Swift 中";
​
/* No comment provided by engineer. */
"中文" = "中文";
​
/* No comment provided by engineer. */
"分类缓存是时刻阀值:%d,当时缓存时刻:%d" = "分类缓存是时刻阀值:%1$d,当时缓存时刻:%2$d";
​
/* No comment provided by engineer. */
"弹窗组件页面" = "弹窗组件页面";
​
/* No comment provided by engineer. */
"我也是服务器数据" = "我也是服务器数据";
​
/* No comment provided by engineer. */
"我是服务器数据" = "我是服务器数据";
​
/* No comment provided by engineer. */
"是为 中" = "是为 中";
​
/* No comment provided by engineer. */
"更新资质合规:%@" = "更新资质合规:%@";
​
/* No comment provided by engineer. */
"第%d张图片不契合标准,请替换或删去后从头上传" = "第%d张图片不契合标准,请替换或删去后从头上传";
​
/* No comment provided by engineer. */
"英文" = "英文";
​
/* No comment provided by engineer. */
"订单编号:%@" = "订单编号:%@";

现在,您现已成功生成了国际化言语包 Localizable.strings。接下来,您能够将其翻译成其他言语,并将其放入相应的 .lproj 文件夹中,以完结多言语支撑。


多个 Localizable.strings 文件兼并问题

genstrings 东西会在指定的输出目录中生成一个新的 Localizable.strings 文件,假如该文件现已存在,genstrings 会掩盖它。因而,你不能直接运用 genstrings 将数据追加到现有的 en.lproj/Localizable.strings 文件中。

问: xargs genstrings -o en.lproj 怎样追加数据到 en.lproj 文件中

答(解决计划由 HiBox 中的GPT4.0 供给)

genstrings 东西会在指定的输出目录中生成一个新的 Localizable.strings 文件,假如该文件现已存在,genstrings 会掩盖它。因而,你不能直接运用 genstrings 将数据追加到现有的 en.lproj/Localizable.strings 文件中。

可是,你能够运用以下办法将新生成的 Localizable.strings 文件与现有的文件兼并:

1、运用 genstrings 将新的本地化字符串输出到一个临时目录,例如temp.lproj:

# 本地化字符串写入在 en.lproj/Localizable.strings 文件中
➜  HDTranslateModule git:(main) ✗ find Example/HDTranslateModule -name "*.m" | xargs genstrings -o en.lproj
​
# swift 文件本地化字符串写入在  temp.lproj/Localizable.strings 文件中
➜  HDTranslateModule git:(main) ✗ mkdir temp.lproj
➜  HDTranslateModule git:(main) ✗ find Example/HDTranslateModule -name "*.swift" | xargs genstrings -o temp.lproj

2、运用 iconven.lproj/Localizable.stringstemp.lproj/Localizable.strings 文件转化为UTF-8编码,并将成果保存到临时文件:

✗ iconv -f UTF-16 -t UTF-8 en.lproj/Localizable.strings > en.lproj/Localizable.strings.utf8
✗ iconv -f UTF-16 -t UTF-8 temp.lproj/Localizable.strings > temp.lproj/Localizable.strings.utf8

3、运用awk指令将两个UTF-8编码的文件兼并,并删去重复的键:

✗ awk -F= -v OFS== '!_[$1]++' en.lproj/Localizable.strings.utf8 temp.lproj/Localizable.strings.utf8 > en.lproj/Localizable.strings.merged.utf8

4、运用 iconv 将兼并后的UTF-8编码的文件转化回UTF-16编码,并将成果保存到 en.lproj/Localizable.strings 文件:

✗ iconv -f UTF-8 -t UTF-16 en.lproj/Localizable.strings.merged.utf8 > en.lproj/Localizable.strings

5、删去临时文件:

rm en.lproj/Localizable.strings.utf8 temp.lproj/Localizable.strings.utf8 en.lproj/Localizable.strings.merged.utf8
✗ rm -r temp.lproj

总结: 这个计划优点便是在 Objective-C、Swift 源码工程中将查找案牍、运用国际代码(NSLocalizedString)一次性解决,而且输出 多言语包,即 Localizable.strings 文件。


1.2、Mach-O查找搜集

Step1、Section- Segment查找

在这个场景中,咱们将运用 Hopper Disassembler 东西来剖析字符串在哪个 Section 中的哪个 Segment 中。Hopper Disassembler 是一个反汇编和反编译东西,能够协助咱们更好地理解二进制文件的结构和功能。

  • 首要,翻开 Hopper Disassembler 并加载你想要剖析的 Mach-O 二进制文件。一般,这是一个 iOS 或 macOS 运用程序的首要可履行文件。
  • 在 Hopper 的左边窗格中,你会看到一个名为 “Segments and Sections” 的面板。这个面板显现了二进制文件中的一切 Segment 和 Section。
  • 经过打开 Segment,你能够查看它们包括的各个 Section。一般,字符串数据存储在 __TEXT Segment 的 __cstring__objc_methname Section 中。__TEXT Segment 包括了程序的只读数据,如代码和字符串常量。
  • 单击你感兴趣的 Section,Hopper 将在右侧窗格中显现该 Section 的内容。你能够在这儿查看和查找字符串数据。
  • 假如你想要查找特定的字符串,能够运用 Hopper 的查找功能。在顶部菜单栏中,挑选 “Edit” > “Find”,然后输入你要查找的字符串。Hopper 将高亮显现一切匹配的成果。

经过这种办法,你能够找到字符串在 Mach-O 二进制文件中的确切位置。这关于剖析和调试运用程序十分有用,特别是当你需求了解字符串是如安在程序中运用的。

场景1(查找 __cstring 中的字符串):

iOS多语言解决方案全面指南

场景2(查找 __ustring 中的字符串):

iOS多语言解决方案全面指南

能够发现在 Mach-O 文件中寄存 字符串首要是在 __TEXT __cstring__TEXT __ustring 中。


问:请你总结一下 cstring 仍是 ustring 散布寄存哪些内容?最好有代码举例说明。

答(解决计划由 HiBox 中的GPT4.0 供给):

Objective-C 中,cstring 和 ustring 节别离用于存储 C 风格的字符串常量和 NSString 目标的字符串常量。以下是两者的区别和代码示例:

__cstring 节:存储 C 风格的字符串常量,即以 ‘\0’ 结束的字符数组。

const char *cString = "Hello, World!";

在这个比如中,”Hello, World!” 是一个 C 风格的字符串常量,它将被存储在 __cstring 节中。

__ustring 节:存储 NSString 目标的字符串常量。

NSString *nsString = @"Hello, World!";

在这个比如中,”Hello, World!” 是一个 NSString 目标的字符串常量,它将被存储在 __ustring 节中。

需求留意的是,编译器或许会对字符串常量进行优化,因而在某些情况下,即便您运用的是 NSString 目标,字符串常量依然或许被存储在 cstring 节中。这种优化取决于编译器的完结,因而在不同的编译器或编译选项下,成果或许会有所不同。在实践编程中,您一般不需求关心字符串常量是存储在 cstring 仍是 __ustring 节中,因为这是编译器内部的优化细节。您只需求重视怎样正确地运用字符串即可。

问:Swift也是相似的寄存规则吗?

答:

是的,Swift 中的字符串常量存储规则与 Objective-C 相似。在 Swift 中,字符串常量一般存储在程序的常量区域,这个区域相似于 Objective-C 中的 __ustring 节。这有助于优化内存运用和进步程序运转功率。

Swift 中,字符串常量是经过 String 类型表明的。例如:

let swiftString = "Hello, World!"

在这个比如中, “Hello, World!” 是一个字符串常量,它将被存储在相似于 Objective-C 中的 __ustring 节的内存区域中。需求留意的是,Swift 的 String 类型与 Objective-C 的 NSString 类型在底层完结上有所不同,但它们在存储字符串常量方面的规则是相似的。

问:测验发现,”Hello, World!” 是在 __cstring 节中

答:

抱愧之前的回答给您带来了困扰。经过进一步了解,我发现在 Swift 中,字符串常量实践上是存储在 cstring 节中的。尽管这个字符串常量被视为一个 String 目标,但编译器会将其存储在 cstring 节中以进行优化。

所以,在您的示例中:

let swiftString = "Hello, World!"

“Hello, World!” 字符串常量确实是存储在 __cstring 节中的。

总结: 从上面的一问一答看出,GPT4包括的知识面很全,上下文的理解能力也很强,可是许多问题的答案是有问题的,这儿一定需求有自己的判别力和实践操作,防止被带入到坑中。


Step2、主动生成言语包

这部分的思路及流程为

Mach-O查找字符串 > 中文字符串查找搜集 > 中文字符串编解码复原 > 拟定格局输出到文件中

  • Mach-O查找字符串

    运用 Hopper Disassembler 或相似东西剖析 Mach-O 二进制文件,找到字符串地点的 Section 和 Segment。一般,字符串数据存储在 TEXT Segment 的 cstring 或 __objc_methname Section 中。

  • 中文字符串查找搜集

    遍历 Section 中的字符串,运用正则表达式或其他办法挑选出中文字符串。

  • 中文字符串编解码复原

    对搜集到的中文字符串进行编解码复原。这或许涉及到将 Unicode 转义序列(如 \u4e2d\u6587)转化为实践的中文字符(如 中文)。你能够运用 Python 或其他编程言语的内置函数来完结这个功能。

  • 拟定格局输出到文件中

    将复原后的中文字符串依照指定的格局输出到文件中,你能够运用 JSON、XML 或其他格局来存储这个映射。

经过以上过程,你能够主动从 Mach-O 二进制文件中提取中文字符串,并将它们输出到言语包文件中。这将大大简化本地化过程,进步作业功率。

以上流程的解决计划代码由 HiBox 中的GPT4.0 供给。


__ustring 中文字符串获取

问1:运用 otool 指令找到 _TEXT 中 __ustring 的数据

答1:

otool是一个用于显现Mach-O文件信息的指令行东西。要运用otool找到__TEXT段中__ustring部分的数据,您能够依照以下过程操作:
​
首要,运用otool的-l选项列出Mach-O文件的一切段和节:
otool -l <Mach-O文件名>
这将显现Mach-O文件中的一切段和节的信息。找到_TEXT段中名为__ustring 的节。
​
记下__ustring节的addr(地址)和size(大小)。
运用otool的-s选项提取__ustring节的数据:
otool -s __TEXT __ustring <Mach-O文件名>
​
这将显现 __TEXT 段中__ustring节的原始数据。请留意,这些数据将以十六进制格局显现。

实践1:

✗ otool -s __TEXT __ustring HDTranslateModule_Example > ustring.txt

得到 ustring.txt 文件为:

HDTranslateModule_Example:
Contents of (__TEXT,__ustring) section
000000010000add6  f1 82 87 65 00 00 39 5f 97 7a c4 7e f6 4e 75 98 
000000010000ade6  62 97 00 00 06 52 7b 7c 13 7f 58 5b 2f 66 f6 65 
000000010000adf6  f4 95 00 96 3c 50 1a ff 25 00 64 00 0c ff 53 5f 
000000010000ae06  4d 52 13 7f 58 5b f6 65 f4 95 1a ff 25 00 64 00 
000000010000ae16  00 00 f4 66 b0 65 44 8d 28 8d 08 54 c4 89 1a ff 
000000010000ae26  25 00 40 00 00 00 11 62 2f 66 0d 67 a1 52 68 56 
000000010000ae36  70 65 6e 63 00 00 2c 7b 25 00 64 00 20 5f fe 56 
000000010000ae46  47 72 0d 4e 26 7b 08 54 c4 89 03 83 0c ff f7 8b 
000000010000ae56  ff 66 62 63 16 62 20 52 64 96 0e 54 cd 91 b0 65 
000000010000ae66  0a 4e 20 4f 00 00 a2 8b 55 53 16 7f f7 53 1a ff 
000000010000ae76  25 00 40 00 00 00 2d 4e 87 65 00 00 11 62 5f 4e 
000000010000ae86  2f 66 0d 67 a1 52 68 56 70 65 6e 63 00 00 

直接运用

# -X print no leading addresses or headers
✗ otool -X -s __TEXT __ustring HDTranslateModule_Example > ustring.txt

终究数据为:

000000010000add6  f1 82 87 65 00 00 39 5f 97 7a c4 7e f6 4e 75 98
000000010000ade6  62 97 00 00 06 52 7b 7c 13 7f 58 5b 2f 66 f6 65 
000000010000adf6  f4 95 00 96 3c 50 1a ff 25 00 64 00 0c ff 53 5f 
000000010000ae06  4d 52 13 7f 58 5b f6 65 f4 95 1a ff 25 00 64 00 
000000010000ae16  00 00 f4 66 b0 65 44 8d 28 8d 08 54 c4 89 1a ff 
000000010000ae26  25 00 40 00 00 00 11 62 2f 66 0d 67 a1 52 68 56 
000000010000ae36  70 65 6e 63 00 00 2c 7b 25 00 64 00 20 5f fe 56 
000000010000ae46  47 72 0d 4e 26 7b 08 54 c4 89 03 83 0c ff f7 8b 
000000010000ae56  ff 66 62 63 16 62 20 52 64 96 0e 54 cd 91 b0 65 
000000010000ae66  0a 4e 20 4f 00 00 a2 8b 55 53 16 7f f7 53 1a ff 
000000010000ae76  25 00 40 00 00 00 2d 4e 87 65 00 00 11 62 5f 4e 
000000010000ae86  2f 66 0d 67 a1 52 68 56 70 65 6e 63 00 00 

问2:

得到的数据为:

“””

000000010000add6 f1 82 87 65 00 00 39 5f 97 7a c4 7e f6 4e 75 98 000000010000ade6 62 97 00 00 06 52 7b 7c 13 7f 58 5b 2f 66 f6 65 000000010000adf6 f4 95 00 96 3c 50 1a ff 25 00 64 00 0c ff 53 5f 000000010000ae06 4d 52 13 7f 58 5b f6 65 f4 95 1a ff 25 00 64 00 000000010000ae16 00 00 f4 66 b0 65 44 8d 28 8d 08 54 c4 89 1a ff 000000010000ae26 25 00 40 00 00 00 11 62 2f 66 0d 67 a1 52 68 56 000000010000ae36 70 65 6e 63 00 00 2c 7b 25 00 64 00 20 5f fe 56 000000010000ae46 47 72 0d 4e 26 7b 08 54 c4 89 03 83 0c ff f7 8b 000000010000ae56 ff 66 62 63 16 62 20 52 64 96 0e 54 cd 91 b0 65 000000010000ae66 0a 4e 20 4f 00 00 a2 8b 55 53 16 7f f7 53 1a ff 000000010000ae76 25 00 40 00 00 00 2d 4e 87 65 00 00 11 62 5f 4e 000000010000ae86 2f 66 0d 67 a1 52 68 56 70 65 6e 63 00 00

“””

怎样将这个数据解析成中文字符串

答2:

了解您的需求。在这种情况下,咱们能够修正脚本,以便它能够从otool输出的数据中提取十六进制字符串。以下是一个修正后的Python脚本,用于将otool输出的十六进制数据转化为字符串:
​
​
import sys
import re
​
def otool_hex_to_str(otool_output):
   # 提取十六进制数据
   hex_data = re.findall(r'\t([0-9a-fA-F ]+)', otool_output)
​
   # 将十六进制数据连接成一个字符串
   hex_str = ''.join(hex_data).replace(' ', '')
​
   # 将十六进制数据转化为字节串
   byte_data = bytes.fromhex(hex_str)
​
   # 测验以UTF-16编码解码字节串
   decoded_str = byte_data.decode('utf-16', errors='ignore')
​
   return decoded_str
​
if __name__ == '__main__':
   if len(sys.argv) != 2:
     print("用法: python otool_hex_to_str.py <otool输出>")
   else:
     print(otool_hex_to_str(sys.argv[1]))
​
将此代码保存为otool_hex_to_str.py,然后在指令行中运转:
​
​
python otool_hex_to_str.py "000000010000add6  f1 82 87 65 00 00 39 5f 97 7a c4 7e f6 4e 75 98 
000000010000ade6  62 97 00 00 06 52 7b 7c 13 7f 58 5b 2f 66 f6 65 
000000010000adf6  f4 95 00 96 3c 50 1a ff 25 00 64 00 0c ff 53 5f 
000000010000ae06  4d 52 13 7f 58 5b f6 65 f4 95 1a ff 25 00 64 00 
000000010000ae16  00 00 f4 66 b0 65 44 8d 28 8d 08 54 c4 89 1a ff 
000000010000ae26  25 00 40 00 00 00 11 62 2f 66 0d 67 a1 52 68 56 
000000010000ae36  70 65 6e 63 00 00 2c 7b 25 00 64 00 20 5f fe 56 
000000010000ae46  47 72 0d 4e 26 7b 08 54 c4 89 03 83 0c ff f7 8b 
000000010000ae56  ff 66 62 63 16 62 20 52 64 96 0e 54 cd 91 b0 65 
000000010000ae66  0a 4e 20 4f 00 00 a2 8b 55 53 16 7f f7 53 1a ff 
000000010000ae76  25 00 40 00 00 00 2d 4e 87 65 00 00 11 62 5f 4e 
000000010000ae86  2f 66 0d 67 a1 52 68 56 70 65 6e 63 00 00 "

实践2:

得到的数据为

英文弹窗组件页面分类缓存是时刻阀值:%d,当时缓存时刻:%d更新资质合规:%@我是服务器数据第%d张图片不契合标准,请替换或删去后从头上传订单编号:%@中文我也是服务器数据

尽管能正常将十六进制转成中文,可是一切的中文字符串都集组成一个字符串,明显也不契合咱们的诉求。

问3:

运用你的代码输出的成果为:

“””英文弹窗组件页面分类缓存是时刻阀值:%d,当时缓存时刻:%d更新资质合规:%@我是服务器数据第%d张图片不契合标准,请替换或删去后从头上传订单编号:%@中文我也是服务器数据”””

这儿仅仅一个字符串,可是实践上应该有多个中文字符串

答3:

十分抱愧,我之前的理解有误。现在我理解您的需求是将原始的6个字符串别离打印出来。以下是修正后的脚本:
​
import sys
import re
​
def otool_hex_to_str(otool_output):
   # 提取十六进制数据
   hex_data = re.findall(r'\t([0-9a-fA-F ]+)', otool_output)
​
   # 将十六进制数据连接成一个字符串
   hex_str = ''.join(hex_data).replace(' ', '')
​
   # 将十六进制数据转化为字节串
   byte_data = bytes.fromhex(hex_str)
​
   # 测验以UTF-16编码解码字节串
   decoded_str = byte_data.decode('utf-16', errors='ignore')
​
   # 运用正则表达式匹配一切非空字符
   all_strings = re.findall(r'[^\x00]+', decoded_str)
​
   return all_strings
​
if __name__ == '__main__':
   if len(sys.argv) != 2:
     print("用法: python otool_hex_to_str.py <otool输出>")
   else:
     all_strings = otool_hex_to_str(sys.argv[1])
     for s in all_strings:
       print(s)
​
将此代码保存为otool_hex_to_str.py,然后在指令行中运转:
​
​
python otool_hex_to_str.py "000000010000add6  f1 82 87 65 00 00 39 5f 97 7a c4 7e f6 4e 75 98 
000000010000ade6  62 97 00 00 06 52 7b 7c 13 7f 58 5b 2f 66 f6 65 
000000010000adf6  f4 95 00 96 3c 50 1a ff 25 00 64 00 0c ff 53 5f 
000000010000ae06  4d 52 13 7f 58 5b f6 65 f4 95 1a ff 25 00 64 00 
000000010000ae16  00 00 f4 66 b0 65 44 8d 28 8d 08 54 c4 89 1a ff 
000000010000ae26  25 00 40 00 00 00 11 62 2f 66 0d 67 a1 52 68 56 
000000010000ae36  70 65 6e 63 00 00 2c 7b 25 00 64 00 20 5f fe 56 
000000010000ae46  47 72 0d 4e 26 7b 08 54 c4 89 03 83 0c ff f7 8b 
000000010000ae56  ff 66 62 63 16 62 20 52 64 96 0e 54 cd 91 b0 65 
000000010000ae66  0a 4e 20 4f 00 00 a2 8b 55 53 16 7f f7 53 1a ff 
000000010000ae76  25 00 40 00 00 00 2d 4e 87 65 00 00 11 62 5f 4e 
000000010000ae86  2f 66 0d 67 a1 52 68 56 70 65 6e 63 00 00"
​
这将依照原始次序别离打印出多个字符串。期望这次的解决计划能满意您的需求。假如您有任何问题,请随时告诉我。

实践3:

运用最新的代码输出为:

英文
弹窗组件页面
分类缓存是时刻阀值:%d,当时缓存时刻:%d
更新资质合规:%@
我是服务器数据
第%d张图片不契合标准,请替换或删去后从头上传
订单编号:%@
中文
我也是服务器数据

现已是契合预期的答案了

问4:

请将终究打印的数据,以json数组的办法写入translate.txt中

答4:

以下是修正后的脚本,能够将otool输出的十六进制数据转化为多个字符串,并将它们作为JSON数组写入translate.txt文件:

import sys
import re
import json
​
def otool_hex_to_str(otool_output):
  # 提取十六进制数据
  hex_data = re.findall(r'\t([0-9a-fA-F ]+)', otool_output)
​
  # 将十六进制数据连接成一个字符串
  hex_str = ''.join(hex_data).replace(' ', '')
​
  # 将十六进制数据转化为字节串
  byte_data = bytes.fromhex(hex_str)
​
  # 测验以UTF-16编码解码字节串
  decoded_str = byte_data.decode('utf-16', errors='ignore')
​
  # 运用正则表达式匹配一切非空字符
  all_strings = re.findall(r'[^\x00]+', decoded_str)
​
  return all_strings
​
if __name__ == '__main__':
  with open('ustring.txt', 'r') as f:
    otool_output = f.read()
  all_strings = otool_hex_to_str(otool_output)
  with open('translate.txt', 'w', encoding='utf-8') as f:
    json.dump(all_strings, f, ensure_ascii=False, indent=4)
  print("已将字符串以JSON数组办法写入translate.txt文件")

将此代码保存为otool_hex_to_str.py,然后保证ustring.txt文件包括以下内容:

000000010000add6  f1 82 87 65 00 00 39 5f 97 7a c4 7e f6 4e 75 98
000000010000ade6  62 97 00 00 06 52 7b 7c 13 7f 58 5b 2f 66 f6 65 
000000010000adf6  f4 95 00 96 3c 50 1a ff 25 00 64 00 0c ff 53 5f 
000000010000ae06  4d 52 13 7f 58 5b f6 65 f4 95 1a ff 25 00 64 00 
000000010000ae16  00 00 f4 66 b0 65 44 8d 28 8d 08 54 c4 89 1a ff 
000000010000ae26  25 00 40 00 00 00 11 62 2f 66 0d 67 a1 52 68 56 
000000010000ae36  70 65 6e 63 00 00 2c 7b 25 00 64 00 20 5f fe 56 
000000010000ae46  47 72 0d 4e 26 7b 08 54 c4 89 03 83 0c ff f7 8b 
000000010000ae56  ff 66 62 63 16 62 20 52 64 96 0e 54 cd 91 b0 65 
000000010000ae66  0a 4e 20 4f 00 00 a2 8b 55 53 16 7f f7 53 1a ff 
000000010000ae76  25 00 40 00 00 00 2d 4e 87 65 00 00 11 62 5f 4e 
000000010000ae86  2f 66 0d 67 a1 52 68 56 70 65 6e 63 00 00

在指令行中运转python otool_hex_to_str.py,它将从ustring.txt文件中读取数据并将成果作为JSON数组写入translate.txt文件。期望这次的解决计划能满意您的需求。假如您有任何问题,请随时告诉我。



__cstring 中文字符串获取

问1:

运用 otool 指令找到 _TEXT 中 __cstring 的数据

答1:

要运用otool指令找到__TEXT段中的__cstring数据,请在指令行中运转以下指令:
​
​
otool -V -X -s __TEXT __cstring HDTranslateModule_Example
​
这儿的 HDTranslateModule_Example 是您要剖析的可履行文件的称号。请保证将其替换为您实践要剖析的文件名。
​
​
这个指令的解释如下:
​
​
​
otool: macOS上的一个指令行东西,用于显现Mach-O文件(macOS可履行文件格局)的信息。
​
-V: 以详细模式显现输出。
​
-X: 以十六进制格局显现输出。
​
-s __TEXT __cstring: 提取__TEXT段中的__cstring部分。
​
HDTranslateModule: 要剖析的可履行文件的称号。
​
​
运转此指令后,您将在指令行中看到__TEXT段中__cstring部分的数据。假如您期望将输出保存到文件中,能够将输出重定向到文件,如下所示:
​
​
otool -V -X -s __TEXT __cstring HDTranslateModule_Example > cstring.txt
​
这将把输出保存到名为cstring.txt的文件中。

实践1:

履行上面的指令后,得到的 cstring.txt 数据如下:

systemlanguage: %@
v16@?0q8
UITableViewCell
UILabelCell
UIButtonCell
UIButtonCell2
UITextViewCell
UITextViewCell2
UITextFieldCell
UITextFieldCell2
UILable.text Case
UILable.attributedText Case
UIButton.setTitle Case
UIButton.setAttributedTitle Case
UITextView.text Case
UITextView.attributedText Case
UITextField.text Case
UITextField.attributedText Case
v32@?0@"UITabBarItem"8Q16^B24
UILable.text
%d
%@
UILable.attributedText
UIButton.setTitle
UIButton.setAttributedTitle
UITextView.text
UITextView.attributedText
UITextField.text
UITextField.attributedText
​
hello
Swift \37777777744\37777777670\37777777655
Swift En
\37777777746\37777777630\37777777657\37777777744\37777777670\37777777672 \37777777744\37777777670\37777777655
Hello
\37777777744\37777777675\37777777640\37777777745\37777777645\37777777675
Hello \37777777744\37777777675\37777777640\37777777745\37777777645\37777777675
​
​
_TtC25HDTranslateModule_Example9HDExample
v16@0:8
@16@0:8
value
​
\n
__TEXT
swift_getObjCClassMetadata
_TtCs12_SwiftObject
swift_getTypeContextDescriptor
swift_getExistentialTypeMetadata
objc_addLoadImageFunc
__swift5_proto

经过源码和输出产品对比,能够大胆猜测 \37777777 前缀的字符串为中文字符串,那么接下来的目标便是将其转成中文字符串。

问2:

cstring.txt 部分数据如下:

“””

Swift \37777777744\37777777670\37777777655 Swift En \37777777746\37777777630\37777777657\37777777744\37777777670\37777777672 \37777777744\37777777670\37777777655

“””

上面一些乱码字符,或许是编码过错或许其他原因导致的。应该是一种 特别的编码办法,你能够协助转成中文吗?

咱们先从 “”” \37777777744\37777777670\37777777655 “”” 开端。

答2:

这些乱码字符或许是八进制的 Unicode 编码。我能够测验将它们转化为中文:
​
​
原始字符串:\37777777744\37777777670\37777777655
转化为八进制:344 270 255
​
​
将八进制转化为 Unicode 字符:
​
​
344 ->270 ->255 -> 
​
​
经过转化,咱们得到的字符串是 ""。看起来这并不是中文字符。这或许是因为原始数据编码有误,或许这些字符自身就不是中文。

结论明显是不契合预期,只能接着问。

问3:

这些字符串看着八进制的,你先转成16进制,然后想办法转成中文试试看

答3:

首要,将八进制转化为十六进制:
​
​
八进制:37777777744 37777777670 37777777655
十进制:4294967244 4294967192 4294967175
十六进制:FFFFFFC4 FFFFFFD2 FFFFFFAD
​
​
接下来,测验将十六进制转化为中文字符。因为这些十六进制数值超出了 Unicode 的规模,咱们需求将它们转化为有用的 Unicode 编码。咱们能够测验将这些数值与有用 Unicode 规模内的数值进行按位与操作:
​
​
​
FFFFFFC4 & 0x10FFFF = C4
​
FFFFFFD2 & 0x10FFFF = D2
​
FFFFFFAD & 0x10FFFF = AD
​
​
将十六进制转化为 Unicode 字符:
​
​
​
C4 -> 
​
D2 -> 
​
AD -> 173 (软连字符)
​
​
经过转化,咱们得到的字符串是 "•"。看起来这并不是中文字符。这或许是因为原始数据编码有误,或许这些字符自身就不是中文。

这儿 GPT4 犯了一个常见的过错,在进行进制转化的时候,会经常呈现误差,这儿在认真比较好,发现八进制转十六进制答案有误,只能接着问。

问4:

请查看8进制转16进制是否有误

答4:

抱愧刚刚的回答中,我在将八进制转化为十六进制时呈现了过错。现在我将从头进行转化:
​
​
原始字符串:\37777777744\37777777670\37777777655
八进制:344 270 255
​
八进制:344 270 255
十六进制:E4 B8 AD

经过一次次承认,它给出了预期的成果,可是仍是没有得到终究的中文字符串,只能接着再问。

问5:

请将如下字符串转译成中文: “””E4 B8 AD”””

答5:

def hex_to_chinese(hex_str):
  hex_numbers = hex_str.split()
  byte_array = bytearray()
  for hex_number in hex_numbers:
    byte_array.append(int(hex_number, 16))
  return byte_array.decode('utf-8')
​
input_str = "E4 B8 AD"
result = hex_to_chinese(input_str)
print(result)
​
这段代码将输出:
​
中

总算能完结特别编码转成中文的能力了。趁着上下文还在,持续往完好的计划施行。

问6:

现在咱们一步步来解决问题: 首要你想办法从 “Swift \37777777744\37777777670\37777777655” 找到 “\37777777744\37777777670\37777777655” 然后将 “\37777777744\37777777670\37777777655” 作为八进制,转成 十六进制 终究调用 printChinese 办法得到中文。

答6:

import re
from urllib import parse
​
def octal_to_hex(octal_str):
  decimal_num = int(octal_str, 8)
  hex_num = hex(decimal_num)[2:]
  return hex_num.zfill(2)
​
def printChinese(x):
  y = x.encode('unicode_escape')
  z = y.decode('utf-8').replace('\x', '%')
  un = parse.unquote(z)
  return un
​
input_str = "Swift \37777777744\37777777670\37777777655"# 1. 从 "Swift \37777777744\37777777670\37777777655" 找到 "\37777777744\37777777670\37777777655"
octal_str = re.findall(r'(\[0-7]{3})+', input_str)[0]
​
# 2. 将 "\37777777744\37777777670\37777777655" 作为八进制,转成 十六进制
octal_list = re.findall(r'\([0-7]{3})', octal_str)
hex_list = [octal_to_hex(octal_str) for octal_str in octal_list]
hex_str = ''.join(hex_list)
​
# 3. 调用 printChinese 办法得到中文
chinese_str = printChinese(hex_str)
​
# 替换原始字符串中的八进制编码
output_str = input_str.replace(octal_str, chinese_str)
print(output_str)
​
# 这个脚本首要从输入字符串中找到八进制编码,然后将其转化为十六进制编码,终究调用 printChinese 函数将十六进制编码转化为中文字符。运转此代码后,将输出 "Swift 中"。

现在的脚本也能完结从 一行混合中字符串中,经过特定的规则找到编码前的中文字符串,然后经过之前的过程替换然后输出完好的一行带有中文的字符串:Swift 中。可是仍是不够,因为还存在其他场景没有考虑清楚,接着问。

问7:

这个字符串是有空格的,可是用上面的脚本履行,空格消失了,请供给新的兼容代码

“””

\37777777746\37777777630\37777777657\37777777744\37777777670\37777777672 \37777777744\37777777670\37777777655

“””

(补白 \37777777672 和 \37777777744 中心有一个空格)

答7:

import re
​
def octal_to_hex(octal_str):
   decimal_num = int(octal_str, 8)
   hex_num = hex(decimal_num)[2:]
   return hex_num.zfill(2)
​
def convert_octal_to_hex(input_str):
   octal_numbers = re.findall(r'\37777777\d{3}', input_str)
   for octal_number in octal_numbers:
     octal_number = octal_number[1:]  # 去掉最初的反斜杠
     hex_number = octal_to_hex(octal_number)
     hex_str = "\x" + hex_number
     input_str = input_str.replace("\" + octal_number, hex_str)
   return input_str
​
input_str = "\37777777746\37777777630\37777777657\37777777744\37777777670\37777777672 \37777777744\37777777670\37777777655"
output_str = convert_octal_to_hex(input_str)
print("output_str:", output_str)
​
这个代码会将原始数据中的八进制数字转化为十六进制数字,并保存其间的空格。输出成果为:
output_str: \x7e\x18\x19\x7c\x1e\x1e \x7c\x1e\x1d

问8:

请将终究输出的中文写入到 cstring_output.json 文件中,而且以 json 数组的办法。

答8:

以下是修正后的代码,将输出的中文写入到 cstring_output.json 文件中,并以 JSON 数组的办法存储:

import re
from urllib import parse
import json
​
def octal_to_hex(octal_number):
  # 八进制转成十进制数据
  decimal_number = int(octal_number, 8)
  # Unicode 字符串按位运算
  valide_number = decimal_number & 0xFF
  # 转成可用的十六进制数据
  hex_number = f"{valide_number:02x}"
  return hex_number
​
def hex_to_chinese(x):
  y = x.encode('unicode_escape')
  z = y.decode('utf-8').replace('\x', '%')
  # 兼容剩余的 \ 符号
  z = z.replace('\', '')
  un = parse.unquote(z)
  return un
​
def convert_octal_to_hex(input_str):
  octal_numbers = re.findall(r'\37777777\d{3}', input_str)
  for octal_number in octal_numbers:
    octal_number = octal_number[1:] # 去掉最初的反斜杠
    hex_number = octal_to_hex(octal_number)
    hex_str = "\" + hex_number
    input_str = input_str.replace("\" + octal_number, hex_str)
  return input_str
​
def dispose_line(input_str):
  start_index = input_str.find(r"\37777777")
  if start_index < 0:
    return None
  octal_str = input_str[start_index:]
  output_str = convert_octal_to_hex(input_str)
  return hex_to_chinese(output_str)
​
print("start")
file_path = 'cstring.txt'
output_file_path = 'cstring_output.json'
output_data = []
​
with open(file_path, 'r') as f:
  for line in f:
    line = line.strip() # 去掉换行符
    value = dispose_line(line)
    if value:
      output_data.append(value)
​
with open(output_file_path, 'w', encoding='utf-8') as f:
  json.dump(output_data, f, ensure_ascii=False, indent=4)
​
print("end")

这段代码将读取 cstring.txt 文件中的每一行,并运用 dispose_line 函数处理每一行。处理后的中文字符串将增加到 output_data 列表中。终究,将 output_data 列表以 JSON 数组的办法写入到 cstring_output.json 文件中。

总结: 本文经过几个问题和答案的办法,简要介绍了怎样运用 GPT 的广泛知识面来解决跨范畴问题。尽管这些过程现已过简化,但实践操作过程中或许会遇到更多应战。GPT 能够在给定恰当提示和引导的情况下,迅速供给实践办法。可是,这些办法或许并不完善,因而在实践运用中,开发者仍需进行多次实践和验证,以保证解决计划的有用性。


2、案牍翻译


问1:

运用python代码,调用google的接口将中文翻译成英文

答1:

# 要运用Python调用Google翻译接口,您能够运用googletrans库。首要,保证您现已装置了这个库。在指令行中运转以下指令进行装置:
​
​
pip install googletrans==4.0.0-rc1
​
# 然后,运用以下代码将中文翻译成英文:
​
​
from googletrans import Translator
​
def translate_text(text, src_lang='zh-CN', dest_lang='en'):
  translator = Translator(service_urls=['translate.google.com'])
  result = translator.translate(text, src=src_lang, dest=dest_lang)
  return result.text
​
if __name__ == "__main__":
  chinese_text = "你好,国际!"
  english_text = translate_text(chinese_text)
  print(english_text)
​
# 这段代码首要导入Translator类,然后界说一个translate_text函数,该函数承受要翻译的文本、源言语和目标言语作为参数。在__main__部分,咱们将一段中文文本传递给translate_text函数,并打印翻译后的英文文本。

这儿的计划是可行,可是因为是第三方对google翻译接口进行封装,导致有运用次数的约束。

问2:

恳求 api_key 然后运用python代码调用 翻译接口

答2:

# 依据您供给的过错信息,您或许在运用google-cloud-translate库。为了解决这个问题,请依照以下过程操作:# 首要,保证您现已装置了google-cloud-translate库。在指令行中运转以下指令进行装置:
​
pip install google-cloud-translate
​
​
# 设置环境变量GOOGLE_APPLICATION_CREDENTIALS,指向包括您的API密钥的JSON文件。在指令行中运转以下指令(替换/path/to/your/credentials.json为实践途径):
​
​
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/your/credentials.json"
# 为了削减每次都需求履行上面的指令,能够在 .bash_profile 文件中增加即可
​
​
# 运用以下代码将中文翻译成英文:
​
​
from google.cloud import translate_v2 as translate
​
def translate_text(text, src_lang='zh-CN', dest_lang='en'):
  translate_client = translate.Client()
  result = translate_client.translate(text, target_language=dest_lang, source_language=src_lang)
  return result['translatedText']
​
if __name__ == "__main__":
  chinese_text = "你好,国际!"
  english_text = translate_text(chinese_text)
  print(english_text)
​
# 这段代码首要导入translate_v2模块,然后界说一个translate_text函数,该函数承受要翻译的文本、源言语和目标言语作为参数。在__main__部分,咱们将一段中文文本传递给translate_text函数,并打印翻译后的英文文本。
​
​
# 请留意,您不再需求在translate.Client()中供给api_key参数。相反,库将从环境变量GOOGLE_APPLICATION_CREDENTIALS中读取API密钥。

这儿不引荐运用api_key的办法,需求从装备文件来获取相关 api_key 等数据。

问3:

GOOGLE_APPLICATION_CREDENTIALS 文件内容,能够举例说明下吗?怎样获取它?

答3:

# GOOGLE_APPLICATION_CREDENTIALS文件是一个包括Google Cloud服务帐户密钥的JSON文件。这个文件一般由Google Cloud Console生成,用于对您的运用程序进行身份验证。文件内容包括项目ID、私钥、客户端ID等信息。
​
​
# 以下是一个示例GOOGLE_APPLICATION_CREDENTIALS文件的内容:
​
​
{
  "type": "service_account",
  "project_id": "your_project_id",
  "private_key_id": "your_private_key_id",
  "private_key": "-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY\n-----END PRIVATE KEY-----\n",
  "client_email": "your_service_account_email",
  "client_id": "your_client_id",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/your_service_account_email"
}
​
# 请留意,您需求运用您自己的项目ID、私钥ID、私钥、服务帐户电子邮件和客户端ID替换示例中的占位符。
​
​
# 要生成这个文件,请依照以下过程操作:
​
​
​
拜访Google Cloud Console:https://console.cloud.google.com/。
​
挑选您的项目。
​
在左边菜单中,点击"IAM & Admin",然后点击"Service Accounts"。
​
点击"创立服务帐户",输入服务帐户的详细信息,然后点击"创立"。
​
在"授予此服务帐户拜访权限"部分,挑选恰当的人物(例如,关于翻译API,您能够挑选"Cloud Translation API Editor"人物),然后点击"持续"。
​
在"授予用户拜访权限"部分,您能够挑选向其他用户授予拜访权限,或许跳过这一步。
​
点击"完结"以创立服务帐户。
​
回来服务帐户列表,找到您刚刚创立的服务帐户,然后点击右侧的"操作"列中的铅笔图标。
​
在"密钥"选项卡下,点击"增加密钥",然后挑选"JSON"。
​
JSON密钥文件将主动下载到您的计算机。这便是您需求设置为GOOGLE_APPLICATION_CREDENTIALS环境变量的文件。
​
​
设置环境变量后,您的运用程序将能够运用这个文件中的凭据拜访Google Cloud服务。

教程十分详细简略,可是需求提前预备好 支撑外汇信用卡。关于 python 小白来说,怎样完结并发调用翻译API有点难度,只好持续求助。

问4:

translate_text 翻译串行太慢,能够支撑并发吗,请供给相关代码

答4:

# 能够运用Python的concurrent.futures库完结并发翻译。以下是一个运用ThreadPoolExecutor完结并发翻译的示例:
​
​
import concurrent.futures
from google.cloud import translate_v2 as translate
​
def translate_text(text, src_lang='zh-CN', dest_lang='en'):
  translate_client = translate.Client()
  result = translate_client.translate(text, target_language=dest_lang, source_language=src_lang)
  return result['translatedText']
​
def translate_concurrently(text_list, src_lang='zh-CN', dest_lang='en', max_workers=5):
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
    future_to_text = {executor.submit(translate_text, text, src_lang, dest_lang): text for text in text_list}
    translated_texts = []
    for future in concurrent.futures.as_completed(future_to_text):
      translated_texts.append(future.result())
  return translated_texts
​
if __name__ == "__main__":
  chinese_texts = ["你好,国际!", "我喜欢编程", "Python是一门很好的编程言语"]
  translated_texts = translate_concurrently(chinese_texts)
  print(translated_texts)
​
# 在这个示例中,咱们界说了一个新的函数translate_concurrently,它承受一个文本列表、源言语、目标言语和最大作业线程数作为参数。这个函数运用ThreadPoolExecutor并发地调用translate_text函数,并将翻译后的文本增加到translated_texts列表中。
​
​
# 在__main__部分,咱们创立了一个包括多个中文文本的列表,并调用translate_concurrently函数进行并发翻译。终究,咱们打印翻译后的英文文本列表。
​
​
# 请留意,因为翻译API或许有恳求约束,过高的并发量或许导致恳求被拒绝。您能够依据需求调整max_workers参数以控制并发量。

翻译过程中,会将字符串自身包括的的 \n 符号转译了,导致在完结国际化呈现了问题。

问5:

中文包括 \n 换行符号,导致翻译的时候转译了,怎样解决

答5:

# 在您供给的代码中,您运用了repr()函数来获取all_strings[index]和all_translate_strings[index]的可打印表明。这将保证\n不被转义。可是,repr()函数还会在字符串的最初和结束增加额定的单引号。为了防止这个问题,您能够在format()函数中运用切片操作来去掉这些额定的单引号。
​
​
以下是修正后的write_final_ustring_local_file函数:
​
​
def write_final_ustring_local_file(file_final_path, all_strings, all_translate_strings):
  with open(file_final_path, 'w', encoding='utf-8') as f:
    for index in range(len(all_strings)):
      # 运用原始字符串表明法,repr(),以便在all_translate_strings中的\n不被转义
      f.write(r'"{}" = "{}";'.format(
        repr(all_strings[index])[1:-1], repr(all_translate_strings[index])[1:-1]) + '\n')
​
在这个修正后的版别中,咱们运用切片操作[1:-1]来去掉repr()函数增加的额定单引号。这样,您将取得一个包括未转义的\n的字符串,而不会有额定的单引号。
​
​
def write_final_ustring_local_file(file_final_path, all_strings, all_translate_strings):
  with open(file_final_path, 'w', encoding='utf-8') as f:
    for index in range(len(all_strings)):
      # 运用原始字符串表明法,repr(),以便在all_translate_strings中的\n不被转义
      f.write(r'"{}" = "{}";'.format(
        repr(all_strings[index])[1:-1], repr(all_translate_strings[index])[1:-1]) + '\n')

3、最佳实践

在前面的案牍搜集和案牍翻译主动化基础上,我供给了一个完好的主动化搜集翻译脚本及代码注释。以下是脚本的详细内容及运用说明

3.1、处理 __ustring 节数据

__ustring 节是用于存储 Unicode 字符串的数据段。为了完结中文字符串的查找、搜集和翻译主动化,咱们能够编写一个名为 find_chinese_ustrings.py 的脚本。以下是一个完好的完结示例:

import re
import sys
from urllib import parse
import json
import os
from google.cloud import translate_v2 as translate
from concurrent.futures import ThreadPoolExecutor
​
​
# 现已翻译好的个数
completed_translations = 0
# 总的需求翻译的个数
translations = 0
​
​
# 运用google进行私钥翻译:pip install google-cloud-translate
def translate_text(text, src_lang='zh-CN', dest_lang='en'):
  translate_client = translate.Client()
  result = translate_client.translate(
    text, target_language=dest_lang, source_language=src_lang)
  return result['translatedText']
​
​
# 翻译指令
def translate_worker(value):
  if adjustChinese(value):
    translated_text = translate_text(value)
    if translated_text:
      # 运用global关键字指示咱们要运用和修正全局变量
      global completed_translations
      global translations
​
      completed_translations += 1
      # print(value+"翻译成功")
      print("翻译进展:", completed_translations, "/", translations)
      return (value, translated_text)
    else:
      print(value+"翻译失利")
  return None
​
​
# 八进制转成十六进制
def octal_to_hex(octal_number):
  # 八进制转成十进制数据
  decimal_number = int(octal_number, 8)
  # Unicode 字符串按位运算
  valide_number = decimal_number & 0xFF
  # 转成可用的十六进制数据
  hex_number = hex(valide_number)[1:]
  return hex_number
​
​
# 十六进制转成中文
def hex_to_chinese(x):
  y = x.encode('unicode_escape')
  z = y.decode('utf-8').replace('\x', '%')
  # 兼容剩余的 \ 符号
  z = z.replace('\', '')
  un = parse.unquote(z)
  return un
​
​
# 处理一行数据,找到中文的特别字符,转成中文
def convert_octal_to_hex(input_str):
  octal_numbers = re.findall(r'\37777777\d{3}', input_str)
  # print(octal_numbers)
  for octal_number in octal_numbers:
    octal_number = octal_number[1:] # 去掉最初的反斜杠
    # print("octal_number", octal_number)
​
    # 八进制转成十进制数据,而且转成可用的十六进制数据
    hex_number = octal_to_hex(octal_number)
​
    hex_str = "\" + hex_number
    # 这儿是做替换操作,防止丢失空格
    input_str = input_str.replace("\" + octal_number, hex_str)
  return input_str
​
​
# 处理一行数据,找到中文的特别字符,转成中文
def dispose_line(input_str):
  # 1. 从 "Swift \37777777744\37777777670\37777777655" 找到 "\37777777744\37777777670\37777777655"
  start_index = input_str.find(r"\37777777")
  if start_index < 0:
    return None
  octal_str = input_str[start_index:]
  # print("octal_str", octal_str)
​
  # 2. 将 "\37777777744\37777777670\37777777655" 作为八进制,转成 十六进制
  output_str = convert_octal_to_hex(input_str)
  # print("output_str", output_str)
  return hex_to_chinese(output_str)
​
​
# 判别value是否是中文
def adjustChinese(value):
  if value:
    if len(value) > 0:
      for s in value:
        if u'\u4e00' <= s <= u'\u9fff':
          return True
  return False
​
​
# 运用otool指令,将二进制文件中的__cstring阶段,导出到文件中
def otool_to_file(macho_file, cstring_file):
  os.system(
    f"otool -V -X -s __TEXT __cstring {macho_file} > {cstring_file}")
​
​
# 从文件中读取数据,进行翻译
def translate_cstring(cstring_file):
  all_strings = []
  with open(cstring_file, 'r') as f:
    for line in f:
      # 去掉换行符
      line = line.strip()
      value = dispose_line(line)
​
      if adjustChinese(value):
        all_strings.append(value)
​
  global translations
  translations = len(all_strings)
​
  # 这个修正后的代码运用了ThreadPoolExecutor来完结多线程翻译。translate_worker函数是一个新的辅佐函数,
  # 它承受一个字符串并测验翻译。otool_hex_to_str函数中的循环被替换为一个executor.map调用,
  # 它将translate_worker运用于all_strings中的每个元素。这将并行履行翻译使命,然后加快速度。
  with ThreadPoolExecutor() as executor:
    results = list(executor.map(translate_worker, all_strings))
​
  output_datas = []
  output_translate_datas = []
  for result in results:
    if result is not None:
      output_datas.append(result[0])
      output_translate_datas.append(result[1])
​
  return output_datas, output_translate_datas
​
​
# 将翻译好的数据,写入到文件中
def write_to_file(output_data, output_file_path):
  with open(output_file_path, 'w', encoding='utf-8') as f:
    json.dump(output_data, f, ensure_ascii=False, indent=2)
​
​
# 将翻译后的all_strings、all_translate_strings写入output_file、file_translate_path文件
def write_ustring_local_file(output_file, file_translate_path, all_strings, all_translate_strings):
  with open(output_file, 'w', encoding='utf-8') as f:
    json.dump(all_strings, f, ensure_ascii=False, indent=2)
  with open(file_translate_path, 'w', encoding='utf-8') as f:
    json.dump(all_translate_strings, f, ensure_ascii=False, indent=2)
​
​
# 将all_strings、all_translate_strings 以 "中文"="English" 的办法写入file_final_path.json文件
def write_final_ustring_local_file(file_final_path, all_strings, all_translate_strings):
  with open(file_final_path, 'w', encoding='utf-8') as f:
    for index in range(len(all_strings)):
      # 运用原始字符串表明法,repr(),以便在all_translate_strings中的\n不被转义
      # 运用了repr()函数来获取all_strings[index]和all_translate_strings[index]的可打印表明。
      # 这将保证\n不被转义。可是,repr()函数还会在字符串的最初和结束增加额定的单引号。
      # 为了防止这个问题,您能够在format()函数中运用切片操作来去掉这些额定的单引号。
      f.write(r'"{}" = "{}";'.format(
        repr(all_strings[index])[1:-1], repr(all_translate_strings[index])[1:-1]) + '\n')
​
​
# 读取file_path、file_translate_path文件,并将其json数组以 如下格局写入ustring_output.json文件
def read_ustring_local_file(file_path, file_translate_path):
  with open(file_path, 'r', encoding='utf-8') as f:
    file_path_data = json.load(f)
  with open(file_translate_path, 'r', encoding='utf-8') as f:
    file_translate_path_data = json.load(f)
  return file_path_data, file_translate_path_data
​
​
# python3 find_chinese_cstrings.py HDFindStringDemo
if __name__ == '__main__':
  print("start")
  macho_file = sys.argv[1]
  cstring_file = f"{macho_file}_cstring.txt"
  output_file = f"{macho_file}_cstring_output.json"
  file_translate_path = f"{macho_file}_cstring_output_translate.json"
  file_final_path = f"{macho_file}_cstring_output_final.json"
​
  ##########  翻译+读写全流程 ##########
  #  翻译+读写全流程 1. 运用otool指令,将二进制文件中的__cstring阶段,导出到文件中
  otool_to_file(macho_file, cstring_file)
​
  #  翻译+读写全流程 2. 从文件中读取数据,转成中文后,再进行翻译
  all_strings, all_translate_strings = translate_cstring(cstring_file)
​
  # 翻译+读写全流程 3. 将翻译好的数据,写入到文件中
  write_ustring_local_file(
    output_file, file_translate_path, all_strings, all_translate_strings)
​
  # # 翻译+读写全流程 4. 将all_strings、all_translate_strings 以 "中文"="English" 的办法写入file_final_path.json文件
  write_final_ustring_local_file(
    file_final_path, all_strings, all_translate_strings)
  ##########  翻译+读写全流程 ##########
​
  ### 因为在线调用google翻译api一定量会收费,所以当履行完【翻译+读写全流程】后,读取本地文件来生成想要的格局文件 ###
  ##########  本地文件读取翻译 ##########
  # 本地文件读取翻译 1. 读取file_path、file_translate_path文件
  # all_strings, all_translate_strings = read_ustring_local_file(
  #   output_file, file_translate_path)
​
  # 本地文件读取 2. 将all_strings、all_translate_strings 以 "中文"="English" 的办法写入file_final_path.json文件
  # write_final_ustring_local_file(
  #   file_final_path, all_strings, all_translate_strings)
  ##########  本地文件读取翻译 ##########
​
  print("end")
​

运用说明: 请保证二进制文件 HDTranslateModule_Example 和脚本位于同一目录下,然后依照以下过程操作:

  • 翻开指令行东西(如:终端、指令提示符等)。
  • 运用 cd 指令切换到包括二进制文件和脚本的目录。
  • 履行相应的指令来运转脚本。

留意: 因为脚本需求实时调用 Google 翻译 API,请保证在履行脚本时能正常拜访以下网址:cloud.google.com/docs/authen…

在运用过程中,假如遇到任何问题或需求进一步的协助,请随时发问,我会竭诚为你供给支撑。

✗ ls
HDTranslateModule_Example find_chinese_cstrings.py  find_chinese_ustrings.py
✗ python3 find_chinese_ustrings.py HDTranslateModule_Example
start
翻译进展: 1 / 9
翻译进展: 2 / 9
翻译进展: 3 / 9
翻译进展: 4 / 9
翻译进展: 5 / 9
翻译进展: 6 / 9
翻译进展: 7 / 9
翻译进展: 8 / 9
翻译进展: 9 / 9
end
​
✗ ls
HDTranslateModule_Example # 二进制文件
HDTranslateModule_Example_ustring.txt # otools得到的十六进制数据
HDTranslateModule_Example_ustring_output.json # 从ustring.txt中提炼出中文字符串,并输出成json数组
HDTranslateModule_Example_ustring_output_translate.json #从ustring_output.json翻译成英文
HDTranslateModule_Example_ustring_output_final.json #将ustring_output.json和ustring_output_final.json 依照iOS所需求的言语包格局输出成终究文件
find_chinese_cstrings.py # 处理 __cstring 节中的中文字符串的脚本
find_chinese_ustrings.py # 处理 __ustring 节中的中文字符串的脚本# 终究的输出数据
✗ cat HDTranslateModule_Example_ustring_output_final.json
"英文" = "English";
"弹窗组件页面" = "Popup component page";
"分类缓存是时刻阀值:%d,当时缓存时刻:%d" = "Category cache is time threshold: %d, current cache time: %d";
"更新资质合规:%@" = "Update qualification compliance: %@";
"我是服务器数据" = "i am server data";
"第%d张图片不契合标准,请替换或删去后从头上传" = "The %d image does not meet the specifications, please replace or delete it and upload it again";
"订单编号:%@" = "Order number: %@";
"中文" = "Chinese";
"我也是服务器数据" = "I am also server data";

3.2、处理 __cstring 节数据

__cstring 节是用于存储 C 风格字符串的数据段。为了完结中文字符串的查找、搜集和翻译主动化,咱们能够编写一个名为 find_chinese_cstrings.py 的脚本。以下是一个简略的完结示例:

import sys
import re
import json
import os
from google.cloud import translate_v2 as translate
from concurrent.futures import ThreadPoolExecutor
​
completed_translations = 0
translations = 0
​
​
# 运用google进行私钥翻译:pip install google-cloud-translate
def translate_text(text, src_lang='zh-CN', dest_lang='en'):
  translate_client = translate.Client()
  result = translate_client.translate(
    text, target_language=dest_lang, source_language=src_lang)
  return result['translatedText']
​
​
# 翻译指令
def translate_worker(value):
  if adjustChinese(value):
    translated_text = translate_text(value)
    if translated_text:
      # 运用global关键字指示咱们要运用和修正全局变量
      global completed_translations
      global translations
​
      completed_translations += 1
      # print(value+"翻译成功")
      print("翻译进展:", completed_translations, "/", translations)
      return (value, translated_text)
    else:
      print(value+"翻译失利")
  return None
​
​
# 十六进制转成中文,而且中英文翻译
def otool_hex_to_str(otool_output):
  # 提取十六进制数据
  hex_data = re.findall(r'\t([0-9a-fA-F ]+)', otool_output)
  # print("hex_data", hex_data)# 将十六进制数据连接成一个字符串
  hex_str = ''.join(hex_data).replace(' ', '')
  # print("hex_str", hex_str)# 将十六进制数据转化为字节串
  byte_data = bytes.fromhex(hex_str)
  # print("byte_data", byte_data)# 测验以UTF-16编码解码字节串
  decoded_str = byte_data.decode('utf-16', errors='ignore')
  # print("decoded_str", decoded_str)# 运用正则表达式匹配一切非空字符
  all_strings = re.findall(r'[^\x00]+', decoded_str)
  # print("all_strings", all_strings)# all_strings或许有非中文,请过滤掉
  output_datas = []
  output_translate_datas = []
  total_texts = len(all_strings)
​
  global translations
  translations = len(all_strings)
​
  # 这个修正后的代码运用了ThreadPoolExecutor来完结多线程翻译。translate_worker函数是一个新的辅佐函数,
  # 它承受一个字符串并测验翻译。otool_hex_to_str函数中的循环被替换为一个executor.map调用,
  # 它将translate_worker运用于all_strings中的每个元素。这将并行履行翻译使命,然后加快速度。
  with ThreadPoolExecutor() as executor:
    results = list(executor.map(translate_worker, all_strings))
​
  for result in results:
    if result is not None:
      output_datas.append(result[0])
      output_translate_datas.append(result[1])
​
  return output_datas, output_translate_datas
​
​
# 判别是否包括中文
def adjustChinese(value):
  if value:
    if len(value) > 0:
      for s in value:
        if u'\u4e00' <= s <= u'\u9fff':
          return True
  return False
​
​
# 将otool指令输出的内容写入文件
def otool_to_file(macho_file, ustring_file):
  # 将otool指令输出的内容写入文件
  os.system(f"otool  -X -s __TEXT __ustring {macho_file} > {ustring_file}")
​
​
# 读取ustring_file文件,并将其json数组,然后翻译,回来原始数据和翻译后数据
def read_ustring_file(ustring_file):
  with open(ustring_file, 'r') as f:
    otool_output = f.read()
  return otool_hex_to_str(otool_output)
​
​
# 将翻译后的all_strings、all_translate_strings写入file_path、file_translate_path文件
def write_ustring_local_file(file_path, file_translate_path, all_strings, all_translate_strings):
  with open(file_path, 'w', encoding='utf-8') as f:
    json.dump(all_strings, f, ensure_ascii=False, indent=2)
  with open(file_translate_path, 'w', encoding='utf-8') as f:
    json.dump(all_translate_strings, f, ensure_ascii=False, indent=2)
​
​
# 读取file_path、file_translate_path文件,并将其json数组以 如下格局写入ustring_output.json文件
def read_ustring_local_file(file_path, file_translate_path):
  with open(file_path, 'r', encoding='utf-8') as f:
    file_path_data = json.load(f)
  with open(file_translate_path, 'r', encoding='utf-8') as f:
    file_translate_path_data = json.load(f)
  return file_path_data, file_translate_path_data
​
​
# 将all_strings、all_translate_strings 以 "中文"="English" 的办法写入file_final_path.json文件
def write_final_ustring_local_file(file_final_path, all_strings, all_translate_strings):
  with open(file_final_path, 'w', encoding='utf-8') as f:
    for index in range(len(all_strings)):
      # 运用原始字符串表明法,repr(),以便在all_translate_strings中的\n不被转义
      # 运用了repr()函数来获取all_strings[index]和all_translate_strings[index]的可打印表明。
      # 这将保证\n不被转义。可是,repr()函数还会在字符串的最初和结束增加额定的单引号。
      # 为了防止这个问题,您能够在format()函数中运用切片操作来去掉这些额定的单引号。
      f.write(r'"{}" = "{}";'.format(
        repr(all_strings[index])[1:-1], repr(all_translate_strings[index])[1:-1]) + '\n')
​
​
# python3 find_chinese_utrings.py HDFindStringDemo
if __name__ == '__main__':
  print("start")
​
  macho_file = sys.argv[1]
  ustring_file = f"{macho_file}_ustring.txt"
  output_file = f"{macho_file}_ustring_output.json"
  file_translate_path = f"{macho_file}_ustring_output_translate.json"
  file_final_path = f"{macho_file}_ustring_output_final.json"##########  翻译+读写全流程 ##########
  # 翻译+读写全流程step1:将otool指令输出的内容写入文件
  otool_to_file(macho_file, ustring_file)
​
  # 翻译+读写全流程step2:读取ustring_file文件,并将其json数组,然后翻译,回来原始数据和翻译后数据
  all_strings, all_translate_strings = read_ustring_file(ustring_file)
​
  # 翻译+读写全流程step3:将翻译后的all_strings、all_translate_strings写入output_file、file_translate_path文件
  write_ustring_local_file(
    output_file, file_translate_path, all_strings, all_translate_strings)
​
  # 翻译+读写全流程step4:将all_strings、all_translate_strings 以 "中文"="English" 的办法写入file_final_path.json文件
   write_final_ustring_local_file(
     file_final_path, all_strings, all_translate_strings)
  ##########  翻译+读写全流程 ############# 因为在线调用google翻译api一定量会收费,所以当履行完【翻译+读写全流程】后,读取本地文件来生成想要的格局文件 ###
  ##########  本地文件读取翻译 ##########
  # 本地文件读取step1 读取output_file、file_translate_path文件,并将其json数组以 如下格局写入ustring_output.json文件
  # all_strings, all_translate_strings = read_ustring_local_file(
  #   output_file, file_translate_path)# 本地文件读取step2:将all_strings、all_translate_strings 以 "中文"="English" 的办法写入file_final_path.json文件
  # write_final_ustring_local_file(
  #   file_final_path, all_strings, all_translate_strings)
  ##########  本地文件读取翻译 ##########print("end")
​

运用说明: 请保证二进制文件 HDTranslateModule_Example 和脚本位于同一目录下,然后依照以下过程操作:

  • 翻开指令行东西(如:终端、指令提示符等)。
  • 运用 cd 指令切换到包括二进制文件和脚本的目录。
  • 履行相应的指令来运转脚本。

留意: 因为脚本需求实时调用 Google 翻译 API,请保证在履行脚本时能正常拜访以下网址:cloud.google.com/docs/authen…

在运用过程中,假如遇到任何问题或需求进一步的协助,请随时发问,我会竭诚为你供给支撑。

✗ python3 find_chinese_cstrings.py HDTranslateModule_Example
start
翻译进展: 1 / 4
翻译进展: 2 / 4
翻译进展: 3 / 4
翻译进展: 4 / 4
end
​
✗ ls
HDTranslateModule_Example # 二进制文件
HDTranslateModule_Example_cstring.txt # otools得到的十六进制数据
HDTranslateModule_Example_cstring_output.json # 从cstring.txt中提炼出中文字符串,并输出成json数组
HDTranslateModule_Example_cstring_output_translate.json #从cstring_output.json翻译成英文
HDTranslateModule_Example_cstring_output_final.json #将cstring_output.json和cstring_output_final.json 依照iOS所需求的言语包格局输出成终究文件
find_chinese_cstrings.py # 处理 __cstring 节中的中文字符串的脚本
find_chinese_ustrings.py # 处理 __ustring 节中的中文字符串的脚本# 终究的输出文件
✗ cat HDTranslateModule_Example_cstring_output_final.json 
"Swift 中" = "in Swift";
"是为 中" = "is for the";
"你好" = "Hello";
"Hello 你好" = "hello hello";

恭喜你完结了第一部分的作业!在实践中需求任何协助或建议,请随时向我发问评论沟通

二、工程装备

本部分的作业相对常规且简略,网络上的相关资料也较为丰厚。以下是一些建议的参阅资料和大致的作业内容:

参阅资料:

  1. 3分钟完结iOS言语本地化/国际化(图文详解):本文详细介绍了怎样完结 iOS 项目的本地化和国际化。
  2. iOS开发 国际化/多言语适配:本文评论了在进行国际化过程中或许遇到的一些问题及其解决计划。

作业内容:

  1. 项目支撑国际化:为项目增加多言语支撑,包括在项目设置中启用国际化选项以及创立对应的本地化文件。
  2. App 称号国际化:依据不同的言语环境,为 App 设置不同的显现称号。
  3. 发动页国际化:依据不同的言语环境,为 App 供给不同的发动页。
  4. Info 文件称号国际化:为 Info.plist 文件中的各项设置供给多言语支撑。
  5. 字符串国际化:运用 NSLocalizedString 函数将代码中的字符串进行本地化处理,以习惯不同的言语环境。
  6. 图片/资源国际化:为不同的言语环境供给相应的图片和资源文件。
  7. Storyboard/XIB 国际化:为 Storyboard 和 XIB 文件中的 UI 元素供给多言语支撑。

经过以上过程,您能够为您的 iOS 项目完结本地化和国际化,以习惯全球不同区域的用户需求。

三、代码完结

HDTranslateModule 是一个用于完结多言语支撑、言语切换以及翻译后案牍处理的组件化库。以下是该库的首要特性,详细细节不再打开。

  1. 多言语支撑:HDTranslateModule 支撑多种言语,能够依据用户的需求和设备设置主动习惯不同的言语环境。
  2. 言语切换:HDTranslateModule 供给了简略易用的 API,能够在运转时动态切换 App 的显现言语,无需重启运用。
  3. 翻译后案牍处理:HDTranslateModule 能够处理翻译后的案牍,保证在不同言语环境下,案牍的显现作用和语义都能契合预期。
  4. 组件化:HDTranslateModule 将多言语支撑、言语切换和翻译后案牍处理等功能封装成了一个独立的组件,能够方便地集成到现有的 iOS 项目中。
  5. 易于扩展:HDTranslateModule 的规划允许开发者依据自己的需求轻松地增加新的言语支撑和功能。

在运用过程中,假如遇到任何问题或需求进一步的协助,请随时反馈。

#import <Foundation/Foundation.h>
​
​
typedef NS_ENUM(NSInteger, JDLTLanguageMode) {
  JDLTLanguageSyetem = 0,
  JDLTLanguageEN,
  JDLTLanguageZH
};
​
NS_ASSUME_NONNULL_BEGIN@interface JDLTTranslateManager : NSObject
​
+ (instancetype)shared;
​
​
/// 自界说设置本地言语包的资源途径
/// - Parameters:
///  - bundle: 言语包地点的bundle
///  - tableName: 言语包地点的bundle的文件称号
- (void)customTranslateBundle:(NSBundle *)bundle tableName:(NSString *)tableName;
​
// 获取用户在App内选中的言语,假如用户未选中默许是System,则运用体系言语
- (JDLTLanguageMode)selectedLanguage;
- (void)updateLanguage:(JDLTLanguageMode)mode;
​
// 获取用户在体系选中的言语
- (JDLTLanguageMode)systemSelectedLanguage;
​
// 依据selectedLanguage和systemSelectedLanguage终究运用到的言语,只能或许是EN和ZH
// 假如用户在App挑选优先运用,不然运用体系言语
- (JDLTLanguageMode)realUsedLanguage;
​
​
// 将 text 翻译成对应的言语
+ (NSString *)translateText:(NSString *)text;
​
@end

1、手动完结

运用 Foundation 结构自带的 NSLocalizedString(key, comment) 宏依据 Key 获取对应的字符串,然后赋值给代码中的字符串。这种办法需求手动为每个需求国际化的字符串增加对应的 Key 和翻译。

2、半主动化完结

  • Hook 文本显现,一致替换

    在前面搜集了多言语包的基础上,经过 AOP(面向切面编程)来 Hook 常用文本显现的 API,然后主动运用国际化案牍进行替换。这儿搜集了常用的案牍显现控件:UILabel、UITextField、UITextView、UITabBarItem、UINavigationItem、UIViewController ,然后别离对显现案牍的 API 进行 hook,详细完结参阅 JDLTTranslateHook 类。

  • 手动替换

    • JDLTAttributedString:富文本支撑,能够处理富文本中的国际化案牍。
    • Format 习惯:参阅 HDTranslateModule 中的 demo 代码,处理字符串格局化时的国际化问题。
    • 文本对应控件的宽高习惯:依据不同言语环境下的案牍长度和显现作用,调整控件的宽高以习惯国际化案牍。

经过以上办法,能够完结 iOS 项目的国际化支撑,使得 App 在不同言语环境下都能正常显现对应的案牍。一起,半主动化完结能够减轻开发者的作业量,进步开发功率。

四、服务器

关于一个完好的项目来说,除了本地 hardcode 需求国际化外,服务器下发的数据也需求支撑国际化。尽管服务器内部的多言语装备这儿不打开,但咱们能够评论一下如安在 App 内或手机体系切换言语时,将当时挑选的言语告知服务器,以便运用对应的言语化数据展现。

以下是一些建议:

  1. 在 API 恳求中增加言语参数:为了让服务器知道客户端当时运用的言语,能够在每个 API 恳求中增加一个表明当时言语的参数。例如,能够在恳求的 URL 中增加一个 lang 参数,或许在恳求头中增加一个 Accept-Language 字段。这样,服务器就能够依据这个参数回来对应言语的数据。
  2. 在用户登录或注册时发送言语信息:假如你的运用需求用户登录或注册,能够在用户登录或注册时将当时运用的言语发送给服务器。服务器能够将这个信息保存在用户的账户信息中,以便在后续的恳求中运用。
  3. 监听言语切换事情:当用户在 App 内或手机体系中切换言语时,能够监听这个事情,并将新的言语信息发送给服务器。这样,服务器就能够实时更新用户的言语设置,并在后续的恳求中回来正确的言语数据。
  4. 缓存服务器下发的多言语数据:为了进步功能和削减网络恳求,能够在客户端缓存服务器下发的多言语数据。当用户切换言语时,能够优先从缓存中获取对应的数据,假如缓存中没有,则向服务器恳求。
  5. 处理服务器回来的多言语数据:在客户端,需求正确处理服务器回来的多言语数据。例如,能够运用相似于本地化字符串的办法来处理服务器回来的文本,以保证在不同言语环境下都能正常显现。

经过以上办法,能够完结服务器下发数据的国际化支撑,使得 App 在不同言语环境下都能正常显现服务器回来的数据。一起,这些办法能够与本地的国际化完结相结合,供给一个完好的多言语支撑计划。

五、示例作用

本文中提到的脚本、国际化API的AOP功能以及相应的示例代码现已开源。请拜访 HDTranslateModule 了解更多。如有疑问或在运用过程中遇到问题,请在 issues 中提出。

六、参阅资料

iOS开发 国际化/多言语适配

3分钟完结iOS言语本地化/国际化(图文详解)

iOS文本的多言语适配和实践

iOS CocoaPods 子模块完结多言语

Mach-O学习

HiBox

otools简介

Google Clound Console

github-copilot