feat(i18n): 全面实现应用核心功能模块的国际化支持
- 新增 i18n 翻译资源,覆盖睡眠、饮水、体重、锻炼、用药 AI 识别、步数、健身圆环、基础代谢及设置等核心模块 - 重构相关页面及组件(如 SleepDetail, WaterDetail, WorkoutHistory 等)使用 `useI18n` 钩子替换硬编码文本 - 升级 `utils/date` 工具库与 `DateSelector` 组件,支持基于语言环境的日期格式化与显示 - 完善登录页、注销流程及权限申请弹窗的双语提示信息 - 优化部分页面的 UI 细节与字体样式以适配多语言显示
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user