anhuiqiang před 1 týdnem
rodič
revize
a176a0294b

+ 25 - 0
docs/DEVELOPMENT_STANDARDS.md

@@ -69,3 +69,28 @@ async processDocument(file: Express.Multer.File) {
 2. **New code must follow English comments and logs standards**
 3. **When refactoring existing code, update comments and logs to English simultaneously**
 4. **All error messages must use the i18n system for internationalization**
+
+## Validation i18n Rules
+
+### class-validator Limitation
+The `@MinLength`, `@MaxLength`, `@IsEmail`, etc. decorators from `class-validator` have a **static `message` property** that cannot access NestJS's `I18nService` at runtime. Therefore:
+
+- **DO NOT** use hardcoded messages in validation decorators like:
+  ```typescript
+  @MinLength(8, { message: 'Password must be at least 8 characters long' })
+  ```
+
+- **DO** perform validation in the controller layer with i18n support:
+  ```typescript
+  if (password.length < 6) {
+    throw new BadRequestException(this.i18nService.getErrorMessage('passwordMinLength'));
+  }
+  ```
+
+- **OR** remove the decorator and rely on controller-level validation only
+
+### Adding New Validation Rules
+When adding new validation to DTOs, ensure validation messages are internationalized by:
+1. Adding the i18n key to `server/src/i18n/messages.ts`
+2. Adding validation logic in the controller or service layer using `I18nService`
+

+ 1 - 2
server/src/user/dto/update-user.dto.ts

@@ -1,4 +1,4 @@
-import { IsBoolean, IsOptional, IsString, MinLength, IsEnum } from 'class-validator';
+import { IsBoolean, IsOptional, IsString, IsEnum } from 'class-validator';
 import { ApiPropertyOptional } from '@nestjs/swagger';
 import { UserRole } from '../user-role.enum';
 
@@ -18,6 +18,5 @@ export class UpdateUserDto {
 
   @IsOptional()
   @IsString()
-  @MinLength(6)
   password?: string;
 }

+ 5 - 0
server/src/user/user.controller.ts

@@ -202,6 +202,11 @@ export class UserController {
       }
     }
 
+    // Validate password length if provided
+    if (body.password && body.password.length < 6) {
+      throw new BadRequestException(this.i18nService.getErrorMessage('passwordMinLength'));
+    }
+
     return this.userService.updateUser(id, body);
   }
 

+ 2 - 2
web/components/views/SettingsView.tsx

@@ -623,7 +623,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
 
         try {
             const result = await userService.importUsers(file);
-            showSuccess(t('importSuccess').replace('$1', (result.created + result.updated).toString()).replace('$2', result.errors.length.toString()));
+            showSuccess(t('importSuccess').replace('$1', (result.success || 0).toString()).replace('$2', (result.failed || 0).toString()));
             fetchUsers();
             if (result.errors.length > 0) {
                 console.warn('Import had errors:', result.errors);
@@ -669,7 +669,7 @@ export const SettingsView: React.FC<SettingsViewProps> = ({
     };
 
     const handleDeleteModel = async (id: string) => {
-        if (await confirm(t('confirmClear'))) {
+        if (await confirm(t('confirmDeleteModel'))) {
             await onUpdateModels('delete', { id } as ModelConfig);
         }
     };

+ 3 - 0
web/utils/translations.ts

@@ -200,6 +200,7 @@ export const translations = {
     errorNoModel: "未选择推理模型或配置无效。",
     aiDisclaimer: "AI 可能会犯错。请核实源文件中的重要信息。",
     confirmClear: "确定要清空所有文件及索引吗?",
+    confirmDeleteModel: "确定要删除此模型吗?",
     removeFile: "移除文件",
     apiError: "缺少配置或 API 密钥无效。",
     geminiError: "API 请求失败。",
@@ -1090,6 +1091,7 @@ export const translations = {
     errorNoModel: "No inference model selected or config invalid.",
     aiDisclaimer: "AI can make mistakes. Verify important info.",
     confirmClear: "Delete all files and indices?",
+    confirmDeleteModel: "Are you sure you want to delete this model?",
     removeFile: "Remove file",
     apiError: "Missing config or invalid API Key.",
     geminiError: "API Request Failed.",
@@ -1883,6 +1885,7 @@ export const translations = {
     errorNoModel: "モデルが選択されていないか、設定が無効です。",
     aiDisclaimer: "AIは間違いを犯す可能性があります。",
     confirmClear: "すべてのファイルを削除しますか?",
+    confirmDeleteModel: "このモデルを削除してもよろしいですか?",
     removeFile: "ファイルを削除",
     apiError: "設定が不足しているか、APIキーが有効ではありません。",
     geminiError: "APIリクエストに失敗しました。",