欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net

在这里插入图片描述

📋 前言

国际化(i18n)和本地化(l10n)是移动应用走向全球市场的关键步骤。无论是多语言支持、货币格式化、日期时间显示,还是度量单位转换,都需要获取用户设备的本地化设置。react-native-localize 是一个强大的本地化工具库,提供了丰富的 API 来获取设备的语言、地区、货币、时区等本地化信息,帮助开发者轻松实现应用的国际化适配。

🎯 库简介

基本信息

  • 库名称: react-native-localize
  • 版本信息:
    • 3.1.0 + @react-native-ohos/react-native-localize: 支持 RN 0.72 版本
    • 3.4.2 + @react-native-ohos/react-native-localize: 支持 RN 0.77 版本
    • 3.6.2 + @react-native-ohos/react-native-localize: 支持 RN 0.82 版本
  • 官方仓库: https://github.com/zoontek/react-native-localize
  • 鸿蒙仓库: https://atomgit.com/openharmony-sig/rntpc_react-native-localize
  • 主要功能:
    • 🌐 获取用户首选语言列表
    • 💱 获取用户首选货币代码
    • 📅 获取日历格式和时区
    • 🔢 获取数字格式设置
    • 🕐 判断是否使用24小时制

为什么需要本地化库?

特性 手动获取 react-native-localize
语言列表 ⚠️ 需调用原生API ✅ 统一接口获取
货币代码 ❌ 需自行推断 ✅ 直接获取用户偏好
时区信息 ⚠️ 需原生模块 ✅ 跨平台一致API
数字格式 ❌ 需手动处理 ✅ 获取格式设置
最佳语言匹配 ❌ 需自行实现 ✅ 内置匹配算法
HarmonyOS 支持 ❌ 无 ✅ 完善适配

核心功能

功能 说明 HarmonyOS 支持
getLocales() 获取用户首选语言列表
getCurrencies() 获取用户首选货币代码
getCountry() 获取用户所在国家代码
getCalendar() 获取日历格式
getTimeZone() 获取时区信息
uses24HourClock() 判断是否使用24小时制
getNumberFormatSettings() 获取数字格式设置
findBestLanguageTag() 查找最佳匹配语言
getTemperatureUnit() 获取温度单位
usesMetricSystem() 判断是否使用公制

兼容性验证

在以下环境验证通过:

  • RNOH: 0.72.90; SDK: HarmonyOS 6.0.0 (API Version 20); IDE: DevEco Studio 6.0.2; ROM: HarmonyOS 6.0.0

📦 安装步骤

1. 安装依赖

请到三方库的 Releases 发布地址查看配套的版本信息:

三方库版本 发布信息 支持 RN 版本
3.1.0 @react-native-ohos/react-native-localize 0.72
3.4.2 @react-native-ohos/react-native-localize 0.77
3.6.2 @react-native-ohos/react-native-localize 0.82

在这里插入图片描述

# RN 0.72 版本
npm install @react-native-ohos/react-native-localize@3.1.1-rc.1

# RN 0.77 版本
npm install @react-native-ohos/react-native-localize@3.4.2

# RN 0.82 版本
npm install @react-native-ohos/react-native-localize@3.6.2

# 或者使用 yarn
yarn add @react-native-ohos/react-native-localize

2. 验证安装

安装完成后,检查 package.json 文件:

{
  "dependencies": {
    "@react-native-ohos/react-native-localize": "^3.1.1-rc.1"
  }
}

🔧 HarmonyOS 平台配置 ⭐

1. 在工程根目录的 oh-package.json5 添加 overrides 字段(看自己的版本是什么,在package.json中)

打开 harmony/oh-package.json5,添加以下配置:
在这里插入图片描述

{
  // ... 其他配置
  "overrides": {
    "@rnoh/react-native-openharmony": "0.72.90"
  }
}

2. 引入原生端代码

方式一:通过 HAR 包引入(推荐)

💡 提示:HAR 包位于三方库安装路径的 harmony 文件夹下。

