feat(i18n): 全面实现应用核心功能模块的国际化支持
- 新增 i18n 翻译资源,覆盖睡眠、饮水、体重、锻炼、用药 AI 识别、步数、健身圆环、基础代谢及设置等核心模块 - 重构相关页面及组件(如 SleepDetail, WaterDetail, WorkoutHistory 等)使用 `useI18n` 钩子替换硬编码文本 - 升级 `utils/date` 工具库与 `DateSelector` 组件,支持基于语言环境的日期格式化与显示 - 完善登录页、注销流程及权限申请弹窗的双语提示信息 - 优化部分页面的 UI 细节与字体样式以适配多语言显示
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import {
|
||||
HeartRateZoneStat,
|
||||
WorkoutDetailMetrics,
|
||||
@@ -59,6 +60,7 @@ export function WorkoutDetailModal({
|
||||
onRetry,
|
||||
errorMessage,
|
||||
}: WorkoutDetailModalProps) {
|
||||
const { t } = useI18n();
|
||||
const animation = useRef(new Animated.Value(visible ? 1 : 0)).current;
|
||||
const [isMounted, setIsMounted] = useState(visible);
|
||||
const [showIntensityInfo, setShowIntensityInfo] = useState(false);
|
||||
@@ -229,26 +231,26 @@ export function WorkoutDetailModal({
|
||||
{loading ? (
|
||||
<View style={styles.loadingBlock}>
|
||||
<ActivityIndicator color="#5C55FF" />
|
||||
<Text style={styles.loadingLabel}>正在加载锻炼详情...</Text>
|
||||
<Text style={styles.loadingLabel}>{t('workoutDetail.loading')}</Text>
|
||||
</View>
|
||||
) : metrics ? (
|
||||
<>
|
||||
<View style={styles.metricsRow}>
|
||||
<View style={styles.metricItem}>
|
||||
<Text style={styles.metricTitle}>体能训练时间</Text>
|
||||
<Text style={styles.metricTitle}>{t('workoutDetail.metrics.duration')}</Text>
|
||||
<Text style={styles.metricValue}>{metrics.durationLabel}</Text>
|
||||
</View>
|
||||
<View style={styles.metricItem}>
|
||||
<Text style={styles.metricTitle}>运动热量</Text>
|
||||
<Text style={styles.metricTitle}>{t('workoutDetail.metrics.calories')}</Text>
|
||||
<Text style={styles.metricValue}>
|
||||
{metrics.calories != null ? `${metrics.calories} 千卡` : '--'}
|
||||
{metrics.calories != null ? `${metrics.calories} ${t('workoutDetail.metrics.caloriesUnit')}` : '--'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.metricsRow}>
|
||||
<View style={styles.metricItem}>
|
||||
<View style={styles.metricTitleRow}>
|
||||
<Text style={styles.metricTitle}>运动强度</Text>
|
||||
<Text style={styles.metricTitle}>{t('workoutDetail.metrics.intensity')}</Text>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowIntensityInfo(true)}
|
||||
style={styles.metricInfoButton}
|
||||
@@ -262,9 +264,9 @@ export function WorkoutDetailModal({
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.metricItem}>
|
||||
<Text style={styles.metricTitle}>平均心率</Text>
|
||||
<Text style={styles.metricTitle}>{t('workoutDetail.metrics.averageHeartRate')}</Text>
|
||||
<Text style={styles.metricValue}>
|
||||
{metrics.averageHeartRate != null ? `${metrics.averageHeartRate} 次/分` : '--'}
|
||||
{metrics.averageHeartRate != null ? `${metrics.averageHeartRate} ${t('workoutDetail.metrics.heartRateUnit')}` : '--'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -275,11 +277,11 @@ export function WorkoutDetailModal({
|
||||
) : (
|
||||
<View style={styles.errorBlock}>
|
||||
<Text style={styles.errorText}>
|
||||
{errorMessage || '未能获取到完整的锻炼详情'}
|
||||
{errorMessage || t('workoutDetail.errors.loadFailed')}
|
||||
</Text>
|
||||
{onRetry ? (
|
||||
<TouchableOpacity style={styles.retryButton} onPress={onRetry}>
|
||||
<Text style={styles.retryButtonText}>重新加载</Text>
|
||||
<Text style={styles.retryButtonText}>{t('workoutDetail.retry')}</Text>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
</View>
|
||||
@@ -288,7 +290,7 @@ export function WorkoutDetailModal({
|
||||
|
||||
<View style={styles.section}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={styles.sectionTitle}>心率范围</Text>
|
||||
<Text style={styles.sectionTitle}>{t('workoutDetail.sections.heartRateRange')}</Text>
|
||||
</View>
|
||||
|
||||
{loading ? (
|
||||
@@ -299,21 +301,21 @@ export function WorkoutDetailModal({
|
||||
<>
|
||||
<View style={styles.heartRateSummaryRow}>
|
||||
<View style={styles.heartRateStat}>
|
||||
<Text style={styles.statLabel}>平均心率</Text>
|
||||
<Text style={styles.statLabel}>{t('workoutDetail.sections.averageHeartRate')}</Text>
|
||||
<Text style={styles.statValue}>
|
||||
{metrics.averageHeartRate != null ? `${metrics.averageHeartRate}次/分` : '--'}
|
||||
{metrics.averageHeartRate != null ? `${metrics.averageHeartRate} ${t('workoutDetail.sections.heartRateUnit')}` : '--'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.heartRateStat}>
|
||||
<Text style={styles.statLabel}>最高心率</Text>
|
||||
<Text style={styles.statLabel}>{t('workoutDetail.sections.maximumHeartRate')}</Text>
|
||||
<Text style={styles.statValue}>
|
||||
{metrics.maximumHeartRate != null ? `${metrics.maximumHeartRate}次/分` : '--'}
|
||||
{metrics.maximumHeartRate != null ? `${metrics.maximumHeartRate} ${t('workoutDetail.sections.heartRateUnit')}` : '--'}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.heartRateStat}>
|
||||
<Text style={styles.statLabel}>最低心率</Text>
|
||||
<Text style={styles.statLabel}>{t('workoutDetail.sections.minimumHeartRate')}</Text>
|
||||
<Text style={styles.statValue}>
|
||||
{metrics.minimumHeartRate != null ? `${metrics.minimumHeartRate}次/分` : '--'}
|
||||
{metrics.minimumHeartRate != null ? `${metrics.minimumHeartRate} ${t('workoutDetail.sections.heartRateUnit')}` : '--'}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -336,7 +338,7 @@ export function WorkoutDetailModal({
|
||||
width={Dimensions.get('window').width - 72}
|
||||
height={220}
|
||||
fromZero={false}
|
||||
yAxisSuffix="次/分"
|
||||
yAxisSuffix={t('workoutDetail.sections.heartRateUnit')}
|
||||
withInnerLines={false}
|
||||
bezier
|
||||
chartConfig={{
|
||||
@@ -360,20 +362,20 @@ export function WorkoutDetailModal({
|
||||
) : (
|
||||
<View style={styles.chartEmpty}>
|
||||
<MaterialCommunityIcons name="chart-line-variant" size={32} color="#C5CBE2" />
|
||||
<Text style={styles.chartEmptyText}>图表组件不可用,无法展示心率曲线</Text>
|
||||
<Text style={styles.chartEmptyText}>{t('workoutDetail.chart.unavailable')}</Text>
|
||||
</View>
|
||||
)
|
||||
) : (
|
||||
<View style={styles.chartEmpty}>
|
||||
<MaterialCommunityIcons name="heart-off-outline" size={32} color="#C5CBE2" />
|
||||
<Text style={styles.chartEmptyText}>暂无心率采样数据</Text>
|
||||
<Text style={styles.chartEmptyText}>{t('workoutDetail.chart.noData')}</Text>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<View style={styles.sectionError}>
|
||||
<Text style={styles.errorTextSmall}>
|
||||
{errorMessage || '未获取到心率数据'}
|
||||
{errorMessage || t('workoutDetail.errors.noHeartRateData')}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -381,7 +383,7 @@ export function WorkoutDetailModal({
|
||||
|
||||
<View style={styles.section}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={styles.sectionTitle}>心率训练区间</Text>
|
||||
<Text style={styles.sectionTitle}>{t('workoutDetail.sections.heartRateZones')}</Text>
|
||||
</View>
|
||||
|
||||
{loading ? (
|
||||
@@ -391,7 +393,7 @@ export function WorkoutDetailModal({
|
||||
) : metrics ? (
|
||||
metrics.heartRateZones.map(renderHeartRateZone)
|
||||
) : (
|
||||
<Text style={styles.errorTextSmall}>暂无区间统计</Text>
|
||||
<Text style={styles.errorTextSmall}>{t('workoutDetail.errors.noZoneStats')}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
@@ -410,36 +412,36 @@ export function WorkoutDetailModal({
|
||||
<TouchableWithoutFeedback onPress={() => { }}>
|
||||
<View style={styles.intensityInfoSheet}>
|
||||
<View style={styles.intensityHandle} />
|
||||
<Text style={styles.intensityInfoTitle}>什么是运动强度?</Text>
|
||||
<Text style={styles.intensityInfoTitle}>{t('workoutDetail.intensityInfo.title')}</Text>
|
||||
<Text style={styles.intensityInfoText}>
|
||||
运动强度是你完成一项任务所用的能量估算,是衡量锻炼和其他日常活动能耗强度的指标,单位为 MET(千卡/(千克·小时))。
|
||||
{t('workoutDetail.intensityInfo.description1')}
|
||||
</Text>
|
||||
<Text style={styles.intensityInfoText}>
|
||||
因为每个人的代谢状况不同,MET 以身体的静息能耗作为参考,便于衡量不同活动的强度。
|
||||
{t('workoutDetail.intensityInfo.description2')}
|
||||
</Text>
|
||||
<Text style={styles.intensityInfoText}>
|
||||
例如:散步(约 3 km/h)相当于 2 METs,意味着它需要消耗静息状态 2 倍的能量。
|
||||
{t('workoutDetail.intensityInfo.description3')}
|
||||
</Text>
|
||||
<Text style={styles.intensityInfoText}>
|
||||
注:当设备未提供 METs 值时,系统会根据您的卡路里消耗和锻炼时长自动计算(使用70公斤估算体重)。
|
||||
{t('workoutDetail.intensityInfo.description4')}
|
||||
</Text>
|
||||
<View style={styles.intensityFormula}>
|
||||
<Text style={styles.intensityFormulaLabel}>运动强度计算公式</Text>
|
||||
<Text style={styles.intensityFormulaValue}>METs = 活动能耗(千卡/小时) ÷ 静息能耗(1 千卡/小时)</Text>
|
||||
<Text style={styles.intensityFormulaLabel}>{t('workoutDetail.intensityInfo.formula.title')}</Text>
|
||||
<Text style={styles.intensityFormulaValue}>{t('workoutDetail.intensityInfo.formula.value')}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.intensityLegend}>
|
||||
<View style={styles.intensityLegendRow}>
|
||||
<Text style={styles.intensityLegendRange}>{'< 3'}</Text>
|
||||
<Text style={[styles.intensityLegendLabel, styles.intensityLow]}>低强度活动</Text>
|
||||
<Text style={styles.intensityLegendRange}>{t('workoutDetail.intensityInfo.legend.low')}</Text>
|
||||
<Text style={[styles.intensityLegendLabel, styles.intensityLow]}>{t('workoutDetail.intensityInfo.legend.lowLabel')}</Text>
|
||||
</View>
|
||||
<View style={styles.intensityLegendRow}>
|
||||
<Text style={styles.intensityLegendRange}>3 - 6</Text>
|
||||
<Text style={[styles.intensityLegendLabel, styles.intensityMedium]}>中强度活动</Text>
|
||||
<Text style={styles.intensityLegendRange}>{t('workoutDetail.intensityInfo.legend.medium')}</Text>
|
||||
<Text style={[styles.intensityLegendLabel, styles.intensityMedium]}>{t('workoutDetail.intensityInfo.legend.mediumLabel')}</Text>
|
||||
</View>
|
||||
<View style={styles.intensityLegendRow}>
|
||||
<Text style={styles.intensityLegendRange}>{'≥ 6'}</Text>
|
||||
<Text style={[styles.intensityLegendLabel, styles.intensityHigh]}>高强度活动</Text>
|
||||
<Text style={styles.intensityLegendRange}>{t('workoutDetail.intensityInfo.legend.high')}</Text>
|
||||
<Text style={[styles.intensityLegendLabel, styles.intensityHigh]}>{t('workoutDetail.intensityInfo.legend.highLabel')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user