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.整理HardCodeApp國際化是要在保持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中需要轉義,下面是一些常用的特殊字符轉義之後的樣
符號轉義表示「" 或 "『' 或 '&& 或 &<< 或 <>> 或 >換行\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的去重功能,選擇 數據 > 刪除重複項,以待翻譯欄位所在列為查重列,可以得到最終的結果
可以將得到文檔提交給專業的翻譯團隊了。
得到翻譯好的的excel文檔之後
依舊可以藉助excel的拼接功能,在D1輸入拼接語句="<string name="&""""&A1&""""&">"&C1&"</string>",可以生成符合strings.xml中格式要求的內容
下拉統一格式,所有的都可以自動拼接
最後在項目中的res目錄下創建各自的資源文件夾,右擊res文件夾 > New > Android resource directory
選擇Locale
例如選擇的英語,會得到如下的文件夾
然後將前面整理好的各國語言的strings.xml放到對應的目錄下即可。
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國際化的工作並沒有太多的技術難度,更多的是一些繁瑣的文本處理。如何利用自動化的工具來解放人工操作,如何從看似雜亂無章的內容中尋找出規律,如何改善自己的編程規範,才是真正能從這個過程中學習到的東西。當然我只是列舉了部分情況,不同的項目都會對應著不同的特例,很難面面俱到,仍然需要不斷的摸索。