Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
cfec417
feat:初步翻译
helemazuba-boop Apr 5, 2026
9cfe0f2
temp:暂存新的翻译成果
helemazuba-boop Apr 6, 2026
b4ba3cc
feat(i18n): complete internationalization refactor with next-intl\n\n…
helemazuba-boop Apr 6, 2026
cab0c78
fix: address bot review feedback for cookie sync and RLS vulnerabilit…
helemazuba-boop Apr 6, 2026
6992fa6
fix: resolve missing translation keys for discardAndEdit and keepEdit…
helemazuba-boop Apr 6, 2026
0f5787e
chore: resolve dependency errors
mrmagic2020 Apr 6, 2026
12947b0
fix(i18n): update cross-imports from old route tree to [locale] tree
mrmagic2020 Apr 6, 2026
80fdd73
refactor(i18n): remove legacy non-locale route tree
mrmagic2020 Apr 6, 2026
fb2ca92
fix(i18n): migrate navigation imports to locale-aware next-intl
mrmagic2020 Apr 6, 2026
01171ef
fix(security): validate redirect param in auth confirm route
mrmagic2020 Apr 6, 2026
2d6ede4
chore: remove debug logging and redundant provider
mrmagic2020 Apr 6, 2026
cdd94c7
fix(i18n): replace hardcoded English strings with translation keys
mrmagic2020 Apr 6, 2026
4679fd0
chore: remove non-i18n files and fix style
mrmagic2020 Apr 6, 2026
14c248f
fix(i18n): restore NextIntlClientProvider in root layout with messages
mrmagic2020 Apr 6, 2026
a4f8512
fix: allow unauthenticated access to landing page
mrmagic2020 Apr 6, 2026
9c98d00
fix(i18n): resolve missing keys, add locale switcher and CI check
mrmagic2020 Apr 7, 2026
25edc2b
fix(i18n): localize date formatting across the app
mrmagic2020 Apr 7, 2026
ae58981
fix(i18n): localize insights generated date and cluster/weak spot counts
mrmagic2020 Apr 7, 2026
242e896
docs: add Chinese version of README.md and update language links
helemazuba-boop Apr 7, 2026
b3b1e85
Fix typo in file attachment description
helemazuba-boop Apr 7, 2026
03e4a49
fix(i18n): remove duplicate NextIntlClientProvider from root layout
mrmagic2020 Apr 8, 2026
6f46585
Revert "fix(i18n): remove duplicate NextIntlClientProvider from root …
mrmagic2020 Apr 8, 2026
d4bb7fd
fix(i18n): scope root layout provider to CookieConsent messages only
mrmagic2020 Apr 8, 2026
08bcdf3
fix(i18n): replace {plural} raw variable with proper ICU plural syntax
mrmagic2020 Apr 8, 2026
b24f815
fix(i18n): prevent false locale prefix match in stripLocaleFromPath
mrmagic2020 Apr 8, 2026
04bb727
fix(i18n): eliminate redundant Supabase client in middleware
mrmagic2020 Apr 8, 2026
d5beb91
fix(i18n): use routing.locales instead of hardcoded locale array
mrmagic2020 Apr 8, 2026
dac9302
fix(i18n): remove dead || fallback patterns from insights page
mrmagic2020 Apr 8, 2026
c174080
fix(i18n): remove incorrect defaultValue option from tCommon() call
mrmagic2020 Apr 8, 2026
bf2409b
fix(i18n): use ICU plural syntax for CommonUtils time-ago keys
mrmagic2020 Apr 8, 2026
e49301a
fix(i18n): localize remaining hardcoded English strings
mrmagic2020 Apr 8, 2026
d3407e0
fix(i18n): add plural support to notebook card problem count
mrmagic2020 Apr 8, 2026
adf93eb
fix: resolve lint and type errors across i18n refactor
mrmagic2020 Apr 8, 2026
74592a6
fix(i18n): replace all remaining (s) patterns with ICU plural syntax
mrmagic2020 Apr 8, 2026
7001bac
style: format code
mrmagic2020 Apr 10, 2026
9b60967
feat(i18n): add compile-time type safety for translation keys
mrmagic2020 Apr 11, 2026
89fa059
refactor(i18n): dissolve CommonUtils namespace and deduplicate keys
mrmagic2020 Apr 11, 2026
3ee4abd
fix(i18n): pass locale to formatRelativeTime in activity feed
mrmagic2020 Apr 11, 2026
b1e4767
fix: add missing translator deps to useCallback/useEffect arrays
mrmagic2020 Apr 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions .github/workflows/i18n-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: i18n Check

