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:
@@ -559,19 +559,17 @@ export default function MedicationDetailScreen() {
|
||||
|
||||
const formLabel = medication ? t(`medications.manage.formLabels.${medication.form}`) : '';
|
||||
const dosageLabel = medication ? `${medication.dosageValue} ${medication.dosageUnit}` : '--';
|
||||
const startDateLabel = medication
|
||||
? dayjs(medication.startDate).format('YYYY年M月D日')
|
||||
: '--';
|
||||
|
||||
|
||||
// 计算服药周期显示
|
||||
const medicationPeriodLabel = useMemo(() => {
|
||||
if (!medication) return '--';
|
||||
|
||||
const startDate = dayjs(medication.startDate).format('YYYY年M月D日');
|
||||
const format = t('medications.detail.plan.dateFormat', { defaultValue: 'YYYY年M月D日' });
|
||||
const startDate = dayjs(medication.startDate).format(format);
|
||||
|
||||
if (medication.endDate) {
|
||||
// 有结束日期,显示开始日期到结束日期
|
||||
const endDate = dayjs(medication.endDate).format('YYYY年M月D日');
|
||||
const endDate = dayjs(medication.endDate).format(format);
|
||||
return `${startDate} - ${endDate}`;
|
||||
} else {
|
||||
// 没有结束日期,显示长期
|
||||
@@ -581,22 +579,23 @@ export default function MedicationDetailScreen() {
|
||||
|
||||
// 计算有效期显示
|
||||
const expiryDateLabel = useMemo(() => {
|
||||
if (!medication?.expiryDate) return '未设置';
|
||||
if (!medication?.expiryDate) return t('medications.detail.plan.expiryStatus.notSet');
|
||||
|
||||
const expiry = dayjs(medication.expiryDate);
|
||||
const today = dayjs();
|
||||
const daysUntilExpiry = expiry.diff(today, 'day');
|
||||
const format = t('medications.detail.plan.dateFormat', { defaultValue: 'YYYY年M月D日' });
|
||||
|
||||
if (daysUntilExpiry < 0) {
|
||||
return `${expiry.format('YYYY年M月D日')} (已过期)`;
|
||||
return `${expiry.format(format)} (${t('medications.detail.plan.expiryStatus.expired')})`;
|
||||
} else if (daysUntilExpiry === 0) {
|
||||
return `${expiry.format('YYYY年M月D日')} (今天到期)`;
|
||||
return `${expiry.format(format)} (${t('medications.detail.plan.expiryStatus.expiresToday')})`;
|
||||
} else if (daysUntilExpiry <= 30) {
|
||||
return `${expiry.format('YYYY年M月D日')} (${daysUntilExpiry}天后到期)`;
|
||||
return `${expiry.format(format)} (${t('medications.detail.plan.expiryStatus.expiresInDays', { days: daysUntilExpiry })})`;
|
||||
} else {
|
||||
return expiry.format('YYYY年M月D日');
|
||||
return expiry.format(format);
|
||||
}
|
||||
}, [medication?.expiryDate]);
|
||||
}, [medication?.expiryDate, t]);
|
||||
|
||||
const reminderTimes = medication?.medicationTimes?.length
|
||||
? medication.medicationTimes.join('、')
|
||||
@@ -617,8 +616,8 @@ export default function MedicationDetailScreen() {
|
||||
const aiActionLabel = aiAnalysisLoading
|
||||
? t('medications.detail.aiAnalysis.analyzingButton')
|
||||
: hasAiAnalysis
|
||||
? '重新分析'
|
||||
: '获取 AI 分析';
|
||||
? t('medications.detail.aiAnalysis.reanalyzeButton')
|
||||
: t('medications.detail.aiAnalysis.getAnalysisButton');
|
||||
|
||||
const handleOpenNoteModal = useCallback(() => {
|
||||
setNoteDraft(medication?.note ?? '');
|
||||
@@ -645,15 +644,15 @@ export default function MedicationDetailScreen() {
|
||||
const trimmed = nameDraft.trim();
|
||||
if (!trimmed) {
|
||||
Alert.alert(
|
||||
'提示',
|
||||
'药物名称不能为空'
|
||||
t('common.hint'),
|
||||
t('medications.detail.name.errors.empty')
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (Array.from(trimmed).length > 10) {
|
||||
Alert.alert(
|
||||
'提示',
|
||||
'药物名称不能超过10个字'
|
||||
t('common.hint'),
|
||||
t('medications.detail.name.errors.tooLong')
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -675,8 +674,8 @@ export default function MedicationDetailScreen() {
|
||||
} catch (err) {
|
||||
console.error('更新药物名称失败', err);
|
||||
Alert.alert(
|
||||
'提示',
|
||||
'名称更新失败,请稍后再试'
|
||||
t('common.hint'),
|
||||
t('medications.detail.name.errors.updateFailed')
|
||||
);
|
||||
} finally {
|
||||
setNameSaving(false);
|
||||
@@ -908,16 +907,17 @@ export default function MedicationDetailScreen() {
|
||||
const handleStartDatePress = useCallback(() => {
|
||||
if (!medication) return;
|
||||
|
||||
const startDate = dayjs(medication.startDate).format('YYYY年M月D日');
|
||||
const format = t('medications.detail.plan.dateFormat', { defaultValue: 'YYYY年M月D日' });
|
||||
const startDate = dayjs(medication.startDate).format(format);
|
||||
let message;
|
||||
if (medication.endDate) {
|
||||
const endDate = dayjs(medication.endDate).format('YYYY年M月D日');
|
||||
message = `从 ${startDate} 至 ${endDate}`;
|
||||
const endDate = dayjs(medication.endDate).format(format);
|
||||
message = t('medications.detail.plan.periodRange', { startDate, endDate, defaultValue: `从 ${startDate} 至 ${endDate}` });
|
||||
} else {
|
||||
message = `从 ${startDate} 至长期`;
|
||||
message = t('medications.detail.plan.periodLongTerm', { startDate, defaultValue: `从 ${startDate} 至长期` });
|
||||
}
|
||||
|
||||
Alert.alert('服药周期', message);
|
||||
Alert.alert(t('medications.detail.plan.period'), message);
|
||||
}, [medication, t]);
|
||||
|
||||
const handleTimePress = useCallback(() => {
|
||||
@@ -990,7 +990,7 @@ export default function MedicationDetailScreen() {
|
||||
|
||||
} catch (err) {
|
||||
console.error('更新有效期失败', err);
|
||||
Alert.alert('更新失败', '有效期更新失败,请稍后重试');
|
||||
Alert.alert(t('medications.detail.updateErrors.expiryDate'), t('medications.detail.updateErrors.expiryDateMessage'));
|
||||
} finally {
|
||||
setUpdatePending(false);
|
||||
}
|
||||
@@ -1185,7 +1185,7 @@ export default function MedicationDetailScreen() {
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.error('[MEDICATION_DETAIL] AI 草稿保存失败', err);
|
||||
Alert.alert('保存失败', err?.message || '请稍后再试');
|
||||
Alert.alert(t('medications.detail.aiDraft.saveError.title'), err?.message || t('medications.detail.aiDraft.saveError.message'));
|
||||
} finally {
|
||||
setAiDraftSaving(false);
|
||||
}
|
||||
@@ -1297,7 +1297,7 @@ export default function MedicationDetailScreen() {
|
||||
<View style={styles.photoUploadingIndicator}>
|
||||
<ActivityIndicator color={colors.primary} size="small" />
|
||||
<Text style={[styles.uploadingText, { color: '#FFF' }]}>
|
||||
上传中...
|
||||
{t('medications.detail.photo.uploading')}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -1415,12 +1415,12 @@ export default function MedicationDetailScreen() {
|
||||
</TouchableOpacity>
|
||||
</Section>
|
||||
|
||||
<Section title="AI 分析" color={colors.text}>
|
||||
<Section title={t('medications.detail.sections.aiAnalysis')} color={colors.text}>
|
||||
<View style={[styles.aiCardContainer, { backgroundColor: colors.surface }]}>
|
||||
<View style={styles.aiHeaderRow}>
|
||||
<View style={styles.aiHeaderLeft}>
|
||||
<Ionicons name="sparkles-outline" size={18} color={colors.primary} />
|
||||
<Text style={[styles.aiHeaderTitle, { color: colors.text }]}>分析结果</Text>
|
||||
<Text style={[styles.aiHeaderTitle, { color: colors.text }]}>{t('medications.detail.aiAnalysis.title')}</Text>
|
||||
</View>
|
||||
<View
|
||||
style={[
|
||||
@@ -1439,7 +1439,7 @@ export default function MedicationDetailScreen() {
|
||||
{ color: hasAiAnalysis ? '#16a34a' : aiAnalysisLocked ? '#ef4444' : colors.primary },
|
||||
]}
|
||||
>
|
||||
{hasAiAnalysis ? '已生成' : aiAnalysisLocked ? '会员专享' : '待生成'}
|
||||
{hasAiAnalysis ? t('medications.detail.aiAnalysis.status.generated') : aiAnalysisLocked ? t('medications.detail.aiAnalysis.status.memberExclusive') : t('medications.detail.aiAnalysis.status.pending')}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -1467,7 +1467,7 @@ export default function MedicationDetailScreen() {
|
||||
style={styles.aiScoreBadge}
|
||||
>
|
||||
<Ionicons name="thumbs-up-outline" size={14} color="#fff" />
|
||||
<Text style={styles.aiScoreBadgeText}>AI 推荐</Text>
|
||||
<Text style={styles.aiScoreBadgeText}>{t('medications.detail.aiAnalysis.recommendation')}</Text>
|
||||
</LinearGradient>
|
||||
)}
|
||||
</View>
|
||||
@@ -1476,7 +1476,7 @@ export default function MedicationDetailScreen() {
|
||||
{medication.name}
|
||||
</Text>
|
||||
<Text style={[styles.aiHeroSubtitle, { color: colors.textSecondary }]} numberOfLines={3}>
|
||||
{aiAnalysisResult?.mainUsage || '获取 AI 分析,快速了解适用人群、成分安全与使用建议。'}
|
||||
{aiAnalysisResult?.mainUsage || t('medications.detail.aiAnalysis.placeholder')}
|
||||
</Text>
|
||||
<View style={styles.aiChipRow}>
|
||||
<View style={styles.aiChip}>
|
||||
@@ -1527,7 +1527,7 @@ export default function MedicationDetailScreen() {
|
||||
<View style={styles.aiColumns}>
|
||||
<View style={[styles.aiBubbleCard, { backgroundColor: '#ECFEFF', borderColor: '#BAF2F4' }]}>
|
||||
<View style={styles.aiBubbleHeader}>
|
||||
<Text style={[styles.aiBubbleTitle, { color: '#0284c7' }]}>适合人群</Text>
|
||||
<Text style={[styles.aiBubbleTitle, { color: '#0284c7' }]}>{t('medications.detail.aiAnalysis.categories.suitableFor')}</Text>
|
||||
<Ionicons name="checkmark-circle" size={16} color="#0ea5e9" />
|
||||
</View>
|
||||
{aiAnalysisResult.suitableFor.map((item, idx) => (
|
||||
@@ -1540,7 +1540,7 @@ export default function MedicationDetailScreen() {
|
||||
|
||||
<View style={[styles.aiBubbleCard, { backgroundColor: '#FEF2F2', borderColor: '#FEE2E2' }]}>
|
||||
<View style={styles.aiBubbleHeader}>
|
||||
<Text style={[styles.aiBubbleTitle, { color: '#ef4444' }]}>不适合人群</Text>
|
||||
<Text style={[styles.aiBubbleTitle, { color: '#ef4444' }]}>{t('medications.detail.aiAnalysis.categories.unsuitableFor')}</Text>
|
||||
<Ionicons name="alert-circle" size={16} color="#ef4444" />
|
||||
</View>
|
||||
{aiAnalysisResult.unsuitableFor.map((item, idx) => (
|
||||
@@ -1552,9 +1552,9 @@ export default function MedicationDetailScreen() {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{renderAdviceCard('可能的副作用', aiAnalysisResult.sideEffects, 'warning-outline', '#f59e0b', '#FFFBEB')}
|
||||
{renderAdviceCard('储存建议', aiAnalysisResult.storageAdvice, 'cube-outline', '#10b981', '#ECFDF3')}
|
||||
{renderAdviceCard('健康/使用建议', aiAnalysisResult.healthAdvice, 'sparkles-outline', '#6366f1', '#EEF2FF')}
|
||||
{renderAdviceCard(t('medications.detail.aiAnalysis.categories.sideEffects'), aiAnalysisResult.sideEffects, 'warning-outline', '#f59e0b', '#FFFBEB')}
|
||||
{renderAdviceCard(t('medications.detail.aiAnalysis.categories.storageAdvice'), aiAnalysisResult.storageAdvice, 'cube-outline', '#10b981', '#ECFDF3')}
|
||||
{renderAdviceCard(t('medications.detail.aiAnalysis.categories.healthAdvice'), aiAnalysisResult.healthAdvice, 'sparkles-outline', '#6366f1', '#EEF2FF')}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@@ -1580,8 +1580,8 @@ export default function MedicationDetailScreen() {
|
||||
<View style={styles.aiMembershipLeft}>
|
||||
<Ionicons name="diamond-outline" size={18} color="#f59e0b" />
|
||||
<View>
|
||||
<Text style={styles.aiMembershipTitle}>会员专享 AI 深度解读</Text>
|
||||
<Text style={styles.aiMembershipSub}>解锁完整药品分析与无限次使用</Text>
|
||||
<Text style={styles.aiMembershipTitle}>{t('medications.detail.aiAnalysis.membershipCard.title')}</Text>
|
||||
<Text style={styles.aiMembershipSub}>{t('medications.detail.aiAnalysis.membershipCard.subtitle')}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Ionicons name="chevron-forward" size={18} color="#f59e0b" />
|
||||
@@ -1622,22 +1622,67 @@ export default function MedicationDetailScreen() {
|
||||
{isAiDraft ? (
|
||||
<View style={styles.footerButtonContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.secondaryFooterBtn}
|
||||
activeOpacity={0.9}
|
||||
onPress={() => router.replace('/medications/ai-camera')}
|
||||
>
|
||||
<Text style={styles.secondaryFooterText}>重新拍摄</Text>
|
||||
{isLiquidGlassAvailable() ? (
|
||||
<GlassView
|
||||
style={[styles.secondaryFooterBtn, { borderWidth: 0, overflow: 'hidden', backgroundColor: 'transparent' }]}
|
||||
glassEffectStyle="regular"
|
||||
isInteractive={true}
|
||||
>
|
||||
<Text style={styles.secondaryFooterText}>{t('medications.detail.aiDraft.reshoot')}</Text>
|
||||
</GlassView>
|
||||
) : (
|
||||
<View style={styles.secondaryFooterBtn}>
|
||||
<Text style={styles.secondaryFooterText}>{t('medications.detail.aiDraft.reshoot')}</Text>
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.primaryFooterBtn, { backgroundColor: colors.primary }]}
|
||||
style={{ flex: 1 }}
|
||||
activeOpacity={0.9}
|
||||
onPress={handleAiDraftSave}
|
||||
disabled={aiDraftSaving}
|
||||
>
|
||||
{aiDraftSaving ? (
|
||||
<ActivityIndicator color={colors.onPrimary} />
|
||||
{isLiquidGlassAvailable() ? (
|
||||
<GlassView
|
||||
style={[
|
||||
styles.primaryFooterBtn,
|
||||
{
|
||||
width: '100%',
|
||||
shadowOpacity: 0,
|
||||
backgroundColor: 'transparent',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
]}
|
||||
glassEffectStyle="regular"
|
||||
tintColor={colors.primary}
|
||||
isInteractive={true}
|
||||
>
|
||||
{aiDraftSaving ? (
|
||||
<ActivityIndicator color="#fff" />
|
||||
) : (
|
||||
<Text style={[styles.primaryFooterText, { color: '#fff' }]}>
|
||||
{t('medications.detail.aiDraft.saveAndCreate')}
|
||||
</Text>
|
||||
)}
|
||||
</GlassView>
|
||||
) : (
|
||||
<Text style={[styles.primaryFooterText, { color: colors.onPrimary }]}>保存并创建</Text>
|
||||
<View
|
||||
style={[
|
||||
styles.primaryFooterBtn,
|
||||
{ width: '100%', backgroundColor: colors.primary },
|
||||
]}
|
||||
>
|
||||
{aiDraftSaving ? (
|
||||
<ActivityIndicator color={colors.onPrimary} />
|
||||
) : (
|
||||
<Text style={[styles.primaryFooterText, { color: colors.onPrimary }]}>
|
||||
{t('medications.detail.aiDraft.saveAndCreate')}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
@@ -1726,7 +1771,7 @@ export default function MedicationDetailScreen() {
|
||||
<View style={styles.modalHandle} />
|
||||
<View style={styles.modalHeader}>
|
||||
<Text style={[styles.modalTitle, { color: colors.text }]}>
|
||||
编辑药物名称
|
||||
{t('medications.detail.name.edit')}
|
||||
</Text>
|
||||
<TouchableOpacity onPress={handleCloseNameModal} hitSlop={12}>
|
||||
<Ionicons name="close" size={20} color={colors.textSecondary} />
|
||||
@@ -1744,7 +1789,7 @@ export default function MedicationDetailScreen() {
|
||||
<TextInput
|
||||
value={nameDraft}
|
||||
onChangeText={handleNameChange}
|
||||
placeholder="请输入药物名称"
|
||||
placeholder={t('medications.detail.name.placeholder')}
|
||||
placeholderTextColor={colors.textMuted}
|
||||
style={[styles.nameInput, { color: colors.text }]}
|
||||
autoFocus
|
||||
@@ -1777,7 +1822,7 @@ export default function MedicationDetailScreen() {
|
||||
<ActivityIndicator color={colors.onPrimary} size="small" />
|
||||
) : (
|
||||
<Text style={[styles.modalActionPrimaryText, { color: colors.onPrimary }]}>
|
||||
保存
|
||||
{t('common.save')}
|
||||
</Text>
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
@@ -2599,7 +2644,7 @@ const styles = StyleSheet.create({
|
||||
elevation: 4,
|
||||
},
|
||||
aiScoreBadgeText: {
|
||||
fontSize: 12,
|
||||
fontSize: 10,
|
||||
fontWeight: '700',
|
||||
color: '#fff',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user