在真正将国际化实践前,只知道通过NSLocalizedString方法将相应语言的字符串加载进来即可。但最近公司项目的新需求增加英文版本,并支持应用内无死角切换~,这才跳过各种坑实现了应用内切换语言,并记录至此。


环境

系统环境: iOS7 - iOS9

开发环境: Swift2 & Xcode7

DEMO: LocalDemo

这个Demo的功能主要是切换语言后相应的界面文字&图片以及搜索引擎都会随语言变化。我们会围绕这个DEMO进行讲解,读者可以先下载这个Demo运行看下效果再往下


iOS国际化原理分析

国际化其实都大同小异,其核心思想就是为每种语言单独定义一份资源。

iOS就是通过xxx.lproj目录来定义每个语言的资源,这里的资源可以是图片,文本,Storyboard,Xib等。我们可以看看LocalDemo源代码的物理目录结构

Base,暂时无需理会

English

中文

每种语言都有自己的 语言代码.lproj文件夹,加载资源时只需要加载相应语言文件夹下的资源就OK,这步可以系统为我们完成,也可以手动去做。

项目源代码中如果有多个不同目录的国际化资源,则会有产生多个xxx.lproj,但在编译打包后,会集中放在app的根目录中的xxx.lproj中,不信你看~

开始国际化


首先点击项目->PROJECT->Info->Localizations中添加要支持的语言.

此处Use Base Internationalization开启状态下,每个国际化资源文件会有个Base选项,主要针对String,Storyboard,Xib作为一个基础的模板,像后述storyboard国际化中方案二就是基于Base StoryBoard进行改动。

在点击+ 添加相应语言时会弹出以下对话框,意思是为现有的资源添加语言文件,我们点击Finish就行了.

文本的国际化


主要针对代码中的字符串进行国际化,比如说一些消息,UI标题等。

我们通过一个Localizable.strings文件来存储每个语言的文本,它是iOS默认加载的文件,如果想用自定义名称命名,在使用NSLocalizedString方法时指定tableName为自定义名称就好了,但你的应用规模不是很大就不要分模块搞特殊了。

每个资源文件如果想为一种语言添加支持,通过其属性面板中的Localization添加相应语言就行了,此时Localizable.strings处于可展开状态,子级有着相应语言的副本。我们把相应语言的文本放在副本里面就行了.

此处Base与前面提过到的开启Use Base Internationalization是有关联的,只有开启了全局Use Base Internationalization此处才会显示。那为什么这里没有勾选Base?Base做为一个基础模板,作用于Strings文件是没有太大意义的,另外去掉Base意义着在Base.lproj中少了一个strings文件,APP大小也所有下降,这点对于图片的Base更是如此.

在上图可以看到其实就是为每一套语言新建一份strings,其内容采用"key" = "value";的格式,注意有;号

我们在代码中这样写就行了

1
2
3
NSLocalizedString("首页",comment: "")
NSLocalizedString("好友",comment: "")
NSLocalizedString("我",comment: "")

另外中文strings【Localizable.strings(Simplified)】可以不要的(可以理解为中文为APP的默认语言),因为key就是value,当找不到相应的语言strings或value时会直接返回key。nice!这样一来我们做文本的国际化就只要维护一个英文副本strings就O了

图片的国际化


二种方案,通过原生支持与自定义命名

注意,新版Xcode中Images.xcassets不支持国际化(属性页面中没有Localization),Xcode5以前是支持的

  • 方案一:自定义文本命名

利用文本国际化的方式,在代码中调用

1
UIImage(named: NSLocalizedString("search_logo",comment: ""))

不推荐,一是因为做法太low了,工作量明显加大。二是不能在Storyboard或XIB中使用

  • 方案二:原生支持

同上,Base副本去掉。另外需要注意的是,使用这种方式,在XIB或Storyboard中引用图片时如果只使用名称是实时显示不了的,一定要加上后缀名。如avater.png

使用方式不变,iOS会自动找相应语言(xxx.lproj)下的图片

1
UIImage(named: "avater")

对于图片的放置,正确姿态应该是需要国际化的图片放在自定义Group里面,不需要国际化的图片放在Images.xcassets

Storyboard&XIB的国际化


前面的两种资源国际化比较简单,但Storyboard国际化就稍微麻烦了点。同样它也有二种方案

  • 方案一:每种语言定制一套Storyboard

在上图我们可以看到,每种语言都可以切换为strings或Storyboard(默认为strings)。如果选用Interface Builder Storyboard方案,那么每种语言都有一套相应的Storyboard,各个语言Storyboard间的界面改动不关联.

  • 方案二:基于基础的Base StoryBoard以及每种语言一套strings

基于一个基础的Storyboard,可以看作是一个基础的模板,Storyboard里面所有的文本类资源(如UILabel的text)都会被放在相应语言的strings里面。此时我们为Storyboard里的字符类资源作国际化只需要编辑相应语言的strings就行了

