This commit is contained in:
imeepos 2026-01-28 14:15:33 +08:00
parent 30fa29b0ac
commit b46ad76161
7 changed files with 120 additions and 5 deletions

View File

@ -154,6 +154,7 @@ export default function HomeScreen() {
<TemplateCard
id={item.id}
title={item.title}
titleEn={item.titleEn}
previewUrl={item.previewUrl}
webpPreviewUrl={item.webpPreviewUrl}
coverImageUrl={item.coverImageUrl}

View File

@ -178,9 +178,11 @@ export const DynamicForm = forwardRef<DynamicFormRef, DynamicFormProps>(
const handlePickAndUploadImage = useCallback(async (nodeId: string) => {
try {
const [imageUri] = await imgPicker({ maxImages: 1 })
console.log('[DynamicForm] 选择图片成功:', imageUri)
// 上传图片
const url = await uploadFile({ uri: imageUri })
console.log('[DynamicForm] 上传成功URL:', url)
updateFormData(nodeId, url)
setPreviewImages((prev) => ({ ...prev, [nodeId]: imageUri }))
} catch (error: any) {
@ -188,7 +190,7 @@ export const DynamicForm = forwardRef<DynamicFormRef, DynamicFormProps>(
if (error?.message === '未选择任何图片') {
return
}
console.error('Pick and upload failed:', error)
console.error('[DynamicForm] 上传失败:', { error, message: error?.message, stack: error?.stack })
Toast.show(t('dynamicForm.uploadFailed') || '上传失败,请重试')
}
}, [t, updateFormData])

View File

@ -77,4 +77,50 @@ describe('TemplateCard Component', () => {
expect(typeof TemplateCard).toBe('object')
})
})
describe('Multi-language support', () => {
it('should display Chinese title when language is zh-CN', () => {
const title = '测试模板'
const titleEn = 'Test Template'
const language = 'zh-CN'
// 根据语言选择显示的标题
const displayTitle = language === 'en-US' ? titleEn : title
expect(displayTitle).toBe('测试模板')
})
it('should display English title when language is en-US', () => {
const title = '测试模板'
const titleEn = 'Test Template'
const language = 'en-US'
// 根据语言选择显示的标题
const displayTitle = language === 'en-US' ? titleEn : title
expect(displayTitle).toBe('Test Template')
})
it('should fallback to Chinese title when titleEn is missing', () => {
const title = '测试模板'
const titleEn = undefined
const language = 'en-US'
// 当 titleEn 不存在时,回退到中文标题
const displayTitle = language === 'en-US' && titleEn ? titleEn : title
expect(displayTitle).toBe('测试模板')
})
it('should fallback to Chinese title when titleEn is empty string', () => {
const title = '测试模板'
const titleEn = ''
const language = 'en-US'
// 当 titleEn 为空字符串时,回退到中文标题
const displayTitle = language === 'en-US' && titleEn ? titleEn : title
expect(displayTitle).toBe('测试模板')
})
})
})

View File

