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

@@ -3,6 +3,7 @@ import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { ROUTES } from '@/constants/Routes';
import { useAppSelector } from '@/hooks/redux';
import { useI18n } from '@/hooks/useI18n';
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
import { addDietRecord, type CreateDietRecordDto, type MealType } from '@/services/dietRecords';
import { selectFoodRecognitionResult } from '@/store/foodRecognitionSlice';
@@ -65,15 +66,8 @@ const mockFoodItems = [
}
];
// 餐次映射
const MEAL_TYPE_MAP = {
breakfast: '早餐',
lunch: '午餐',
dinner: '晚餐',
snack: '加餐'
};
export default function FoodAnalysisResultScreen() {
const { t } = useI18n();
const safeAreaTop = useSafeAreaTop()
const router = useRouter();
const params = useLocalSearchParams<{
@@ -190,6 +184,15 @@ export default function FoodAnalysisResultScreen() {
}
};
// 餐次映射
const MEAL_TYPE_MAP = {
breakfast: t('nutritionRecords.mealTypes.breakfast'),
lunch: t('nutritionRecords.mealTypes.lunch'),
dinner: t('nutritionRecords.mealTypes.dinner'),
snack: t('nutritionRecords.mealTypes.snack'),
other: t('nutritionRecords.mealTypes.other'),
};
// 计算所有食物的总营养数据
const totalCalories = foodItems.reduce((sum, item) => sum + item.calories, 0);
const totalProtein = foodItems.reduce((sum, item) => sum + item.protein, 0);
@@ -253,24 +256,24 @@ export default function FoodAnalysisResultScreen() {
// 餐次选择选项
const mealOptions = [
{ key: 'breakfast' as const, label: '早餐', color: '#FF6B35' },
{ key: 'lunch' as const, label: '午餐', color: '#4CAF50' },
{ key: 'dinner' as const, label: '晚餐', color: '#2196F3' },
{ key: 'snack' as const, label: '加餐', color: '#FF9800' },
{ key: 'breakfast' as const, label: t('nutritionRecords.mealTypes.breakfast'), color: '#FF6B35' },
{ key: 'lunch' as const, label: t('nutritionRecords.mealTypes.lunch'), color: '#4CAF50' },
{ key: 'dinner' as const, label: t('nutritionRecords.mealTypes.dinner'), color: '#2196F3' },
{ key: 'snack' as const, label: t('nutritionRecords.mealTypes.snack'), color: '#FF9800' },
];
if (!imageUri && !recognitionResult) {
return (
<View style={styles.container}>
<HeaderBar
title="分析结果"
title={t('foodAnalysisResult.title')}
onBack={() => router.back()}
/>
<View style={{
paddingTop: safeAreaTop
}} />
<View style={styles.errorContainer}>
<Text style={styles.errorText}></Text>
<Text style={styles.errorText}>{t('foodAnalysisResult.error.notFound')}</Text>
</View>
</View>
);
@@ -287,7 +290,7 @@ export default function FoodAnalysisResultScreen() {
/>
<HeaderBar
title="分析结果"
title={t('foodAnalysisResult.title')}
onBack={() => router.back()}
transparent={true}
/>
@@ -316,7 +319,7 @@ export default function FoodAnalysisResultScreen() {
<View style={styles.placeholderContainer}>
<View style={styles.placeholderContent}>
<Ionicons name="restaurant-outline" size={48} color="#666" />
<Text style={styles.placeholderText}></Text>
<Text style={styles.placeholderText}>{t('foodAnalysisResult.placeholder')}</Text>
</View>
</View>
)}
@@ -325,8 +328,8 @@ export default function FoodAnalysisResultScreen() {
<View style={styles.descriptionBubble}>
<Text style={styles.descriptionText}>
{recognitionResult ?
`置信度: ${recognitionResult.confidence}%` :
dayjs().format('YYYY年M月D日')
t('foodAnalysisResult.confidence', { value: recognitionResult.confidence }) :
dayjs().format(t('foodAnalysisResult.dateFormats.today'))
}
</Text>
</View>
@@ -337,31 +340,31 @@ export default function FoodAnalysisResultScreen() {
{/* 卡路里 */}
<View style={styles.calorieSection}>
<Text style={styles.calorieValue}>{totalCalories}</Text>
<Text style={styles.calorieUnit}></Text>
<Text style={styles.calorieUnit}>{t('foodAnalysisResult.nutrients.caloriesUnit')}</Text>
</View>
{/* 营养圆环图 */}
<View style={styles.nutritionRings}>
<NutritionRing
label="蛋白质"
label={t('foodAnalysisResult.nutrients.protein')}
value={totalProtein.toFixed(1)}
unit="克"
unit={t('foodAnalysisResult.nutrients.unit')}
percentage={Math.min(100, proteinPercentage)}
color="#4CAF50"
resetToken={animationTrigger}
/>
<NutritionRing
label="脂肪"
label={t('foodAnalysisResult.nutrients.fat')}
value={totalFat.toFixed(1)}
unit="克"
unit={t('foodAnalysisResult.nutrients.unit')}
percentage={Math.min(100, fatPercentage)}
color="#FF9800"
resetToken={animationTrigger}
/>
<NutritionRing
label="碳水"
label={t('foodAnalysisResult.nutrients.carbs')}
value={totalCarbohydrate.toFixed(1)}
unit="克"
unit={t('foodAnalysisResult.nutrients.unit')}
percentage={Math.min(100, carbohydratePercentage)}
color="#2196F3"
resetToken={animationTrigger}
@@ -372,7 +375,7 @@ export default function FoodAnalysisResultScreen() {
{/* 食物摄入部分 */}
<View style={styles.foodIntakeSection}>
<Text style={styles.foodIntakeTitle}>
{recognitionResult ? '识别结果' : '食物摄入'}
{recognitionResult ? t('foodAnalysisResult.sections.recognitionResult') : t('foodAnalysisResult.sections.foodIntake')}
</Text>
{recognitionResult && recognitionResult.analysisText && (
<Text style={styles.analysisText}>{recognitionResult.analysisText}</Text>
@@ -384,15 +387,15 @@ export default function FoodAnalysisResultScreen() {
<View style={styles.nonFoodIcon}>
<Ionicons name="alert-circle-outline" size={48} color="#FF9800" />
</View>
<Text style={styles.nonFoodTitle}></Text>
<Text style={styles.nonFoodTitle}>{t('foodAnalysisResult.nonFood.title')}</Text>
<Text style={styles.nonFoodMessage}>
{recognitionResult.nonFoodMessage || recognitionResult.analysisText}
</Text>
<View style={styles.nonFoodSuggestions}>
<Text style={styles.nonFoodSuggestionsTitle}></Text>
<Text style={styles.nonFoodSuggestionItem}> </Text>
<Text style={styles.nonFoodSuggestionItem}> </Text>
<Text style={styles.nonFoodSuggestionItem}> 线</Text>
<Text style={styles.nonFoodSuggestionsTitle}>{t('foodAnalysisResult.nonFood.suggestions.title')}</Text>
<Text style={styles.nonFoodSuggestionItem}>{t('foodAnalysisResult.nonFood.suggestions.item1')}</Text>
<Text style={styles.nonFoodSuggestionItem}>{t('foodAnalysisResult.nonFood.suggestions.item2')}</Text>
<Text style={styles.nonFoodSuggestionItem}>{t('foodAnalysisResult.nonFood.suggestions.item3')}</Text>
</View>
</View>
)}
@@ -411,7 +414,7 @@ export default function FoodAnalysisResultScreen() {
</View>
<View style={styles.foodIntakeCalories}>
<Text style={styles.foodIntakeCaloriesValue}>{item.calories}</Text>
<Text style={styles.foodIntakeCaloriesValue}>{item.calories}{t('foodAnalysisResult.nutrients.caloriesUnit')}</Text>
{shouldHideRecordBar ? null : <TouchableOpacity
style={styles.editButton}
onPress={() => handleEditFood(item)}
@@ -442,7 +445,7 @@ export default function FoodAnalysisResultScreen() {
activeOpacity={0.8}
>
<Ionicons name="camera-outline" size={20} color={Colors.light.onPrimary} style={{ marginRight: 8 }} />
<Text style={styles.retakePhotoButtonText}></Text>
<Text style={styles.retakePhotoButtonText}>{t('foodAnalysisResult.actions.retake')}</Text>
</TouchableOpacity>
</View>
) : (
@@ -471,7 +474,7 @@ export default function FoodAnalysisResultScreen() {
{isRecording ? (
<ActivityIndicator size="small" color="#FFF" />
) : (
<Text style={styles.recordButtonText}></Text>
<Text style={styles.recordButtonText}>{t('foodAnalysisResult.actions.record')}</Text>
)}
</TouchableOpacity>
</View>
@@ -492,7 +495,7 @@ export default function FoodAnalysisResultScreen() {
/>
<View style={styles.mealSelectorModal}>
<View style={styles.mealSelectorHeader}>
<Text style={styles.mealSelectorTitle}></Text>
<Text style={styles.mealSelectorTitle}>{t('foodAnalysisResult.mealSelector.title')}</Text>
<TouchableOpacity onPress={() => setShowMealSelector(false)}>
<Ionicons name="close" size={24} color="#666" />
</TouchableOpacity>
@@ -539,8 +542,8 @@ export default function FoodAnalysisResultScreen() {
<View style={styles.imageViewerHeader}>
<Text style={styles.imageViewerHeaderText}>
{recognitionResult ?
`置信度: ${recognitionResult.confidence}%` :
dayjs().format('YYYY年M月D日 HH:mm')
t('foodAnalysisResult.confidence', { value: recognitionResult.confidence }) :
dayjs().format(t('foodAnalysisResult.dateFormats.full'))
}
</Text>
</View>
@@ -551,7 +554,7 @@ export default function FoodAnalysisResultScreen() {
style={styles.imageViewerFooterButton}
onPress={() => setShowImagePreview(false)}
>
<Text style={styles.imageViewerFooterButtonText}></Text>
<Text style={styles.imageViewerFooterButtonText}>{t('foodAnalysisResult.actions.close')}</Text>
</TouchableOpacity>
</View>
)}
@@ -587,6 +590,8 @@ function FoodEditModal({
onFormDataChange({ ...formData, [field]: value });
};
const { t } = useI18n();
return (
<Modal
visible={visible}
@@ -598,14 +603,14 @@ function FoodEditModal({
<View style={styles.editModalSheet}>
<View style={styles.modalHandle} />
<Text style={styles.modalTitle}></Text>
<Text style={styles.modalTitle}>{t('foodAnalysisResult.editModal.title')}</Text>
{/* 食物名称 */}
<View style={styles.editFieldContainer}>
<Text style={styles.editFieldLabel}></Text>
<Text style={styles.editFieldLabel}>{t('foodAnalysisResult.editModal.fields.name')}</Text>
<TextInput
style={styles.editInput}
placeholder="输入食物名称"
placeholder={t('foodAnalysisResult.editModal.fields.namePlaceholder')}
placeholderTextColor="#999"
value={formData.name}
onChangeText={(value) => handleFieldChange('name', value)}
@@ -615,10 +620,10 @@ function FoodEditModal({
{/* 重量/数量 */}
<View style={styles.editFieldContainer}>
<Text style={styles.editFieldLabel}> ()</Text>
<Text style={styles.editFieldLabel}>{t('foodAnalysisResult.editModal.fields.amount')}</Text>
<TextInput
style={styles.editInput}
placeholder="输入重量"
placeholder={t('foodAnalysisResult.editModal.fields.amountPlaceholder')}
placeholderTextColor="#999"
value={formData.amount}
onChangeText={(value) => handleFieldChange('amount', value)}
@@ -628,10 +633,10 @@ function FoodEditModal({
{/* 卡路里 */}
<View style={styles.editFieldContainer}>
<Text style={styles.editFieldLabel}> ()</Text>
<Text style={styles.editFieldLabel}>{t('foodAnalysisResult.editModal.fields.calories')}</Text>
<TextInput
style={styles.editInput}
placeholder="输入卡路里"
placeholder={t('foodAnalysisResult.editModal.fields.caloriesPlaceholder')}
placeholderTextColor="#999"
value={formData.calories}
onChangeText={(value) => handleFieldChange('calories', value)}
@@ -645,13 +650,13 @@ function FoodEditModal({
onPress={onClose}
style={styles.modalCancelBtn}
>
<Text style={styles.modalCancelText}></Text>
<Text style={styles.modalCancelText}>{t('foodAnalysisResult.editModal.actions.cancel')}</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={onSave}
style={[styles.modalSaveBtn, { backgroundColor: Colors.light.primary }]}
>
<Text style={[styles.modalSaveText, { color: Colors.light.onPrimary }]}></Text>
<Text style={[styles.modalSaveText, { color: Colors.light.onPrimary }]}>{t('foodAnalysisResult.editModal.actions.save')}</Text>
</TouchableOpacity>
</View>
</View>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { useI18n } from '@/hooks/useI18n';
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
import {
deleteNutritionAnalysisRecord,
@@ -30,6 +31,7 @@ import {
import ImageViewing from 'react-native-image-viewing';
export default function NutritionAnalysisHistoryScreen() {
const { t } = useI18n();
const safeAreaTop = useSafeAreaTop();
const router = useRouter();
@@ -95,15 +97,15 @@ export default function NutritionAnalysisHistoryScreen() {
setHasMore(page < response.data.totalPages);
setCurrentPage(page);
} else {
const errorMessage = response.message || '获取历史记录失败';
const errorMessage = response.message || t('nutritionAnalysisHistory.errors.fetchFailed');
setError(errorMessage);
Alert.alert('错误', errorMessage);
Alert.alert(t('nutritionAnalysisHistory.errors.error'), errorMessage);
}
} catch (error) {
console.error('[HISTORY] 获取历史记录失败:', error);
const errorMessage = '获取历史记录失败,请稍后重试';
const errorMessage = t('nutritionAnalysisHistory.errors.fetchFailedRetry');
setError(errorMessage);
Alert.alert('错误', errorMessage);
Alert.alert(t('nutritionAnalysisHistory.errors.error'), errorMessage);
} finally {
setLoading(false);
setRefreshing(false);
@@ -173,13 +175,13 @@ export default function NutritionAnalysisHistoryScreen() {
const getStatusText = (status: string) => {
switch (status) {
case 'success':
return '成功';
return t('nutritionAnalysisHistory.status.success');
case 'failed':
return '失败';
return t('nutritionAnalysisHistory.status.failed');
case 'processing':
return '处理中';
return t('nutritionAnalysisHistory.status.processing');
default:
return '未知';
return t('nutritionAnalysisHistory.status.unknown');
}
};
@@ -208,15 +210,15 @@ export default function NutritionAnalysisHistoryScreen() {
// 处理删除记录
const handleDeleteRecord = useCallback((recordId: number) => {
Alert.alert(
'确认删除',
'确定要删除这条营养分析记录吗?此操作无法撤销。',
t('nutritionAnalysisHistory.delete.confirmTitle'),
t('nutritionAnalysisHistory.delete.confirmMessage'),
[
{
text: '取消',
text: t('nutritionAnalysisHistory.delete.cancel'),
style: 'cancel',
},
{
text: '删除',
text: t('nutritionAnalysisHistory.delete.delete'),
style: 'destructive',
onPress: async () => {
try {
@@ -231,10 +233,10 @@ export default function NutritionAnalysisHistoryScreen() {
triggerLightHaptic();
// 显示成功提示
Alert.alert('成功', '记录已删除');
Alert.alert(t('nutritionAnalysisHistory.delete.successTitle'), t('nutritionAnalysisHistory.delete.successMessage'));
} catch (error) {
console.error('[HISTORY] 删除记录失败:', error);
Alert.alert('错误', '删除失败,请稍后重试');
Alert.alert(t('nutritionAnalysisHistory.errors.error'), t('nutritionAnalysisHistory.errors.deleteFailed'));
} finally {
setDeletingId(null);
}
@@ -256,11 +258,11 @@ export default function NutritionAnalysisHistoryScreen() {
<View style={styles.recordInfo}>
{isSuccess && (
<Text style={styles.recordTitle}>
{item.nutritionCount}
{t('nutritionAnalysisHistory.recognized', { count: item.nutritionCount })}
</Text>
)}
<Text style={styles.recordDate}>
{dayjs(item.createdAt).format('YYYY年M月D日 HH:mm')}
{dayjs(item.createdAt).format(t('nutritionAnalysisHistory.dateFormat'))}
</Text>
<View style={[styles.statusBadge, { backgroundColor: getStatusColor(item.status) }]}>
<Text style={styles.statusText}>{getStatusText(item.status)}</Text>
@@ -327,25 +329,25 @@ export default function NutritionAnalysisHistoryScreen() {
<>
{mainNutrients.energy && (
<View style={styles.nutritionItem}>
<Text style={styles.nutritionLabel}></Text>
<Text style={styles.nutritionLabel}>{t('nutritionAnalysisHistory.nutrients.energy')}</Text>
<Text style={styles.nutritionValue}>{mainNutrients.energy}</Text>
</View>
)}
{mainNutrients.protein && (
<View style={styles.nutritionItem}>
<Text style={styles.nutritionLabel}></Text>
<Text style={styles.nutritionLabel}>{t('nutritionAnalysisHistory.nutrients.protein')}</Text>
<Text style={styles.nutritionValue}>{mainNutrients.protein}</Text>
</View>
)}
{mainNutrients.carbs && (
<View style={styles.nutritionItem}>
<Text style={styles.nutritionLabel}></Text>
<Text style={styles.nutritionLabel}>{t('nutritionAnalysisHistory.nutrients.carbs')}</Text>
<Text style={styles.nutritionValue}>{mainNutrients.carbs}</Text>
</View>
)}
{mainNutrients.fat && (
<View style={styles.nutritionItem}>
<Text style={styles.nutritionLabel}></Text>
<Text style={styles.nutritionLabel}>{t('nutritionAnalysisHistory.nutrients.fat')}</Text>
<Text style={styles.nutritionValue}>{mainNutrients.fat}</Text>
</View>
)}
@@ -371,7 +373,7 @@ export default function NutritionAnalysisHistoryScreen() {
activeOpacity={0.7}
>
<Text style={styles.expandButtonText}>
{isExpanded ? '收起详情' : '展开详情'}
{isExpanded ? t('nutritionAnalysisHistory.actions.collapse') : t('nutritionAnalysisHistory.actions.expand')}
</Text>
<Ionicons
name={isExpanded ? 'chevron-up-outline' : 'chevron-down-outline'}
@@ -383,7 +385,7 @@ export default function NutritionAnalysisHistoryScreen() {
{/* 详细信息 */}
{isExpanded && isSuccess && item.analysisResult && item.analysisResult.data && (
<View style={styles.detailsContainer}>
<Text style={styles.detailsTitle}></Text>
<Text style={styles.detailsTitle}>{t('nutritionAnalysisHistory.details.title')}</Text>
{item.analysisResult.data.map((nutritionItem: NutritionItem) => (
<View key={nutritionItem.key} style={styles.detailItem}>
<View style={styles.nutritionInfo}>
@@ -397,8 +399,8 @@ export default function NutritionAnalysisHistoryScreen() {
))}
<View style={styles.metaInfo}>
<Text style={styles.metaText}>AI : {item.aiModel}</Text>
<Text style={styles.metaText}>: {item.aiProvider}</Text>
<Text style={styles.metaText}>{t('nutritionAnalysisHistory.details.aiModel')}: {item.aiModel}</Text>
<Text style={styles.metaText}>{t('nutritionAnalysisHistory.details.provider')}: {item.aiProvider}</Text>
</View>
</View>
)}
@@ -410,8 +412,8 @@ export default function NutritionAnalysisHistoryScreen() {
const renderEmptyState = () => (
<View style={styles.emptyState}>
<Ionicons name="document-text-outline" size={64} color="#CCC" />
<Text style={styles.emptyStateText}></Text>
<Text style={styles.emptyStateSubtext}></Text>
<Text style={styles.emptyStateText}>{t('nutritionAnalysisHistory.empty.title')}</Text>
<Text style={styles.emptyStateSubtext}>{t('nutritionAnalysisHistory.empty.subtitle')}</Text>
</View>
);
@@ -419,8 +421,8 @@ export default function NutritionAnalysisHistoryScreen() {
const renderErrorState = () => (
<View style={styles.errorState}>
<Ionicons name="alert-circle-outline" size={64} color="#F44336" />
<Text style={styles.errorStateText}></Text>
<Text style={styles.errorStateSubtext}>{error || '未知错误'}</Text>
<Text style={styles.errorStateText}>{t('nutritionAnalysisHistory.errors.loadFailed')}</Text>
<Text style={styles.errorStateSubtext}>{error || t('nutritionAnalysisHistory.errors.unknownError')}</Text>
<TouchableOpacity
style={styles.retryButton}
onPress={() => {
@@ -428,7 +430,7 @@ export default function NutritionAnalysisHistoryScreen() {
fetchRecords(1, true);
}}
>
<Text style={styles.retryButtonText}></Text>
<Text style={styles.retryButtonText}>{t('nutritionAnalysisHistory.actions.retry')}</Text>
</TouchableOpacity>
</View>
);
@@ -440,7 +442,7 @@ export default function NutritionAnalysisHistoryScreen() {
return (
<View style={styles.loadingFooter}>
<ActivityIndicator size="small" color={Colors.light.primary} />
<Text style={styles.loadingFooterText}>...</Text>
<Text style={styles.loadingFooterText}>{t('nutritionAnalysisHistory.loadingMore')}</Text>
</View>
);
};
@@ -456,7 +458,7 @@ export default function NutritionAnalysisHistoryScreen() {
/>
<HeaderBar
title="历史记录"
title={t('nutritionAnalysisHistory.title')}
onBack={() => router.back()}
transparent={true}
/>
@@ -477,7 +479,7 @@ export default function NutritionAnalysisHistoryScreen() {
activeOpacity={0.7}
>
<Text style={[styles.filterButtonText, !statusFilter && styles.filterButtonTextActive]}>
{t('nutritionAnalysisHistory.filter.all')}
</Text>
</TouchableOpacity>
<TouchableOpacity
@@ -494,7 +496,7 @@ export default function NutritionAnalysisHistoryScreen() {
activeOpacity={0.7}
>
<Text style={[styles.filterButtonText, statusFilter === 'success' && styles.filterButtonTextActive]}>
{t('nutritionAnalysisHistory.status.success')}
</Text>
</TouchableOpacity>
<TouchableOpacity
@@ -511,7 +513,7 @@ export default function NutritionAnalysisHistoryScreen() {
activeOpacity={0.7}
>
<Text style={[styles.filterButtonText, statusFilter === 'failed' && styles.filterButtonTextActive]}>
{t('nutritionAnalysisHistory.status.failed')}
</Text>
</TouchableOpacity>
</View>
@@ -520,7 +522,7 @@ export default function NutritionAnalysisHistoryScreen() {
{loading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={Colors.light.primary} />
<Text style={styles.loadingText}>...</Text>
<Text style={styles.loadingText}>{t('nutritionAnalysisHistory.loading')}</Text>
</View>
) : (
<FlatList
@@ -555,7 +557,7 @@ export default function NutritionAnalysisHistoryScreen() {
HeaderComponent={() => (
<View style={styles.imageViewerHeader}>
<Text style={styles.imageViewerHeaderText}>
{dayjs().format('YYYY年M月D日 HH:mm')}
{dayjs().format(t('nutritionAnalysisHistory.dateFormat'))}
</Text>
</View>
)}
@@ -565,7 +567,7 @@ export default function NutritionAnalysisHistoryScreen() {
style={styles.imageViewerFooterButton}
onPress={() => setShowImagePreview(false)}
>
<Text style={styles.imageViewerFooterButtonText}></Text>
<Text style={styles.imageViewerFooterButtonText}>{t('nutritionLabelAnalysis.actions.close')}</Text>
</TouchableOpacity>
</View>
)}

View File

@@ -2,6 +2,7 @@ import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { useCosUpload } from '@/hooks/useCosUpload';
import { useI18n } from '@/hooks/useI18n';
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
import {
analyzeNutritionImage,
@@ -29,6 +30,7 @@ import {
import ImageViewing from 'react-native-image-viewing';
export default function NutritionLabelAnalysisScreen() {
const { t } = useI18n();
const safeAreaTop = useSafeAreaTop();
const router = useRouter();
const { pushIfAuthedElseLogin, ensureLoggedIn } = useAuthGuard();
@@ -77,7 +79,7 @@ export default function NutritionLabelAnalysisScreen() {
const requestCameraPermission = async () => {
const { status } = await ImagePicker.requestCameraPermissionsAsync();
if (status !== 'granted') {
Alert.alert('权限不足', '需要相机权限才能拍摄成分表');
Alert.alert(t('nutritionLabelAnalysis.camera.permissionDenied'), t('nutritionLabelAnalysis.camera.permissionMessage'));
return false;
}
return true;
@@ -153,7 +155,7 @@ export default function NutritionLabelAnalysisScreen() {
// 直接使用服务端返回的数据,不做任何转换
setNewAnalysisResult(analysisResponse);
} else {
throw new Error(analysisResponse.message || '分析失败');
throw new Error(analysisResponse.message || t('nutritionLabelAnalysis.errors.analysisFailed.defaultMessage'));
}
} catch (error: any) {
console.error('[NUTRITION_ANALYSIS] 新API分析失败:', error);
@@ -162,8 +164,8 @@ export default function NutritionLabelAnalysisScreen() {
// 显示错误提示
Alert.alert(
'分析失败',
error.message || '无法识别成分表,请尝试拍摄更清晰的照片'
t('nutritionLabelAnalysis.errors.analysisFailed.title'),
error.message || t('nutritionLabelAnalysis.errors.analysisFailed.message')
);
} finally {
setIsUploading(false);
@@ -182,7 +184,7 @@ export default function NutritionLabelAnalysisScreen() {
/>
<HeaderBar
title="成分表分析"
title={t('nutritionLabelAnalysis.title')}
onBack={() => router.back()}
transparent={true}
right={
@@ -253,7 +255,7 @@ export default function NutritionLabelAnalysisScreen() {
activeOpacity={0.8}
>
<Ionicons name="search-outline" size={20} color="#FFF" />
<Text style={styles.analyzeButtonText}></Text>
<Text style={styles.analyzeButtonText}>{t('nutritionLabelAnalysis.actions.startAnalysis')}</Text>
</TouchableOpacity>
)}
@@ -274,7 +276,7 @@ export default function NutritionLabelAnalysisScreen() {
<View style={styles.placeholderContainer}>
<View style={styles.placeholderContent}>
<Ionicons name="document-text-outline" size={48} color="#666" />
<Text style={styles.placeholderText}></Text>
<Text style={styles.placeholderText}>{t('nutritionLabelAnalysis.placeholder.text')}</Text>
</View>
{/* 操作按钮区域 */}
<View style={styles.imageActionButtonsContainer}>
@@ -284,7 +286,7 @@ export default function NutritionLabelAnalysisScreen() {
activeOpacity={0.8}
>
<Ionicons name="camera-outline" size={20} color={Colors.light.onPrimary} />
<Text style={styles.imageActionButtonText}></Text>
<Text style={styles.imageActionButtonText}>{t('nutritionLabelAnalysis.actions.takePhoto')}</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.imageActionButton, styles.imageActionButtonSecondary]}
@@ -292,7 +294,7 @@ export default function NutritionLabelAnalysisScreen() {
activeOpacity={0.8}
>
<Ionicons name="image-outline" size={20} color={Colors.light.primary} />
<Text style={[styles.imageActionButtonText, { color: Colors.light.primary }]}></Text>
<Text style={[styles.imageActionButtonText, { color: Colors.light.primary }]}>{t('nutritionLabelAnalysis.actions.selectFromAlbum')}</Text>
</TouchableOpacity>
</View>
</View>
@@ -307,7 +309,7 @@ export default function NutritionLabelAnalysisScreen() {
<View style={styles.analysisSectionHeaderIcon}>
<Ionicons name="document-text-outline" size={18} color="#6B6ED6" />
</View>
<Text style={styles.analysisSectionTitle}></Text>
<Text style={styles.analysisSectionTitle}>{t('nutritionLabelAnalysis.results.title')}</Text>
</View>
<View style={styles.analysisCardsWrapper}>
{newAnalysisResult.data.map((item, index) => (
@@ -352,7 +354,7 @@ export default function NutritionLabelAnalysisScreen() {
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={Colors.light.primary} />
<Text style={styles.loadingText}>
... {uploadProgress > 0 ? `${Math.round(uploadProgress)}%` : ''}
{t('nutritionLabelAnalysis.status.uploading')} {uploadProgress > 0 ? `${Math.round(uploadProgress)}%` : ''}
</Text>
</View>
)}
@@ -361,7 +363,7 @@ export default function NutritionLabelAnalysisScreen() {
{isAnalyzing && !newAnalysisResult && !isUploading && (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={Colors.light.primary} />
<Text style={styles.loadingText}>...</Text>
<Text style={styles.loadingText}>{t('nutritionLabelAnalysis.status.analyzing')}</Text>
</View>
)}
</ScrollView>
@@ -377,7 +379,7 @@ export default function NutritionLabelAnalysisScreen() {
HeaderComponent={() => (
<View style={styles.imageViewerHeader}>
<Text style={styles.imageViewerHeaderText}>
{dayjs().format('YYYY年M月D日 HH:mm')}
{dayjs().format(t('nutritionLabelAnalysis.imageViewer.dateFormat'))}
</Text>
</View>
)}
@@ -387,7 +389,7 @@ export default function NutritionLabelAnalysisScreen() {
style={styles.imageViewerFooterButton}
onPress={() => setShowImagePreview(false)}
>
<Text style={styles.imageViewerFooterButtonText}></Text>
<Text style={styles.imageViewerFooterButtonText}>{t('nutritionLabelAnalysis.actions.close')}</Text>
</TouchableOpacity>
</View>
)}
@@ -514,7 +516,7 @@ const styles = StyleSheet.create({
},
imageActionButtonText: {
color: Colors.light.onPrimary,
fontSize: 14,
fontSize: 12,
fontWeight: '600',
marginLeft: 6,
},