feat(i18n): 全面实现应用核心功能模块的国际化支持

- 新增 i18n 翻译资源,覆盖睡眠、饮水、体重、锻炼、用药 AI 识别、步数、健身圆环、基础代谢及设置等核心模块
- 重构相关页面及组件(如 SleepDetail, WaterDetail, WorkoutHistory 等)使用 `useI18n` 钩子替换硬编码文本
- 升级 `utils/date` 工具库与 `DateSelector` 组件,支持基于语言环境的日期格式化与显示
- 完善登录页、注销流程及权限申请弹窗的双语提示信息
- 优化部分页面的 UI 细节与字体样式以适配多语言显示
This commit is contained in:
richarjiang
2025-11-27 17:54:36 +08:00
parent 08adf0f20d
commit fbe0c92f0f
26 changed files with 2508 additions and 1622 deletions

View File

@@ -3,6 +3,7 @@ import { ThemedView } from '@/components/ThemedView';
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
import { useI18n } from '@/hooks/useI18n';
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
import {
fetchActivityRingsForDate,
@@ -51,6 +52,7 @@ type WeekData = {
};
export default function FitnessRingsDetailScreen() {
const { t } = useI18n();
const safeAreaTop = useSafeAreaTop()
const colorScheme = useColorScheme();
const [weekData, setWeekData] = useState<WeekData[]>([]);
@@ -82,7 +84,7 @@ export default function FitnessRingsDetailScreen() {
exerciseInfoAnim.setValue(0);
}
} catch (error) {
console.error('加载锻炼分钟说明偏好失败:', error);
console.error(t('fitnessRingsDetail.errors.loadExerciseInfoPreference'), error);
}
};
@@ -98,7 +100,15 @@ export default function FitnessRingsDetailScreen() {
for (let i = 0; i < 7; i++) {
const currentDay = startOfWeek.add(i, 'day');
const isToday = currentDay.isSame(today, 'day');
const dayNames = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
const dayNames = [
t('fitnessRingsDetail.weekDays.monday'),
t('fitnessRingsDetail.weekDays.tuesday'),
t('fitnessRingsDetail.weekDays.wednesday'),
t('fitnessRingsDetail.weekDays.thursday'),
t('fitnessRingsDetail.weekDays.friday'),
t('fitnessRingsDetail.weekDays.saturday'),
t('fitnessRingsDetail.weekDays.sunday')
];
try {
const activityRingsData = await fetchActivityRingsForDate(currentDay.toDate());
@@ -303,7 +313,7 @@ export default function FitnessRingsDetailScreen() {
setShowExerciseInfo(false);
});
} catch (error) {
console.error('保存锻炼分钟说明偏好失败:', error);
console.error(t('fitnessRingsDetail.errors.saveExerciseInfoPreference'), error);
}
};
@@ -380,7 +390,7 @@ export default function FitnessRingsDetailScreen() {
{/* 活动热量卡片 */}
<View style={styles.metricCard}>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}></Text>
<Text style={styles.cardTitle}>{t('fitnessRingsDetail.cards.activeCalories.title')}</Text>
<TouchableOpacity style={styles.helpButton}>
<Text style={styles.helpIcon}>?</Text>
</TouchableOpacity>
@@ -390,25 +400,25 @@ export default function FitnessRingsDetailScreen() {
<Text style={[styles.valueText, { color: '#FF3B30' }]}>
{Math.round(activeEnergyBurned)}/{activeEnergyBurnedGoal}
</Text>
<Text style={styles.unitText}></Text>
<Text style={styles.unitText}>{t('fitnessRingsDetail.cards.activeCalories.unit')}</Text>
</View>
<Text style={styles.cardSubtext}>
{Math.round(activeEnergyBurned)}
{Math.round(activeEnergyBurned)}{t('fitnessRingsDetail.cards.activeCalories.unit')}
</Text>
{renderBarChart(
hourlyCaloriesData.map(h => h.calories),
Math.max(activeEnergyBurnedGoal / 24, 1),
'#FF3B30',
'千卡'
t('fitnessRingsDetail.cards.activeCalories.unit')
)}
</View>
{/* 锻炼分钟卡片 */}
<View style={styles.metricCard}>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}></Text>
<Text style={styles.cardTitle}>{t('fitnessRingsDetail.cards.exerciseMinutes.title')}</Text>
<TouchableOpacity style={styles.helpButton}>
<Text style={styles.helpIcon}>?</Text>
</TouchableOpacity>
@@ -418,18 +428,18 @@ export default function FitnessRingsDetailScreen() {
<Text style={[styles.valueText, { color: '#FF9500' }]}>
{Math.round(appleExerciseTime)}/{appleExerciseTimeGoal}
</Text>
<Text style={styles.unitText}></Text>
<Text style={styles.unitText}>{t('fitnessRingsDetail.cards.exerciseMinutes.unit')}</Text>
</View>
<Text style={styles.cardSubtext}>
{Math.round(appleExerciseTime)}
{Math.round(appleExerciseTime)}{t('fitnessRingsDetail.cards.exerciseMinutes.unit')}
</Text>
{renderBarChart(
hourlyExerciseData.map(h => h.minutes),
Math.max(appleExerciseTimeGoal / 8, 1),
'#FF9500',
'分钟'
t('fitnessRingsDetail.cards.exerciseMinutes.unit')
)}
{/* 锻炼分钟说明 */}
@@ -450,15 +460,15 @@ export default function FitnessRingsDetailScreen() {
}
]}
>
<Text style={styles.exerciseTitle}>:</Text>
<Text style={styles.exerciseTitle}>{t('fitnessRingsDetail.cards.exerciseMinutes.info.title')}</Text>
<Text style={styles.exerciseDesc}>
"快走"
{t('fitnessRingsDetail.cards.exerciseMinutes.info.description')}
</Text>
<Text style={styles.exerciseRecommendation}>
30
{t('fitnessRingsDetail.cards.exerciseMinutes.info.recommendation')}
</Text>
<TouchableOpacity style={styles.knowButton} onPress={handleKnowButtonPress}>
<Text style={styles.knowButtonText}></Text>
<Text style={styles.knowButtonText}>{t('fitnessRingsDetail.cards.exerciseMinutes.info.knowButton')}</Text>
</TouchableOpacity>
</Animated.View>
)}
@@ -467,7 +477,7 @@ export default function FitnessRingsDetailScreen() {
{/* 活动小时数卡片 */}
<View style={styles.metricCard}>
<View style={styles.cardHeader}>
<Text style={styles.cardTitle}></Text>
<Text style={styles.cardTitle}>{t('fitnessRingsDetail.cards.standHours.title')}</Text>
<TouchableOpacity style={styles.helpButton}>
<Text style={styles.helpIcon}>?</Text>
</TouchableOpacity>
@@ -477,18 +487,18 @@ export default function FitnessRingsDetailScreen() {
<Text style={[styles.valueText, { color: '#007AFF' }]}>
{Math.round(appleStandHours)}/{appleStandHoursGoal}
</Text>
<Text style={styles.unitText}></Text>
<Text style={styles.unitText}>{t('fitnessRingsDetail.cards.standHours.unit')}</Text>
</View>
<Text style={styles.cardSubtext}>
{Math.round(appleStandHours)}
{Math.round(appleStandHours)}{t('fitnessRingsDetail.cards.standHours.unit')}
</Text>
{renderBarChart(
hourlyStandData.map(h => h.hasStood),
1,
'#007AFF',
'小时'
t('fitnessRingsDetail.cards.standHours.unit')
)}
</View>
</View>
@@ -536,9 +546,9 @@ export default function FitnessRingsDetailScreen() {
{/* 周闭环天数统计 */}
<View style={styles.statsContainer}>
<View style={styles.statRow}>
<Text style={[styles.statLabel, { color: Colors[colorScheme ?? 'light'].text }]}></Text>
<Text style={[styles.statLabel, { color: Colors[colorScheme ?? 'light'].text }]}>{t('fitnessRingsDetail.stats.weeklyClosedRings')}</Text>
<View style={styles.statValue}>
<Text style={[styles.statNumber, { color: Colors[colorScheme ?? 'light'].text }]}>{getClosedRingCount()}</Text>
<Text style={[styles.statNumber, { color: Colors[colorScheme ?? 'light'].text }]}>{getClosedRingCount()}{t('fitnessRingsDetail.stats.daysUnit')}</Text>
</View>
</View>
</View>
@@ -575,12 +585,12 @@ export default function FitnessRingsDetailScreen() {
{Platform.OS === 'ios' && (
<View style={styles.modalActions}>
<Pressable onPress={closeDatePicker} style={[styles.modalBtn]}>
<Text style={styles.modalBtnText}></Text>
<Text style={styles.modalBtnText}>{t('fitnessRingsDetail.datePicker.cancel')}</Text>
</Pressable>
<Pressable onPress={() => {
onConfirmDate(pickerDate);
}} style={[styles.modalBtn, styles.modalBtnPrimary]}>
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary]}></Text>
<Text style={[styles.modalBtnText, styles.modalBtnTextPrimary]}>{t('fitnessRingsDetail.datePicker.confirm')}</Text>
</Pressable>
</View>
)}