有关Android国际化的一点积累

来源:互联网 发布:美国特效软件 编辑:程序博客网 时间:2024/05/22 04:55
转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://wy521angel.blog.51cto.com/3262615/1643618

    流行好用的软件应该能够适用于不同地区的市场。下面记录一些在项目中国际化的应用,有关图片和文本资源的自适应。

     Android采用XML资源文件来管理所有字符串消息,如果系统设置的Custom Locale,没有对应的本地化资源文件,那么程序就会取默认的res\values\strings.xml。在此我们默认的strings为英文,如下:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">L10NTest</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Hello world!</string>
    <string name="I_love_you">I love you.</string>
</resources>

    我们添加有关中文的字符串资源,这需要为values目录添加中文的语言国家版本。values文件夹的命名方式为:values-语言代码-r国家代码。

    如果想要支持中文的话,则需要在res目录下添加values-zh-rCN。该文件夹中的strings如下:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">国际化测试</string>
    <string name="action_settings">设置</string>
    <string name="hello_world">世界,你好!</string>
    <string name="I_love_you">我爱你.</string>
</resources>

    如果希望应用的图片也能随语言环境改变,则需要为drawable目录添加其他的语言国家版本。drawable文件夹的命名方式为:drawable-语言代码-r国家代码。同样支持中文的话需要在res目录下添加drawable-zh-rCN。

    如果还需要为drawable目录按照分辨率提供文件夹,可以在其后追加分辨率后缀,如:drawable-zh-rCN-mdpi、drawable-en-rUS-xhdpi等等。

    主Activity中没有写任何东西,下面是它的XML布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical" >
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/country"
        tools:ignore="ContentDescription" />
 <!-- tools:ignore="ContentDescription",屏蔽掉对View设置备注说明的提示 -->
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        android:textSize="20sp" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/I_love_you"
        android:textSize="20sp" />
</LinearLayout>

    在android ADT 16.0以后的控件中,没有文本描述的view如果不加android:contentDescription="@string/……"的话就会有黄 色的下划线,提示“[Accessibility] Missing contentDescription attribute on image”。加“ tools:ignore="ContentDescription"“便是屏蔽掉该提示。这句代码不加也没什么影响,在界面上不会有其他效果,当然也可以写一些字符串数据做提示,方便他人阅读。或者直接写成:

1
2
3
4
5
<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@null"
        android:src="@drawable/country" />

