开发工具: 华为云码道

原始仓库: 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 代码即可”。因此本次适配要做的只有两件事:

  1. 在平台清单里补一行 ohos: default_package: flutter_dynamic_translator,让 OHOS 构建时把包当作本平台可用;
  2. 补上上游缺失的语言资产(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 SDK3.47.7-ohos-1.0.0Flutter 编译与 OHOS 平台工具链
Flutter 分支oh-3.47-devCPF-Flutter 对应开发分支
Dart SDK3.13.3Dart 语言与包管理环境
HarmonyOS 开发套件26.0.0(API 26)开发套件版本及对应的 API 级别
compileSdkVersion26.0.0编译时使用的 SDK API
targetSdkVersion26.0.0应用面向的行为版本
compatibleSdkVersion6.1.1(24)当前工程声明的最低兼容版本
插件版本1.0.10pubspec.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.mdOHOS 适配变更记录

四、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 侧之间只有一条通道,且不参与翻译:

  1. rootBundle:从应用资产包读取 .arb,这条链路不经过原生代码;
  2. MethodChannel('flutter_dynamic_translator'):只有一个 getPlatformVersion 方法,OHOS 侧没有实现。

Flutter 页面

T 对外 API

load: rootBundle.loadString

HAP 内 assets/lang/*.arb

静态 Map 翻译表

get: 查表

MethodChannel flutter_dynamic_translator

OHOS 无原生实现

MissingPluginException

4.1.1 一次语言切换的时序
界面 HAP 资产 rootBundle T (静态入口) Flutter 页面 界面 HAP 资产 rootBundle T (静态入口) Flutter 页面 T.load('zh') loadString('assets/lang/zh.arb') 读取资产 JSON 文本 jsonString json.decode + Map.from Future 完成 setState 重建 T.get('greeting') "你好,世界!"

注意这里没有 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;
  }
}
模型取值行为
_translationsassets/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.mdOHOS 新增能力、适配版本、兼容限制与测试范围
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.dartmock flutter/assets,断言 T.load 解析与 T.get 的命中/回退
test/flutter_dynamic_translator_method_channel_test.dartmock 通道,断言 getPlatformVersion 的转发
example/test/widget_test.dartmock 资产与通道,断言首屏英文译文、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 模块配置自动签名:

  1. 用 DevEco Studio 打开 example/ohos,不是仓库根目录;
  2. 等待工程 Sync 成功,确认 Project 视图中存在 entry 模块;
  3. 打开 File > Project Structure > Signing Configs;
  4. 为 default product 选择或生成签名;
  5. 确认设备、应用包名、证书和 Profile 匹配;
  6. 再回到终端执行 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 在设备上测试语言切换

  1. 打开应用,确认首屏为英文:标题、问候语、键值表都来自 en.arb;
  2. 切换到“中文”,确认标题变为“动态翻译演示”、问候语变为“你好,世界!”;
  3. 检查键值表的 5 行全部换成中文,且标题里的文件名变为 zh.arb;
  4. 向下滚动,查看“平台版本”卡片显示的 MissingPluginException 原文;
  5. 查看“行为探测”卡:当前文件为 assets/lang/zh.arb,缺失键 not_exist 回退为 not_exist;
  6. 打开右上角“关于”对话框,确认版本信息;
  7. 切回 English,确认可反复切换且不残留上一门语言;
  8. 退出应用再进入,确认默认回到英文。

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 的版本是否匹配。

处理顺序:

  1. 在 DevEco Studio SDK Manager 中确认 API 26 组件已经下载完整;
  2. 检查 Flutter 和 DevEco Studio 使用的 SDK 路径是否一致;
  3. 避免误用 /Applications/DevEco-Studio.app/Contents/sdk 之类的不完整目录;
  4. 确认 SDK 根目录下存在 toolchains、ets、js、native、previewer;
  5. 执行 flutter config --ohos-sdk <正确路径>;
  6. 重新执行 flutter doctor -v 和 DevEco Studio Sync。
为什么连接 API 24 手机仍然会报这个错误?

因为 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;
  • default product 和 target 已正确关联;
  • 当前账号、证书和调试设备状态有效。

9.4 能安装但“平台版本”一直显示省略号或异常

先确认界面显示的具体内容:

  1. 显示 …:说明探测 Future 还没返回,通常是异常被吞在未捕获的异步错误里;
  2. 显示 MissingPluginException(...):说明通道没有原生实现,这是本库在 OHOS 上的预期结果;
  3. 显示具体版本号:说明有原生实现接管了通道,与本库的适配方式不符。

本库的 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 非空时用错误卡片展示原始异常,同时展示“当前语言文件”路径。


相关链接

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