Cordova 开发鸿蒙PC应用翻译应用实现技术博客

Cordova 开发鸿蒙PC应用翻译应用实现技术博客

目录

  1. 项目概述
  2. 技术选型
  3. 功能需求分析
  4. 实现步骤
  5. 核心代码解析
  6. API 集成详解
  7. 用户体验优化
  8. 常见问题与解决方案
  9. 最佳实践
  10. 总结

项目概述

翻译应用是一个基于 Cordova 框架开发的移动应用,通过调用第三方 API 实现多语言文本翻译功能。用户只需输入文本,选择源语言和目标语言,即可快速获得准确的翻译结果。应用支持自动语言检测和 30+ 种语言的互译。

预览

image-20251121161905896

核心功能

  • 多语言支持:支持 30+ 种语言的翻译
  • 自动语言检测:系统能够自动识别输入文本的语言
  • 灵活的语言选择:支持手动指定源语言和目标语言
  • Token 管理:自动保存和加载 API Token
  • 响应式设计:完美适配移动设备和桌面浏览器
  • 用户友好界面:美观的 UI 设计和流畅的交互体验

技术选型

前端技术栈

  • HTML5:页面结构
  • CSS3:样式设计和响应式布局
  • JavaScript (ES6+):业务逻辑和 API 调用
  • Cordova:跨平台移动应用框架

