Add Chinese translations for medication management and personal settings

- Introduced new translation files for medication, personal, and weight management in Chinese.
- Updated the main index file to include the new translation modules.
- Enhanced the medication type definitions to include 'ointment'.
- Refactored workout type labels to utilize i18n for better localization support.
- Improved sleep quality descriptions and recommendations with i18n integration.
This commit is contained in:
richarjiang
2025-11-28 17:29:51 +08:00
parent fbe0c92f0f
commit bca6670390
42 changed files with 7972 additions and 6632 deletions

View File

@@ -1,5 +1,6 @@
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { Colors, palette } from '@/constants/Colors';
import { useI18n } from '@/hooks/useI18n';
import { getMedicationRecognitionStatus } from '@/services/medications';
import { MedicationRecognitionTask } from '@/types/medication';
import { Ionicons } from '@expo/vector-icons';
@@ -13,14 +14,15 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const STATUS_STEPS: { key: MedicationRecognitionTask['status']; label: string }[] = [
{ key: 'analyzing_product', label: '正在进行产品分析...' },
{ key: 'analyzing_suitability', label: '正在检测适宜人群...' },
{ key: 'analyzing_ingredients', label: '正在评估成分信息...' },
{ key: 'analyzing_effects', label: '正在生成安全建议...' },
const STEP_KEYS: MedicationRecognitionTask['status'][] = [
'analyzing_product',
'analyzing_suitability',
'analyzing_ingredients',
'analyzing_effects',
];
export default function MedicationAiProgressScreen() {
const { t } = useI18n();
const { taskId, cover } = useLocalSearchParams<{ taskId?: string; cover?: string }>();
const insets = useSafeAreaInsets();
const [task, setTask] = useState<MedicationRecognitionTask | null>(null);
@@ -35,11 +37,16 @@ export default function MedicationAiProgressScreen() {
const floatAnim = useRef(new Animated.Value(0)).current;
const opacityAnim = useRef(new Animated.Value(0.3)).current;
const steps = useMemo(() => STEP_KEYS.map(key => ({
key,
label: t(`medications.aiProgress.steps.${key}`)
})), [t]);
const currentStepIndex = useMemo(() => {
if (!task) return 0;
const idx = STATUS_STEPS.findIndex((step) => step.key === task.status);
const idx = STEP_KEYS.indexOf(task.status as any);
if (idx >= 0) return idx;
if (task.status === 'completed') return STATUS_STEPS.length;
if (task.status === 'completed') return STEP_KEYS.length;
return 0;
}, [task]);
@@ -77,12 +84,12 @@ export default function MedicationAiProgressScreen() {
pollingTimerRef.current = null;
}
// 显示错误提示弹窗
setErrorMessage(data.errorMessage || '识别失败,请重新拍摄');
setErrorMessage(data.errorMessage || t('medications.aiProgress.errors.default'));
setShowErrorModal(true);
}
} catch (err: any) {
console.error('[MEDICATION_AI] status failed', err);
setError(err?.message || '查询失败,请稍后再试');
setError(err?.message || t('medications.aiProgress.errors.queryFailed'));
} finally {
setLoading(false);
}
@@ -148,12 +155,12 @@ export default function MedicationAiProgressScreen() {
};
}, []);
const progress = task?.progress ?? Math.min(100, (currentStepIndex / STATUS_STEPS.length) * 100 + 10);
const progress = task?.progress ?? Math.min(100, (currentStepIndex / steps.length) * 100 + 10);
return (
<SafeAreaView style={styles.container}>
<LinearGradient colors={['#fdfdfd', '#f3f6fb']} style={StyleSheet.absoluteFill} />
<HeaderBar title="识别中" onBack={() => router.back()} transparent />
<LinearGradient colors={[palette.gray[25], palette.gray[50]]} style={StyleSheet.absoluteFill} />
<HeaderBar title={t('medications.aiProgress.title')} onBack={() => router.back()} transparent />
<View style={{ height: insets.top }} />
<View style={styles.heroCard}>
@@ -172,7 +179,7 @@ export default function MedicationAiProgressScreen() {
{/* 渐变蒙版边框,增加视觉层次 */}
<LinearGradient
colors={['rgba(14, 165, 233, 0.3)', 'rgba(6, 182, 212, 0.2)', 'transparent']}
colors={[Colors.light.primary + '4D', Colors.light.accentPurple + '33', 'transparent']}
style={styles.gradientBorder}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 1 }}
@@ -206,7 +213,7 @@ export default function MedicationAiProgressScreen() {
</View>
<View style={styles.stepList}>
{STATUS_STEPS.map((step, index) => {
{steps.map((step, index) => {
const active = index === currentStepIndex;
const done = index < currentStepIndex;
return (
@@ -221,7 +228,7 @@ export default function MedicationAiProgressScreen() {
{task?.status === 'completed' && (
<View style={styles.stepRow}>
<View style={[styles.bullet, styles.bulletDone]} />
<Text style={[styles.stepLabel, styles.stepLabelDone]}>...</Text>
<Text style={[styles.stepLabel, styles.stepLabelDone]}>{t('medications.aiProgress.steps.completed')}</Text>
</View>
)}
</View>
@@ -251,7 +258,7 @@ export default function MedicationAiProgressScreen() {
<View style={styles.errorModalContent}>
{/* 标题 */}
<Text style={styles.errorModalTitle}></Text>
<Text style={styles.errorModalTitle}>{t('medications.aiProgress.modal.title')}</Text>
{/* 提示信息 */}
<View style={styles.errorMessageBox}>
@@ -268,29 +275,29 @@ export default function MedicationAiProgressScreen() {
<GlassView
style={styles.retryButton}
glassEffectStyle="regular"
tintColor="rgba(14, 165, 233, 0.9)"
tintColor={Colors.light.primary}
isInteractive={true}
>
<LinearGradient
colors={['rgba(14, 165, 233, 0.95)', 'rgba(6, 182, 212, 0.95)']}
colors={[Colors.light.primary, Colors.light.accentPurple]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={styles.retryButtonGradient}
>
<Ionicons name="camera" size={20} color="#FFFFFF" style={{ marginRight: 8 }} />
<Text style={styles.retryButtonText}></Text>
<Text style={styles.retryButtonText}>{t('medications.aiProgress.modal.retry')}</Text>
</LinearGradient>
</GlassView>
) : (
<View style={styles.retryButton}>
<LinearGradient
colors={['#0ea5e9', '#06b6d4']}
colors={[Colors.light.primary, Colors.light.accentPurple]}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={styles.retryButtonGradient}
>
<Ionicons name="camera" size={20} color="#FFFFFF" style={{ marginRight: 8 }} />
<Text style={styles.retryButtonText}></Text>
<Text style={styles.retryButtonText}>{t('medications.aiProgress.modal.retry')}</Text>
</LinearGradient>
</View>
)}
@@ -311,9 +318,9 @@ const styles = StyleSheet.create({
marginHorizontal: 20,
marginTop: 24,
borderRadius: 24,
backgroundColor: '#fff',
backgroundColor: Colors.light.card,
padding: 16,
shadowColor: '#0f172a',
shadowColor: Colors.light.text,
shadowOpacity: 0.08,
shadowRadius: 18,
shadowOffset: { width: 0, height: 10 },
@@ -322,7 +329,7 @@ const styles = StyleSheet.create({
height: 230,
borderRadius: 18,
overflow: 'hidden',
backgroundColor: '#e2e8f0',
backgroundColor: palette.gray[50],
},
heroImage: {
width: '100%',
@@ -330,7 +337,7 @@ const styles = StyleSheet.create({
},
heroPlaceholder: {
flex: 1,
backgroundColor: '#e2e8f0',
backgroundColor: palette.gray[50],
},
// 深色蒙版层,让点阵更清晰可见
overlayMask: {
@@ -368,15 +375,15 @@ const styles = StyleSheet.create({
width: 5,
height: 5,
borderRadius: 2.5,
backgroundColor: '#FFFFFF',
shadowColor: '#0ea5e9',
backgroundColor: Colors.light.background,
shadowColor: Colors.light.primary,
shadowOpacity: 0.9,
shadowRadius: 6,
shadowOffset: { width: 0, height: 0 },
},
progressRow: {
height: 8,
backgroundColor: '#f1f5f9',
backgroundColor: palette.gray[50],
borderRadius: 10,
marginTop: 14,
overflow: 'hidden',
@@ -384,13 +391,13 @@ const styles = StyleSheet.create({
progressBar: {
height: '100%',
borderRadius: 10,
backgroundColor: '#0ea5e9',
backgroundColor: Colors.light.primary,
},
progressText: {
marginTop: 8,
fontSize: 14,
fontWeight: '700',
color: '#0f172a',
color: Colors.light.text,
textAlign: 'right',
},
stepList: {
@@ -407,24 +414,24 @@ const styles = StyleSheet.create({
width: 14,
height: 14,
borderRadius: 7,
backgroundColor: '#e2e8f0',
backgroundColor: palette.gray[50],
},
bulletActive: {
backgroundColor: '#0ea5e9',
backgroundColor: Colors.light.primary,
},
bulletDone: {
backgroundColor: '#22c55e',
backgroundColor: Colors.light.success,
},
stepLabel: {
fontSize: 15,
color: '#94a3b8',
color: Colors.light.textMuted,
},
stepLabelActive: {
color: '#0f172a',
color: Colors.light.text,
fontWeight: '700',
},
stepLabelDone: {
color: '#16a34a',
color: Colors.light.successDark,
fontWeight: '700',
},
loadingBox: {
@@ -433,7 +440,7 @@ const styles = StyleSheet.create({
gap: 12,
},
errorText: {
color: '#ef4444',
color: Colors.light.danger,
fontSize: 14,
},
// Modal 样式
@@ -445,10 +452,10 @@ const styles = StyleSheet.create({
},
errorModalContainer: {
width: SCREEN_WIDTH - 48,
backgroundColor: '#FFFFFF',
backgroundColor: Colors.light.card,
borderRadius: 28,
overflow: 'hidden',
shadowColor: '#0ea5e9',
shadowColor: Colors.light.primary,
shadowOpacity: 0.15,
shadowRadius: 24,
shadowOffset: { width: 0, height: 8 },
@@ -465,36 +472,36 @@ const styles = StyleSheet.create({
width: 96,
height: 96,
borderRadius: 48,
backgroundColor: 'rgba(14, 165, 233, 0.08)',
backgroundColor: palette.purple[50],
alignItems: 'center',
justifyContent: 'center',
},
errorModalTitle: {
fontSize: 22,
fontWeight: '700',
color: '#0f172a',
color: Colors.light.text,
marginBottom: 16,
textAlign: 'center',
},
errorMessageBox: {
backgroundColor: '#f0f9ff',
backgroundColor: palette.purple[25],
borderRadius: 16,
padding: 20,
marginBottom: 28,
width: '100%',
borderWidth: 1,
borderColor: 'rgba(14, 165, 233, 0.2)',
borderColor: palette.purple[200],
},
errorMessageText: {
fontSize: 15,
lineHeight: 24,
color: '#475569',
color: Colors.light.textSecondary,
textAlign: 'center',
},
retryButton: {
borderRadius: 16,
overflow: 'hidden',
shadowColor: '#0ea5e9',
shadowColor: Colors.light.primary,
shadowOpacity: 0.25,
shadowRadius: 12,
shadowOffset: { width: 0, height: 6 },
@@ -509,6 +516,6 @@ const styles = StyleSheet.create({
retryButtonText: {
fontSize: 18,
fontWeight: '700',
color: '#FFFFFF',
color: Colors.light.onPrimary,
},
});