fix(i18n): 清除前端页面中残留的硬编码中文

问题: 在英文/日文语言环境下,部分页面仍显示中文字符

修复内容:

## genex-mobile
- register_page.dart: `const TextSpan(text: ' 和 ')` → `context.t('register.and')`
- 4 语言文件新增 `register.and` key (和/和/and/および)

## miniapp
- login + h5-register: 移除 Toast fallback 中文 (`|| '验证码已发送'` 等)
- login + h5-register: 移除 JSX 中硬编码的《》书名号,改为 i18n 值内包含
  · zh-CN: `《用户协议》`、`《隐私政策》`
  · en-US: `User Agreement`、`Privacy Policy` (无括号)
  · ja-JP: `「利用規約」`、`「プライバシーポリシー」`
- ai-guide 组件: 4 个推荐标签从硬编码改为 `t('ai_recommend_N')`
  · 新增 12 个 i18n key (3 语言 × 4 标签)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-02-23 21:27:24 -08:00
parent e59c0d0527
commit 7d00cade2f
9 changed files with 35 additions and 19 deletions

View File

@ -71,6 +71,7 @@ const Map<String, String> en = {
'register.agreement': 'I have read and agree to',
'register.userAgreement': 'Terms of Service',
'register.privacyPolicy': 'Privacy Policy',
'register.and': 'and',
'register.submit': 'Sign Up',
'register.stepVerify': 'Verify',
'register.stepPassword': 'Password',

View File

@ -71,6 +71,7 @@ const Map<String, String> ja = {
'register.agreement': '以下に同意します',
'register.userAgreement': '「利用規約」',
'register.privacyPolicy': '「プライバシーポリシー」',
'register.and': 'および',
'register.submit': '登録',
'register.stepVerify': '認証',
'register.stepPassword': 'パスワード設定',

View File

@ -71,6 +71,7 @@ const Map<String, String> zhCN = {
'register.agreement': '我已阅读并同意',
'register.userAgreement': '《用户协议》',
'register.privacyPolicy': '《隐私政策》',
'register.and': '',
'register.submit': '注册',
'register.stepVerify': '验证',
'register.stepPassword': '设密码',

View File

@ -71,6 +71,7 @@ const Map<String, String> zhTW = {
'register.agreement': '我已閱讀並同意',
'register.userAgreement': '《使用者協議》',
'register.privacyPolicy': '《隱私權政策》',
'register.and': '',
'register.submit': '註冊',
'register.stepVerify': '驗證',
'register.stepPassword': '設密碼',

View File

@ -267,7 +267,7 @@ class _RegisterPageState extends State<RegisterPage> {
text: context.t('register.userAgreement'),
style: AppTypography.bodySmall.copyWith(color: AppColors.primary),
),
const TextSpan(text: ' '),
TextSpan(text: ' ${context.t('register.and')} '),
TextSpan(
text: context.t('register.privacyPolicy'),
style: AppTypography.bodySmall.copyWith(color: AppColors.primary),

View File

@ -18,10 +18,10 @@ const AiGuide: React.FC<AiGuideProps> = ({ type }) => {
return (
<scroll-view scrollX className="ai-suggest-bar">
{[
{ id: 1, text: '星巴克 8.5折' },
{ id: 2, text: 'Nike 限时特价' },
{ id: 3, text: '新品餐饮券' },
{ id: 4, text: '高评级推荐' },
{ id: 1, text: t('ai_recommend_1') },
{ id: 2, text: t('ai_recommend_2') },
{ id: 3, text: t('ai_recommend_3') },
{ id: 4, text: t('ai_recommend_4') },
].map(s => (
<view key={s.id} className="ai-tag">
<text className="ai-tag-icon"></text>

View File

@ -151,8 +151,8 @@ const translations: Record<Locale, Record<string, string>> = {
'login_send_code': '获取验证码',
'login_resend': '{seconds}秒后重发',
'login_agree_prefix': '我已阅读并同意',
'login_user_agreement': '用户协议',
'login_privacy_policy': '隐私政策',
'login_user_agreement': '用户协议',
'login_privacy_policy': '隐私政策',
'login_and': '和',
// ── Redeem ──
@ -310,6 +310,10 @@ const translations: Record<Locale, Record<string, string>> = {
// ── AI Guide ──
'ai_guide_greeting': '你好我是AI助手可以帮你找到最适合的券。试试搜索"星巴克"',
'ai_recommend_1': '星巴克 8.5折',
'ai_recommend_2': 'Nike 限时特价',
'ai_recommend_3': '新品餐饮券',
'ai_recommend_4': '高评级推荐',
// ── Share Card ──
'share_miniapp_code': '小程序码',
@ -766,6 +770,10 @@ const translations: Record<Locale, Record<string, string>> = {
// ── AI Guide ──
'ai_guide_greeting': 'Hi! I\'m the AI assistant. I can help you find the best coupons. Try searching "Starbucks"!',
'ai_recommend_1': 'Starbucks 15% off',
'ai_recommend_2': 'Nike Flash Sale',
'ai_recommend_3': 'New Dining Coupons',
'ai_recommend_4': 'Top Rated Picks',
// ── Share Card ──
'share_miniapp_code': 'Mini Program Code',
@ -1063,8 +1071,8 @@ const translations: Record<Locale, Record<string, string>> = {
'login_send_code': 'コードを送信',
'login_resend': '{seconds}秒後に再送信',
'login_agree_prefix': '以下に同意します:',
'login_user_agreement': '利用規約',
'login_privacy_policy': 'プライバシーポリシー',
'login_user_agreement': '利用規約',
'login_privacy_policy': 'プライバシーポリシー',
'login_and': 'および',
// ── Redeem ──
@ -1222,6 +1230,10 @@ const translations: Record<Locale, Record<string, string>> = {
// ── AI Guide ──
'ai_guide_greeting': 'こんにちはAIアシスタントです。最適なクーポンを見つけるお手伝いをします。「スターバックス」で検索してみてください',
'ai_recommend_1': 'スタバ 15%オフ',
'ai_recommend_2': 'Nike タイムセール',
'ai_recommend_3': '新作グルメクーポン',
'ai_recommend_4': '高評価おすすめ',
// ── Share Card ──
'share_miniapp_code': 'ミニプログラムコード',

View File

@ -26,7 +26,7 @@ const H5RegisterPage: React.FC = () => {
setCodeSending(true);
sendSmsCode(phone, 'REGISTER')
.then(() => {
Taro.showToast({ title: t('login_code_sent') || '验证码已发送', icon: 'success' });
Taro.showToast({ title: t('login_code_sent'), icon: 'success' });
setCodeCountdown(60);
const timer = setInterval(() => {
setCodeCountdown((prev) => {
@ -51,7 +51,7 @@ const H5RegisterPage: React.FC = () => {
const { config } = require('../../config');
Taro.setStorageSync(config.TOKEN_KEY, result.accessToken);
Taro.setStorageSync(config.USER_KEY, JSON.stringify(result.user));
Taro.showToast({ title: t('register_success') || '注册成功', icon: 'success' });
Taro.showToast({ title: t('register_success'), icon: 'success' });
setTimeout(() => {
Taro.reLaunch({ url: '/pages/home/index' });
}, 1000);
@ -163,15 +163,15 @@ const H5RegisterPage: React.FC = () => {
</view>
<view className="terms-text-wrap">
<text className="terms-text-normal">{t('login_agree_prefix')}</text>
<text className="terms-text-link">{`${t('login_user_agreement')}`}</text>
<text className="terms-text-link">{t('login_user_agreement')}</text>
<text className="terms-text-normal">{t('login_and')}</text>
<text className="terms-text-link">{`${t('login_privacy_policy')}`}</text>
<text className="terms-text-link">{t('login_privacy_policy')}</text>
</view>
</view>
{/* Primary CTA Button */}
<view className="register-btn" onClick={handleRegister}>
<text className="register-btn-text">{submitting ? (t('loading') || '注册中...') : t('register_now')}</text>
<text className="register-btn-text">{submitting ? t('loading') : t('register_now')}</text>
</view>
{/* Social Login Divider */}

View File

@ -25,7 +25,7 @@ const LoginPage: React.FC = () => {
setCodeSending(true);
sendSmsCode(phone, 'LOGIN')
.then(() => {
Taro.showToast({ title: t('login_code_sent') || '验证码已发送', icon: 'success' });
Taro.showToast({ title: t('login_code_sent'), icon: 'success' });
setCodeCountdown(60);
const timer = setInterval(() => {
setCodeCountdown((prev) => {
@ -75,7 +75,7 @@ const LoginPage: React.FC = () => {
<view className="login-actions">
<view className="wechat-btn" onClick={handleWechatLogin}>
<text className="wechat-icon">💬</text>
<text className="wechat-text">{logging ? (t('loading') || '登录中...') : t('login_wechat')}</text>
<text className="wechat-text">{logging ? t('loading') : t('login_wechat')}</text>
</view>
<view className="divider">
@ -116,16 +116,16 @@ const LoginPage: React.FC = () => {
</view>
</view>
<view className="login-btn" onClick={handlePhoneLogin}>
<text className="login-btn-text">{logging ? (t('loading') || '登录中...') : t('login_btn')}</text>
<text className="login-btn-text">{logging ? t('loading') : t('login_btn')}</text>
</view>
</view>
{/* Terms */}
<view className="terms">
<text className="terms-text">{t('login_agree')}</text>
<text className="terms-link">{`${t('login_user_agreement')}`}</text>
<text className="terms-link">{t('login_user_agreement')}</text>
<text className="terms-text">{t('login_and')}</text>
<text className="terms-link">{`${t('login_privacy_policy')}`}</text>
<text className="terms-link">{t('login_privacy_policy')}</text>
</view>
</view>
</view>