Android App國際化

2021-02-24 騰訊音樂技術團隊

前言

internationalization (國際化)簡稱 i18n,因為在i和n之間還有18個字符,localization(本地化),簡稱L10n。一般用語言_地區的形式表示一種語言,如zh_CN表示簡體中文。Android 對i18n和L10n提供了非常好的支持。Android沒有專門的API來提供國際化,而是通過對不同resource的命名來達到國際化的目的,同時這種命名方法還可用於對硬體的區分,如不同的屏幕解析度用不同的圖片。我們引用這些resource時,在java代碼中是通過R.resource_type.resource_name的方式來使用,如R.string.title,在xml中直接引用,如@ string/title引用了名字為title的字符串。values表示默認的資源文件夾,即當Resource找不到匹配的資源時,會使用values文件夾下的資源。文件夾的命名必須都是小寫字符,否則在一些大小寫敏感的文件系統中可能會出錯。

本文主要介紹Android App國際化過程中遇到的問題和解決方案。

1.整理HardCode

App國際化是要在保持App原功能不變的情況下,主要對字符串進行替換。一般在涉及字符串的位置都要在strings.xml裡面設置對應的item,方便後續的修改和復用,其他語言只需要新建values_xx(xx表示國家代號)資源文件夾,系統會自動進行替換。但是經常一個項目由多位同事參與,每個人的編程習慣會有異同,在xml或者java文件中會有遺漏的硬編碼(HardCode),因此第一步是整理項目中的硬編碼,統一歸併到strings.xml中。

Android Studio是Google官方推薦的Android IDE,擁有很多強大的功能。利用集成的Lint工具,可以將項目中大部分的HardCode搜索出來。首先建立一份模板文件,進入Preferences > Inspections,新建一份Profile


然後自定義名稱,主要用於檢測HardCode,以後可以根據需要自由切換Profile。


在下面的勾選框中選擇Android > Lint > Internationailization > Hardcoded text 和 TextView Internationailization兩項,顏色是黃色,表示在代碼中會以黃色來提醒。


然後由Lint進行代碼分析,選擇Analyze > Inspect Code,選擇自己的項目,選擇之前保存的Inspection模板


檢測之後,Hardcoded text可以掃描出xml中出現的HardCode代碼


TextView Internationailization可以掃描出setText的問題,而這種問題主要分兩種.

第一種是直接插入字符串,第二種是拼接字符串。


細心的同學會發現最上面的setText("我的測試")並沒有被標記出來,這也是Lint的一個問題所在。

