HarmonyOS APP开发:智能翻译应用端到端构建

举报
Jack20 发表于 2026/06/21 14:28:33 2026/06/21
【摘要】 HarmonyOS APP开发:智能翻译应用端到端构建核心要点:基于HarmonyOS AI能力构建支持文本翻译、语音翻译、拍照翻译的全功能翻译应用 一、背景与动机出国旅行,菜单看不懂;看外文论文,专业术语一头雾水;跟外国同事开会,沟通全靠比划——这些场景你一定不陌生。翻译需求无处不在,但传统的翻译工具有几个硬伤:要么必须联网(流量贵、延迟高),要么功能单一(只能翻文本),要么切换麻烦(截...

HarmonyOS APP开发:智能翻译应用端到端构建

核心要点:基于HarmonyOS AI能力构建支持文本翻译、语音翻译、拍照翻译的全功能翻译应用


一、背景与动机

出国旅行,菜单看不懂;看外文论文,专业术语一头雾水;跟外国同事开会,沟通全靠比划——这些场景你一定不陌生。翻译需求无处不在,但传统的翻译工具有几个硬伤:要么必须联网(流量贵、延迟高),要么功能单一(只能翻文本),要么切换麻烦(截图→打开翻译App→粘贴→翻译)。

HarmonyOS给了我们一个全新的解题思路:端侧AI翻译。不需要网络,不需要切换App,文本、语音、拍照三种翻译模式一站式搞定。而且端侧推理意味着你的对话内容不会离开设备,隐私安全有保障。

本文将带你构建一个完整的智能翻译应用,覆盖文本翻译、语音翻译、拍照翻译三大核心功能,并加入翻译历史、离线词库、语言检测等增强特性。


二、核心原理

2.1 翻译应用架构

一个全功能翻译应用的核心是"多模态输入→统一翻译引擎→多格式输出"的架构。无论输入是文本、语音还是图片,最终都会转化为文本送入翻译引擎。

flowchart TD
    A[用户输入] --> B{输入模式}
    B -->|文本| C[文本预处理]
    B -->|语音| D[语音识别 ASR]
    B -->|拍照| E[OCR文字识别]
    
    D --> C
    E --> C
    
    C --> F[语言检测]
    F --> G{源语言}
    G -->|已识别| H[翻译引擎]
    G -->|未识别| I[语言选择器]
    I --> H
    
    H --> J[翻译结果]
    J --> K[文本展示]
    J --> L[语音播报 TTS]
    J --> M[翻译历史存储]
    
    H --> N[离线词库查询]
    N -->|命中| J
    N -->|未命中| O[端侧模型推理]
    O --> J
    
    classDef primary fill:#4F46E5,stroke:#3730A3,color:#fff
    classDef warning fill:#F59E0B,stroke:#D97706,color:#fff
    classDef error fill:#EF4444,stroke:#DC2626,color:#fff
    classDef info fill:#06B6D4,stroke:#0891B2,color:#fff
    classDef purple fill:#8B5CF6,stroke:#7C3AED,color:#fff
    
    class A,B primary
    class C,D,E info
    class F,G,I warning
    class H,N,O purple
    class J,K,L,M error

2.2 端侧翻译原理

HarmonyOS的文本翻译基于轻量级Seq2Seq模型,核心流程:

  1. 分词:将源语言文本拆分为子词单元(Subword)
  2. 编码:Encoder将子词序列编码为语义向量
  3. 解码:Decoder将语义向量解码为目标语言子词序列
  4. 后处理:拼接子词、调整语序、修正标点

端侧模型通常使用知识蒸馏技术,将大模型的能力压缩到小模型中,在保持翻译质量的同时大幅降低计算需求。

2.3 OCR翻译流程

拍照翻译的核心是OCR(光学字符识别)+ 翻译的组合:

  1. 图像预处理:裁剪、矫正、增强对比度
  2. 文字检测:定位图片中的文字区域
  3. 文字识别:将文字区域转为文本
  4. 文本翻译:将识别出的文本翻译为目标语言
  5. 结果叠加:将翻译结果叠加到原图对应位置

三、代码实战

3.1 翻译引擎核心模块

// TranslateEngine.ets - 翻译引擎核心模块
import { textTranslation } from '@kit.AIServiceKit';
import { BusinessError } from '@kit.BasicServicesKit';

// 支持的语言列表
export interface LanguageInfo {
  code: string;       // 语言代码
  name: string;       // 显示名称
  nativeName: string; // 原生名称
  flag: string;       // 国旗emoji
}

// 翻译结果
export interface TranslateResult {
  sourceText: string;
  translatedText: string;
  sourceLang: string;
  targetLang: string;
  confidence: number;
  timestamp: number;
}

// 支持的语言
export const SUPPORTED_LANGUAGES: LanguageInfo[] = [
  { code: 'zh', name: '中文', nativeName: '中文', flag: '🇨🇳' },
  { code: 'en', name: '英语', nativeName: 'English', flag: '🇺🇸' },
  { code: 'ja', name: '日语', nativeName: '日本語', flag: '🇯🇵' },
  { code: 'ko', name: '韩语', nativeName: '한국어', flag: '🇰🇷' },
  { code: 'fr', name: '法语', nativeName: 'Français', flag: '🇫🇷' },
  { code: 'de', name: '德语', nativeName: 'Deutsch', flag: '🇩🇪' },
  { code: 'es', name: '西班牙语', nativeName: 'Español', flag: '🇪🇸' },
  { code: 'ru', name: '俄语', nativeName: 'Русский', flag: '🇷🇺' },
  { code: 'pt', name: '葡萄牙语', nativeName: 'Português', flag: '🇧🇷' },
  { code: 'it', name: '意大利语', nativeName: 'Italiano', flag: '🇮🇹' }
];

export class TranslateEngine {
  private translator: textTranslation.Translator | null = null;
  private isInitialized: boolean = false;
  private currentSourceLang: string = 'zh';
  private currentTargetLang: string = 'en';
  
  // 翻译历史记录
  private history: TranslateResult[] = [];
  private readonly MAX_HISTORY = 100;
  
  // 离线词库(常用短语快速查询)
  private offlineDict: Map<string, string> = new Map();
  
  // 初始化翻译引擎
  async init(sourceLang: string = 'zh', targetLang: string = 'en'): Promise<boolean> {
    try {
      this.currentSourceLang = sourceLang;
      this.currentTargetLang = targetLang;
      
      // 检查翻译能力
      const isSupported = textTranslation.isAvailable();
      if (!isSupported) {
        console.error('[TranslateEngine] 设备不支持文本翻译');
        return false;
      }
      
      // 创建翻译器
      const config: textTranslation.TranslateConfig = {
        sourceLang: sourceLang,
        targetLang: targetLang,
        modelType: textTranslation.ModelType.LOCAL // 端侧模型
      };
      
      this.translator = await textTranslation.createTranslator(config);
      this.isInitialized = true;
      
      // 加载离线词库
      this.loadOfflineDict();
      
      console.info(`[TranslateEngine] 初始化成功: ${sourceLang}${targetLang}`);
      return true;
      
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[TranslateEngine] 初始化失败: ${err.code} - ${err.message}`);
      return false;
    }
  }
  
  // 切换语言对
  async switchLanguage(sourceLang: string, targetLang: string): Promise<boolean> {
    // 释放旧引擎
    this.release();
    // 重新初始化
    return await this.init(sourceLang, targetLang);
  }
  
  // 执行翻译
  async translate(text: string): Promise<TranslateResult> {
    if (!this.isInitialized || !this.translator) {
      throw new Error('翻译引擎未初始化');
    }
    
    const trimmedText = text.trim();
    if (!trimmedText) {
      return this.createEmptyResult();
    }
    
    // 1. 先查离线词库
    const dictResult = this.offlineDict.get(`${this.currentSourceLang}:${trimmedText}`);
    if (dictResult) {
      const result: TranslateResult = {
        sourceText: trimmedText,
        translatedText: dictResult,
        sourceLang: this.currentSourceLang,
        targetLang: this.currentTargetLang,
        confidence: 1.0,
        timestamp: Date.now()
      };
      this.addToHistory(result);
      return result;
    }
    
    try {
      // 2. 调用端侧翻译引擎
      const request: textTranslation.TranslateRequest = {
        query: trimmedText,
        sourceLang: this.currentSourceLang,
        targetLang: this.currentTargetLang
      };
      
      const response = await this.translator.translate(request);
      
      const result: TranslateResult = {
        sourceText: trimmedText,
        translatedText: response.translatedText,
        sourceLang: this.currentSourceLang,
        targetLang: this.currentTargetLang,
        confidence: 0.9, // 端侧模型暂无置信度返回
        timestamp: Date.now()
      };
      
      // 存入历史
      this.addToHistory(result);
      
      return result;
      
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[TranslateEngine] 翻译失败: ${err.code} - ${err.message}`);
      
      return {
        sourceText: trimmedText,
        translatedText: `[翻译失败] ${err.message}`,
        sourceLang: this.currentSourceLang,
        targetLang: this.currentTargetLang,
        confidence: 0,
        timestamp: Date.now()
      };
    }
  }
  
  // 批量翻译(分段翻译长文本)
  async translateBatch(texts: string[]): Promise<TranslateResult[]> {
    const results: TranslateResult[] = [];
    for (const text of texts) {
      const result = await this.translate(text);
      results.push(result);
    }
    return results;
  }
  
  // 语言检测(基于字符特征)
  detectLanguage(text: string): string {
    // 简单的基于字符范围的语言检测
    let chineseCount = 0;
    let japaneseCount = 0;
    let koreanCount = 0;
    let latinCount = 0;
    let cyrillicCount = 0;
    
    for (const char of text) {
      const code = char.charCodeAt(0);
      if (code >= 0x4E00 && code <= 0x9FFF) chineseCount++;
      else if (code >= 0x3040 && code <= 0x309F) japaneseCount++; // 平假名
      else if (code >= 0x30A0 && code <= 0x30FF) japaneseCount++; // 片假名
      else if (code >= 0xAC00 && code <= 0xD7AF) koreanCount++;
      else if (code >= 0x0400 && code <= 0x04FF) cyrillicCount++;
      else if ((code >= 0x0041 && code <= 0x007A) || (code >= 0x00C0 && code <= 0x024F)) latinCount++;
    }
    
    const total = chineseCount + japaneseCount + koreanCount + latinCount + cyrillicCount;
    if (total === 0) return 'en';
    
    if (chineseCount / total > 0.3) return 'zh';
    if (japaneseCount / total > 0.2) return 'ja';
    if (koreanCount / total > 0.2) return 'ko';
    if (cyrillicCount / total > 0.3) return 'ru';
    
    return 'en'; // 默认英语
  }
  
  // 加载离线词库
  private loadOfflineDict(): void {
    // 常用短语快速翻译(避免每次都调用AI模型)
    const commonPhrases: Array<[string, string, string]> = [
      ['zh', '你好', 'Hello'],
      ['zh', '谢谢', 'Thank you'],
      ['zh', '再见', 'Goodbye'],
      ['zh', '对不起', 'Sorry'],
      ['zh', '多少钱', 'How much'],
      ['zh', '在哪里', 'Where is'],
      ['zh', '我不懂', 'I don\'t understand'],
      ['zh', '请帮忙', 'Please help'],
      ['en', 'Hello', '你好'],
      ['en', 'Thank you', '谢谢'],
      ['en', 'Goodbye', '再见'],
      ['en', 'Sorry', '对不起'],
      ['ja', 'こんにちは', '你好'],
      ['ja', 'ありがとう', '谢谢'],
      ['ko', '안녕하세요', '你好'],
      ['ko', '감사합니다', '谢谢']
    ];
    
    for (const [lang, source, target] of commonPhrases) {
      this.offlineDict.set(`${lang}:${source}`, target);
    }
  }
  
  // 添加到历史
  private addToHistory(result: TranslateResult): void {
    this.history.unshift(result);
    if (this.history.length > this.MAX_HISTORY) {
      this.history.pop();
    }
  }
  
  // 获取历史记录
  getHistory(): TranslateResult[] {
    return [...this.history];
  }
  
  // 清空历史
  clearHistory(): void {
    this.history = [];
  }
  
  // 创建空结果
  private createEmptyResult(): TranslateResult {
    return {
      sourceText: '',
      translatedText: '',
      sourceLang: this.currentSourceLang,
      targetLang: this.currentTargetLang,
      confidence: 0,
      timestamp: Date.now()
    };
  }
  
  // 交换源语言和目标语言
  async swapLanguages(): Promise<boolean> {
    const tempLang = this.currentSourceLang;
    this.currentSourceLang = this.currentTargetLang;
    this.currentTargetLang = tempLang;
    return await this.switchLanguage(this.currentSourceLang, this.currentTargetLang);
  }
  
  // 释放资源
  release(): void {
    if (this.translator) {
      this.translator.release();
      this.translator = null;
      this.isInitialized = false;
    }
  }
}