API 服务

  • API 提供商:AlAPI(alapi.cn
  • API 端点https://v3.alapi.cn/api/fanyi
  • 请求方式:POST
  • 数据格式:URL-encoded form data

功能需求分析

1. 用户输入需求

  • API Token(必填)
  • 要翻译的文本(必填)
  • 源语言(可选,默认自动检测)
  • 目标语言(可选,默认英语)

2. 功能需求

  • 表单验证
  • API 调用和错误处理
  • 翻译结果展示(原文、译文、语言信息)
  • Token 本地存储
  • 加载状态提示
  • 错误信息提示

3. 用户体验需求

  • 响应式设计
  • 流畅的交互动画
  • 清晰的错误提示
  • Token 获取引导
  • 多语言名称显示

实现步骤

步骤 1: 创建页面结构

创建 translate.html 文件,包含:

  • 导航栏
  • 页面标题和说明
  • 表单区域(Token、文本输入、语言选择)
  • 结果展示区域
  • Token 获取弹窗

步骤 2: 设计 UI 样式

使用 CSS3 实现:

  • 响应式布局
  • 渐变背景和阴影效果
  • 表单样式和焦点效果
  • 弹窗样式
  • 移动端适配

步骤 3: 实现 JavaScript 逻辑

创建 translate.js 文件,实现:

  • 表单提交处理
  • API 调用
  • 数据验证
  • 结果展示
  • Token 存储
  • 语言名称映射

步骤 4: 配置安全策略

更新 Content Security Policy (CSP),允许:

  • 访问 API 域名
  • 加载 iframe 内容
  • 执行必要的脚本

核心代码解析

1. HTML 结构

表单部分
<form id="translate-form">
    <div class="form-group">
        <label for="token" class="label-with-help">
            <span>API Token *</span>
            <button type="button" class="help-btn" onclick="openTokenModal()">获取 Token</button>
        </label>
        <div class="token-input-wrapper">
            <input type="text" id="token" name="token" placeholder="请输入API Token" required>
        </div>
    </div>

    <div class="form-group">
        <label for="text">要翻译的文本 *</label>
        <textarea id="text" name="text" placeholder="请输入要翻译的文本" required></textarea>
    </div>

    <div class="form-row">
        <div class="form-group">
            <label for="from">源语言</label>
            <select id="from" name="from">
                <option value="auto">自动检测</option>
                <option value="zh" selected>中文</option>
                <option value="en">英语</option>
                <!-- 更多语言选项... -->
            </select>
        </div>

        <div class="form-group">
            <label for="to">目标语言</label>
            <select id="to" name="to">
                <option value="zh">中文</option>
                <option value="en" selected>英语</option>
                <!-- 更多语言选项... -->
            </select>
        </div>
    </div>

    <button type="submit" class="submit-btn" id="submit-btn">翻译</button>
</form>
结果展示区域
<div class="result-container" id="result-container">
    <div class="result-title">翻译结果</div>
    <div class="translate-result">
        <div class="translate-item">
            <div class="translate-label">原文</div>
            <div class="translate-text source" id="source-text"></div>
        </div>
        <div class="translate-item">
            <div class="translate-label">译文</div>
            <div class="translate-text target" id="target-text"></div>
        </div>
        <div class="language-info">
            <span>源语言:<strong id="from-lang"></strong></span>
            <span>目标语言:<strong id="to-lang"></strong></span>
        </div>
    </div>
</div>

2. CSS 样式设计

响应式表单布局
.form-container {
    background-color: #f8f9fa;
    padding: 25px;
    border-radius: 8px;
    margin-bottom: 30px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.form-group textarea {
    min-height: 120px;
    resize: vertical;
    font-family: inherit;
}

.form-group input:focus,
.form-group textarea:focus,
.form-group select:focus {
    outline: none;
    border-color: #3498db;
    box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}
翻译结果样式
.translate-result {
    background-color: white;
    padding: 25px;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.translate-item {
    margin-bottom: 20px;
    padding-bottom: 20px;
    border-bottom: 1px solid #eee;
}

.translate-label {
    font-size: 12px;
    color: #7f8c8d;
    margin-bottom: 8px;
    text-transform: uppercase;
    font-weight: bold;
}

.translate-text {
    font-size: 18px;
    line-height: 1.8;
    color: #2c3e50;
    word-wrap: break-word;
}

.translate-text.source {
    color: #555;
}

.translate-text.target {
    color: #2980b9;
    font-weight: 500;
}

3. JavaScript 核心逻辑

语言名称映射
// 语言名称映射
const languageNames = {
    'auto': '自动检测',
    'zh': '中文',
    'en': '英语',
    'yue': '粤语',
    'wyw': '文言文',
    'jp': '日语',
    'kor': '韩语',
    'fra': '法语',
    'spa': '西班牙语',
    'th': '泰语',
    'ara': '阿拉伯语',
    'ru': '俄语',
    'pt': '葡萄牙语',
    'de': '德语',
    'it': '意大利语',
    'el': '希腊语',
    'nl': '荷兰语',
    'pl': '波兰语',
    'bul': '保加利亚语',
    'est': '爱沙尼亚语',
    'dan': '丹麦语',
    'fin': '芬兰语',
    'cs': '捷克语',
    'rom': '罗马尼亚语',
    'slo': '斯洛文尼亚语',
    'swe': '瑞典语',
    'hu': '匈牙利语',
    'cht': '繁体中文',
    'vie': '越南语'
};
API 调用函数
function translateText() {
    const submitBtn = document.getElementById('submit-btn');
    const loading = document.getElementById('loading');
    const errorMessage = document.getElementById('error-message');
    const resultContainer = document.getElementById('result-container');
    
    // 获取表单数据
    const formData = {
        token: document.getElementById('token').value.trim(),
        q: document.getElementById('text').value.trim(),
        from: document.getElementById('from').value,
        to: document.getElementById('to').value
    };
    
    // 验证文本
    if (!formData.q) {
        showError('请输入要翻译的文本');
        return;
    }
    
    // 验证token
    if (!formData.token) {
        showError('请输入API Token');
        return;
    }
    
    // 显示加载状态
    submitBtn.disabled = true;
    submitBtn.textContent = '翻译中...';
    loading.classList.add('show');
    errorMessage.classList.remove('show');
    resultContainer.classList.remove('show');
    
    // 构建请求参数
    const params = new URLSearchParams();
    params.append('token', formData.token);
    params.append('q', formData.q);
    params.append('from', formData.from);
    params.append('to', formData.to);
    
    // 发送POST请求
    fetch('https://v3.alapi.cn/api/fanyi', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: params.toString()
    })
    .then(response => {
        if (!response.ok) {
            throw new Error('网络请求失败: ' + response.status);
        }
        return response.json();
    })
    .then(data => {
        console.log('API响应:', data);
        
        if (data.success && data.code === 200) {
            displayTranslation(data.data);
        } else {
            showError(data.message || '翻译失败,请检查参数');
        }
    })
    .catch(error => {
        console.error('请求错误:', error);
        showError('请求失败: ' + error.message + '。请检查网络连接或API Token是否正确');
    })
    .finally(() => {
        // 恢复按钮状态
        submitBtn.disabled = false;
        submitBtn.textContent = '翻译';
        loading.classList.remove('show');
    });
}
结果展示函数
function displayTranslation(data) {
    const resultContainer = document.getElementById('result-container');
    const sourceText = document.getElementById('source-text');
    const targetText = document.getElementById('target-text');
    const fromLang = document.getElementById('from-lang');
    const toLang = document.getElementById('to-lang');
    
    // 显示原文
    if (data.src) {
        sourceText.textContent = data.src;
    } else {
        sourceText.textContent = '无原文';
    }
    
    // 显示译文
    if (data.dst) {
        targetText.textContent = data.dst;
    } else {
        targetText.textContent = '无译文';
    }
    
    // 显示语言信息
    if (data.from) {
        fromLang.textContent = languageNames[data.from] || data.from;
    } else {
        fromLang.textContent = '未知';
    }
    
    if (data.to) {
        toLang.textContent = languageNames[data.to] || data.to;
    } else {
        toLang.textContent = '未知';
    }
    
    // 显示结果容器
    resultContainer.classList.add('show');
    
    // 滚动到结果区域
    resultContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
Token 本地存储
// 页面加载完成后的初始化
document.addEventListener('DOMContentLoaded', function() {
    // 尝试从本地存储加载token
    const savedToken = localStorage.getItem('translate_api_token');
    if (savedToken) {
        document.getElementById('token').value = savedToken;
    }
    
    // 保存token到本地存储
    document.getElementById('token').addEventListener('blur', function() {
        const token = this.value.trim();
        if (token) {
            localStorage.setItem('translate_api_token', token);
        }
    });
    
    console.log('翻译页面加载完成');
});

API 集成详解

API 请求参数

参数名类型必填说明示例
tokenstring接口调用token,需要在token管理中创建qlVquQZPYSeaCi6u
qstring要翻译的文本你好啊
fromstring来源语种。默认 zhzhauto
tostring翻译语种,默认enen

API 响应格式

{
  "request_id": "850395543089061888",
  "success": true,
  "message": "success",
  "code": 200,
  "data": {
    "from": "zh",
    "to": "en",
    "src": "鸿蒙生态",
    "dst": "HarmonyOS ecology"
  },
  "time": 1763710097,
  "usage": 0
}

请求实现

// 构建请求参数
const params = new URLSearchParams();
params.append('token', formData.token);
params.append('q', formData.q);
params.append('from', formData.from);
params.append('to', formData.to);

// 发送POST请求
fetch('https://v3.alapi.cn/api/fanyi', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: params.toString()
})

错误处理

.then(response => {
    if (!response.ok) {
        throw new Error('网络请求失败: ' + response.status);
    }
    return response.json();
})
.then(data => {
    if (data.success && data.code === 200) {
        displayTranslation(data.data);
    } else {
        showError(data.message || '翻译失败,请检查参数');
    }
})
.catch(error => {
    console.error('请求错误:', error);
    showError('请求失败: ' + error.message + '。请检查网络连接或API Token是否正确');
});

用户体验优化

1. 表单验证

前端验证
// 验证文本
if (!formData.q) {
    showError('请输入要翻译的文本');
    return;
}

// 验证token
if (!formData.token) {
    showError('请输入API Token');
    return;
}
HTML5 验证
<textarea id="text" name="text" placeholder="请输入要翻译的文本" required></textarea>
<input type="text" id="token" name="token" placeholder="请输入API Token" required>

2. 加载状态提示

// 显示加载状态
submitBtn.disabled = true;
submitBtn.textContent = '翻译中...';
loading.classList.add('show');

// 恢复按钮状态
submitBtn.disabled = false;
submitBtn.textContent = '翻译';
loading.classList.remove('show');

3. 语言名称友好显示

// 使用语言名称映射,将语言代码转换为友好的中文名称
if (data.from) {
    fromLang.textContent = languageNames[data.from] || data.from;
} else {
    fromLang.textContent = '未知';
}

4. 结果展示优化

.translate-text {
    font-size: 18px;
    line-height: 1.8;
    color: #2c3e50;
    word-wrap: break-word;  /* 长文本自动换行 */
}

.translate-text.source {
    color: #555;  /* 原文使用较淡的颜色 */
}

.translate-text.target {
    color: #2980b9;  /* 译文使用醒目的蓝色 */
    font-weight: 500;
}

5. 自动滚动到结果

// 滚动到结果区域
resultContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });

常见问题与解决方案

问题 1: 跨域请求失败

原因:浏览器的同源策略限制

解决方案

  1. 配置正确的 CSP 策略
  2. 使用 fetch API(Cordova 环境支持跨域)
  3. 确保 API 服务器支持 CORS
<meta http-equiv="Content-Security-Policy" 
      content="default-src 'self' data: https://ssl.gstatic.com https://v3.alapi.cn https://www.alapi.cn 'unsafe-eval'; 
               style-src 'self' 'unsafe-inline'; 
               media-src *; 
               img-src 'self' data: content:; 
               script-src 'self' 'unsafe-inline' 'unsafe-eval'; 
               connect-src 'self' https://v3.alapi.cn https://www.alapi.cn; 
               frame-src https://www.alapi.cn;">

问题 2: 长文本显示问题

原因:文本过长导致布局混乱

解决方案

.translate-text {
    word-wrap: break-word;  /* 自动换行 */
    word-break: break-all;  /* 允许在任意字符间换行 */
    overflow-wrap: break-word;  /* 兼容性更好的换行 */
}

问题 3: 自动检测语言不准确

原因:API 自动检测可能误判

解决方案

  1. 提供手动选择源语言的选项
  2. 默认使用自动检测,但允许用户覆盖
  3. 在结果中显示检测到的语言,让用户确认
