feat(i18n): 实现应用国际化支持,添加中英文翻译
- 为所有UI组件添加国际化支持,替换硬编码文本 - 新增useI18n钩子函数统一管理翻译 - 完善中英文翻译资源,覆盖统计、用药、通知设置等模块 - 优化Tab布局使用翻译键值替代静态文本 - 更新药品管理、个人资料编辑等页面的多语言支持
This commit is contained in:
@@ -5,6 +5,7 @@ import { IconSymbol } from '@/components/ui/IconSymbol';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
|
||||
import {
|
||||
fetchMedications,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
selectMedicationsLoading,
|
||||
updateMedicationAction,
|
||||
} from '@/store/medicationsSlice';
|
||||
import { selectUserProfile } from '@/store/userSlice';
|
||||
import type { Medication, MedicationForm } from '@/types/medication';
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -33,25 +35,12 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
|
||||
type FilterType = 'all' | 'active' | 'inactive';
|
||||
|
||||
const FORM_LABELS: Record<MedicationForm, string> = {
|
||||
capsule: '胶囊',
|
||||
pill: '药片',
|
||||
injection: '注射',
|
||||
spray: '喷雾',
|
||||
drop: '滴剂',
|
||||
syrup: '糖浆',
|
||||
other: '其他',
|
||||
};
|
||||
|
||||
const FILTER_CONFIG: { key: FilterType; label: string }[] = [
|
||||
{ key: 'all', label: '全部' },
|
||||
{ key: 'active', label: '进行中' },
|
||||
{ key: 'inactive', label: '已停用' },
|
||||
];
|
||||
// 这些常量将在组件内部定义,以便使用翻译函数
|
||||
|
||||
const DEFAULT_IMAGE = require('@/assets/images/medicine/image-medicine.png');
|
||||
|
||||
export default function ManageMedicationsScreen() {
|
||||
const { t } = useI18n();
|
||||
const dispatch = useAppDispatch();
|
||||
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
|
||||
const colors = Colors[theme];
|
||||
@@ -59,6 +48,7 @@ export default function ManageMedicationsScreen() {
|
||||
const insets = useSafeAreaInsets();
|
||||
const medications = useAppSelector(selectMedications);
|
||||
const loading = useAppSelector(selectMedicationsLoading);
|
||||
const userProfile = useAppSelector(selectUserProfile);
|
||||
const [activeFilter, setActiveFilter] = useState<FilterType>('all');
|
||||
const [pendingMedicationId, setPendingMedicationId] = useState<string | null>(null);
|
||||
const [deactivateSheetVisible, setDeactivateSheetVisible] = useState(false);
|
||||
@@ -121,7 +111,7 @@ export default function ManageMedicationsScreen() {
|
||||
).unwrap();
|
||||
} catch (error) {
|
||||
console.error('更新药物状态失败', error);
|
||||
Alert.alert('操作失败', '切换药物状态时发生问题,请稍后重试。');
|
||||
Alert.alert(t('medications.manage.toggleError.title'), t('medications.manage.toggleError.message'));
|
||||
} finally {
|
||||
setPendingMedicationId(null);
|
||||
}
|
||||
@@ -144,7 +134,7 @@ export default function ManageMedicationsScreen() {
|
||||
).unwrap();
|
||||
} catch (error) {
|
||||
console.error('停用药物失败', error);
|
||||
Alert.alert('操作失败', '停用药物时发生问题,请稍后重试。');
|
||||
Alert.alert(t('medications.manage.deactivate.error.title'), t('medications.manage.deactivate.error.message'));
|
||||
} finally {
|
||||
setDeactivateLoading(false);
|
||||
setMedicationToDeactivate(null);
|
||||
@@ -153,14 +143,25 @@ export default function ManageMedicationsScreen() {
|
||||
|
||||
// 创建独立的药品卡片组件,使用 React.memo 优化渲染
|
||||
const MedicationCard = React.memo(({ medication, onPress }: { medication: Medication; onPress: () => void }) => {
|
||||
// 使用翻译函数获取剂型标签
|
||||
const FORM_LABELS: Record<MedicationForm, string> = {
|
||||
capsule: t('medications.manage.formLabels.capsule'),
|
||||
pill: t('medications.manage.formLabels.pill'),
|
||||
injection: t('medications.manage.formLabels.injection'),
|
||||
spray: t('medications.manage.formLabels.spray'),
|
||||
drop: t('medications.manage.formLabels.drop'),
|
||||
syrup: t('medications.manage.formLabels.syrup'),
|
||||
other: t('medications.manage.formLabels.other'),
|
||||
};
|
||||
|
||||
const dosageLabel = `${medication.dosageValue} ${medication.dosageUnit || ''} ${FORM_LABELS[medication.form] ?? ''}`.trim();
|
||||
const frequencyLabel = `${medication.repeatPattern === 'daily' ? '每日' : medication.repeatPattern === 'weekly' ? '每周' : '自定义'} | ${dosageLabel}`;
|
||||
const frequencyLabel = `${medication.repeatPattern === 'daily' ? t('medications.manage.frequency.daily') : medication.repeatPattern === 'weekly' ? t('medications.manage.frequency.weekly') : t('medications.manage.frequency.custom')} | ${dosageLabel}`;
|
||||
const startDateLabel = dayjs(medication.startDate).isValid()
|
||||
? dayjs(medication.startDate).format('M月D日')
|
||||
: '未知日期';
|
||||
: t('medications.manage.unknownDate');
|
||||
const reminderLabel = medication.medicationTimes?.length
|
||||
? medication.medicationTimes.join('、')
|
||||
: `${medication.timesPerDay} 次/日`;
|
||||
: `${medication.timesPerDay} ${t('medications.manage.cardMeta.reminderNotSet')}`;
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
@@ -180,7 +181,7 @@ export default function ManageMedicationsScreen() {
|
||||
{frequencyLabel}
|
||||
</ThemedText>
|
||||
<ThemedText style={[styles.cardMeta, { color: colors.textMuted }]}>
|
||||
{`开始于 ${startDateLabel} | 提醒:${reminderLabel}`}
|
||||
{t('medications.manage.cardMeta', { date: startDateLabel, reminder: reminderLabel })}
|
||||
</ThemedText>
|
||||
</View>
|
||||
</View>
|
||||
@@ -250,26 +251,27 @@ export default function ManageMedicationsScreen() {
|
||||
<View style={styles.decorativeCircle2} />
|
||||
|
||||
<HeaderBar
|
||||
title="药品管理"
|
||||
title={t('medications.manage.title')}
|
||||
onBack={() => router.back()}
|
||||
variant="minimal"
|
||||
transparent
|
||||
/>
|
||||
|
||||
<View style={{ paddingTop: safeAreaTop }} />
|
||||
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
styles.content,
|
||||
{ paddingBottom: insets.bottom + 32 },
|
||||
{
|
||||
paddingTop: safeAreaTop , // HeaderBar高度 + 额外间距
|
||||
paddingBottom: insets.bottom + 32
|
||||
},
|
||||
]}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={styles.pageHeader}>
|
||||
<View>
|
||||
<ThemedText style={styles.title}>我的用药</ThemedText>
|
||||
<ThemedText style={styles.title}>{t('medications.greeting', { name: userProfile.name || '朋友' })}</ThemedText>
|
||||
<ThemedText style={[styles.subtitle, { color: colors.textMuted }]}>
|
||||
管理所有药品的状态与提醒
|
||||
{t('medications.manage.subtitle')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
@@ -294,7 +296,11 @@ export default function ManageMedicationsScreen() {
|
||||
</View>
|
||||
|
||||
<View style={[styles.segmented, { backgroundColor: colors.surface }]}>
|
||||
{FILTER_CONFIG.map((filter) => {
|
||||
{[
|
||||
{ key: 'all' as FilterType, label: t('medications.manage.filters.all') },
|
||||
{ key: 'active' as FilterType, label: t('medications.manage.filters.active') },
|
||||
{ key: 'inactive' as FilterType, label: t('medications.manage.filters.inactive') },
|
||||
].map((filter) => {
|
||||
const isActive = filter.key === activeFilter;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
@@ -341,14 +347,14 @@ export default function ManageMedicationsScreen() {
|
||||
{listLoading ? (
|
||||
<View style={[styles.loading, { backgroundColor: colors.surface }]}>
|
||||
<ActivityIndicator color={colors.primary} />
|
||||
<ThemedText style={styles.loadingText}>正在载入药品信息...</ThemedText>
|
||||
<ThemedText style={styles.loadingText}>{t('medications.manage.loading')}</ThemedText>
|
||||
</View>
|
||||
) : filteredMedications.length === 0 ? (
|
||||
<View style={[styles.empty, { backgroundColor: colors.surface }]}>
|
||||
<Image source={DEFAULT_IMAGE} style={styles.emptyImage} contentFit="contain" />
|
||||
<ThemedText style={styles.emptyTitle}>暂无药品</ThemedText>
|
||||
<ThemedText style={styles.emptyTitle}>{t('medications.manage.empty.title')}</ThemedText>
|
||||
<ThemedText style={[styles.emptySubtitle, { color: colors.textSecondary }]}>
|
||||
还没有相关药品记录,点击右上角添加
|
||||
{t('medications.manage.empty.subtitle')}
|
||||
</ThemedText>
|
||||
</View>
|
||||
) : (
|
||||
@@ -365,10 +371,10 @@ export default function ManageMedicationsScreen() {
|
||||
setMedicationToDeactivate(null);
|
||||
}}
|
||||
onConfirm={handleDeactivateMedication}
|
||||
title={`停用 ${medicationToDeactivate.name}?`}
|
||||
description="停用后,当天已生成的用药计划会一并删除,且无法恢复。"
|
||||
confirmText="确认停用"
|
||||
cancelText="取消"
|
||||
title={t('medications.manage.deactivate.title', { name: medicationToDeactivate.name })}
|
||||
description={t('medications.manage.deactivate.description')}
|
||||
confirmText={t('medications.manage.deactivate.confirm')}
|
||||
cancelText={t('medications.manage.deactivate.cancel')}
|
||||
destructive
|
||||
loading={deactivateLoading}
|
||||
/>
|
||||
@@ -419,7 +425,7 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
},
|
||||
title: {
|
||||
fontSize: 26,
|
||||
fontSize: 24,
|
||||
fontWeight: '600',
|
||||
},
|
||||
subtitle: {
|
||||
|
||||
Reference in New Issue
Block a user