用力抱一下APP國(guó)際化

image

APP國(guó)際化,說(shuō)的直白應(yīng)該也叫本土化或者本地化,如果你的應(yīng)用上線(xiàn)到谷歌應(yīng)用市場(chǎng),那么應(yīng)該做好本地化的支持,用來(lái)支持不同語(yǔ)言及地區(qū)的風(fēng)俗習(xí)慣,當(dāng)然也要結(jié)合公司拓展的海外市場(chǎng)需要,那么對(duì)于一款應(yīng)用,至少應(yīng)該做到多語(yǔ)言和多布局的支持。
最近忙于阿拉伯語(yǔ)適配工作,自己便去搜羅和整理了一些,也踩過(guò)很多的坑,如果你的APP在做國(guó)際化支持,那么推薦你閱讀下,這也許是篇值得參考的文章,若對(duì)你有所幫助的話(huà),那就反手點(diǎn)個(gè)大大的贊哇!

國(guó)際化資源

資源是指文本字符串、布局、聲音、圖形和你的Android 應(yīng)用需要的任何其他靜態(tài)數(shù)據(jù)。

  • res/drawable/(必需的目錄,包含至少一個(gè)圖形文件,用作 Google Play 上的應(yīng)用圖標(biāo))
  • res/layout/(必需的目錄,包含定義默認(rèn)布局的 XML 文件)
  • res/anim/(如果您有任何 res/anim-<qualifiers> 文件夾,則為必需)
  • res/xml/(如果您有任何 res/xml-<qualifiers> 文件夾,則為必需)
  • res/raw/(如果您有任何 res/raw-<qualifiers> 文件夾,則為必需)
    當(dāng)你的應(yīng)用支持國(guó)際化,那么必須要為這些資源至少設(shè)置一套默認(rèn)的資源,當(dāng)應(yīng)用在您沒(méi)有提供特定于該語(yǔ)言區(qū)域的文本的語(yǔ)言區(qū)域中運(yùn)行時(shí),Android就會(huì)去加載一些默認(rèn)的資源,所以默認(rèn)資源很重要。通常默認(rèn)資源被認(rèn)為是你的app內(nèi)部使用最多的資源,需要注意的是,在資源的加載過(guò)程中,語(yǔ)言區(qū)域幾乎總是處于優(yōu)先地位,是被系統(tǒng)優(yōu)先加載的。
    除了語(yǔ)言區(qū)域可以作為本地化資源的區(qū)分之外,Android系統(tǒng)也為我們提供了兩種布局方向區(qū)分,即ldrtlldltr,ldrtl 是指“布局方向從右到左”。ldltr 是指“布局方向從左到右”(默認(rèn)的隱式值)。舉個(gè)栗子:若我們使用阿拉伯語(yǔ),則layout-ar是被優(yōu)先加載的,而layout-ldrtl優(yōu)先級(jí)則沒(méi)有語(yǔ)言區(qū)域ar的優(yōu)先級(jí)高,如果我們使用的是其他的RTL語(yǔ)言,譬如說(shuō)波斯語(yǔ),那么就會(huì)去加載layout-ldrtl下的資源。
res/
    layout/
        main.xml (Default layout)
    layout-ar/
        main.xml (Specific layout for Arabic)
    layout-ldrtl/
        main.xml (Any "right-to-left" language, except
                  for Arabic, because the "ar" language qualifier
                  has a higher precedence.)

針對(duì)于其他資源,譬如drawable圖片、anim動(dòng)畫(huà)、raw靜態(tài)資源和xml的本地化同樣可以通過(guò)語(yǔ)言區(qū)域作為劃分,也可以通過(guò)布局方向作為區(qū)分,所以對(duì)于本地化來(lái)說(shuō)我們可以結(jié)合多種方式靈活運(yùn)用他們。AS創(chuàng)建Resource File或者Resource Directory系統(tǒng)已經(jīng)提供選擇語(yǔ)言和一些特殊的區(qū)域。如下圖:


