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ū)分,即ldrtl與ldltr,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ū)域。如下圖:
國(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ì)講到。
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ǔ)言,如下圖:
偽語(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ū)域。
英語(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ì)顛倒原始消息中字符的順序。
要使用 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è)贊吧!