HarmonyOS APP开发:智能翻译应用端到端构建
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模型,核心流程:
- 分词:将源语言文本拆分为子词单元(Subword)
- 编码:Encoder将子词序列编码为语义向量
- 解码:Decoder将语义向量解码为目标语言子词序列
- 后处理:拼接子词、调整语序、修正标点
端侧模型通常使用知识蒸馏技术,将大模型的能力压缩到小模型中,在保持翻译质量的同时大幅降低计算需求。
2.3 OCR翻译流程
拍照翻译的核心是OCR(光学字符识别)+ 翻译的组合:
- 图像预处理:裁剪、矫正、增强对比度
- 文字检测:定位图片中的文字区域
- 文字识别:将文字区域转为文本
- 文本翻译:将识别出的文本翻译为目标语言
- 结果叠加:将翻译结果叠加到原图对应位置
三、代码实战
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 迁移要点
- 新增语言对:HarmonyOS 6扩展了端侧翻译的语言覆盖,建议更新语言选择列表
- 模型升级:新版本的端侧翻译模型质量显著提升,建议检查并更新模型
- 实时翻译集成:利用新API实现通话翻译、会议翻译等场景
六、总结
本文从零构建了一个全功能的HarmonyOS智能翻译应用,核心知识点如下:
智能翻译应用
├── 翻译引擎
│ ├── textTranslation端侧翻译
│ ├── 离线词库快速查询
│ ├── 语言检测(基于字符特征)
│ ├── 翻译历史管理
│ └── 语言对切换与交换
├── OCR拍照翻译
│ ├── textRecognition文字识别
│ ├── 文本块定位与提取
│ ├── 逐块翻译与结果叠加
│ └── 图像预处理建议
├── 语音翻译
│ ├── ASR语音识别
│ ├── 实时翻译流程
│ └── TTS结果播报
├── UI交互
│ ├── 三模式切换(文本/语音/拍照)
│ ├── 语言选择弹窗
│ ├── 翻译结果展示与操作
│ └── 翻译历史
└── 工程化
├── 翻译缓存策略
├── 长文本分段处理
├── 语言对兼容性检查
├── 多权限管理
└── HarmonyOS 6实时翻译
关键收获:
- 端侧翻译的核心优势是隐私保护和低延迟,但语言对覆盖有限
- 离线词库是提升常用短语翻译速度的利器
- OCR+翻译的组合让拍照翻译成为可能,但要注意图片质量对准确率的影响
- 翻译缓存避免重复计算,长文本必须分段处理
- HarmonyOS 6的实时翻译将开启"无障碍沟通"新时代
翻译应用是AI最实用的落地场景之一。随着端侧模型能力的持续增强,离线翻译的质量正在快速追赶云端翻译。而实时翻译的出现,更是让语言不再是沟通的障碍。
- 点赞
- 收藏
- 关注作者
评论(0)