// 如果自动检测结果不准确,用户可以手动选择源语言
<select id="from" name="from">
    <option value="auto">自动检测</option>
    <option value="zh" selected>中文</option>
    <!-- 其他语言选项 -->
</select>

问题 4: Token 丢失

原因:浏览器清除缓存或 localStorage 被清空

解决方案

  1. 使用 localStorage 持久化存储
  2. 添加 Token 输入提示
  3. 提供快速获取 Token 的入口

问题 5: 移动端输入体验不佳

原因:textarea 在移动端可能显示异常

解决方案

.form-group textarea {
    min-height: 120px;
    resize: vertical;  /* 允许垂直调整大小 */
    font-family: inherit;  /* 继承字体 */
    font-size: 16px;  /* 防止 iOS 自动缩放 */
}

最佳实践

1. 代码组织

  • 分离关注点:HTML 负责结构,CSS 负责样式,JavaScript 负责逻辑
  • 模块化设计:将功能拆分为独立的函数
  • 命名规范:使用有意义的变量和函数名

2. 错误处理

// 完善的错误处理链
fetch(url, options)
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        return response.json();
    })
    .then(data => {
        if (data.success) {
            // 处理成功情况
        } else {
            throw new Error(data.message || '未知错误');
        }
    })
    .catch(error => {
        // 统一错误处理
        console.error('Error:', error);
        showError(error.message);
    });

3. 用户体验

  • 即时反馈:按钮状态、加载提示、错误信息
  • 数据持久化:Token 自动保存
  • 无障碍访问:合理的标签和提示文字
  • 响应式设计:适配各种屏幕尺寸

4. 性能优化

// 防抖处理(避免频繁请求)
let debounceTimer;
function translateTextDebounced() {
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(() => {
        translateText();
    }, 500);
}

// 请求取消(避免重复请求)
let currentController = null;
function translateText() {
    // 取消之前的请求
    if (currentController) {
        currentController.abort();
    }
    
    currentController = new AbortController();
    fetch(url, {
        signal: currentController.signal,
        // ...
    });
}

5. 安全性

  • 输入验证:前端和后端双重验证
  • CSP 配置:限制资源加载来源
  • Token 安全:不在代码中硬编码 Token
  • HTTPS:生产环境使用 HTTPS
  • XSS 防护:使用 textContent 而非 innerHTML

项目结构

www/
├── translate.html          # 翻译页面
├── js/
│   └── translate.js        # 翻译功能脚本
├── index.html              # 首页
├── about.html              # 关于我们
├── poem.html               # 藏头诗页面
└── css/
    └── index.css           # 样式文件

harmonyos/entry/src/main/resources/rawfile/www/
├── translate.html          # 翻译页面(同步)
├── js/
│   └── translate.js        # 翻译功能脚本(同步)
└── ...

部署说明

1. 开发环境

# 添加浏览器平台(用于测试)
hcordova platform add browser

# 运行浏览器预览
hcordova run browser

2. 生产环境

# 构建 HarmonyOS 应用
hcordova build harmonyos

# 构建 Android 应用
hcordova build android

# 构建 iOS 应用(仅 macOS)
hcordova build ios