打开 harmony/entry/oh-package.json5,添加以下依赖:

"dependencies": {
  "@react-native-ohos/react-native-localize": "file:../../node_modules/@react-native-ohos/react-native-localize/harmony/rn_localize.har"
}

点击右上角的 sync 按钮,或者在终端执行:

cd harmony/entry
ohpm install

3. 配置 CMakeLists 和引入 RNLocalizePackage(仅 0.77 需要)

打开 harmony/entry/src/main/cpp/CMakeLists.txt,添加:

project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")

add_subdirectory("${RNOH_CPP_DIR}" ./rn)

# 添加 RNLocalize 模块
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-localize/src/main/cpp" ./rn_localize)

add_library(rnoh_app SHARED
    "./PackageProvider.cpp"
    "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)

target_link_libraries(rnoh_app PUBLIC rnoh)

# 链接 RNLocalize 库
+ target_link_libraries(rnoh_app PUBLIC rnoh_localize)

打开 harmony/entry/src/main/cpp/PackageProvider.cpp,添加:

#include "RNOH/PackageProvider.h"
+ #include "RNLocalizePackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
        + std::make_shared<RNLocalizePackage>(ctx),
    };
}

4. 在 ArkTs 侧引入 RNLocalizePackage

打开 harmony/entry/src/main/ets/RNPackagesFactory.ts,添加:

import type { RNPackageContext, RNPackage } from 'rnoh/ts';
+ import { RNLocalizePackage } from '@react-native-ohos/react-native-localize/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    + new RNLocalizePackage(ctx),
  ];
}

🚀 同步并运行

点击 DevEco Studio 右上角的 sync 按钮,或者在终端执行:

cd harmony/entry
ohpm install

然后编译、运行即可。

📖 API 详解

getLocales() - 获取用户首选语言列表

返回用户首选的语言环境列表,按优先级排列。这是实现应用多语言支持的核心 API,通过获取用户的语言偏好来决定应用显示的语言版本。

返回值类型Locale[]

Locale 对象结构

属性 类型 说明 示例值
languageCode string ISO 639-1 语言代码 “zh”, “en”, “ja”
countryCode string ISO 3166-1 国家代码 “CN”, “US”, “JP”
languageTag string 完整的 BCP 47 语言标签 “zh-Hans-CN”, “en-US”
isRTL boolean 是否为从右到左的语言 false, true

使用场景

  • 应用启动时确定默认语言
  • 多语言内容切换
  • 判断是否需要 RTL 布局支持
import * as RNLocalize from 'react-native-localize';

const locales = RNLocalize.getLocales();
// 返回示例:
// [
//   { languageCode: "zh", countryCode: "CN", languageTag: "zh-Hans-CN", isRTL: false },
//   { languageCode: "en", countryCode: "US", languageTag: "en-US", isRTL: false }
// ]

// 实际应用:根据用户语言偏好加载对应的翻译文件
const primaryLocale = locales[0];
const languageCode = primaryLocale.languageCode; // "zh"

// 判断是否需要 RTL 布局
if (primaryLocale.isRTL) {
  // 应用 RTL 样式,如 flexDirection: 'row-reverse'
}

getCurrencies() - 获取用户首选货币代码

返回用户设备设置的首选货币代码列表,按优先级排列。这对于电商、金融类应用尤为重要,可以自动显示用户熟悉的货币符号。

返回值类型string[]

返回值说明:返回 ISO 4217 货币代码数组,如 [“CNY”, “USD”]

使用场景

  • 电商应用自动显示本地货币价格
  • 金融应用默认货币选择
  • 价格格式化显示
const currencies = RNLocalize.getCurrencies();
// 返回示例:["CNY", "USD"]

// 实际应用:根据货币代码格式化价格
const currencySymbols: Record<string, string> = {
  CNY: '¥',
  USD: '$',
  EUR: '€',
  JPY: '¥',
};

const formatPrice = (amount: number, currency: string) => {
  const symbol = currencySymbols[currency] || currency;
  return `${symbol}${amount.toFixed(2)}`;
};

