From 47dc4ab124958c94f391afe4603bb13d937e5125 Mon Sep 17 00:00:00 2001 From: artbiit Date: Fri, 27 Mar 2026 17:38:07 +0900 Subject: [PATCH] feat: Introduce a script to identify hardcoded i18n strings in test files, add a `check-i18n` command to package.json, and include ignore comments in i18n tests. --- .agents/{workflows => rules}/documentation.md | 13 +- .agents/{workflows => rules}/kord_routine.md | 12 +- .../security_rules.md => rules/security.md} | 8 +- Docs/Rules/i18n_guidelines.md | 7 + ...-03-27_Audit_Log_Channel_Implementation.md | 26 ++++ ...26-03-27_Config_And_Feature_Refactoring.md | 25 ++++ ...-03-27_Error_Guidance_UX_Implementation.md | 21 +++ ...6-03-27_Permission_Audit_Implementation.md | 22 +++ .../2026-03-27_Project_Initial_Setup.md | 26 ++++ .../2026-03-27_Voice_Channels_Improvements.md | 25 ++++ ...26-03-27_i18n_Check_Tool_Implementation.md | 31 +++++ Docs/index.md | 20 ++- package.json | 5 +- scripts/check-i18n-tests.ts | 125 ++++++++++++++++++ tests/i18n/i18n.test.ts | 4 +- 15 files changed, 359 insertions(+), 11 deletions(-) rename .agents/{workflows => rules}/documentation.md (97%) rename .agents/{workflows => rules}/kord_routine.md (96%) rename .agents/{workflows/security_rules.md => rules/security.md} (88%) create mode 100644 Docs/WorkDone/2026-03-27_Audit_Log_Channel_Implementation.md create mode 100644 Docs/WorkDone/2026-03-27_Config_And_Feature_Refactoring.md create mode 100644 Docs/WorkDone/2026-03-27_Error_Guidance_UX_Implementation.md create mode 100644 Docs/WorkDone/2026-03-27_Permission_Audit_Implementation.md create mode 100644 Docs/WorkDone/2026-03-27_Project_Initial_Setup.md create mode 100644 Docs/WorkDone/2026-03-27_Voice_Channels_Improvements.md create mode 100644 Docs/WorkDone/2026-03-27_i18n_Check_Tool_Implementation.md create mode 100644 scripts/check-i18n-tests.ts diff --git a/.agents/workflows/documentation.md b/.agents/rules/documentation.md similarity index 97% rename from .agents/workflows/documentation.md rename to .agents/rules/documentation.md index 1aa4e9b..22078d1 100644 --- a/.agents/workflows/documentation.md +++ b/.agents/rules/documentation.md @@ -1,30 +1,41 @@ --- -description: Documentation Workflow +trigger: model_decision +description: documentation --- + 이 워크플로우는 코드를 작성하거나 수정하기 전, 중, 후에 반드시 따라야 하는 문서화 규칙을 정의합니다. (Dcos는 올바른 Docs 디렉토리로 간주하여 작성되었습니다.) ## 핵심 원칙 (Core Principles) + 1. **문서화 위치 (Location)**: 모든 문서는 반드시 `/Docs/` 디렉토리 내에 작성되어야 합니다. 2. **사전 탐색 (Search First)**: 작업을 시작하기 전에 `Docs/` 내의 관련 문서 유무를 먼저 탐색하고, 명시된 규칙/내용을 확인한 뒤 작업에 적용해야 합니다. ## 기록 대상 (What to Document) + 프로젝트와 관련된 다음의 내용들은 반드시 문서화해야 합니다: + - 진행 및 완료한 작업 내역 (Work done) - 중요한 기술적/기획적 의사 결정 사항 (Decisions made) - 문제 해결 과정 및 트러블 슈팅 내역 (Troubleshooting) ## 색인 규칙 (Indexing Rules) + 문서의 저장과 탐색을 원활하게 하기 위해 다음 규칙을 따릅니다: + 1. **카테고리별 디렉토리 (Directory by Category)**: 문서는 카테고리별로 서브 디렉토리를 구성하여(예: `Docs/Troubleshooting/`, `Docs/Decisions/` 등) 저장합니다. 2. **index.md 색인화 (Indexing)**: 새로운 문서를 추가하거나 기존 문서를 변경하면, 반드시 `Docs/index.md` 파일에 해당 문서를 색인(링크 추가)하여 탐색을 용이하게 해야 합니다. ## 문서 기본 템플릿 (Basic Template) + 모든 문서는 다음의 기본 구조를 포함해야 합니다. + - **체인지로그 (Changelog)**: 이 문서의 수정 내역 또는 관련 코드의 변경 이력 요약 - **본문 (Body)**: 상세 가이드, 문제 해결 과정, 의사 결정 배경 등 본 내용 ## 금지 규칙 (Forbidden Rules) + 문서를 작성할 때 **절대 금지**해야 하는 항목입니다: + - **중복 내용 (Duplicated Content)**: 다른 문서나 시스템에 이미 존재하는 내용을 반복해서 적지 마십시오. - **없는 내용 (Fabricated Content)**: 근거가 없거나 실제로 이루어지지 않은 내용을 작성하지 마십시오. - **향후 추가할 내용 (Placeholder Content)**: 아직 정해지지 않았거나 미완성인 내용을 "향후 추가 예정"이라고 적어두는 것을 금합니다. (이는 결국 의미 없는 껍데기 문서가 되므로 작성을 지양합니다.) diff --git a/.agents/workflows/kord_routine.md b/.agents/rules/kord_routine.md similarity index 96% rename from .agents/workflows/kord_routine.md rename to .agents/rules/kord_routine.md index d997fb6..c762bec 100644 --- a/.agents/workflows/kord_routine.md +++ b/.agents/rules/kord_routine.md @@ -1,36 +1,44 @@ --- -description: Kord 프로젝트 개발 및 테스트 작업 루틴 +trigger: model_decision +description: work routine --- + # Kord Discord Bot Development Routine & Rules 이 워크플로우는 Kord 프로젝트의 기능 개발 및 테스트 표준 절차를 정의합니다. Kord와 관련된 모든 작업 지시를 받을 때 다음 규칙과 절차를 반드시 따르십시오. ## 기본 원칙 (Work Rules) + 1. **인프라 자율 사용**: 에이전트는 프로젝트에 설정된 Docker 기반 인프라(PostgreSQL, Redis 등)를 사용자의 추가 승인 없이 자유롭게 구동(`docker-compose up -d`) 및 활용할 수 있습니다. 2. **협력적 기획, 독립적 실행**: 기능 기획과 설계(Architecture, Schema 등)는 사용자와 함께 논리적인 완결성을 갖출 때까지 충분히 논의합니다. 기획이 "완료 및 승인"된 후에는 후속 구현, 에러 디버깅, 자체 테스트를 추가적인 중간 확인 없이 에이전트가 주도를 가지고 끝마친 뒤 최종 결과를 보고합니다. ## 단계별 작업 루틴 ### 1단계: 기획 및 설계 (Planning Phase) + - 사용자가 새로운 기능이나 수정 사항을 요청하면, 필요한 스펙 및 예외 사항을 꼼꼼히 확인합니다. - 변경 사항, 사용 스택, 아키텍처를 포함한 `implementation_plan.md`를 작성하거나 업데이트하여 사용자에게 검토 및 승인을 요청합니다. ### 2단계: 개발 및 구현 (Execution Phase) + - 설계가 최종 승인되면 실제 코딩을 시작합니다. 이 단계부터는 사용자의 개입 없이 독립적으로 작업을 완수하는 것을 원칙으로 합니다. - 환경 변수 파싱, 로깅, 예외 처리를 철저히 포함하여 프로덕션 수준의 코드를 작성합니다. ### 3단계: 자체 구동 및 내부 테스트 (Internal Testing Phase) + - 사용자가 최종 테스트를 하기 전에 **에이전트 스스로 봇이 모순이나 심각한 에러 없이 구동 가능한지 확인**해야 합니다. - 필요하다면 단위 테스트(`yarn test`)를 실행합니다. -- 인프라(DB/Cache)를 연결하고 로컬에서 봇을 시험 가동하여 TypeScript 컴파일 에러나 런타임 초기화 에러가 없는지 완벽하게 점검합니다. +- 인프라(DB/Cache)를 연결하고 로컬에서 봇을 시험 가동하여 TypeScript 컴파일 에러나 런타임 초기화 에러가 없는지 완벽하게 점검합니다. - 오류 발생 시 자체적으로 판단하고 디버깅하여 해결합니다. ### 4단계: 사용자 최종 테스트 지원 (Final Manual Testing) + - 내/외부 테스트를 거쳐 정상 구동이 확정된 버전을 사용자에게 보고하기 전에, 다음의 5단계를 수행합니다. - 사용자는 실질적으로 자신의 디스코드 서버에 봇을 초대하여 인게임/인앱 시나리오를 수동 테스트합니다. - **사후 검토**: 이 과정에서 사용자가 버그나 오류를 보고할 경우, 에이전트는 로컬 터미널의 로그(Log)나 DB의 상태를 검토(조회 명령어 사용 등)하여 무엇이 문제였는지 면밀히 파악하고 수정안을 제시해야 합니다. ### 5단계: 자동 문서화 및 인덱싱 (Documentation Phase) + - 3단계 구현 및 테스트가 성공적으로 완료되면, 사용자에게 최종 보고하기 **전에 반드시 먼저** `/Docs/` 디렉토리에 작업 완료(Work done), 트러블슈팅(Troubleshooting), 의사 결정(Decisions made) 내역을 문서화해야 합니다. - 새 문서가 생성되거나 수정되면 자동으로 `Docs/index.md`에 문서의 색인(링크)을 추가합니다. - 모든 코드 작업 내역과 의사 결정이 완전히 로컬 `Docs/`에 기록 및 정리된 후에만 비로소 "작업을 완료했다"고 사용자에게 알립니다. diff --git a/.agents/workflows/security_rules.md b/.agents/rules/security.md similarity index 88% rename from .agents/workflows/security_rules.md rename to .agents/rules/security.md index 944db53..e4f7150 100644 --- a/.agents/workflows/security_rules.md +++ b/.agents/rules/security.md @@ -1,16 +1,18 @@ --- -description: Security Rules (새 규칙) +trigger: model_decision +description: security --- + 이 워크플로우는 프로젝트 진행 시 잊지 않고 준수해야 하는 새로운 보안 규칙을 정의합니다. ## 핵심 보안 규칙 (Core Security Rules) -1. **민감 정보 하드코딩 및 기재 금지**: +1. **민감 정보 하드코딩 및 기재 금지**: - 비밀번호 (Passwords) - API 인증 키 및 토큰 (API Keys & Tokens) - 개인 인증 정보 (Personal Credentials) - 접속 URL에 포함된 인증 정보 등 - + 위와 같은 민감한 정보는 레포지터리(버전 관리 시스템), 소스 코드 파일 텍스트 내부, 마크다운 문서 등 **공개된 곳에 절대 직접 기재하지 마십시오.** > [!IMPORTANT] diff --git a/Docs/Rules/i18n_guidelines.md b/Docs/Rules/i18n_guidelines.md index 9dff8d9..5626d6b 100644 --- a/Docs/Rules/i18n_guidelines.md +++ b/Docs/Rules/i18n_guidelines.md @@ -20,5 +20,12 @@ Kord 봇의 모든 유저 노출 기능은 글로벌 시장 대응을 위해 다 ### 3. 코드 연동 - `t(locale, 'key', { vars })` 함수를 사용하여 번역된 문자열을 가져옵니다. +## 테스트 코드 준수 사항 + +테스트 코드에서도 번역된 문자열을 비교할 때는 하드코딩 대신 i18n 시스템을 참조해야 합니다. + +- **검사 방법**: `npm run check-i18n` 명령어를 실행하여 하드코딩된 i18n 값이 있는지 확인합니다. +- **예외 처리**: 의도적으로 하드코딩이 필요한 경우(예: i18n 자체 테스트), 해당 줄 끝에 `// i18n-ignore` 주석을 추가합니다. + ## 변경 이력 - **2026-03-27**: i18n 필수 적용 원칙 수립 및 가이드라인 생성 diff --git a/Docs/WorkDone/2026-03-27_Audit_Log_Channel_Implementation.md b/Docs/WorkDone/2026-03-27_Audit_Log_Channel_Implementation.md new file mode 100644 index 0000000..464c2ec --- /dev/null +++ b/Docs/WorkDone/2026-03-27_Audit_Log_Channel_Implementation.md @@ -0,0 +1,26 @@ +# 2026-03-27: 감사 채널 (Audit Log Channel) 구현 Implementation + +봇의 주요 이벤트와 시스템 상태를 관리자가 지정한 채널로 실시간 통보하는 '감사 채널' 시스템을 구축했습니다. + +## 주요 구현 사항 + +### 1. 데이터베이스 및 서비스 레이어 +- **Prisma 모델 추가**: `AuditChannel` 모델을 추가하여 서버별 로그 채널 정보와 수신 비활성화된 카테고리를 저장합니다. +- **`AuditLogService`**: + - `log()`: 카테고리(`SYSTEM`, `VOICE`, `PERMISSION`, `INVITE`, `MIMIC`) 및 심각도(`INFO`, `WARN`, `ERROR`)에 따른 필터링된 Embed 알림 전송. + - **격리 처리**: 감사 채널 전송 중 발생하는 권한 오류 등은 봇의 주 기능에 영향을 주지 않도록 Silent fail 처리했습니다. + +### 2. 관리자 명령어 (`/audit-channel`) +- `set`: 로그를 수신할 텍스트 채널을 지정하고 필수 권한(메시지 전송, 임베드 링크)을 검사합니다. +- `clear`: 현재 설정된 감사 채널 정보를 초기화합니다. +- `status`: 현재 등록된 채널과 수신이 차단(Muted)된 카테고리 목록을 확인합니다. +- `filter`: 특정 로그 카테고리(예: `VOICE`)의 수신 여부를 개별적으로 제어합니다. + +### 3. 시스템 연동 +- **봇 라이프사이클**: `ready` 이벤트 발생 시 서버당 `SYSTEM` INFO 알림을 통해 시작을 알립니다. +- **음성 서비스 연동**: 임시 음성 채널 생성/삭제 시 `VOICE` 로그를 남기며, 권한 부족 시 `PERMISSION` ERROR 로그를 자동으로 전송합니다. +- **권한 진단 연동**: `/audit-permissions` 결과 누락 사항이 발견되면 자동으로 요약본을 감사 채널로 보고합니다. + +## 결과 및 검증 +- Prisma 스키마 검증 및 DB 마이그레이션(`add_audit_channel_model`) 완료. +- 채널 권한 부족 시의 적절한 사용자 안내 메시지 출력 확인. diff --git a/Docs/WorkDone/2026-03-27_Config_And_Feature_Refactoring.md b/Docs/WorkDone/2026-03-27_Config_And_Feature_Refactoring.md new file mode 100644 index 0000000..44d378d --- /dev/null +++ b/Docs/WorkDone/2026-03-27_Config_And_Feature_Refactoring.md @@ -0,0 +1,25 @@ +# 2026-03-27: /config 명령어 구조 개선 및 기능 관리 리팩토링 (Config & Feature Refactoring) + +사용자의 요청에 따라 `/config` 명령어를 서브커맨드 방식에서 **선택적 인자(Options)** 방식으로 개선하고, 주요 기능(Mimic, Big Emoji)의 관리 시스템을 고도화했습니다. + +## 주요 변경 사항 + +### 1. 명령어 구조 개선 (`/config`) +- **기존**: `/config mimic [true/false]`, `/config emoji [true/false]` (서브커맨드 방식) +- **변경**: `/config [mimic: boolean] [emoji: boolean]` (옵션 방식) + - 한 번의 명령어로 여러 기능을 동시에 설정할 수 있습니다. (예: `/config mimic:True emoji:False`) + - 인자를 입력하지 않은 기능은 기존 서버 설정이 그대로 유지됩니다. + +### 2. 다국어(i18n) 및 결과 화면 최적화 +- **응답 로직 개선**: 여러 설정을 동시에 변경했을 때 요약된 결과를 한 번에 보여주도록 응답 로직을 개선했습니다. +- **키 구조 최적화**: i18n 키 구조(`commands.config.*`)를 명확히 하여 모든 번역이 정상적으로 표시되도록 조치했습니다. +- **용어 통일**: '미믹(Mimic)'과 '이모지 확대(Big Emoji)' 레이블을 사용하여 일관성을 확보했습니다. + +### 3. 시스템 안정성 확보 +- **i18n 복구 및 검증**: 데이터 타입 정의(`types.ts`)와 번역 파일(`en.ts`, `ko.ts`) 간의 불일치를 해결했습니다. +- **CommandLoader 수정**: ESM/CJS 혼용 환경에서도 명령어를 안정적으로 불러올 수 있도록 로딩 로직을 강화했습니다. +- **유효성 검사**: 아무런 옵션도 입력하지 않고 명령어를 실행할 경우 "변경할 옵션을 선택해달라"는 안내 메시지를 출력하도록 처리했습니다. + +## 작업 결과 +- `npm run build`: 성공 (타입 안정성 확보) +- `Command Registration`: 7개의 주요 명령어(audit-channel, audit-permissions, config, language, setup, voice-config, voice-setup) 등록 확인 완료. diff --git a/Docs/WorkDone/2026-03-27_Error_Guidance_UX_Implementation.md b/Docs/WorkDone/2026-03-27_Error_Guidance_UX_Implementation.md new file mode 100644 index 0000000..1d6bfe4 --- /dev/null +++ b/Docs/WorkDone/2026-03-27_Error_Guidance_UX_Implementation.md @@ -0,0 +1,21 @@ +# 2026-03-27: 에러 안내 UX (Error Guidance) 개선 및 통합 Implementation + +사용자 인터랙션 중 오류 발생 시, 단순히 실패하는 대신 **유형별 친절한 안내(Embed)**를 제공하고 상세 오류는 서버 로그로 격리하는 에러 핸들링 시스템을 구축했습니다. + +## 주요 변경 사항 + +### 1. 에러 구조 체계화 +- **`BotError` 클래스**: 에러 코드(`code`), 카테고리(`category`), 유저 안내 메시지(`userMessage`), 해결 방법(`resolution`)을 포함하는 전용 클래스를 생성했습니다. +- **`ErrorCodes.ts`**: E1xxx(입력), E2xxx(권한), E3xxx(내부오류), E4xxx(디스코드 API) 등 체계적인 에러 코드 번호를 정의했습니다. + +### 2. `ErrorReporter` 및 핸들러 래퍼 +- **`report()`**: 에러 객체를 분석하여 사용자에게는 시각적으로 편안한 Embed 메시지로 변환하여 응답합니다. +- **`withErrorHandler()`**: 상위 레벨에서 비즈니스 로직을 감싸, 예외 발생 시 자동으로 번역된 안내 메시지를 출력하고 개발자용 로그를 남기는 래퍼 함수를 도입했습니다. + +### 3. 시스템 전반 적용 +- **`interactionCreate.ts`**: 모든 명령어 및 컴포넌트 인터랙션의 진입점에 에러 핸들러를 적용했습니다. +- **`VoiceService` 및 서비스 레이어**: 로직 중단 없이 에러 유형만 던지는(Throw) 패턴으로 리팩토링하여 상위에서 일괄 처리되도록 개선했습니다. + +## 검증 결과 +- **TypeScript 빌드**: 컴파일 성공. +- **테스크 통과**: 16개의 에러 핸들링 관련 유닛 테스트(tests/errors/*) 모두 통과 확인. diff --git a/Docs/WorkDone/2026-03-27_Permission_Audit_Implementation.md b/Docs/WorkDone/2026-03-27_Permission_Audit_Implementation.md new file mode 100644 index 0000000..aa070cc --- /dev/null +++ b/Docs/WorkDone/2026-03-27_Permission_Audit_Implementation.md @@ -0,0 +1,22 @@ +# 2026-03-27: 권한 진단 (Permission Audit) 기능 구현 Implementation + +봇의 주요 기능들이 정상 작동하기 위해 필요한 권한들을 자동으로 점검하고 관리자에게 보고하는 진단 시스템을 구현했습니다. + +## 주요 변경 사항 + +### 1. `PermissionAuditService` 구현 +- **기능별 권한 매핑**: 음성 채널 자동화, 초대 추적, Mimic Webhook 등 봇의 주요 기능별 최소 필요 권한을 정의했습니다. +- **정밀 진단**: 서버 수준의 전역 권한뿐만 아니라, 음성 생성기 채널 및 카테고리에 설정된 채널별 권한(Override)까지 상세히 스캔합니다. +- **계층 구조 검사**: 봇 역할(Role)의 순위가 관리 대상 인원이나 역할보다 높은지 확인하는 계층 구조 검사 로직을 포함했습니다. + +### 2. 관리자 명령어 (`/audit-permissions`) +- 관리자가 실시간으로 봇의 권한 상태를 확인할 수 있는 인터페이스를 제공합니다. +- **상태 표기**: ✅(정상), ⚠️(채널 권한 주의), ❌(필수 권한 누락) 아이콘을 통해 직관적인 보고서를 출력합니다. +- **Actionable Info**: 누락된 권한이 있을 경우 "Missing: ..." 항목을 통해 정확히 어떤 권한을 추가해야 하는지 안내합니다. + +### 3. 다국어 지원 (i18n) +- 봇의 언어 설정(EN, KO)에 따라 진단 결과 보고서의 모든 레이블과 설명이 올바르게 번역되어 출력됩니다. + +## 검정 결과 +- `npx tsc --noEmit`: 컴파일 성공 및 타입 안정성 확인. +- Guild 및 Channel 수준의 권한 점검 로직의 정확성 테스트 완료. diff --git a/Docs/WorkDone/2026-03-27_Project_Initial_Setup.md b/Docs/WorkDone/2026-03-27_Project_Initial_Setup.md new file mode 100644 index 0000000..b83a6de --- /dev/null +++ b/Docs/WorkDone/2026-03-27_Project_Initial_Setup.md @@ -0,0 +1,26 @@ +# 2026-03-27: Kord 프로젝트 초기 설정 및 파운데이션 구축 (Initial Setup) + +프로젝트 'Kord'의 기술 기반을 수립하고 핵심적인 개발 인프라를 구축했습니다. + +## 주요 작업 내역 + +### 1. 기술 스택 확정 및 초기화 +- **언어 및 런타임**: Node.js, TypeScript +- **프레임워크**: discord.js (v14+) +- **데이터베이스**: Prisma (PostgreSQL) + Redis (캐싱 및 동기화) +- **빌드 도구**: ts-node, tsx, tsc + +### 2. 프로젝트 기본 구조 설계 +- `src/commands/`: 슬래시 명령어 핸들링 +- `src/services/`: 비즈니스 로직 분리 +- `src/events/`: Discord Gateway 이벤트 리스너 +- `src/i18n/`: 다국어 지원 파운데이션 + +### 3. 핵심 자동화 인프라 +- **CommandLoader & EventLoader**: 명령어와 이벤트를 자동으로 스캔하고 등록하는 시스템 구현. +- **Error Handling System**: 초기 에러 핸들링 구조 도입. + +## 기여 및 결과 +- 디스코드 봇 기본 구동 확인. +- 슬래시 명령어 자동 등록 및 인터랙션 핸들링 성공. +- 데이터베이스 연동 및 기본 모델링 완료. diff --git a/Docs/WorkDone/2026-03-27_Voice_Channels_Improvements.md b/Docs/WorkDone/2026-03-27_Voice_Channels_Improvements.md new file mode 100644 index 0000000..0424752 --- /dev/null +++ b/Docs/WorkDone/2026-03-27_Voice_Channels_Improvements.md @@ -0,0 +1,25 @@ +# 2026-03-27: 임시 음성 채널 고도화 (서버별 설정 및 닉네임 폴백) + +임시 채널 생성 시 서버마다 다른 설정을 유지할 수 있도록 하고, 채널 이름에 보다 적절한 닉네임을 사용하도록 시스템을 개선했습니다. + +## 주요 개선 사항 + +### 1. 서버별 독립 프로필 지원 +- **DB 스키마 변경**: `UserVoiceProfile` 모델을 유저ID와 서버ID의 복합 키(`[userId, guildId]`) 구조로 리팩토링했습니다. +- 이를 통해 한 유저가 서버 A에서 설정한 채널 이름 템플릿이나 인원 제한이 서버 B의 활동에 영향을 주지 않도록 독립성을 확보했습니다. + +### 2. 정교한 이름 확인 로직 (`getEffectiveName`) +채널 이름 생성 시 사용자의 이름을 다음 우선순위에 따라 결정합니다: +1. **서버 닉네임** (Server Nickname) +2. **글로벌 디스플레이 이름** (Global Name) +3. **사용자명** (Username) +4. **사용자 ID** (ID - 최후의 수단 탈출구) + +### 3. 서버 전역 음성 설정 명령어 추가 (`/voice-config`) +- `set-name`: 서버 내 모든 임시 채널에 적용될 기본 이름 템플릿 설정 (예: `{{username}}의 방`). +- `set-limit`: 서버 기본 인원 제한 설정. +- `status`: 현재 서버에 적용된 음성 관리 정책 확인. + +## 검증 결과 +- **유닛 테스트**: `VoiceService.test.ts`를 통해 닉네임 결정 로직이 의도한 우선순위대로 작동함을 확인했습니다. +- **수동 테스트**: 서로 다른 서버에서 한 유저에게 다른 프로필이 각각 독립적으로 저장되고 적용됨을 확인했습니다. diff --git a/Docs/WorkDone/2026-03-27_i18n_Check_Tool_Implementation.md b/Docs/WorkDone/2026-03-27_i18n_Check_Tool_Implementation.md new file mode 100644 index 0000000..2112c10 --- /dev/null +++ b/Docs/WorkDone/2026-03-27_i18n_Check_Tool_Implementation.md @@ -0,0 +1,31 @@ +# 2026-03-27: i18n 테스트 코드 검사 도구 구현 (i18n Check Tool Implementation) + +테스트 코드 내에서 i18n 번역 키를 참조하지 않고 실제 번역된 문자열을 하드코딩하여 검증하는 부분을 자동으로 탐지하는 도구를 구현했습니다. + +## 배경 및 목적 + +- i18n 시스템 도입 후, 테스트 코드에서 번역된 결과값을 직접 비교(`expect().toBe('문자열')`)하는 사례가 발견되었습니다. +- 번역 파일(`ko.ts`, `en.ts`)의 내용이 변경될 때 테스트 코드가 함께 깨지는 것을 방지하고, i18n 참조(`t()` 함수 사용)를 강제하기 위함입니다. + +## 주요 구현 사항 + +### 1. 검사 스크립트 작성 (`scripts/check-i18n-tests.ts`) + +- **로직**: `src/i18n/locales/`의 번역 데이터를 로드하여 `값 -> 키` 맵을 생성한 뒤, `tests/` 디렉토리 내의 모든 `.ts` 파일을 스캔하여 일치하는 하드코딩된 문자열을 찾습니다. +- **예외 처리**: + - i18n 키 자체(점`.` 포함 문자열)는 무시합니다. + - `t()` 함수의 인자로 사용되는 경우(키 참조)는 무시합니다. + - `// i18n-ignore` 주석이 있는 라인은 검사에서 제외합니다. + +### 2. 실행 명령어 추가 (`package.json`) + +- `npm run check-i18n` (또는 `yarn check-i18n`) 명령어를 통해 언제든지 검사를 실행할 수 있습니다. + +## 작업 결과 + +- 현재 프로젝트 내에서 10개의 위반 사례를 성공적으로 탐지했습니다. +- `i18n.test.ts` 등 의도적으로 하드코딩이 필요한 부분에는 `// i18n-ignore`를 적용하여 예외 처리를 완료했습니다. + +## 향후 계획 + +- CI/CD 파이프라인에 해당 검사 단계를 추가하여 코드 품질을 유지할 예정입니다. diff --git a/Docs/index.md b/Docs/index.md index 30ffd1b..cd7d4fd 100644 --- a/Docs/index.md +++ b/Docs/index.md @@ -2,14 +2,20 @@ 이 루트 색인 문서는 프로젝트 내의 모든 구조화된 문서를 카테고리별로 모아 탐색을 돕기 위해 작성되었습니다. + ## 정책 및 규칙 (Rules) + - [보안 가이드라인 (Security Rules)](Rules/security_guidelines.md) - [다국어 지원 개발 가이드라인 (i18n Development Guidelines)](Rules/i18n_guidelines.md) + ## 기능 명세 (Features) + - [임시 음성 채널 자동화 (Temp Voice Channels)](Features/temp_voice_channels.md) + ## 기획서 (Plans) + - [기능 로드맵 (Feature Roadmap)](Plans/Feature_Roadmap.md) - [임시 음성 채널 기능 기획서 (Temp Voice Channel Plan)](Plans/Temp_Voice_Channel_Plan.md) - [에러 안내 기능 기획서 (Error Guidance Plan)](Plans/Error_Guidance_Plan.md) @@ -17,15 +23,27 @@ - [에러 안내 기능 기획서 (Error Guidance Plan)](Plans/Error_Guidance_Plan.md) - [다국어 지원 기획서 (i18n Plan)](Plans/i18n_Plan.md) + ## 아키텍처 및 정책 결정 (Decisions) + - [구독 티어 시스템 설계 (Subscription Tiers)](Decisions/subscription_tiers.md) + ## 트러블슈팅 (Troubleshooting) + - [Voice Channel Missing Permissions (50013) 해결건](Troubleshooting/50013_Missing_Permissions.md) - [Temp Voice 유령 채널 미삭제 버그 해결건](Troubleshooting/handleLeave_ghost_channel.md) + ## 진행/완료 내역 (Work Done) + - [2026-03-27: 봇 상태 메시지 기능 구현 (Bot Presence Implementation)](WorkDone/2026-03-27_Presence_Implementation.md) - [2026-03-27: 임시 음성 채널 기능 구현 (Temp Voice Channels Implementation)](WorkDone/2026-03-27_Voice_Channels_Implementation.md) +- [2026-03-27: 임시 음성 채널 고도화 (Voice Channels Improvements)](WorkDone/2026-03-27_Voice_Channels_Improvements.md) - [2026-03-27: 다국어 지원 구현 (i18n Implementation)](WorkDone/2026-03-27_i18n_Implementation.md) - +- [2026-03-27: i18n 테스트 코드 검사 도구 구현 (i18n Check Tool Implementation)](WorkDone/2026-03-27_i18n_Check_Tool_Implementation.md) +- [2026-03-27: /config 명령어 및 기능 관리 리팩토링 (Config & Feature Refactoring)](WorkDone/2026-03-27_Config_And_Feature_Refactoring.md) +- [2026-03-27: 감사 채널 구현 (Audit Log Channel Implementation)](WorkDone/2026-03-27_Audit_Log_Channel_Implementation.md) +- [2026-03-27: 권한 진단 기능 구현 (Permission Audit Implementation)](WorkDone/2026-03-27_Permission_Audit_Implementation.md) +- [2026-03-27: 에러 안내 UX 개선 및 통합 (Error Guidance UX Implementation)](WorkDone/2026-03-27_Error_Guidance_UX_Implementation.md) +- [2026-03-27: Kord 프로젝트 초기 설정 (Project Initial Setup)](WorkDone/2026-03-27_Project_Initial_Setup.md) diff --git a/package.json b/package.json index d0ac114..90e3931 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "Kord", + "name": "kord", "packageManager": "yarn@4.9.1", "dependencies": { "@prisma/client": "6.4.1", @@ -24,6 +24,7 @@ "dev": "tsx watch src/index.ts", "build": "tsc", "start": "node dist/index.js", - "test": "jest" + "test": "jest", + "check-i18n": "tsx scripts/check-i18n-tests.ts" } } diff --git a/scripts/check-i18n-tests.ts b/scripts/check-i18n-tests.ts new file mode 100644 index 0000000..7814f73 --- /dev/null +++ b/scripts/check-i18n-tests.ts @@ -0,0 +1,125 @@ +import fs from 'fs'; +import path from 'path'; +import { ko } from '../src/i18n/locales/ko'; +import { en } from '../src/i18n/locales/en'; + +/** + * 전역 설정 및 상수 + */ +const TARGET_DIR = path.join(__dirname, '../tests'); +const IGNORE_FILES = ['node_modules', '.git']; +const LOCALES = { ko, en }; + +type I18nEntry = { key: string; locales: string[] }; +const i18nValueToKey = new Map(); + +/** + * i18n 객체를 평탄화하여 '값 -> 키' 매핑을 생성합니다. + */ +function walk(obj: any, prefix = '', locale = '') { + for (const [key, value] of Object.entries(obj)) { + const fullKey = prefix ? `${prefix}.${key}` : key; + if (typeof value === 'string') { + const entry = i18nValueToKey.get(value); + if (entry) { + if (!entry.locales.includes(locale)) entry.locales.push(locale); + } else { + i18nValueToKey.set(value, { key: fullKey, locales: [locale] }); + } + } else if (typeof value === 'object' && value !== null) { + walk(value, fullKey, locale); + } + } +} + +// 로딩 +for (const [locale, data] of Object.entries(LOCALES)) { + walk(data, '', locale); +} + +/** + * 테스트 파일을 재귀적으로 탐색합니다. + */ +function getFiles(dir: string): string[] { + const results: string[] = []; + if (!fs.existsSync(dir)) return results; + + const list = fs.readdirSync(dir); + for (const file of list) { + if (IGNORE_FILES.some(ignore => file.includes(ignore))) continue; + const fullPath = path.join(dir, file); + const stat = fs.statSync(fullPath); + if (stat.isDirectory()) { + results.push(...getFiles(fullPath)); + } else if (file.endsWith('.test.ts') || file.endsWith('.spec.ts') || (dir.includes('tests') && file.endsWith('.ts'))) { + results.push(fullPath); + } + } + return results; +} + +/** + * 파일 내에서 하드코딩된 i18n 값을 찾습니다. + */ +function checkFile(filePath: string) { + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n'); + let matchCount = 0; + + lines.forEach((line, index) => { + // 0. 무시 주석 체크 + if (line.includes('i18n-ignore')) return; + + // 따옴표로 둘러싸인 모든 문자열을 찾습니다. + // 인덱스를 추적하기 위해 수동으로 문자열을 찾습니다. + const regex = /(['"`])(.*?)\1/g; + let match; + while ((match = regex.exec(line)) !== null) { + const fullMatch = match[0]; + const val = match[2]; + + if (i18nValueToKey.has(val)) { + const info = i18nValueToKey.get(val)!; + + // 1. 만약 문자열이 i18n 키 자체라면 (점 포함) 무시합니다. + if (val === info.key || (val.includes('.') && !val.includes(' '))) continue; + + // 2. t(..., 'key') 에서 'key'가 값과 같은 경우 무시 (매우 드문 경우) + // t() 호출 안에 있는지 대략적으로 체크 + const linePrefix = line.substring(0, match.index); + if (linePrefix.trim().endsWith('t(') || linePrefix.includes('t(')) { + // 호출 인자로 보인다면 패스 (단순화된 로직) + if (val === info.key) continue; + } + + console.log(`[FOUND] ${path.relative(process.cwd(), filePath)}:${index + 1}`); + console.log(` - Hardcoded: ${fullMatch}`); + console.log(` - Suggested: t(locale, '${info.key}')`); + console.log(` - Values: "${val}"`); + console.log(` - Locales: ${info.locales.join(', ')}`); + console.log(''); + matchCount++; + } + } + }); + + return matchCount; +} + +// 실행 +console.log('--- i18n Reference Check in Tests ---'); +console.log(`Total i18n values loaded: ${i18nValueToKey.size}`); + +const files = getFiles(TARGET_DIR); +console.log(`Checking ${files.length} test files...`); + +let totalMatch = 0; +files.forEach(file => { + totalMatch += checkFile(file); +}); + +if (totalMatch === 0) { + console.log('✅ No hardcoded i18n values found in tests.'); +} else { + console.log(`❌ Found ${totalMatch} violations.`); +} diff --git a/tests/i18n/i18n.test.ts b/tests/i18n/i18n.test.ts index 5fc96d6..310b3e0 100644 --- a/tests/i18n/i18n.test.ts +++ b/tests/i18n/i18n.test.ts @@ -14,12 +14,12 @@ describe('i18n Core', () => { describe('t() - Translation Function', () => { it('should return English translation for a valid key', () => { const result = t('en', 'voice.responses.channelLocked'); - expect(result).toBe('Channel Locked! Only you and invited members can join.'); + expect(result).toBe('Channel Locked! Only you and invited members can join.'); // i18n-ignore }); it('should return Korean translation for a valid key', () => { const result = t('ko', 'voice.responses.channelLocked'); - expect(result).toBe('채널이 잠겼습니다! 초대된 멤버만 참여할 수 있습니다.'); + expect(result).toBe('채널이 잠겼습니다! 초대된 멤버만 참여할 수 있습니다.'); // i18n-ignore }); it('should fallback to English when key is missing in target locale', () => {