首选方案二。因为采用方案一,意义着你每改动一个界面元素就得去相应语言Storyboard一一改动,那跟为每个语言新起一个项目是一样的道理。但是采用方案二,我们只需改动Base Storyboard就行了.

注意,方案二中相应语言的strings一旦生成后,Base Storyboard有任何编辑都不会影响到strings,这就意味着如果我们删除或添加了一个UILabel的text,strings也不能同步改动。

还好,Xcode为我们提供了ibtool工具来生成Storyboard的strings文件。

1
ibtool Main.storyboard --generate-strings-file ./NewTemp.string

但是ibtool生成的strings文件是BaseStoryboard的strings(默认语言的strings),且会把我们原来的strings替换掉。所以我们要做的就是把新生成的strings与旧的strings进行冲突处理(新的附加上,删除掉的注释掉),这一切可以用这个pythoy脚本来实现,见AutoGenStrings.py。然后我们将借助Xcode 中 Run Script来运行这段脚本。这样每次Build时都会保证语言strings与Base Storyboard保持一致。

应用内切换语言


应用启动时,首先会读取NSUserDefaults中的key为AppleLanguages的内容,该key返回一个String数组,存储着APP支持的语言列表,数组的第一项为APP当前默认的语言。

在安装后第一次打开APP时,会自动初始化该key为当前系统的语言编码,如简体中文就是zh-Hans。

1
2
//获取APP当前语言
(NSUserDefaults.standardUserDefaults().valueForKey("AppleLanguages") as! Array)[0]

那么我们要实现语言切换改变AppleLanguages的值即可,但是这里有一个坑,因为苹果没提供给我们直接修改APP默认语言的API,我们只能通过NSUserDefaults手动去操作,且AppleLanguages的值改变后APP得重新启动后才会生效(才会读取相应语言的lproj中的资源,意义着就算你改了,资源还是加载的APP启动时lproj中的资源),猜测应该是框架层在第一次加载时对AppleLanguages的值进行了内存缓冲

1
2
3
4
//设置APP当前语言
var def = NSUserDefaults.standardUserDefaults()
def.setValue([“zh-Hans”], forKey:"AppleLanguages")
def.synchronize()

那么问题来了,如何做到改变AppleLanguages的值就加载相应语言的lproj资源?

其实,APP中的Storyboard的加载,图片与字符串的加载都是在NSBundle.mainBundle()上操作的,那么我们只要在语言切换后把NSBundle.mainBundle()替换成当前语言的bundle就行了,这样系统通过NSBundle.mainBundle()去加载资源时实则是加载的当前语言bundle中的资源

lproj目录可以用一个NSBundle表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import Foundation
 
/**
*  当调用onLanguage后替换掉mainBundle为当前语言的bundle
*/
private  let _bundle:UnsafePointer=  unsafeBitCast(0,UnsafePointer.self)
class BundleEx: NSBundle {
    override func localizedStringForKey(key: String, value: String?, table tableName: String?) -> String {
        if let bundle = languageBundle() {
            return bundle.localizedStringForKey(key, value: value, table: tableName)
        }else{
            return super.localizedStringForKey(key, value: value, table: tableName)
        }
    }
}
 
extension NSBundle{
    private struct Static {
        static var onceToken : dispatch_once_t = 0
    }
    func onLanguage(){
        //替换NSBundle.mainBundle()为自定义的BundleEx
        dispatch_once(&Static.onceToken) {
            object_setClass(NSBundle.mainBundle(), BundleEx.self)
        }
    }
     
    //当前语言的bundle
    func languageBundle()->NSBundle?{
        return Languager.standardLanguager().currentLanguageBundle
    }
}

以上Languager是 iOS-i18n 开源库的一部分,我把项目中国际化部分封装了下,有兴趣的童鞋可以去看看

其他


  • 设置运行语言环境

有时我们第一次安装APP时不想默认跟随系统,那么可以通过Xcode的scheme来指定特定语言

Storyboard实时预览,直接上图~

  • IB中UIImageView国际化无效

解决办法就是为UIImageView扩展一个方法,然后通过IB中的User Defined Runtime Attributes把imageName传进去

1
2
3
4
5
6
7
8
9
10
extension UIImageView{
    var locale:String{
        get{
            return ""
        }
        set(newlocale){
            self.image = localizedImage(newlocale)
        }
    }
}
  • IB中UITextView国际化无效

解决办法和UIImageView类似,扩展一个方法,然后把self.text做为key去strings文件中拿相应语言的value

1
2
3
4
5
6
7
8
9
10
extension UITextView{
    var locale:Bool{
        get{
            return true
        }
        set(newlocale){
            self.text = localized(self.text)
        }
    }
}
  • LaunchScreen.xib的国际化

很遗憾,到目前为止,还不支持LaunchScreen.xib的国际化,我们只能通过自定义一个LaunchViewController来完成此需求,但也有些不足,就是应用启动时会黑屏一段时间,所以建议启动页面不要弄国际化.

