feat: introduce i18n system and class invitation codes

Add complete i18n infrastructure using next-intl (cookie-driven, without i18n routing) with zh-CN/en dictionary files, locale switcher, and NextIntlClientProvider in root layout. Add class invitation code system with new class_invitation_codes table, data-access layer (generate/validate/consume/revoke), server actions with permission checks, rate limiting, and audit logging. Add class-invitation-manager UI component. Refactor onboarding stepper to use i18n translations and accept new invitation code format (6-char alphanumeric) with backward compatibility for legacy 6-digit codes.
This commit is contained in:
SpecialX
2026-06-22 14:04:55 +08:00
parent a4d096a6fc
commit c90748124d
25 changed files with 2911 additions and 30 deletions

View File

@@ -0,0 +1,31 @@
{
"login": {
"title": "登录",
"subtitle": "使用您的账号登录 Next_Edu",
"email": "邮箱",
"password": "密码",
"rememberMe": "记住我",
"signIn": "登录",
"signingIn": "登录中...",
"forgotPassword": "忘记密码?",
"noAccount": "还没有账号?",
"register": "注册"
},
"register": {
"title": "注册",
"subtitle": "创建 Next_Edu 账号",
"email": "邮箱",
"password": "密码",
"confirmPassword": "确认密码",
"name": "姓名",
"signUp": "注册",
"signingUp": "注册中...",
"hasAccount": "已有账号?",
"signIn": "登录"
},
"errors": {
"invalidCredentials": "邮箱或密码错误",
"emailExists": "该邮箱已被注册",
"passwordTooWeak": "密码强度不足"
}
}

View File

@@ -0,0 +1,55 @@
{
"invitation": {
"title": "班级邀请码",
"generate": "生成邀请码",
"regenerate": "重新生成",
"revoke": "撤销",
"copy": "复制",
"copied": "已复制",
"code": "邀请码",
"status": "状态",
"expiresAt": "过期时间",
"usedCount": "已用次数",
"maxUses": "最大次数",
"createdAt": "创建时间",
"note": "备注",
"neverExpires": "永久有效",
"unlimited": "不限",
"active": "有效",
"disabled": "已禁用",
"expired": "已过期",
"exhausted": "已用尽",
"list": "邀请码列表",
"empty": "暂无邀请码",
"join": "加入班级",
"joinPlaceholder": "请输入 6 位邀请码",
"joinSuccess": "加入班级成功",
"joinFailed": "加入班级失败",
"invalidCode": "邀请码无效或已失效",
"rateLimited": "尝试过于频繁,请稍后再试",
"alreadyInClass": "你已在该班级中",
"subjectConflict": "该班级此科目已有任课教师",
"generateSuccess": "邀请码生成成功",
"generateFailed": "邀请码生成失败",
"revokeSuccess": "邀请码已撤销",
"revokeFailed": "邀请码撤销失败",
"regenerateSuccess": "邀请码已重新生成",
"regenerateConfirm": "重新生成后旧邀请码将立即失效,确定继续吗?",
"revokeConfirm": "撤销后此邀请码将立即失效,确定继续吗?",
"expiresInHours": "有效期(小时)",
"maxUsesLabel": "最大使用次数",
"customNote": "备注(可选)",
"customNotePlaceholder": "如:开学季临时码",
"generateWithCustom": "生成自定义邀请码",
"defaultDuration": "永久有效",
"defaultMaxUses": "不限次数"
},
"class": {
"title": "班级",
"name": "班级名称",
"grade": "年级",
"homeroomTeacher": "班主任",
"studentCount": "学生人数",
"actions": "操作"
}
}

View File

@@ -0,0 +1,53 @@
{
"app": {
"title": "Next_Edu - K12 智慧教务系统",
"description": "企业级 K12 教务管理系统"
},
"actions": {
"save": "保存",
"cancel": "取消",
"delete": "删除",
"confirm": "确认",
"search": "搜索",
"reset": "重置",
"submit": "提交",
"back": "返回",
"next": "下一步",
"previous": "上一步",
"finish": "完成",
"skip": "跳过",
"retry": "重试",
"close": "关闭",
"add": "添加",
"remove": "移除",
"edit": "编辑",
"view": "查看",
"loading": "加载中...",
"saving": "保存中...",
"submitting": "提交中..."
},
"status": {
"success": "成功",
"failure": "失败",
"pending": "待处理",
"active": "已启用",
"disabled": "已禁用",
"expired": "已过期",
"exhausted": "已用尽"
},
"locale": {
"switch": "切换语言",
"zh-CN": "中文",
"en": "English"
},
"empty": {
"default": "暂无数据",
"noResults": "未找到匹配结果"
},
"pagination": {
"previous": "上一页",
"next": "下一页",
"page": "第 {page} 页",
"total": "共 {total} 条"
}
}

