20、Launcher的国际化(i18n):字符串资源管理、RTL布局适配、区域设置对排序的影响

国际化,简称 i18n。说白了就是让你的 Launcher 能说「各国语言」,能适应「不同书写习惯」。比如阿拉伯语从右往左读,中文从左往右读。

我刚开始做 Launcher 时,觉得国际化不就是翻译字符串吗?后来发现,坑多着呢。尤其是 RTL 布局,稍不注意界面就乱成一锅粥。今天咱们就聊聊这三个核心点:字符串资源管理、RTL 布局适配、区域设置对排序的影响。

20.1 字符串资源管理

这是国际化的基本功。Android 系统通过资源文件夹的「限定符」来区分不同语言。

20.1.1 资源文件夹命名规则

你需要在 res/ 目录下创建带语言限定符的文件夹。比如:

  • res/values/strings.xml —— 默认语言(通常是英语)
  • res/values-zh/strings.xml —— 简体中文
  • res/values-ar/strings.xml —— 阿拉伯语
  • res/values-ja/strings.xml —— 日语

系统会根据用户的语言设置,自动加载对应的 strings.xml

核心原则:永远不要硬编码字符串。所有用户可见的文字,都必须放在 strings.xml 中。

20.1.2 字符串资源的最佳实践

我个人习惯把字符串按模块分组。比如 Launcher 的字符串可以分成:

  • 通用:app_name、search_hint
  • 桌面:add_widget、remove_shortcut
  • 抽屉:all_apps、recent_apps
  • 设置:settings_title、grid_columns

举个例子,res/values/strings.xml 的内容:

<resources>
    <string name="app_name">Launcher</string>
    <string name="search_hint">Search apps</string>
    <string name="add_widget">Add widget</string>
    <string name="all_apps">All apps</string>
</resources>

对应的中文版本 res/values-zh/strings.xml

<resources>
    <string name="app_name">桌面启动器</string>
    <string name="search_hint">搜索应用</string>
    <string name="add_widget">添加小部件</string>
    <string name="all_apps">所有应用</string>
</resources>

小技巧:字符串中如果有占位符,记得用 %1$s%2$d 这种格式。不同语言的语序可能不同,占位符能帮你灵活调整。

20.1.3 字符串的复数形式

有些语言有单复数变化。比如英语中 "1 app" 和 "2 apps" 不同。Android 提供了 <plurals> 标签:

<plurals name="app_count">
    <item quantity="one">%d app</item>
    <item quantity="other">%d apps</item>
</plurals>

在代码中这样使用:

int count = 5;
String text = resources.getQuantityString(R.plurals.app_count, count, count);

嗯,这里要注意:中文没有复数变化,所以中文的 plurals 只需要一个 other 条目就行。

20.2 RTL布局适配

RTL 是 Right-to-Left 的缩写。阿拉伯语、希伯来语都是从右往左写的。如果你的 Launcher 不支持 RTL,那在这些语言下界面会完全颠倒。

20.2.1 启用 RTL 支持

首先,在 AndroidManifest.xml 中声明:

<application
    android:supportsRtl="true">
    ...
</application>

这一步是必须的。否则系统不会自动镜像你的布局。

20.2.2 使用 start/end 代替 left/right

这是 RTL 适配的核心。在布局文件中,用 layout_marginStart 代替 layout_marginLeft,用 paddingEnd 代替 paddingRight

举个例子:

<!-- 错误写法 -->
<TextView
    android:layout_marginLeft="16dp"
    android:paddingRight="8dp" />

<!-- 正确写法 -->
<TextView
    android:layout_marginStart="16dp"
    android:paddingEnd="8dp" />

为什么?因为 left 在 RTL 下仍然是左边,但 start 在 RTL 下会变成右边。系统会自动镜像。

我曾经踩过的坑:在 Launcher 的拖拽逻辑中,我用了 getLeft()getRight() 来计算位置。结果在 RTL 模式下,拖拽方向完全反了。后来全部改成 getStart()getEnd() 才解决。

20.2.3 布局镜像

有些布局需要整体镜像。比如 Launcher 的底部导航栏,在 LTR 下从左到右排列,在 RTL 下应该从右到左排列。

