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;