View File

@@ -0,0 +1,23 @@
{
"auth": {
"required": "请先登录",
"forbidden": "无权限访问",
"sessionExpired": "会话已过期,请重新登录"
},
"network": {
"requestFailed": "请求失败,请稍后重试",
"timeout": "请求超时",
"serverError": "服务器错误"
},
"validation": {
"required": "此字段为必填",
"invalidFormat": "格式错误",
"tooShort": "长度过短",
"tooLong": "长度过长"
},
"db": {
"notFound": "记录不存在",
"duplicate": "记录已存在",
"constraintViolation": "数据约束冲突"
}
}

View File

@@ -0,0 +1,78 @@
{
"title": "首次登录引导",
"description": "完成首次登录信息配置",
"steps": {
"roleConfirm": "角色确认",
"basicInfo": "基础信息",
"roleInfo": "角色信息",
"complete": "完成"
},
"role": {
"yourRole": "你的角色",
"allRoles": "全部角色:{roles}",
"adminAssigned": "角色由管理员预分配,如需变更请联系管理员。",
"admin": "管理员",
"teacher": "教师",
"student": "学生",
"parent": "家长",
"grade_head": "年级主任",
"teaching_head": "教研组长"
},
"form": {
"name": "姓名",
"nameRequired": "请填写姓名",
"nameMax": "姓名长度不能超过 50 个字符",
"phone": "电话",
"phoneRequired": "请填写电话",
"phoneInvalid": "请输入有效的手机号11 位,以 1 开头)",
"address": "住址",
"addressMax": "住址长度不能超过 200 个字符"
},
"teacher": {
"classCodes": "班级邀请码",
"classCodesOptional": "班级邀请码(可选,可多个)",
"classCodesPlaceholder": "每行一个或用逗号分隔6 位字母数字",
"classCodesHint": "服务端会校验邀请码有效性及科目是否已被其他教师占用。",
"subjects": "教学科目",
"subjectsOptional": "教学科目(可选,可多选)",
"subjectsHint": "可选多个科目,服务端会为每个班级的每个所选科目绑定任课教师。若班级该科目已有任课教师,绑定将被拒绝。"
},
"student": {
"classCodesOptional": "班级邀请码(可选)",
"classCodesPlaceholder": "每行一个或用逗号分隔6 位字母数字",
"classCodesHint": "若管理员已预分配班级,可跳过此项。"
},
"parent": {
"bindHint": "通过子女邮箱 + 子女生日 + 子女手机号后 4 位绑定子女(至少完整填写一个,可添加多个)。",
"childN": "子女 {index}",
"childEmail": "子女邮箱",
"childEmailRequired": "请填写子女邮箱",
"childEmailInvalid": "子女邮箱格式错误",
"childBirthDate": "子女生日",
"childBirthDateRequired": "请填写子女生日",
"childBirthDateInvalid": "子女生日格式错误YYYY-MM-DD",
"childPhoneSuffix": "子女手机号后 4 位",
"childPhoneSuffixRequired": "请填写子女手机号后 4 位",
"childPhoneSuffixInvalid": "子女手机号后 4 位格式错误",
"childRelation": "关系",
"childRelationPlaceholder": "父亲 / 母亲 / 其他",
"addChild": "添加子女"
},
"complete": {
"ready": "已准备完成",
"readyHint": "点击完成后进入系统。"
},
"validation": {
"needNamePhone": "请填写姓名与电话",
"needOneChild": "请至少完整填写一个子女的绑定信息"
},
"progress": {
"label": "Onboarding 进度"
},
"toast": {
"completeSuccess": "配置完成",
"partialFailure": "配置完成,但 {count} 项绑定失败",
"submitFailed": "提交失败",
"inputInvalid": "输入校验失败"
}
}