一、国际化编程的本质及原理分析
- 一套软件,多个语言包
Java是第一个设计成支持国际化的编程语言
- 重点在于如下两个类;
- java.util.ResourceBundle 用于加载一个语言_国家包
- java.util.Locale 定义一个语言_国家
- 语言 zh,en等,国家/地区CN,US等
- 语言文件
- 就是一个properties文件
- Properties 类在加载.proerties文件时使用的iso-8859-1的编码,所以由中文时必须进行native2ascii进行转义。
- 包含键值对,形式为key=value,例如age=20;name=中国
- 存储编码必须为ASCII编码,如果是ASCII以外的文字,则必须使用native2ascii.exe(%JAVA_HOME%/bin目录下)进行转码为Unicode,命令格式如下:
native2ascii xxx.properties xxx_语言_国家.properties
命名规则:
- 包名_语言_国家地区.properties (语言和国家/地区可选)
- message.properties
- message.zh.properties
- message.zh_CN.properties
二、代码实践
项目文件结构如下图:
请先忽略其中的resource文件夹,com目录是编译Main.java时自动生成的class包目录,重点先关注Main.java文件,其中Main.java文件的内容如下:
package com.cholen;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
class MainWindow extends JFrame implements ActionListener{
private JLabel jlbAccount;
private JLabel jlbPassword;
private JTextField jtfAccount;
private JTextField jtfPassword;
private JButton btnLogin;
private JButton btnCancel;
private JPanel mainPanel;
private JMenuBar jMenuBar;
private JMenu jmLanguage;
private JMenuItem jmiZH;
private JMenuItem jmiEN;
private static Locale locale;
private static ResourceBundle bundle;
public MainWindow(){
mainPanel = new JPanel();
mainPanel.setLayout(new GridLayout(3, 2, 5, 5));
jlbAccount = new JLabel();
jlbAccount.setHorizontalAlignment(JLabel.CENTER);
jlbAccount.setVerticalAlignment(JLabel.CENTER);
jlbPassword = new JLabel();
jlbPassword.setHorizontalAlignment(JLabel.CENTER);
jlbAccount.setVerticalAlignment(JLabel.CENTER);
jtfAccount = new JTextField();
jtfPassword = new JTextField();
btnLogin = new JButton();
btnCancel = new JButton();
jMenuBar = new JMenuBar();
jmLanguage = new JMenu();
jmiZH = new JMenuItem();
jmiEN = new JMenuItem();
jmLanguage.add(jmiZH);
jmLanguage.addSeparator();
jmLanguage.add(jmiEN);
jMenuBar.add(jmLanguage);
mainPanel.add(jlbAccount);
mainPanel.add(jtfAccount);
mainPanel.add(jlbPassword);
mainPanel.add(jtfPassword);
mainPanel.add(btnLogin);
mainPanel.add(btnCancel);
setLanguage(locale);
jmiEN.addActionListener(this);
jmiZH.addActionListener(this);
this.setJMenuBar(jMenuBar);
this.getContentPane().add(mainPanel);
this.setSize(512, 600);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==jmiZH){
locale = new Locale("zh", "CN");
this.setLanguage(locale);
}else if(e.getSource()==jmiEN){
locale = new Locale("en", "US");
this.setLanguage(locale);
}
}
private void setLanguage(Locale locale){
bundle = ResourceBundle.getBundle("messages", locale);
this.setTitle(bundle.getString("mainWindowTitle"));
jmLanguage.setText(bundle.getString("jmLanguage"));
jmiZH.setText(bundle.getString("jmiZH"));
jmiEN.setText(bundle.getString("jmiEN"));
jlbAccount.setText(bundle.getString("jlbAccount"));
jlbPassword.setText(bundle.getString("jlbPassword"));
btnLogin.setText(bundle.getString("btnLogin"));
btnCancel.setText(bundle.getString("btnCancel"));
}
static {
// 取得系统默认的国家_语言环境
locale = Locale.getDefault();
}
}
public class Main{
public static void main(String[] args){
new MainWindow();
}
}
进入到Main.java所在的文件夹下,执行如下编译命令即可生成上述的com目录:
javac -d . Main.java
其中messages_zh_CN.properties原始内容如下:
jmLanguage=语言
jmiZH=中文
jmiEN=英文
jlbAccount=账号
jlbPassword=密码
btnLogin=登录
btnCancel=取消
mainWindowTitle=微信
- 请务必在保存时将该文件保存为ascii类型
进入到message_zh_CN.properties文件所在的目录,接下来用native2ascii.exe进行转码,命令如下:
native2ascii message_zh_CN.properties message_zh_CN.properties
- native2ascii的第一个参数是src文件名,第二个参数是out文件名
这里将输出和输入定为一样的,则原来的message_zh_CN.properties会被覆盖,被覆盖后的内容如下:
jmLanguage=\u8bed\u8a00
jmiZH=\u4e2d\u6587
jmiEN=\u82f1\u6587
jlbAccount=\u8d26\u53f7
jlbPassword=\u5bc6\u7801
btnLogin=\u767b\u5f55
btnCancel=\u53d6\u6d88
mainWindowTitle=\u5fae\u4fe1
其中message_en_US.properties的内容如下:
jmLanguage=language
jmiZH=Chinese
jmiEN=English
jlbAccount=account
jlbPassword=password
btnLogin=login
btnCancel=cancel
mainWindowTitle=WeChat
- 由于其本身就是英文,所以不需要用native2ascii进行转码,但请务必在保存时选择保存为ascii类型
直接用java命令运行生成的com.cholen.Main.class 文件,效果图如下面视频所展示:
Java国际化编程效果展示 (中英文切换)
这里视频不清晰的话,请移步到下方链接地址:
https://www.bilibili.com/video/BV1hA41177Yv/
三、实践过程中的问题
3.1 问题提出
在开始编写代码时,我并没有将MainWindow类实现ActionListener接口,而是想到的是直接在构造函数中为菜单项 “中文”、“英文”(代码中的jmiZH、jmiEN)直接addActionListener实现语言_地区的转换,而本例由于要在addActionListener中调用该类的一个成员方法setLanguage(Locale locale), 由于此时构造函数没有执行完,对象还没有生成,所以这样的做法不成功。
3.2 问题解决
让MainWindow这个类去实现ActionListener接口,并重写其中的actionPerformed(ActionEvent e)方法,如下:
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==jmiZH){
locale = new Locale("zh", "CN");
this.setLanguage(locale);
}else if(e.getSource()==jmiEN){
locale = new Locale("en", "US");
this.setLanguage(locale);
}
}
请务必在构造函数中为菜单项 “中文”、“英文”(代码中的jmiZH、jmiEN)添加事件监听,代码如下:
jmiEN.addActionListener(this);
jmiZH.addActionListener(this);
四、代码实践后的思考
4.1 关于.properties文件存放的路径问题
messages_en_US.properties和messages_zh_CN.properties文件目前必须放在类的顶级目录下,
- 能否将其放在某个包下面,此时如何找到该资源文件?
- 能否新建一个resource目录,再在resource目录下新建一个language目录,将其放在该目录下,此时如何找到该资源文件?
4.2 解决方案
4.2.1 可以将资源文件放在某个包下面,譬如我将其放在com包目录下,如下图展示:
那么此时源代码中的:
bundle = ResourceBundle.getBundle("messages", locale);
这一行应改为:
bundle = ResourceBundle.getBundle("com.messages", locale);
也就是说带上包名
4.2.2 可以将资源文件放在\resource\language目录下,如下图展示:
问题的关键就在于Java对于外部资源的访问,这是一个主题,值得弄懂学习,读者可以自行学习,我这里直接给出解决方案,自己写一个流将资源文件读入(请务必关注资源的路径问题)。