你可以用 android:layoutDirection="locale" 让布局跟随语言方向:

<LinearLayout
    android:layoutDirection="locale"
    android:orientation="horizontal">
    ...
</LinearLayout>

或者,在代码中动态判断:

int layoutDirection = ViewCompat.getLayoutDirection(view);
if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) {
    // RTL 下的特殊处理
} else {
    // LTR 下的处理
}

20.2.4 图标和图片的镜像

有些图标在 RTL 下需要镜像。比如返回箭头,在 LTR 下指向左边,在 RTL 下应该指向右边。

你可以在 res/ 下创建 drawable-ldrtl/ 文件夹,放入镜像后的图标。系统会自动加载。

或者,用 autoMirrored="true" 属性:

<vector
    android:autoMirrored="true"
    ... />

这样系统会自动镜像矢量图,省去你手动准备两份资源的麻烦。

20.3 区域设置对排序的影响

这个点容易被忽略。不同语言对「排序」的理解完全不同。

20.3.1 字母排序的差异

比如英语中 "apple" 排在 "banana" 前面。但在德语中,"ä" 可能排在 "a" 后面,也可能排在 "z" 后面,取决于区域设置。

中文更复杂。是按拼音排序?还是按笔画排序?不同区域设置下结果不同。

我建议使用 Collator 类来进行排序:

import java.text.Collator;
import java.util.Locale;

Collator collator = Collator.getInstance(Locale.CHINA);
List<String> appNames = new ArrayList<>();
// 添加应用名称...
Collections.sort(appNames, collator);

Collator 会根据指定的 Locale 自动处理排序规则。比直接用 String.compareTo() 靠谱得多。

20.3.2 Launcher 中的应用排序

在 Launcher 中,应用列表的排序直接影响用户体验。我一般这样处理:

  1. 获取当前系统的 Locale
  2. 根据 Locale 创建对应的 Collator
  3. 用 Collator 对应用名称进行排序
  4. 如果应用名称相同,再用包名排序作为 fallback

代码示例:

Locale currentLocale = getResources().getConfiguration().getLocales().get(0);
Collator collator = Collator.getInstance(currentLocale);

Collections.sort(appList, (app1, app2) -> {
    int result = collator.compare(app1.getLabel(), app2.getLabel());
    if (result == 0) {
        return app1.getPackageName().compareTo(app2.getPackageName());
    }
    return result;
});

注意:不要用 Locale.getDefault()。在 Android 上,getResources().getConfiguration().getLocales() 才是用户当前设置的语言列表。

20.3.3 数字和特殊字符的处理

不同语言对数字和特殊字符的排序位置也不同。比如英语中数字排在字母前面,但有些语言中数字排在字母后面。

你想想看,如果用户安装了上百个应用,排序规则不对,找个应用得翻半天。所以一定要用 Collator,别自己写排序逻辑。

20.4 知识体系总览

下面这张图总结了 Launcher 国际化的核心知识点:

Launcher 国际化(i18n)知识体系 字符串资源管理 • 资源文件夹命名规则 • values/values-zh/values-ar • 字符串按模块分组 • 占位符使用 %1$s • 复数形式 <plurals> • 避免硬编码字符串 RTL 布局适配 • 声明 supportsRtl="true" • start/end 代替 left/right • layoutDirection="locale" • 代码中判断 RTL 方向 • 图标 autoMirrored • drawable-ldrtl 文件夹 区域设置与排序 • 不同语言排序规则 • 使用 Collator 类 • 根据 Locale 创建 Collator • 应用列表排序实现 • 数字/特殊字符处理 • 避免用 compareTo() 核心目标:让 Launcher 在全球用户面前表现一致、体验良好

20.5 总结

国际化不是简单的翻译。它涉及资源管理、布局适配、排序逻辑等多个方面。

我个人经验是:从项目一开始就把国际化考虑进去。不要等到所有功能做完了再回头适配 RTL,那会改到你怀疑人生。

最后送大家一句话:国际化做得好,用户遍布全球;国际化做得差,Bug 遍布全球。


公众号:蓝海资料掘金营,微信deep3321