移动端国际化:多语言、RTL 与地区格式

FreeGuideOnline 最新 2026-06-17

移动端国际化:多语言、RTL 与地区格式

国际化(i18n)使你的应用能适应不同语言和地区的用户。移动端国际化需要处理三个核心领域:多语言文本、从右到左(RTL)布局和地区敏感的数据格式。本教程将指引你系统地完成这三项工作,并以 Flutter 和 Android(Kotlin)为例展示关键实现,概念可平移到 SwiftUI 或 React Native。


1. 多语言支持

多语言支持的目标是根据用户的系统语言设置,动态展示对应的翻译文本。关键步骤包括:定义语言资源、加载资源、在 UI 中引用、提供切换机制。

1.1 字符串资源管理

将用户可见的字符串从代码中抽离,放入专用的资源文件。

  • Flutter (ARB 格式)lib/l10n 下创建 app_en.arbapp_es.arb 等文件。每个文件是一个 JSON:

    // app_en.arb
    {
      "appTitle": "My App",
      "@appTitle": { "description": "The application title" },
      "greeting": "Hello, {name}!",
      "@greeting": { "placeholders": { "name": { "type": "String" } } }
    }
    

    pubspec.yaml 启用代码生成:

    flutter:
      generate: true
    

    并使用 flutter_localizations 包。运行 flutter gen-l10n 后,会生成一个 AppLocalizations 类。

  • Android (XML 资源)res/values/strings.xml 存放默认(英文)字符串,在 res/values-es/strings.xml 存放西班牙语。使用占位符:

    <!-- values/strings.xml -->
    <string name="greeting">Hello, %1$s!</string>
    

    Android 会自动根据系统区域加载对应的资源。

1.2 在 UI 中使用本地化字符串

避免硬编码字符串,始终通过本地化对象引用。

  • Flutter

    Text(AppLocalizations.of(context)!.greeting('Alice'))
    
  • Android (View)

    <TextView android:text="@string/greeting" />
    

    在代码中:getString(R.string.greeting, "Alice")

  • Android (Compose)

    Text(text = stringResource(R.string.greeting, "Alice"))
    

1.3 动态切换语言

有时用户希望在应用内切换语言,而非仅跟随系统。

  • Flutter
    使用 Localizations.override 包裹整个 MaterialApp,并管理一个 Locale 状态。核心代码示意:

    MaterialApp(
      locale: _currentLocale,
      localizationsDelegates: AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      home: MyHomePage(),
    )
    

    更改 _currentLocale 并重建即可。

  • Android
    需谨慎处理,因为 Android 倾向于跟随系统。可在 Activity 的 attachBaseContext 中设置自定义的 ContextWrapper,覆盖 getResources() 的配置。或使用 AppCompatDelegate.setApplicationLocales()(Android 13+)。推荐使用 AppLocalesManager 等现代 API,注意保存用户偏好到 SharedPreferences 并在启动时应用。


2. 适配从右到左(RTL)布局

阿拉伯语、希伯来语等语言的阅读方向为从右到左。应用布局需要镜像反转。

2.1 启用 RTL 支持

  • Flutter
    MaterialApp 默认会根据当前 Locale 自动决定文本方向。可通过 directionality 属性强制指定,但建议依赖框架。检查是否为 RTL:

    final isRtl = Directionality.of(context) == TextDirection.rtl;
    
  • Android
    在清单文件中,为 <application> 添加 android:supportsRtl="true"。这样,系统会镜像默认的布局流向。UI 组件中的 start/end 属性代替 left/right

2.2 布局中的镜像技巧

原则:使用语义化的对齐方式,而非硬编码方向。

场景 错误做法 正确做法
水平内边距 paddingLeft: 16 paddingStart: 16 (Flutter) 或 android:paddingStart
文本对齐 textAlign: left textAlign: TextAlign.start
图标方向(如箭头) 直接使用图标 在 RTL 下旋转或变换,如 Transform.flipmirror 属性
  • Flutter 示例

    Padding(
      padding: EdgeInsetsDirectional.only(start: 16.0),
      child: Text(
        'Hello',
        textAlign: TextAlign.start,
      ),
    )
    

    对于需要翻转的图标(如返回箭头),可使用 Transform.scale(scaleX: -1, child: Icon(Icons.arrow_back)) 并配合 Directionality 条件判断。

  • Android XML 示例

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingStart="16dp"
        android:paddingEnd="16dp">
        <TextView
            android:text="@string/hello"
            android:textAlignment="viewStart" />
    </LinearLayout>
    

