HarmonyOS NEXT 实战:从零开发一个多语言翻译助手 App

本文记录使用 HarmonyOS NEXT (API 23) + ArkTS/ArkUI 从零开发一款完整翻译应用的全过程,包含架构设计、核心代码、编译排坑全记录。


一、前言

最近在钻研 HarmonyOS NEXT 应用开发,为了练手决定做一个翻译助手 App。市面上翻译软件很多,但自己动手做一个不仅能加深对 ArkTS/ArkUI 的理解,还能积累 Stage 模型下的完整开发经验。

本文你能学到

  • HarmonyOS NEXT 项目结构拆解
  • ArkTS 声明式 UI 开发实践(@Entry / @Component / @Builder / @State / @Prop)
  • 数据持久化(Preferences API)的正确用法
  • HTTP 网络请求 + 多 API 容灾策略
  • ArkTS 编译错误的排查与修复(含 8 个典型错误)
  • HarmonyOS SVG 图标的使用技巧

二、项目概况

功能清单

模块 功能
🌐 翻译核心 支持中/英/日/韩/法/德/西/俄/葡/意/泰/越 12 种语言互译
🔄 语言自动检测 输入文本自动识别语言
🕐 翻译历史 保存最近 200 条记录,支持搜索
⭐ 收藏夹 标记常用翻译
📋 一键复制 翻译结果复制到系统剪贴板
🔀 语言互换 源语言与目标语言一键交换

技术栈

  • 语言/框架:ArkTS + ArkUI 声明式 UI
  • SDK 版本:API 23 (HarmonyOS NEXT)
  • 数据持久化@kit.ArkDatapreferences
  • 网络请求@kit.NetworkKithttp
  • 翻译 API:LibreTranslate(主)+ MyMemory(备用)
  • 应用模型:Stage 模型

三、项目结构

MyApplication/
├── AppScope/                         # 应用全局配置
│   └── app.json5                     # bundleName、图标、版本
├── entry/src/main/
│   ├── ets/
│   │   ├── entryability/             # UIAbility 生命周期入口
│   │   ├── model/                    # 数据模型
│   │   │   └── TranslationEntry.ets  # 翻译记录 + 语言定义
│   │   ├── data/                     # 数据持久化层
│   │   │   └── PreferencesManager.ets
│   │   ├── service/                  # 业务服务层
│   │   │   └── TranslationService.ets
│   │   ├── utils/                    # 工具类
│   │   │   └── SystemUtils.ets       # 剪贴板 + 时间格式化
│   │   └── pages/
│   │       └── Index.ets             # 主页面(三合一)
│   ├── resources/                    # 资源文件
│   │   └── base/
│   │       ├── element/              # 颜色、字体、字符串
│   │       └── media/                # SVG 图标
│   └── module.json5                  # 模块配置 + 权限
└── build-profile.json5               # 构建配置

四、核心代码实现

4.1 数据模型

定义翻译记录的数据结构和 12 种支持的语言:

// model/TranslationEntry.ets
export interface TranslationEntry {
  id: string;
  sourceLang: string;
  targetLang: string;
  sourceText: string;
  targetText: string;
  timestamp: number;
  isFavorite: boolean;
}

export interface LanguageOption {
  code: string;
  name: string;
}

export const LANGUAGES: LanguageOption[] = [
  { code: 'zh', name: '中文' },
  { code: 'en', name: '英语' },
  { code: 'ja', name: '日语' },
  { code: 'ko', name: '韩语' },
  { code: 'fr', name: '法语' },
  { code: 'de', name: '德语' },
  { code: 'es', name: '西班牙语' },
  { code: 'ru', name: '俄语' },
  { code: 'pt', name: '葡萄牙语' },
  { code: 'it', name: '意大利语' },
  { code: 'th', name: '泰语' },
  { code: 'vi', name: '越南语' },
];

4.2 翻译服务层

双 API 容灾设计,主用 LibreTranslate(开源免费),备用 MyMemory:

// service/TranslationService.ets
export class TranslationService {
  private static readonly LIBRE_TRANSLATE_URL = 'https://libretranslate.com/translate';
  private static readonly MY_MEMORY_URL = 'https://api.mymemory.translated.net/get';

  async translate(text: string, sourceLang: string, targetLang: string): Promise<string> {
    if (!text.trim()) return '';

    // 方案一:LibreTranslate
    try {
      const result = await this.translateLibre(text, sourceLang, targetLang);
      if (result) return result;
    } catch (err) {
      // 自动切换到 MyMemory
    }

    // 方案二:MyMemory
    try {
      const result = await this.translateMyMemory(text, sourceLang, targetLang);
      if (result) return result;
    } catch (err) {
      throw new Error('所有翻译服务均不可用');
    }
  }

