feat(i18n): 全面实现应用核心功能模块的国际化支持
- 新增 i18n 翻译资源,覆盖睡眠、饮水、体重、锻炼、用药 AI 识别、步数、健身圆环、基础代谢及设置等核心模块 - 重构相关页面及组件(如 SleepDetail, WaterDetail, WorkoutHistory 等)使用 `useI18n` 钩子替换硬编码文本 - 升级 `utils/date` 工具库与 `DateSelector` 组件,支持基于语言环境的日期格式化与显示 - 完善登录页、注销流程及权限申请弹窗的双语提示信息 - 优化部分页面的 UI 细节与字体样式以适配多语言显示
This commit is contained in:
@@ -5,6 +5,7 @@ import { Colors } from '@/constants/Colors';
|
||||
import { getTabBarBottomPadding } from '@/constants/TabBar';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||
import { appStoreReviewService } from '@/services/appStoreReview';
|
||||
import { deleteWeightRecord, fetchWeightHistory, updateUserProfile, updateWeightRecord, WeightHistoryItem } from '@/store/userSlice';
|
||||
@@ -24,6 +25,7 @@ import {
|
||||
} from 'react-native';
|
||||
|
||||
export default function WeightRecordsPage() {
|
||||
const { t } = useI18n();
|
||||
const safeAreaTop = useSafeAreaTop()
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -43,7 +45,7 @@ export default function WeightRecordsPage() {
|
||||
try {
|
||||
await dispatch(fetchWeightHistory() as any);
|
||||
} catch (error) {
|
||||
console.error('加载体重历史失败:', error);
|
||||
console.error(t('weightRecords.loadingHistory'), error);
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
@@ -92,15 +94,15 @@ export default function WeightRecordsPage() {
|
||||
await dispatch(deleteWeightRecord(id) as any);
|
||||
await loadWeightHistory();
|
||||
} catch (error) {
|
||||
console.error('删除体重记录失败:', error);
|
||||
Alert.alert('错误', '删除体重记录失败,请重试');
|
||||
console.error(t('weightRecords.alerts.deleteFailed'), error);
|
||||
Alert.alert('错误', t('weightRecords.alerts.deleteFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleWeightSave = async () => {
|
||||
const weight = parseFloat(inputWeight);
|
||||
if (isNaN(weight) || weight <= 0 || weight > 500) {
|
||||
alert('请输入有效的体重值(0-500kg)');
|
||||
alert(t('weightRecords.alerts.invalidWeight'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -130,8 +132,8 @@ export default function WeightRecordsPage() {
|
||||
setEditingRecord(null);
|
||||
await loadWeightHistory();
|
||||
} catch (error) {
|
||||
console.error('保存体重失败:', error);
|
||||
Alert.alert('错误', '保存体重失败,请重试');
|
||||
console.error(t('weightRecords.alerts.saveFailed'), error);
|
||||
Alert.alert('错误', t('weightRecords.alerts.saveFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -190,7 +192,7 @@ export default function WeightRecordsPage() {
|
||||
/>
|
||||
|
||||
<HeaderBar
|
||||
title="体重记录"
|
||||
title={t('weightRecords.title')}
|
||||
right={<TouchableOpacity onPress={handleAddWeight} style={styles.addButton}>
|
||||
<Ionicons name="add" size={24} color="#192126" />
|
||||
</TouchableOpacity>}
|
||||
@@ -204,27 +206,27 @@ export default function WeightRecordsPage() {
|
||||
<View style={styles.statsRow}>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={styles.statValue}>{totalWeightLoss.toFixed(1)}kg</Text>
|
||||
<Text style={styles.statLabel}>累计减重</Text>
|
||||
<Text style={styles.statLabel}>{t('weightRecords.stats.totalLoss')}</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={styles.statValue}>{currentWeight.toFixed(1)}kg</Text>
|
||||
<Text style={styles.statLabel}>当前体重</Text>
|
||||
<Text style={styles.statLabel}>{t('weightRecords.stats.currentWeight')}</Text>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={styles.statValue}>{initialWeight.toFixed(1)}kg</Text>
|
||||
<View style={styles.statLabelContainer}>
|
||||
<Text style={styles.statLabel}>初始体重</Text>
|
||||
<Text style={styles.statLabel}>{t('weightRecords.stats.initialWeight')}</Text>
|
||||
<TouchableOpacity onPress={handleEditInitialWeight} style={styles.editIcon}>
|
||||
<Ionicons name="create-outline" size={14} color="#FF9500" />
|
||||
<Ionicons name="create-outline" size={12} color="#FF9500" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.statItem}>
|
||||
<Text style={styles.statValue}>{targetWeight.toFixed(1)}kg</Text>
|
||||
<View style={styles.statLabelContainer}>
|
||||
<Text style={styles.statLabel}>目标体重</Text>
|
||||
<Text style={styles.statLabel}>{t('weightRecords.stats.targetWeight')}</Text>
|
||||
<TouchableOpacity onPress={handleEditTargetWeight} style={styles.editIcon}>
|
||||
<Ionicons name="create-outline" size={14} color="#FF9500" />
|
||||
<Ionicons name="create-outline" size={12} color="#FF9500" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -282,8 +284,8 @@ export default function WeightRecordsPage() {
|
||||
) : (
|
||||
<View style={styles.emptyContainer}>
|
||||
<View style={styles.emptyContent}>
|
||||
<Text style={styles.emptyText}>暂无体重记录</Text>
|
||||
<Text style={styles.emptySubtext}>点击右上角添加按钮开始记录</Text>
|
||||
<Text style={styles.emptyText}>{t('weightRecords.empty.title')}</Text>
|
||||
<Text style={styles.emptySubtext}>{t('weightRecords.empty.subtitle')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
@@ -309,10 +311,10 @@ export default function WeightRecordsPage() {
|
||||
<Ionicons name="close" size={24} color={themeColors.text} />
|
||||
</TouchableOpacity>
|
||||
<Text style={[styles.modalTitle, { color: themeColors.text }]}>
|
||||
{pickerType === 'current' && '记录体重'}
|
||||
{pickerType === 'initial' && '编辑初始体重'}
|
||||
{pickerType === 'target' && '编辑目标体重'}
|
||||
{pickerType === 'edit' && '编辑体重记录'}
|
||||
{pickerType === 'current' && t('weightRecords.modal.recordWeight')}
|
||||
{pickerType === 'initial' && t('weightRecords.modal.editInitialWeight')}
|
||||
{pickerType === 'target' && t('weightRecords.modal.editTargetWeight')}
|
||||
{pickerType === 'edit' && t('weightRecords.modal.editRecord')}
|
||||
</Text>
|
||||
<View style={{ width: 24 }} />
|
||||
</View>
|
||||
@@ -329,21 +331,21 @@ export default function WeightRecordsPage() {
|
||||
</View>
|
||||
<View style={styles.inputWrapper}>
|
||||
<Text style={[styles.weightDisplay, { color: inputWeight ? themeColors.text : themeColors.textSecondary }]}>
|
||||
{inputWeight || '输入体重'}
|
||||
{inputWeight || t('weightRecords.modal.inputPlaceholder')}
|
||||
</Text>
|
||||
<Text style={[styles.unitLabel, { color: themeColors.textSecondary }]}>kg</Text>
|
||||
<Text style={[styles.unitLabel, { color: themeColors.textSecondary }]}>{t('weightRecords.modal.unit')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Weight Range Hint */}
|
||||
<Text style={[styles.hintText, { color: themeColors.textSecondary }]}>
|
||||
请输入 0-500 之间的数值,支持小数
|
||||
{t('weightRecords.modal.inputHint')}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
{/* Quick Selection */}
|
||||
<View style={styles.quickSelectionSection}>
|
||||
<Text style={[styles.quickSelectionTitle, { color: themeColors.text }]}>快速选择</Text>
|
||||
<Text style={[styles.quickSelectionTitle, { color: themeColors.text }]}>{t('weightRecords.modal.quickSelection')}</Text>
|
||||
<View style={styles.quickButtons}>
|
||||
{[50, 60, 70, 80, 90].map((weight) => (
|
||||
<TouchableOpacity
|
||||
@@ -358,7 +360,7 @@ export default function WeightRecordsPage() {
|
||||
styles.quickButtonText,
|
||||
inputWeight === weight.toString() && styles.quickButtonTextSelected
|
||||
]}>
|
||||
{weight}kg
|
||||
{weight}{t('weightRecords.modal.unit')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
@@ -386,7 +388,7 @@ export default function WeightRecordsPage() {
|
||||
onPress={handleWeightSave}
|
||||
disabled={!inputWeight.trim()}
|
||||
>
|
||||
<Text style={styles.saveButtonText}>确定</Text>
|
||||
<Text style={styles.saveButtonText}>{t('weightRecords.modal.confirm')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
@@ -461,7 +463,7 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
statValue: {
|
||||
fontSize: 16,
|
||||
fontSize: 14,
|
||||
fontWeight: '800',
|
||||
color: '#192126',
|
||||
marginBottom: 4,
|
||||
@@ -475,6 +477,7 @@ const styles = StyleSheet.create({
|
||||
fontSize: 12,
|
||||
color: '#687076',
|
||||
marginRight: 4,
|
||||
textAlign: 'center'
|
||||
},
|
||||
editIcon: {
|
||||
padding: 2,
|
||||
|
||||
Reference in New Issue
Block a user