iOS国际化(多语言)漫谈

目录

概览

各类资源的国际化

1.文本html

2.图片java

3.nibpython

4.其余资源git

特定模块/功能的国际化

1.APP图标github

2.应用名与权限提示shell

3.启动图(LaunchScreen)json

4.app调系统资源页面的国际化swift

5.涉及服务端数据内容的国际化数组

app内更改语言

1.更改语言的方案xcode

2.未作国际化的旧项目迁移


概览

国际化的本质是为每种语言单独提供一份资源(文本,图片,音视频等)。 本文术语 本地化:指单独一种语言 国际化:多种语言的合体

在工程的Localizations中每新增一种语言,xcode会提示咱们生成对应的文件,然后也生成了对应的文件夹。

新增本地化语言

新增中文简体后生成的文件夹与文件
两种语言

iOS为这些文件提供了快捷的国际化方案。对于字符串资源文件生成相应语言的字符串文件放在对应的文件夹中,而XIB和StoryBoard则可选整个文件和字符串资源。具体的方案后续讨论。

image.png

若是忘了添加某个资源的具体语言文件,或者后续增长的资源文件,能够经过该资源文件的 文件监察器File Inspector 中的 Localize按钮添加。

image.png

Localizable.stringsInfoPlist.strings在国际化方案中是常见的。

  • Localizable.strings 这个是读取多语言字符串方法NSLocalizedString默认会加载的文件,若是自定了这个文件名字,则使用NSLocalizedStringFromTable指定table便可
  • InfoPlist.strings info.plist的字符串国际化文件,系统默认读取,名字固定

各类资源的国际化

1. 文本

添加了多语言的字符串资源文件处于可展开状态,子级有着相应语言的副本。咱们把相应语言的文本放在副本里面就好了。

字符串文件中具体的格式是"key" = "value";笔者发现写成key = "value";也是不会有问题的(可是不加双引号不能有空格,会识别不了),好比应用名称的本地化:

应用名称的国际化
看截图,最终的应用名称是后面那一个,说明有效且 被覆盖了

使用NSLocalizedString(key, comment)来读取字符串。第二个参数comment能够是nil,能够是一段为空的字符串,也能够是对key的注释。

看一下这个方法的实现

#define NSLocalizedString(key, comment) \
	    [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:nil]

...
/* Method for retrieving localized strings. */
- (NSString *)localizedStringForKey:(NSString *)key value:(nullable NSString *)value table:(nullable NSString *)tableName NS_FORMAT_ARGUMENT(1);
复制代码

localizedStringForKey:value:table:是NSBundle的对象方法,因而可知,能够加载不一样的包名和字符串资源表的字符串。也提供了相关宏

#define NSLocalizedStringFromTable(key, tbl, comment) \
	    [NSBundle.mainBundle localizedStringForKey:(key) value:@"" table:(tbl)]
#define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \
	    [bundle localizedStringForKey:(key) value:@"" table:(tbl)]
复制代码

重要:当找不到相应的语言strings或value时会直接返回key,若是你用英文的内容做为Key,甚至均可以不用维护英文本地化。

2. 图片

Xcode5以后图片资产(Assets.xcassets)再也不支持国际化了,单张图片资源的方式仍然可用,使用方式同字符串。将须要国际化的图片拖入工程,选择文件监察器,点击Localize并选择多个语言后便可生成如如字符串资源同样的可展开状态了。要配置不一样语言的图片前往该语言目录替换便可。

图片国际化

这个系统提供的方案支持Interface Builder版的nib资源国际化(固然Localizable Strings 方式很显然只是字符串而已),也支持+ imageNamed:加载方式的国际化。

还有个只适合用于纯代码,不支持nib的方式。就是把图片的名称作字符串国际化,而后再使用+ imageNamed:加载。

另外,针对+imageWithContentsOfFile:的加载方式,能够经过分类的方式,根据语言修改相应的加载路径。

PS:图片的国际化带来的是多份的副本,若是国际化中须要作的具体本地化语言较多,必然形成包的急剧增大。因此建议能避免就避免。

3. nib文件(XIB和StoryBoard)

StoryBoard本地化

nib文件的国际化方式上面提到有两种方式:

  • 只作字符串资源(Localizable Strings)
  • 整个nib文件(Interface Builder CocoaTouch XIB/StoryBoard)

nib文件有一个大坑,画重点

各个本地化的nib修改不会同步,nib的修改也不会同步至字符串资源

也就是说第一种方案每增长一种语言就得再画一个页面,本地化语言多的话,额外工做量惊人。 第二种方案,也得本身将新增的字符串拷贝出来。

若是要把更新同步的过程作成自动化,固然也是字符串方便一点。用整个nib文件作国际化比字符串资源方式强的地方也就是如下两点了

  • 图片
  • 不一样本地化不一样布局

