第14章:国际化(i18n)
作者:步子哥 (steper@foxmail.com)
14.1 为什么需要国际化?
不同语言的用户
// 不支持国际化
Label label = new Label(shell, SWT.NONE);
label.setText("Hello, World!"); // 只支持英文
// 支持国际化
Label label = new Label(shell, SWT.NONE);
label.setText(getMessage("greeting")); // 根据语言显示不同文本
费曼解释:国际化的重要性
国际化 = 多语言菜单
- 英语菜单:Hello, World!
- 中文菜单:你好,世界!
- 西班牙语菜单:Hola, Mundo!
- 不同语言的用户看到不同菜单
类比:
国际化 = 多语言说明书
- 英语说明书:English Manual
- 中文说明书:中文说明书
- 西班牙语说明书:Manual en Español
- 不同语言的用户看到不同说明书
日期、数字格式的差异
// 英语格式(美国)
SimpleDateFormat enDateFormat = new SimpleDateFormat("MM/dd/yyyy");
System.out.println(enDateFormat.format(new Date())); // 01/13/2026
// 中文格式(中国)
SimpleDateFormat zhDateFormat = new SimpleDateFormat("yyyy年MM月dd日");
System.out.println(zhDateFormat.format(new Date())); // 2026年01月13日
// 英语格式(数字)
DecimalFormat enNumberFormat = new DecimalFormat("#,##0.00");
System.out.println(enNumberFormat.format(1234567.89)); // 1,234,567.89
// 中文格式(数字)
DecimalFormat zhNumberFormat = new DecimalFormat("#,##0.00");
System.out.println(zhNumberFormat.format(1234567.89)); // 1,234,567.89
费曼解释:不同地区的格式差异
日期格式 = 写日记的格式
- 英语(美国):01/13/2026(月/日/年)
- 中文(中国):2026年01月13日(年/月/日)
- 西班牙语(西班牙):13/01/2026(日/月/年)
数字格式 = 写数字的格式
- 英语(美国):1,234,567.89(千分位逗号,小数点)
- 中文(中国):1,234,567.89(千分位逗号,小数点)
- 德语(德国):1.234.567,89(千分位点号,逗号)
类比:
日期格式 = 日历格式
- 美国日历:01/13/2026
- 中国日历:2026年01月13日
- 西班牙日历:13/01/2026
数字格式 = 账本格式
- 美国账本:1,234,567.89
- 中国账本:1,234,567.89
- 德国账本:1.234.567,89
代码示例:国际化的重要性
public class I18nExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("国际化示例");
shell.setLayout(new GridLayout(1, false));
// 不支持国际化
Label noI18nLabel = new Label(shell, SWT.WRAP);
noI18nLabel.setText("不支持国际化:只显示英文 \"Hello, World!\"");
GridData labelData = new GridData();
labelData.horizontalAlignment = GridData.FILL;
labelData.grabExcessHorizontalSpace = true;
noI18nLabel.setLayoutData(labelData);
// 支持国际化
Label i18nLabel = new Label(shell, SWT.WRAP);
i18nLabel.setText("支持国际化:根据用户语言显示不同文本");
i18nLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
// 选择语言
Composite languagePanel = new Composite(shell, SWT.NONE);
languagePanel.setLayout(new FillLayout(SWT.HORIZONTAL));
Button enButton = new Button(languagePanel, SWT.PUSH);
enButton.setText("英语");
enButton.addListener(SWT.Selection, event -> {
Locale locale = Locale.US;
showLocalizedMessage(locale);
});
Button zhButton = new Button(languagePanel, SWT.PUSH);
zhButton.setText("中文");
zhButton.addListener(SWT.Selection, event -> {
Locale locale = Locale.CHINA;
showLocalizedMessage(locale);
});
Button esButton = new Button(languagePanel, SWT.PUSH);
esButton.setText("西班牙语");
esButton.addListener(SWT.Selection, event -> {
Locale locale = new Locale("es", "ES");
showLocalizedMessage(locale);
});
// 显示当前系统语言
Button systemButton = new Button(shell, SWT.PUSH);
systemButton.setText("显示系统语言");
systemButton.addListener(SWT.Selection, event -> {
Locale systemLocale = Locale.getDefault();
showLocalizedMessage(systemLocale);
});
shell.setBounds(100, 100, 500, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static void showLocalizedMessage(Locale locale) {
String language = locale.getDisplayLanguage();
String country = locale.getDisplayCountry();
String greeting = getGreeting(locale);
String date = getFormattedDate(locale);
String number = getFormattedNumber(locale);
String message = String.format(
"语言:%s\n地区:%s\n\n问候:%s\n日期:%s\n数字:1,234,567.89",
language, country, greeting, date
);
System.out.println(message);
}
private static String getGreeting(Locale locale) {
String language = locale.getLanguage();
switch (language) {
case "en":
return "Hello, World!";
case "zh":
return "你好,世界!";
case "es":
return "Hola, Mundo!";
default:
return "Hello, World!";
}
}
private static String getFormattedDate(Locale locale) {
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
return dateFormat.format(new Date());
}
private static String getFormattedNumber(Locale locale) {
NumberFormat numberFormat = NumberFormat.getInstance(locale);
return numberFormat.format(1234567.89);
}
}
14.2 资源束(ResourceBundle)的使用
创建属性文件
messages.properties(英语)
greeting=Hello, World!
welcome=Welcome to my application!
name=Name:
email=Email:
save=Save
cancel=Cancel
exit=Exit
messages_zh.properties(中文)
greeting=\u4f60\u597d\uff0c\u4e16\u754c\uff01
welcome=\u6b22\u8fce\u4f7f\u7528\u6211\u7684\u5e94\u7528\uff01
name=\u59d3\u540d\uff1a
email=\u90ae\u7bb1\uff1a
save=\u4fdd\u5b58
cancel=\u53d6\u6d88
exit=\u9000\u51fa
messages_es.properties(西班牙语)
greeting=Hola, Mundo!
welcome=¡Bienvenido a mi aplicación!
name=Nombre:
email=Correo electrónico:
save=Guardar
cancel=Cancelar
exit=Salir
费曼解释:属性文件的命名规则
属性文件命名 = 命名规范
- messages.properties:默认(英语)
- messages_zh.properties:中文
- messages_es.properties:西班牙语
- messages_fr.properties:法语
- 格式:messages_语言_地区.properties
类比:
属性文件命名 = 多语言书名
- 英文书:The Lord of the Rings
- 中文书:魔戒
- 西班牙语书:El Señor de los Anillos
- 法语书:Le Seigneur des Anneaux
加载资源
// 加载默认资源(英语)
ResourceBundle bundle = ResourceBundle.getBundle("messages");
// 加载指定语言的资源
Locale locale = new Locale("zh", "CN");
ResourceBundle zhBundle = ResourceBundle.getBundle("messages", locale);
// 获取资源
String greeting = bundle.getString("greeting");
System.out.println("问候:" + greeting); // Hello, World!
String zhGreeting = zhBundle.getString("greeting");
System.out.println("中文问候:" + zhGreeting); // 你好,世界!
费曼解释:ResourceBundle 的加载机制
ResourceBundle = 多语言书库
- 你要借一本书(加载资源)
- 你告诉图书管理员:你要什么语言(Locale)
- 图书管理员给你对应语言的书(ResourceBundle)
- 你阅读书中的内容(getString)
类比:
ResourceBundle = 多语言菜单
- 你要点菜(加载资源)
- 你告诉服务员:你要什么语言菜单(Locale)
- 服务员给你对应语言的菜单(ResourceBundle)
- 你点菜单上的菜(getString)
占位符替换
// 属性文件
// welcome=Welcome, {0}!
// 加载资源
ResourceBundle bundle = ResourceBundle.getBundle("messages");
// 替换占位符
String welcome = MessageFormat.format(bundle.getString("welcome"), "John");
System.out.println("欢迎:" + welcome); // Welcome, John!
费曼解释:占位符的原理
占位符 = 填空题
- 题目:Welcome, {0}!
- 答案:John
- 填空:Welcome, John!
类比:
占位符 = 邮件模板
- 模板:尊敬的 {0} 先生/女士:
- 填写:尊敬的 John 先生/女士:
代码示例:资源束的使用
public class ResourceBundleExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("资源束示例");
shell.setLayout(new GridLayout(1, false));
// 加载资源
ResourceBundle bundle = ResourceBundle.getBundle("messages");
// 显示问候
Label greetingLabel = new Label(shell, SWT.WRAP | SWT.CENTER);
greetingLabel.setText(bundle.getString("greeting"));
GridData greetingData = new GridData();
greetingData.horizontalAlignment = GridData.FILL;
greetingData.grabExcessHorizontalSpace = true;
greetingLabel.setLayoutData(greetingData);
// 显示欢迎
Label welcomeLabel = new Label(shell, SWT.WRAP | SWT.CENTER);
String welcome = MessageFormat.format(bundle.getString("welcome"), System.getProperty("user.name"));
welcomeLabel.setText(welcome);
welcomeLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
// 按钮:切换语言
Button enButton = new Button(shell, SWT.PUSH);
enButton.setText("English");
enButton.addListener(SWT.Selection, event -> {
Locale locale = Locale.US;
ResourceBundle enBundle = ResourceBundle.getBundle("messages", locale);
updateUI(enBundle);
});
Button zhButton = new Button(shell, SWT.PUSH);
zhButton.setText("中文");
zhButton.addListener(SWT.Selection, event -> {
Locale locale = Locale.CHINA;
ResourceBundle zhBundle = ResourceBundle.getBundle("messages", locale);
updateUI(zhBundle);
});
Button esButton = new Button(shell, SWT.PUSH);
esButton.setText("Español");
esButton.addListener(SWT.Selection, event -> {
Locale locale = new Locale("es", "ES");
ResourceBundle esBundle = ResourceBundle.getBundle("messages", locale);
updateUI(esBundle);
});
shell.setBounds(100, 100, 400, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static void updateUI(ResourceBundle bundle) {
// 注意:实际应用中需要传递 shell 和 Label 的引用
// 这里只是为了演示,实际需要重构代码
System.out.println("切换语言:" + bundle.getLocale().getDisplayLanguage());
System.out.println("问候:" + bundle.getString("greeting"));
System.out.println("欢迎:" + bundle.getString("welcome"));
}
}
14.3 SWT 的国际化支持
控件文本的翻译
// 加载资源
ResourceBundle bundle = ResourceBundle.getBundle("messages");
// 创建控件
Label nameLabel = new Label(shell, SWT.NONE);
nameLabel.setText(bundle.getString("name"));
Text nameText = new Text(shell, SWT.BORDER);
Label emailLabel = new Label(shell, SWT.NONE);
emailLabel.setText(bundle.getString("email"));
Text emailText = new Text(shell, SWT.BORDER);
// 按钮
Button saveButton = new Button(shell, SWT.PUSH);
saveButton.setText(bundle.getString("save"));
Button cancelButton = new Button(shell, SWT.PUSH);
cancelButton.setText(bundle.getString("cancel"));
费曼解释:控件文本翻译的流程
控件文本翻译 = 多语言标签
- 标签(控件)上有文字(文本)
- 不同语言显示不同文字(翻译)
类比:
控件文本翻译 = 多语言路标
- 路标(控件)上有文字(文本)
- 不同语言显示不同文字(翻译)
菜单的翻译
// 加载资源
ResourceBundle bundle = ResourceBundle.getBundle("messages");
// 创建菜单
Menu menuBar = new Menu(shell, SWT.BAR);
shell.setMenuBar(menuBar);
MenuItem fileMenu = new MenuItem(menuBar, SWT.CASCADE);
fileMenu.setText(bundle.getString("file"));
Menu fileMenuContent = new Menu(shell, SWT.DROP_DOWN);
fileMenu.setMenu(fileMenuContent);
MenuItem newItem = new MenuItem(fileMenuContent, SWT.PUSH);
newItem.setText(bundle.getString("new"));
MenuItem openItem = new MenuItem(fileMenuContent, SWT.PUSH);
openItem.setText(bundle.getString("open"));
new MenuItem(fileMenuContent, SWT.SEPARATOR);
MenuItem exitItem = new MenuItem(fileMenuContent, SWT.PUSH);
exitItem.setText(bundle.getString("exit"));
费曼解释:菜单翻译的流程
菜单翻译 = 多语言餐厅菜单
- 菜单(Menu)上有菜名(菜单项)
- 不同语言显示不同菜名(翻译)
类比:
菜单翻译 = 多语言路标
- 路标(Menu)上有文字(菜单项)
- 不同语言显示不同文字(翻译)
错误消息的翻译
// 加载资源
ResourceBundle bundle = ResourceBundle.getBundle("messages");
// 属性文件
// error.invalidEmail=Invalid email address!
// error.emptyField=The {0} field cannot be empty!
// 显示错误消息
MessageBox messageBox = new MessageBox(shell, SWT.ICON_ERROR | SWT.OK);
messageBox.setText(bundle.getString("error"));
String email = "";
if (!email.contains("@")) {
messageBox.setMessage(bundle.getString("error.invalidEmail"));
messageBox.open();
}
费曼解释:错误消息翻译的流程
错误消息翻译 = 多语言警示牌
- 警示牌(MessageBox)上有文字(错误消息)
- 不同语言显示不同文字(翻译)
类比:
错误消息翻译 = 多语言警告
- 警告(MessageBox)上有文字(错误消息)
- 不同语言显示不同文字(翻译)
代码示例:SWT 国际化
public class SWTI18nExample {
private static ResourceBundle bundle;
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new GridLayout(2, false));
// 加载默认资源
bundle = ResourceBundle.getBundle("messages");
// 标题
Label titleLabel = new Label(shell, SWT.NONE);
titleLabel.setText(bundle.getString("greeting"));
GridData titleData = new GridData();
titleData.horizontalSpan = 2;
titleData.horizontalAlignment = GridData.CENTER;
titleData.grabExcessHorizontalSpace = true;
titleLabel.setLayoutData(titleData);
// 姓名
Label nameLabel = new Label(shell, SWT.NONE);
nameLabel.setText(bundle.getString("name"));
Text nameText = new Text(shell, SWT.BORDER);
GridData nameData = new GridData();
nameData.horizontalAlignment = GridData.FILL;
nameData.grabExcessHorizontalSpace = true;
nameText.setLayoutData(nameData);
// 邮箱
Label emailLabel = new Label(shell, SWT.NONE);
emailLabel.setText(bundle.getString("email"));
Text emailText = new Text(shell, SWT.BORDER);
GridData emailData = new GridData();
emailData.horizontalAlignment = GridData.FILL;
emailData.grabExcessHorizontalSpace = true;
emailText.setLayoutData(emailData);
// 按钮
Button saveButton = new Button(shell, SWT.PUSH);
saveButton.setText(bundle.getString("save"));
saveButton.addListener(SWT.Selection, event -> {
String name = nameText.getText().trim();
String email = emailText.getText().trim();
if (name.isEmpty()) {
showError("error.emptyField", bundle.getString("name"));
return;
}
if (email.isEmpty()) {
showError("error.emptyField", bundle.getString("email"));
return;
}
if (!email.contains("@")) {
showError("error.invalidEmail", null);
return;
}
showSuccess();
});
Button cancelButton = new Button(shell, SWT.PUSH);
cancelButton.setText(bundle.getString("cancel"));
cancelButton.addListener(SWT.Selection, event -> {
shell.dispose();
});
// 语言切换
Button enButton = new Button(shell, SWT.PUSH);
enButton.setText("English");
enButton.addListener(SWT.Selection, event -> {
Locale locale = Locale.US;
bundle = ResourceBundle.getBundle("messages", locale);
updateUI(shell, nameLabel, emailLabel, saveButton, cancelButton);
});
Button zhButton = new Button(shell, SWT.PUSH);
zhButton.setText("中文");
zhButton.addListener(SWT.Selection, event -> {
Locale locale = Locale.CHINA;
bundle = ResourceBundle.getBundle("messages", locale);
updateUI(shell, nameLabel, emailLabel, saveButton, cancelButton);
});
shell.setBounds(100, 100, 400, 200);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
private static void updateUI(Shell shell, Label nameLabel, Label emailLabel,
Button saveButton, Button cancelButton) {
shell.setText(bundle.getString("title"));
nameLabel.setText(bundle.getString("name"));
emailLabel.setText(bundle.getString("email"));
saveButton.setText(bundle.getString("save"));
cancelButton.setText(bundle.getString("cancel"));
shell.layout();
}
private static void showError(String errorKey, String fieldName) {
Display display = Display.getCurrent();
Shell shell = display.getActiveShell();
String errorMessage;
if (fieldName != null) {
errorMessage = MessageFormat.format(bundle.getString(errorKey), fieldName);
} else {
errorMessage = bundle.getString(errorKey);
}
MessageBox messageBox = new MessageBox(shell, SWT.ICON_ERROR | SWT.OK);
messageBox.setText(bundle.getString("error"));
messageBox.setMessage(errorMessage);
messageBox.open();
}
private static void showSuccess() {
Display display = Display.getCurrent();
Shell shell = display.getActiveShell();
MessageBox messageBox = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
messageBox.setText(bundle.getString("success"));
messageBox.setMessage(bundle.getString("save.success"));
messageBox.open();
}
}
14.4 RTL(从右到左)语言支持
从右到左的布局
// 创建 RTL 布局
Composite rtlComposite = new Composite(shell, SWT.RIGHT_TO_LEFT);
rtlComposite.setLayout(new GridLayout(2, false));
// 标签
Label nameLabel = new Label(rtlComposite, SWT.NONE);
nameLabel.setText("姓名:");
Text nameText = new Text(rtlComposite, SWT.BORDER);
// 邮箱
Label emailLabel = new Label(rtlComposite, SWT.NONE);
emailLabel.setText("邮箱:");
Text emailText = new Text(rtlComposite, SWT.BORDER);
费曼解释:RTL 布局的作用
RTL = 从右到左
- 英语(LTR):Hello, World!(从左到右)
- 阿拉伯语(RTL):مرحبا بالعالم!(从右到左)
- 希伯来语(RTL):שלום עולם!(从右到左)
类比:
RTL = 反向阅读
- 英语(LTR):从左到右读
- 阿拉伯语(RTL):从右到左读
- 就像有人从右边往左边走
支持阿拉伯语、希伯来语
// 检测是否是 RTL 语言
Locale locale = Locale.getDefault();
boolean isRTL = locale.getLanguage().equals("ar") || locale.getLanguage().equals("he");
if (isRTL) {
// 创建 RTL 布局
Composite rtlComposite = new Composite(shell, SWT.RIGHT_TO_LEFT);
// 添加控件...
} else {
// 创建 LTR 布局
Composite ltrComposite = new Composite(shell, SWT.NONE);
// 添加控件...
}
费曼解释:如何检测 RTL 语言?
检测 RTL 语言 = 检查语言代码
- 阿拉伯语:ar(RTL)
- 希伯来语:he(RTL)
- 其他语言:默认为 LTR
类比:
检测 RTL 语言 = 检查书写方向
- 英语:从左到右写
- 阿拉伯语:从右到左写
- 检查语言代码,知道书写方向
代码示例:RTL 语言支持
public class RTLLanguageExample {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("RTL 语言支持");
shell.setLayout(new GridLayout(1, false));
// 检测系统语言
Locale systemLocale = Locale.getDefault();
boolean isRTL = systemLocale.getLanguage().equals("ar") ||
systemLocale.getLanguage().equals("he");
Label languageLabel = new Label(shell, SWT.WRAP | SWT.CENTER);
languageLabel.setText("系统语言:" + systemLocale.getDisplayLanguage() +
"\n是否是 RTL:" + (isRTL ? "是" : "否"));
GridData languageData = new GridData();
languageData.horizontalAlignment = GridData.FILL;
languageData.grabExcessHorizontalSpace = true;
languageLabel.setLayoutData(languageData);
// LTR 布局
Group ltrGroup = new Group(shell, SWT.NONE);
ltrGroup.setText("LTR 布局(英语)");
ltrGroup.setLayout(new GridLayout(2, false));
ltrGroup.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
Label ltrNameLabel = new Label(ltrGroup, SWT.NONE);
ltrNameLabel.setText("姓名:");
Text ltrNameText = new Text(ltrGroup, SWT.BORDER);
ltrNameText.setText("Hello, World!");
Label ltrEmailLabel = new Label(ltrGroup, SWT.NONE);
ltrEmailLabel.setText("邮箱:");
Text ltrEmailText = new Text(ltrGroup, SWT.BORDER);
ltrEmailText.setText("test@example.com");
// RTL 布局
Group rtlGroup = new Group(shell, SWT.RIGHT_TO_LEFT);
rtlGroup.setText("RTL 布局(阿拉伯语)");
rtlGroup.setLayout(new GridLayout(2, false));
rtlGroup.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
Label rtlNameLabel = new Label(rtlGroup, SWT.NONE);
rtlNameLabel.setText("الاسم:");
Text rtlNameText = new Text(rtlGroup, SWT.BORDER);
rtlNameText.setText("مرحبا بالعالم!");
Label rtlEmailLabel = new Label(rtlGroup, SWT.NONE);
rtlEmailLabel.setText("البريد الإلكتروني:");
Text rtlEmailText = new Text(rtlGroup, SWT.BORDER);
rtlEmailText.setText("test@example.com");
// 按钮:切换到 RTL 布局
Button rtlButton = new Button(shell, SWT.PUSH);
rtlButton.setText("切换到 RTL");
rtlButton.addListener(SWT.Selection, event -> {
Group group = new Group(shell, SWT.RIGHT_TO_LEFT);
group.setText("新 RTL 布局");
group.setLayout(new GridLayout(2, false));
Label nameLabel = new Label(group, SWT.NONE);
nameLabel.setText("الاسم:");
Text nameText = new Text(group, SWT.BORDER);
nameText.setText("مرحبا بالعالم!");
group.pack();
group.setVisible(true);
});
shell.setBounds(100, 100, 500, 400);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
14.5 本章小结
国际化总结
| 概念 | 作用 | 类比 |
|---|---|---|
| ResourceBundle | 加载多语言资源 | 多语言书库 |
| 属性文件 | 存储多语言文本 | 多语言菜单 |
| 占位符 | 替换动态内容 | 填空题 |
| RTL | 从右到左布局 | 反向阅读 |
| MessageFormat | 格式化消息 | 邮件模板 |
费曼测试:你能解释清楚吗?
- 为什么需要国际化?
- 不同语言的用户看到不同语言的内容
- ResourceBundle 的加载机制?
- 根据 Locale 加载对应语言的资源文件
- 占位符的作用?
- 替换动态内容,如用户名、日期等
- RTL 布局的含义?
- 从右到左的布局,支持阿拉伯语、希伯来语等
下一章预告
现在你已经掌握了国际化,
可以支持多种语言,
服务全球用户。
下一章,我们将学习主题与样式,
让应用支持 Dark Mode,
提升用户体验。
练习题:
- 创建一个应用,支持英语和中文,通过 ResourceBundle 加载资源。
- 创建一个表单,包含姓名、邮箱,支持多语言。
- 创建一个应用,支持阿拉伯语,使用 RTL 布局。
(提示:使用 ResourceBundle.getBundle("messages") 加载资源,使用 SWT.RIGHTTOLEFT 创建 RTL 布局)