  // 语言自动检测
  static detectLanguage(text: string): string {
    for (let i = 0; i < text.length; i++) {
      const code = text.charCodeAt(i);
      if (code >= 0x4e00 && code <= 0x9fff) return 'zh';
      if ((code >= 0x3040 && code <= 0x309f) || 
          (code >= 0x30a0 && code <= 0x30ff)) return 'ja';
      if ((code >= 0xac00 && code <= 0xd7af) || 
          (code >= 0x1100 && code <= 0x11ff)) return 'ko';
    }
    return 'en';
  }
}

注意:使用 JSON.parse() 时在 ArkTS 中必须加上类型断言,否则会报 arkts-no-any-unknown 错误:

interface LibreTranslateResponse {
  translatedText: string;
}

const json = JSON.parse(data.result as string) as LibreTranslateResponse;

4.3 数据持久化层

使用 HarmonyOS 的 Preferences API 存储翻译历史:

// data/PreferencesManager.ets
export class PreferencesManager {
  private pref: preferences.Preferences | null = null;

  async init(context: Context): Promise<void> {
    this.pref = await preferences.getPreferences(context, STORE_NAME);
  }

  async addEntry(sourceLang: string, targetLang: string,
    sourceText: string, targetText: string): Promise<TranslationEntry> {
    const entries = await this.getAllEntries();
    const newEntry: TranslationEntry = {
      id: generateId(),
      sourceLang, targetLang,
      sourceText, targetText,
      timestamp: Date.now(),
      isFavorite: false,
    };
    entries.unshift(newEntry);
    if (entries.length > 200) entries.length = 200;
    await this.saveEntries(entries);
    return newEntry;
  }

  async searchEntries(keyword: string): Promise<TranslationEntry[]> {
    const entries = await this.getAllEntries();
    const kw = keyword.toLowerCase();
    return entries.filter(e =>
      e.sourceText.toLowerCase().includes(kw) ||
      e.targetText.toLowerCase().includes(kw)
    );
  }
}

4.4 主页面 UI 架构

采用三 Tab 设计:翻译 / 历史 / 收藏。使用 @Builder 拆分为多个构建函数:

// pages/Index.ets
@Entry
@Component
struct Index {
  @State currentTab: number = 0;
  @State sourceText: string = '';
  @State translatedText: string = '';
  @State isTranslating: boolean = false;
  @State showLangPicker: boolean = false;

  build() {
    Column() {
      this.buildTopBar();           // 顶部 Tab 导航

      if (this.currentTab === 0) {
        this.buildTranslationPage(); // 翻译页面
      } else if (this.currentTab === 1) {
        HistoryTab({...});          // 历史记录(子组件)
      } else if (this.currentTab === 2) {
        FavoritesTab({...});        // 收藏夹(子组件)
      }
    }
    // 语言选择弹窗
    .bindContentCover($$this.showLangPicker, this.buildLangPicker())
  }

  @Builder
  buildLanguageBar() { /* 源语言 ↔ 目标语言选择栏 */ }

  @Builder
  buildInputArea() { /* 文本输入区域 */ }

  @Builder
  buildTranslateButton() { /* 翻译按钮 + Loading 动画 */ }

  @Builder
  buildResultArea() { /* 翻译结果 + 复制/朗读按钮 */ }

  @Builder
  buildLangPicker() { /* 语言选择底部弹窗 */ }
}

五、SVG 图标的最佳实践

HarmonyOS 的 Image 组件支持 .fillColor() 方法动态着色,但前提是 SVG 文件中不能内嵌 fill 属性

正确的做法(无 fill):

<!-- ic_arrow_down.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
</svg>
Image($r('app.media.ic_arrow_down'))
  .width(12).height(12)
  .fillColor($r('app.color.text_secondary'))  // 动态设色

错误的做法(硬编码 fill 将覆盖 .fillColor()):

<path d="..." fill="#666666"/>   <!-- 删掉 fill 属性 -->

六、编译排坑全记录(8个错误+17个警告)

这是本文最有价值的部分——我在 hvigor assembleHap 编译中踩了 8 个坑,完全解决后成功通过。

❌ 错误1:arkts-no-any-unknown

Use explicit types instead of "any", "unknown"

原因:ArkTS 不允许 JSON.parse() 的隐式 any 返回值。

解决:定义接口 + 类型断言:

interface LibreTranslateResponse { translatedText: string; }
const json = JSON.parse(data.result as string) as LibreTranslateResponse;

❌ 错误2:数字前缀污染

.bindContentCover(344this.showLangPicker, ...)  ← 多出来的 344

原因:之前修复脚本的写入异常导致 $$ 被替换为 344

解决:回退为 $$this.showLangPicker$$ 是 ArkUI 双向绑定语法)。

