feat: custom JWT auth + i18n with Chinese default

Replace Clerk with self-contained JWT auth (bcryptjs + jose), remove all
external auth dependencies. Add full i18n system with Chinese as default
language and English toggle — React Context + JSON dictionaries, no
external i18n library.

Auth:
- Add passwordHash to User model, generate JWT secret
- Create /api/auth/register, login, logout, me endpoints
- Rewrite proxy.ts middleware for custom session validation
- Add AuthProvider + useAuth hook, sign-in/sign-up pages
- Wire auth into layout and all API routes

i18n:
- React Context I18nProvider with localStorage + cookie persistence
- dictionaries/zh.ts (default) and dictionaries/en.ts
- LanguageSwitcher component, server-side createServerT() utility
- CJK font stack via [lang="zh"] CSS selector
- HTML lang attribute synced on language change
- All pages, components, TabBar, StageBadge translated
- Engine fallback strings (reflection-analyzer, summary-generator)
- API routes pass locale from cookie to engines

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jackyu66git
2026-05-20 22:47:14 +08:00
co-authored by Claude Opus 4.6
parent 4e83ce95cd
commit 00db3f6c96
43 changed files with 1647 additions and 300 deletions
+266
View File
@@ -0,0 +1,266 @@
import type { Dict } from "@/lib/i18n/types";
const en: Dict = {
// -----------------------------------------------------------------------
// Navigation (TabBar)
// -----------------------------------------------------------------------
nav: {
today: "Today",
progress: "Progress",
rewards: "Rewards",
summary: "Summary",
settings: "Settings",
},
// -----------------------------------------------------------------------
// Shared labels
// -----------------------------------------------------------------------
shared: {
loading: "Loading...",
back: "← Back",
task_type: {
attention: "Attention",
emotion: "Emotion",
stress: "Stress",
clarity: "Clarity",
integration: "Integration",
},
difficulty: {
beginner: "Beginner",
intermediate: "Intermediate",
advanced: "Advanced",
},
stage: {
beginner: "Beginner",
foundation: "Foundation",
growing: "Growing",
established: "Established",
advanced: "Advanced",
},
reward_type: {
daily_completion: "Daily Practice",
streak_3: "3-Day Streak",
streak_7: "7-Day Streak",
streak_14: "14-Day Streak",
streak_30: "30-Day Streak",
stage_up: "Stage Advance",
first_session: "First Practice",
},
},
// -----------------------------------------------------------------------
// Home page
// -----------------------------------------------------------------------
home: {
greeting: {
morning: "Good morning",
afternoon: "Good afternoon",
evening: "Good evening",
fallback_name: "there",
subtitle: "Your practice is waiting.",
},
onboarding: {
title: "One step left",
description: "Complete onboarding in Settings to start your practice.",
cta: "Go to Settings",
},
stats: {
streak: "Streak",
streak_unit: "days",
sessions: "Sessions",
sessions_unit: "total",
},
metrics: {
title: "Core metrics",
attention: "Attention",
emotion: "Emotion",
stress: "Stress",
consistency: "Consistency",
},
task: {
heading: "Today's practice",
no_task: "No task for today yet. Generate one to start your practice.",
generate: "Generate today's task",
generating: "Generating...",
begin: "Begin practice",
why_prefix: "", // unused in English, kept for key parity with zh
},
},
// -----------------------------------------------------------------------
// Progress page
// -----------------------------------------------------------------------
progress: {
title: "Your progress",
metrics_title: "Core metrics",
stage_title: "Stage progress",
attention: "Attention",
emotional_stability: "Emotional Stability",
stress_level: "Stress Level",
consistency: "Consistency",
stages: {
beginner: "Beginner",
foundation: "Foundation",
growing: "Growing",
established: "Established",
advanced: "Advanced",
},
stage_prefix: "Stage",
onboarding_needed: "Complete onboarding to see your progress.",
loading: "Loading...",
},
// -----------------------------------------------------------------------
// Rewards page
// -----------------------------------------------------------------------
rewards: {
title: "Rewards",
empty_subtitle: "Your rewards will appear here as you practice.",
empty_message: "Complete your first practice to earn a reward.",
earned_one: "{count} reward earned",
earned_other: "{count} rewards earned",
},
// -----------------------------------------------------------------------
// Summary page
// -----------------------------------------------------------------------
summary: {
title: "Weekly summary",
week_of: "Week of {date}",
empty_subtitle: "Generated each week from your practice history",
empty_message:
"Your first weekly summary will be generated after you complete at least one practice session. Summaries run automatically each week.",
},
// -----------------------------------------------------------------------
// Settings page
// -----------------------------------------------------------------------
settings: {
title: "Settings",
onboarding: {
title: "Welcome to Inner OS",
description:
"Inner OS generates one personalized mental training task each day, guided by your practice history and inner development stage. Your reflections shape what comes next. Everything is private — just you and your practice.",
cta: "Get started",
setting_up: "Setting up...",
done: "You're all set. Head home to start your practice.",
error: "Something went wrong. Try again.",
},
profile: {
title: "Profile",
signed_in_as: "Signed in as",
stage_and_sessions: "Stage {stage} · {count} sessions",
},
about: {
title: "About Inner OS",
p1: "Inner OS is a private inner development practice. You receive one personalized mental training task each day, tailored to your current state and development stage.",
p2: "Your reflections are private. Your data is yours. There are no leaderboards, no social features, no competition — just you and the work of becoming more aware, more stable, and more yourself.",
},
sign_out: "Sign out",
signing_out: "Signing out...",
},
// -----------------------------------------------------------------------
// Practice page
// -----------------------------------------------------------------------
practice: {
title: "Your practice",
why_prefix: "Why this task: ",
start: "Start",
finish_early: "Finish early",
complete: "Practice complete",
reflect: "Reflect on your practice",
},
// -----------------------------------------------------------------------
// Reflection page
// -----------------------------------------------------------------------
reflect: {
no_session: "No session to reflect on. Go home.",
title: "Reflect on your practice",
label: "What did you notice during your practice?",
placeholder:
"Were you focused or distracted? What emotions came up? Did anything surprise you?",
submit: "Complete practice",
submitting: "Saving...",
error: "Failed to save reflection",
generic_error: "Something went wrong",
result_title: "Practice complete",
focus: "focus",
emotional_awareness: "emotional awareness",
back_home: "Back home",
},
// -----------------------------------------------------------------------
// Sign-in page
// -----------------------------------------------------------------------
sign_in: {
title: "Sign in",
subtitle: "Welcome back to Inner OS",
email: "Email",
password: "Password",
submit: "Sign in",
submitting: "Signing in...",
fallback_error: "Login failed",
network_error: "Network error. Try again.",
no_account: "No account?",
sign_up_link: "Sign up",
},
// -----------------------------------------------------------------------
// Sign-up page
// -----------------------------------------------------------------------
sign_up: {
title: "Sign up",
subtitle: "Start your inner development practice",
email: "Email",
password: "Password",
confirm_password: "Confirm password",
submit: "Create account",
submitting: "Creating account...",
password_mismatch: "Passwords do not match",
password_short: "Password must be at least 6 characters",
fallback_error: "Registration failed",
network_error: "Network error. Try again.",
has_account: "Already have an account?",
sign_in_link: "Sign in",
},
// -----------------------------------------------------------------------
// Engine fallback strings (server-side, not React)
// -----------------------------------------------------------------------
engine: {
reflection: {
keyword_blocked: "Thank you for sharing.",
no_reflection: "You completed your practice today.",
safety_blocked: "You completed your practice today.",
llm_failed: "You showed up today. That's what matters.",
},
summary: {
no_sessions:
"No completed practice sessions this week. Every week is a fresh start — your practice is here when you're ready.",
safety_fallback: "Your practice summary for this week.",
fallback_template:
"You completed {count} practice session{plural} this week. Showing up consistently is the foundation everything else is built on. Your current streak is {streak} day{streak_plural}. See you next week.",
},
},
};
export default en;
+262
View File
@@ -0,0 +1,262 @@
import type { Dict } from "@/lib/i18n/types";
const zh: Dict = {
// -----------------------------------------------------------------------
// 导航 (TabBar)
// -----------------------------------------------------------------------
nav: {
today: "今日",
progress: "进度",
rewards: "奖励",
summary: "周报",
settings: "设置",
},
// -----------------------------------------------------------------------
// 共享标签
// -----------------------------------------------------------------------
shared: {
loading: "加载中...",
back: "← 返回",
task_type: {
attention: "注意力",
emotion: "情绪",
stress: "压力",
clarity: "清晰度",
integration: "整合",
},
difficulty: {
beginner: "入门",
intermediate: "进阶",
advanced: "高级",
},
stage: {
beginner: "入门",
foundation: "基础",
growing: "成长",
established: "稳固",
advanced: "高级",
},
reward_type: {
daily_completion: "每日练习",
streak_3: "连续3天",
streak_7: "连续7天",
streak_14: "连续14天",
streak_30: "连续30天",
stage_up: "阶段提升",
first_session: "首次练习",
},
},
// -----------------------------------------------------------------------
// 首页
// -----------------------------------------------------------------------
home: {
greeting: {
morning: "早上好",
afternoon: "下午好",
evening: "晚上好",
fallback_name: "朋友",
subtitle: "你的练习在等你。",
},
onboarding: {
title: "还差一步",
description: "在设置中完成引导,开始你的练习之旅。",
cta: "前往设置",
},
stats: {
streak: "连续天数",
streak_unit: "天",
sessions: "练习次数",
sessions_unit: "次",
},
metrics: {
title: "核心指标",
attention: "注意力",
emotion: "情绪",
stress: "压力",
consistency: "持续性",
},
task: {
heading: "今日练习",
no_task: "今日还没有练习任务,生成一个开始吧。",
generate: "生成今日任务",
generating: "生成中...",
begin: "开始练习",
why_prefix: "",
},
},
// -----------------------------------------------------------------------
// 进度页
// -----------------------------------------------------------------------
progress: {
title: "我的进度",
metrics_title: "核心指标",
stage_title: "阶段进度",
attention: "注意力",
emotional_stability: "情绪稳定性",
stress_level: "压力水平",
consistency: "持续性",
stages: {
beginner: "入门",
foundation: "基础",
growing: "成长",
established: "稳固",
advanced: "高级",
},
stage_prefix: "阶段",
onboarding_needed: "完成引导后即可查看进度。",
loading: "加载中...",
},
// -----------------------------------------------------------------------
// 奖励页
// -----------------------------------------------------------------------
rewards: {
title: "奖励",
empty_subtitle: "练习后奖励会在这里出现。",
empty_message: "完成第一次练习即可获得奖励。",
earned_one: "已获得 {count} 个奖励",
earned_other: "已获得 {count} 个奖励",
},
// -----------------------------------------------------------------------
// 周报页
// -----------------------------------------------------------------------
summary: {
title: "每周总结",
week_of: "{date}当周",
empty_subtitle: "每周根据练习记录自动生成",
empty_message: "完成至少一次练习后,你的第一份每周总结将会生成。总结每周自动运行。",
},
// -----------------------------------------------------------------------
// 设置页
// -----------------------------------------------------------------------
settings: {
title: "设置",
onboarding: {
title: "欢迎来到 Inner OS",
description:
"Inner OS 每天为你生成一个个性化的心理训练任务,根据你的练习历史和内在发展阶段量身定制。你的反思会塑造接下来的内容。一切完全私密——只有你和你的练习。",
cta: "开始使用",
setting_up: "设置中...",
done: "一切就绪,返回首页开始练习吧。",
error: "出了点问题,请重试。",
},
profile: {
title: "个人信息",
signed_in_as: "已登录:",
stage_and_sessions: "阶段 {stage} · {count} 次练习",
},
about: {
title: "关于 Inner OS",
p1: "Inner OS 是一个私密的内在成长练习工具。每天根据你当前的状态和发展阶段,为你量身定制一个心理训练任务。",
p2: "你的反思是私密的。你的数据属于你。没有排行榜,没有社交功能,没有竞争——只有你和更加自觉、更加稳定、更加真实的自己。",
},
sign_out: "退出登录",
signing_out: "退出中...",
},
// -----------------------------------------------------------------------
// 练习页
// -----------------------------------------------------------------------
practice: {
title: "你的练习",
why_prefix: "任务说明:",
start: "开始",
finish_early: "提前结束",
complete: "练习完成",
reflect: "记录感受",
},
// -----------------------------------------------------------------------
// 反思页
// -----------------------------------------------------------------------
reflect: {
no_session: "没有可记录的练习,返回首页。",
title: "记录你的感受",
label: "练习中你注意到了什么?",
placeholder: "你专注还是走神了?出现了什么情绪?有什么让你意外的吗?",
submit: "完成练习",
submitting: "保存中...",
error: "保存失败",
generic_error: "出了点问题",
result_title: "练习完成",
focus: "专注度",
emotional_awareness: "情绪觉察",
back_home: "返回首页",
},
// -----------------------------------------------------------------------
// 登录页
// -----------------------------------------------------------------------
sign_in: {
title: "登录",
subtitle: "欢迎回到 Inner OS",
email: "邮箱",
password: "密码",
submit: "登录",
submitting: "登录中...",
fallback_error: "登录失败",
network_error: "网络错误,请重试。",
no_account: "还没有账号?",
sign_up_link: "注册",
},
// -----------------------------------------------------------------------
// 注册页
// -----------------------------------------------------------------------
sign_up: {
title: "注册",
subtitle: "开始你的内在成长练习",
email: "邮箱",
password: "密码",
confirm_password: "确认密码",
submit: "创建账号",
submitting: "创建中...",
password_mismatch: "两次密码不一致",
password_short: "密码至少需要6位字符",
fallback_error: "注册失败",
network_error: "网络错误,请重试。",
has_account: "已有账号?",
sign_in_link: "登录",
},
// -----------------------------------------------------------------------
// 引擎回退字符串 (服务端使用)
// -----------------------------------------------------------------------
engine: {
reflection: {
keyword_blocked: "感谢你的分享。",
no_reflection: "你完成了今天的练习。",
safety_blocked: "你完成了今天的练习。",
llm_failed: "你今天来了,这本身就是最重要的。",
},
summary: {
no_sessions: "本周没有完成的练习。每一周都是全新的开始——你的练习随时等你回来。",
safety_fallback: "本周练习总结。",
fallback_template: "本周你完成了 {count} 次练习。持续出现是所有进步的基础。当前连续 {streak} 天。下周见。",
},
},
};
export default zh;