- 新增自定义挑战创建页面,支持设置挑战类型、时间范围、目标值等 - 实现挑战邀请码系统,支持通过邀请码加入自定义挑战 - 完善挑战详情页面的多语言翻译支持 - 优化用户认证状态检查逻辑,使用token作为主要判断依据 - 添加阿里字体文件支持,提升UI显示效果 - 改进确认弹窗组件,支持Liquid Glass效果和自定义内容 - 优化应用启动流程,直接读取onboarding状态而非预加载用户数据
754 lines
20 KiB
Markdown
754 lines
20 KiB
Markdown
# 常见任务和模式
|
||
|
||
## 图标库使用规范 - 禁止使用 MaterialIcons
|
||
|
||
**最后更新**: 2025-10-24
|
||
|
||
### 重要规则
|
||
|
||
**项目中不允许使用 MaterialIcons**,所有图标必须使用 Ionicons 以保持图标库的一致性。
|
||
|
||
### 问题描述
|
||
|
||
在项目中发现使用 MaterialIcons 的情况,需要将所有 MaterialIcons 替换为 Ionicons,以保持图标库的一致性。
|
||
|
||
### 解决方案
|
||
|
||
将所有 MaterialIcons 导入和使用替换为对应的 Ionicons。
|
||
|
||
### 实现模式
|
||
|
||
#### 1. 替换导入语句
|
||
|
||
```typescript
|
||
// ❌ 禁止使用
|
||
import { MaterialIcons } from "@expo/vector-icons";
|
||
|
||
// ✅ 正确写法
|
||
import { Ionicons } from "@expo/vector-icons";
|
||
```
|
||
|
||
#### 2. 替换图标名称和属性
|
||
|
||
```typescript
|
||
// ❌ 禁止使用
|
||
<MaterialIcons name="arrow-back-ios" size={20} color="#333" />
|
||
|
||
// ✅ 正确写法 - 使用 HeaderBar 中的返回按钮实现
|
||
<Ionicons name="chevron-back" size={24} color="#333" />
|
||
```
|
||
|
||
#### 3. 常见图标映射
|
||
|
||
- `arrow-back-ios` → `chevron-back` (返回按钮)
|
||
- `auto-awesome` → `star` (星星/自动推荐)
|
||
- `tips-and-updates` → `bulb` (提示/建议)
|
||
- `fact-check` → `checkbox` (检查/确认)
|
||
- `check-circle` → `checkmark-circle` (勾选圆圈)
|
||
- `remove` → `remove` (移除/删除,名称相同)
|
||
|
||
### 重要注意事项
|
||
|
||
1. **图标大小调整**:Ionicons 和 MaterialIcons 的默认大小可能不同,需要适当调整
|
||
2. **图标名称差异**:两个图标库的图标名称不同,需要找到对应的功能图标
|
||
3. **样式一致性**:确保替换后的图标在视觉上与原设计保持一致
|
||
4. **Liquid Glass 兼容性**:替换后的图标需要继续支持 Liquid Glass 效果
|
||
5. **代码审查**:在代码审查中需要特别检查是否使用了 MaterialIcons
|
||
|
||
### 参考实现
|
||
|
||
- `components/ui/HeaderBar.tsx` - 返回按钮的标准实现
|
||
- `components/model/MembershipModal.tsx` - 完整的 MaterialIcons 替换示例
|
||
|
||
## 按钮组件 Liquid Glass 兼容性
|
||
|
||
**最后更新**: 2025-10-24
|
||
|
||
### 重要原则
|
||
|
||
**所有按钮组件都需要尝试兼容 Liquid Glass**,这是项目的设计要求。
|
||
|
||
### 实现模式
|
||
|
||
#### 1. 导入必要的组件
|
||
|
||
```typescript
|
||
import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
|
||
```
|
||
|
||
#### 2. 检查设备支持情况
|
||
|
||
```typescript
|
||
const isGlassAvailable = isLiquidGlassAvailable();
|
||
```
|
||
|
||
#### 3. 实现条件渲染的按钮
|
||
|
||
```typescript
|
||
<TouchableOpacity
|
||
onPress={handlePress}
|
||
disabled={isLoading}
|
||
activeOpacity={0.7}
|
||
>
|
||
{isLiquidGlassAvailable() ? (
|
||
<GlassView
|
||
style={styles.button}
|
||
glassEffectStyle="clear" // 或 "regular"
|
||
tintColor="rgba(255, 255, 255, 0.3)" // 自定义色调
|
||
isInteractive={true} // 启用交互反馈
|
||
>
|
||
<Ionicons name="icon-name" size={20} color="#333" />
|
||
</GlassView>
|
||
) : (
|
||
<View style={[styles.button, styles.fallbackButton]}>
|
||
<Ionicons name="icon-name" size={20} color="#333" />
|
||
</View>
|
||
)}
|
||
</TouchableOpacity>
|
||
```
|
||
|
||
#### 4. 定义样式
|
||
|
||
```typescript
|
||
const styles = StyleSheet.create({
|
||
button: {
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: 20, // 圆形按钮
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
overflow: "hidden", // 保证玻璃边界圆角效果
|
||
// 其他通用样式...
|
||
},
|
||
fallbackButton: {
|
||
backgroundColor: "rgba(255, 255, 255, 0.9)",
|
||
borderWidth: 1,
|
||
borderColor: "rgba(255, 255, 255, 0.3)",
|
||
},
|
||
});
|
||
```
|
||
|
||
### 重要注意事项
|
||
|
||
1. **兼容性检查**:必须使用 `isLiquidGlassAvailable()` 检查设备支持情况
|
||
2. **overflow: 'hidden'**:GlassView 组件需要设置此属性以保证圆角效果
|
||
3. **降级样式**:为不支持 Liquid Glass 的设备提供视觉上相似的替代方案
|
||
4. **交互反馈**:设置 `isInteractive={true}` 启用原生的触觉反馈
|
||
5. **图标居中**:确保使用 `alignItems: 'center'` 和 `justifyContent: 'center'` 使图标完全居中
|
||
6. **色调自定义**:通过 `tintColor` 属性自定义按钮的颜色主题
|
||
|
||
### 常用配置
|
||
|
||
- **glassEffectStyle**: "clear"(透明)或 "regular"(常规)
|
||
- **tintColor**: 根据按钮功能选择合适的颜色
|
||
- 返回/导航操作:白色系 `rgba(255, 255, 255, 0.3)`
|
||
- 删除操作:红色系 `rgba(244, 67, 54, 0.2)`
|
||
- 确认操作:绿色系 `rgba(76, 175, 80, 0.2)`
|
||
- 信息操作:蓝色系 `rgba(33, 150, 243, 0.2)`
|
||
|
||
### 参考实现
|
||
|
||
- `components/model/MembershipModal.tsx` - 悬浮返回按钮
|
||
- `components/glass/button.tsx` - 通用 Glass 按钮组件
|
||
- `app/(tabs)/_layout.tsx` - 标签栏按钮实现
|
||
|
||
## HeaderBar 顶部距离处理
|
||
|
||
**最后更新**: 2025-10-16
|
||
|
||
### 问题描述
|
||
|
||
当使用 HeaderBar 组件时,需要正确处理内容区域的顶部距离,确保内容不会被状态栏或刘海屏遮挡。
|
||
|
||
### 解决方案
|
||
|
||
使用 `useSafeAreaTop` hook 获取安全区域顶部距离,并应用到内容容器的样式中。
|
||
|
||
### 实现模式
|
||
|
||
#### 1. 导入必要的 hook
|
||
|
||
```typescript
|
||
import { useSafeAreaTop } from "@/hooks/useSafeAreaWithPadding";
|
||
```
|
||
|
||
#### 2. 在组件中获取 safeAreaTop
|
||
|
||
```typescript
|
||
const safeAreaTop = useSafeAreaTop();
|
||
```
|
||
|
||
#### 3. 应用到内容容器
|
||
|
||
```typescript
|
||
// 方式1: 直接应用到 View 组件
|
||
<View style={[styles.filterContainer, { paddingTop: safeAreaTop }]}>
|
||
|
||
// 方式2: 应用到 ScrollView 的 contentContainerStyle
|
||
<ScrollView
|
||
contentContainerStyle={{ paddingTop: safeAreaTop }}
|
||
>
|
||
|
||
// 方式3: 应用到 SectionList 的 style
|
||
<SectionList
|
||
style={{ paddingTop: safeAreaTop }}
|
||
>
|
||
```
|
||
|
||
### 重要注意事项
|
||
|
||
1. **不要在 StyleSheet 中使用变量**:不能在 `StyleSheet.create()` 中直接使用 `safeAreaTop` 变量
|
||
2. **使用动态样式**:必须通过内联样式或数组样式的方式动态应用 `safeAreaTop`
|
||
3. **不需要额外偏移**:通常只需要 `safeAreaTop`,不需要添加额外的固定像素值
|
||
|
||
### 示例代码
|
||
|
||
```typescript
|
||
// ❌ 错误写法 - 在 StyleSheet 中使用变量
|
||
const styles = StyleSheet.create({
|
||
filterContainer: {
|
||
paddingTop: safeAreaTop, // 这会导致错误
|
||
},
|
||
});
|
||
|
||
// ✅ 正确写法 - 使用动态样式
|
||
<View style={[styles.filterContainer, { paddingTop: safeAreaTop }]}>
|
||
```
|
||
|
||
### 参考页面
|
||
|
||
- `app/steps/detail.tsx`
|
||
- `app/water/detail.tsx`
|
||
- `app/profile/goals.tsx`
|
||
- `app/workout/history.tsx`
|
||
- `app/challenges/[id]/leaderboard.tsx`
|
||
|
||
## Liquid Glass 风格图标按钮实现
|
||
|
||
**最后更新**: 2025-10-16
|
||
|
||
### 问题描述
|
||
|
||
在应用中实现符合 Liquid Glass 设计风格的图标按钮,需要考虑毛玻璃效果和兼容性处理。
|
||
|
||
### 解决方案
|
||
|
||
使用 `GlassView` 组件实现毛玻璃效果,并提供不支持 Liquid Glass 的设备的降级方案。
|
||
|
||
### 实现模式
|
||
|
||
#### 1. 导入必要的组件和函数
|
||
|
||
```typescript
|
||
import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
|
||
```
|
||
|
||
#### 2. 检查设备支持情况
|
||
|
||
```typescript
|
||
const isGlassAvailable = isLiquidGlassAvailable();
|
||
```
|
||
|
||
#### 3. 实现条件渲染的按钮
|
||
|
||
```typescript
|
||
<TouchableOpacity
|
||
onPress={handlePress}
|
||
disabled={isLoading}
|
||
activeOpacity={0.7}
|
||
>
|
||
{isGlassAvailable ? (
|
||
<GlassView
|
||
style={styles.glassButton}
|
||
glassEffectStyle="clear" // 或 "regular"
|
||
tintColor="rgba(244, 67, 54, 0.2)" // 自定义色调
|
||
isInteractive={true} // 启用交互反馈
|
||
>
|
||
<Ionicons name="trash-outline" size={20} color="#F44336" />
|
||
</GlassView>
|
||
) : (
|
||
<View style={[styles.glassButton, styles.fallbackButton]}>
|
||
<Ionicons name="trash-outline" size={20} color="#F44336" />
|
||
</View>
|
||
)}
|
||
</TouchableOpacity>
|
||
```
|
||
|
||
#### 4. 定义样式
|
||
|
||
```typescript
|
||
const styles = StyleSheet.create({
|
||
glassButton: {
|
||
width: 36,
|
||
height: 36,
|
||
borderRadius: 18, // 圆形按钮
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
overflow: "hidden", // 保证玻璃边界圆角效果
|
||
},
|
||
fallbackButton: {
|
||
borderWidth: 1,
|
||
borderColor: "rgba(244, 67, 54, 0.3)",
|
||
backgroundColor: "rgba(244, 67, 54, 0.1)",
|
||
},
|
||
});
|
||
```
|
||
|
||
### 重要注意事项
|
||
|
||
1. **兼容性处理**:必须使用 `isLiquidGlassAvailable()` 检查设备支持情况
|
||
2. **overflow: 'hidden'**:GlassView 组件需要设置此属性以保证圆角效果
|
||
3. **降级样式**:为不支持 Liquid Glass 的设备提供视觉上相似的替代方案
|
||
4. **交互反馈**:设置 `isInteractive={true}` 启用原生的触觉反馈
|
||
5. **色调自定义**:通过 `tintColor` 属性自定义按钮的颜色主题
|
||
|
||
### 常用配置
|
||
|
||
- **glassEffectStyle**: "clear"(透明)或 "regular"(常规)
|
||
- **tintColor**: 根据按钮功能选择合适的颜色
|
||
- 删除操作:红色系 `rgba(244, 67, 54, 0.2)`
|
||
- 确认操作:绿色系 `rgba(76, 175, 80, 0.2)`
|
||
- 信息操作:蓝色系 `rgba(33, 150, 243, 0.2)`
|
||
|
||
### 参考实现
|
||
|
||
- `app/food/nutrition-analysis-history.tsx` - 删除按钮实现
|
||
- `components/glass/button.tsx` - 通用 Glass 按钮组件
|
||
- `app/(tabs)/_layout.tsx` - 标签栏按钮实现
|
||
|
||
## 登录验证实现模式
|
||
|
||
**最后更新**: 2025-10-16
|
||
|
||
### 问题描述
|
||
|
||
在应用中实现需要登录才能访问的功能时,需要判断用户是否已登录,未登录时先跳转到登录页面。
|
||
|
||
### 解决方案
|
||
|
||
使用 `useAuthGuard` hook 中的 `pushIfAuthedElseLogin` 方法处理需要登录验证的导航操作,使用 `ensureLoggedIn` 方法处理需要登录验证的功能实现。
|
||
|
||
### 权限校验原则
|
||
|
||
**重要**: 功能实现如果包含服务端接口的调用,需要使用 `ensureLoggedIn` 来判断用户是否登录。
|
||
|
||
### 实现模式
|
||
|
||
#### 1. 导入必要的 hook
|
||
|
||
```typescript
|
||
import { useAuthGuard } from "@/hooks/useAuthGuard";
|
||
```
|
||
|
||
#### 2. 在组件中获取方法
|
||
|
||
```typescript
|
||
const { pushIfAuthedElseLogin, ensureLoggedIn } = useAuthGuard();
|
||
```
|
||
|
||
#### 3. 替换导航操作
|
||
|
||
```typescript
|
||
// ❌ 原来的写法 - 没有登录验证
|
||
<TouchableOpacity
|
||
onPress={() => router.push('/food/nutrition-analysis-history')}
|
||
activeOpacity={0.7}
|
||
>
|
||
|
||
// ✅ 修改后的写法 - 带登录验证
|
||
<TouchableOpacity
|
||
onPress={() => pushIfAuthedElseLogin('/food/nutrition-analysis-history')}
|
||
activeOpacity={0.7}
|
||
>
|
||
```
|
||
|
||
#### 4. 服务端接口调用的登录验证
|
||
|
||
对于需要调用服务端接口的功能,使用 `ensureLoggedIn` 进行登录验证:
|
||
|
||
```typescript
|
||
// ❌ 原来的写法 - 没有登录验证
|
||
<TouchableOpacity
|
||
onPress={() => startNewAnalysis(imageUri)}
|
||
activeOpacity={0.8}
|
||
>
|
||
|
||
// ✅ 修改后的写法 - 带登录验证
|
||
<TouchableOpacity
|
||
onPress={async () => {
|
||
// 先验证登录状态
|
||
const isLoggedIn = await ensureLoggedIn();
|
||
if (isLoggedIn) {
|
||
startNewAnalysis(imageUri);
|
||
}
|
||
}}
|
||
activeOpacity={0.8}
|
||
>
|
||
```
|
||
|
||
#### 5. 完整示例(包含 Liquid Glass 兼容性处理)
|
||
|
||
```typescript
|
||
{
|
||
isLiquidGlassAvailable() ? (
|
||
<TouchableOpacity
|
||
onPress={() => pushIfAuthedElseLogin("/food/nutrition-analysis-history")}
|
||
activeOpacity={0.7}
|
||
>
|
||
<GlassView
|
||
style={styles.historyButton}
|
||
glassEffectStyle="clear"
|
||
tintColor="rgba(255, 255, 255, 0.2)"
|
||
isInteractive={true}
|
||
>
|
||
<Ionicons name="time-outline" size={24} color="#333" />
|
||
</GlassView>
|
||
</TouchableOpacity>
|
||
) : (
|
||
<TouchableOpacity
|
||
onPress={() => pushIfAuthedElseLogin("/food/nutrition-analysis-history")}
|
||
style={[styles.historyButton, styles.fallbackBackground]}
|
||
activeOpacity={0.7}
|
||
>
|
||
<Ionicons name="time-outline" size={24} color="#333" />
|
||
</TouchableOpacity>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 重要注意事项
|
||
|
||
1. **统一体验**:使用 `pushIfAuthedElseLogin` 可以确保登录后自动跳转到目标页面
|
||
2. **参数传递**:该方法支持传递路由参数,格式为 `pushIfAuthedElseLogin('/path', { param: value })`
|
||
3. **登录重定向**:登录页面会接收 `redirectTo` 和 `redirectParams` 参数用于登录后跳转
|
||
4. **兼容性**:与 Liquid Glass 设计风格完全兼容,可以同时使用
|
||
5. **服务端接口调用**:所有调用服务端接口的功能必须使用 `ensureLoggedIn` 进行登录验证
|
||
6. **异步处理**:`ensureLoggedIn` 是异步函数,需要使用 `await` 等待结果
|
||
|
||
### 其他可用方法
|
||
|
||
- `ensureLoggedIn()` - 检查登录状态,未登录时跳转到登录页面,返回布尔值表示是否已登录
|
||
- `guardHandler(fn, options)` - 包装一个函数,在执行前确保用户已登录
|
||
- `isLoggedIn` - 布尔值,表示当前用户是否已登录
|
||
|
||
### 使用场景选择
|
||
|
||
- **页面导航**:使用 `pushIfAuthedElseLogin` 处理页面跳转
|
||
- **服务端接口调用**:使用 `ensureLoggedIn` 验证登录状态后再执行功能
|
||
- **函数包装**:使用 `guardHandler` 包装需要登录验证的函数
|
||
|
||
### 参考实现
|
||
|
||
- `app/food/nutrition-label-analysis.tsx` - 成分表分析功能登录验证
|
||
- `app/(tabs)/personal.tsx` - 个人中心编辑按钮
|
||
- `hooks/useAuthGuard.ts` - 完整的认证守卫实现
|
||
|
||
## 路由常量管理
|
||
|
||
**最后更新**: 2025-10-16
|
||
|
||
### 问题描述
|
||
|
||
在应用开发中,所有路由路径都应该使用常量定义,而不是硬编码字符串。这样可以确保路由的一致性,便于维护和重构。
|
||
|
||
### 解决方案
|
||
|
||
将所有路由路径定义在 `constants/Routes.ts` 文件中,并在组件中使用这些常量。
|
||
|
||
### 实现模式
|
||
|
||
#### 1. 添加新路由常量
|
||
|
||
在 `constants/Routes.ts` 文件中添加新的路由常量:
|
||
|
||
```typescript
|
||
export const ROUTES = {
|
||
// 现有路由...
|
||
|
||
// 新增路由
|
||
FOOD_CAMERA: "/food/camera",
|
||
} as const;
|
||
```
|
||
|
||
#### 2. 在组件中使用路由常量
|
||
|
||
导入并使用路由常量,而不是硬编码路径:
|
||
|
||
```typescript
|
||
import { ROUTES } from "@/constants/Routes";
|
||
|
||
// ❌ 错误写法 - 硬编码路径
|
||
router.push("/food/camera?mealType=dinner");
|
||
|
||
// ✅ 正确写法 - 使用路由常量
|
||
router.push(`${ROUTES.FOOD_CAMERA}?mealType=dinner`);
|
||
```
|
||
|
||
#### 3. 结合登录验证使用
|
||
|
||
对于需要登录验证的路由,结合 `pushIfAuthedElseLogin` 使用:
|
||
|
||
```typescript
|
||
import { ROUTES } from '@/constants/Routes';
|
||
import { useAuthGuard } from '@/hooks/useAuthGuard';
|
||
|
||
const { pushIfAuthedElseLogin } = useAuthGuard();
|
||
|
||
// 在需要登录验证的路由中使用
|
||
<TouchableOpacity
|
||
onPress={() => pushIfAuthedElseLogin(`${ROUTES.FOOD_CAMERA}?mealType=${currentMealType}`)}
|
||
activeOpacity={0.7}
|
||
>
|
||
```
|
||
|
||
### 重要注意事项
|
||
|
||
1. **统一管理**:所有路由路径都必须在 `constants/Routes.ts` 中定义
|
||
2. **命名规范**:使用大写字母和下划线,如 `FOOD_CAMERA`
|
||
3. **路径一致性**:常量名应该清晰表达路由的用途
|
||
4. **参数处理**:查询参数和路径参数在使用时动态拼接
|
||
5. **类型安全**:使用 `as const` 确保类型推导
|
||
|
||
### 路由分类
|
||
|
||
按照功能模块对路由进行分组:
|
||
|
||
```typescript
|
||
export const ROUTES = {
|
||
// Tab路由
|
||
TAB_EXPLORE: "/explore",
|
||
TAB_COACH: "/coach",
|
||
|
||
// 营养相关路由
|
||
NUTRITION_RECORDS: "/nutrition/records",
|
||
FOOD_LIBRARY: "/food-library",
|
||
FOOD_CAMERA: "/food/camera",
|
||
|
||
// 用户相关路由
|
||
AUTH_LOGIN: "/auth/login",
|
||
PROFILE_EDIT: "/profile/edit",
|
||
} as const;
|
||
```
|
||
|
||
### 参考实现
|
||
|
||
- `constants/Routes.ts` - 路由常量定义
|
||
- `components/NutritionRadarCard.tsx` - 使用路由常量和登录验证
|
||
- `app/food/camera.tsx` - 食物拍照页面实现
|
||
|
||
## 多语言翻译实现规范
|
||
|
||
**最后更新**: 2025-11-26
|
||
|
||
### 重要原则
|
||
|
||
**所有用户可见的文本都必须支持多语言翻译**,这是项目的基本要求。不允许在代码中硬编码任何用户可见的中文或英文文本。
|
||
|
||
### 问题描述
|
||
|
||
在开发新功能或修改现有功能时,所有用户界面文本都需要支持多语言切换,确保应用能够为不同语言用户提供本地化体验。
|
||
|
||
### 解决方案
|
||
|
||
使用项目集成的 i18next 翻译系统,在 `i18n/index.ts` 中定义翻译资源,在组件中使用 `useI18n` hook 获取翻译文本。
|
||
|
||
### 实现模式
|
||
|
||
#### 1. 导入必要的 hook
|
||
|
||
```typescript
|
||
import { useI18n } from "@/hooks/useI18n";
|
||
```
|
||
|
||
#### 2. 在组件中获取翻译函数
|
||
|
||
```typescript
|
||
const { t } = useI18n();
|
||
```
|
||
|
||
#### 3. 添加翻译资源
|
||
|
||
在 `i18n/index.ts` 中为新的功能模块添加翻译资源:
|
||
|
||
```typescript
|
||
// 中文翻译
|
||
const newFeatureResources = {
|
||
title: "新功能标题",
|
||
subtitle: "新功能描述",
|
||
button: "按钮文本",
|
||
loading: "加载中...",
|
||
error: "操作失败,请稍后重试",
|
||
success: "操作成功",
|
||
};
|
||
|
||
// 英文翻译
|
||
const newFeatureResourcesEn = {
|
||
title: "New Feature Title",
|
||
subtitle: "New feature description",
|
||
button: "Button Text",
|
||
loading: "Loading...",
|
||
error: "Operation failed, please try again later",
|
||
success: "Operation successful",
|
||
};
|
||
|
||
// 添加到资源对象中
|
||
resources = {
|
||
zh: {
|
||
translation: {
|
||
// 现有翻译...
|
||
newFeature: newFeatureResources,
|
||
},
|
||
},
|
||
en: {
|
||
translation: {
|
||
// 现有翻译...
|
||
newFeature: newFeatureResourcesEn,
|
||
},
|
||
},
|
||
};
|
||
```
|
||
|
||
#### 4. 在组件中使用翻译
|
||
|
||
```typescript
|
||
// ❌ 错误写法 - 硬编码文本
|
||
<Text>加载中...</Text>
|
||
<Text>操作失败,请稍后重试</Text>
|
||
|
||
// ✅ 正确写法 - 使用翻译函数
|
||
<Text>{t('newFeature.loading')}</Text>
|
||
<Text>{t('newFeature.error')}</Text>
|
||
```
|
||
|
||
#### 5. 动态参数翻译
|
||
|
||
对于包含动态参数的文本,使用插值语法:
|
||
|
||
```typescript
|
||
// 翻译资源中
|
||
welcome: '欢迎,{{name}}!'
|
||
itemsCount: '共 {{count}} 个项目'
|
||
|
||
// 组件中使用
|
||
<Text>{t('newFeature.welcome', { name: userName })}</Text>
|
||
<Text>{t('newFeature.itemsCount', { count: items.length })}</Text>
|
||
```
|
||
|
||
#### 6. 嵌套翻译键
|
||
|
||
对于复杂功能,使用嵌套的翻译键结构:
|
||
|
||
```typescript
|
||
// 翻译资源
|
||
modal: {
|
||
title: '确认操作',
|
||
description: '确定要执行此操作吗?',
|
||
buttons: {
|
||
confirm: '确认',
|
||
cancel: '取消',
|
||
},
|
||
}
|
||
|
||
// 组件中使用
|
||
<Text>{t('newFeature.modal.title')}</Text>
|
||
<Text>{t('newFeature.modal.buttons.confirm')}</Text>
|
||
```
|
||
|
||
### 重要注意事项
|
||
|
||
1. **禁止硬编码**:所有用户可见的文本都必须通过翻译函数获取
|
||
2. **完整翻译**:中文和英文翻译都必须提供,保持翻译完整性
|
||
3. **语义化命名**:翻译键应该清晰表达文本的用途和含义
|
||
4. **参数化文本**:包含动态内容的文本应该使用插值参数
|
||
5. **一致性**:相同功能的文本应该使用相同的翻译键
|
||
6. **Toast 消息**:Toast 提示消息也需要翻译支持
|
||
7. **错误消息**:错误提示信息必须支持多语言
|
||
8. **表单验证**:表单验证错误信息需要翻译
|
||
|
||
### 常见翻译模式
|
||
|
||
#### 1. 状态文本
|
||
|
||
```typescript
|
||
status: {
|
||
loading: '加载中...',
|
||
success: '操作成功',
|
||
error: '操作失败',
|
||
empty: '暂无数据',
|
||
}
|
||
```
|
||
|
||
#### 2. 按钮文本
|
||
|
||
```typescript
|
||
buttons: {
|
||
confirm: '确认',
|
||
cancel: '取消',
|
||
save: '保存',
|
||
delete: '删除',
|
||
edit: '编辑',
|
||
add: '添加',
|
||
}
|
||
```
|
||
|
||
#### 3. 表单相关
|
||
|
||
```typescript
|
||
form: {
|
||
placeholders: {
|
||
email: '请输入邮箱地址',
|
||
password: '请输入密码',
|
||
},
|
||
errors: {
|
||
required: '此字段为必填项',
|
||
invalid: '格式不正确',
|
||
},
|
||
}
|
||
```
|
||
|
||
#### 4. 列表和表格
|
||
|
||
```typescript
|
||
list: {
|
||
empty: '暂无数据',
|
||
loading: '加载中...',
|
||
loadMore: '加载更多',
|
||
refresh: '刷新',
|
||
}
|
||
```
|
||
|
||
### 翻译键命名规范
|
||
|
||
1. **使用小写字母和点号分隔**:`feature.section.item`
|
||
2. **按功能模块分组**:`challenges.title`, `challenges.subtitle`
|
||
3. **语义化命名**:`buttons.confirm`, `errors.network`
|
||
4. **避免缩写**:使用 `description` 而不是 `desc`
|
||
|
||
### 参考实现
|
||
|
||
- `app/(tabs)/challenges.tsx` - 完整的多语言翻译实现示例
|
||
- `i18n/index.ts` - 翻译资源配置
|
||
- `hooks/useI18n.ts` - 翻译 hook 实现
|
||
- `app/(tabs)/personal.tsx` - 个人中心页面翻译实现
|
||
- `app/food/nutrition-label-analysis.tsx` - 营养分析页面翻译实现
|
||
|
||
### 检查清单
|
||
|
||
在开发新功能时,请确保:
|
||
|
||
- [ ] 所有用户可见的文本都使用了翻译函数
|
||
- [ ] 在 `i18n/index.ts` 中添加了对应的中文和英文翻译
|
||
- [ ] Toast 消息支持多语言
|
||
- [ ] 错误提示信息支持多语言
|
||
- [ ] 表单验证错误信息支持多语言
|
||
- [ ] 动态参数文本使用了插值语法
|
||
- [ ] 翻译键命名符合规范
|
||
- [ ] 测试了语言切换功能
|
||
|
||
### 最佳实践
|
||
|
||
1. **开发时即考虑多语言**:在编写组件时就使用翻译函数,而不是事后添加
|
||
2. **保持翻译一致性**:相同含义的文本使用相同的翻译键
|
||
3. **定期审查**:定期检查是否有硬编码文本遗漏
|
||
4. **测试验证**:在开发完成后测试语言切换功能是否正常
|