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": "Sign In",
"subtitle": "Sign in to your Next_Edu account",
"email": "Email",
"password": "Password",
"rememberMe": "Remember me",
"signIn": "Sign In",
"signingIn": "Signing in...",
"forgotPassword": "Forgot password?",
"noAccount": "Don't have an account?",
"register": "Register"
},
"register": {
"title": "Register",
"subtitle": "Create your Next_Edu account",
"email": "Email",
"password": "Password",
"confirmPassword": "Confirm password",
"name": "Name",
"signUp": "Sign Up",
"signingUp": "Signing up...",
"hasAccount": "Already have an account?",
"signIn": "Sign In"
},
"errors": {
"invalidCredentials": "Invalid email or password",
"emailExists": "This email is already registered",
"passwordTooWeak": "Password is too weak"
}
}

View File

@@ -0,0 +1,55 @@
{
"invitation": {
"title": "Class Invitation Code",
"generate": "Generate Code",
"regenerate": "Regenerate",
"revoke": "Revoke",
"copy": "Copy",
"copied": "Copied",
"code": "Code",
"status": "Status",
"expiresAt": "Expires At",
"usedCount": "Used Count",
"maxUses": "Max Uses",
"createdAt": "Created At",
"note": "Note",
"neverExpires": "Never expires",
"unlimited": "Unlimited",
"active": "Active",
"disabled": "Disabled",
"expired": "Expired",
"exhausted": "Exhausted",
"list": "Invitation Codes",
"empty": "No invitation codes",
"join": "Join Class",
"joinPlaceholder": "Enter 6-character code",
"joinSuccess": "Joined class successfully",
"joinFailed": "Failed to join class",
"invalidCode": "Invalid or expired invitation code",
"rateLimited": "Too many attempts, please try again later",
"alreadyInClass": "You are already in this class",
"subjectConflict": "This subject already has a teacher for this class",
"generateSuccess": "Invitation code generated",
"generateFailed": "Failed to generate invitation code",
"revokeSuccess": "Invitation code revoked",
"revokeFailed": "Failed to revoke invitation code",
"regenerateSuccess": "Invitation code regenerated",
"regenerateConfirm": "The old code will be invalidated immediately after regeneration. Continue?",
"revokeConfirm": "The code will be invalidated immediately after revocation. Continue?",
"expiresInHours": "Validity (hours)",
"maxUsesLabel": "Max uses",
"customNote": "Note (optional)",
"customNotePlaceholder": "e.g., Back-to-school temporary code",
"generateWithCustom": "Generate custom code",
"defaultDuration": "Never expires",
"defaultMaxUses": "Unlimited"
},
"class": {
"title": "Class",
"name": "Class Name",
"grade": "Grade",
"homeroomTeacher": "Homeroom Teacher",
"studentCount": "Student Count",
"actions": "Actions"
}
}

View File

@@ -0,0 +1,53 @@
{
"app": {
"title": "Next_Edu - K12 Smart Education System",
"description": "Enterprise Grade K12 Education Management System"
},
"actions": {
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"confirm": "Confirm",
"search": "Search",
"reset": "Reset",
"submit": "Submit",
"back": "Back",
"next": "Next",
"previous": "Previous",
"finish": "Finish",
"skip": "Skip",
"retry": "Retry",
"close": "Close",
"add": "Add",
"remove": "Remove",
"edit": "Edit",
"view": "View",
"loading": "Loading...",
"saving": "Saving...",
"submitting": "Submitting..."
},
"status": {
"success": "Success",
"failure": "Failure",
"pending": "Pending",
"active": "Active",
"disabled": "Disabled",
"expired": "Expired",
"exhausted": "Exhausted"
},
"locale": {
"switch": "Switch language",
"zh-CN": "中文",
"en": "English"
},
"empty": {
"default": "No data",
"noResults": "No matching results found"
},
"pagination": {
"previous": "Previous",
"next": "Next",
"page": "Page {page}",
"total": "{total} items"
}
}