image

國(guó)際化字符串

  • 拒絕任何形式的硬編碼字符串,所有字符串應(yīng)該通過(guò)string.xml資源文件加載,便于本地化
 ../values-en/strings.xml 英語(yǔ)
 <string name="my_topic_btn">My Topic</string>
 ../values-ar/strings.xml 阿拉伯語(yǔ)
 <string name="my_topic_btn">??????</string>
 ../values-fr/strings.xml 法語(yǔ)
 <string name="my_topic_btn">mon sujet</string>
 ../values-hi/strings.xml 印度語(yǔ)
 <string name = "my_topic_btn"> ???? ????</string>
  • 對(duì)于不應(yīng)該被翻譯的代碼、占位符、特殊符號(hào)或名稱(chēng),應(yīng)該進(jìn)行標(biāo)記,不做翻譯,可以使用 <xliff:g> 占位符標(biāo)記,但是務(wù)必提供指定ID來(lái)說(shuō)明用途
// 占位符最好不要被翻譯,特別是阿拉伯語(yǔ)
 <string name="admission">
   ???? <xliff:g id="xliff_admission">%1$s</xliff:g> ??????
 </string>
// url連接地址不要做翻譯
<string name="web_url">
  Visit us at <xliff:g id="main_web_url">http://my/app/home.html</xliff:g>
</string>
// 用戶(hù)名不要做翻譯
<string name="user_name">
  username: <xliff:g id="name">Herry</xliff:g>
</string>

LTR與RTL布局

我們一般的閱讀習(xí)慣都是從左往右,即LTR(left to right),這是Android系統(tǒng)的默認(rèn)支持的布局方式,除此之外,當(dāng)targetSdkVersion 設(shè)為 17 或更高版本,則系統(tǒng)會(huì)激活和使用各種 RTL API,所謂的RTL即從右往左的布局,用來(lái)支持中東國(guó)家的閱讀習(xí)慣,常見(jiàn)的語(yǔ)種有阿拉伯語(yǔ)、波斯語(yǔ)、希伯來(lái)語(yǔ)等等。
Android控件已經(jīng)大部分支持RTL布局了,但是一些自定義的控件需要自己做適配。通常情況只需要在<application>標(biāo)簽增加 android:supportsRtl="true",就可啟用RTL API來(lái)支持RTL布局,具體的適配方案后面會(huì)詳細(xì)講到。

image

RTL布局預(yù)覽

Android Studio默認(rèn)已經(jīng)為我們提供了對(duì)應(yīng)語(yǔ)言區(qū)域的預(yù)覽,打開(kāi)預(yù)覽的界面Locale for Preview,默認(rèn)會(huì)顯示Default(en-us),使用的布局為L(zhǎng)TR,若我們使用到了阿拉伯語(yǔ)等RTL語(yǔ)言,預(yù)覽可以選擇對(duì)應(yīng)的ar語(yǔ)或者RTL語(yǔ)言,如下圖:

image

偽語(yǔ)言區(qū)域

Android系統(tǒng)平臺(tái)默認(rèn)提供了兩種偽語(yǔ)言區(qū)域,英語(yǔ) (XA)和AR (XB),分別表示從左到右 (LTR) 和從右到左 (RTL) 顯示的語(yǔ)言。即使我們不使用 RTL 語(yǔ)言,偽語(yǔ)言區(qū)域也可以幫助我們創(chuàng)建應(yīng)用的 RTL 版本。 部分定制手機(jī)可能不存在這兩種偽語(yǔ)言區(qū)域。

image

英語(yǔ) (XA):在基本英文界面文本中添加拉丁語(yǔ)重音符號(hào),通過(guò)添加不帶重音符號(hào)的文本擴(kuò)展原始文本,并用方括號(hào)將每個(gè)消息單元括起來(lái),以使擴(kuò)展文本中的潛在問(wèn)題暴露出來(lái)。潛在的問(wèn)題可能是布局損壞和消息語(yǔ)法錯(cuò)誤,表現(xiàn)為一個(gè)句子被分成多個(gè)部分,顯示為多條由括號(hào)括住的消息。
AR (XB):將從左到右顯示的原始消息的文本方向設(shè)為從右到左的方向,它會(huì)顛倒原始消息中字符的順序。
image