也可。

    以下是应用的res结构:

        wKiom1VI3QTQd4XcAAEtvUBA0Pg471.jpg

    我增加了”drawable-en-rUS“、”drawable-zh-rCN“和”values-zh-rCN“三个文件夹,其中前两个文件夹中放了名称都是”country“的图片,只是前者为美国国旗图片,后者为中国国旗图片。第三个文件夹中的strings文件上面已给出。

    运行程序,如果设置系统的语言和输入为中文,效果图如下:

        wKioL1VI4RaQEdNfAAVLO6c0vGY031.jpg

    如果设置为英文,效果图如下:

        wKioL1VI4bDR5LIhAAgH3Dcmpg4069.jpg

    我并没有增加有关英文的本地化资源文件,所以程序就会取res\values\下的strings.xml,当然也可以增加对应的文件夹为:values-en-rUS,其中放置相关文本资源。

    如果是其他国家的语言环境,那文件夹该如何命名呢,以字符串资源为例,如下:

        中文(中国):values-zh-rCN

        中文(台湾):values-zh-rTW

        中文(香港):values-zh-rHK

        英语(美国):values-en-rUS

        英语(英国):values-en-rGB

        英文(澳大利亚):values-en-rAU

        英文(加拿大):values-en-rCA

        英文(爱尔兰):values-en-rIE

        英文(印度):values-en-rIN

        英文(新西兰):values-en-rNZ

        英文(新加坡):values-en-rSG

        英文(南非):values-en-rZA

        阿拉伯文(埃及):values-ar-rEG

        阿拉伯文(以色列):values-ar-rIL

        保加利亚文:  values-bg-rBG

        加泰罗尼亚文:values-ca-rES

        捷克文:values-cs-rCZ

        丹麦文:values-da-rDK

        德文(奥地利):values-de-rAT

        德文(瑞士):values-de-rCH

        德文(德国):values-de-rDE

        德文(列支敦士登):values-de-rLI

        希腊文:values-el-rGR

        西班牙文(西班牙):values-es-rES

        西班牙文(美国):values-es-rUS

        芬兰文(芬兰):values-fi-rFI

        法文(比利时):values-fr-rBE

        法文(加拿大):values-fr-rCA

        法文(瑞士):values-fr-rCH

        法文(法国):values-fr-rFR

        希伯来文:values-iw-rIL

        印地文:values-hi-rIN

        克罗里亚文:values-hr-rHR

        匈牙利文:values-hu-rHU

        印度尼西亚文:values-in-rID

        意大利文(瑞士):values-it-rCH

        意大利文(意大利):values-it-rIT

        日文:values-ja-rJP

        韩文:values-ko-rKR

        立陶宛文:valueslt-rLT

        拉脱维亚文:values-lv-rLV

        挪威博克马尔文:values-nb-rNO

        荷兰文(比利时):values-nl-BE

        荷兰文(荷兰):values-nl-rNL

        波兰文:values-pl-rPL

        葡萄牙文(巴西):values-pt-rBR

        葡萄牙文(葡萄牙):values-pt-rPT

        罗马尼亚文:values-ro-rRO

        俄文:values-ru-rRU

        斯洛伐克文:values-sk-rSK

        斯洛文尼亚文:values-sl-rSI

        塞尔维亚文:values-sr-rRS

        瑞典文:values-sv-rSE

        泰文:values-th-rTH

        塔加洛语:values-tl-rPH

        土耳其文:values--r-rTR

        乌克兰文:values-uk-rUA

        越南文:values-vi-rVN

    事实上可以通过程序获取JAVA所有支持的语言和国家的,程序代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.Locale;
public class Test {
 public static void main(String[] args) {
  //调用Locale类的getAvailableLocales方法获取,该方法返回一个数组,其中包含JAVA支持的语言和国家
  Locale[] localeList = Locale.getAvailableLocales();
  //依次获取所支持的国家和语言
  for (int i = 0; i < localeList.length; i++) {
   System.out.println(localeList[i].getDisplayCountry() + "="
     + localeList[i].getCountry() + ""
     + localeList[i].getDisplayLanguage() + "="
     + localeList[i].getLanguage());
  }
 }
}

    输出结果如下(有些国家可能会使用多种语言):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