View File

@@ -0,0 +1,23 @@
{
"auth": {
"required": "Please sign in first",
"forbidden": "Access denied",
"sessionExpired": "Session expired, please sign in again"
},
"network": {
"requestFailed": "Request failed, please try again later",
"timeout": "Request timed out",
"serverError": "Server error"
},
"validation": {
"required": "This field is required",
"invalidFormat": "Invalid format",
"tooShort": "Too short",
"tooLong": "Too long"
},
"db": {
"notFound": "Record not found",
"duplicate": "Record already exists",
"constraintViolation": "Data constraint violation"
}
}

View File

@@ -0,0 +1,78 @@
{
"title": "First Login Onboarding",
"description": "Complete your first login setup",
"steps": {
"roleConfirm": "Role Confirmation",
"basicInfo": "Basic Information",
"roleInfo": "Role Information",
"complete": "Complete"
},
"role": {
"yourRole": "Your Role",
"allRoles": "All roles: {roles}",
"adminAssigned": "Roles are pre-assigned by administrators. Contact an administrator to change.",
"admin": "Administrator",
"teacher": "Teacher",
"student": "Student",
"parent": "Parent",
"grade_head": "Grade Head",
"teaching_head": "Teaching Head"
},
"form": {
"name": "Name",
"nameRequired": "Please enter your name",
"nameMax": "Name cannot exceed 50 characters",
"phone": "Phone",
"phoneRequired": "Please enter your phone number",
"phoneInvalid": "Please enter a valid 11-digit phone number starting with 1",
"address": "Address",
"addressMax": "Address cannot exceed 200 characters"
},
"teacher": {
"classCodes": "Class Invitation Codes",
"classCodesOptional": "Class invitation codes (optional, multiple allowed)",
"classCodesPlaceholder": "One per line or comma-separated, 6 alphanumeric characters",
"classCodesHint": "The server validates the code and checks whether the subject is already assigned to another teacher.",
"subjects": "Teaching Subjects",
"subjectsOptional": "Teaching subjects (optional, multiple selection)",
"subjectsHint": "Multiple subjects can be selected. The server binds the teacher to each subject for each class. If a subject already has a teacher, the binding will be rejected."
},
"student": {
"classCodesOptional": "Class invitation codes (optional)",
"classCodesPlaceholder": "One per line or comma-separated, 6 alphanumeric characters",
"classCodesHint": "Skip this if an administrator has pre-assigned your class."
},
"parent": {
"bindHint": "Bind children via child email + child birthday + last 4 digits of child phone (at least one complete entry required, multiple allowed).",
"childN": "Child {index}",
"childEmail": "Child Email",
"childEmailRequired": "Please enter the child's email",
"childEmailInvalid": "Invalid child email format",
"childBirthDate": "Child Birthday",
"childBirthDateRequired": "Please enter the child's birthday",
"childBirthDateInvalid": "Invalid child birthday format (YYYY-MM-DD)",
"childPhoneSuffix": "Child Phone Last 4 Digits",
"childPhoneSuffixRequired": "Please enter the last 4 digits of the child's phone",
"childPhoneSuffixInvalid": "Invalid last 4 digits format",
"childRelation": "Relationship",
"childRelationPlaceholder": "Father / Mother / Other",
"addChild": "Add Child"
},
"complete": {
"ready": "Ready to Finish",
"readyHint": "Click finish to enter the system."
},
"validation": {
"needNamePhone": "Please enter your name and phone",
"needOneChild": "Please complete at least one child's binding information"
},
"progress": {
"label": "Onboarding progress"
},
"toast": {
"completeSuccess": "Setup complete",
"partialFailure": "Setup complete, but {count} binding(s) failed",
"submitFailed": "Submission failed",
"inputInvalid": "Input validation failed"
}
}