3.2 OCR拍照翻译模块

// OCRTranslator.ets - OCR拍照翻译模块
import { textRecognition } from '@kit.AIServiceKit';
import { image } from '@kit.ImageKit';
import { camera } from '@kit.CameraKit';

// OCR识别结果
export interface OCRResult {
  text: string;              // 识别出的完整文本
  blocks: OCRTextBlock[];    // 文本块列表
  sourceLang: string;        // 检测到的语言
}

// 文本块
export interface OCRTextBlock {
  text: string;              // 文本内容
  bounds: OCRBounds;         // 文本区域边界
  confidence: number;        // 识别置信度
  translatedText?: string;   // 翻译结果
}

// 文本区域边界
export interface OCRBounds {
  left: number;
  top: number;
  right: number;
  bottom: number;
}

export class OCRTranslator {
  private recognizer: textRecognition.TextRecognition | null = null;
  private translateEngine: TranslateEngine | null = null;
  
  // 初始化OCR引擎
  async init(translateEngine: TranslateEngine): Promise<boolean> {
    this.translateEngine = translateEngine;
    
    try {
      const isSupported = textRecognition.isAvailable();
      if (!isSupported) {
        console.error('[OCRTranslator] 设备不支持文字识别');
        return false;
      }
      
      // 创建OCR识别器
      const config: textRecognition.TextRecognitionConfig = {
        modelType: textRecognition.ModelType.LOCAL
      };
      
      this.recognizer = await textRecognition.createTextRecognition(config);
      console.info('[OCRTranslator] OCR引擎初始化成功');
      return true;
      
    } catch (error) {
      console.error(`[OCRTranslator] 初始化失败: ${JSON.stringify(error)}`);
      return false;
    }
  }
  