通常也不会去根据不一样本地化语言去修改布局,阿拉伯国家也就改个文本方向,这个特性视图自带,全局修改便可。

[UIView appearance].semanticContentAttribute = UISemanticContentAttributeForceRightToLeft;
复制代码

nib中的图片资源用代码也可轻松解决,综上笔者建议针对nib文件只作字符串资源(Localizable Strings)。

因此,上面的图片方案选了支持nib的方案,而后如今不用了 🤣 意不意外?惊不惊喜?

不说这个了,来讲说怎么作nib新增的须要国际化的字符串同步吧。

Xcode为咱们提供了ibtool工具来生成nib的strings文件

ibtool FirstViewController.xib --generate-strings-file lanuchScreen.strings 
ibtool Main.storyboard --generate-strings-file storyBoard.strings
复制代码

可是ibtool生成的strings文件是BaseStoryboard的strings(默认语言的strings),且会把咱们原来的(甚至是翻译好的)strings替换掉。仍是本身用脚原本作这个工做靠谱点,再借助Xcode 中 Run Script 来运行这段脚本,更新的时候build一下就好了。 具体的脚本代码在最后的Demo中,Run Script的添加方法: Target->Build Phases->New Run Script Phase,在shell里面写入下面指令

python ${SRCROOT}/${TARGET_NAME}/RunScript/AutoGenStrings.py ${SRCROOT}/${TARGET_NAME}
复制代码

4. 其余资源

其余资源国际化

其余资源(json、音视频、压缩包等等)的国际化方式与图片资源相同,读的时候读 主Bundle 便可,不一样语言环境下iOS自动切。

剧透:后续的应用内切换语言会利用主Bundle的这一特性。

5. 涉及服务端数据内容的国际化

这部份内容的国际化,可考虑如下两种方案:

  • 服务端不关心当前用户的本地化语言,返回全部适配的本地化内容,由客户端本身控制显示
  • 服务端获取当前用户的本地化信息,返回相应的本地化内容

第一种适合适配本地化语言较少的状况,好比只适配中英文;而第二种,对配置信息的依赖比较高,服务端须要修改的内容也是比较多的。

若是使用第一种方案,一些经常使用的报错信息或者其余业务成功等信息能够整理成特定的code由客户端直接作解析,减小信息传输量(虽然相比单个本地化仍是会大不少)。

若是使用第二种方案,能够在请求头中带入当前用户的本地化信息,服务端根据这个判断,能够简便得多。


特定模块/功能的国际化

1. APP图标

除了动态图标的方法, 暂时也查不到什么动态修改图标的方法了。这个方法多用于 APP的节日活动。 事实上也没人去作这个的国际化,顺带提一下。

2. 应用名与权限提示

应用名与权限提示的国际化就是依赖info.plist的国际化。不一样本地化文件放不一样的键值对便可。

应用名称与相机权限的简体中文本地化

3. 启动图(LaunchScreen)

Xcode Overview 的 Adding Assets章节中有关于启动图的描述

Because the launch screen is shown before your app is running, you can only use a single root view of type UIView or UIViewController.

也就是说启动界面的展现是发生在main函数入口以前,也就决定了咱们没法动态地修改启动图。另外,如下nib的两种国际化方式也是无效的。

nib的两种国际化方式
另辟蹊径,利用info.plist的国际化来作LaunchScreen的静态国际化,以下:
LaunchScreen的国际化

这里要吐槽一下,即便作了静态国际化,如下两种情况是不会切换的

  • 系统切换语言的时候
  • 重启系统

只有从新安装app的时候才会切换 因此作启动图的国际化意义有限。

也看到有人说本身作一个LaunchScreenController做为启动页,可是这个情况下,app启动会黑屏一段时间,这不是想要的效果啊。

4. app调系统页面的国际化

关于调用系统资源,相机,相册,通信录之类,APP内修改语言暂没找到刷新的方法。若是你有方法,麻烦告知楼主,很是感谢。

只有在app重启时,main函数中应用程序代理(AppDelegate)返回以前去设置偏好设置的 AppleLanguages才是有效的。

因此,目前的解决方案就是这些页面所有本身实现。另外,导航按钮的国际化文本也不是在 main bundle 中加载的,通常app也会自定义这个按钮,这个不是痛点。

若有其它,欢迎补充。

app内更改语言

1.更改语言的方案

app的语言过年聚系统语言设置变化是最基本的国际化需求,更多的时候咱们但愿可以作到app内部热切换。

APP中的资源加载(Storyboard、图片、字符串)基本是在NSBundle.mainBundle()上操做的(自建私有库或者是三方库可能会本身作国际化,把国际化字符串资源放在本身的bundle中,如MJRefresh),那么咱们只要在语言切换后把相应资源加载的bundle替换成当前语言的bundle就好了。以下替换为 字符串资源加载的主要代码:

id value = language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:@"zh-Hans" ofType:@"lproj"]] : nil;
objc_setAssociatedObject([NSBundle mainBundle], &kBundleKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
……
- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName {
    NSBundle *bundle = objc_getAssociatedObject(self, &kBundleKey);
    if (bundle) {
        return [bundle localizedStringForKey:key value:value table:tableName];
    } else {
        return [super localizedStringForKey:key value:value table:tableName];
    }
}
复制代码

相应的nib文件中的图片加载和UITextView中的文本也须要

若是,项目是纯代码的,也就是说国际化涉及不到nib文件,那就不须要这个实现方式了。本身存个当前语言标记,切换时换一个,作好判断便可。

若是想要在第一次安装的时候跟随系统的语言,应用启动后从NSUserDefault中能够读到语言数组,其中数组的第一个元素便是主要语言(Primary Language),系统的当前语言。 这些语言字符串当中的最后一部分是地区,会根据地区变化,后续根据这个列表判断的时候须要注意。

NSArray *array = [[NSUserDefaults standardUserDefaults] arrayForKey:@"AppleLanguages"];
复制代码

语言列表(地区为中国)
语言列表(地区为美国)

不论哪一种方式,切换语言都是须要刷新视图的,这个没法避免。 那么,如何优雅地刷新UI

  • 微博的思路是,在切换语言时,发送通知NSNotification,全部的UI控件监听通知,而后在适当的时候刷新UI。 那么其实这么写,须要作的东西不少,或是经过Base类来实现,或是经过runtime实现,总之Button、Label、TextField等等都须要有一套统一的更新机制,可能不是一个最简单的办法。
  • 而微信切换的方案是,刷新keyWindow的rootViewController,而后跳转到设置页。

这个思路有篇文章说的比较详细,直接看:在iOS App内优雅的动态切换语言

2.未作国际化的旧项目迁移

老项目的国际化迁移也都会牵涉到以上提到的各类问题。可是以上的问题都不是主要的,主要的是那些散落在代码中的各类须要国际化的文本。一个一个去抠出来确定不现实。 Xcode为咱们提供了一个工具genstrings,这个工具与ibtool相似,也是导出字符串资源文件的。只不过ibtool适用于nib文件,而genstrings适用于源代码文件。支持C,Objective-C,swift(官方未明确指出,笔者尝试经过),java等语言文件,以下官方描述:

The genstrings tool can parse C, Objective-C, and Java code files with the .c.m, or .java filename extensions.

然而,仍是有不少工做要作,看一下官方的来那个外一个描述:

If you wrote your code using the Core Foundation and Foundation macros, the simplest way to create your strings files is using the genstrings command-line tool. You can use this tool to generate a new set of strings files or update a set of existing files based on your source code.

也就是说,这个脚本生效的前提是必需要使用NSLocalizedString系列宏,一个一个去替换字符串为这个宏的读取的这个工做仍是得本身作的。不过,想一想也是符合逻辑的,毕竟哪一个字符串要国际化仍是得开发者本身确认。经过Find navigator本身作吧。

如何使用

//指定到en.lproj目录下的Localizable.strings文件,直接覆盖
genstrings -o en.lproj *.swift
//指定到en.lproj目录下的Localizable.strings文件,追加内容
genstrings -a -o en.lproj *.swift
复制代码

其余参数可使用man genstrings命令查看,再也不赘述。 这个命令行工具一样有ibtool的诟病,全量输出。因此,在使用的时候千万当心别覆盖了已经翻译的内容。

看下效果:

效果

另外,这个命令一次只能解析一个文件,简单写了一个递归脚本:

#!/bin/bash
function getdir(){
for element in `ls $1`
do
dir_or_file=$1"/"$element
if [ -d $dir_or_file ]
then
getdir $dir_or_file
else
echo $dir_or_file
    suffix="${dir_or_file##*.}"
    if [ "$suffix"x = "swift"x ]||[ "$suffix"x = "m"x ]||[ "$suffix"x = "mm"x ];
    then
        genstrings -a -o en.lproj $dir_or_file
    fi
fi
done
}
root_dir="./"
getdir $root_dir
复制代码

iOS国际化至此结束,若有哪里不清楚,欢迎查看demo,或者留言。 最后附上:demo地址

参考文章: Internationalization and Localization Guide Using the genstrings Tool to Create Strings Files iOS国际化——经过脚本使storyboard翻译自增 iOS国际化 在iOS App内优雅的动态切换语言


笔者和朋友作了淘宝优惠券公众号,购物领券省钱,帮忙关注一下。

微信公众号
相关文章
相关标签/搜索