Flutter 纯 Dart 动态翻译库 flutter_dynamic_translator 鸿蒙适配实践,无 ArkTS 原生代码,基于 ARB 资产运行时切换多语言
开发工具: 华为云码道
原始仓库: flutter-translator/flutter-dynamic-translator
本文配套仓库: oh-flutter/flutter-dynamic-translator
DevEco Studio:
26.0.0(26.0.0.851)
flutter_dynamic_translator 提供一套按语言文件在运行时翻译的最小 API:T.load(locale) 读取 .arb 语言文件,T.get(key) 按键取值,取不到时直接回退为 key 本身。它的特殊之处在于:没有 ArkTS 业务代码,翻译完全由纯 Dart 完成,OHOS 侧只需要在平台清单里登记一个入口。本文以 flutter_dynamic_translator 1.0.10 为例,介绍从上游源码准备工程、补全 OHOS 平台、补上缺失的语言资产、构建并在真机验证的完整过程。

插件目前支持 ohos 平台,源码位于 AtomGit 配套仓库。文中的代码以提交 5ae22b433e28ae82a275a370122ec258bc100656 为参考,对应 TAG 1.0.10-ohos-1.0.0-beta.1。

需要说明的是,上游仓库名是 flutter-dynamic-translator,Dart 包名是 flutter_dynamic_translator。写依赖、传 --project-name、引用 package: 时都以包名为准,仓库名只用于 clone 和地址。
一、插件简介与适配目标
1.1 这个库提供什么
调用一次 T.load('zh'),把 assets/lang/zh.arb 读进内存里的 Map<String, String>;之后所有 T.get(key) 都从这张表里取:
await T.load('en'); // 读取 assets/lang/en.arb
Text(T.get('greeting')); // "Hello, world!"
Text(T.get('not_exist')); // "not_exist"(回退 key 本身)
T.load 是异步的,T.get 是同步的。翻译表是进程级静态变量,切换语言只需再调用一次 T.load,再触发一次页面重建。
1.2 适配范围
上游 pubspec.yaml 的平台清单里,android/ios/web/linux/macos/windows 六个平台都写着 default_package: flutter_dynamic_translator 这种自引用形式:
flutter:
plugin:
platforms:
android:
default_package: flutter_dynamic_translator
ios:
default_package: flutter_dynamic_translator
web:
default_package: flutter_dynamic_translator
linux:
default_package: flutter_dynamic_translator
macos:
default_package: flutter_dynamic_translator
windows:
default_package: flutter_dynamic_translator
default_package 指向包自身,表示“这个平台上没有独立实现,用包里的 Dart 代码即可”。因此本次适配要做的只有两件事:
- 在平台清单里补一行
ohos: default_package: flutter_dynamic_translator,让 OHOS 构建时把包当作本平台可用; - 补上上游缺失的语言资产(
example/assets/lang/*.arb),让T.load有文件可读。
除此之外没有任何 ArkTS 业务代码,也没有权限申请。
二、环境准备
环境搭建参考社区文档:Flutter OH 开发环境搭建,完成 Flutter OH SDK 安装、环境变量和 DevEco Studio 配置。
完成后,在宿主机终端执行以下命令,确认当前选中的是支持 OHOS 的 Flutter 工具链,并能发现目标设备:
flutter --version
flutter doctor -v
hdc list targets


工程使用的工具链和 SDK 配置如下:
| 项目 | 版本或配置 | 用途 |
|---|---|---|
| Flutter OHOS SDK | 3.47.7-ohos-1.0.0 | Flutter 编译与 OHOS 平台工具链 |
| Flutter 分支 | oh-3.47-dev | CPF-Flutter 对应开发分支 |
| Dart SDK | 3.13.3 | Dart 语言与包管理环境 |
| HarmonyOS 开发套件 | 26.0.0(API 26) | 开发套件版本及对应的 API 级别 |
compileSdkVersion | 26.0.0 | 编译时使用的 SDK API |
targetSdkVersion | 26.0.0 | 应用面向的行为版本 |
compatibleSdkVersion | 6.1.1(24) | 当前工程声明的最低兼容版本 |
| 插件版本 | 1.0.10 | pubspec.yaml 中的包版本 |
| 原生语言 | 无(纯 Dart) | 翻译逻辑与资产读取都在 Dart 侧 |
| 插件产物 | Dart 包 | 不需要 HAR,宿主应用直接编译包内源码 |
2.1 开发套件版本与工程中的 SDK 版本配置
26.0.0(API 26) 和 26.0.0 涉及 HarmonyOS 开发套件版本(API 版本)及其底座 OpenHarmony 的版本号体系。在本文工程中,相关版本的含义与配置方式如下:
26.0.0(API 26)表示 HarmonyOS 开发套件版本为26.0.0,对应 API 26。26.0.0是本文 HarmonyOS 应用工程中compileSdkVersion和targetSdkVersion的属性值。6.1.1(24)是本文工程中compatibleSdkVersion的属性值,声明最低兼容 API 24。
对应的 product 配置为:
{
"name": "default",
"compatibleSdkVersion": "6.1.1(24)",
"compileSdkVersion": "26.0.0",
"targetSdkVersion": "26.0.0",
"runtimeOS": "HarmonyOS"
}

这组配置使用 API 26 SDK 编译,并以 API 26 为目标版本,最低兼容 API 24。compatibleSdkVersion 这一项不是照抄模板得来的:适配提交里最初沿用了脚手架给出的 26.0.0,在 API 24 的设备上安装会直接被拒(install failed due to older sdk version in the device),改成 6.1.1(24) 后安装通过。细节见 9.7。
三、从源码仓库开始准备适配工程
3.1 将上游源码同步到 AtomGit
适配已有第三方插件时,先从 Pub 包信息或项目 README 确认上游地址、包名、版本和许可证。同步仓库时保留源码、许可证和提交历史,方便后续跟踪上游更新。
在 AtomGit 网页的新建仓库流程中,找到从已有仓库导入的入口,填写上游 Git 地址,选择自己有写权限的组织或个人空间,设置目标仓库名称后执行导入。完成后检查目标分支、pubspec.yaml、LICENSE 和提交历史是否完整,并记录适配起点的提交号。如果上游本身托管在 AtomGit,也可以通过 Fork 获得自己的工作仓库。
flutter-dynamic-translator 的上游是 flutter-translator/flutter-dynamic-translator,pubspec.yaml 声明的许可证为 MIT。下面使用 AtomGit 地址拉取代码;需要提交修改时,使用自己有写权限的仓库或 Fork。
3.2 将代码拉取到宿主机
在安装了 Flutter OH 和 DevEco Studio 的开发电脑上打开终端,进入准备存放项目的目录,执行:
git clone https://atomgit.com/oh-flutter/flutter-dynamic-translator.git
cd flutter-dynamic-translator
pwd
ls
git remote -v
git status --short --branch
git rev-parse HEAD
git clone 会创建 flutter-dynamic-translator/ 目录;cd 后的位置就是下文所说的插件仓库根目录,这里应能看到 pubspec.yaml、lib/ 和 example/。Git 仓库名是 flutter-dynamic-translator,Dart 包名是 flutter_dynamic_translator。
需要使用与本文相同的代码版本时,在没有未提交修改的仓库中切换到以下提交:
git switch --detach 5ae22b433e28ae82a275a370122ec258bc100656
适配其他插件时,使用该插件对应版本的 tag 或 commit 作为分支起点。

图 1:在宿主机终端拉取原始仓库并查看提交历史。
上游仓库是一个长期小步提交的仓库:最早的提交大多是 init、edited Readme,568eb92 d 是适配前的最后一个上游提交。上游没有发布过任何 TAG,因此下文的 TAG 全部是适配侧新建的。
3.3 创建适配分支与版本标记
分支命名沿用 feat/ohos_库名称_版本号,库名称取 pubspec.yaml 的 name,版本号取此次适配的基线版本。本例的提交直接落在 main 上:
git branch --show-current
git status --short --branch
如果需要走评审流程,也可以新建分支:
git switch -c feat/ohos_flutter_dynamic_translator_1.0.10
适配完成后打 TAG。TAG 名沿用 <上游版本>-ohos-1.0.0-beta.1 的形式:
git tag 1.0.10-ohos-1.0.0-beta.1 -m "flutter_dynamic_translator 1.0.10 OpenHarmony adaptation"
git tag --list
git log --oneline -4

图 2:分支、TAG、近期提交与远端配置。
origin 指向 AtomGit 上的配套仓库,github 保留为上游原始仓库的只读远端,方便对比上游更新。
3.4 补全 OHOS 适配结构
本例不需要生成 ohos/ 原生目录。命令 flutter create --template=plugin --platforms=ohos . 会生成 HAR 模块、index.ets 和 ArkTS 插件模板,而这个库没有任何原生逻辑,多出来的 ArkTS 桩只会增加维护面。适配只改了 pubspec.yaml 的平台清单。
真正需要 flutter create 的是示例应用的宿主工程。如果 example/ohos/ 不存在,进入示例应用补全:
cd example
flutter create --platforms=ohos .
cd ..
--platforms=ohos指定只补 OHOS 平台目录。- 最后的
.表示在当前目录补全工程,不是另建一层目录。
配套仓库已经包含 example/ohos/,直接运行示例时可以跳过这一步。生成或修改后,用 diff 检查变化:
git status --short
git show 92e75c0 -- pubspec.yaml
flutter analyze

图 3:平台清单的改动、资产读取入口与静态分析结果。
3.5 适配后的项目目录
适配后的关键目录如下:
flutter-dynamic-translator/
├── lib/
│ ├── flutter_dynamic_translator.dart
│ ├── flutter_dynamic_translator_method_channel.dart
│ └── flutter_dynamic_translator_platform_interface.dart
├── example/
│ ├── assets/lang/en.arb
│ ├── assets/lang/zh.arb
│ ├── lib/main.dart
│ ├── test/widget_test.dart
│ ├── integration_test/plugin_integration_test.dart
│ └── ohos/entry/
├── test/
├── README.OpenHarmony.md
├── README.OpenHarmony_CN.md
├── CHANGELOG.OpenHarmony.md
└── pubspec.yaml
项目根目录如下,其中包含 example/、OpenHarmony 中英文说明和变更记录文件;注意根目录没有 ohos/,这是纯 Dart 库的正常形态:

图 4:适配后的项目根目录与文件职责。
| 文件 | 主要职责 |
|---|---|
lib/flutter_dynamic_translator.dart | 对外入口 T:load / get / getPlatformVersion |
lib/flutter_dynamic_translator_platform_interface.dart | 定义平台无关接口与实例替换入口 |
lib/flutter_dynamic_translator_method_channel.dart | 通道 flutter_dynamic_translator 的 getPlatformVersion 实现 |
example/assets/lang/en.arb、zh.arb | 语言文件(上游缺失,适配时补全) |
example/lib/main.dart | 中英切换、键值表、平台版本探测、行为探测 |
example/ohos/ | 示例宿主工程(AppScope + entry) |
test/ | T.load / T.get 与通道的单测 |
README.OpenHarmony_CN.md / README.OpenHarmony.md | 中文与英文适配说明 |
CHANGELOG.OpenHarmony.md | OHOS 适配变更记录 |
四、Dart 接口与通道分析
OHOS 适配需要遵循 Dart 层已有的方法、参数和回调约定。先阅读 lib/flutter_dynamic_translator.dart、lib/flutter_dynamic_translator_platform_interface.dart 和 lib/flutter_dynamic_translator_method_channel.dart,确认哪些能力走资产、哪些能力走通道。适配其他已有平台的插件时,也应保留其公开接口和其他平台实现。
本例的对应关系如下:
| Dart 入口或模型 | 通道协议 | OHOS 实现 | 应保持的行为 |
|---|---|---|---|
T.load(locale) | 无通道,rootBundle.loadString | 宿主应用的资产打包进 HAP | 读取 assets/lang/<locale>.arb,JSON 解析成 Map |
T.get(key) | 无通道 | 纯 Dart 查表 | 命中返回值,未命中返回 key 本身 |
T.getPlatformVersion() | flutter_dynamic_translator / getPlatformVersion | 无原生注册 | 抛 MissingPluginException,由调用方决定如何展示 |
关键结论:这个库的“能力”不在原生侧。翻译行为完全由资产文件和 Dart 解析决定,通道只留了一个可选的平台版本探测。
4.1 跨端架构与调用时序
Flutter 侧和 HarmonyOS 侧之间只有一条通道,且不参与翻译:
rootBundle:从应用资产包读取.arb,这条链路不经过原生代码;MethodChannel('flutter_dynamic_translator'):只有一个getPlatformVersion方法,OHOS 侧没有实现。
4.1.1 一次语言切换的时序
注意这里没有 EventChannel,也没有订阅与取消订阅:语言切换是一次性的命令,页面重建后重新取值即可。适配时不要为了套用“事件流”的模板而虚构订阅机制。
4.2 翻译表模型
翻译表就是一个 Map<String, String>,load 时整体替换,get 时按键取值:
class T {
static Map<String, String> _translations = {};
static Future<void> load(String locale) async {
final jsonString = await rootBundle.loadString('assets/lang/$locale.arb');
final jsonMap = json.decode(jsonString);
_translations = Map<String, String>.from(jsonMap);
}
static String get(String key) {
return _translations[key] ?? key;
}
}
| 模型 | 取值 | 行为 |
|---|---|---|
_translations | assets/lang/<locale>.arb 的 JSON 对象 | 整体替换,不做增量合并 |
T.get(key) | 命中 | 返回译文 |
T.get(key) | 未命中 | 返回 key 本身,不抛异常、不打日志 |
两点边界值得在接入前明确:
- 未命中的 key 会原样显示。界面上出现
greeting这样的英文短语,说明键不在当前语言文件里,而不是渲染错误。 load失败时旧表保持不变。load内部先loadString再赋值,资产缺失会在解码前抛错,上一门语言的译文仍在内存里,界面容易出现“标题变了、正文没变”的错觉。
4.3 公开 API 与平台接口
对外只有三个静态方法,没有回调、没有实例:
class T {
static Future<String?> getPlatformVersion();
static Future<void> load(String locale);
static String get(String key);
}
平台接口只定义了一个方法,供测试替换实现:
abstract class FlutterDynamicTranslatorPlatform extends PlatformInterface {
static FlutterDynamicTranslatorPlatform _instance =
MethodChannelFlutterDynamicTranslator();
static FlutterDynamicTranslatorPlatform get instance => _instance;
Future<String?> getPlatformVersion() {
throw UnimplementedError('platformVersion() has not been implemented.');
}
}
注意 T 没有通过平台接口转发 load / get,它直接调用 rootBundle。这意味着替换平台实例不会影响翻译行为,只会影响 getPlatformVersion。
4.4 Dart 通道协议分析
4.4.1 通道名称必须两端完全一致
static const MethodChannel _channel =
MethodChannel('flutter_dynamic_translator');
通道名称属于跨语言协议。本例 OHOS 侧没有注册任何 handler,因此 invokeMethod 会直接抛 MissingPluginException,而不是返回 null。
4.4.2 getPlatformVersion 的实际行为
static Future<String?> getPlatformVersion() async {
try {
final String? version = await _channel.invokeMethod('getPlatformVersion');
return version;
} on PlatformException {
return null;
}
}
这段代码只捕获了 PlatformException。MissingPluginException 与 PlatformException 没有继承关系,所以异常会原样抛给调用方。在 OHOS 上调用它,得到的是:
MissingPluginException(No implementation found for method getPlatformVersion
on channel flutter_dynamic_translator)
9.4 与 9.5 都基于这个事实。示例页没有把这个异常藏起来,而是把原文显示在“平台版本”卡片里——截图里能直接读到它,这比写“调用失败”四个字更有信息量。
4.4.3 没有事件流
上游没有 EventChannel,也没有订阅、取消订阅、状态码这类概念。业务侧只需要关心“当前是哪门语言”和“某个键的译文是什么”,这两件事都由 T 的静态状态和页面重建解决。
五、补全 OHOS 实现与工程配置
5.1 为什么没有 ArkTS 实现
原生插件类(XxxPlugin.ets)的作用是把 Dart 命令翻译成系统 API 调用。这个库没有系统 API 需要调用:资产读取由 Flutter Engine 处理,JSON 解析是 Dart 代码。所以 OHOS 侧唯一需要的动作,是在 pubspec.yaml 里声明“本平台使用包内 Dart 实现”。
代价是 T.getPlatformVersion() 在 OHOS 上不可用。上游的六个平台里,web/linux/macos/windows 也是同样的自引用声明、同样没有原生实现,因此这不是 OHOS 特有的问题,而是这个包的既有设计。
如果业务确实需要平台版本,正确做法是在应用层自己用 deviceInfo 取值,而不是依赖这个包的探测方法。示例页保留了这个调用,只是为了让这条边界在真机上可观察。
5.2 声明插件和宿主权限
本例不需要任何权限。.arb 文件随应用一起打包,读取资产不涉及用户隐私,也不涉及系统能力申请。example/ohos/entry/src/main/module.json5 保持脚手架默认配置即可,不需要 requestPermissions。
5.3 注册并导出插件
pubspec.yaml 通过以下配置声明 OHOS 平台入口:
flutter:
plugin:
platforms:
ohos:
default_package: flutter_dynamic_translator
与 pluginClass: XxxPlugin 的插件不同,这里没有 ohos/index.ets,也不需要导出 ArkTS 类。Flutter 工具在构建时看到 default_package 指向包自身,就不会去找原生插件,也不会生成 GeneratedPluginRegistrant.ets 注册项。
排查注册问题时要留意这一点:没有注册项是这个库的正常状态,不能凭“注册文件里没有这一行”判断适配失败。
5.4 检查 example 的 OHOS 应用结构
本例的 example/ohos/build-profile.json5 应在 products 中设置版本。下面是需核对的配置片段,请合并到现有工程;其中 signingConfig: "default" 需要与本机配置的签名名称一致,签名材料保留在本地。
{
"app": {
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "6.1.1(24)",
"runtimeOS": "HarmonyOS",
"compileSdkVersion": "26.0.0",
"targetSdkVersion": "26.0.0"
}
]
},
"modules": [
{
"name": "entry",
"srcPath": "./entry",
"targets": [
{
"name": "default",
"applyToProducts": ["default"]
}
]
}
]
}
配置后,在 DevEco Studio 中执行一次 Sync Project。如果 entry 模块正常识别,Project 视图中会出现 entry,并能打开 entry 模块的签名配置。
还要核对 AppScope/app.json5 的 bundleName。本仓库沿用示例工程的 com.example.color_system_example,同一台设备上装过其他示例时会被覆盖安装——这解释了下文 aa start 使用的包名为什么不是仓库名。
六、补全交付文件并提交适配分支
6.1 除代码外还要补全哪些文件
代码适配完成后,还需要整理安装说明、接口文档、版本记录和开源信息。接收仓库有专用模板时,按其格式填写:
| 文件 | 应写清楚的内容 |
|---|---|
README.OpenSource | 上游名称、源码地址、适配版本或提交、版权及许可证信息;按仓库模板列出第三方依赖 |
README.md | 原项目说明、OHOS 支持入口、配套 Demo 和文档链接;保留上游信息 |
README.OpenHarmony_CN.md | 简介、AtomGit 安装方式、版本对应关系、环境约束、接口表、示例、已验证范围和遗留问题 |
README.OpenHarmony.md | 与中文说明对应的英文文档 |
CHANGELOG.OpenHarmony.md | OHOS 新增能力、适配版本、兼容限制与测试范围 |
LICENSE / NOTICE | 保留上游许可证;本仓库为 MIT,无需额外 NOTICE |
example/README.md | 依赖方式、运行目录、签名、操作步骤与效果图 |
pubspec.yaml | 核对包名、版本、平台清单、仓库地址和许可证 |
.gitignore | 忽略构建缓存及本机签名材料,不漏提交必要源码和配置 |
README.OpenSource 记录库本身的来源与版本。本例的包名为 flutter_dynamic_translator,版本为 1.0.10,采用 MIT 许可证;Flutter 和 HarmonyOS SDK 版本写入环境说明。
6.2 提交前检查
提交前先完成第八节的插件、example 和真机测试,再从根目录检查改动:
git branch --show-current
git diff --check
git status --short
git diff --stat
git diff
检查 diff 中的接口、平台注册和依赖变化,移除本机路径及签名信息,并使文档中的版本和分支与提交内容一致。本次提交前专门处理了两类问题:
example/ohos/build-profile.json5里的signingConfigs被 DevEco 自动写入了本机证书路径和口令,提交版必须是空数组;example/test/widget_test.dart还留着脚手架生成的MyApp引用,flutter analyze因此报creation_with_non_type,需要重写成针对实际页面的断言。
6.3 提交并推送到 AtomGit
文档和代码整理完成后,在根目录暂存并提交:
git add -A
git diff --cached --check
git diff --cached --stat
git commit -m "fix: strip signing materials, rewrite example tests, correct docs metadata"
git remote -v
git branch --show-current
git push -u origin main
git push origin 1.0.10-ohos-1.0.0-beta.1
推送时,origin 应指向自己的 AtomGit 仓库,当前分支为 main。推送后在 AtomGit 发起合并请求,说明上游来源和版本、OHOS 实现范围、依赖及权限、测试环境、操作结果、已知限制,并附 Demo 运行图。目标分支和评审流程以接收仓库要求为准。
七、使用根目录 example 演示接入
仓库自带 example/,可以直接用来调试和体验语言切换。
7.1 本地适配时使用路径依赖
当前 example/pubspec.yaml 的依赖是:
dependencies:
flutter:
sdk: flutter
flutter_dynamic_translator:
path: ../
../ 相对于 example/pubspec.yaml 指向插件根目录,修改根目录插件后可直接联调。example 的 pubspec.yaml 还声明了语言目录:
flutter:
uses-material-design: true
assets:
- assets/lang/
这行 assets 声明是 T.load 能工作的前提:.arb 文件必须属于某个声明过资产的包,才会被打进 HAP。
7.2 通过 AtomGit 引入插件
业务应用通过 AtomGit 引入时,将 flutter_dynamic_translator 的 path 配置替换为下面的 Git 依赖。这里固定到本文使用的 TAG:
dependencies:
flutter:
sdk: flutter
flutter_dynamic_translator:
git:
url: https://atomgit.com/oh-flutter/flutter-dynamic-translator.git
ref: 1.0.10-ohos-1.0.0-beta.1
使用自己的适配版本时,先推送分支,再将 url 改为对应仓库,ref 改为对应分支或提交。正式发布后可固定到 tag。
从插件根目录执行:
cd example
flutter pub get
flutter pub deps
检查 example/pubspec.lock 中 flutter_dynamic_translator 的来源为 git,并核对 url、ref 和 resolved-ref。同时检查没有 dependency_overrides 或 pubspec_overrides.yaml 将其覆盖回本地依赖。
7.3 调用接口实现语言切换
下面的页面展示语言切换按钮和当前译文,可用于 example/lib/main.dart。仓库中的完整 Demo 还提供键值表、平台版本探测和行为探测卡片。
import 'package:flutter/material.dart';
import 'package:flutter_dynamic_translator/flutter_dynamic_translator.dart';
void main() {
runApp(const MaterialApp(home: TranslatorPage()));
}
class TranslatorPage extends StatefulWidget {
const TranslatorPage({super.key});
State<TranslatorPage> createState() => _TranslatorPageState();
}
class _TranslatorPageState extends State<TranslatorPage> {
String _locale = 'en';
String _error = '';
void initState() {
super.initState();
_load('en');
}
Future<void> _load(String locale) async {
try {
await T.load(locale);
if (!mounted) return;
setState(() {
_locale = locale;
_error = '';
});
} catch (e) {
if (!mounted) return;
setState(() => _error = e.toString());
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(T.get('appTitle'))),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(T.get('greeting'),
style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 12),
SegmentedButton<String>(
segments: const [
ButtonSegment(value: 'en', label: Text('English')),
ButtonSegment(value: 'zh', label: Text('中文')),
],
selected: {_locale},
onSelectionChanged: (sel) => _load(sel.first),
),
const SizedBox(height: 12),
Text('当前文件:assets/lang/$_locale.arb'),
if (_error.isNotEmpty)
Text(_error, style: const TextStyle(color: Colors.red)),
],
),
),
);
}
}
7.4 页面退出时的处理
T 没有订阅、没有监听器、也没有需要释放的原生资源,dispose 里不需要做清理。异步回调仍然要先检查 mounted,避免页面销毁后继续调用 setState。
需要注意的是一种“看似状态不同步”的情况:T.load 失败时静态翻译表保持上一次的值,页面若只用 T.get 渲染而不读 _locale,会显示上一门语言的译文。示例页把当前语言和当前文件路径都显示出来,就是为了让这种不一致一眼可见。
八、验证、构建与鸿蒙设备运行效果
8.1 分别验证插件与 example
从插件仓库根目录执行:
flutter pub get
flutter analyze
flutter test
cd example
flutter pub get
flutter analyze
flutter test
本次实际结果是:flutter analyze 在两个目录都返回 No issues found;flutter test 在本机无法执行——命令启动后没有输出并一直挂起,直到超时被终止。这是本机 Flutter OH 工具链的环境问题(同类现象还有 Invalid SDK hash 与 Connection closed before test suite loaded),与仓库代码无关。因此本文不声明测试通过。
测试文件本身按真实契约编写,可在工具链正常的环境里直接运行:
| 文件 | 覆盖内容 |
|---|---|
test/flutter_dynamic_translator_test.dart | mock flutter/assets,断言 T.load 解析与 T.get 的命中/回退 |
test/flutter_dynamic_translator_method_channel_test.dart | mock 通道,断言 getPlatformVersion 的转发 |
example/test/widget_test.dart | mock 资产与通道,断言首屏英文译文、MissingPluginException 上屏、切到中文后的译文 |
example/integration_test/plugin_integration_test.dart | 真机上加载 en.arb 并断言 greeting 的具体值 |
8.2 确认设备连接
hdc list targets
flutter devices
设备首次连接电脑时,需要在手机端确认调试授权。列表为空时,检查 USB 连接、调试模式和电脑授权。
8.3 配置签名
真机安装的 HAP 通常需要有效签名。推荐使用 DevEco Studio 为 entry 模块配置自动签名:
- 用 DevEco Studio 打开
example/ohos,不是仓库根目录; - 等待工程 Sync 成功,确认 Project 视图中存在
entry模块; - 打开 File > Project Structure > Signing Configs;
- 为
defaultproduct 选择或生成签名; - 确认设备、应用包名、证书和 Profile 匹配;
- 再回到终端执行 Flutter 构建或运行。
签名材料保存在本机,公开仓库中只保留构建所需的通用配置。
8.4 构建与安装
以下命令在 example/ 目录执行,将 <device-id> 替换为设备列表中的实际 ID:
flutter run -d <device-id>
也可以先构建 HAP:
flutter build hap --debug
本机 flutter build hap 在 hvigor 阶段会因环境问题提前退出,可用下面两步替代,效果一致:
cd example/ohos
ohpm install
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
典型产物位于:
example/ohos/entry/build/default/outputs/default/
本次构建输出 BUILD SUCCESSFUL,entry-default-signed.hap 约 138 MiB。安装命令要在产物目录执行,并把文件名写成相对路径,避免路径被二次拼接:
cd example/ohos/entry/build/default/outputs/default
hdc install -r entry-default-signed.hap
8.5 在设备上测试语言切换
- 打开应用,确认首屏为英文:标题、问候语、键值表都来自
en.arb; - 切换到“中文”,确认标题变为“动态翻译演示”、问候语变为“你好,世界!”;
- 检查键值表的 5 行全部换成中文,且标题里的文件名变为
zh.arb; - 向下滚动,查看“平台版本”卡片显示的
MissingPluginException原文; - 查看“行为探测”卡:当前文件为
assets/lang/zh.arb,缺失键not_exist回退为not_exist; - 打开右上角“关于”对话框,确认版本信息;
- 切回 English,确认可反复切换且不残留上一门语言;
- 退出应用再进入,确认默认回到英文。
8.6 鸿蒙设备运行效果
完成适配后,Flutter 应用能够在鸿蒙设备上按语言文件切换文案,界面元素(标题、问候语、说明文字、键值表)全部随 T.load 的结果更新。
下面展示英文首屏、切换到中文和滚动到探测卡片的效果:
英文 en.arb | 中文 zh.arb | 探测卡片 |
|---|---|---|
T.get('greeting') → Hello, world! | T.get('greeting') → 你好,世界! | 缺失键回退 + 平台版本异常原文 |
关于对话框与切回英文的效果:
设备信息与验证范围:TLR-AL00,OpenHarmony 6.1.0.145(API 24)。验证到的是“资产加载 + 纯 Dart 翻译”这条链路;getPlatformVersion 在本平台不可用,属于设计使然,见 5.1 与 9.4。
九、FAQ:适配过程与使用问题
9.1 Missing SDK components
典型错误如下:
Missing SDK components. SDK path: ...,
missing components: toolchains,ets,js,native,previewer.
这个错误发生在 Hvigor 同步阶段。通常需要检查构建工具使用的 SDK 路径、组件是否完整,以及 Hvigor 与 SDK 的版本是否匹配。
处理顺序:
- 在 DevEco Studio SDK Manager 中确认 API 26 组件已经下载完整;
- 检查 Flutter 和 DevEco Studio 使用的 SDK 路径是否一致;
- 避免误用
/Applications/DevEco-Studio.app/Contents/sdk之类的不完整目录; - 确认 SDK 根目录下存在
toolchains、ets、js、native、previewer; - 执行
flutter config --ohos-sdk <正确路径>; - 重新执行
flutter doctor -v和 DevEco Studio Sync。
因为 Sync 和 Compile 首先读取本机 SDK。手机 API 版本只在部署、安装和运行时参与兼容判断。即使完全不连接手机,本地 SDK 不完整时也会得到相同错误。
当前工程的 compatibleSdkVersion 是 API 24,因此 API 24 设备在安装版本门槛上是满足的;但设备是否支持具体能力,仍要看功能本身。
9.2 DevEco Studio 中看不到 entry 模块
本库没有 ohos/ 插件模块,只有示例宿主工程 example/ohos/entry。请直接使用 DevEco Studio 打开:
flutter-dynamic-translator/example/ohos
如果仍看不到 entry,先解决 SDK Sync 错误,再检查 example/ohos/build-profile.json5 的 modules 是否包含 ./entry。同步失败时,Project Structure 无法正确解析模块,签名界面也可能不显示 entry。
9.3 无法手动签名
签名配置依附于可构建的应用模块和 product。工程 Sync 失败,或者打开了仓库根目录(根目录没有 ohos/ 工程)时,DevEco Studio 都不会提供 entry 签名入口。
建议先确认:
- 打开的是
example/ohos; - SDK 组件完整并且 Sync 成功;
entry的模块类型为entry;defaultproduct 和 target 已正确关联;- 当前账号、证书和调试设备状态有效。
9.4 能安装但“平台版本”一直显示省略号或异常
先确认界面显示的具体内容:
- 显示
…:说明探测 Future 还没返回,通常是异常被吞在未捕获的异步错误里; - 显示
MissingPluginException(...):说明通道没有原生实现,这是本库在 OHOS 上的预期结果; - 显示具体版本号:说明有原生实现接管了通道,与本库的适配方式不符。
本库的 getPlatformVersion 只捕获 PlatformException,MissingPluginException 会原样抛出。业务侧如果需要平台版本,请自行读取设备信息,不要依赖这个方法。
9.5 MissingPluginException
在本库上出现这个异常,多数不是适配失败,而是调用了一个本来就没有原生实现的方法:
MissingPluginException(No implementation found for method getPlatformVersion
on channel flutter_dynamic_translator)
先区分调用的是哪个方法:T.load / T.get 不走通道,不会产生这个异常;只有 T.getPlatformVersion 会。若异常来自业务自己的其他通道,再按常规排查——执行 flutter clean、重新构建、检查自动注册文件是否包含对应插件:
cd example
flutter clean
flutter pub get
flutter run -d <device-id>
9.6 界面显示的是 key 而不是译文
这是 T.get 的回退行为,不是渲染问题。按顺序检查:
- 该 key 是否存在于当前加载的
.arb文件里(注意大小写); - 当前
_locale与文件名是否一致,例如_locale == 'zh'时读的是zh.arb; - 切换语言后是否触发了页面重建(
setState); - 宿主应用的
pubspec.yaml是否声明了assets/lang/。
示例页的“行为探测”卡把 T.get('not_exist') 的结果直接打屏,正常应显示 not_exist。如果这里显示空字符串,说明回退逻辑被改写过了。
9.7 编译成功但安装失败
常见原因包括:
- HAP 未签名或使用了错误的 Profile;
- 设备未加入调试设备列表;
- 包名与签名 Profile 不匹配;
- 安装包的
compatibleSdkVersion高于设备 API; - 手机上已经安装了使用不同证书签名的同包名应用。
本次适配真实遇到的是第四条:
error: failed to install bundle. code:9568297
error: install failed due to older sdk version in the device.
工程里 compatibleSdkVersion 写的是 26.0.0,设备是 API 24,安装直接被拒。改成 6.1.1(24) 并重新构建后安装成功。这类错误码指向版本门槛,不要先去怀疑签名。
9.8 flutter create 不认识 ohos,或包名不合法
先执行 flutter --version,确认使用的是 OHOS 版工具链,环境配置回到第二节的社区链接核对。包名报错时,确认当前目录包含目标插件的 pubspec.yaml,并显式传入 --project-name flutter_dynamic_translator;仓库名 flutter-dynamic-translator 含连字符,不能直接作为 Dart 包名。
注意本库不需要生成插件侧的 ohos/,只需在 example/ 下补全宿主工程。
9.9 AtomGit 依赖提示找不到分支或无权限
先检查 URL 是否指向已同步的目标仓库,再确认 main 分支和 TAG 1.0.10-ohos-1.0.0-beta.1 已推送:
git ls-remote --heads origin
git ls-remote --tags origin
TAG 尚未推送时,可以先使用第七节的提交号作为 ref。私有仓库还需在本机配置 Git 认证。
9.10 改了 .arb 文件,Demo 为什么没变化
三个位置要一起检查:
.arb文件在example/assets/lang/下,而不是插件根目录;example/pubspec.yaml的flutter.assets声明了assets/lang/;- 资源变更需要重新构建并重装 HAP,热重载不会重新打包资产。
如果使用的是 Git 依赖,插件侧的文件与资产来自远程提交,本地改动不会生效,需要先提交推送再更新依赖。
9.11 加载失败后,界面显示的是上一门语言
T.load 失败时不会清空 _translations,上一次成功加载的译文仍在内存里。若页面只按 T.get 渲染、不显示当前语言,看起来就像“切换没生效”。
处理方式是在 load 外层捕获异常,把当前选中语言和实际加载结果一起显示出来;示例页的做法是:_error 非空时用错误卡片展示原始异常,同时展示“当前语言文件”路径。
相关链接
更多推荐



所有评论(0)