  // 识别图片中的文字
  async recognizeText(pixelMap: image.PixelMap): Promise<OCRResult> {
    if (!this.recognizer) {
      throw new Error('OCR引擎未初始化');
    }
    
    try {
      // 执行OCR识别
      const result = await this.recognizer.recognize(pixelMap);
      
      // 解析识别结果
      const blocks: OCRTextBlock[] = [];
      let fullText = '';
      
      if (result.textBlocks) {
        for (const block of result.textBlocks) {
          const textBlock: OCRTextBlock = {
            text: block.text || '',
            bounds: {
              left: block.bounds?.left || 0,
              top: block.bounds?.top || 0,
              right: block.bounds?.right || 0,
              bottom: block.bounds?.bottom || 0
            },
            confidence: block.confidence || 0
          };
          
          blocks.push(textBlock);
          fullText += textBlock.text + '\n';
        }
      }
      
      // 检测语言
      const sourceLang = this.translateEngine?.detectLanguage(fullText) || 'en';
      
      return {
        text: fullText.trim(),
        blocks,
        sourceLang
      };
      
    } catch (error) {
      console.error(`[OCRTranslator] 识别失败: ${JSON.stringify(error)}`);
      return { text: '', blocks: [], sourceLang: 'en' };
    }
  }
  
