Android多语言适配要点速记

Posted by lx8421bcd on June 1, 2023

前言

前段时间做的一个项目涉及到了多语言支持,需要支持英文、简体中文、繁体中文,在做多语言适配时,遇到了一些……不能算是坑的问题吧,虽然已经整理进代码库,但觉得最好还是略作笔记,方便回忆开发过程,也方便他人少踩点坑。

项目开发要点

全链条参与意识

有没有产品、设计、研发、测试,全链条参与的意识,应该算是一个多语言项目能否顺利开发的先决条件。

一个多语言项目并不只是客户端的工作,以国内产品的开发情况来说,从产品原型到UI交互视觉稿,默认都是中文的;但多语言项目到了开发阶段,出成品的时候要求是多种语言的,如果在产品设计的时候没有考虑到不同语言的区别,那就会在开发-设计-产品之间产生很多反馈与修改。

比如UI显示问题,中文几个字,英文一大段,如:”请输入6位验证码”,一般来说这种hint中文一行都能显示下,但如果直球翻译成英文”Please enter the six-digit verification code”,在一些小屏手机上显示就会很局促了。还有一些地方,明明一个单词可以说清楚,但负责翻译的同学没看过设计稿,直接照着文本翻译,甚至于这人计算机英语就不怎么行,一些计算机英语使用常识他翻译的跟机翻一样,比如手机号登录,简洁一点:Phone login,但有人就给你翻译成”Log in with your mobile phone number “,前者可以直截了当的作为一个button,后者嘛……

开发过程中遇到这些问题就需要设计乃至产品介入,浪费时间且不说,更重要的是打乱了整个研发团队的工作节奏,每一次逆研发链条的反馈与修改都需要从上游同步到整个下游,即便团队开发流程再规范,这种同步太多太细碎之后,也难免会出现疏漏。结果就是今天开发跟设计说这里文案有问题,设计和产品商量调整了,测试不知道;明天iOS和设计说这里文案显示不下,要修改,Android不知道。整个团队陷入一种乱哄哄的状态,所有人都在忙,但项目却很毛糙。

而在产品原型和UI、视觉稿阶段就进行多语言规划可以完全避免这种问题。

工作流程建议

产品设计阶段:

  • 如果能出多语言的视觉稿是最好的,如果受限于研发时间不能出多语言视觉稿,则至少应要求将页面中对应的文案的多语言文案备注出来(这一步可由PD在视觉稿上标注)
  • 整理好基础多语言文案(如:确认-confirm,取消-cancel,提交-submit等),之后视觉稿中同类元素则无须重复翻译,减轻工作量
  • 避免出现由于语言问题导致UI变化,设计中尽可能避免为语言问题开特例

开发阶段:

  • PD与UI在设计过程中很少会考虑非功能性需求(比如 error toast),在开发过程中应对toast进行归纳整理,交由PD或者对应负责人翻译
  • 除了通用的常识性翻译,不要自己翻译,避免出现不同平台文案不一致

测试阶段

  • 如果遇到不同翻译的显示问题(比如英文过长),应与PD/UI沟通,确定显示方案

Android技术要点

Android本身已经由很完善的多语言适配方案,所以并没有什么技巧,按规则写string.xml就完事了。唯一需要注意的一点是,不要在代码中写死字符串,这一点可以用Android Lint规则进行限制

如何安排strings.xml

简而言之的优先级:地区 > 语言 > 默认

一个完整的strings.xml文件的路径是 resource/values-[language]-[locale]

language是语言,比如en、zh、fr等,locale是地区,比如英语中有us、uk、au等,中文有rCN(大陆),rTW(台湾)等。

系统进行语言适配时,会根据当前的手机环境,选择对应的语言-地区下的配置文件,比如在中国大陆,会优先选择values-zh-rCN,如果当前这个引用的资源在zh-rCN里没有,则会去查找values-zh下的string.xml文件,如果也没有,则会去查values下面的string.xml,这是兜底的默认配置了,如果这个文件下也没有,那就应该build不过去了……

在做语言适配时,如果你不做地区的适配,那在配置values内的strings.xml文件时,就不要加地区。比如,如果你就适配中文和英文,英文作为默认语言,那你就配置默认的values下的strings.xml文件放英文,values-zh下放中文就行了。如果中文要区分简繁,再去设置values-zh-rCN、values-zh-rTW等目录下的配置文件。没有必要特意将中文放在zh-rCN下面,这样做如果你强制指定语言选项,在应用启动时刷新语言配置的话是没有问题的,但如果你有跟随系统的选项,直接将中文放在zh-rCN下就会导致其他地区,如TW、HK等地的用户默认适配到英文去了。

