feat(client): i18n copy + reco lang refresh + home polish
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
export type MockContentItem = {
|
||||
id: string;
|
||||
text: string;
|
||||
textKey: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 本地 mock 内容(后续接后端时可替换)
|
||||
*/
|
||||
export const MOCK_CONTENT: MockContentItem[] = [
|
||||
{ id: 'c1', text: '你已经很努力了,今天也值得被温柔对待。' },
|
||||
{ id: 'c2', text: '深呼吸三次,把注意力带回当下。' },
|
||||
{ id: 'c3', text: '允许自己慢一点,情绪会像云一样飘过。' },
|
||||
{ id: 'c4', text: '你不需要完美,你已经足够好。' },
|
||||
{ id: 'c5', text: '把手放在心口,对自己说一句:辛苦了。' }
|
||||
{ id: 'c1', textKey: 'mock.c1' },
|
||||
{ id: 'c2', textKey: 'mock.c2' },
|
||||
{ id: 'c3', textKey: 'mock.c3' },
|
||||
{ id: 'c4', textKey: 'mock.c4' },
|
||||
{ id: 'c5', textKey: 'mock.c5' }
|
||||
];
|
||||
|
||||
|
||||
22
client/src/i18n/ALL_COPY.md
Normal file
22
client/src/i18n/ALL_COPY.md
Normal file
@@ -0,0 +1,22 @@
|
||||
## 应用文案总表(请在此文件对应的 JSON 中修改)
|
||||
|
||||
**单一文案源文件**:`client/src/i18n/locales/all.json`
|
||||
|
||||
- **English**:`all.json` 的 `en`
|
||||
- **繁体中文**:`all.json` 的 `zh-TW`
|
||||
|
||||
> 说明:项目运行时只读取 `all.json`;请不要再改 `locales/en.json`、`locales/zh-TW.json`(它们已不再作为运行时数据源)。
|
||||
|
||||
### 快速索引(高频文案)
|
||||
|
||||
- **Home**:`home.*`
|
||||
- **Push 提示**:`push.*`
|
||||
- **主题**:`theme.*`
|
||||
- **我的/Profile**:`profile.*`
|
||||
- **收藏**:`favorites.*`
|
||||
- **设置**:`settings.*`
|
||||
- **Onboarding(问卷)**:`onboardingSurvey.steps.*`
|
||||
- **Onboarding(兴趣)**:`intent.*`
|
||||
- **Mock 文案**:`mock.*`
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ import * as Localization from 'expo-localization';
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import en from './locales/en.json';
|
||||
import zhTW from './locales/zh-TW.json';
|
||||
// 用 require 避免 TS 的 json module 配置差异导致无法编译
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const all = require('./locales/all.json') as { en: Record<string, unknown>; 'zh-TW': Record<string, unknown> };
|
||||
|
||||
/**
|
||||
* 语言码约定:
|
||||
@@ -83,8 +84,8 @@ export async function initI18n(): Promise<void> {
|
||||
|
||||
await i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
'zh-TW': { translation: zhTW },
|
||||
en: { translation: en },
|
||||
'zh-TW': { translation: all['zh-TW'] as any },
|
||||
en: { translation: all.en as any },
|
||||
},
|
||||
lng: initialLang,
|
||||
fallbackLng: DEFAULT_FALLBACK_LANGUAGE,
|
||||
|
||||
314
client/src/i18n/locales/all.json
Normal file
314
client/src/i18n/locales/all.json
Normal file
@@ -0,0 +1,314 @@
|
||||
{
|
||||
"en": {
|
||||
"common": {
|
||||
"ok": "OK",
|
||||
"cancel": "Cancel",
|
||||
"error": "Error",
|
||||
"openLinkError": "Cannot open link",
|
||||
"back": "Back",
|
||||
"close": "Close"
|
||||
},
|
||||
"onboarding": {
|
||||
"title": "Welcome",
|
||||
"progress": "{{current}}/{{total}}",
|
||||
"next": "Next",
|
||||
"skip": "Skip",
|
||||
"skipAll": "Skip onboarding",
|
||||
"q1Title": "How are you feeling lately?",
|
||||
"q1Desc": "No right or wrong. You can skip and adjust later.",
|
||||
"q2Title": "What kind of support do you want?",
|
||||
"q2Desc": "For example: gentle reminders, mindfulness, emotional support.",
|
||||
"q3Title": "When do you need comfort the most?",
|
||||
"q3Desc": "Morning, afternoon, late night, or specific moments.",
|
||||
"q4Title": "A gentle sentence for yourself",
|
||||
"q4Desc": "You can skip. We’ll stay with you along the way."
|
||||
},
|
||||
"onboardingSurvey": {
|
||||
"steps": {
|
||||
"name": { "title": "What should I call you?" },
|
||||
"status": {
|
||||
"title": "Your current stage?",
|
||||
"options": {
|
||||
"pregnant": "Pregnant / preparing for motherhood",
|
||||
"has_kids": "Already have kids",
|
||||
"no_fill": "Prefer not to say"
|
||||
}
|
||||
},
|
||||
"emotion": {
|
||||
"title": "How are you feeling right now?",
|
||||
"options": {
|
||||
"happy": "Happy / satisfied",
|
||||
"calm": "Calm / grounded",
|
||||
"stressed": "Stressed / overwhelmed",
|
||||
"low": "Down / low mood"
|
||||
}
|
||||
},
|
||||
"influence": {
|
||||
"title": "What has been affecting you lately?",
|
||||
"options": {
|
||||
"family": "Family & kids",
|
||||
"work": "Work or study",
|
||||
"relationship": "Intimate relationship",
|
||||
"friends": "Friends & social life",
|
||||
"health": "Mental & physical health"
|
||||
}
|
||||
},
|
||||
"support": {
|
||||
"title": "What support do you need most?",
|
||||
"options": {
|
||||
"emotional": "Emotional support",
|
||||
"parenting": "Parenting stress",
|
||||
"self_worth": "Self-worth",
|
||||
"anxiety": "Anxiety relief",
|
||||
"balance": "Rest & balance"
|
||||
}
|
||||
},
|
||||
"reminder": { "title": "How many reminders do you want per day?" }
|
||||
}
|
||||
},
|
||||
"intent": {
|
||||
"title": "What kind of help do you want?",
|
||||
"love": "Love",
|
||||
"life": "Life",
|
||||
"travel": "Travel",
|
||||
"work": "Career"
|
||||
},
|
||||
"push": {
|
||||
"title": "Notifications",
|
||||
"cardTitle": "Turn on gentle reminders",
|
||||
"cardDesc": "We’ll send a short mindful phrase when you may need it. You can change this anytime in Settings.",
|
||||
"enable": "Enable",
|
||||
"later": "Later",
|
||||
"loading": "Working…",
|
||||
"errorTitle": "Notice",
|
||||
"errorDesc": "It’s okay if enabling fails. You can keep using the app."
|
||||
},
|
||||
"home": {
|
||||
"title": "Mindfulness",
|
||||
"like": "Like",
|
||||
"dislike": "Dislike",
|
||||
"favorites": "Favorites",
|
||||
"settings": "Settings",
|
||||
"theme": "Theme",
|
||||
"profile": "Me"
|
||||
},
|
||||
"theme": {
|
||||
"title": "Theme",
|
||||
"scenery": "Scenery",
|
||||
"color": "Color"
|
||||
},
|
||||
"profile": {
|
||||
"title": "Me",
|
||||
"favorites": "My Likes",
|
||||
"widget": "Widget",
|
||||
"dailyReminder": "Daily Reminder",
|
||||
"privacy": "Privacy Policy",
|
||||
"terms": "Terms of Use",
|
||||
"language": "Language",
|
||||
"todoTitle": "Notice",
|
||||
"todoDesc": "This feature is a placeholder for this iteration."
|
||||
},
|
||||
"dailyReminder": {
|
||||
"title": "Daily Reminder",
|
||||
"timesUnit": "times",
|
||||
"pushLabel": "Push Reminder",
|
||||
"ok": "Ok",
|
||||
"minus": "Decrease",
|
||||
"plus": "Increase"
|
||||
},
|
||||
"widget": {
|
||||
"lockScreen": "Lock Screen Widget",
|
||||
"homeScreen": "Home Screen Widget",
|
||||
"previewDate": "Thu, Jan 29",
|
||||
"previewQuote": "I’m proud of who I am, even while becoming who I want to be."
|
||||
},
|
||||
"favorites": {
|
||||
"title": "Favorites",
|
||||
"empty": "No favorites yet.",
|
||||
"unknownText": "This quote is no longer available."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"language": "Language",
|
||||
"version": "Version",
|
||||
"widgetTitle": "iOS Widget",
|
||||
"widgetDesc": "Put gentle reminders on your home screen: long-press → tap “+” → search “Mindfulness” → add a size you like."
|
||||
},
|
||||
"consent": {
|
||||
"title": "You Are Perfect.",
|
||||
"subtitle": "Everything Will Be Better.",
|
||||
"agree": "Agree & Continue",
|
||||
"privacy": "Privacy Policy",
|
||||
"terms": "Terms of Use"
|
||||
},
|
||||
"permissions": {
|
||||
"notificationsDenied": "Notifications are denied. Please enable them in Settings."
|
||||
},
|
||||
"language": {
|
||||
"zhTW": "繁體中文",
|
||||
"en": "English"
|
||||
},
|
||||
"mock": {
|
||||
"c1": "You’ve been trying your best. You deserve kindness today.",
|
||||
"c2": "Take three deep breaths and return to the present moment.",
|
||||
"c3": "It’s okay to slow down. Emotions pass like clouds.",
|
||||
"c4": "You don’t need to be perfect. You are enough.",
|
||||
"c5": "Place a hand on your heart and say: You did well today."
|
||||
}
|
||||
},
|
||||
"zh-TW": {
|
||||
"common": {
|
||||
"ok": "確定",
|
||||
"cancel": "取消",
|
||||
"back": "返回",
|
||||
"close": "關閉"
|
||||
},
|
||||
"onboarding": {
|
||||
"title": "歡迎",
|
||||
"progress": "{{current}}/{{total}}",
|
||||
"next": "下一步",
|
||||
"skip": "跳過",
|
||||
"skipAll": "跳過整個引導",
|
||||
"q1Title": "你最近的感受更接近哪一種?",
|
||||
"q1Desc": "沒有對錯,你可以跳過,之後也能慢慢調整。",
|
||||
"q2Title": "你更希望獲得哪種支持?",
|
||||
"q2Desc": "例如:溫柔提醒、正念練習、情緒陪伴。",
|
||||
"q3Title": "你通常在什麼時候最需要被安慰?",
|
||||
"q3Desc": "例如:清晨、午后、深夜,或某些特定時刻。",
|
||||
"q4Title": "給自己一句溫柔的話",
|
||||
"q4Desc": "你可以直接跳過,我們會在之後繼續陪你。"
|
||||
},
|
||||
"onboardingSurvey": {
|
||||
"steps": {
|
||||
"name": { "title": "我可以怎麼稱呼你?" },
|
||||
"status": {
|
||||
"title": "媽媽的狀態?",
|
||||
"options": {
|
||||
"pregnant": "懷孕中/準備成為媽媽",
|
||||
"has_kids": "已經有孩子",
|
||||
"no_fill": "不想填寫"
|
||||
}
|
||||
},
|
||||
"emotion": {
|
||||
"title": "當下情緒狀態?",
|
||||
"options": {
|
||||
"happy": "愉悅、滿足",
|
||||
"calm": "平靜、安穩",
|
||||
"stressed": "被壓得有點喘不過氣",
|
||||
"low": "情緒低落"
|
||||
}
|
||||
},
|
||||
"influence": {
|
||||
"title": "是什麼影響了你最近的感受?",
|
||||
"options": {
|
||||
"family": "家庭與孩子",
|
||||
"work": "工作或學習",
|
||||
"relationship": "親密關係",
|
||||
"friends": "朋友與人際",
|
||||
"health": "身心健康"
|
||||
}
|
||||
},
|
||||
"support": {
|
||||
"title": "最需要什麼支持?",
|
||||
"options": {
|
||||
"emotional": "情緒支持",
|
||||
"parenting": "育兒壓力",
|
||||
"self_worth": "自我價值",
|
||||
"anxiety": "焦慮舒緩",
|
||||
"balance": "休息與平衡"
|
||||
}
|
||||
},
|
||||
"reminder": { "title": "你需要每天幾次提醒?" }
|
||||
}
|
||||
},
|
||||
"intent": {
|
||||
"title": "你希望得到什麼幫助?",
|
||||
"love": "愛情",
|
||||
"life": "生活",
|
||||
"travel": "旅遊",
|
||||
"work": "職場"
|
||||
},
|
||||
"push": {
|
||||
"title": "通知",
|
||||
"cardTitle": "開啟溫柔提醒",
|
||||
"cardDesc": "我們會在你需要的時候,送上一句正念短句或溫柔提醒(可隨時在設定中調整)。",
|
||||
"enable": "立即開啟",
|
||||
"later": "稍後",
|
||||
"loading": "處理中…",
|
||||
"errorTitle": "提示",
|
||||
"errorDesc": "開啟失敗也沒關係,你仍然可以繼續使用應用。"
|
||||
},
|
||||
"home": {
|
||||
"title": "正念",
|
||||
"like": "喜歡",
|
||||
"dislike": "不喜歡",
|
||||
"favorites": "收藏",
|
||||
"settings": "設定",
|
||||
"theme": "主題",
|
||||
"profile": "我的"
|
||||
},
|
||||
"theme": {
|
||||
"title": "主題",
|
||||
"scenery": "風景",
|
||||
"color": "顏色"
|
||||
},
|
||||
"profile": {
|
||||
"title": "我的",
|
||||
"favorites": "我的喜歡",
|
||||
"widget": "小工具",
|
||||
"dailyReminder": "每日提醒",
|
||||
"privacy": "隱私政策",
|
||||
"terms": "使用條款",
|
||||
"language": "語言",
|
||||
"todoTitle": "提示",
|
||||
"todoDesc": "此功能本期先占位,後續迭代補齊。"
|
||||
},
|
||||
"dailyReminder": {
|
||||
"title": "每日提醒",
|
||||
"timesUnit": "次",
|
||||
"pushLabel": "推送提醒",
|
||||
"ok": "確定",
|
||||
"minus": "減少次數",
|
||||
"plus": "增加次數"
|
||||
},
|
||||
"widget": {
|
||||
"lockScreen": "鎖屏小工具",
|
||||
"homeScreen": "桌面小工具",
|
||||
"previewDate": "1月29日週四 · 已至臘月十一",
|
||||
"previewQuote": "我也對現在的自己感到滿意,即使我仍在努力成為想成為的人。"
|
||||
},
|
||||
"favorites": {
|
||||
"title": "收藏夾",
|
||||
"empty": "這裡還沒有收藏內容。",
|
||||
"unknownText": "這條文案暫時無法顯示。"
|
||||
},
|
||||
"settings": {
|
||||
"title": "設定",
|
||||
"language": "語言",
|
||||
"version": "版本",
|
||||
"widgetTitle": "iOS 小工具",
|
||||
"widgetDesc": "把溫柔提醒放到桌面上:長按主畫面 → 點「+」 → 搜尋「正念」 → 添加你喜歡的尺寸。"
|
||||
},
|
||||
"consent": {
|
||||
"agree": "同意並繼續",
|
||||
"privacy": "隱私協議",
|
||||
"terms": "用戶使用協議"
|
||||
},
|
||||
"permissions": {
|
||||
"notificationsDenied": "系統權限已被拒絕,請前往手機設定開啟通知。"
|
||||
},
|
||||
"language": {
|
||||
"zhTW": "繁體中文",
|
||||
"en": "English"
|
||||
},
|
||||
"mock": {
|
||||
"c1": "你已經很努力了,今天也值得被溫柔對待。",
|
||||
"c2": "深呼吸三次,把注意力帶回當下。",
|
||||
"c3": "允許自己慢一點,情緒會像雲一樣飄過。",
|
||||
"c4": "你不需要完美,你已經足夠好。",
|
||||
"c5": "把手放在心口,對自己說一句:辛苦了。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,13 +35,14 @@ function withTimeout(ms: number): AbortController {
|
||||
export async function fetchRecoFeed(req: RecoRequest): Promise<RecoEngineResult> {
|
||||
const controller = withTimeout(12_000);
|
||||
const url = `${API_BASE_URL}/v1/reco/feed`;
|
||||
const acceptLanguage = i18n.language?.toLowerCase().startsWith('zh') ? 'tc' : 'en';
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// 让后端做 locale 选择(目前后端只区分 en/tc)
|
||||
'Accept-Language': i18n.language || 'en',
|
||||
'Accept-Language': acceptLanguage,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
k: req.k,
|
||||
|
||||
@@ -42,6 +42,12 @@ export type RecoFeedCacheItem = {
|
||||
|
||||
export type RecoFeedCache = {
|
||||
saved_at: string; // ISO8601
|
||||
/**
|
||||
* 缓存文案的语言(后端目前只区分 en / tc)
|
||||
* - en: English
|
||||
* - tc: 繁体中文
|
||||
*/
|
||||
lang?: 'en' | 'tc';
|
||||
items: RecoFeedCacheItem[];
|
||||
meta?: Record<string, unknown>;
|
||||
};
|
||||
@@ -107,6 +113,10 @@ export async function setReaction(contentId: string, reaction: Reaction): Promis
|
||||
export type FavoriteItem = {
|
||||
favId: string; // 唯一标识,支持重复点赞同一文案
|
||||
id: string;
|
||||
/**
|
||||
* 收藏时的文案快照(强烈建议写入,避免后续 cache 覆盖导致无法还原文案)
|
||||
*/
|
||||
text?: string;
|
||||
date: string;
|
||||
themeMode: ThemeMode;
|
||||
background: string; // 颜色值或图片路径
|
||||
@@ -120,7 +130,7 @@ export async function addFavorite(item: FavoriteItem): Promise<void> {
|
||||
const list = await getFavorites();
|
||||
// 允许重复点赞,不再根据 id 去重
|
||||
const newList = [item, ...list];
|
||||
console.log('Adding to favorites, new list size:', newList.length);
|
||||
console.log('Adding to favorites:', JSON.stringify(item));
|
||||
await setJson(KEY_FAVORITES_ITEMS, newList);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user