27、国际化与本地化:多语言支持、区域设置、输入法切换、时区管理
国际化与本地化,说白了就是让你的车机系统能“入乡随俗”。
我最早接触这块是在一个出口项目上,客户要求同时支持阿拉伯语和英语。当时我以为只是翻译字符串那么简单,结果一测,界面全崩了——文字从右往左显示,布局乱成一锅粥。嗯,从那以后我再也不敢小看 i18n 了。
27.1 多语言支持:不只是翻译字符串
Android Automotive OS 的多语言支持,核心是 resources 机制。你只需要在 res/values-xx 目录下放对应的 strings.xml,系统会根据用户的语言偏好自动加载。
但车机有个特殊点:很多文字是显示在仪表盘或 HUD 上的。这些屏幕通常资源有限,字符串长度一旦超了,就会截断或重叠。我在项目中遇到过,德语单词特别长,一个“高速公路出口”能写满半个屏幕。后来我们强制给每个翻译加了字符数上限,并在 CI 里加了 lint 检查。
res/
values/strings.xml # 默认语言(通常英语)
values-zh/strings.xml # 简体中文
values-ar/strings.xml # 阿拉伯语(注意 RTL)
values-de/strings.xml # 德语
values-ja/strings.xml # 日语
strings.xml 里给每个字符串加 translatable="false" 标记那些不需要翻译的(比如品牌名、协议常量)。这样翻译团队不会误改。
27.2 区域设置:数字、日期、货币的“潜规则”
你以为改了语言就完事了?区域设置(Locale)才是真正的坑。同样是中文,中国大陆用“2025年4月1日”,台湾用“2025年4月1日”但数字格式不同,香港又混用英文月份。车机上的导航 ETA、能耗统计、充电费用,全跟这个挂钩。
Android 提供了 java.time.format.DateTimeFormatter 和 java.text.NumberFormat,但车机场景下我建议直接用 android.icu.text 包。为什么?因为 ICU 库对区域的支持更全,比如阿拉伯语数字、泰语数字都能正确处理。
// 获取当前区域
Locale currentLocale = getResources().getConfiguration().getLocales().get(0);
// 日期格式化
DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)
.withLocale(currentLocale);
String formattedDate = dateFormatter.format(LocalDate.now());
// 数字格式化(比如电量百分比)
NumberFormat percentFormat = NumberFormat.getPercentInstance(currentLocale);
percentFormat.setMinimumFractionDigits(1);
String batteryText = percentFormat.format(0.85); // 输出 "85.0%" 或 "٨٥٫٠٪"
String.format("%.2f", value) 会输出阿拉伯数字(٠٫١٢٣),但某些老款仪表盘只认拉丁数字。解决方案是强制指定 Locale.US 做格式化,再单独处理 RTL 排版。
27.3 输入法切换:车机上的“多语种键盘”
车机输入法和手机完全不同。驾驶员在行驶中不可能盯着屏幕选键盘,所以输入法切换必须做到“盲操”且“快速”。
Android Automotive OS 默认支持 InputMethodManager 切换,但车厂通常会有定制需求。比如,有些车在导航搜索时自动切换到拼音输入法,在设置 Wi-Fi 密码时切换到英文键盘。
我参与的一个项目里,客户要求方向盘上的“语音键”长按能切换输入法。实现起来其实不复杂:
// 获取输入法管理器
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// 列出所有已启用的输入法
List<InputMethodInfo> enabledImes = imm.getEnabledInputMethodList();
// 切换到下一个输入法(循环)
int currentIndex = ...; // 记录当前输入法索引
int nextIndex = (currentIndex + 1) % enabledImes.size();
imm.switchToNextInputMethod(getWindow().getDecorView().getWindowToken(), false);
onCreate 里直接切换输入法,那时窗口还没绑定。我曾经这么干过,结果系统直接崩溃。正确的做法是在 onWindowFocusChanged 回调里做。
27.4 时区管理:车是移动的,时间也是
车机跟手机最大的不同是:车会跨时区。你从北京开到乌鲁木齐,中间经过好几个时区。如果车机时间不自动更新,导航 ETA、充电预约、甚至闹钟全都会乱套。
Android Automotive OS 提供了 TimeManager 服务,但底层依赖 TelephonyManager 或 LocationManager 获取时区。如果车辆没有蜂窝网络(比如纯电车离线状态),就需要靠 GNSS 信号来推算时区。
// 监听时区变化
BroadcastReceiver timeZoneReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String timeZone = intent.getStringExtra("time-zone");
// 更新系统时区
AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
alarmManager.setTimeZone(timeZone);
}
};
registerReceiver(timeZoneReceiver, new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED));
27.5 知识体系总览
下面这张图概括了国际化与本地化的核心模块和它们之间的依赖关系。你可以把它当作一个检查清单,看看自己的项目覆盖了哪些。
27.6 实战建议:从项目第一天就规划 i18n
很多团队在原型阶段只用英文,等到要出口了才回头加国际化。结果呢?代码里到处是硬编码的字符串,布局写死了宽度,时间格式化散落在各个 Activity 里。改起来比重新写还痛苦。
我个人的经验是:
- 字符串全部抽到 strings.xml,包括 Toast、Dialog、甚至 Log 里的提示。这样翻译团队可以直接拿文件去翻,不用碰代码。
- 布局用 wrap_content 和 match_parent,不要写死 dp。尤其是阿拉伯语,文字长度可能膨胀 30%。
- 时间处理统一用 UTC 存储,只在显示时转成当地时区。否则跨时区数据会乱。
- 测试要覆盖 RTL 和长文本。我见过一个导航界面,德语翻译后按钮文字变成了两行,直接把“开始导航”按钮挤出了屏幕。