Locale对象的问题

Locale.getDfault()

需要注意的一点是,在代码中调用 Locale.getDefault()获取Locale对象,是来自于当前这个Application的数据,它是可以被修改的,比如我们在执行设置应用语言的时候

DisplayMetrics dm = res.getDisplayMetrics();
Configuration conf = res.getConfiguration();
conf.locale = getCurrentLanguageOption().getLocale();
res.updateConfiguration(conf, dm);

Resources.updateConfiguration()执行完毕后,再调用 Locale.getDefault()方法拿到的就是设置过的locale对象了,这一点需要特别注意。

获取用户系统Locale信息

如果需要忽略当前应用内的环境,获取用户系统的Locale信息,那么需要调用的方法是

Resources.getSystem().getConfiguration().locale

此方法可以确保拿到的是用户手机系统下的locale信息

Locale对象比较

注意不要直接使用Locale对象进行比较,无论是直接的compare或是toString(),只去比较具体的字段即可,从手机拿到的Locale对的信息是要比设置的Locale对象丰富的,比如大部分简体中文、中国大陆地区的配置的手机,拿到的Locale对象直接toString()得到的信息是:

zh_CN_#Hans

其中Hans表示的是简体中文,繁体中文则是hant,对应的是locale对象中的script字段

但当你使用Locale.CHINA获取locale对象时,toString得到的则是

zh_CN

可以看见Locale的常量对象内的信息是要少很多的

所以当需要进行语言、地区信息比对时,最好是对Locale对象内的信息按需取用,而非直接对Locale对象进行操作

确保页面使用指定的Locale对象

如上文的代码所示,所谓修改应用的语言,实际上就是复写应用Resources内的locale变量,以确保其他组件使用此变量时拿到的是我们设置的语言配置,但与设置DPI进行等比缩放时遇到的问题一样,Resources对象内的属性在应用运行时是经常会被应用内其他程序复写的,如果在初始化UI组件时,从Resources内拿到的Locale对象不是我们设置的语言配置,那表现出来的效果就是语言设置不生效。

为了避免这一点,最好的办法是重写ApplicationActivity等组件内的getResources()方法

    @Override
    public Resources getResources() {
        Resources res = super.getResources();
        DisplayMetrics dm = res.getDisplayMetrics();
        Configuration conf = res.getConfiguration();
        conf.locale = getCurrentLanguageOption().getLocale();
        res.updateConfiguration(conf, dm);
        return res;
    }

修改语言生效的方式

首先无论用SharedPreferences进行存储,或者其它方式,语言设置肯定是有key-value的形式存储于应用缓存的,这样才能保证应用在冷启动时恢复对应的语言设置。怎样存储、还原,各有方法,本文不做赘述。

目前市面上应用执行修改语言的生效方式,以下三种比较主流

  • 重启应用

    • 最简单,最安全,对既有代码的影响最小,但体验是最差的。
    • 重启方式有杀进程的,有关掉所有Activity然后重启Splash流程的,但思路是一样的。应用冷启动本身就是所有应用都有的逻辑,所以对既有代码不会有任何影响,体验差则是打断了用户的使用体验,重启是一个很重的行为。
  • 自定义语言修改广播事件,所有Activity收到广播后调用 recreate()重建

    • 比较简单,比较安全,对既有代码影响较小,体验稍差。
    • 此方案一般只需要修改Activity基类,增加接收广播逻辑即可,主要问题是,对于已有的项目来说,很多代码是没有考虑过Activity销毁重建情况的(比如一些fragment创建的代码),执行 recrate()会导致崩溃,需要逐一排查。体验上的问题则是,在页面重建时,用户手机将会产生卡顿,虽然很短,大部分手机就100ms左右,但很明显。
  • 监控所有文本类控件,在收到语言修改事件后刷新

    • 最为复杂,容易出问题,需要整个代码设计为此feature服务,但体验最好
    • 此方案目前仅在一两个大厂应用上见过,因为是局部刷新不会存在UI卡顿问题,但开发成本也是最为高昂的,文本信息不止来源于客户端,也来大量源于服务端,这就意味着从UI实现到接口实现都需要去考虑语言切换问题,稍有不慎就会出现语言修改不生效的异常。

应该说这三种方法并不是互斥的,完全可以在一个项目的做多语言适配的早期先采取重启应用的方式保证稳定性,后期体验优化再切换到应用内重建页面。而方法二和三完全可以在一个项目内同时存在。即只对语言设置页做局部刷新,后台页面仍采取重建的方式,降低适配成本。

根据项目需求和开发成本酌情考虑即可。