马来西亚=MY马来文=ms
卡塔尔=QA阿拉伯文=ar
冰岛=IS冰岛文=is
芬兰=FI芬兰文=fi
=波兰文=pl
马耳他=MT英文=en
瑞士=CH意大利文=it
比利时=BE荷兰文=nl
沙特阿拉伯=SA阿拉伯文=ar
伊拉克=IQ阿拉伯文=ar
波多黎哥=PR西班牙文=es
智利=CL西班牙文=es
=芬兰文=fi
奥地利=AT德文=de
=丹麦文=da
英国=GB英文=en
巴拿马=PA西班牙文=es
=塞尔维亚文=sr
也门=YE阿拉伯文=ar
马其顿王国=MK马其顿文=mk
=马其顿文=mk
加拿大=CA英文=en
越南=VN越南文=vi
荷兰=NL荷兰文=nl
美国=US西班牙文=es
中国=CN中文=zh
洪都拉斯=HN西班牙文=es
美国=US英文=en
=法文=fr
=泰文=th
=阿拉伯文=ar
摩洛哥=MA阿拉伯文=ar
=拉托维亚文(列托)=lv
=德文=de
印度尼西亚=ID印度尼西亚文=in
=克罗地亚文=hr
南非=ZA英文=en
韩国=KR朝鲜文=ko
突尼斯=TN阿拉伯文=ar
=印度尼西亚文=in
=日文=ja
塞尔维亚=RS塞尔维亚文=sr
白俄罗斯=BY白俄罗斯文=be
台湾地区=TW中文=zh
苏丹=SD阿拉伯文=ar
=葡萄牙文=pt
=冰岛文=is
日本=JP日文=ja
玻利维亚=BO西班牙文=es
阿尔及利亚=DZ阿拉伯文=ar
=马来文=ms
阿根廷=AR西班牙文=es
阿拉伯联合酋长国=AE阿拉伯文=ar
加拿大=CA法文=fr
=斯洛文尼亚文=sl
=西班牙文=es
立陶宛=LT立陶宛文=lt
黑山=ME塞尔维亚文=sr
叙利亚=SY阿拉伯文=ar
俄罗斯=RU俄文=ru
比利时=BE法文=fr
西班牙=ES西班牙文=es
=保加利亚文=bg
以色列=IL希伯来文=iw
=瑞典文=sv
=英文=en
=希伯来文=iw
丹麦=DK丹麦文=da
哥斯达黎加=CR西班牙文=es
香港=HK中文=zh
=中文=zh
西班牙=ES加泰罗尼亚文=ca
泰国=TH泰文=th
乌克兰=UA乌克兰文=uk
多米尼加共和国=DO西班牙文=es
委内瑞拉=VE西班牙文=es
波兰=PL波兰文=pl
利比亚=LY阿拉伯文=ar
约旦=JO阿拉伯文=ar
=意大利文=it
=乌克兰文=uk
匈牙利=HU匈牙利文=hu
=爱尔兰文=ga
危地马拉=GT西班牙文=es
巴拉圭=PY西班牙文=es
保加利亚=BG保加利亚文=bg
克罗地亚=HR克罗地亚文=hr
波斯尼亚和黑山共和国=BA塞尔维亚文=sr
罗马尼亚=RO罗马尼亚文=ro
卢森堡=LU法文=fr
=挪威文=no
=立陶宛文=lt
新加坡=SG英文=en
厄瓜多尔=EC西班牙文=es
波斯尼亚和黑山共和国=BA塞尔维亚文=sr
尼加拉瓜=NI西班牙文=es
=斯洛伐克文=sk
=俄文=ru
=马耳他文=mt
萨尔瓦多=SV西班牙文=es
=荷兰文=nl
印度=IN印地文=hi
=爱沙尼亚文=et
希腊=GR希腊文=el
斯洛文尼亚=SI斯洛文尼亚文=sl
意大利=IT意大利文=it
日本=JP日文=ja
卢森堡=LU德文=de
瑞士=CH法文=fr
马耳他=MT马耳他文=mt
巴林=BH阿拉伯文=ar
=阿尔巴尼亚文=sq
=越南文=vi
黑山=ME塞尔维亚文=sr
巴西=BR葡萄牙文=pt
挪威=NO挪威文=no
=希腊文=el
瑞士=CH德文=de
新加坡=SG中文=zh
科威特=KW阿拉伯文=ar
埃及=EG阿拉伯文=ar
爱尔兰=IE爱尔兰文=ga
秘鲁=PE西班牙文=es
捷克共和国=CZ捷克文=cs
土耳其=TR土耳其文=tr
=捷克文=cs
乌拉圭=UY西班牙文=es
爱尔兰=IE英文=en
印度=IN英文=en
阿曼=OM阿拉伯文=ar
塞尔维亚及黑山=CS塞尔维亚文=sr
=加泰罗尼亚文=ca
=白俄罗斯文=be
=塞尔维亚文=sr
=朝鲜文=ko
阿尔巴尼亚=AL阿尔巴尼亚文=sq
葡萄牙=PT葡萄牙文=pt
拉脱维亚=LV拉托维亚文(列托)=lv
塞尔维亚=RS塞尔维亚文=sr
斯洛伐克=SK斯洛伐克文=sk
墨西哥=MX西班牙文=es
澳大利亚=AU英文=en
挪威=NO挪威文=no
新西兰=NZ英文=en
瑞典=SE瑞典文=sv
=罗马尼亚文=ro
黎巴嫩=LB阿拉伯文=ar
德国=DE德文=de
泰国=TH泰文=th
=土耳其文=tr
哥伦比亚=CO西班牙文=es
菲律宾=PH英文=en
爱沙尼亚=EE爱沙尼亚文=et
塞浦路斯=CY希腊文=el
=匈牙利文=hu
法国=FR法文=fr

    在做项目时,我遇到这样一种情况,字符串资源需要JAVA代码来控制,而不是在XML文件中获取,下面提供该方法:

    创建ConstantBase类来定义你需要的字符串,如下:

1
2
3
4
5
6
7
package com.example.l10ntest;
public class ConstantBase {
 public String PUT_NAME;
 public String SAY_LOVE;
 public String NEXT;
 public String Second_Page;
}

    中英文的字符串类分别命名如下,中文:

1
2
3
4
5
6
7
8
9
10
package com.example.l10ntest;
public class ConstantCH extends ConstantBase {
 public ConstantCH() {
  //此处定义需要的中文字符串
  PUT_NAME = "请输入你的名字。";
  SAY_LOVE = "我爱你";
  NEXT = "下一页";
  Second_Page = "第二页";
 }
}

    英文:

1
2
3
4
5
6
7
8
9
10
package com.example.l10ntest;
public class ConstantEN extends ConstantBase {
 public ConstantEN() {
  // 此处定义需要的英文字符串
  PUT_NAME = "Please put your name.";
  SAY_LOVE = "I love you";
  NEXT = "next";
  Second_Page = "The second page";
 }
}

    我在新创建的Constant类中写了本地语言的判断方法,该类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.l10ntest;
import java.util.Locale;
public class Constant {
 public static ConstantBase string = produceConstant();
 public static ConstantBase produceConstant() {
  if (isLocalLanguageZH()) {
   string = new ConstantCH();
  else {
   string = new ConstantEN();
  }
  return string;
 }
 //判断本地语言是否是中文
 public static boolean isLocalLanguageZH() {
  if (Locale.getDefault().getLanguage().contains("zh")
    && Locale.getDefault().getCountry().contains("CN")) {
   return true;
  }
  return false;
 }
}

    主类的XML文件做一下修改(暂时将ImageView屏蔽掉,后面增加了一个Button),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical" >
    <!-- <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@null"
        android:src="@drawable/country" /> -->
       
    <!-- tools:ignore="ContentDescription",屏蔽掉对View设置备注说明的提示 -->
    <TextView
        android:id="@+id/putName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@null"
        android:textSize="20sp" />
    <TextView
        android:id="@+id/iloveyou"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@null"
        android:textSize="20sp" />
    <Button
        android:id="@+id/next"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

    在主类中,写如下代码:

1
2
3
4
((TextView) findViewById(R.id.putName))
    .setText(Constant.string.PUT_NAME);
  ((TextView) findViewById(R.id.iloveyou))
    .setText(Constant.string.SAY_LOVE);

    这样便可以通过JAVA代码来控制字符串的获取了。但是发现一个问题,就是当程序切换到后台时,你将手机终端的语言修改,比如当前中文修改为英文,再从后台将该程序拉起时,显示的文本信息并不会随着变换,如果以前是中文,修改系统语言后还是中文,国际化似乎不生效。最后发现只有将该程序进程杀死,再次启动,国际化才会生效。但是每次切换一下终端语言就人为杀进程重启应用,显然是不合理的。

    事实上当locale信息改变之后,系统会发广播消息Intent.ACTION_LOCALE_CHANGED,我们可以写接收该广播的代码,并做一些操作。

