feat(i18n): 全面实现应用核心功能模块的国际化支持
- 新增 i18n 翻译资源,覆盖睡眠、饮水、体重、锻炼、用药 AI 识别、步数、健身圆环、基础代谢及设置等核心模块 - 重构相关页面及组件(如 SleepDetail, WaterDetail, WorkoutHistory 等)使用 `useI18n` 钩子替换硬编码文本 - 升级 `utils/date` 工具库与 `DateSelector` 组件,支持基于语言环境的日期格式化与显示 - 完善登录页、注销流程及权限申请弹窗的双语提示信息 - 优化部分页面的 UI 细节与字体样式以适配多语言显示
This commit is contained in:
@@ -13,6 +13,7 @@ import { PRIVACY_POLICY_URL, USER_AGREEMENT_URL } from '@/constants/Agree';
|
||||
import { Colors } from '@/constants/Colors';
|
||||
import { useAppDispatch } from '@/hooks/redux';
|
||||
import { useColorScheme } from '@/hooks/useColorScheme';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { fetchMyProfile, login } from '@/store/userSlice';
|
||||
import Toast from 'react-native-toast-message';
|
||||
|
||||
@@ -23,6 +24,7 @@ export default function LoginScreen() {
|
||||
const color = Colors[scheme];
|
||||
const pageBackground = scheme === 'light' ? color.pageBackgroundEmphasis : color.background;
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useI18n();
|
||||
const AnimatedLinear = useMemo(() => Animated.createAnimatedComponent(LinearGradient), []);
|
||||
|
||||
// 背景动效:轻微平移/旋转与呼吸动画
|
||||
@@ -79,12 +81,12 @@ export default function LoginScreen() {
|
||||
const guardAgreement = useCallback((action: () => void) => {
|
||||
if (!hasAgreed) {
|
||||
Alert.alert(
|
||||
'请先阅读并同意',
|
||||
'继续登录前,请阅读并勾选《隐私政策》和《用户协议》。点击“同意并继续”将默认勾选并继续登录。',
|
||||
t('login.agreement.alert.title'),
|
||||
t('login.agreement.alert.message'),
|
||||
[
|
||||
{ text: '取消', style: 'cancel' },
|
||||
{ text: t('login.agreement.alert.cancel'), style: 'cancel' },
|
||||
{
|
||||
text: '同意并继续',
|
||||
text: t('login.agreement.alert.confirm'),
|
||||
onPress: () => {
|
||||
setHasAgreed(true);
|
||||
setTimeout(() => action(), 0);
|
||||
@@ -96,7 +98,7 @@ export default function LoginScreen() {
|
||||
return;
|
||||
}
|
||||
action();
|
||||
}, [hasAgreed]);
|
||||
}, [hasAgreed, t]);
|
||||
|
||||
const onAppleLogin = useCallback(async () => {
|
||||
if (!appleAvailable) return;
|
||||
@@ -110,7 +112,7 @@ export default function LoginScreen() {
|
||||
});
|
||||
const identityToken = (credential as any)?.identityToken;
|
||||
if (!identityToken || typeof identityToken !== 'string') {
|
||||
throw new Error('未获取到 Apple 身份令牌');
|
||||
throw new Error(t('login.errors.appleIdentityTokenMissing'));
|
||||
}
|
||||
await dispatch(login({ appleIdentityToken: identityToken })).unwrap();
|
||||
|
||||
@@ -118,7 +120,7 @@ export default function LoginScreen() {
|
||||
await dispatch(fetchMyProfile())
|
||||
|
||||
Toast.show({
|
||||
text1: '登录成功',
|
||||
text1: t('login.success.loginSuccess'),
|
||||
type: 'success',
|
||||
});
|
||||
// 登录成功后处理重定向
|
||||
@@ -145,12 +147,12 @@ export default function LoginScreen() {
|
||||
console.log('err.code', err.code);
|
||||
|
||||
if (err?.code === 'ERR_CANCELED' || err?.code === 'ERR_REQUEST_CANCELED') return;
|
||||
const message = err?.message || '登录失败,请稍后再试';
|
||||
Alert.alert('登录失败', message);
|
||||
const message = err?.message || t('login.errors.loginFailed');
|
||||
Alert.alert(t('login.errors.loginFailedTitle'), message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [appleAvailable, router, searchParams?.redirectParams, searchParams?.redirectTo]);
|
||||
}, [appleAvailable, router, searchParams?.redirectParams, searchParams?.redirectTo, dispatch, t]);
|
||||
|
||||
|
||||
// 登录按钮不再因未勾选协议而禁用,仅在加载中禁用
|
||||
@@ -244,14 +246,14 @@ export default function LoginScreen() {
|
||||
<Ionicons name="chevron-back" size={24} color={scheme === 'dark' ? '#ECEDEE' : '#192126'} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
<Text style={[styles.headerTitle, { color: color.text }]}>登录</Text>
|
||||
<Text style={[styles.headerTitle, { color: color.text }]}>{t('login.title')}</Text>
|
||||
<View style={{ width: 32 }} />
|
||||
</View>
|
||||
|
||||
<ScrollView contentContainerStyle={styles.content} showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.headerWrap}>
|
||||
<ThemedText style={[styles.title, { color: color.text }]}>Out Live</ThemedText>
|
||||
<ThemedText style={[styles.subtitle, { color: color.textMuted }]}>健康生活,自律让我更自由</ThemedText>
|
||||
<ThemedText style={[styles.subtitle, { color: color.textMuted }]}>{t('login.subtitle')}</ThemedText>
|
||||
</View>
|
||||
|
||||
{/* Apple 登录 */}
|
||||
@@ -276,12 +278,12 @@ export default function LoginScreen() {
|
||||
color="#FFFFFF"
|
||||
style={{ marginRight: 10 }}
|
||||
/>
|
||||
<Text style={styles.appleText}>登录中...</Text>
|
||||
<Text style={styles.appleText}>{t('login.loggingIn')}</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="logo-apple" size={22} color="#FFFFFF" style={{ marginRight: 10 }} />
|
||||
<Text style={styles.appleText}>使用 Apple 登录</Text>
|
||||
<Text style={styles.appleText}>{t('login.appleLogin')}</Text>
|
||||
</>
|
||||
)}
|
||||
</GlassView>
|
||||
@@ -294,12 +296,12 @@ export default function LoginScreen() {
|
||||
color="#FFFFFF"
|
||||
style={{ marginRight: 10 }}
|
||||
/>
|
||||
<Text style={styles.appleText}>登录中...</Text>
|
||||
<Text style={styles.appleText}>{t('login.loggingIn')}</Text>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Ionicons name="logo-apple" size={22} color="#FFFFFF" style={{ marginRight: 10 }} />
|
||||
<Text style={styles.appleText}>使用 Apple 登录</Text>
|
||||
<Text style={styles.appleText}>{t('login.appleLogin')}</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
@@ -319,13 +321,13 @@ export default function LoginScreen() {
|
||||
{hasAgreed && <Ionicons name="checkmark" size={14} color={color.onPrimary} />}
|
||||
</View>
|
||||
</Pressable>
|
||||
<Text style={[styles.agreementText, { color: color.textMuted }]}>我已阅读并同意</Text>
|
||||
<Text style={[styles.agreementText, { color: color.textMuted }]}>{t('login.agreement.readAndAgree')}</Text>
|
||||
<Pressable onPress={() => Linking.openURL(PRIVACY_POLICY_URL)}>
|
||||
<Text style={[styles.link, { color: color.primary }]}>《隐私政策》</Text>
|
||||
<Text style={[styles.link, { color: color.primary }]}>{t('login.agreement.privacyPolicy')}</Text>
|
||||
</Pressable>
|
||||
<Text style={[styles.agreementText, { color: color.textMuted }]}>和</Text>
|
||||
<Text style={[styles.agreementText, { color: color.textMuted }]}>{t('login.agreement.and')}</Text>
|
||||
<Pressable onPress={() => Linking.openURL(USER_AGREEMENT_URL)}>
|
||||
<Text style={[styles.link, { color: color.primary }]}>《用户协议》</Text>
|
||||
<Text style={[styles.link, { color: color.primary }]}>{t('login.agreement.userAgreement')}</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user