feat(i18n): 实现应用国际化支持,添加中英文翻译

- 为所有UI组件添加国际化支持,替换硬编码文本
- 新增useI18n钩子函数统一管理翻译
- 完善中英文翻译资源,覆盖统计、用药、通知设置等模块
- 优化Tab布局使用翻译键值替代静态文本
- 更新药品管理、个人资料编辑等页面的多语言支持
This commit is contained in:
richarjiang
2025-11-13 11:09:55 +08:00
parent 416d144387
commit 2dca3253e6
21 changed files with 1669 additions and 366 deletions

View File

@@ -9,6 +9,7 @@ import { calculateRemainingCalories } from '@/utils/nutrition';
import dayjs from 'dayjs';
import { router } from 'expo-router';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Animated, Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
import Svg, { Circle } from 'react-native-svg';
@@ -25,10 +26,12 @@ export type NutritionRadarCardProps = {
// 简化的圆环进度组件
const SimpleRingProgress = ({
remainingCalories,
totalAvailable
totalAvailable,
t
}: {
remainingCalories: number;
totalAvailable: number;
t: any;
}) => {
const animatedProgress = useRef(new Animated.Value(0)).current;
const radius = 32;
@@ -82,7 +85,7 @@ const SimpleRingProgress = ({
<Text style={{ fontSize: 12, fontWeight: '600', color: '#192126' }}>
{Math.round(remainingCalories)}
</Text>
<Text style={{ fontSize: 8, color: '#9AA3AE' }}></Text>
<Text style={{ fontSize: 8, color: '#9AA3AE' }}>{t('statistics.components.diet.remaining')}</Text>
</View>
</View>
);
@@ -93,6 +96,7 @@ export function NutritionRadarCard({
style,
resetToken,
}: NutritionRadarCardProps) {
const { t } = useTranslation();
const [currentMealType] = useState<'breakfast' | 'lunch' | 'dinner' | 'snack'>('breakfast');
const [loading, setLoading] = useState(false);
@@ -122,7 +126,7 @@ export function NutritionRadarCard({
dispatch(fetchDailyBasalMetabolism(targetDate)).unwrap(),
]);
} catch (error) {
console.error('NutritionRadarCard: 获取营养卡片数据失败:', error);
console.error('NutritionRadarCard: Failed to get nutrition card data:', error);
} finally {
setLoading(false);
}
@@ -133,14 +137,14 @@ export function NutritionRadarCard({
const nutritionStats = useMemo(() => {
return [
{ label: '热量', value: nutritionSummary ? `${Math.round(nutritionSummary.totalCalories)} 千卡` : '0 千卡', color: '#FF6B6B' },
{ label: '蛋白质', value: nutritionSummary ? `${nutritionSummary.totalProtein.toFixed(1)} g` : '0.0 g', color: '#4ECDC4' },
{ label: '碳水', value: nutritionSummary ? `${nutritionSummary.totalCarbohydrate.toFixed(1)} g` : '0.0 g', color: '#45B7D1' },
{ label: '脂肪', value: nutritionSummary ? `${nutritionSummary.totalFat.toFixed(1)} g` : '0.0 g', color: '#FFA07A' },
{ label: '纤维', value: nutritionSummary ? `${nutritionSummary.totalFiber.toFixed(1)} g` : '0.0 g', color: '#98D8C8' },
{ label: '钠', value: nutritionSummary ? `${Math.round(nutritionSummary.totalSodium)} mg` : '0 mg', color: '#F7DC6F' },
{ label: t('statistics.components.diet.calories'), value: nutritionSummary ? `${Math.round(nutritionSummary.totalCalories)} ${t('statistics.components.diet.kcal')}` : `0 ${t('statistics.components.diet.kcal')}`, color: '#FF6B6B' },
{ label: t('statistics.components.diet.protein'), value: nutritionSummary ? `${nutritionSummary.totalProtein.toFixed(1)} g` : '0.0 g', color: '#4ECDC4' },
{ label: t('statistics.components.diet.carb'), value: nutritionSummary ? `${nutritionSummary.totalCarbohydrate.toFixed(1)} g` : '0.0 g', color: '#45B7D1' },
{ label: t('statistics.components.diet.fat'), value: nutritionSummary ? `${nutritionSummary.totalFat.toFixed(1)} g` : '0.0 g', color: '#FFA07A' },
{ label: t('statistics.components.diet.fiber'), value: nutritionSummary ? `${nutritionSummary.totalFiber.toFixed(1)} g` : '0.0 g', color: '#98D8C8' },
{ label: t('statistics.components.diet.sodium'), value: nutritionSummary ? `${Math.round(nutritionSummary.totalSodium)} mg` : '0 mg', color: '#F7DC6F' },
];
}, [nutritionSummary]);
}, [nutritionSummary, t]);
// 计算还能吃的卡路里
const consumedCalories = nutritionSummary?.totalCalories || 0;
@@ -168,10 +172,10 @@ export function NutritionRadarCard({
source={require('@/assets/images/icons/icon-healthy-diet.png')}
style={styles.titleIcon}
/>
<Text style={styles.cardTitle}></Text>
<Text style={styles.cardTitle}>{t('statistics.components.diet.title')}</Text>
</View>
<Text style={styles.cardSubtitle}>
{loading ? '加载中...' : `更新: ${dayjs(nutritionSummary?.updatedAt).format('MM-DD HH:mm')}`}
{loading ? t('statistics.components.diet.loading') : t('statistics.components.diet.updated', { time: dayjs(nutritionSummary?.updatedAt).format('MM-DD HH:mm') })}
</Text>
</View>
@@ -180,6 +184,7 @@ export function NutritionRadarCard({
<SimpleRingProgress
remainingCalories={(loading || activeCaloriesLoading) ? 0 : remainingCalories}
totalAvailable={(loading || activeCaloriesLoading) ? 0 : effectiveBasalMetabolism + effectiveActiveCalories}
t={t}
/>
</View>
@@ -199,7 +204,7 @@ export function NutritionRadarCard({
<View style={styles.calorieSection}>
<View style={styles.calorieContent}>
<View style={styles.calculationRow}>
<Text style={styles.calorieSubtitle}></Text>
<Text style={styles.calorieSubtitle}>{t('statistics.components.diet.remaining')}</Text>
<View style={styles.remainingCaloriesContainer}>
<AnimatedNumber
value={(loading || activeCaloriesLoading) ? 0 : remainingCalories}
@@ -207,11 +212,11 @@ export function NutritionRadarCard({
style={styles.mainValue}
format={(v) => (loading || activeCaloriesLoading) ? '--' : Math.round(v).toString()}
/>
<Text style={styles.calorieUnit}></Text>
<Text style={styles.calorieUnit}>{t('statistics.components.diet.kcal')}</Text>
</View>
<Text style={styles.calculationText}> = </Text>
<View style={styles.calculationItem}>
<Text style={styles.calculationLabel}></Text>
<Text style={styles.calculationLabel}>{t('statistics.components.diet.basal')}</Text>
</View>
<AnimatedNumber
value={loading ? 0 : effectiveBasalMetabolism}
@@ -221,7 +226,7 @@ export function NutritionRadarCard({
/>
<Text style={styles.calculationText}> + </Text>
<View style={styles.calculationItem}>
<Text style={styles.calculationLabel}></Text>
<Text style={styles.calculationLabel}>{t('statistics.components.diet.exercise')}</Text>
</View>
<AnimatedNumber
value={activeCaloriesLoading ? 0 : effectiveActiveCalories}
@@ -231,7 +236,7 @@ export function NutritionRadarCard({
/>
<Text style={styles.calculationText}> - </Text>
<View style={styles.calculationItem}>
<Text style={styles.calculationLabel}></Text>
<Text style={styles.calculationLabel}>{t('statistics.components.diet.diet')}</Text>
</View>
<AnimatedNumber
value={loading ? 0 : consumedCalories}
@@ -260,7 +265,7 @@ export function NutritionRadarCard({
style={styles.foodOptionImage}
/>
</View>
<Text style={styles.foodOptionText}>AI识别</Text>
<Text style={styles.foodOptionText}>{t('statistics.components.diet.aiRecognition')}</Text>
</TouchableOpacity>
<TouchableOpacity
@@ -277,7 +282,7 @@ export function NutritionRadarCard({
style={styles.foodOptionImage}
/>
</View>
<Text style={styles.foodOptionText}></Text>
<Text style={styles.foodOptionText}>{t('statistics.components.diet.foodLibrary')}</Text>
</TouchableOpacity>
<TouchableOpacity
@@ -294,7 +299,7 @@ export function NutritionRadarCard({
style={styles.foodOptionImage}
/>
</View>
<Text style={styles.foodOptionText}></Text>
<Text style={styles.foodOptionText}>{t('statistics.components.diet.voiceRecord')}</Text>
</TouchableOpacity>
<TouchableOpacity
@@ -311,7 +316,7 @@ export function NutritionRadarCard({
style={styles.foodOptionImage}
/>
</View>
<Text style={styles.foodOptionText}></Text>
<Text style={styles.foodOptionText}>{t('statistics.components.diet.nutritionLabel')}</Text>
</TouchableOpacity>
</View>