const userCurrency = currencies[0]; // "CNY"
const displayPrice = formatPrice(99.99, userCurrency); // "¥99.99"

getCountry() - 获取用户所在国家代码

返回用户设备设置的国家/地区代码,用于判断用户的地理位置偏好。

返回值类型string

返回值说明:返回 ISO 3166-1 alpha-2 国家代码,如 “CN”、“US”、“JP”

使用场景

  • 内容地区限制判断
  • 默认配送地址设置
  • 地区特定功能开关
const country = RNLocalize.getCountry();
// 返回示例:"CN"

// 实际应用:根据国家显示不同内容
const getAvailableFeatures = (countryCode: string) => {
  const featuresByCountry: Record<string, string[]> = {
    CN: ['wechat-pay', 'alipay', 'unionpay'],
    US: ['apple-pay', 'google-pay', 'paypal'],
    JP: ['line-pay', 'paypay'],
  };
  return featuresByCountry[countryCode] || ['credit-card'];
};

const availablePaymentMethods = getAvailableFeatures(country);

getCalendar() - 获取日历格式

返回用户设备设置的日历类型,用于日期相关的显示和计算。

返回值类型string

常见返回值

返回值 说明 主要使用地区
“gregorian” 公历(格里高利历) 全球通用
“chinese” 农历 中国
“japanese” 日本年号历 日本
“islamic” 伊斯兰历 中东地区

使用场景

  • 日历应用日期显示
  • 节假日判断
  • 日期选择器本地化
const calendar = RNLocalize.getCalendar();
// 返回示例:"gregorian"

// 实际应用:根据日历类型调整日期显示
const formatDateByCalendar = (date: Date, calendarType: string) => {
  switch (calendarType) {
    case 'chinese':
      // 返回农历日期(需要额外的农历转换库)
      return convertToLunarDate(date);
    case 'japanese':
      // 返回日本年号日期
      return formatJapaneseYear(date);
    default:
      return date.toLocaleDateString();
  }
};

getTimeZone() - 获取时区信息

返回用户设备设置的时区标识符,用于时间相关的计算和显示。

返回值类型string

返回值说明:返回 IANA 时区标识符,如 “Asia/Shanghai”、“America/New_York”

使用场景

  • 跨时区时间显示
  • 日程安排和提醒
  • 服务器时间同步
const timeZone = RNLocalize.getTimeZone();
// 返回示例:"Asia/Shanghai"

// 实际应用:将 UTC 时间转换为本地时间
const convertToLocalTime = (utcTime: Date, timeZone: string) => {
  const options: Intl.DateTimeFormatOptions = {
    timeZone,
    year: 'numeric',
    month: 'long',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
    timeZoneName: 'short',
  };
  return utcTime.toLocaleString('zh-CN', options);
};

const localTime = convertToLocalTime(new Date(), timeZone);
// 输出:"2024年1月15日 下午3:30 CST"

uses24HourClock() - 判断是否使用24小时制

判断用户设备是否使用 24 小时制时间格式,用于时间显示的本地化。

返回值类型boolean

返回值说明true 表示使用 24 小时制,false 表示使用 12 小时制(AM/PM)

使用场景

  • 时间选择器格式
  • 时间显示格式
  • 日程时间显示
const uses24Hour = RNLocalize.uses24HourClock();
// 返回示例:true