  // 识别并翻译
  async recognizeAndTranslate(pixelMap: image.PixelMap): Promise<OCRResult> {
    // 1. OCR识别
    const ocrResult = await this.recognizeText(pixelMap);
    
    if (!ocrResult.text || !this.translateEngine) {
      return ocrResult;
    }
    
    // 2. 切换到检测到的语言
    if (ocrResult.sourceLang !== 'en') {
      await this.translateEngine.switchLanguage(ocrResult.sourceLang, 'zh');
    }
    
    // 3. 逐块翻译
    for (const block of ocrResult.blocks) {
      if (block.text.trim()) {
        const translateResult = await this.translateEngine.translate(block.text);
        block.translatedText = translateResult.translatedText;
      }
    }
    
    return ocrResult;
  }
  
  // 释放资源
  release(): void {
    if (this.recognizer) {
      this.recognizer.release();
      this.recognizer = null;
    }
  }
}

3.3 完整翻译应用UI

// SmartTranslatePage.ets - 智能翻译应用主页面
import { camera } from '@kit.CameraKit';
import { picker } from '@kit.CoreFileKit';

@Entry
@Component
struct SmartTranslatePage {
  @State sourceText: string = '';
  @State translatedText: string = '';
  @State sourceLang: LanguageInfo = SUPPORTED_LANGUAGES[0]; // 中文
  @State targetLang: LanguageInfo = SUPPORTED_LANGUAGES[1]; // 英语
  @State isTranslating: boolean = false;
  @State currentMode: 'text' | 'voice' | 'camera' = 'text';
  @State historyList: TranslateResult[] = [];
  @State showLanguagePicker: boolean = false;
  @State pickerType: 'source' | 'target' = 'source';
  @State ocrResult: OCRResult | null = null;
  @State voiceText: string = '';
  
  // 引擎实例
  private translateEngine: TranslateEngine = new TranslateEngine();
  private ocrTranslator: OCRTranslator = new OCRTranslator();
  private asr: SpeechRecognizer = new SpeechRecognizer();
  private tts: SpeechSynthesizer = new SpeechSynthesizer();
  
  async aboutToAppear() {
    // 初始化翻译引擎
    await this.translateEngine.init(this.sourceLang.code, this.targetLang.code);
    await this.ocrTranslator.init(this.translateEngine);
    
    // 初始化语音
    await this.asr.init({
      onResult: (text: string, isFinal: boolean) => {
        this.voiceText = text;
        if (isFinal) {
          this.sourceText = text;
          this.doTranslate();
        }
      },
      onError: (error: string) => {
        console.error(`语音识别错误: ${error}`);
      },
      onVolumeChange: () => {}
    });
    
    await this.tts.init();
  }
  
