-
Notifications
You must be signed in to change notification settings - Fork 1
[FEAT] i18n 자동화 스크립트 도입 #402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Walkthrough프로젝트에 i18n 런타임 초기화, 언어 기반 라우팅, AST 기반 한글 추출·자동 래핑 스크립트 및 번역 파일 동기화 유틸리티와 관련 스크립트·의존성이 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as npm run i18n:transform
participant Glob as glob (파일 스캐너)
participant FS as File I/O (read/write)
participant Parser as parseCode (Babel)
participant Transformer as transformAST
participant Generator as generateCode
participant TransUpdate as updateTranslationFiles
CLI->>Glob: src/**/*.tsx 조회
Glob-->>CLI: 파일 목록
loop 파일별 처리
CLI->>FS: 파일 읽기
FS-->>Parser: 코드 전달
Parser->>Transformer: AST 전달
Transformer-->>Generator: 변환된 AST + 키 집합
Generator->>FS: 변환 코드 생성/비교
FS-->>CLI: 변경된 경우만 쓰기
end
CLI->>TransUpdate: 집계된 키 전달
TransUpdate->>FS: 각 언어 translation.json 보장·읽기·쓰기
FS-->>TransUpdate: 파일 갱신 완료
TransUpdate-->>CLI: 완료
sequenceDiagram
participant User as 브라우저 사용자
participant Router as React Router
participant LWrap as LanguageWrapper
participant i18n as i18next
User->>Router: /ko/... 또는 /en/... 요청
Router->>LWrap: lang 파라미터 전달
LWrap->>i18n: 언어 유효성 검사 및 changeLanguage()
i18n-->>LWrap: 언어 변경 완료
LWrap->>Router: Outlet 렌더링
Router-->>User: 해당 언어로 렌더링된 페이지 응답
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50분
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (6)
src/routes/LanguageWrapper.tsx (1)
1-20: URL 기반 언어 동기화 흐름이 단순하고 명확합니다
useParams로:lang을 읽고, 지원 언어(ko,en)만 허용하면서 i18n과 동기화하는 구조가 깔끔합니다. 기본값을ko로 두는 것도 주석과 구현이 잘 맞습니다.선택적으로 고려해볼 부분은 있습니다:
- 타입 안정성:
useParams<{ lang?: 'ko' | 'en' }>()처럼 제네릭을 주거나,type SupportedLang = (typeof supportedLangs)[number]를 도입하면 추후 언어 추가 시 타입 레벨에서 누락을 잡기 좋습니다.- 효과 의존성:
현재useEffectdeps가[lang]만인데, 모듈 상수(supportedLangs)와 i18n 인스턴스는 변경되지 않으므로 기능상 문제는 없습니다. 다만react-hooks/exhaustive-deps규칙을 사용 중이라면 린트 경고 여부를 한 번 확인해 보셔도 좋겠습니다.전반적으로 동작에는 문제가 없고, 위 사항은 선택적인 개선 포인트입니다.
src/routes/routes.tsx (1)
16-80: 언어별 라우트 구조와 인증 래핑은 깔끔하지만, 리다이렉트 시 언어 prefix 손실 가능성을 확인해 주세요
appRoutes+protectedAppRoutes로 공통 라우트 정의 후,'/'와':lang'두 레이어에서 재사용하는 패턴은 중복을 줄이면서 언어별 라우팅을 잘 표현하고 있습니다.path: ''가 LanguageWrapper 하위에서 index 역할을 하므로,/→TableListPage,/:lang→ 언어별 기본 페이지로 동작하는 구조도 자연스럽습니다.- NotFound(
path: '*')를 공통으로 두 라우트 트리 아래에 두어/...와/:lang/...모두에 대해 404를 처리하는 점도 일관적입니다.다만 인증 흐름과 언어 prefix의 조합은 한 번 점검해 보는 것이 좋겠습니다:
- 현재
ProtectedRoute구현(별도 파일 참고)이Navigate to={'/home'}로 고정되어 있다면, 예를 들어/en/table/customize/123/end접근 시 인증 실패 →/home으로 리다이렉트되어 언어 prefix(en)가 사라집니다.- 다국어 URL 전략을 강하게 가져가려면, 현재 URL의 언어 세그먼트를 유지해
/en/home처럼 리다이렉트하는 쪽이 UX 일관성 측면에서 더 나을 수 있습니다.개선 아이디어(예시):
ProtectedRoute내부에서useParams<{ lang?: string }>()또는useLocation().pathname을 사용해 언어 세그먼트를 추출한 뒤,
Navigate to={lang ? \/${lang}/home` : '/home'}`처럼 prefix를 보존하도록 조정.- 또는 현재 라우터에서 lang 정보를 Context로 노출하고,
ProtectedRoute가 해당 Context를 사용하도록 하는 방식도 가능합니다.라우터 구조 자체는 잘 정리되어 있고, 위 사항은 언어별 URL UX 일관성을 높이기 위한 권장 리팩터입니다.
Also applies to: 89-100
scripts/utils/translationUtils.ts (1)
1-45: 번역 JSON 자동 업데이트 로직은 직관적이며, 언어 코드 관리와 구조 전략만 맞춰두면 좋겠습니다
keys가 없으면 바로 반환하고, 각 언어별로 파일 생성/로딩 후 누락된 키만 추가하는 흐름이 단순하고 안전합니다.ko는 원문을 그대로 값으로, 그 외 언어(기본 en)는 빈 문자열로 초기화하는 정책도 실제 번역 워크플로와 잘 맞습니다.updated플래그를 사용해 변경이 있을 때만writeJSON을 호출하는 점도 I/O 측면에서 효율적입니다.권장 리팩터/정책 측면에서 고려해볼 부분:
- 언어 코드의 단일 소스 관리
- 여기의
languages = ['ko', 'en'],src/i18n.ts의supportedLngs,LanguageWrapper의supportedLangs가 모두 별도 상수로 존재합니다.- 추후 언어를 추가/제거할 때 실수 가능성을 줄이려면, 공용 상수 모듈(예:
shared/i18nConfig.ts또는scripts/config/i18n.ts)로 분리해 스크립트와 런타임에서 함께 사용하는 방안을 고려해 볼 만합니다.- 중첩 키 전략 명시
- 현재 타입을
Record<string, string>으로 잡았기 때문에,"home.title"처럼 플랫 키를 쓰는 전략에 최적화되어 있습니다.- 향후 중첩 오브젝트 구조(예:
{ home: { title: '...' } })를 사용할 계획이 없다면, 주석으로 "플랫 키만 지원"을 명시해 두면 사용하는 쪽에서 오해를 줄일 수 있습니다.현재 동작에는 문제 없어 보이고, 위 내용은 유지보수성과 확장성을 높이기 위한 권장 수준의 개선 제안입니다.
scripts/i18nTransform.ts (2)
6-26: 개별 파일 오류 처리 추가를 권장합니다.현재
processFile에서 예외가 발생하면 전체 스크립트가 중단됩니다. 대량의 파일을 처리할 때 일부 파일의 오류로 인해 전체 작업이 중단되는 것을 방지하기 위해 try-catch 블록을 추가하는 것이 좋습니다.다음과 같이 개선할 수 있습니다:
async function processFile(filePath: string) { + try { console.log(`\n파일 처리 중: ${filePath}`); const originalCode = await fs.readFile(filePath, 'utf-8'); const ast = parseCode(originalCode); const koreanKeys = transformAST(ast); if (koreanKeys.size === 0) { console.log('한글 텍스트를 찾지 못했습니다.'); return; } await updateTranslationFiles(koreanKeys); const newCode = generateCode(ast); if (newCode !== originalCode) { await fs.writeFile(filePath, newCode, 'utf-8'); console.log(`파일 업데이트 완료: ${filePath}`); } else { console.log('변경 사항이 없습니다.'); } + } catch (error) { + console.error(`파일 처리 실패: ${filePath}`, error); + // 에러를 기록하고 계속 진행 + } }
37-39: 선택사항: 대량 파일 처리 시 병렬 처리 고려현재 파일들을 순차적으로 처리하고 있습니다. 파일 수가 많을 경우 성능 개선을 위해 제한된 동시성으로 병렬 처리를 고려할 수 있습니다.
예시:
// Promise.allSettled를 사용한 병렬 처리 const results = await Promise.allSettled( files.map(file => processFile(file)) ); // 실패한 파일 보고 const failed = results.filter(r => r.status === 'rejected'); if (failed.length > 0) { console.log(`\n처리 실패한 파일: ${failed.length}개`); }scripts/utils/astUtils.ts (1)
205-217: 훅 중복 체크 로직 개선 권장Line 208에서
stmt.node.declarations[0]만 확인하고 있습니다. 이론적으로 하나의VariableDeclaration에 여러 선언이 있을 수 있으므로, 모든 선언을 확인하는 것이 더 안전합니다.다음과 같이 개선할 수 있습니다:
bodyPath.get('body').forEach((stmt) => { if (stmt.isVariableDeclaration()) { - const declaration = stmt.node.declarations[0]; - if ( - declaration?.init?.type === 'CallExpression' && - t.isIdentifier(declaration.init.callee) && - declaration.init.callee.name === 'useTranslation' - ) { - hasHook = true; - } + stmt.node.declarations.forEach((declaration) => { + if ( + declaration?.init?.type === 'CallExpression' && + t.isIdentifier(declaration.init.callee) && + declaration.init.callee.name === 'useTranslation' + ) { + hasHook = true; + } + }); } });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (9)
package.json(4 hunks)scripts/i18nTransform.ts(1 hunks)scripts/utils/astUtils.ts(1 hunks)scripts/utils/fileUtils.ts(1 hunks)scripts/utils/translationUtils.ts(1 hunks)src/i18n.ts(1 hunks)src/main.tsx(1 hunks)src/routes/LanguageWrapper.tsx(1 hunks)src/routes/routes.tsx(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
scripts/utils/translationUtils.ts (1)
scripts/utils/fileUtils.ts (3)
ensureFile(16-34)readJSON(10-13)writeJSON(48-55)
scripts/i18nTransform.ts (2)
scripts/utils/astUtils.ts (3)
parseCode(16-21)transformAST(60-269)generateCode(274-280)scripts/utils/translationUtils.ts (1)
updateTranslationFiles(14-45)
src/routes/routes.tsx (2)
src/routes/ProtectedRoute.tsx (1)
ProtectedRoute(6-17)src/routes/LanguageWrapper.tsx (1)
LanguageWrapper(7-20)
🪛 Gitleaks (8.29.0)
package.json
[high] 18-18: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (13)
src/main.tsx (1)
8-8: i18n 초기화 시점 연결이 적절합니다엔트리포인트에서
./i18n을 사이드 이펙트 import로 불러와서 렌더 전에 i18n이 초기화되도록 한 구조가 자연스럽고 문제 없어 보입니다.src/i18n.ts (1)
1-31: i18n 설정이 전체 구조와 잘 맞지만, Suspense 및 감지 전략은 한 번 점검해 보세요
supportedLngs: ['ko', 'en'],fallbackLng: 'ko',backend.loadPath: '/locales/{{lng}}/translation.json'구성이public/locales/{lng}/translation.json구조 및updateTranslationFiles와 잘 맞아서 런타임/스크립트가 일관되게 동작할 것으로 보입니다.detection.order: ['path', 'localStorage', 'navigator']와LanguageWrapper의:lang처리(지원 언어만 허용, 없으면 ko 기본값)가 서로 보완 관계에 있어 설계 방향도 자연스럽습니다.다만 아래 두 가지는 실제 앱 구조와 맞는지 한 번만 더 확인해 주세요:
- React Suspense 사용 여부
react: { useSuspense: true }설정이면 번역 로딩 중에useTranslation이 Suspense를 던지므로, 상위 트리(예:RouterProvider바깥 또는 App 루트)에<Suspense fallback={...}>가 존재해야 합니다. 기존에 Suspense를 사용하지 않았다면, 어디에 boundary를 둘지 결정이 필요합니다.- path 기반 언어 감지 + 라우팅 조합
현재는LanguageDetector의 path 감지와LanguageWrapper의 수동changeLanguage호출이 함께 동작할 수 있습니다. 실제로는 둘 다 같은 언어로 맞춰 줄 가능성이 높지만, 초기 진입 시 어떤 순서로 언어가 결정되는지(특히/vs/:lang접근 시)를 한 번 디버깅/로그 확인해 두면 향후 디버깅에 도움이 됩니다.전체 설정 자체는 합리적으로 보이며, 위 사항은 런타임 환경과의 정합성 검증 관점에서의 확인 요청입니다.
scripts/i18nTransform.ts (1)
1-4: LGTM!import 경로에
.ts확장자를 포함한 것은 ts-node로 직접 실행하기 위한 올바른 접근입니다.scripts/utils/fileUtils.ts (2)
5-13: LGTM!파일 읽기 유틸리티 함수들이 깔끔하게 구현되었습니다.
48-55: LGTM!JSON 직렬화와 파일 쓰기가 적절하게 구현되었습니다. 들여쓰기 2칸으로 가독성도 좋습니다.
scripts/utils/astUtils.ts (6)
1-9: LGTM!Babel 패키지의 CommonJS/ESM interop 문제를 해결하기 위한 타입 캐스팅이 적절히 적용되었습니다.
16-21: LGTM!React TypeScript 파일 파싱을 위한 Babel 설정이 적절합니다.
72-184: LGTM!한글 문자열 탐색 및 수집 로직이 잘 구현되었습니다. 특히:
- 이미
t()로 감싸진 문자열 중복 처리 방지 (lines 78-83, 104-109)- Import/Export 문 및 객체 키 제외 (lines 96-100)
- 템플릿 리터럴의 placeholder 이름 충돌 해결 (lines 153-161)
186-198: LGTM!useTranslation import 추가 로직이 적절합니다. 한글 키가 있고 기존 import가 없을 때만 추가합니다.
232-266: LGTM!문자열 변환 로직이 올바르게 구현되었습니다:
- 템플릿 리터럴의 interpolation 객체 처리 (lines 233-246)
- JSX 컨텍스트에서
JSXExpressionContainer로 적절히 래핑 (lines 257-261)
274-280: LGTM!코드 생성 설정이 적절합니다.
retainLines: true로 원본 구조를 유지하고,minimal: true로 가독성을 보장합니다.package.json (2)
19-19: LGTM!i18n 변환 스크립트가 명확한 이름으로 적절히 추가되었습니다.
27-35: 의존성 버전 업데이트 및 react-i18next 메이저 버전 검토 필요합니다.현재 설정된 i18n 라이브러리와 개발 도구 버전을 확인한 결과:
- i18next: ^25.5.2 → 최신 25.6.3 업데이트 가능 (마이너 버전, 안전)
- react-i18next: ^15.7.3 → 최신 16.3.5 (메이저 버전 차이) — 호환성 검토 필수
- i18next-browser-languagedetector, i18next-http-backend: 최신 버전과 일치
- 보안: 현재 버전들은 알려진 취약점 없음 (구버전 CVE 제외)
조치 사항:
- i18next를 25.6.3으로 마이너 업데이트 검토
- react-i18next 메이저 버전 업그레이드 전 변경 사항 검토 필수 (breaking changes 확인)
- 기타 Babel, glob, ts-node 패키지도 필요시 업데이트 검토
| "build-storybook": "storybook build", | ||
| "test": "vitest", | ||
| "chromatic": "npx chromatic --project-token=chpt_a6dc39eba6488b2" | ||
| "chromatic": "npx chromatic --project-token=chpt_a6dc39eba6488b2", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chromatic 토큰을 환경 변수로 이동하세요.
Line 18에 Chromatic 프로젝트 토큰이 평문으로 노출되어 있습니다. 버전 관리 시스템에 토큰을 커밋하면 보안 위험이 발생할 수 있습니다.
다음과 같이 환경 변수를 사용하도록 수정하세요:
- "chromatic": "npx chromatic --project-token=chpt_a6dc39eba6488b2",
+ "chromatic": "npx chromatic",그리고 프로젝트 루트에 .env 파일을 생성하여 토큰을 저장하세요:
CHROMATIC_PROJECT_TOKEN=chpt_a6dc39eba6488b2
.env 파일은 .gitignore에 추가되어야 합니다.
또는 Chromatic CLI가 CHROMATIC_PROJECT_TOKEN 환경 변수를 자동으로 인식하므로 CI/CD 환경에 환경 변수로 설정할 수 있습니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "chromatic": "npx chromatic --project-token=chpt_a6dc39eba6488b2", | |
| "chromatic": "npx chromatic", |
🧰 Tools
🪛 Gitleaks (8.29.0)
[high] 18-18: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🤖 Prompt for AI Agents
In package.json around line 18, the Chromatic project token is hard-coded;
update the npm script to call chromatic without the literal token so it reads
the token from the CHROMATIC_PROJECT_TOKEN environment variable instead. Remove
the plain token from the repo, add a .env file at project root containing
CHROMATIC_PROJECT_TOKEN=your_token (and add .env to .gitignore), and ensure
CI/CD sets CHROMATIC_PROJECT_TOKEN in its environment variables (or export it
locally) so the Chromatic CLI picks it up automatically.
| function isReactComponentFunction(path: NodePath): boolean { | ||
| // 함수 선언문 | ||
| if (path.isFunctionDeclaration()) { | ||
| return path.node.id?.name?.[0] === path.node.id?.name?.[0]?.toUpperCase(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
함수 선언문 컴포넌트 판별 로직 오류를 수정하세요.
Line 29의 조건문 path.node.id?.name?.[0] === path.node.id?.name?.[0]?.toUpperCase()는 첫 글자를 자기 자신과 비교하여 항상 true를 반환합니다. 이는 React 컴포넌트가 아닌 일반 함수도 컴포넌트로 잘못 인식할 수 있습니다.
다음과 같이 수정하세요:
// 함수 선언문
if (path.isFunctionDeclaration()) {
- return path.node.id?.name?.[0] === path.node.id?.name?.[0]?.toUpperCase();
+ return /^[A-Z]/.test(path.node.id?.name || '');
}또는:
- return path.node.id?.name?.[0] === path.node.id?.name?.[0]?.toUpperCase();
+ const name = path.node.id?.name;
+ return name ? name[0] === name[0].toUpperCase() : false;🤖 Prompt for AI Agents
In scripts/utils/astUtils.ts around lines 26 to 30, the function-declaration
check compares the first character to itself which is always true; replace that
condition by first extracting the identifier name (e.g., const name =
path.node.id?.name), ensure it exists, then compare its first character to its
uppercase form (e.g., const first = name[0]; return first ===
first.toUpperCase() && first !== first.toLowerCase()) so only identifiers
starting with an uppercase letter (actual alphabetic characters) are treated as
React components.
| console.log(`[writeFile] 파일 쓰기 시작: ${filePath}`); | ||
| const dir = path.dirname(filePath); | ||
|
|
||
| await fsp.mkdir(dir, { recursive: true }).catch(() => {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
에러 처리를 개선하세요.
현재 mkdir의 모든 에러를 무시하고 있습니다. 디렉토리가 이미 존재하는 경우(EEXIST)는 무시해도 되지만, 권한 문제나 디스크 공간 부족 등의 다른 에러도 무시되어 문제를 진단하기 어려울 수 있습니다.
다음과 같이 개선을 제안합니다:
- await fsp.mkdir(dir, { recursive: true }).catch(() => {});
+ await fsp.mkdir(dir, { recursive: true }).catch((err) => {
+ if (err.code !== 'EEXIST') {
+ console.warn(`디렉토리 생성 경고: ${dir}`, err.message);
+ }
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await fsp.mkdir(dir, { recursive: true }).catch(() => {}); | |
| await fsp.mkdir(dir, { recursive: true }).catch((err) => { | |
| if (err.code !== 'EEXIST') { | |
| console.warn(`디렉토리 생성 경고: ${dir}`, err.message); | |
| } | |
| }); |
🤖 Prompt for AI Agents
In scripts/utils/fileUtils.ts around line 41, the current await fsp.mkdir(dir, {
recursive: true }).catch(() => {}); swallows all errors; change it to catch the
error and only ignore the "directory exists" case while rethrowing or logging
other errors: await the mkdir inside a try/catch, and in the catch check
err.code === 'EEXIST' (or err && err.code === 'EEXIST') to ignore that specific
case, otherwise rethrow the error (or log and throw) so permission/disk errors
surface.
jaeml06
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i-meant-to-be
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
스크립트 제외 코드 변경 사항 자체는 많지 않기도 하고 딱히 변경할 것은 없어 보입니다!
다만 저도 치코랑 동일한 문제가 발생해요. 저는 npm i로 썬데이가 새로 추가한 패키지 설치하고 npm run i118:transform 실행했습니다.
|
제 환경에서는 잘 동작이 됐는데 왜 이런 문제가 발생하나 찾아보았습니다. 저희 프론트 프로젝트 설정은 하지만 ts-node는 CJS 기반으로 설계된 실행기라 ESM 환경 지원이 완벽하지 않다고 합니다. 따라서 ts-node가 모든 모듈을 제대로 잡아 트랜스파일하지 못해 일부 .ts 파일이 Node ESM 로더로 넘어가 Node에서 .ts 확장자를 실행할 수 없기 때문에 TypeError: Unkown file extension ".ts"에러가 발생합니다!! 따라서 스크립트 실행기를 ts-node를 유지하고 tsconfig에 설정을 더 넣는 것보다 .ts 파일을 EMS/TS 환경에 더 적합한 tsx 실행기로 변경하는 것이 더 좋겠다고 판단하여 수정했습니다. 혹시이 ,, 이제는 제대로 실행이 되는지 한번 확인 부탁드립니다 !!!!!! |
jaeml06
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 변환되는 것 확인했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/routes/routes.tsx(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/routes/routes.tsx (2)
src/routes/ProtectedRoute.tsx (1)
ProtectedRoute(6-17)src/routes/LanguageWrapper.tsx (1)
LanguageWrapper(7-20)
🔇 Additional comments (1)
src/routes/routes.tsx (1)
22-93: 라우트 구성이 잘 정리되었습니다.상대 경로로의 전환과
requiresAuth플래그를 통한 인증 요구사항 명시가 깔끔합니다.path: ''를 사용한 인덱스 라우트 정의도 적절합니다.
| // 인증 보호 로직을 적용한 라우트 | ||
| const protectedAppRoutes = appRoutes.map((route) => ({ | ||
| ...route, | ||
| element: route.requiresAuth ? ( | ||
| <ProtectedRoute>{route.element}</ProtectedRoute> | ||
| ) : ( | ||
| route.element | ||
| ), | ||
| })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "ProtectedRoute.tsx" -o -name "ProtectedRoute.ts" -o -name "ProtectedRoute.jsx"Repository: debate-timer/debate-timer-fe
Length of output: 103
🏁 Script executed:
cat -n ./src/routes/ProtectedRoute.tsxRepository: debate-timer/debate-timer-fe
Length of output: 655
🏁 Script executed:
# Verify the router structure to understand how language routing works
sed -n '105,126p' src/routes/routes.tsxRepository: debate-timer/debate-timer-fe
Length of output: 506
ProtectedRoute의 리다이렉트 경로가 언어 라우팅 구조와 호환되지 않습니다.
ProtectedRoute가 인증되지 않은 사용자를 절대 경로 '/home'으로 리다이렉트하고 있습니다. 현재 라우팅 구조에서 보호된 라우트는 /:lang 패턴 하에 중첩되어 있으므로, 사용자가 /en/composition에 있을 때 인증 실패로 /home으로 리다이렉트되면 언어 컨텍스트가 손실됩니다.
ProtectedRoute에서 useLocation()으로 현재 경로에서 언어를 추출하고, 해당 언어로 리다이렉트 URL을 구성하도록 수정하세요. 예: 현재 경로가 /en/...이면 /:lang/home로 리다이렉트.
🤖 Prompt for AI Agents
In src/routes/routes.tsx around lines 95–103, ProtectedRoute currently redirects
unauthenticated users to the absolute path '/home', which loses the current
language segment (/:lang) for nested routes; update ProtectedRoute to read the
current location via useLocation(), parse the leading language segment (e.g.,
from pathname.match(/^\/([^/]+)/)), build the redirect path by prefixing that
lang to '/home' (fall back to a default lang if none), and redirect to
`/${lang}/home` instead of '/home' so language context is preserved.
| children: [ | ||
| { | ||
| path: '/', | ||
| element: <LanguageWrapper />, | ||
| children: protectedAppRoutes, // 기본 언어(ko) 라우트 | ||
| }, | ||
| { | ||
| path: ':lang', // 다른 언어 라우트 | ||
| element: <LanguageWrapper />, | ||
| children: protectedAppRoutes, | ||
| }, | ||
| ], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:lang 동적 세그먼트가 실제 라우트 경로와 충돌할 수 있습니다.
:lang 동적 파라미터는 모든 단일 경로 세그먼트와 매칭됩니다. 예를 들어 /home 접근 시:
'/'라우트의 자식'home'으로 매칭될 수 있고':lang'라우트에서lang='home'으로 매칭될 수도 있습니다
React Router v7에서는 정적 세그먼트가 동적 세그먼트보다 우선하지만, 의도치 않은 동작이 발생할 수 있습니다. 지원하는 언어 코드를 명시적으로 제한하는 것이 더 안전합니다.
children: [
{
path: '/',
element: <LanguageWrapper />,
children: protectedAppRoutes, // 기본 언어(ko) 라우트
},
{
- path: ':lang', // 다른 언어 라우트
+ path: ':lang(en|zh)', // 지원하는 언어만 명시적으로 매칭
element: <LanguageWrapper />,
children: protectedAppRoutes,
},
],또는 별도의 정적 경로로 분리하는 방안을 고려해 주세요:
+ {
+ path: 'en',
+ element: <LanguageWrapper />,
+ children: protectedAppRoutes,
+ },📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| children: [ | |
| { | |
| path: '/', | |
| element: <LanguageWrapper />, | |
| children: protectedAppRoutes, // 기본 언어(ko) 라우트 | |
| }, | |
| { | |
| path: ':lang', // 다른 언어 라우트 | |
| element: <LanguageWrapper />, | |
| children: protectedAppRoutes, | |
| }, | |
| ], | |
| children: [ | |
| { | |
| path: '/', | |
| element: <LanguageWrapper />, | |
| children: protectedAppRoutes, // 기본 언어(ko) 라우트 | |
| }, | |
| { | |
| path: ':lang(en|zh)', // 지원하는 언어만 명시적으로 매칭 | |
| element: <LanguageWrapper />, | |
| children: protectedAppRoutes, | |
| }, | |
| ], |
i-meant-to-be
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
잘 동작하는 거 확인했습니다! 승인 남겨요. 고생하셨어요!

🚩 연관 이슈
closed #382
📝 작업 내용
🚀 배경
GA 분석 결과 영미권, 중화권의 접속이 많아 서비스의 언어로 영어, 중국어를 추가로 지원하기 위한 i18n 작업을 진행하기로 했습니다.
작업을 진행 중 수많은 한글 텍스트를 t()로 감싸고, 훅과 import문을 선언하고, 번역 키를 언어마다 추가하고 수정하는 비효율성과 과정에서 발생하는 휴먼 에러를 방지 및 해결하고자 자동화 스크립트를 만들었습니다!! 추가로 i18n의 초기세팅도 함께 진행하였습니다.
✨ 주요 기능
t()함수로 자동 변환interpolation문법에 맞게 자동 변환 interpolation 참고자료useTranslation훅 및react-i18nextimport 구문 자동 주입public/locales/*/translation.json파일에 자동 추가🗂️ 파일별 동작 원리
스크립트는 여러 파일과 각 단계별로 구성되어 있습니다.
1.
scripts/i18nTransform.ts이 파일은 전체 변환 프로세스를 시작하고 조율합니다.
glob을 사용하여src폴더 내의 모든.tsx파일을 찾아 목록을 만듭니다.processFile함수에 전달합니다.processFile함수:astUtils.ts의parseCode를 호출하여 코드를 AST로 변환합니다.astUtils.ts의transformAST를 호출하여 AST를 분석하고 수정합니다.translationUtils.ts의updateTranslationFiles를 호출하여translation.json파일을 업데이트합니다.astUtils.ts의generateCode를 호출하여 수정된 AST를 다시 코드 문자열로 변환합니다.2.
scripts/utils/astUtils.ts이 파일은 실제 코드 분석과 변환의 모든 핵심 로직을 담당합니다.
parseCode(code)함수:isReactComponentFunction(path)함수:transformAST(ast)함수:traverse를 사용하여 AST를 순회합니다.StringLiteral,JSXText,TemplateLiteral노드를 만날 때마다, React 컴포넌트 내부에 있는지 확인합니다.simpleStringsToTransform(템플릿 리터럴이 아닌 문자열) 또는templateLiteralsToTransform(템플릿 리터럴인 문자열) 목록에 저장합니다.koreanKeys에, 훅을 주입할 컴포넌트는componentsToModify에 기록합니다.useTranslation훅과 import 구문을 코드 상단에 삽입합니다. (이미 삽입되어 있는 경우는 삽입하지 않습니다.)templateLiteralsToTransform와simpleStringsToTransform목록을 순회하며, 저장된 위치의 노드들만t()함수 호출로 변환합니다.generateCode(ast)함수:3.
scripts/utils/translationUtils.ts수집된 번역 키를 관리하고 파일에 기록하는 역할을 합니다. 또한 작업 과정을 로그로 보여줍니다.
updateTranslationFiles(keys)함수:public/locales/ko/translation.json과en/translation.json파일을 읽습니다.keys목록을 순회하며, JSON 파일에 아직 존재하지 않는 키만 추가합니다. (ko는 원문,en은 빈 문자열로 초기화)4.
scripts/utils/fileUtils.ts파일 시스템에 접근하여 파일을 읽고 쓰는 기본적인 입출력 작업을 처리하는 헬퍼 함수들의 모음입니다.
readFile(filePath)/writeFile(filePath, data)함수:readJSON(filePath)/writeJSON(filePath, data)함수:readJSON은 파일을 읽고 그 내용을 자바스크립트 객체로 파싱하며,updateTranslationFiles에서 기존 번역 파일을 읽을 때 사용됩니다.writeJSON은 자바스크립트 객체를 JSON 문자열로 변환하여 파일에 저장하며,updateTranslationFiles에서 새로운 키가 추가된 번역 객체를 저장할 때 사용됩니다.ensureFile(filePath)함수:🤔 자동화에서 제외된 기능
추가로 구현하고 싶다고 공유했던 내용 중 구현을 해보고 자동화에서 제외한 기능들입니다.
컴포넌트 외부 상수/props 기본값 번역
t()함수 사용이 불가능하며,i18n.t()는 실시간 언어 변경에 반응하지 않는 문제가 있었습니다.숫자/문자 추출

-30초, +30초 등 각각 키로 추출되는 것을 한글인 '초'만 추출하고 싶었습니다. 하지만 아래의 이유들로 제외하였습니다.
📖 사용법
아래 명령어를 통해 스크립트를 실행할 수 있습니다!
추후에 자동화 스크립트가 완성되면 커밋 전 자동으로 실행되도록 하는 방안까지 고려해 보겠습니다!!
🏞️ 스크린샷 (선택)
🗣️ 리뷰 요구사항 (선택)
추가로 고려해야할 부분이 있거나 궁금하신 점 댓글이나 디스코드로 편하게 남겨주시면 바로 달려갑니다 = 33
자동화에 이것저것 기능을 넣어 설명이 길어졌는데 열심히 읽어주셔서 감사합니다 🥹
Summary by CodeRabbit
새로운 기능
Chore
✏️ Tip: You can customize this high-level summary in your review settings.