    不妨多写几个Activity页面做测试,我写了两个,MainActivity经过修改的XML前面已经给出了,MainActivity如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.example.l10ntest;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends BaseActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  ((TextView) findViewById(R.id.putName))
    .setText(Constant.string.PUT_NAME);
  ((TextView) findViewById(R.id.iloveyou))
    .setText(Constant.string.SAY_LOVE);
  Button btn = (Button) findViewById(R.id.next);
  btn.setText(Constant.string.NEXT);
  btn.setOnClickListener(new OnClickListener() {
    
   @Override
   public void onClick(View v) {
    startActivity(new Intent(MainActivity.this,SecondActivity.class));
   }
  });
 }
}

    第二个Activity(SecondActivity)我没有写XML布局,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.example.l10ntest;
import android.os.Bundle;
import android.view.Gravity;
import android.widget.TextView;
public class SecondActivity extends BaseActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  TextView textView = new TextView(this);
  textView.setGravity(Gravity.CENTER);
  textView.setText(Constant.string.Second_Page);
  textView.setTextSize(30);
  setContentView(textView);
 }
}

    事实上一个应用一般不会只有一个Activity的,我将两个Activity都继承了BaseActivity,在BaseActivity中对这两个Activity做统一管理,BaseActivity如下:

1
2
3
4
5
6
7
8
9
10
package com.example.l10ntest;
import android.app.Activity;
import android.os.Bundle;
public class BaseActivity extends Activity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  Utils.addToList(this);
 }
}

    只调用了Utils类里的一个方法,在Activity创建后,将它添加到List里面。Utils类代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.example.l10ntest;
import java.util.ArrayList;
import java.util.Iterator;
import android.app.Activity;
import android.util.Log;
public class Utils {
 private static String TAG = "Utils";
 // 创建存放Activity的集合
 private static ArrayList<Activity> activityList = new ArrayList<Activity>();
 public static void addToList(Activity activity) {
  Log.i(TAG, "activity name:" + activity.getClass().getName());
  activityList.add(activity);
 }
 public static void stopApp() {
  Iterator<Activity> itr = activityList.iterator();
  Activity a = null;
  while (itr.hasNext()) {
   a = itr.next();
   a.finish();
  }
  Log.i(TAG, "stopApp end.");
  System.exit(0);
 }
}

    这里面有两个方法,一个就是添加Activity做统一管理,第二个是停止程序的方法,先将List中的Activity依次杀死,再杀死进程退出程序。

    下面就是比较重要的接收系统广播的类了,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.l10ntest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AppExitReceiver extends BroadcastReceiver {
 @Override
 public void onReceive(Context context, Intent intent) {
  if (intent == null || intent.getAction() == null)
   return;
  if (intent.getAction().equals(Intent.ACTION_LOCALE_CHANGED)) {
   Utils.stopApp();
  }
 }
}

    AndroidManifest.xml文件如下,配置了一个广播接收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.l10ntest"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.l10ntest.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.example.l10ntest.SecondActivity"
            android:label="@string/app_name" >
        </activity>
        <receiver android:name="com.example.l10ntest.AppExitReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.LOCALE_CHANGED" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

    整理一下思路:

    写了两个Activity,这两个Activity都用到了文本资源,如“((TextView) findViewById(R.id.iloveyou)).setText(Constant.string.SAY_LOVE);”和“textView.setText(Constant.string.Second_Page);”MainActivity中增加了跳转到SecondActivity的按钮。两个Activity都继承了BaseActivity,当它们创建后,会通过BaseActivity中的“Utils.addToList(this)”将它们依次添加到Utils类中的activityList里。

    在终端切换系统语言,发送Intent.ACTION_LOCALE_CHANGED广播后,我们创建AppExitReceiver类来接收该广播,并调用“Utils.stopApp()“来将创建的Activity依次关闭,并且退出应用程序,这样不管该程序从后台切回来还是点击该程序的图标,程序都会重新启动,显示的文本信息会随着系统语言的变换而变换。

本文出自 “皓轩” 博客,请务必保留此出处http://wy521angel.blog.51cto.com/3262615/1643618

0 0
原创粉丝点击