  build() {
    Column() {
      // 顶部语言选择栏
      this.LanguageBar();
      
      // 模式切换
      this.ModeTabs();
      
      // 主内容区
      if (this.currentMode === 'text') {
        this.TextTranslateView();
      } else if (this.currentMode === 'voice') {
        this.VoiceTranslateView();
      } else {
        this.CameraTranslateView();
      }
      
      // 翻译历史
      this.HistorySection();
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0A0A1A')
  }
  
  // 语言选择栏
  @Builder LanguageBar() {
    Row() {
      // 源语言
      Text(`${this.sourceLang.flag} ${this.sourceLang.name}`)
        .fontSize(15)
        .fontColor('#E0E7FF')
        .backgroundColor('#1E1B4B')
        .borderRadius(20)
        .padding({ left: 14, right: 14, top: 8, bottom: 8 })
        .onClick(() => {
          this.pickerType = 'source';
          this.showLanguagePicker = true;
        })
      
      // 交换按钮
      Text('⇄')
        .fontSize(22)
        .fontColor('#4F46E5')
        .margin({ left: 12, right: 12 })
        .onClick(() => this.swapLanguages())
      
      // 目标语言
      Text(`${this.targetLang.flag} ${this.targetLang.name}`)
        .fontSize(15)
        .fontColor('#E0E7FF')
        .backgroundColor('#1E1B4B')
        .borderRadius(20)
        .padding({ left: 14, right: 14, top: 8, bottom: 8 })
        .onClick(() => {
          this.pickerType = 'target';
          this.showLanguagePicker = true;
        })
    }
    .width('100%')
    .justifyContent(FlexAlign.Center)
    .padding({ top: 16, bottom: 8 })
    
    // 语言选择弹窗
    if (this.showLanguagePicker) {
      this.LanguagePickerDialog();
    }
  }
  
  // 语言选择弹窗
  @Builder LanguagePickerDialog() {
    Column() {
      Row() {
        Text('选择语言')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')
        
        Blank()
        
        Text('✕')
          .fontSize(18)
          .fontColor('#9CA3AF')
          .onClick(() => { this.showLanguagePicker = false; })
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 12, bottom: 8 })
      
      List() {
        ForEach(SUPPORTED_LANGUAGES, (lang: LanguageInfo) => {
          ListItem() {
            Row() {
              Text(`${lang.flag}  ${lang.name}`)
                .fontSize(15)
                .fontColor('#E0E7FF')
              
              Blank()
              
              Text(lang.nativeName)
                .fontSize(13)
                .fontColor('#9CA3AF')
            }
            .width('100%')
            .padding(14)
            .borderRadius(8)
            .backgroundColor(
              (this.pickerType === 'source' && lang.code === this.sourceLang.code) ||
              (this.pickerType === 'target' && lang.code === this.targetLang.code)
                ? '#312E81' : '#1A1A2E'
            )
            .margin({ bottom: 4 })
            .onClick(() => {
              if (this.pickerType === 'source') {
                this.sourceLang = lang;
              } else {
                this.targetLang = lang;
              }
              this.showLanguagePicker = false;
              this.updateEngineLanguage();
            })
          }
        })
      }
      .width('100%')
      .height(300)
      .padding({ left: 12, right: 12 })
    }
    .width('90%')
    .backgroundColor('#0F0F2A')
    .borderRadius(16)
    .shadow({ radius: 20, color: '#00000060', offsetY: 8 })
    .position({ x: '5%', y: 80 })
    .zIndex(10)
  }
  