2.3 自定义组件与手势

  • 滑动手势(如 Dismissible)的方向应使用 startToEnd 代替 leftToRight,以便在 RTL 时自动交换。
  • 动画和位移也应使用逻辑方向 (Offset 基于轴),或者在创建 Animation 时考虑 Directionality
  • 列表中的项目顺序通常不需要手动反转,因为框架的布局系统会自动处理行和列的起始位置。但如果你自己维护了索引相关的逻辑(如 index == 0 为第一个),需要确认业务逻辑是否依赖视觉顺序。多数情况下无需修改。

3. 地区格式:日期、时间、数字、货币

同一个语言不同地区(如英语美国 vs. 英语英国)对日期、数字格式的要求不同。需要根据完整的区域标识符(语言+地区)进行格式化。

3.1 使用 Intl 包(Flutter 平台)

intl 是 Dart 官方的国际化库。

  1. 添加依赖:intl: ^0.18.1
  2. 初始化时指定 locale:
    import 'package:intl/intl.dart';
    final currentLocale = Localizations.localeOf(context);
    final formatter = DateFormat.yMd(currentLocale.toString());
    String formatted = formatter.format(DateTime.now());
    // 对于数字
    final numberFormat = NumberFormat.decimalPattern(currentLocale.toString());
    String formattedNumber = numberFormat.format(1234567.89);
    
  3. 消息的复杂插值也在 ARB 文件中结合 intl 实现。

3.2 Android 平台格式工具

  • 日期与时间android.icu.text.DateFormat(API 24+)或 java.text.SimpleDateFormat(兼容旧版,但注意线程安全)。推荐使用 java.time.format.DateTimeFormatter(需要 API 26+ 或使用 desugar)。
    val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
    val formatted = LocalDate.now().format(formatter)
    
  • 数字NumberFormat.getNumberInstance(locale).format(1234567.89)
  • 货币NumberFormat.getCurrencyInstance(locale).format(99.99)
  • 相对时间:使用 RelativeDateTimeFormatter(Android ICU)实现“5分钟前”等。

3.3 通用最佳实践

  • 始终将完整区域标识符传给格式化器,而不是仅语言代码。例如使用 ar_SA 而不只是 ar,因为沙特阿拉伯与阿联酋的数字格式可能不同。
  • 避免自行拼接字符串,比如 "$" + amount。请用货币格式化器处理符号位置。
  • 测试边界案例:不同地区的小数分隔符(点 vs 逗号)、千位分隔符、日期字段顺序(月/日/年 vs 日/月/年)、12/24 小时制。
  • 展示数字和日期时,尊重用户偏好:系统设置中若用户选择了 24 小时制或特定数字系统,应用应继承这些偏好。Android 的 DateUtilsNumberFormat 默认会这样做;Flutter 中需显式传递 locale 或使用 MaterialLocalizations 提供的辅助方法。

4. 测试与验证

在开发过程中可以通过以下方式快速验证国际化效果:

  • Flutter:在 MaterialApp 中临时指定 locale: Locale('ar')supportedLocales,观察布局。
  • Android:使用模拟器或真机,在设置中添加阿拉伯语等 RTL 语言,并设为默认。也可使用开发者选项中的“强制使用 RTL 布局方向”。
  • 预览多种地区格式:准备单元测试,针对不同 Locale 调用格式化逻辑,断言输出是否符合预期(例如 en_US 的日期应为 MM/DD/YYYY 格式)。

总结

移动端国际化是一个贯穿开发全程的过程。遵循以下路径可避免大多数坑:

  1. 将所有字符串外部化,使用成熟的资源管理机制。
  2. 布局使用逻辑方向(start/end),禁止硬编码左右值。
  3. 日期、数字等格式严格通过系统 API 处理,并传入完整的区域标识符。

当你养成良好的 i18n 习惯后,新增语言或地区的成本将降至极低,你的应用也将真正触达全球用户。