要使用 Android 偽語(yǔ)言區(qū)域,必須運(yùn)行 Android 4.3(API 級(jí)別 18)或更高版本,并在設(shè)備上啟用開(kāi)發(fā)者選項(xiàng)。在 Android Studio 中,可以通過(guò)以下配置添加到 build.gradle 文件來(lái)為特定應(yīng)用啟用偽語(yǔ)言區(qū)域.

 android {
      ...
      buildTypes {
        debug {
          pseudoLocalesEnabled true
        }
      }

RTL布局,阿拉伯語(yǔ)的適配

  • 一些簡(jiǎn)要的屬性及API
name desc chinese
android:layoutDirection the direction of layout drawing 設(shè)置組件的布局排列方向
android:textDirection the direction of the text 設(shè)置組件的文字排列方向
android:textAlignment the alignment of the text 設(shè)置文字的對(duì)齊方式
getLayoutDirectionFromLocale() the layout direction for a given Locale 獲取指定地區(qū)的慣用布局方式
  • 啟用系統(tǒng)的RTL支持,這個(gè)只需要在清單文件增加相關(guān)配置即可
<application  
 android:supportsRtl="true">
</application>
  • 全局替換xxxLeft/xxxRight為xxxStart/xxxEnd,可通過(guò)AS中選中Refactor->Add RTL Support


    image
  • TextView和EditText控件的全局適配,可以通過(guò)主題theme指定全局的樣式
 <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- 阿拉伯語(yǔ)言適配-->
        <item name="android:textViewStyle">@style/TextViewStyle.TextDirection</item>
        <item name="editTextStyle">@style/EditTextStyle.Alignment</item>
    </style>

    <!--阿拉伯語(yǔ)適配文本-->
    <style name="TextViewStyle.RTL" parent="android:Widget.TextView">
        <item name="android:textDirection">locale</item>
    </style>

    <!--阿拉伯語(yǔ)適配編輯框-->
    <style name="EditTextStyle.RTL" parent="@android:style/Widget.EditText">
        <item name="android:textAlignment">viewStart</item>
        <item name="android:gravity">start|center_vertical</item>
        <item name="android:textDirection">locale</item>
    </style>
  • 適配圖片,部分比較敏感的圖片,比如箭頭一些方向性的圖標(biāo),需要?jiǎng)?chuàng)建翻轉(zhuǎn)鏡像。
    第一種方式:通過(guò)適配drawable資源目錄,放置對(duì)應(yīng)的翻轉(zhuǎn)后的圖片資源,比如:drawable-ldrtl-xhdpi,但是這樣可能會(huì)增加額外的包體積大小。
..res
  ..drawable-ldrtl-xhdpi
    ..icon_logo.png
  ..drawable-ldrtl-xxhdpi
    ..icon_logo.png

第二種方式:如果是svg矢量圖或者自定義的drawable,可以通過(guò)設(shè)置android:autoMirrored="true"屬性,當(dāng)系統(tǒng)檢測(cè)到RTL布局時(shí),會(huì)自動(dòng)創(chuàng)建圖片鏡像。若是ImageView可以通過(guò)Drawable對(duì)象來(lái)創(chuàng)建鏡像。

// svg創(chuàng)建鏡像
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="20dp"
    android:height="20dp"
    android:autoMirrored="true"
    android:viewportWidth="20"
    android:viewportHeight="20">
  <path
      android:pathData="M5.5,10l3,4l7,-8"
      android:strokeLineCap="round"/>
</vector>

