芯图相册国际化技术实现总结
📋 概述
本文档全面总结了芯图相册(XinTu Album)的国际化(i18n)技术实现方案,涵盖用户界面、动态内容、原生代码和跨平台一致性等各个方面。
技术栈:
- i18n 库:
i18next+react-i18next - 支持语言: 中文(zh)、英文(en)
- 存储方案: 语言设置存储在
ImageStorageService中,统一管理应用配置 - 平台支持: PC端(Electron)和移动端(React Native Android)
🏗️ 基本实现方案
1.1 核心架构
1.1.1 i18n 初始化配置
文件位置: src/i18n/index.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import zh from './locales/zh/common.json';
import en from './locales/en/common.json';
const resources = {
zh: { common: zh },
en: { common: en },
};
i18n
.use(initReactI18next)
.init({
resources,
lng: getSavedLanguageSync(),
fallbackLng: 'zh',
defaultNS: 'common',
interpolation: {
escapeValue: false, // React已经转义了
},
compatibilityJSON: 'v3', // 兼容React Native
});
关键特性:
- 同步初始化:使用
getSavedLanguageSync()同步读取语言设置,确保应用启动时语言正确 - 异步加载:应用启动后通过
loadSavedLanguage()异步加载保存的语言设置,支持react-native-localize - 语言持久化:语言设置存储在
ImageStorageService的app_language字段中
1.1.2 语言检测机制
系统语言检测:
-
Web/Desktop 环境:
const detectSystemLanguage = () => { if (typeof navigator !== 'undefined' && navigator.language) { const systemLang = navigator.language.toLowerCase(); if (systemLang.startsWith('zh')) return 'zh'; if (systemLang.startsWith('en')) return 'en'; } return 'zh'; // 默认中文 }; -
React Native 环境:
const detectSystemLanguageAsync = async () => { if (isReactNative) { const localizeModule = await import('react-native-localize'); const locales = localizeModule.getLocales(); if (locales && locales.length > 0) { const systemLang = locales[0].languageCode.toLowerCase(); if (systemLang === 'zh') return 'zh'; if (systemLang === 'en') return 'en'; } } return 'zh'; };
语言设置管理:
- 读取:
getCurrentLanguageAsync()- 从ImageStorageService读取保存的语言设置 - 保存:
changeLanguage(lng)- 保存语言设置到ImageStorageService并切换 i18n 语言 - 验证:切换语言后会验证保存是否成功,确保数据一致性
1.2 翻译资源组织
文件结构:
src/i18n/
├── index.js # i18n 配置和工具函数
└── locales/
├── zh/
│ └── common.json # 中文翻译资源
└── en/
└── common.json # 英文翻译资源
命名空间:
common: 通用翻译命名空间(默认)- 所有翻译键都使用
common命名空间,通过useTranslation('common')使用
翻译键命名规范:
app.*: 应用基础信息(如app.name)home.*: 首页相关翻译(如home.ready,home.scanning)category.*: 分类相关翻译(如category.noImages)imagePreview.*: 图片预览相关翻译(如imagePreview.detectionResult)settings.*: 设置页面相关翻译(如settings.memberActivated)common.*: 通用翻译(如common.success,common.cancel)
🎨 用户可见界面信息的国际化
2.1 界面基础文本国际化
2.1.1 React 组件中的使用
标准用法:
import { useTranslation } from 'react-i18next';
const MyComponent = () => {
const { t, i18n } = useTranslation('common');
return (
<Text>{t('home.ready')}</Text>
);
};
关键 API:
t(key, options): 翻译函数,根据当前语言返回对应的翻译文本i18n.language: 获取当前语言代码('zh' 或 'en')i18n.t(key, { lng: 'en' }): 指定语言进行翻译(用于动态切换)
2.1.2 常见界面元素翻译
| 翻译键 | 中文 | 英文 | 使用位置 |
|---|---|---|---|
app.name | 芯图相册 | Xintu Album | 应用标题 |
home.ready | 图片分类应用已就绪 | Image classification app is ready | 首页状态 |
home.scanning | 扫描中... | Scanning... | 扫描状态 |
home.scanComplete | 扫描完成 | Scan complete | 扫描完成提示 |
home.smartScan | 智能扫描 | Smart Scan | 扫描按钮 |
category.noImages | 暂无图片 | No images | 空状态提示 |
common.success | 成功 | Success | 通用成功提示 |
common.cancel | 取消 | Cancel | 取消按钮 |
英文界面效果展示:
PC端英文界面:按内容、城市、颜色智能分类
2.2 弹窗(Alert)国际化
2.2.1 标准弹窗实现
基础用法:
import { useTranslation } from 'react-i18next';
import { Alert } from 'react-native';
const MyComponent = () => {
const { t, i18n } = useTranslation('common');
const showAlert = () => {
Alert.alert(
t('common.success'),
t('settings.memberActivated')
);
};
};
2.2.2 动态语言弹窗(重要)
问题场景:在异步回调(如 setInterval)中,t 函数可能使用的是闭包中的旧语言值。
解决方案:使用 i18n.t() 并显式指定语言参数:
// ❌ 错误示例:可能使用旧的语言值
Alert.alert(t('common.success'), t('settings.memberActivated'));
// ✅ 正确示例:使用当前语言
const currentLang = i18n.language || 'zh';
Alert.alert(
i18n.t('common.success', { lng: currentLang }),
i18n.t('settings.memberActivated', { lng: currentLang })
);
实现位置:
src/screens/mobile/SettingsScreen.mobile.jssrc/screens/desktop/SettingsScreen.desktop.js
关键代码:
// 会员激活弹窗
const currentLang = i18n.language || 'zh';
Alert.alert(
i18n.t('common.success', { lng: currentLang }),
i18n.t('settings.memberActivated', { lng: currentLang })
);
🌍 城市信息的国际化
3.1 实现策略
核心原则:在扫描时根据用户语言配置保存对应语言的城市名称,而不是在显示时翻译。
3.1.1 数据来源
-
远程API(优先):
- 返回字段:
name(英文)、name_zh(中文)、admin1(省份英文)、admin1_zh(省份中文) - API:
https://api.bigdatacloud.net/data/reverse-geocode-client
- 返回字段:
-
本地数据(降级):
- 仅包含中文城市名称
- 文件:
src/data/cities.json
3.1.2 JavaScript 实现
文件位置: src/services/CityLocationService.js
关键方法:
async findNearestCityAsync(latitude, longitude, maxDistance = 200, useRemoteApi = true, language = 'zh') {
// 1. 检查缓存(包含语言信息)
const cacheKey = this.getCacheKey(latitude, longitude, language);
// 2. 优先使用远程API(传递语言参数)
if (useRemoteApi) {
nearestCity = await this.findNearestCityRemote(latitude, longitude, language);
}
// 3. 如果远程API失败,回退到本地查询(本地数据只有中文)
if (!nearestCity) {
nearestCity = this.findNearestCity(latitude, longitude, maxDistance);
}
// 4. 缓存结果(使用包含语言的缓存键)
return nearestCity;
}
远程API调用:
async findNearestCityRemote(latitude, longitude, language = 'zh') {
const url = `https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${latitude}&longitude=${longitude}&localityLanguage=${language}`;
const response = await fetch(url);
const data = await response.json();
// 根据语言选择城市名称
const cityName = language === 'en'
? (data.locality || data.principalSubdivision) // 英文:优先使用 locality
: (data.localityInfo?.administrative?.[0]?.name || data.locality); // 中文:优先使用中文名
return {
name: cityName,
province: language === 'en' ? data.principalSubdivision : data.localityInfo?.administrative?.[1]?.name,
country: language === 'en' ? 'China' : '中国',
distance: null
};
}
使用位置: src/services/GalleryScannerService.js
// 获取当前语言设置
const currentLanguage = await getCurrentLanguageAsync();
// 查找最近的城市信息(传递语言参数)
const nearestCity = await cityLocationService.findNearestCityAsync(
latitude,
longitude,
200,
useRemoteApi,
currentLanguage // 传递当前语言设置
);
if (nearestCity) {
locationInfo.city = nearestCity.name; // 直接使用对应语言的城市名称
locationInfo.province = nearestCity.province;
locationInfo.country = nearestCity.country || '中国';
}
3.1.3 Android 原生实现
文件位置: android/app/src/main/java/com/imageclassifier/v2/GalleryScanService.java
关键代码(第839-866行):
String cityName;
String provinceName;
if ("en".equals(language)) {
// 英文:优先使用英文名,如果没有则使用中文名
cityName = mainCity.optString("name", mainCity.optString("name_zh", ""));
// 省份:优先使用admin1(省份英文名),如果没有则尝试admin1_zh,最后fallback到城市名称
provinceName = mainCity.optString("admin1",
mainCity.optString("admin1_zh",
mainCity.optString("name_zh", mainCity.optString("name", ""))));
} else {
// 中文(默认):优先使用中文名,如果没有则使用英文名
cityName = mainCity.optString("name_zh", mainCity.optString("name", ""));
// 省份:优先使用admin1_zh(省份中文名),如果没有则尝试admin1,最后fallback到城市名称
provinceName = mainCity.optString("admin1_zh",
mainCity.optString("admin1",
mainCity.optString("name_zh", mainCity.optString("name", ""))));
// 标准化城市名称:移除"市"后缀(仅中文)
if (cityName.endsWith("市")) {
cityName = cityName.substring(0, cityName.length() - 1);
}
}
Map<String, String> location = new HashMap<>();
location.put("city", cityName);
location.put("country", "en".equals(language) ? "China" : "中国"); // 根据语言设置国家名称
location.put("province", provinceName);
语言参数来源:
- Android 原生代码从
ImageStorageService读取app_language设置 - 在扫描时传递语言参数给城市查询方法
3.2 显示逻辑
直接显示:城市名称在扫描时已根据用户语言配置保存,显示时直接使用数据库中存储的值,无需额外翻译。
注意事项:
- ✅ 已存储的数据不会自动更新:如果用户切换语言,已扫描的图片的城市名称不会自动更新,需要重新扫描
- ✅ 降级处理:如果远程API失败,降级到本地查询时只能保存中文名称(本地数据只有中文)
- ✅ 缓存机制:使用包含语言的缓存键,确保不同语言的结果分别缓存
📂 内容类别信息的国际化
4.1 实现策略
核心原则:从配置文件中读取中英文名称,动态选择显示,而不是存储在 i18n JSON 文件中。
4.1.1 数据来源
配置文件: public/initialSettings.json
{
"categoryNameMap": {
"social_activities": {
"chinese": "社交活动",
"english": "Social Activities"
},
"pets": {
"chinese": "宠物萌照",
"english": "Pet Photos"
},
"single_person": {
"chinese": "单人照片",
"english": "Single Person"
},
"foods": {
"chinese": "美食记录",
"english": "Food Records"
},
"travel_scenery": {
"chinese": "旅行风景",
"english": "Travel Scenery"
},
"screenshot": {
"chinese": "手机截图",
"english": "Mobile Screenshots"
},
"other": {
"chinese": "其它",
"english": "Other Images"
},
"idcard": {
"chinese": "证件照",
"english": "ID Card"
},
"NA": {
"chinese": "待分类",
"english": "to be classified"
},
"qrcode": {
"chinese": "二维码",
"english": "QR Code"
}
}
}
4.1.2 JavaScript 实现
文件位置: src/services/ConfigService.js
关键方法:
getCategoryDisplayName(categoryId, language = 'chinese') {
const categoryByKey = this.getCategoryByKey(categoryId);
if (categoryByKey) {
return categoryByKey[language] || categoryByKey.chinese || categoryByKey.english || categoryId;
}
const category = this.getCategoryById(categoryId);
if (category) {
return category[language] || category.chinese || category.english || categoryId;
}
return categoryId;
}
使用位置: src/screens/desktop/HomeScreen.desktop.js
const categories = configService.getAllCategoriesWithUI().map(category => {
// 根据当前语言动态选择分类名称
const currentLang = i18n.language || 'zh';
const categoryName = currentLang === 'en'
? (category.english || category.chinese) // 英文:优先使用英文,如果没有则使用中文
: (category.chinese || category.english); // 中文:优先使用中文,如果没有则使用英文
return {
id: category.id,
name: categoryName,
icon: '📷',
color: '#607D8B'
};
});
图片预览页面: src/screens/desktop/ImagePreviewScreen.desktop.js
const getCategoryInfo = (categoryId) => {
const category = configService.getCategoryByKey(categoryId);
if (!category) {
return {
name: i18n.t('imagePreview.unknownCategoryName'),
icon: '📷',
color: '#607D8B'
};
}
// 根据当前语言设置获取分类名称
const currentLang = i18n.language || 'zh';
const language = currentLang === 'en' ? 'english' : 'chinese';
return {
name: category[language] || category.chinese || category.english || categoryId,
icon: '📷',
color: '#607D8B'
};
};
4.2 设计优势
- 单一数据源:分类名称只在一个地方维护(
initialSettings.json),避免重复 - 自动回退:如果首选语言不存在,自动使用另一种语言
- 灵活性:支持动态添加新分类,只需更新配置文件
- 性能优化:不需要在 i18n JSON 中维护大量分类名称
分类信息国际化效果展示:

PC端英文界面:按内容、城市、颜色智能分类

移动端英文界面:按内容分类

移动端英文界面:按城市分类

移动端英文界面:按颜色分类
🤖 智能检测信息的国际化
5.1 扫描进度消息
5.1.1 实现位置
JavaScript 版本: src/services/GalleryScannerService.js
Android 原生版本: src/services/GalleryScannerService.android.js
5.1.2 翻译键定义
| 翻译键 | 中文 | 英文 |
|---|---|---|
home.scanProgress.initScanning | 初始化扫描: 准备扫描环境 | Initializing scan: Preparing scan environment |
home.scanProgress.scanningFiles | 扫描文件: {{processed}}/{{total}} | Scanning files: {{processed}}/{{total}} |
home.scanProgress.categoryQuery | 查询分类: {{processed}}/{{total}} | Querying categories: {{processed}}/{{total}} |
home.scanProgress.smartRecognitionStart | 开始智能识别 {{count}} 张图片 | Starting smart recognition for {{count}} images |
home.scanProgress.smartRecognition | 智能识别: {{processed}}/{{total}} | Smart recognition: {{processed}}/{{total}} |
home.scanProgress.localRecognitionStart | 开始本地识别 {{count}} 张图片 | Starting local recognition for {{count}} images |
home.scanProgress.localRecognition | 本地识别: {{processed}}/{{total}} | Local recognition: {{processed}}/{{total}} |
home.scanProgress.locationEnrichmentStart | 开始位置信息丰富 {{count}} 张图片 | Starting location enrichment for {{count}} images |
home.scanProgress.locationEnrichment | 位置信息丰富: {{processed}}/{{total}} | Location enrichment: {{processed}}/{{total}} |
home.scanProgress.similarityDetectionStart | 开始相似度检测 | Starting similarity detection |
home.scanProgress.similarityDetectionProgress | 相似度检测: {{processed}}/{{total}} ({{groups}} 组) | Similarity detection: {{processed}}/{{total}} ({{groups}} groups) |
home.scanProgress.removingFiles | 移除文件: {{count}} | Removing files: {{count}} |
home.scanProgress.completed | 扫描完成 | Scan completed |
5.1.3 实现示例
JavaScript 版本:
async processProgressData(rawProgress) {
const { stage, filesProcessed, filesFound } = rawProgress;
let simpleMessage = '';
switch (stage) {
case 'init_scanning':
simpleMessage = i18n.t('home.scanProgress.initScanning');
break;
case 'scanning_files':
simpleMessage = i18n.t('home.scanProgress.scanningFiles', {
processed: filesProcessed || 0,
total: filesFound || 0
});
break;
case 'remote_inference':
simpleMessage = i18n.t('home.scanProgress.smartRecognition', {
processed: filesProcessed || 0,
total: filesFound || 0
});
break;
case 'similarity_detection':
const groupsCount = this.imagesClassified || 0;
simpleMessage = i18n.t('home.scanProgress.similarityDetectionProgress', {
processed: filesProcessed,
total: filesFound,
groups: groupsCount
});
break;
case 'completed':
simpleMessage = i18n.t('home.scanProgress.completed');
break;
}
return { simpleMessage, ... };
}
Android 原生版本(类似实现,但支持更多阶段):
case 'remote_inference':
if (filesFound > 0 && filesProcessed === 0) {
simpleMessage = i18n.t('home.scanProgress.smartRecognitionStart', { count: filesFound });
} else {
simpleMessage = i18n.t('home.scanProgress.smartRecognition', {
processed: filesProcessed || 0,
total: filesFound || 0
});
}
break;
扫描进度消息国际化效果展示:

移动端英文界面:照片扫描进展显示
5.2 检测结果标签
5.2.1 翻译键定义
| 翻译键 | 中文 | 英文 |
|---|---|---|
imagePreview.detectionResult | 检测结果 | Detection Result |
imagePreview.classificationComplete | 分类完成 | Classification Complete |
imagePreview.objects | 个对象 | objects |
imagePreview.idCardDetection | 身份证检测 | ID Card Detection |
imagePreview.idCardFront | 身份证正面 | ID Card Front |
imagePreview.idCardBack | 身份证背面 | ID Card Back |
imagePreview.generalDetection | 通用物体检测 | General Object Detection |
imagePreview.uncategorizedCategory | 未分类 | Uncategorized |
imagePreview.unknownCategoryName | 未知分类 | Unknown Category |
5.2.2 使用示例
文件位置: src/screens/desktop/ImagePreviewScreen.desktop.js
{/* 检测结果显示 */}
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>
🔍 {t('imagePreview.detectionResult')}:
</Text>
<Text style={styles.infoValue}>
{currentImage.message && currentImage.message !== i18n.t('imagePreview.classificationComplete')
? currentImage.message
: `${((currentImage.idCardDetections?.length || 0) + (currentImage.generalDetections?.length || 0))}${t('imagePreview.objects')}`
}
</Text>
</View>
{/* 身份证检测结果 */}
{currentImage.idCardDetections && currentImage.idCardDetections.length > 0 && (
<View style={styles.detectionSection}>
<Text style={styles.detectionTitle}>
🆔 {t('imagePreview.idCardDetection')}:
</Text>
{currentImage.idCardDetections.map((detection, index) => (
<View key={index} style={styles.detectionItem}>
<Text style={styles.detectionText}>
{detection.class === 'id_card_front'
? t('imagePreview.idCardFront')
: t('imagePreview.idCardBack')
}
({(detection.confidence * 100).toFixed(1)}%)
</Text>
</View>
))}
</View>
)}
检测结果国际化效果展示:
PC端英文界面:照片预览以及信息查看
移动端英文界面:单照片预览
移动端英文界面:查看单照片的详细信息
移动端英文界面:照片相似组检测
💻 原生代码和JS代码的国际化
6.1 JavaScript 代码国际化
6.1.1 标准实现
导入和使用:
import { useTranslation } from 'react-i18next';
import i18n, { getCurrentLanguageAsync } from '../../i18n';
const MyComponent = () => {
const { t, i18n } = useTranslation('common');
// 使用翻译函数
const message = t('home.ready');
// 获取当前语言
const currentLang = i18n.language || 'zh';
// 指定语言翻译
const englishMessage = i18n.t('home.ready', { lng: 'en' });
};
6.1.2 异步语言获取
场景:在非 React 组件中(如 Service 类)需要获取当前语言。
实现:
import { getCurrentLanguageAsync } from '../i18n';
class MyService {
async doSomething() {
// 异步获取当前语言设置
const currentLanguage = await getCurrentLanguageAsync();
// 根据语言执行不同逻辑
if (currentLanguage === 'en') {
// 英文逻辑
} else {
// 中文逻辑
}
}
}
6.2 Android 原生代码国际化
6.2.1 语言参数传递
实现位置: android/app/src/main/java/com/imageclassifier/v2/GalleryScanService.java
关键步骤:
-
读取语言设置:
// 从 ImageStorageService 读取语言设置 String language = getLanguageFromSettings(); // 返回 "zh" 或 "en" -
传递语言参数:
// 在城市查询时传递语言参数 Map<String, String> location = getCityLocation(latitude, longitude, language); -
根据语言返回对应文本:
if ("en".equals(language)) { cityName = mainCity.optString("name", mainCity.optString("name_zh", "")); country = "China"; } else { cityName = mainCity.optString("name_zh", mainCity.optString("name", "")); country = "中国"; }
6.2.2 原生代码限制
当前实现:
- ✅ 城市名称:原生代码根据语言设置返回对应语言的城市名称
- ✅ 国家名称:根据语言返回 "China" 或 "中国"
- ❌ 其他文本:原生代码中的其他文本(如错误消息)目前未国际化
未来改进建议:
- 考虑使用 Android 的
Resources系统进行国际化 - 或者将更多文本逻辑移到 JavaScript 层处理
🔄 PC端和移动端国际化的一致性
7.1 共享配置
7.1.1 统一的 i18n 配置
文件位置: src/i18n/index.js
关键特性:
- ✅ 跨平台兼容:使用
compatibilityJSON: 'v3'确保 React Native 兼容性 - ✅ 统一语言资源:PC端和移动端使用相同的翻译文件
- ✅ 统一语言设置:语言设置存储在
ImageStorageService中,PC端使用 IndexedDB,移动端使用 SQLite
7.1.2 统一的语言设置管理
存储方案:
- PC端:
ImageStorageService→IndexedDB→settings.app_language - 移动端:
ImageStorageService→SQLite→settings.app_language
读取和保存:
// 统一的 API,自动适配平台
const storageService = await getImageStorageService();
const settings = await storageService.getSettings();
const language = settings.app_language || 'zh';
// 保存语言设置
await storageService.saveSettings({ ...settings, app_language: 'en' });
7.2 组件实现一致性
7.2.1 相同的翻译键
PC端和移动端使用相同的翻译键:
src/screens/desktop/HomeScreen.desktop.jssrc/screens/mobile/HomeScreen.mobile.js
两者都使用相同的翻译键,如:
home.readyhome.scanningcategory.noImagesimagePreview.detectionResult
7.2.2 相同的实现模式
分类名称处理:
// PC端和移动端使用相同的逻辑
const currentLang = i18n.language || 'zh';
const categoryName = currentLang === 'en'
? (category.english || category.chinese)
: (category.chinese || category.english);
颜色名称翻译:
// PC端和移动端都使用相同的工具函数
import { getColorNameTranslation } from '../../i18n';
const displayColorName = getColorNameTranslation(color, i18n.language);
7.3 数据一致性
7.3.1 城市信息
PC端和移动端:
- ✅ 使用相同的
CityLocationService - ✅ 使用相同的远程API和本地数据
- ✅ 根据相同的语言设置保存城市名称
7.3.2 分类信息
PC端和移动端:
- ✅ 使用相同的
ConfigService - ✅ 读取相同的
initialSettings.json - ✅ 使用相同的分类名称映射逻辑
🔧 动态内容的处理方案
8.1 服务器返回内容的国际化
8.1.1 颜色名称翻译
问题:服务器API返回的颜色名称可能是中文或英文,需要根据用户语言设置翻译。
解决方案:使用颜色名称映射表。
实现位置: src/i18n/index.js
const COLOR_NAME_MAP = {
// 中文 -> 英文
'橙色': 'Orange',
'蓝色': 'Blue',
'红色': 'Red',
// ... 更多映射
// 英文 -> 中文
'Orange': '橙色',
'Blue': '蓝色',
'Red': '红色',
// ... 更多映射
};
export const getColorNameTranslation = (colorName, language = null) => {
const targetLang = language || getCurrentLanguage();
const normalizedColorName = colorName.trim();
if (targetLang === 'zh') {
// 目标语言是中文
// 如果已经是中文,直接返回
if (COLOR_NAME_MAP[normalizedColorName] &&
['橙色', '蓝色', '红色', ...].includes(normalizedColorName)) {
return normalizedColorName;
}
// 尝试从英文映射到中文
const chineseName = COLOR_NAME_MAP[normalizedColorName];
if (chineseName) return chineseName;
} else {
// 目标语言是英文
// 如果已经是英文,直接返回(首字母大写)
const englishNames = ['Orange', 'Blue', 'Red', ...];
if (englishNames.includes(normalizedColorName)) {
return normalizedColorName.charAt(0).toUpperCase() + normalizedColorName.slice(1).toLowerCase();
}
// 尝试从中文映射到英文
const englishName = COLOR_NAME_MAP[normalizedColorName];
if (englishName) return englishName;
}
// 如果找不到映射,返回原始值
return colorName;
};
使用位置: src/screens/desktop/HomeScreen.desktop.js
import { getColorNameTranslation } from '../../i18n';
const ColorCard = ({ color }) => {
const displayColorName = useMemo(() => {
return getColorNameTranslation(color, i18n.language);
}, [color, i18n.language]);
return <Text>{displayColorName}</Text>;
};
8.1.2 方向名称翻译
类似实现:使用方向名称映射表。
实现位置: src/i18n/index.js
const ORIENTATION_NAME_MAP = {
'横屏': 'Landscape',
'竖屏': 'Portrait',
'正方形': 'Square',
'全景': 'Panorama',
// ... 反向映射
};
export const getOrientationNameTranslation = (orientationName, language = null) => {
// 类似颜色名称翻译的实现
// ...
};
8.2 动态消息的国际化
8.2.1 带参数的翻译
实现:使用 i18n 的插值功能。
// 翻译键定义
{
"home.scanProgress.scanningFiles": "扫描文件: {{processed}}/{{total}}",
"home.scanProgress.similarityDetectionProgress": "相似度检测: {{processed}}/{{total}} ({{groups}} 组)"
}
// 使用
i18n.t('home.scanProgress.scanningFiles', {
processed: 10,
total: 100
});
// 输出: "扫描文件: 10/100"
8.2.2 AI 描述消息
问题:AI 返回的描述消息可能是中文或英文,需要根据用户语言设置处理。
当前实现:
- AI 描述消息直接显示,不做翻译(因为 AI 本身会根据上下文生成对应语言的描述)
- 如果 AI 返回的消息不符合用户语言设置,可能需要改进 AI 提示词
未来改进建议:
- 在 AI 请求时传递语言参数,确保返回对应语言的描述
- 或者在后端进行翻译
📝 最佳实践总结
9.1 翻译键命名规范
- 使用有意义的命名空间:
home.*,category.*,imagePreview.* - 使用描述性的键名:
home.scanComplete而不是home.msg1 - 保持一致性:PC端和移动端使用相同的翻译键
9.2 动态内容处理
- 服务器返回内容:使用映射表进行翻译(如颜色名称、方向名称)
- 配置数据:从配置文件读取中英文名称,动态选择(如分类名称)
- 扫描时保存:根据用户语言设置保存对应语言的数据(如城市名称)
9.3 语言设置管理
- 统一存储:使用
ImageStorageService统一管理语言设置 - 异步读取:在 Service 类中使用
getCurrentLanguageAsync()异步读取 - 验证保存:切换语言后验证保存是否成功
9.4 弹窗和异步回调
- 使用当前语言:在异步回调中使用
i18n.t(key, { lng: currentLang })而不是闭包中的t函数 - 显式指定语言:确保使用最新的语言值
🚀 未来改进建议
10.1 已实现功能
- ✅ 基础界面文本国际化
- ✅ 弹窗国际化(包括动态语言切换)
- ✅ 城市信息国际化(扫描时保存)
- ✅ 分类信息国际化(配置文件读取)
- ✅ 智能检测信息国际化(扫描进度、检测结果)
- ✅ 颜色名称翻译(映射表)
- ✅ 方向名称翻译(映射表)
- ✅ PC端和移动端一致性
10.2 待改进功能
-
城市名称映射表:
- 考虑创建城市名称映射表,支持已存储数据的国际化显示
- 避免重新扫描才能看到对应语言的城市名称
-
Android 原生代码国际化:
- 使用 Android
Resources系统进行更全面的国际化 - 或者将更多文本逻辑移到 JavaScript 层
- 使用 Android
-
AI 描述消息:
- 在 AI 请求时传递语言参数
- 确保返回对应语言的描述
-
日期格式化:
- 使用 i18n 的日期格式化功能
- 统一日期显示格式
-
数字格式化:
- 根据语言设置格式化数字(如千位分隔符)
📚 相关文件清单
核心文件
src/i18n/index.js- i18n 配置和工具函数src/i18n/locales/zh/common.json- 中文翻译资源src/i18n/locales/en/common.json- 英文翻译资源
服务文件
src/services/ImageStorageService.js- 语言设置存储src/services/CityLocationService.js- 城市信息国际化src/services/ConfigService.js- 分类信息国际化src/services/GalleryScannerService.js- 扫描进度消息国际化
原生代码
android/app/src/main/java/com/imageclassifier/v2/GalleryScanService.java- Android 原生城市信息国际化
配置文件
public/initialSettings.json- 分类名称配置
📖 参考资料
文档版本: 1.0.0
最后更新: 2025-01-20
维护者: 芯图相册开发团队