  // 模式切换标签
  @Builder ModeTabs() {
    Row() {
      ForEach([
        { mode: 'text' as const, icon: '📝', label: '文本' },
        { mode: 'voice' as const, icon: '🎤', label: '语音' },
        { mode: 'camera' as const, icon: '📷', label: '拍照' }
      ], (item: { mode: 'text' | 'voice' | 'camera'; icon: string; label: string }) => {
        Column() {
          Text(item.icon)
            .fontSize(20)
          Text(item.label)
            .fontSize(12)
            .fontColor(this.currentMode === item.mode ? '#FFFFFF' : '#9CA3AF')
            .margin({ top: 2 })
        }
        .width('30%')
        .padding({ top: 8, bottom: 8 })
        .borderRadius(12)
        .backgroundColor(this.currentMode === item.mode ? '#4F46E5' : '#1A1A2E')
        .onClick(() => { this.currentMode = item.mode; })
      }
    }
    .width('90%')
    .justifyContent(FlexAlign.SpaceAround)
    .margin({ top: 8, bottom: 12 })
  }
  
  // 文本翻译视图
  @Builder TextTranslateView() {
    Column() {
      // 源文本输入
      Column() {
        Row() {
          Text(this.sourceLang.name)
            .fontSize(12)
            .fontColor('#9CA3AF')
          
          Blank()
          
          if (this.sourceText) {
            Text('清空')
              .fontSize(12)
              .fontColor('#4F46E5')
              .onClick(() => {
                this.sourceText = '';
                this.translatedText = '';
              })
          }
        }
        .width('100%')
        
        TextArea({ text: this.sourceText, placeholder: '输入要翻译的文本...' })
          .width('100%')
          .height(120)
          .fontSize(16)
          .fontColor('#FFFFFF')
          .backgroundColor(Color.Transparent)
          .placeholderColor('#6B7280')
          .onChange((value: string) => {
            this.sourceText = value;
          })
        
        // 翻译按钮
        Button(this.isTranslating ? '翻译中...' : '翻译')
          .width('100%')
          .height(44)
          .fontSize(16)
          .fontColor('#FFFFFF')
          .backgroundColor('#4F46E5')
          .borderRadius(22)
          .margin({ top: 8 })
          .enabled(!this.isTranslating && this.sourceText.trim().length > 0)
          .onClick(() => this.doTranslate())
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#1A1A2E')
      .borderRadius(16)
      
      // 翻译结果
      if (this.translatedText) {
        Column() {
          Row() {
            Text(this.targetLang.name)
              .fontSize(12)
              .fontColor('#9CA3AF')
            
            Blank()
            
            // 复制按钮
            Text('📋 复制')
              .fontSize(12)
              .fontColor('#4F46E5')
              .onClick(() => this.copyResult())
            
            // 朗读按钮
            Text('🔊 朗读')
              .fontSize(12)
              .fontColor('#4F46E5')
              .margin({ left: 12 })
              .onClick(() => this.speakResult())
          }
          .width('100%')
          
          Text(this.translatedText)
            .width('100%')
            .fontSize(17)
            .fontColor('#E0E7FF')
            .lineHeight(26)
            .margin({ top: 8 })
        }
        .width('100%')
        .padding(16)
        .backgroundColor('#1E1B4B')
        .borderRadius(16)
        .margin({ top: 12 })
      }
    }
    .width('100%')
    .padding({ left: 16, right: 16 })
  }
  
  // 语音翻译视图
  @Builder VoiceTranslateView() {
    Column() {
      // 语音识别结果
      if (this.voiceText) {
        Text(this.voiceText)
          .fontSize(16)
          .fontColor('#E0E7FF')
          .padding(16)
          .backgroundColor('#1A1A2E')
          .borderRadius(12)
          .width('100%')
      }
      
      // 翻译结果
      if (this.translatedText) {
        Text(this.translatedText)
          .fontSize(18)
          .fontWeight(FontWeight.Medium)
          .fontColor('#FFFFFF')
          .padding(16)
          .backgroundColor('#1E1B4B')
          .borderRadius(12)
          .width('100%')
          .margin({ top: 12 })
      }
      
      Blank()
      
      // 麦克风按钮
      Column() {
        Circle()
          .width(80)
          .height(80)
          .fill('#4F46E5')
          .shadow({ radius: 16, color: '#4F46E580', offsetY: 4 })
        
        Text('🎤')
          .fontSize(32)
          .fontColor('#FFFFFF')
          .position({ x: '50%', y: '50%' })
          .translate({ x: '-50%', y: '-50%' })
      }
      .width(80)
      .height(80)
      .margin({ bottom: 40 })
      .onClick(() => {
        if (this.asr.getIsListening()) {
          this.asr.stopListening();
        } else {
          this.asr.startListening();
        }
      })
    }
    .width('100%')
    .layoutWeight(1)
    .padding({ left: 16, right: 16 })
    .alignItems(HorizontalAlign.Center)
  }
  
  // 拍照翻译视图
  @Builder CameraTranslateView() {
    Column() {
      // OCR识别结果
      if (this.ocrResult) {
        Column() {
          Text('识别结果')
            .fontSize(14)
            .fontColor('#9CA3AF')
            .width('100%')
          
          Text(this.ocrResult.text)
            .fontSize(15)
            .fontColor('#E0E7FF')
            .width('100%')
            .margin({ top: 8 })
            .maxLines(5)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
        }
        .width('100%')
        .padding(16)
        .backgroundColor('#1A1A2E')
        .borderRadius(12)
        
        // 翻译结果
        Column() {
          Text('翻译结果')
            .fontSize(14)
            .fontColor('#9CA3AF')
            .width('100%')
          
          ForEach(this.ocrResult.blocks, (block: OCRTextBlock) => {
            if (block.translatedText) {
              Row() {
                Text(block.text)
                  .fontSize(13)
                  .fontColor('#9CA3AF')
                  .layoutWeight(1)
                
                Text(' → ')
                  .fontSize(13)
                  .fontColor('#4F46E5')
                
                Text(block.translatedText)
                  .fontSize(13)
                  .fontColor('#E0E7FF')
                  .layoutWeight(1)
              }
              .width('100%')
              .padding({ top: 4, bottom: 4 })
            }
          })
        }
        .width('100%')
        .padding(16)
        .backgroundColor('#1E1B4B')
        .borderRadius(12)
        .margin({ top: 12 })
      }
      
      Blank()
      
      // 拍照/选图按钮
      Row() {
        Button('📷 拍照')
          .height(44)
          .fontSize(14)
          .fontColor('#FFFFFF')
          .backgroundColor('#4F46E5')
          .borderRadius(22)
          .layoutWeight(1)
          .onClick(() => this.openCamera())
        
        Button('🖼 选图')
          .height(44)
          .fontSize(14)
          .fontColor('#FFFFFF')
          .backgroundColor('#312E81')
          .borderRadius(22)
          .layoutWeight(1)
          .margin({ left: 12 })
          .onClick(() => this.pickImage())
      }
      .width('100%')
      .padding({ left: 16, right: 16, bottom: 20 })
    }
    .width('100%')
    .layoutWeight(1)
    .padding({ left: 16, right: 16 })
  }
  
  // 翻译历史
  @Builder HistorySection() {
    if (this.historyList.length > 0) {
      Column() {
        Row() {
          Text('翻译历史')
            .fontSize(14)
            .fontWeight(FontWeight.Bold)
            .fontColor('#FFFFFF')
          
          Blank()
          
          Text('清空')
            .fontSize(12)
            .fontColor('#EF4444')
            .onClick(() => {
              this.translateEngine.clearHistory();
              this.historyList = [];
            })
        }
        .width('100%')
        
        List() {
          ForEach(this.historyList.slice(0, 5), (item: TranslateResult) => {
            ListItem() {
              Row() {
                Column() {
                  Text(item.sourceText)
                    .fontSize(13)
                    .fontColor('#E0E7FF')
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                  Text(item.translatedText)
                    .fontSize(12)
                    .fontColor('#9CA3AF')
                    .maxLines(1)
                    .textOverflow({ overflow: TextOverflow.Ellipsis })
                    .margin({ top: 2 })
                }
                .alignItems(HorizontalAlign.Start)
                .layoutWeight(1)
              }
              .width('100%')
              .padding(10)
              .backgroundColor('#1A1A2E')
              .borderRadius(8)
              .margin({ bottom: 4 })
              .onClick(() => {
                this.sourceText = item.sourceText;
                this.translatedText = item.translatedText;
              })
            }
          })
        }
        .width('100%')
        .height(160)
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 8 })
    }
  }
  
  // 执行翻译
  private async doTranslate() {
    if (!this.sourceText.trim()) return;
    
    this.isTranslating = true;
    try {
      const result = await this.translateEngine.translate(this.sourceText);
      this.translatedText = result.translatedText;
      this.historyList = this.translateEngine.getHistory();
    } catch (error) {
      this.translatedText = '翻译失败,请重试';
    }
    this.isTranslating = false;
  }
  
  // 交换语言
  private async swapLanguages() {
    const temp = this.sourceLang;
    this.sourceLang = this.targetLang;
    this.targetLang = temp;
    await this.updateEngineLanguage();
    
    // 交换文本
    const tempText = this.sourceText;
    this.sourceText = this.translatedText;
    this.translatedText = tempText;
  }
  
  // 更新引擎语言
  private async updateEngineLanguage() {
    await this.translateEngine.switchLanguage(this.sourceLang.code, this.targetLang.code);
  }
  
  // 复制结果
  private copyResult() {
    // 实际项目中使用剪贴板API
    console.info(`复制: ${this.translatedText}`);
  }
  
  // 朗读结果
  private speakResult() {
    this.tts.speak(this.translatedText);
  }
  
  // 打开相机
  private openCamera() {
    // 实际项目中使用相机API
    console.info('打开相机');
  }
  
  // 选择图片
  private async pickImage() {
    // 实际项目中使用PhotoViewPicker
    console.info('选择图片');
  }
  
  aboutToDisappear() {
    this.translateEngine.release();
    this.ocrTranslator.release();
    this.asr.release();
    this.tts.release();
  }
}

四、踩坑与注意事项

4.1 翻译引擎的语言对限制

不是所有语言对都支持端侧翻译。HarmonyOS的端侧翻译模型目前主要支持中英、中日、中韩等高频语言对。小语种翻译可能需要回退到云端API:

// 检查语言对是否支持
const supportedPairs = textTranslation.getSupportedLanguages();
const isSupported = supportedPairs.some(
  pair => pair.sourceLang === sourceLang && pair.targetLang === targetLang
);

if (!isSupported) {
  // 降级到云端翻译
  config.modelType = textTranslation.ModelType.CLOUD;
}

4.2 OCR识别的准确率问题

OCR的准确率受多种因素影响:

  • 图片质量:模糊、倾斜、低对比度的图片识别率低
  • 文字大小:过小或过大的文字识别困难
  • 背景干扰:复杂背景会干扰文字检测
  • 语言混合:中英混合文本容易识别错误

建议在拍照翻译时加入图像预处理步骤:灰度化→二值化→去噪→倾斜矫正。

4.3 翻译结果的缓存策略

相同的文本重复翻译是浪费资源。建议实现翻译缓存:

// 翻译缓存(内存+持久化)
private translateCache: Map<string, TranslateResult> = new Map();

async translate(text: string): Promise<TranslateResult> {
  const cacheKey = `${this.currentSourceLang}:${this.currentTargetLang}:${text}`;
  
  // 先查缓存
  const cached = this.translateCache.get(cacheKey);
  if (cached) return cached;
  
  // 再调引擎
  const result = await this.translator.translate(request);
  this.translateCache.set(cacheKey, result);
  return result;
}

4.4 长文本翻译的分段处理

端侧翻译模型对输入长度有限制(通常256-512个token),长文本需要分段翻译:

async translateLongText(text: string): Promise<string> {
  // 按句子分段
  const sentences = text.split(/(?<=[。!?.!?])\s*/);
  
  const results: string[] = [];
  for (const sentence of sentences) {
    if (sentence.trim()) {
      const result = await this.translate(sentence);
      results.push(result.translatedText);
    }
  }
  
  return results.join(' ');
}

4.5 权限管理

翻译应用可能需要以下权限:

  • ohos.permission.INTERNET:云端翻译(如果使用)
  • ohos.permission.MICROPHONE:语音翻译
  • ohos.permission.CAMERA:拍照翻译
  • ohos.permission.READ_IMAGEVIDEO:选择图片翻译

务必在module.json5中声明所有需要的权限,并在运行时逐一请求。


五、HarmonyOS 6适配

5.1 API变更

变更项 HarmonyOS 5 HarmonyOS 6
翻译API textTranslation textTranslation(兼容)
端侧语言对 中英/中日/中韩 新增法德西等8种语言对
OCR textRecognition textRecognition(兼容)
实时翻译 不支持 新增realTimeTranslation API
翻译质量 基础质量 端侧模型升级,质量提升30%

5.2 实时翻译

HarmonyOS 6最激动人心的新功能是实时翻译。它可以在通话或会议中实时翻译对方的语音:

// HarmonyOS 6 新增
import { realTimeTranslation } from '@kit.AIServiceKit';

const rtEngine = realTimeTranslation.createEngine({
  sourceLang: 'en',
  targetLang: 'zh',
  mode: realTimeTranslation.Mode.SPEECH_TO_SPEECH
});

rtEngine.on('translatedText', (text: string) => {
  // 实时显示翻译文本
});

rtEngine.on('translatedAudio', (audio: ArrayBuffer) => {
  // 播放翻译后的语音
});

5.3 迁移要点

  1. 新增语言对:HarmonyOS 6扩展了端侧翻译的语言覆盖,建议更新语言选择列表
  2. 模型升级:新版本的端侧翻译模型质量显著提升,建议检查并更新模型
  3. 实时翻译集成:利用新API实现通话翻译、会议翻译等场景

六、总结

本文从零构建了一个全功能的HarmonyOS智能翻译应用,核心知识点如下:

智能翻译应用
├── 翻译引擎
│   ├── textTranslation端侧翻译
│   ├── 离线词库快速查询
│   ├── 语言检测(基于字符特征)
│   ├── 翻译历史管理
│   └── 语言对切换与交换
├── OCR拍照翻译
│   ├── textRecognition文字识别
│   ├── 文本块定位与提取
│   ├── 逐块翻译与结果叠加
│   └── 图像预处理建议
├── 语音翻译
│   ├── ASR语音识别
│   ├── 实时翻译流程
│   └── TTS结果播报
├── UI交互
│   ├── 三模式切换(文本/语音/拍照)
│   ├── 语言选择弹窗
│   ├── 翻译结果展示与操作
│   └── 翻译历史
└── 工程化
    ├── 翻译缓存策略
    ├── 长文本分段处理
    ├── 语言对兼容性检查
    ├── 多权限管理
    └── HarmonyOS 6实时翻译

关键收获

  • 端侧翻译的核心优势是隐私保护和低延迟,但语言对覆盖有限
  • 离线词库是提升常用短语翻译速度的利器
  • OCR+翻译的组合让拍照翻译成为可能,但要注意图片质量对准确率的影响
  • 翻译缓存避免重复计算,长文本必须分段处理
  • HarmonyOS 6的实时翻译将开启"无障碍沟通"新时代

翻译应用是AI最实用的落地场景之一。随着端侧模型能力的持续增强,离线翻译的质量正在快速追赶云端翻译。而实时翻译的出现,更是让语言不再是沟通的障碍。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。