// 实际应用:根据用户偏好格式化时间
const formatTime = (hours: number, minutes: number, use24Hour: boolean) => {
  if (use24Hour) {
    return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
  } else {
    const period = hours >= 12 ? 'PM' : 'AM';
    const displayHours = hours % 12 || 12;
    return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`;
  }
};

const timeString = formatTime(15, 30, uses24Hour);
// 24小时制输出:"15:30"
// 12小时制输出:"3:30 PM"

getNumberFormatSettings() - 获取数字格式设置

返回用户设备的数字格式化设置,包括小数分隔符和千位分隔符。

返回值类型NumberFormatSettings

返回对象结构

属性 类型 说明 中国示例 美国示例
decimalSeparator string 小数分隔符 “.” “.”
groupingSeparator string 千位分隔符 “,” “,”

💡 提示:不同地区的数字格式差异较大,如欧洲部分地区使用逗号作为小数分隔符。

使用场景

  • 数字输入框格式化
  • 金额显示
  • 数据报表展示
const settings = RNLocalize.getNumberFormatSettings();
// 返回示例:{ decimalSeparator: ".", groupingSeparator: "," }

// 实际应用:根据本地设置格式化数字
const formatNumber = (num: number, settings: typeof settings) => {
  const parts = num.toFixed(2).split('.');
  const intPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, settings.groupingSeparator);
  return intPart + settings.decimalSeparator + parts[1];
};

// 中国/美国格式
formatNumber(1234567.89, { decimalSeparator: '.', groupingSeparator: ',' });
// 输出:"1,234,567.89"

// 欧洲部分国家格式
formatNumber(1234567.89, { decimalSeparator: ',', groupingSeparator: '.' });
// 输出:"1.234.567,89"

findBestLanguageTag() - 查找最佳匹配语言

从应用支持的语言列表中,找到与用户偏好最匹配的语言。这是实现应用多语言切换的关键 API。

参数

  • languageTags: string[] - 应用支持的语言标签数组

返回值类型{ languageTag: string; isRTL: boolean } | undefined

匹配规则

  1. 首先尝试完全匹配(如 “zh-Hans-CN”)
  2. 其次尝试语言+脚本匹配(如 “zh-Hans”)
  3. 最后尝试仅语言匹配(如 “zh”)

使用场景

  • 应用启动时选择最佳语言
  • 语言切换回退逻辑
  • 多语言内容加载
const bestMatch = RNLocalize.findBestLanguageTag(['en-US', 'en', 'fr', 'zh']);
// 返回示例:{ languageTag: "zh", isRTL: false }

// 实际应用:选择最佳匹配语言加载翻译
const supportedLanguages = ['en', 'zh', 'ja', 'ko', 'es', 'fr'];
const bestLanguage = RNLocalize.findBestLanguageTag(supportedLanguages);

if (bestLanguage) {
  // 加载匹配的语言包
  const translations = require(`./locales/${bestLanguage.languageTag}.json`);
  // 应用翻译
  i18n.setTranslations(translations);
} else {
  // 回退到默认语言
  const fallbackTranslations = require('./locales/en.json');
  i18n.setTranslations(fallbackTranslations);
}

useLocalize() - Hook 方式获取本地化信息

React Hook 方式获取本地化 API,适合在函数组件中使用。当本地化设置发生变化时,组件会自动重新渲染。

返回值:包含所有本地化方法的对象

使用场景

  • 函数组件中获取本地化信息
  • 响应本地化设置变化
  • 简化代码结构
import { useLocalize } from 'react-native-localize';

const MyComponent = () => {
  const { getLocales, getCurrencies, getCountry, getTimeZone } = useLocalize();
  
  // 每次调用都会获取最新值
  const locales = getLocales();
  const currencies = getCurrencies();
  const country = getCountry();
  const timeZone = getTimeZone();
  
  return (
    <View>
      <Text>语言: {locales[0]?.languageTag}</Text>
      <Text>货币: {currencies[0]}</Text>
      <Text>国家: {country}</Text>
      <Text>时区: {timeZone}</Text>
    </View>
  );
};

⚠️ 注意:与直接调用 API 不同,Hook 方式会在本地化设置变化时触发组件重新渲染。


📋 完整示例

在这里插入图片描述

import React, { useMemo } from "react";
import {
  SafeAreaView,
  ScrollView,
  StyleSheet,
  Text,
  View,
} from "react-native";
import * as RNLocalize from "react-native-localize";

const translations: Record<string, Record<string, string>> = {
  en: { welcome: "Welcome", language: "Language", currency: "Currency", timezone: "Timezone" },
  zh: { welcome: "欢迎", language: "语言", currency: "货币", timezone: "时区" },
  ja: { welcome: "ようこそ", language: "言語", currency: "通貨", timezone: "タイムゾーン" },
};

const App: React.FC = () => {
  const locales = RNLocalize.getLocales();
  const currencies = RNLocalize.getCurrencies();
  const country = RNLocalize.getCountry();
  const timeZone = RNLocalize.getTimeZone();
  const uses24Hour = RNLocalize.uses24HourClock();
  const numberSettings = RNLocalize.getNumberFormatSettings();

  const bestMatch = RNLocalize.findBestLanguageTag(["en", "zh", "ja"]);
  const currentLang = (bestMatch?.languageTag.split("-")[0] as keyof typeof translations) || "en";
  const t = translations[currentLang] || translations.en;

  const formatNumber = (num: number): string => {
    const parts = num.toFixed(2).split(".");
    const intPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, numberSettings.groupingSeparator);
    return intPart + numberSettings.decimalSeparator + parts[1];
  };

  return (
    <SafeAreaView style={styles.container}>
      <ScrollView contentContainerStyle={styles.content}>
        <Text style={styles.title}>{t.welcome}</Text>

        <View style={styles.card}>
          <Text style={styles.cardTitle}>本地化信息</Text>
          <View style={styles.row}>
            <Text style={styles.label}>{t.language}:</Text>
            <Text style={styles.value}>{locales[0]?.languageTag}</Text>
          </View>
          <View style={styles.row}>
            <Text style={styles.label}>{t.currency}:</Text>
            <Text style={styles.value}>{currencies[0]}</Text>
          </View>
          <View style={styles.row}>
            <Text style={styles.label}>{t.timezone}:</Text>
            <Text style={styles.value}>{timeZone}</Text>
          </View>
          <View style={styles.row}>
            <Text style={styles.label}>国家:</Text>
            <Text style={styles.value}>{country}</Text>
          </View>
          <View style={styles.row}>
            <Text style={styles.label}>24小时制:</Text>
            <Text style={styles.value}>{uses24Hour ? "是" : "否"}</Text>
          </View>
        </View>

        <View style={styles.card}>
          <Text style={styles.cardTitle}>数字格式化示例</Text>
          <Text style={styles.formatText}>1234.56{formatNumber(1234.56)}</Text>
          <Text style={styles.formatText}>9876543.21{formatNumber(9876543.21)}</Text>
        </View>

        <View style={styles.card}>
          <Text style={styles.cardTitle}>支持的语言列表</Text>
          {locales.map((locale, index) => (
            <View key={index} style={styles.localeRow}>
              <Text style={styles.localeText}>{locale.languageTag}</Text>
              {locale.isRTL && <Text style={styles.rtlBadge}>RTL</Text>}
            </View>
          ))}
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "#F5F5F5" },
  content: { padding: 16 },
  title: { fontSize: 24, fontWeight: "bold", marginBottom: 20, color: "#333", textAlign: "center" },
  card: { backgroundColor: "#FFF", borderRadius: 12, padding: 16, marginBottom: 16 },
  cardTitle: { fontSize: 16, fontWeight: "600", marginBottom: 12, color: "#333" },
  row: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 8, borderBottomWidth: 1, borderBottomColor: "#F0F0F0" },
  label: { fontSize: 14, color: "#666" },
  value: { fontSize: 14, fontWeight: "500", color: "#333" },
  formatText: { fontSize: 14, color: "#007AFF", marginVertical: 4 },
  localeRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", paddingVertical: 8 },
  localeText: { fontSize: 14, color: "#333" },
  rtlBadge: { backgroundColor: "#FF9500", color: "#FFF", paddingHorizontal: 8, paddingVertical: 2, borderRadius: 4, fontSize: 12 },
});

export default App;

⚠️ 遗留问题

  • HarmonyOS 侧无法获取温度及长度单位 issue#2
  • SSR(Web 端专属)相关特性暂不支持鸿蒙系统 issue#13
  • react-native-localize 的 Expo 配置插件暂不支持鸿蒙系统 issue#14
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