详述iOS国际化的更多相关文章

  1. 【转】详述iOS国际化

    原文网址:http://www.cocoachina.com/ios/20151120/14258.html 在真正将国际化实践前,只知道通过NSLocalizedString方法将相应语言的字符串加 ...

  2. iOS国际化

    本文介绍iOS国际化包含以下几种: 应用名称,文字,图片和xib 首先在工程里添加支持的语言,这里用了English和中文 然后创建两个.strings类型的文件,文件名分别为InfoPlist和Lo ...

  3. iOS 国际化多语言设置 xcode7

    iOS 国际化多语言设置 方式一: 1. 在storyboard中创建好UI,然后在 project 里面  Localizables 栏目里面,添加你需要的语言:默认是Englist; 比如这里我添 ...

  4. iOS国际化和genstrings所有子文件夹本地化字符串

    iOS国际化和genstrings所有子文件夹本地化字符串 在最近的一个繁忙的对外工程.每天加班.没有时间更新博客.简单谈一下知识的国际化. 首先,我们使用串.必须NSLocalizedString( ...

  5. iOS 国际化 NSLocalizedString

    iOS 国际化.根据系统不同的语言自动切换. 首先.选择项目 Add new file -->iOS -->Resource -->Strings File  . 命名为Locali ...

  6. Ios国际化翻译工具

    IOS Translation Tool(IOS国际化翻译工具) 介绍 当IOS项目国际化的时候,手工去翻译每一个字符串是一件非常痛苦的事情.尤其是当项目中存在N多种语言.而且又很难保证,手工翻译的准 ...

  7. iOS学习——iOS国际化(十二)

    开发的移动应用更希望获取更多用户,走向世界,这就需要应用国际化,国际化其实就是多语言.这篇文章介绍Xcode4.5以后的国际化,包括应用名国际化和应用内容国际化.如果是Xcode4.5之前版本请参考. ...

  8. iOS开发——iOS国际化 APP内语言切换

    最近一个一直在迭代的老项目收到一份新的开发需求,项目需要做国际化适配,简体中文+英文.由于项目中采用了storyboard和纯代码两种布局方式,所以国际化也要同时实现.上网查了些资料,实现了更改系统语 ...

  9. iOS国际化——通过脚本使storyboard翻译自增

    一. 针对两种文件的国际化处理 代码中即.m文件的国际化 首先在你需要进行国际化处理的字符串外面加一层NSLocalizedString,注意中文也是可以的哦 textfield.text = [NS ...

随机推荐

  1. amCharts 破解

    官网下载 http://www.amcharts.com 然后找到amcharts.js , 打开搜索关键字 “utm_source=swf&utm_medium=demo&utm_c ...

  2. 如何修改UITableView每个cell的分隔线和左边的距离?

    在ios7中,UITableViewCell左侧会有默认15像素的空白.这时候,设置setSeparatorInset:UIEdgeInsetsZero 能将空白去掉.但是在ios8中,设置setSe ...

  3. SQL UPDATE 经典

    1 sql中用另一个表的一列来更新数据库表 SELECT TOP 1000 [a] ,[b] ,[c] FROM [单元测试项目].[dbo].[A] SELECT TOP 1000 [a] ,[b] ...

  4. 手把手教你DIY一个春运迁徙图(一)

    换了新工作,也确定了我未来数据可视化的发展方向.新年第一篇博客,又逢春运,这篇技术文章就来交给大家如何做一个酷炫的迁徙图(支持移动哦).(求star 代码点这里) 迁徙图的制作思路分为静态的元素和变换 ...

  5. 探讨CMake中关于RPATH的使用

    最近研究CMake,发现CMake对于RPATH的管理也非常人性化.官方说法是当动态库的编译也和执行档在同级目录下的时候,CMake会自动给执行档加入适当的RPATH.具体可以通过readelf -d ...

  6. javascript在一个字符串中每隔多少字符插入某个字符串

    function insertStr(str,tar,n,m){ var x='' var str=str.split('') if(str.length==0) return for(var i=n ...

  7. Python文件处理之文件指针(四)

    当我们读取文件内容时,并不能重复的读取,比如一个blogCblog.txt文件里有blogCblog内容,用两个read()方法读取blogCblog.txt的内容,会发现,第一个返回文件内容,第二个 ...

  8. python中的builtin函数详解-第二篇

    classmethod(function) 这里不过多说明这个builtin方法的具体用法,python的文档和help函数已经给了这个方法充足的使用说明,所以我这里要说的时关于 classmetho ...

  9. Xcode升级导致插件失效的解决办法-b

    作为iOS界的攻城师,每一次水果发布新的Xcode开发版本时,我们都会跟进,然而那些好用的Xcode插件都会莫名的失灵...对此我各种百度,在这里,我将跟大家分享我是如何解决这些问题的.当然,我的方案 ...

  10. Unity3d在安卓android的更新(APK覆盖)

    其实这并没什么技术难点,也不是完美的热更新方案,只能说是退而求其次的一个方法. 起因主要是因为公司几个U3D项目在立项之初都没有能做好热更新的规化,导致现在要去做U3D的热更新非常难,并且项目已处于中 ...