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:
co-authored by
Claude Opus 4.6
parent
4e83ce95cd
commit
00db3f6c96
@@ -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;
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user