3. 配置检查

  • ✅ 检查 config.xml 中的 CSP 配置
  • ✅ 确认 API 域名在白名单中
  • ✅ 测试 Token 获取功能
  • ✅ 验证表单验证逻辑

测试建议

1. 功能测试

  • 表单验证测试(文本、Token)
  • API 调用测试(正常情况、错误情况)
  • Token 存储测试(保存、加载)
  • 弹窗功能测试(打开、关闭、ESC 键)
  • 语言选择测试(各种语言组合)
  • 自动检测测试(不同语言的文本)

2. 兼容性测试

  • 不同浏览器测试(Chrome、Safari、Firefox)
  • 移动设备测试(iOS、Android、HarmonyOS)
  • 不同屏幕尺寸测试

3. 性能测试

  • API 响应时间
  • 页面加载速度
  • 内存使用情况
  • 长文本处理性能

扩展功能建议

1. 历史记录

// 保存翻译历史
function saveHistory(translationData) {
    const history = JSON.parse(localStorage.getItem('translate_history') || '[]');
    history.unshift({
        src: translationData.src,
        dst: translationData.dst,
        from: translationData.from,
        to: translationData.to,
        timestamp: Date.now()
    });
    // 只保留最近 50 条
    if (history.length > 50) {
        history.pop();
    }
    localStorage.setItem('translate_history', JSON.stringify(history));
}

2. 收藏功能

// 收藏翻译
function favoriteTranslation(translationData) {
    const favorites = JSON.parse(localStorage.getItem('translate_favorites') || '[]');
    favorites.push(translationData);
    localStorage.setItem('translate_favorites', JSON.stringify(favorites));
}

3. 复制功能

// 复制翻译结果
function copyTranslation(text) {
    if (navigator.clipboard) {
        navigator.clipboard.writeText(text).then(() => {
            alert('已复制到剪贴板');
        });
    } else {
        // 兼容旧浏览器
        const textarea = document.createElement('textarea');
        textarea.value = text;
        document.body.appendChild(textarea);
        textarea.select();
        document.execCommand('copy');
        document.body.removeChild(textarea);
        alert('已复制到剪贴板');
    }
}

4. 语音朗读

// 使用 Web Speech API 朗读翻译结果
function speakText(text, lang) {
    if ('speechSynthesis' in window) {
        const utterance = new SpeechSynthesisUtterance(text);
        utterance.lang = lang;  // 设置语言
        speechSynthesis.speak(utterance);
    }
}

5. 快速切换语言

// 添加语言交换按钮
function swapLanguages() {
    const fromSelect = document.getElementById('from');
    const toSelect = document.getElementById('to');
    const temp = fromSelect.value;
    fromSelect.value = toSelect.value;
    toSelect.value = temp;
}

总结

本文详细介绍了如何在 Cordova 应用中实现文本翻译功能,包括:

  1. 完整的实现方案:从页面设计到 API 集成
  2. 用户体验优化:表单验证、加载提示、错误处理
  3. Token 管理:自动保存和获取引导
  4. 响应式设计:完美适配各种设备
  5. 最佳实践:代码组织、错误处理、性能优化

关键技术点

  • Fetch API:现代化的网络请求方式
  • LocalStorage:客户端数据持久化
  • Modal 弹窗:良好的用户交互体验
  • CSP 配置:安全策略配置
  • 响应式 CSS:移动端适配
  • 语言映射:友好的语言名称显示

项目亮点

  • 🌍 多语言支持:支持 30+ 种语言的翻译
  • 🤖 自动检测:智能识别输入文本的语言
  • 🎨 美观的 UI 设计:渐变背景、阴影效果、流畅动画
  • 🔒 完善的错误处理:网络错误、API 错误、用户输入错误
  • 💾 智能的 Token 管理:自动保存、一键获取
  • 📱 完美的移动端适配:响应式布局、触摸优化
  • 良好的性能:快速响应、流畅交互

应用场景

  • 学习辅助:翻译外语学习材料
  • 商务沟通:跨语言交流
  • 旅游出行:实时翻译
  • 文档处理:批量文本翻译
  • 内容创作:多语言内容生成

通过本文的指导,您可以快速实现一个功能完整、用户体验良好的翻译应用。希望本文对您的开发工作有所帮助!


作者: 坚果派开发团队
最后更新: 2025年
版本: 1.0

参考资源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序媛夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值