on:
pull_request:
paths:
- 'web/messages/**'

permissions:
contents: read
pull-requests: write

jobs:
i18n-check:
name: Check translations
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Detect modified locales
id: detect
run: |
# Get all modified locale files (not en.json — that's the source)
CHANGED=$(gh pr diff ${{ github.event.pull_request.number }} --name-only \
| grep '^web/messages/.*\.json$' \
| grep -v 'web/messages/en.json' \
| sed 's|web/messages/||;s|\.json||' \
| paste -sd ',' -)

EN_CHANGED=$(gh pr diff ${{ github.event.pull_request.number }} --name-only \
| grep -c '^web/messages/en.json$' || true)

if [ -z "$CHANGED" ] && [ "$EN_CHANGED" = "0" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "No locale files modified, skipping."
else
echo "skip=false" >> "$GITHUB_OUTPUT"
# If en.json changed, check all locales (source keys may have shifted)
if [ "$EN_CHANGED" != "0" ]; then
ALL=$(ls web/messages/*.json \
| xargs -I{} basename {} .json \
| grep -v '^en$' \
| paste -sd ',' -)
echo "locales=$ALL" >> "$GITHUB_OUTPUT"
echo "Checking all locales (en.json changed): $ALL"
else
echo "locales=$CHANGED" >> "$GITHUB_OUTPUT"
echo "Checking modified locales: $CHANGED"
fi
fi
env:
GH_TOKEN: ${{ github.token }}

- name: Setup Node.js
if: steps.detect.outputs.skip != 'true'
uses: actions/setup-node@v4
with:
node-version: 22

- name: Run i18n status check
if: steps.detect.outputs.skip != 'true'
id: check
working-directory: web
run: |
LOCALES="${{ steps.detect.outputs.locales }}"
REPORT=$(node scripts/i18n-status.js --locales "$LOCALES" 2>&1 || true)
# Save report for the comment step
echo "$REPORT" > /tmp/i18n-report.md
# Also check exit code
node scripts/i18n-status.js --locales "$LOCALES" --json > /tmp/i18n-result.json 2>&1 && \
echo "passed=true" >> "$GITHUB_OUTPUT" || \
echo "passed=false" >> "$GITHUB_OUTPUT"

- name: Find existing comment
if: steps.detect.outputs.skip != 'true'
uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: '## i18n Status Report'

- name: Create or update comment
if: steps.detect.outputs.skip != 'true'
uses: peter-evans/create-or-update-comment@v5
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body-path: /tmp/i18n-report.md
edit-mode: replace

- name: Fail if issues found
if: steps.detect.outputs.skip != 'true' && steps.check.outputs.passed != 'true'
run: |
echo "::error::i18n check found issues. See the PR comment for details."
exit 1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
pr78_diff_utf8.txt

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning].

### Changed

- **i18n key refactor**: Dissolved the 250-key `CommonUtils` junk-drawer namespace into purpose-specific namespaces (`Editor`, `FileManager`, `CopyDialog`, `Problems`, `Statistics`, `DataTable`, `Subjects`, `ProblemSets`, `Common`), removed 100+ cross-namespace duplicate keys, and added compile-time type safety via next-intl `AppConfig` augmentation
- **AI Extraction math formatting**: Multi-line equations now use a single KaTeX `aligned` block instead of multiple separate display math blocks, producing cleaner rendering
- **AI Extraction classification**: Problems with visible multi-step working are now consistently classified as "extended" instead of sometimes "short", even when the final answer is a number

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Wrong Question Notebook

[English](README.md) | [简体中文](README.zh-CN.md)

The Wrong Question Notebook (WQN) is a web application designed to help students systematically track, organise, and revise the problems they answered incorrectly. It provides a comprehensive system for managing problems across multiple notebooks, tracking progress with detailed statistics, and facilitating effective revision through structured review sessions.

**Live:** [wqn.magicworks.app](https://wqn.magicworks.app/)
Expand Down
195 changes: 195 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Wrong Question Notebook

[English](README.md) | [简体中文](README.zh-CN.md)

Wrong Question Notebook(WQN)是一个 Web 应用,旨在帮助学生系统性地追踪、整理和复习自己做错的题目。它能够帮助学生在多个笔记本中管理题目、通过详细统计跟踪学习进度、在结构化的复习会话中提升复习效率。

**在线体验:** [wqn.magicworks.app](https://wqn.magicworks.app/)

## 功能特性

### 错题整理

- 创建带有自定义颜色和图标标识的笔记本来组织题目
- **支持三种题目类型**:选择题、简答题、问答题
- **富文本编辑器**(TipTap),支持 LaTeX 数学公式、表格、列表、图片等
- **更好的答案展示方法**:选择题提供选择按钮、简答题支持多个答案
- **自动判分**:对支持的题目类型进行自动答案验证
- **状态跟踪**:可将题目标记为“错误”、“需要复习”或“已掌握”
- **文件附件**:可上传图片 以及 PDF 作为题目或解析附件
- 支持排序、筛选,并带有分面搜索功能

### 标签系统

- 在笔记本中创建和管理标签
- 为题目添加多个标签,实现灵活分类
- 支持按标签筛选题目
- 支持跨所有笔记本的全局标签总览

### 错题题集&复习

- 创建手动题集或 **Smart Sets**(通过保存的筛选配置自动填充)
- **共享级别**:私有、受限共享(通过邮箱与指定用户共享)或公开
- **基于会话的复习**:开始、暂停和继续复习会话
- 每道题都可单独提交答案,并支持自动判分或自我评估
- **会话总结**:复习结束后的统计与结果拆分
- 作答后可查看题目解析

### 统计仪表板

- 类似 GitHub 的 **活跃度热力图**
- **状态环形图**:错误 / 需要复习 / 已掌握 的分布
- **进度折线图**:随时间变化的累计掌握情况
- **科目柱状图与雷达图**:按笔记本进行对比
- 重点统计卡片(连续学习天数、总数、会话统计)
- 最近活动动态流

### 使用AI提取题目

- 使用 **Google Gemini 2.5 Flash** 从图片中提取题目
- 具有每日使用配额,并支持管理员为用户单独调整额度
- 支持自动识别题目类型、选项和正确答案

### 用户资料

- 可自定义个人资料(头像、用户名、简介、人口统计信息)

### 身份验证与安全

- 基于邮箱的注册与登录,集成 **Cloudflare Turnstile** CAPTCHA
- 带邮箱确认的密码重置流程
- 安全响应头(HSTS、CSP、X-Frame-Options、X-Content-Type-Options、Referrer-Policy、Permissions-Policy)
- HTML 内容清洗(DOMPurify + sanitize-html),并支持数学内容
- 对敏感接口进行速率限制
- 使用 Zod schema 进行请求校验

### 隐私

- **符合 GDPR 标准的 Cookie 隐私偏好设置**,支持精细的偏好设置
- 隐私政策页

## 技术栈

| 层级 | 技术 |
| ---- | ---- |
| 框架 | Next.js 16(App Router、Turbopack) |
| 语言 | TypeScript(严格模式) |
| 样式 | Tailwind CSS 3、tailwindcss-animate |
| 组件 | shadcn/ui(Radix UI + CVA) |
| 富文本 | TipTap(数学、表格、图片、链接、排版) |
| 数学渲染 | KaTeX |
| 认证与数据库 | Supabase(PostgreSQL、Auth、Storage) |
| AI | Google Gemini 2.5 Flash(@google/genai) |
| CAPTCHA | Cloudflare Turnstile |
| 图表 | Chart.js + react-chartjs-2 |
| 数据表格 | TanStack Table |
| 校验 | Zod |
| 主题 | next-themes(class 策略) |
| 分析 | Vercel Analytics + Speed Insights |
| 部署 | Vercel |
| 测试 | Vitest + @vitest/coverage-v8 |
| 代码质量 | ESLint、Prettier |

## 快速开始

### 环境要求

- Node.js 18+
- 一个 [Supabase](https://supabase.com) 项目
- (可选)用于 AI 题目提取的 [Gemini API key](https://aistudio.google.com/)
- (可选)[Cloudflare Turnstile](https://developers.cloudflare.com/turnstile/) site key

### 安装

```bash
git clone https://github.com/mrmagic2020/Wrong-Question-Notebook.git
cd Wrong-Question-Notebook/web
npm install
```

### 环境变量

复制示例文件并填写你的配置:

```bash
cp env.example .env.local
```

| 变量 | 说明 | 必需 |
| ---- | ---- | ---- |
| `NEXT_PUBLIC_SUPABASE_URL` | Supabase 项目 URL | 是 |
| `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY` | Supabase anon / public key | 是 |
| `SUPABASE_SERVICE_ROLE_KEY` | Supabase service role key(服务端使用) | 是 |
| `SITE_URL` | 已部署站点的 URL(用于 sitemap) | 否 |
| `GEMINI_API_KEY` | Google Gemini API key(用于 AI 提取) | 否 |

### 开发

```bash
npm run dev
```

### 常用命令

请在 `web/` 目录下运行:

| 命令 | 用途 |
| ---- | ---- |
| `npm run dev` | 启动开发服务器(Turbopack) |
| `npm run build` | 生产环境构建 |
| `npm run type-check` | TypeScript 检查(`tsc --noEmit`) |
| `npm run lint` | ESLint 检查 |
| `npm run test` | 运行测试(Vitest) |
| `npm run test:watch` | 以监听模式运行测试 |
| `npm run fix-all` | 自动修复 lint 问题并格式化代码 |
| `npm run prepush` | 完整检查:fix-all、type-check、lint、format-check、test、build |

提交前请始终运行 `npm run prepush`。

## 项目结构

```t
web/
app/
(app)/ # 需要登录的页面(笔记本、题目、题集、统计、管理后台)
auth/ # 认证页面(登录、注册、忘记密码等)
api/ # API 路由处理器
privacy/ # 隐私政策页面
page.tsx # 公开主页
layout.tsx # 根布局
globals.css # 全局样式、CSS 工具类、动画
components/
ui/ # shadcn/ui 基础组件
landing/ # 落地页组件
subjects/ # 笔记本卡片与对话框
review/ # 复习会话组件
statistics/ # 仪表板图表与卡片
admin/ # 管理面板组件
cookie-consent/ # GDPR 同意横幅与 Provider
... # 其他功能组件
lib/ # 工具函数、Supabase 客户端、schema、类型、常量
public/ # 静态资源(robots.txt、sitemap.xml)
middleware.ts # Supabase 会话刷新
```

## 部署

详细的 Vercel 部署指南请参见 [DEPLOYMENT.md](DEPLOYMENT.md)。

## 文档

- [CHANGELOG.md](CHANGELOG.md) —— 版本历史
- [DEPLOYMENT.md](DEPLOYMENT.md) —— 部署指南
- [Proposal.md](Proposal.md) —— 初始项目提案

## 贡献

本项目使用 ESLint、Prettier 和 TypeScript 来保证代码质量。请在推送代码前运行以下命令:

```bash
npm run prepush
```

## 许可证

本项目基于 GPL-3.0 许可证发布。详情请参见 [LICENSE](LICENSE)。
Loading