@ -2,10 +2,12 @@ import React, { memo, useCallback, useMemo } from 'react'
import { Pressable, StyleSheet, Text, View, ViewStyle, ImageStyle } from 'react-native'
import { Image } from 'expo-image'
import { LinearGradient } from 'expo-linear-gradient'
import { useTranslation } from 'react-i18next'
export interface TemplateCardProps {
id?: string
title: string
titleEn?: string
previewUrl?: string
webpPreviewUrl?: string
coverImageUrl?: string
@ -49,6 +51,7 @@ const GRADIENT_END = { x: 0, y: 1 }
const TemplateCardComponent: React.FC<TemplateCardProps> = ({
id,
title,
titleEn,
previewUrl,
webpPreviewUrl,
coverImageUrl,
@ -57,9 +60,16 @@ const TemplateCardComponent: React.FC<TemplateCardProps> = ({
onPress,
testID,
}) => {
const { i18n } = useTranslation()
const aspectRatio = useMemo(() => parseAspectRatio(aspectRatioString), [aspectRatioString])
const imageUri = useMemo(() => getImageUri(webpPreviewUrl, previewUrl, coverImageUrl), [webpPreviewUrl, previewUrl, coverImageUrl])
// Select display title based on current language
const displayTitle = useMemo(() => {
const isEnglish = i18n.language === 'en-US'
return isEnglish && titleEn ? titleEn : title
}, [i18n.language, title, titleEn])
// 使用 useCallback 缓存 onPress 回调
const handlePress = useCallback(() => {
if (id) {
@ -105,7 +115,7 @@ const TemplateCardComponent: React.FC<TemplateCardProps> = ({
style={styles.cardImageGradient}
/>
<Text style={styles.cardTitle} numberOfLines={1}>
{title}
{displayTitle}
</Text>
</View>
</Pressable>

View File

@ -103,6 +103,13 @@ const mockCategories: Category[] = [
{ id: 'cat-3', name: 'Category 3', templates: [] },
]
// Test data with multi-language support
const mockCategoriesWithI18n: Array<Category & { nameEn: string }> = [
{ id: 'cat-1', name: '分类1', nameEn: 'Category 1', templates: [] },
{ id: 'cat-2', name: '分类2', nameEn: 'Category 2', templates: [] },
{ id: 'cat-3', name: '分类3', nameEn: 'Category 3', templates: [] },
]
describe('useTabNavigation - core logic', () => {
describe('initialization', () => {
it('should select first category when initialCategoryId is empty', () => {
@ -256,4 +263,41 @@ describe('useTabNavigation - core logic', () => {
expect(state.currentCategory).toBeUndefined()
})
})
describe('multi-language support', () => {
it('should generate tabs with Chinese names when language is zh-CN', () => {
// 模拟中文环境
const language = 'zh-CN'
const tabs = mockCategoriesWithI18n.map(cat =>
language === 'en-US' ? cat.nameEn : cat.name
)
expect(tabs).toEqual(['分类1', '分类2', '分类3'])
})
it('should generate tabs with English names when language is en-US', () => {
// 模拟英文环境
const language = 'en-US'
const tabs = mockCategoriesWithI18n.map(cat =>
language === 'en-US' ? cat.nameEn : cat.name
)
expect(tabs).toEqual(['Category 1', 'Category 2', 'Category 3'])
})
it('should fallback to Chinese name when nameEn is missing', () => {
const categoriesWithMissingEn = [
{ id: 'cat-1', name: '分类1', nameEn: 'Category 1' },
{ id: 'cat-2', name: '分类2', nameEn: '' }, // nameEn 为空
{ id: 'cat-3', name: '分类3' }, // 没有 nameEn 字段
]
const language = 'en-US'
const tabs = categoriesWithMissingEn.map(cat =>
language === 'en-US' && cat.nameEn ? cat.nameEn : cat.name
)
expect(tabs).toEqual(['Category 1', '分类2', '分类3'])
})
})
})

View File

@ -1,9 +1,11 @@
import { useState, useMemo, useCallback, useEffect } from 'react'
import { CategoryTemplate } from '@repo/sdk'
import { useTranslation } from 'react-i18next'
export interface Category {
id: string
name: string
nameEn?: string
templates?: CategoryTemplate[]
}
@ -23,6 +25,7 @@ export interface UseTabNavigationReturn {
export function useTabNavigation(options: UseTabNavigationOptions): UseTabNavigationReturn {
const { categories, initialCategoryId } = options
const { i18n } = useTranslation()
// State for active tab index
const [activeIndex, setActiveIndex] = useState<number>(() => {
@ -52,10 +55,14 @@ export function useTabNavigation(options: UseTabNavigationOptions): UseTabNaviga
}
}, [categories, selectedCategoryId])
// Generate tabs array from category names
// Generate tabs array from category names with i18n support
const tabs = useMemo(() => {
return categories.map(category => category.name)
}, [categories])
const isEnglish = i18n.language === 'en-US'
return categories.map(category => {
// Use English name if available and language is English, otherwise use Chinese name
return isEnglish && category.nameEn ? category.nameEn : category.name
})
}, [categories, i18n.language])
// Get current category based on selectedCategoryId
const currentCategory = useMemo(() => {

View File

@ -6,6 +6,9 @@ import { handleError } from '@/hooks/use-error'
export async function uploadFile(params: { uri: string; mimeType?: string; fileName?: string }): Promise<string> {
const { uri, mimeType, fileName } = params
console.log('[uploadFile] 开始上传:', { uri, mimeType, fileName, platform: Platform.OS })
const formData = new FormData()
formData.append('file', {
uri: uri,
@ -17,8 +20,10 @@ export async function uploadFile(params: { uri: string; mimeType?: string; fileN
const { data, error } = await handleError(async () => await fileController.uploadS3(formData))
if (error || !data?.data) {
console.error('[uploadFile] 上传失败:', { error, data, uri })
throw error || new Error('上传失败')
}
console.log('[uploadFile] 上传成功:', data.data)
return data.data
}