// ImageView創(chuàng)建鏡像
 Drawable drawable = ContextCompat.getDrawable(context, R.drawable.logo);
 // 設(shè)置所有的返回按鈕支持RTL布局
 drawable.setAutoMirrored(true);
 imageView.setImageDrawable(drawable);

  • 僅設(shè)置center_vertical屬性,最好能帶上start/end等屬性。
  • 動(dòng)態(tài)設(shè)置setPadding應(yīng)使用setPaddingRelative代替
  • 動(dòng)態(tài)設(shè)置setCompoundDrawables應(yīng)使用setCompoundDrawablesRelative代替,同樣getCompoundDrawables應(yīng)使用getCompoundDrawablesRelative代替
  • 動(dòng)態(tài)設(shè)置setCompoundDrawablesWithIntrinsicBounds應(yīng)使用setCompoundDrawablesRelativeWithIntrinsicBounds代替
  • ..leftMargin /.rightMargin 使用 setMarginStart/setMarginEnd代替
  • 某些自定義控件包含TextView或者Editext時(shí),如果全局主題適配失效,那么最好需要xml中重寫(xiě)style
  • 自定義控件關(guān)于獲取getX橫坐標(biāo)距離計(jì)算相關(guān)邏輯,需要?jiǎng)討B(tài)判斷是否是RTL布局來(lái)重新計(jì)算坐標(biāo)和距離
// 當(dāng)前布局是否為RTL布局,true RTL/false LTR
boolean isRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL
if (isRtl) {
   // TODO 如果是阿拉伯語(yǔ),重新計(jì)算橫坐標(biāo),得到x的點(diǎn)擊區(qū)域
   return event.getX() > getPaddingEnd() && (event.getX() < getPaddingEnd() + getCompoundDrawablesRelative()[2].getIntrinsicWidth());
   } else {
  return event.getX() > (getWidth() - getPaddingEnd() - getCompoundDrawablesRelative()[2].getIntrinsicWidth()) && (event.getX() < ((getWidth() - getPaddingEnd())));
  }
  • 抽屜布局openDrawer和closeDrawer包含的Gravity屬性 需要通過(guò)GravityCompat替換。比如Gravity.LEFT需要用GravityCompat.START替代
// 替換前
drawerLayout.closeDrawer(Gravity.LEFT)
// 替換后
drawerLayout.closeDrawer(GravityCompat.START)
  • 雙光標(biāo)的現(xiàn)象,部分手機(jī)的輸入框可能在RTL語(yǔ)言下,一段文字的左上角和右下角出現(xiàn)半段主光標(biāo)和副光標(biāo),這屬于正?,F(xiàn)象,是為了多語(yǔ)言文字混編更好的體驗(yàn),如果感覺(jué)不爽,可以通過(guò)layout資源重寫(xiě)一套布局來(lái)解決。
  • 字符串格式化一些列問(wèn)題,像日期和阿拉伯?dāng)?shù)字格式化,String.format可以不使用默認(rèn)的Local
 // 指定Local兼容RTL
 SimpleDateFormat sdf = new SimpleDateFormat(format,Locale.US);
 // 指定Local兼容RTL
 String.format(Locale.US, "%d", minutes)
  • 網(wǎng)頁(yè)webView適配問(wèn)題,可以通過(guò)前端的同學(xué)自己進(jìn)行適配,主要使用dir屬性指定rtl布局