這個情況可以通過正則表達式來搜索,搜索setText\(.*"\),需要注意對括號進行轉義。


這下可以找到所有的setText。下面是幾點注意事項

1. 間接使用setText。我們有時候不會直接使用setText,而是在基類裡對setText進行封裝,比如顯示頁面的頂部名字方法setTitleName,Lint不會對這種間接使用setText的情況進行提示,可以藉助Annotations來幫助Lint排查問題。在方法參數中添加@ StringRes來限制傳入的參數必須是本地資源中的字符串的資源id,同時需要將舊方法刪除,不要讓兩者並存。其他類型的資源如圖片、尺寸、顏色等,也分別可以添加 @ DrawableRes, @ DimenRes, @ ColorRes來限制。

@Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        setTitleName("首頁");//舊方法        setTitleName(R.string.app_name);//新方法        setTitleName(111);//報錯的方法    }    public void setTitleName(String res)    {        tx2.setText(res);    }    public void setTitleName(@StringRes int ResID)    {        tx2.setText(getResources().getString(ResID));    }

2. 佔位符。有些業務需求是要將動態信息和靜態信息拼接起來,開發的時候有時嫌麻煩,就直接通過+號連接字符串了,這是不好的編程習慣,違反App國際化規範。

String name = "張三";int age = 21;tx2.setText("名字是"+name+",年齡是"+age+"的用戶");//不規範用法tx2.setText(String.format("名字是%s,年齡是%d的用戶",name,age));//規範用法

需要注意的是這個格式不能直接粘貼在strings.xml中,需要修改格式將%替換為$,並添加上參數位置,如拼接的第一個參數是%1$s,%1表示第一個位置的變量, $s表示為字符串類型。

 <string name="info">名字是%1$s,年齡是%2$d的用戶</string>

3. 轉義字符。字符串中有時候會出現特殊字符,這類特殊字符在xml中需要轉義,下面是一些常用的特殊字符轉義之後的樣

符號轉義表示「&#34; 或 &quot;『&#39; 或 &apos;&&#38; 或 &amp;<&#60; 或 &lt;>&#62; 或 &gt;換行\n2.Strings.xml > Excel

現在已經整理好了strings.xml文件,下一步就是讓專業的翻譯人員翻譯對應的語言。其實直接提供strings.xml文件就可以了,但是這樣不容易進行去重、統計、整理,一般整理出excel文檔比較合適。比較直接的方法可以直接將內容複製到excel文檔中


然後利用替換功能,可以將<string name="、</string>、>這三段文字依次替換為空(順序不能變),得到如下狀態


然後利用excel的分列功能,以"為分隔符


可以將內容分為兩列


對於重複項的問題,我們採取的方案是以待翻譯的文字作為關鍵字,保持唯一性。先對欄位進行排序


然後找出重複的字符串,還是利用excel的功能在C1處插入公式=IF(COUNTIF(B$1:B1,B1)>1,"重複","")


得到的界面如下


將標記重複的欄位在java或xml代碼中找到引用的地方,替換成重複欄位第一次出現的name(這也是剛才要排序的原因,可以在excel中直接找到第一次出現的name),最後在strings.xml中刪除。

再利用excel的去重功能,選擇 數據 > 刪除重複項,以待翻譯欄位所在列為查重列,可以得到最終的結果


可以將得到文檔提交給專業的翻譯團隊了。

3.Excel > Strings.xml

得到翻譯好的的excel文檔之後


依舊可以藉助excel的拼接功能,在D1輸入拼接語句="<string name="&""""&A1&""""&">"&C1&"</string>",可以生成符合strings.xml中格式要求的內容


下拉統一格式,所有的都可以自動拼接


最後在項目中的res目錄下創建各自的資源文件夾,右擊res文件夾 > New > Android resource directory


選擇Locale


例如選擇的英語,會得到如下的文件夾


然後將前面整理好的各國語言的strings.xml放到對應的目錄下即可。

4.語言切換

App國際化在應用內部需要設置語言切換的功能,修改語言的功能如圖所示

Resources resources = getResources();DisplayMetrics dm = resources.getDisplayMetrics();Configuration config = resources.getConfiguration();// 應用用戶選擇語言config.locale = Locale.US;resources.updateConfiguration(config, dm);

但是修改之後應用並不會自動刷新界面。有如下幾種方式解決問題

重寫onConfigurationChanged方法,在AndroidManifest.xml裡面設置android:configChanges="locale",Configuration發生變化時會調用該方法,和文字有關的控制項需要在這裡重新刷新。界面元素太多的情況下,這種方式會很繁瑣。

@Override public void onConfigurationChanged(Configuration newConfig) {     button.setText(R.string.second);//需要刷新的控制項     super.onConfigurationChanged(newConfig); }

採用recreate方法,註冊一個語言被修改的監聽,回退到其他界面的時候需要刷新界面,recreate方法可以刷新Activity,比正常啟動Activity多調用了onSaveInstanceState和onRestoreInstanceState,而且是Android3.0以後的api,界面也會有明顯的閃屏現象。

一般修改語言界面都在比較深的操作中,上述兩種方式是修改語言之後直接刷新當前界面,另一種方式是重新從主界面進入,對應的實現方式就是清空之前的堆棧信息,直接跳到主界面,微信中修改語言之後的界面效果也是如此。

Intent intent = new Intent(this, MainActivity.class);intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);startActivity(intent);finish();

為了保證用戶設置成功後重新啟動應用時,保存的語言依然生效,要對用戶的選擇語言進行持久化保存,一般是通過SharedPreferences來保存,重新啟動應用時在Application的onCreate()方法中就要讀取保存的語言信息,修改Configuration。

public class MyApplication extends Application {  @Override    public void onCreate() {        super.onCreate();        LanguageUtil.applyUserLocale();    } }

還有一種情況,應用在後臺運行,用戶切換了系統的語言,會影響Configuration。模擬一個場景,系統語言是中文,應用語言初始化是系統語言中文,修改應用語言為日文,發現主界面變成日文,回到桌面,再進入系統設置,修改系統語言為英文,這時再返回應用,應用顯示英文,這個和應用功能違背,應該以應用語言為主。為了解決這種情況,一般在基類BaseActivity裡面添加邏輯判斷,目前應用語言是否和系統語言相同,如果不同就以應用語言為主,相同就跳過。

public class BaseActivity extends Activity {@Override    public void onCreate() {        super.onCreate();        if(!LanguageUtil.isEqualSystem())            LanguageUtil.setUserLocaleCoverSystem();    }}

5.界面適配

導入英文strings.xml之後,界面肯定會有很多錯亂的地方,原因各種各樣。下面僅根據項目中出現的情況,總結出大部分App都會出現的一些問題。

位置問題。最早的產品原型中可能不會考慮到國際化的需求,很多控制項的布局寫成固定值,英文不適用於之前的設計,所以出現錯位、遮擋、顯示不全等現象。在不影響視覺的情況下,可以對位置參數進行微調,如果區別十分明顯,可以將固定值改為代碼中動態獲取,或者將固定值存在values_en的dimens.xml中。

英文過長。中英文之間的翻譯存在長度的不確定性,有時會出現換行的現象。從翻譯的角度來看,可以讓翻譯團隊根據所屬界面的特殊性來重新翻譯,儘量控制在一定字符以內。也可以嘗試使用英文縮寫(這個適用性比較低,只適用於比較流行的詞語,否則容易產生歧義),或者將高寬的固定值改成wrap_content,或者調整字體大小,同樣需要在values_en的dimens.xml中記錄。

按鈕文字全部變成大寫。純文本信息的按鈕在中文情況下是正常顯示,但是setText純英文之後內容自動變成了大寫。以Theme.AppCompat.Light.DarkActionBar主題為例。跟蹤源碼可發現,該主題中有:

<style name="Base.TextAppearance.AppCompat.Button">        <item name="android:textSize">@dimen/abc_text_size_button_material</item>        <item name="textAllCaps">true</item>        <item name="android:textColor">?android:textColorPrimary</item></style>

   關鍵的原因在於:

<item name="textAllCaps">true</item>

解決方法可以在項目的自定義主題中修改如下

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">        <!-- Customize your theme here. -->        <item name="colorPrimary">@color/colorPrimary</item>        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>        <item name="colorAccent">@color/colorAccent</item>        <item name="textAllCaps">false</item></style>

或者為具體的Button控制項設置如下屬性

<Button    android:id="@+id/btn_answer1"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:textAllCaps="false"    />

App國際化適配界面的工作「因App而異」,更多的內容需要一個個界面進行調整,沒有完整的通用方法。遇到特殊情況,需要和產品交流,可能需要修改普通版的原有功能,也有可能屏蔽國際版中的一些功能,要有所取捨。總之整個過程需要多方的參與和溝通,才能有效迅速的達到國際化效果。

總結

Android App國際化的工作並沒有太多的技術難度,更多的是一些繁瑣的文本處理。如何利用自動化的工具來解放人工操作,如何從看似雜亂無章的內容中尋找出規律,如何改善自己的編程規範,才是真正能從這個過程中學習到的東西。當然我只是列舉了部分情況,不同的項目都會對應著不同的特例,很難面面俱到,仍然需要不斷的摸索。

相關焦點

  • Android APP安全測試入門
    另外一款就是SDK模擬器(Software Development Kit)了,這款是特別高大上的,類似虛擬機vm一樣,可以建立多個虛擬機,安裝不同的android系統。這樣就將本地下載的app安裝到了已經啟動android虛擬機中了。adb shell,登錄設備shell,如圖:
  • 【Sobug漏洞時間】Android APP安全測試入門
    SDK小工具SDK中自帶了幾款很不錯的小工具,我比較常用的有adb和emulator。ADB是一個客戶端-伺服器端程序,其中客戶端是你用來操作的電腦,伺服器端是android設備。SDK包中默認就有這倆款小工具AdbAdb命令如下:adb devices 查看啟動的虛擬機設備,如圖:
  • Android 國際化之多語言適配小記
    甲方要求實現 App 國際化多語言,正好抽個時間弄了下,害,被自己蠢到死,特意記錄下.如有不對,歡迎指正,一起交流~簡單說下需要注意的:國際化,多語言目錄創建,資源配置;Locale 資源獲取以及本地緩存,緩存的目的是為了下次重新打開 App 依然是上次選擇的語言;Android 系統間不同的差異,例如 7.0 後不再是唯一默認語言,而是多種語言配置,具體差別如下所示:
  • android app被殺原因專題及常見問題 - CSDN
    分析長按HOME鍵清理App最終會執行到ActivityManagerService.cleanUpRemovedTaskLocked方法中,ActivityManagerService類在文件"frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java"中,
  • 是時候讓 Android Tools 屬性拯救你了
    string name="header_image_string" tools:ignore="MissingTranslation">header image</string></resources>這個對於 Android studio 升級到 3.0 以上的小夥伴來說應該是很常見了,如果我們項目中涉及到國際化支持
  • 從Android的瀏覽器中傳遞cookie數據到App中 - 南湖邊上的小木屋
    客戶要求接下來在瀏覽器上,點擊一個按鈕,能夠啟動app, 並且將cookie中的login信息傳遞給app, 讓app免於再次登陸的繁瑣。2.    技術實現Html上添加一個a標籤,如下<a href=http://blog.csdn.net/nanjingjiangbiao/article/details/"cookie://XXXX "><span>啟動app</span></a>XXX是js在初期化的時候
  • App工程搭建:幾種常見Android代碼架構分析
    本文先分析幾個當今比較流行的android軟體包,最後我們汲取其中覺得優秀的部分,搭建我們自己的通用android工程模板。1. 微盤微盤的架構比較簡單,我把最基本,最主幹的畫了出來:第一層:com.sina.VDisk:com.sina(公司域名)+app(應用程式名稱) 。
  • 通關Android Lint
    不要直接擴展android.widget類,而應該擴展android.support.v7.widget.AppCompat中的一個委託類。 2.Correctness:Messeges 1) MissingTranslation 字符串國際化不完全 2) ExtraTranslation 國際化的字符串,在默認位置(defaultlocale),沒有定義 3) StringFormatInvalid 如果字符串包含'%'字符,則該字符串可能是格式化字符串,將從
  • 帶你了解 Android 約束布局 ConstraintLayout
    ><android.support.constraint.ConstraintLayout    xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:layout_width
  • 5步搞定android混淆
    class/merging/*-keepattributes *Annotation*,InnerClasses-keepattributes Signature-keepattributes SourceFile,LineNumberTable#-#---默認保留區----keep public class * extends android.app.Activity-keep public class
  • Android 這些 Drawable 你都會用嗎?
    app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintLeft_toLeftOf="parent"        app:layout_constraintRight_toRightOf="parent"        app:layout_constraintTop_toTopOf
  • Android新手入門-Android中文SDK
    This guide will help you understand the pieces that make up an Android app)入門指導:構建一個完整的Android程序 (Tutorial: Building a Full Android Application)入門指導文檔將帶領你通過構建一個真實的Android應用程式,一個記事本的創建、編輯
  • Android系統編譯指南
    高通項目舉例如下:1. mmm使用舉例(此時不在FM 目錄)比如單編FM,不在FM目錄下,需要執行mmm vendor/qcom/opensource/commonsys/fm/fmapp2/Test@Test:/Test/Qualcomm_p/E5527M_MSM8917_QM215_r26/LA.UM.7.6.2/LINUX/android$
  • Android MotionLayout動畫:續寫ConstraintLayout新篇章
    ><MotionScene xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto">    <ConstraintSet android:id="@+id/start">
  • 一次Android權限刪除經歷
    2.初步定位首先使用android studio查看了打包出來的apk中的Androidmanifest文件,發現其中確實存在RECEIVE_SMS權限,也就是說打包到apk中的Androidmanifest文件並不是app下的該文件,從android開發者官網中合併多個manifest文件的文檔來看,實際上打包到apk中的manifest文件是由多個menifest文件合併而來的,其合併順序如下
  • InjuredAndroid 1-5
    繞過main activity 來調用其他可導出的activities這裡使用dorzer:dz> run app.activity.info -a  b3nac.injuredandroidPackage: b3nac.injuredandroid  b3nac.injuredandroid.CSPBypassActivity    Permission: null  b3nac.injuredandroid.RCEActivity    Permission: null  b3nac.injuredandroid.ExportedProtectedIntent
  • Android代碼混淆使用手冊
    class/merging/*-keepattributes *Annotation*,InnerClasses-keepattributes Signature-keepattributes SourceFile,LineNumberTable#-#---默認保留區----keep public class * extends android.app.Activity
  • Android Crash 案例解決方案
    ,但是當運行時,如果存在此異常,可能會導致app崩潰 crash。此處強制轉換,會導致 app 編譯沒問題,運行掛掉, Caused by: * java.lang.ClassCastException: * com.programandroid.Exception.ExceptionActivity$ Fruit cannot be cast * to com.programandroid.Exception.ExceptionActivity
  • Android Schema協議略知一二
    :name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <data android:scheme="artist" android
  • android絕對布局
    >02<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"03android:orientation="vertical"04android:layout_width="fill_parent"05android:layout_height