美文网首页
iOS-项目快速支持多语言国际化

iOS-项目快速支持多语言国际化

作者: xing3523 | 来源:发表于2022-01-16 19:23 被阅读0次

前言场景

已存在的项目(中文)突然要支持多语言切换或者国际化了。前段时间突然遇到这么一个比较急的需求。在经历了半脚本半人工体力活的加班修改之后(眼睛都快花了),匆匆完成,内心对结果并不满意,其中还是有着一些重复的,不必要的翻译,最后再去查漏补缺去重。后面事情告一段落。静下心来,就再研究了下方案的优化,把纯体力活尽量干掉。

整理主要步骤

  1. 获取汉字及相关页面文件
  2. strings文案生成及中文中转映射key
  3. 项目代码中文添加本地化方法
  4. xib/storyBoard的检查处理
  5. 检查验证,查看实际效果

简单模拟项目demo准备

  1. 添加设置切换多语言以便查看效果。
  2. 预先增加两个已国际化的汉字
  3. 不同页面同文案
  4. 添加中文注释、断言、log输出、特殊字符换行等场景
  5. xib内包含中文文案
    运行效果如下图

具体流程

1. 获取汉字及相关页面文件

汉字的获取整理是第一步,这一块我们可以用脚本去处理,但是过程中需要注意的有一些不必要处理的要过滤掉。

  • 注释、log输出、断言等
  • 不需要处理的文件包括特定页面、已处理过的页面等。
  • 已经添加国际化的中文。
  • 去重,仅针对翻译,后续替换时需要替换所有。

这里用的是python脚本,主要作用是获取当前文件夹下特定文件下的中文信息。后面完整项目里有,部分代码如下。
整行的过滤部分如下。str为逐行读取的内容。这里过滤条件可视项目情况调整。

                    # log assert类型 忽略
                    if  str.startswith("//") or str.startswith("DYYLog") or str.startswith("NSLog") or str.startswith("print") or str.startswith("NSAssert") or str.startswith("assert"):
                        continue
                    if str.startswith("/*"):
                        isComment = True
                    if str.endswith("*/"):
                        isComment = False
                    if isComment:
                        continue

具体汉字匹配后,已本地化的也需要过滤掉。可视项目情况调整。

                    # 匹配包含中文
                    matchObjs = re.findall(u'"[^"]*[\u4E00-\u9FA5]+[^"\n]*?"', str, re.M|re.S)
                    if matchObjs and len(matchObjs) > 0:
                        for cnStr in matchObjs:
                            # 已本地化则忽略
                            locali1 = "JJLocalized(" + cnStr
                            locali2 = "JJLocalized(@" + cnStr
                            locali3 = cnStr + ".localizedString"
                            if locali1 in str or locali2 in str or locali3 in str:
                                continue

直接运行,可以看到xib、swift、m文件里的都有过滤出来。

运行效果

同时同文件夹下生成了三个文件。

  • 第一个用于接下来的第二步
  • 第二个文件记录需要处理的xib及storyboard
  • 第三个内容是汉字对应的文件完整路径,后续会用到

第一步完成

2. strings文案生成及中文中转映射key

py_cnStr.txt加入项目中。其内容如下,--*--仅作为分隔符。接下来基于以下内容生成strings内容。

ViewController1.swift--*--"晚上好"
ViewController.m--*--"晚上好"
ViewController1.swift--*--"早安"
SettingVC.swift--*--"切换英文"
SettingVC.swift--*--"切换中文"
ViewController.m--*--"早上好"
ViewController1.swift--*--"“特殊字符”%@%d个"
ViewController.m--*--"中午好"
AppDelegate.m--*--"测试2-(Swift)"
ViewController1.swift--*--"有"
ViewController1.swift--*--"测试\n换行"
AppDelegate.m--*--"测试1-(OC)"
ViewController1.xib--*--"xib标题"
ViewController1.swift--*--"晚安"

本着stringskey尽量不使用中文的原则,我们需要中文key做一次格式化处理(后续新增文案应规范命名)。如下:

