当前位置: 首页 > 科技观察

iOS文字多语言适配与实践

时间:2023-03-13 19:09:32 科技观察

背景产品在多个国家使用,产品方希望有更好的多语言用户体验,因此设计师提供了多种字体适配指定语言。基于以上背景,客户需要快速提供解决方案并上线。字体包多语言适配及实际需求分析首先,在了解了产品需求和设计方案后,结合业务研发人员的痛点,梳理出以下需求。产品及设计要求不同的语言有不同的对应字体包。全局字体默认使用设计者指定的字体包。某些语言的字体包缺少某些权重版本,需要回退到下一个权重版本。有一些特殊文字没有使用全局字体包(例如:中文,有自己的字体包,与语言环境无关)。产品迭代需要快速支持扩张,最小化研发投入成本。开发者需要的字体包资源开发的痛点和需求有通用组件(其他业务线都在用,Palfish目前有50+个公共业务组件),不能修改通用组件。仅受shell项目支持并依赖于字体包。字体包资源的来源应该是灵活的。综上所述,产品和设计需求强调字体适配的全球化、多样性和可扩展性,而研发侧重于解耦、职责单一和灵活性。技术设计分析后,首先确定技术框架的分层。如图所示,垂直和水平模块分为3层。1.基础组件提供核心实现,支持需求扩展2.业务组件(无相关修改)3.Shell项目提供资源包和代理。FontPackage组件负责什么?FontPackageManager负责绑定代理获取资源包和控制流程逻辑。FontPackageExtension,负责AOP,增加文本属性,满足特殊场景的多样性。FontPackageModel,映射了字体包资源的配置信息,明确了使用协议。上层业务可以通过添加和调整参数来配置字体包资源。shell项目的资源包配置env:国际编码,default表示设计者指定的默认字体。请注意,某些国际代码代表一种语言。比如英文有en-US、en-GB等多种编码,需要统一为en。font:字体粗细类型,0:light,1:medium,2:bold。默认情况下,斜体替换为中等名称:字体源文件的名称。例如:GothamRndSSm-Medium备注:因为设计者只需要3个字重,所以默认的字重与系统提供的UIFontWeight不一致。//shell工程中的配置文件,反序列化后传回FontPackage层//appfont.json{"list":[{"env":"vi","note":"越南语,按照国际编码:vi,vi-VN.FontPackageManager判断国际编码对应","data":[{"font":0,"name":"genjyuu_light(越文细)"},{"font":1,"name":"genjyuu_medium(越南中文)"},{"font":2,"name":"genjyuu_bold(越南粗体)"}]},{"env":"default","note":"其他语言默认使用字体,但优先考虑设备的国际编码匹配字体包","data":[{"font":0,"name":"GothamRndSSm-Light"},{"font":1,"name":"GothamRndSSm-Medium"},{"font":2,"name":"GothamRndSSm-Bold"}]}]}添加字体包和配置文件,以及冷启动过程:冷启动流程图技术开发FontPackage功能组件共3个类,200+行代码。首先冷启动时,FontPackage会根据json配置,缓存语言码匹配的字体包资源Model。然后使用runtimehookUIFont类的几个构造函数来替换构造函数的fontName参数。目前确定了5个构造函数://processed+(UIFont*)systemFontOfSize:(CGFloat)fontSize;+(UIFont*)systemFontOfSize:(CGFloat)fontSizeweight:(UIFontWeight)weight;+(UIFont*)boldSystemFontOfSize:(CGFloat)fontSize;+(UIFont*)italicSystemFontOfSize:(CGFloat)fontSize;+(UIFont*)fontWithName:(NSString*)fontNamesize:(CGFloat)fontSize;最后统一使用+fontWithName:size:函数初始化,fontName为自定义字体包。功能-fontpackage_name:将原来的fontName替换成对应的自定义字体包。//FontPackageExtension.m//UIFont+FontPackage.m#pragmamark-Hook+(UIFont*)xxxFontPackage_systemFontOfSize:(CGFloat)fontSizeweight:(UIFontWeight)weight{NSString*fontName=@"";if(weight==UIFontWeightMedium){fontName=@"medium";}elseif(weight>UIFontWeightMedium){fontName=@"bold";}return[selffontWithName:fontNamesize:fontSize];}+(UIFont*)xxxFontPackage_italicSystemFontOfSize:(CGFloat)fontSize{//斜体默认mediumreturn[selffontWithName:@"medium"size:fontSize];}+(UIFont*)xxxFontPackage_boldSystemFontOfSize:(CGFloat)fontSize{return[selffontWithName:@"bold"size:fontSize];}+(UIFont*)xxxFontPackage_systemFontOfSize:(CGFloat)fontSize{return[selffontWithName:@""size:fontSize];}+(UIFont*)xxxFontPackage_fontWithName:(NSString*)fontNamesize:(CGFloat)fontSize{fontName=[selffontpackage_name:fontName];返回[selfxxxFontPackage_fontWithName:fontNamesize:fontSize];}+(NSString*)fontpackage_name:(NSString*)fontName{fontName=[fontNamelowercaseString];FontPackageFontreplaceFont=FontPackageFontLight;//默认lightif([fontNamecontainsString:@"medium"]){replaceFont=FontPackageFontMedium;}elseif([fontNamecontainsString:@"bold"]){replaceFont=FontPackage}/FontBold;/匹配替换字体NSString*replaceFontName=[[FontPackageManagershareInstance].fontPackageInfo.dataMapobjectForKey:@(replaceFont)];returnreplaceFontName;}文本信息的多语言适配与实践海外用户的语言本地化也是一个重要的产品功能,但是很多组件确实做到了开发之初未预留本地化扩展接口。客户端需要提供一个优雅的解决方案来处理这个问题。需求分析1.产品及设计需求语言本地化不提供本地化语言,默认使用产品指定的语言快速支持新的语言本地化2.技术需求接入成本低,无需修改解耦成熟组件,其他组件不需要依赖这个功能。技术设计纵向分层和横向模块如图分为3层:1.基础组件提供需求扩展2.业务组件(基本不需要修改,如果有特殊属性需求可以依赖基础components)3.shell项目提供资源包和资源包更新。LocalizedString组件负责什么?LocalizedString,负责文本本地化适配。LocalizedTool负责语言包的配置、读取和替换。LocalizedExtension,负责AOP,补充某些属性。语言包目录如下:在语言包目录中可以看到,语言包是根据语言编码命名的,方便在使用过程中及时定位和读取对应的文件(有多种编码语言,统一使用它们的基础类)。同时,shell项目中会刷新本地语言包。应用启动后会检查是否有可用的新语言包,如果有则保证数据同步。配置语言包后,需要在冷启动时初始化LocalizedString组件。启动时的组件任务流程图如下:冷启动流程图技术开发考虑到字符串最终会显示在UILabel上,[UILabelsetText:]将是设置显示文本的唯一方式。所以我们对[UILabelsetText:]进行了hook和扩展,其内部运行流程图如下:AOP流程图LocalizedString组件包括NSString和UILabel类,扩展了属性。具体代码如下:@interfaceUILabel(Localized)@property(nonatomic,assign)BOOLisAutoLocalized;///<设置文本是否自动转换为本地化语言,默认为YES@end@interfaceNSString(Localized)@property(nonatomic,copy)NSString*oriStr;///<最后一个本地化字符串的原值@property(nonatomic,copy)NSString*localizedStr;///