❌ 错误3:Promise → string 类型不匹配

Conversion of type 'Promise<ValueType>' to type 'string' may be a mistake

原因preferences.get() 返回 ValueType,需要显式转型。

解决

const entries: TranslationEntry[] = JSON.parse(jsonStr) as TranslationEntry[];

❌ 错误4:bindContentCover 参数类型错误

Argument of type 'number' is not assignable to parameter of type 'boolean'

原因:同上,344 数字被传入 isShow: boolean 参数。

解决:恢复为 $$this.showLangPicker

❌ 错误5:this 不是 Context

Argument of type 'this' is not assignable to parameter of type 'Context'

原因:在 ArkUI 组件中 this 指向组件实例,不是 Context

解决:用 getContext(this) 获取正确上下文:

ClipboardUtil.copyText(getContext(this), this.translatedText);

❌ 错误6:textSelectable(true) 过时

Argument of type 'true' is not assignable to parameter of type 'TextSelectableMode'

原因:API 23 中 textSelectable 参数改为枚举类型。

解决

.textSelectable(TextSelectableMode.SELECTABLE)

❌ 错误7:私有属性不能作为组件构造参数传递

Property 'prefManager' is private and can not be initialized through the component constructor.

原因:子组件用 private 声明的属性不能从父组件传入。

解决:根据子组件角色改为 @State(有独立状态)或 @Prop(从父组件传递):

// 父组件传入
@State onSelectEntry: (entry: TranslationEntry) => void = () => {};

// 父组件传入 + 父组件数据
@Prop entry: TranslationEntry = {...};
@Prop onTap: () => void = () => {};

❌ 错误8:资源 $ 符号丢失

WARN: 'app_name' conflict, first declared.

原因$string: / $media: / $color: 中的 $ 在文件写入时丢失。

解决:使用 Python chr(36) 在字符串拼接中注入 $ 符号,逐条修复 module.json5 中的所有资源引用:

D = chr(36)  # dollar sign
content = content.replace(':module_desc', D + 'string:module_desc')
content = content.replace(':layered_image', D + 'media:layered_image')

💡 小贴士:在编写 ArkTS 文件时,$ 在 Shell/Python 中有特殊含义,最好用文件 API 直接写入而非通过 Shell 拼接。


七、module.json5 配置要点

权限声明和 Ability 配置是必填项:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "pages": "$profile:main_pages",
    "abilities": [{
      "name": "EntryAbility",
      "srcEntry": "./ets/entryability/EntryAbility.ets",
      "exported": true,
      "launchType": "singleton",
      "skills": [{
        "entities": ["entity.system.home"],
        "actions": ["ohos.want.action.home"]
      }]
    }],
    "requestPermissions": [{
      "name": "ohos.permission.INTERNET",
      "reason": "$string:permission_internet"
    }]
  }
}

注意配置的 AppScope/resourcesentry/resources 都有各自的 string.json,其中 app_name 只在 AppScope 中定义一次,否则会报 'app_name' conflict 警告。


八、HarmonyOS NEXT 开发心得

8.1 Kit 导入机制

API 23 采用新的 @kit.* 模块导入体系:

能力 导入路径
UI 组件 @kit.ArkUI
网络请求 @kit.NetworkKit
数据持久化 @kit.ArkData
系统剪贴板 @kit.BasicServicesKit(⚠️ 不是 PasteboardKit)
日志 @kit.PerformanceAnalysisKit

8.2 ArkTS 语法限制速查

限制 解决
不能有 any / unknown 始终显式声明类型或用 as 断言
对象字面量不能独立赋值 直接作为函数参数传入
FontWeight.SemiBold 不存在 FontWeight.MediumBold
FlexAlign.FlexEnd 不存在 FlexAlign.End
true 不能传给枚举参数 用枚举值如 TextSelectableMode.SELECTABLE
子组件私有属性不能接收外部传参 @State / @Prop / @Link

8.3 翻译 API 选择

实测 @kit.TranslationKit 在公开 HarmonyOS SDK 23 中不存在,采用 LibreTranslate(开源免费,可自部署)+ MyMemory API(免费但有调用限制)双保险方案是当前最优选择。


九、总结

从零开始搭建一个 HarmonyOS NEXT 应用,涉及了 Stage 模型、ArkTS 声明式 UI、数据持久化、网络请求、SVG 图标等一系列技术点。编译阶段虽然踩了不少坑,但每个错误都让我对 ArkTS 的类型系统有了更深理解。

项目代码不到 1200 行(7 个文件),实现了翻译、历史、收藏三大核心模块,具备了一定的实用价值。

在这里插入图片描述
在这里插入图片描述


如果你也在学习 HarmonyOS 开发,欢迎在评论区交流!觉得有用的话点个赞 👍 支持一下~

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