/// 检测并生成本地化文案
- (void)generationLocalizationStr {
    NSString *path = [[NSBundle mainBundle] pathForResource:@"py_cnStr" ofType:@"txt"];
    NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    NSArray *arr = [str componentsSeparatedByString:@"\n"];
    int num2 = 0;
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, true) objectAtIndex:0];
    NSString *enStringsPath = [docPath stringByAppendingPathComponent:@"waitingTranslation_en.strings"];
    NSString *cnStringsPath = [docPath stringByAppendingPathComponent:@"waitingTranslation_cn.strings"];
    // 英文 strings
    NSMutableArray *enStrArray = [@[] mutableCopy];
    // 中文 strings
    NSMutableArray *cnStrArray = [@[] mutableCopy];
    NSMutableArray *transStrArray = [@[] mutableCopy];
    // 包含中文字符串正则
    NSString *regex = @"[^\"]*[\u4E00-\u9FA5]+[^\"\n]*?";
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
    NSMutableDictionary *numDic = [NSMutableDictionary new];
    
    NSMutableDictionary * keyValue = [@{} mutableCopy];
    NSMutableSet *set = [NSMutableSet new];
    for (NSString *lineStr in arr) {
        NSArray *array = [lineStr componentsSeparatedByString:@"--*--"];
        NSString *originStr = array.lastObject;
        if (originStr.length < 2) {
            continue;
        }
        NSString *txt = [originStr substringWithRange:NSMakeRange(1, originStr.length-2)];
        // 用于检测是否已经有对应翻译了
        NSString *localizedTxt = [txt stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"];
        localizedTxt = [localizedTxt stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
        localizedTxt = [localizedTxt stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""];
        if ([set containsObject:localizedTxt]) {
            continue;
        }
        NSString *tran = localizedTxt.localizedString;
        BOOL hasCN = [predicate evaluateWithObject:tran];
        NSString *fileName = [array.firstObject stringByDeletingPathExtension];
        NSString *hashCode = [NSString stringWithFormat:@"%lu",fileName.hash];
        hashCode = [hashCode substringToIndex:6];
        NSNumber *num = numDic[fileName];
        if (!num) {
            num = @0;
        }
        int num1 = [num intValue];
        NSString *classPre = fileName;
        classPre = [classPre stringByReplacingOccurrencesOfString:@"ViewController" withString:@"VC"];
        classPre = [classPre stringByReplacingOccurrencesOfString:@"Controller" withString:@"C"];
        classPre = [classPre stringByReplacingOccurrencesOfString:@"View" withString:@"V"];
        NSString *key = [NSString stringWithFormat:@"%@_%@_%d",classPre,hashCode,num1];
        if (hasCN) {
            NSLog(@"-->未多语言化%d:%@",num1,originStr);
            [enStrArray addObject:[NSString stringWithFormat:@"\"%@\" = \"\";//%@\n",key,txt]];
            [cnStrArray addObject:[NSString stringWithFormat:@"\"%@\" = \"%@\";\n",key,txt]];
            num1++;
            numDic[fileName] = @(num1);
            keyValue[key] = txt;
            [set addObject:txt];
        } else {
            NSLog(@"-->已多语言化%d:%@-%@",num2,originStr,tran);
            num2++;
        }
    }
    [enStrArray sortUsingSelector:@selector(compare:)];
    [cnStrArray sortUsingSelector:@selector(compare:)];
    [transStrArray sortUsingSelector:@selector(compare:)];
    NSString *enStr = [enStrArray componentsJoinedByString:@""];
    NSString *cnStr = [cnStrArray componentsJoinedByString:@""];
    [self writeStr:enStr toPath:enStringsPath];
    [self writeStr:cnStr toPath:cnStringsPath];
    NSString *keyValuePath = [docPath stringByAppendingPathComponent:@"keyValue.txt"];
    NSArray *sortArray = [keyValue.allKeys sortedArrayUsingSelector:@selector(compare:)];
    NSMutableString *keyValueStr = [@"" mutableCopy];
    for (NSString *key in sortArray) {
        [keyValueStr appendFormat:@"\"%@\": \"%@\",\n", keyValue[key],key];
// OC
//      [keyValueStr appendFormat:@"@\"%@\": @\"%@\",\n", keyValue[key],key];
    }
    [self writeStr:keyValueStr toPath:keyValuePath];
}

这里key的规则为文件名(缩减)+文件名hashCode(前6位)+数字组成。同时对其中汉字去重并过滤掉已国际化的场景,生成strings的过程中,先进行了一次排序,内容会更整齐有序。放在AppDelegate中调用,运行项目,控制台输出未/已国际化的信息同时在沙盒生成三个文件,将其中中文strings文件内容拷入对应Localizable.strings。英文strings如图,可以丢给专业人士填充翻译后再拷入对应英文strings,检查下特殊字符"添加转义。keyValue.txt内容拷入LanguageManager.swift中作为中转字典。如图,第二步完成。


3. 项目代码中文添加本地化方法

对应关系都好了,就剩下代码处的调用了。这里也是脚本添加一步到位,唯一需要注意的是整文件查找替换时已添加国际化的中文办法区分,所以先替换,再替换还原多替换的场景。代码不多,如下:

#-*- coding:utf-8-*-
#处理中文字符的情况
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
 
import os
import codecs
 
project_path = os.path.split(os.path.realpath(__file__))[0]

def logYellow(str):
    print("\033[36m%s\033[0m"%(str))

def updateFile(file,old_str):
    logYellow(file)
    with open(file,"r") as f:
        file_data = f.read()
        f.close
    new_str = old_str + ".localizedString"
    # 替换
    new_file_data = file_data.replace(old_str,new_str)
    # 已处理场景需还原
    new_file_data = new_file_data.replace(new_str + ".localizedString",new_str)
    new_file_data = new_file_data.replace("JJLocalized(%s)"%(new_str),"JJLocalized(%s)"%(old_str))
    new_file_data = new_file_data.replace("JJLocalized(@%s)"%(new_str),"JJLocalized(@%s)"%(old_str))
    
    with open(file,"w") as f:
        f.write(new_file_data)
        f.close
    logYellow("已更新" + old_str)

separatorStr = "--*--"
with open(os.path.join(project_path, "py_cn_wholePath.txt"), 'r+') as f:
    lineList = f.readlines()
    f.close()
    for str in lineList:
        str = str.decode()
        str = str.strip()
        path_info = str.split(separatorStr)
        cnStr = path_info[1]
        updateFile(path_info[0],cnStr)

执行脚本,第三步完成。

4. xib/storyBoard的检查处理

如果是纯代码的项目,那这一步可以跳过了。打开前面生成的文件py_xibCnStr.xlsx,这里只能去挨个检查xib了,如果xib中控件标题都已经是在代码中设置过的,可以把其中文文案删掉或者替换成其它值。在需要保持中文以便更好理解布局的情况下,也可以把该xib文件名加入到第一步脚本中的ignoreFileNames中,没有连线控件的手动拉线设置文案(又是体力活,这里只能祈祷这种场景比较少了)。

5. 检查验证,查看实际效果

前面四步完成后,可以重新运行下第一次的脚本看看未处理的中文是不是清空了。自测下就可以交给测试了,大功告成。

总结

总结下来,这方案执行下来简单步骤就是,执行脚本->拷贝+翻译->执行脚本->检查xib/storyBoard->完成。在没有或较少xib/storyBoard文案的情况下,开发这边的工作小半天基本就足够了。

链接

完整Demo项目链接

相关文章

  • iOS-项目快速支持多语言国际化

    前言场景 已存在的项目(中文)突然要支持多语言切换或者国际化了。前段时间突然遇到这么一个比较急的需求。在经历了半脚...

  • python(09)实践Django-国际化

    Django 支持国际化,多语言,而本项目做国际化完全是因为个人不喜欢在页面上写N多中文的描述。 setting配...

  • 快速找出项目中未国际化的文本

    痛点 对于支持多语言的 APP 来说,国际化非常麻烦,而找出项目中未国际化的文字非常耗时(如果单纯的靠手动查找)。...

  • 国际化:i18n实现多语言

    如果项目需要多语言的支持,我们需要做国际化 使用 vue-i18n 来实现多语言的界面 安装 关于语言包,我们有几...

  • iOS国际化

    添加应用程序多语言支持的国际化文件 工程根目录 -> 选择PROJECT下的项目 -> 选择Info选项卡 -> ...

  • 多语言生成工具

    前言 由于项目用了国际化支持15种语言,做多语言的时候很不方便于是写了个小工具,按照固定的格式一键生成多语言 准备...

  • Vue3 + Element Plus + i18n 国际化

    1 前言 本篇旨在 Vue3 + Element Plus 国际化配置,支持多语言切换 2 Vue3 国际化 2....

  • 国际化方案

    标签:多语言 i18n iplas 背景 应泰方要求,运营系统、网上商城以及移动端服务平台等项目都要做国际化,支持...

  • react-intl 实现多语言

    0. 前言 最近在项目中添加了语言国际化,多语言的功能。 React-intl是雅虎的语言国际化开源项目Forma...

  • iOS 开发之 国际化/本地化 配置

    一、本地化与国际化 1.1、多语言支持的 国际化 和 本地化 有什么区别?答:i18n:国际化(在其他语言叫国际化...

网友评论

      本文标题:iOS-项目快速支持多语言国际化

      本文链接:https://www.haomeiwen.com/subject/jomfhrtx.html