feat(i18n): 实现应用国际化支持,添加中英文翻译

- 为所有UI组件添加国际化支持,替换硬编码文本
- 新增useI18n钩子函数统一管理翻译
- 完善中英文翻译资源,覆盖统计、用药、通知设置等模块
- 优化Tab布局使用翻译键值替代静态文本
- 更新药品管理、个人资料编辑等页面的多语言支持
This commit is contained in:
richarjiang
2025-11-13 11:09:55 +08:00
parent 416d144387
commit 2dca3253e6
21 changed files with 1669 additions and 366 deletions

View File

@@ -1,5 +1,5 @@
import { DateSelector } from '@/components/DateSelector';
import CelebrationAnimation, { CelebrationAnimationRef } from '@/components/CelebrationAnimation';
import { DateSelector } from '@/components/DateSelector';
import { MedicationCard } from '@/components/medication/MedicationCard';
import { ThemedText } from '@/components/ThemedText';
import { IconSymbol } from '@/components/ui/IconSymbol';
@@ -17,6 +17,7 @@ import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { router } from 'expo-router';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
ScrollView,
StyleSheet,
@@ -32,6 +33,7 @@ type MedicationFilter = 'all' | 'taken' | 'missed';
type ThemeColors = (typeof Colors)[keyof typeof Colors];
export default function MedicationsScreen() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const insets = useSafeAreaInsets();
const theme = (useColorScheme() ?? 'light') as 'light' | 'dark';
@@ -147,8 +149,8 @@ export default function MedicationsScreen() {
const displayName = userProfile.name?.trim() || DEFAULT_MEMBER_NAME;
const headerDateLabel = selectedDate.isSame(dayjs(), 'day')
? `今天,${selectedDate.format('M月D日')}`
: selectedDate.format('M月D日 dddd');
? t('medications.dateFormats.today', { date: selectedDate.format('M月D日') })
: t('medications.dateFormats.other', { date: selectedDate.format('M月D日 dddd') });
const emptyState = filteredMedications.length === 0;
@@ -178,9 +180,9 @@ export default function MedicationsScreen() {
>
<View style={styles.header}>
<View>
<ThemedText style={styles.greeting}>{displayName}</ThemedText>
<ThemedText style={styles.greeting}>{t('medications.greeting', { name: displayName })}</ThemedText>
<ThemedText style={[styles.welcome, { color: colors.textMuted }]}>
{t('medications.welcome')}
</ThemedText>
</View>
<View style={styles.headerActions}>
@@ -239,15 +241,10 @@ export default function MedicationsScreen() {
</View>
<View style={styles.sectionSpacing}>
<ThemedText style={styles.sectionHeader}></ThemedText>
<ThemedText style={styles.sectionHeader}>{t('medications.todayMedications')}</ThemedText>
<View style={[styles.segmentedControl, { backgroundColor: colors.surface }]}>
{(['all', 'taken', 'missed'] as MedicationFilter[]).map((filter) => {
const isActive = activeFilter === filter;
const labelMap: Record<MedicationFilter, string> = {
all: '全部',
taken: '已服用',
missed: '未服用',
};
return (
<TouchableOpacity
key={filter}
@@ -263,7 +260,7 @@ export default function MedicationsScreen() {
{ color: isActive ? colors.onPrimary : colors.textSecondary },
]}
>
{labelMap[filter]}
{t(`medications.filters.${filter}`)}
</ThemedText>
<View
style={[
@@ -295,9 +292,9 @@ export default function MedicationsScreen() {
style={styles.emptyIllustration}
contentFit="cover"
/>
<ThemedText style={styles.emptyTitle}></ThemedText>
<ThemedText style={styles.emptyTitle}>{t('medications.emptyState.title')}</ThemedText>
<ThemedText style={[styles.emptySubtitle, { color: colors.textMuted }]}>
{t('medications.emptyState.subtitle')}
</ThemedText>
</View>
) : (