<!DOCTYPE html>
<html dir="rtl">
<head> 
<meta charset="utf-8"> 
<title>RTL布局測(cè)試</title> 
</head>
<body>
<bdo>文本方向從右到左!</bdo>
</body>
</html>
  • 垂直LinearLayout控件使用weight屬性偶爾會(huì)導(dǎo)致適配失效,建議用FrameLayout的layout_gravity屬性控制,或者使用RelativeLayout
  • 使用RelativeLayout時(shí),盡量指定android:layout_alignParentStart,否則大部分界面,譬如在列表RecyclerView中作為item存在時(shí),可能會(huì)出現(xiàn)布局錯(cuò)亂,因?yàn)樗恢榔鹗伎丶奈恢?/li>
  • 對(duì)于ConstraintLayout布局,關(guān)于屏障Barrier控件的barrierDirection屬性支持不友好,AS無(wú)法自動(dòng)將left轉(zhuǎn)換為start,需要自己手動(dòng)適配
    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="start"
        app:constraint_referenced_ids="haha"/>
  • 通常TextView寬度若是match_parent,要為他設(shè)置android:textAlignment="viewStart/viewEnd"
  • 在多層Fragment或者部分界面局部控件突然適配失效的情況,需要在加載頁(yè)面前重新進(jìn)行語(yǔ)言適配
  • 使用到WebView控件,當(dāng)應(yīng)用內(nèi)切換語(yǔ)言后,第一次加載會(huì)導(dǎo)致整個(gè)頁(yè)面出現(xiàn)適配無(wú)效的情況,解決方案如下:
1.在基類(lèi)的BaseActivity的setContentView方法之前重新設(shè)置語(yǔ)言
    public static void changeLanguage(Context context, String newLanguage) {
        Resources resources = context.getResources();
        Configuration configuration = resources.getConfiguration();
        // app locale
        Locale locale = getLocaleByLanguage(newLanguage);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            configuration.setLocale(locale);
        } else {
            configuration.locale = locale;
        }
        DisplayMetrics dm = resources.getDisplayMetrics();
        resources.updateConfiguration(configuration, dm);
        //保存當(dāng)前語(yǔ)言
        ...
    }
2.在使用到WebView的界面,onCrate方法增加如下
 @Override
    public void onCreate(Bundle savedInstanceState) {
        // TODO 解決含有webView控件導(dǎo)致切換語(yǔ)言失效
        new WebView(this).destroy();
        super.onCreate(savedInstanceState);
    }

最后

關(guān)于本地化,個(gè)人的建議就是杜絕任何形式的硬編碼字符串資源,靈活的使用語(yǔ)言區(qū)域限定符和布局方向限定符,某些不應(yīng)該被翻譯的部分應(yīng)當(dāng)合理的使用標(biāo)記符進(jìn)行標(biāo)記,部分圖片盡量通過(guò)系統(tǒng)提供的鏡像API進(jìn)行適配,防止apk資源包變得越來(lái)越龐大,還有一些程序使用過(guò)程中動(dòng)態(tài)的方法需要通過(guò)全局搜索進(jìn)行整體替換,一些第三方庫(kù)這個(gè)不屬于自己能完全控制的范疇,可視情況而定,所以,如果你本身有很不錯(cuò)的開(kāi)源項(xiàng)目,也應(yīng)該考慮下國(guó)際化。另外我很懶的,所以喜歡這篇文章的話(huà)隨手點(diǎn)個(gè)贊吧!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 版本記錄 前言 很多APP都有國(guó)際化版本,常見(jiàn)于一些大公司的產(chǎn)品,比如Facebook、Wechat等,那么國(guó)際化...
    刀客傳奇閱讀 3,905評(píng)論 0 0
  • 邂逅FLutter 萬(wàn)物皆是Widget 一般縮進(jìn)2個(gè)空格 文字居中 Widget Center() Materi...
    JackLeeVip閱讀 3,496評(píng)論 0 4
  • 久違的晴天,家長(zhǎng)會(huì)。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí),離放學(xué)已經(jīng)沒(méi)多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,822評(píng)論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開(kāi)了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    余生動(dòng)聽(tīng)閱讀 10,858評(píng)論 0 11
  • 可愛(ài)進(jìn)取,孤獨(dú)成精。努力飛翔,天堂翱翔。戰(zhàn)爭(zhēng)美好,孤獨(dú)進(jìn)取。膽大飛翔,成就輝煌。努力進(jìn)取,遙望,和諧家園。可愛(ài)游走...
    趙原野閱讀 3,508評(píng)論 1 1

友情鏈接更多精彩內(nèi)容