feat(HK-173): i18n 초기 설정 및 언어 변경 모달 기능 추가#76
Conversation
Walkthrough이 PR은 i18next 기반의 국제화(i18n) 기능을 도입합니다. 패키지 의존성에 i18next, i18next-browser-languagedetector, react-i18next를 추가하고, 한/영/중 세 언어의 번역 파일을 추가했습니다. 헤더 컴포넌트에 언어 선택 UI와 모달을 구현하고, useModal 훅에 toggleModal 함수를 추가했습니다. 기존 콘텐츠 컴포넌트들을 i18n과 연동하여 하드코딩된 텍스트를 useTranslation 훅 기반으로 변경했습니다. Estimated code review effort🎯 3 (Moderate) | ⏱️ ~35-45분 추가 검토 필요 영역:
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
|
Caution Docstrings generation - FAILED No docstrings were generated. |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (6)
src/locales/zh/common.json (1)
1-5: P4: 중국어 번역 검증을 고려해주세요.JSON 구조와 키 네이밍은 정확합니다. 다만, 번역의 정확성과 자연스러움을 중국어 원어민에게 검토받는 것을 권장드립니다.
참고:
- 현재 번역: "HUSK 是一项基于网页的终端服务。用户可以通过终端使用各种功能。"
- 기술적인 용어와 서비스 설명이 자연스러운지 확인이 필요합니다
src/i18n/index.ts (1)
9-26: Pn4: LanguageDetector와 Lang 타입을 맞추기 위한supportedLngs설정 제안현재
resources에는'ko' | 'en' | 'zh'만 정의되어 있는데,i18next-browser-languagedetector는 기본 설정에서en-US,ko-KR같은 locale 코드를 반환할 수 있습니다. 이 경우resolvedLanguage값과 실제 사용 가능한 리소스 키가 달라져서, 모달에서 관리하는Lang타입('ko' | 'en' | 'zh')과 미묘하게 어긋날 수 있습니다.
- 문제점
- Detector가
en-US를 반환하면, 내부적으로는en리소스를 쓰더라도resolvedLanguage는en-US가 되어 UI 레벨에서 언어 하이라이트 상태가 어색해질 수 있습니다.- 개선 방안
supportedLngs를 명시해서 Detector가 리소스에 정의된 언어 코드만 사용하도록 제한하면, 모달의Lang타입과도 자연스럽게 일관성이 맞습니다.- (선택)
debug는 주석 처리하신 것처럼NODE_ENV기반 조건부 사용을 유지하면 좋습니다.예시:
i18n .use(LanguageDetector) .use(initReactI18next) .init({ resources, fallbackLng: 'ko', + supportedLngs: ['ko', 'en', 'zh'], interpolation: { escapeValue: false, }, - // debug: process.env.NODE_ENV === 'development', - debug: false, + debug: process.env.NODE_ENV === 'development', });이 패턴은 i18next 공식 문서의 LanguageDetector 설정 예시(
supportedLngs,fallbackLng)와도 일치하는 구조입니다.src/components/header/index.tsx (1)
32-35: Pn5:onclickFunction없이toggleModal직접 전달로 간결화 가능
onclickFunction은 단순히toggleModal을 한 번 감싸서 호출만 하고 있어, 중간 래퍼 함수 없이 바로 이벤트 핸들러로 넘겨도 됩니다.
- 현재:
const { isOpen, toggleModal, closeModal } = useModal(); const onclickFunction = () => { toggleModal(); }; // ... <LanguageButton onClick={onclickFunction} />
- 제안:
const { isOpen, toggleModal, closeModal } = useModal(); // ... <LanguageButton onClick={toggleModal} />이렇게 하면 불필요한 클로저 생성을 줄이고, React 공식 문서의 “이벤트 핸들러에 함수 참조를 직접 전달하는 패턴”과도 더 잘 맞습니다. (물론 현재 코드도 기능적으로는 문제 없습니다.)
src/components/header/modal/index.tsx (2)
15-27: Pn2:Lang타입과i18n.resolvedLanguage값 간 정합성 및 상태 중복 개선 제안지금 구조에서는 초기 선택 언어 계산 시 약간 헷갈릴 수 있는 부분이 있습니다.
문제점
i18n.resolvedLanguage는en-US,ko-KR처럼 region 코드까지 포함된 값을 반환할 수 있습니다. 그런데 이를 그대로as Lang으로 캐스팅하면, 실제 값은'en-US'인데 타입만'en'으로 간주하게 되어,selectedLanguage === 'en'비교가 항상 false가 되고 활성 하이라이트가 안 보일 수 있습니다.- 언어 상태를
i18n이 이미 관리하고 있는데, 컴포넌트에서selectedLanguage를 별도 state로 다시 관리하여 소스 오브 트루스가 두 군데로 나뉩니다.changeLanguage이후i18n쪽과 로컬 state가 잠깐이라도 어긋날 여지가 있습니다.개선 방안 1: 언어 코드 정규화 유틸 추가
Detector가 반환하는 값을ko/en/zh로 정규화해서 사용하면 일관성이 좋아집니다.type Lang = 'ko' | 'en' | 'zh'; const normalizeLang = (lng?: string): Lang => { const base = (lng ?? 'ko').split('-')[0]; // en-US -> en if (base === 'ko' || base === 'en' || base === 'zh') return base; return 'ko'; }; const LanguageModal = ({ onClose }: LanguageModalProps) => { const { i18n } = useTranslation(); const [selectedLanguage, setSelectedLanguage] = useState<Lang>(() => normalizeLang(i18n.resolvedLanguage)); useEffect(() => { setSelectedLanguage(normalizeLang(i18n.resolvedLanguage)); }, [i18n.resolvedLanguage]); const selectLanguage = (lang: Lang) => { i18n.changeLanguage(lang); onClose(); }; // ... };
- 개선 방안 2: 로컬 state 제거 (더 단순한 패턴)
react-i18next 공식 문서에서도i18n.language/resolvedLanguage를 바로 UI에 사용하는 패턴을 많이 보여줍니다. 하이라이트만 필요하다면 state 없이도 가능합니다.const LanguageModal = ({ onClose }: LanguageModalProps) => { const { i18n } = useTranslation(); const selectedLanguage = normalizeLang(i18n.resolvedLanguage); const selectLanguage = (lang: Lang) => { i18n.changeLanguage(lang); onClose(); }; // selectedLanguage를 그대로 $active 비교에 사용 };
- 장점
- Detector가
en-US등을 반환하는 경우에도 항상ko/en/zh중 하나로 매핑되어, 버튼 활성 상태와 실제 언어 설정이 일치합니다.- 언어 상태의 소스 오브 트루스를
i18n한 곳으로 통일해 유지보수가 쉬워집니다.- React Hooks 의존성 배열도
i18n.resolvedLanguage만 정확히 바라보게 되어, 공식 문서에서 권장하는 “의존성에 사용한 값을 그대로 넣는 패턴”과 잘 맞습니다.
29-54: Pn4:createPortal(document.body)사용 시 SSR/테스트 환경 고려 (선택 사항)현재는 브라우저 환경만 가정하고
document.body에 바로 포탈을 생성하고 있습니다. 앱이 CSR 전용이라면 문제 없지만, 나중에:
- Next.js나 Remix 등 SSR 환경으로 옮기거나
- Jest/RTL 테스트에서
document가 완전히 세팅되지 않은 상태로 이 컴포넌트를 렌더링하는 상황이 생기면
document is not defined에러가 날 수 있습니다.선택적으로 아래와 같이 가드 코드를 두어도 좋겠습니다.
if (typeof document === 'undefined') { return null; } return createPortal( // ... document.body, );이 패턴은 ReactDOM
createPortal공식 문서에서도, 브라우저 객체가 없는 환경에서의 사용 시 주의사항과 같은 맥락으로 자주 언급되는 방식입니다. 지금 당장 필수는 아니지만, 장기적으로 SSR 가능성을 염두에 두고 있다면 참고해 주세요.src/components/header/modal/index.style.tsx (1)
4-65: Pn5: 모달 스타일 전반은 괜찮고, 오버레이 배경만 향후 UX에 맞춰 조정 가능구조적으로는 잘 잡혀 있습니다.
ModalOverlay와ModalContainer의position: fixed+z-index레이어링도 적절하고,- 언어 리스트 버튼의
$active상태 표현도 깔끔합니다.향후 UX 측면에서 한 가지만 선택적으로 고려해볼 수 있습니다.
- 현재
ModalOverlay의background: transparent;라서, 클릭 영역을 막는 용도로만 동작하고 시각적인 “모달이 떴다”는 느낌은 약합니다.- Material Design / Fluent UI 등 모달 가이드를 보면 일반적으로
rgba(0,0,0,0.2~0.4)정도로 배경을 dimming 해서 포커스를 모달로 모아주는 패턴을 많이 사용합니다.예를 들면:
-export const ModalOverlay = styled.div` +export const ModalOverlay = styled.div` position: fixed; inset: 0; - background: transparent; + background: rgba(0, 0, 0, 0.2); z-index: 9998; `;지금도 기능적으로 문제는 없고, 디자인 방향에 따라 선택적으로 적용하시면 될 것 같습니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
package-lock.jsonis excluded by!**/package-lock.json,!**/package-lock.jsonsrc/assets/Language.svgis excluded by!**/*.svg
📒 Files selected for processing (12)
package.json(1 hunks)src/App.tsx(1 hunks)src/components/header/index.style.tsx(2 hunks)src/components/header/index.tsx(2 hunks)src/components/header/modal/index.style.tsx(1 hunks)src/components/header/modal/index.tsx(1 hunks)src/hooks/useModal/index.tsx(1 hunks)src/i18n/index.ts(1 hunks)src/locales/en/common.json(1 hunks)src/locales/ko/common.json(1 hunks)src/locales/zh/common.json(1 hunks)src/pages/main/content/index.tsx(2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/pages/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (README.md)
Store routable page components (homepage, login page, profile page, etc.) in the
pagesdirectory as top-level components for each route
Files:
src/pages/main/content/index.tsx
src/**/*.{ts,tsx}
⚙️ CodeRabbit configuration file
src/**/*.{ts,tsx}: 1. 코드에서의 오타, 불필요한 로직, 중복 코드, 서비스/도메인 설계, 예외 처리를 확인해주세요. 2. 많은 양의 코드리뷰는 피로감을 줄 수 있으므로 꼭 필요한 부분에만 집중해주고, 나머지는 캡션으로 설명해주세요. 3. Pn룰을 사용합니다. 모든 코멘트에는Pn:을 붙여주세요. 중요도에 따라 숫자를 반영해주세요. (P1: 꼭 반영해주세요 (Request changes) / P2: 적극적으로 고려해주세요 (Request changes) / P3: 웬만하면 반영해 주세요 (Comment) / P4: 반영해도 좋고 넘어가도 좋습니다 (Approve) / P5: 그냥 사소한 의견입니다 (Approve)) 4. 코드에 대한 리뷰를 남길 때는 해당 라인 범위에 코멘트를 작성해주세요. 5. 리뷰 포인트별로 문제점, 대안, 장단점을 논리적이고 명확하게 제시하고 경우에 따라 단순하지만 효율적인 예시 코드도 추가해주세요. 6. 타입, 컴포넌트, 클래스명은 PascalCase로 작성합니다. 7. 멤버, 함수명은 camelCase로 작성합니다. 8. React + TypeScript 코드의 경우, 불필요한 리렌더링, 훅 의존성 배열, 과도한 any 사용 여부도 함께 확인해주세요.
Files:
src/pages/main/content/index.tsxsrc/components/header/modal/index.tsxsrc/hooks/useModal/index.tsxsrc/components/header/index.style.tsxsrc/components/header/index.tsxsrc/i18n/index.tssrc/components/header/modal/index.style.tsxsrc/App.tsx
src/components/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (README.md)
Store reusable UI components (buttons, input fields, cards, etc.) in the
componentsdirectory
Files:
src/components/header/modal/index.tsxsrc/components/header/index.style.tsxsrc/components/header/index.tsxsrc/components/header/modal/index.style.tsx
src/hooks/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (README.md)
Store custom React hooks for state management and side effect handling in the
hooksdirectory
Files:
src/hooks/useModal/index.tsx
🧬 Code graph analysis (3)
src/pages/main/content/index.tsx (1)
src/pages/main/content/index.style.tsx (1)
Description(42-48)
src/components/header/modal/index.tsx (1)
src/components/header/modal/index.style.tsx (5)
ModalOverlay(4-9)ModalContainer(11-35)LanguageList(37-41)LanguageListItem(43-45)LanguageOptionButton(47-65)
src/components/header/index.tsx (3)
src/api/context/auth-context/index.tsx (1)
AuthContext(12-12)src/components/header/index.style.tsx (5)
HeaderWrapper(5-19)LogoWrapper(82-88)Logo(21-33)LanguageWrapper(90-93)LanguageButton(43-54)src/components/dashboard/layout/dashboard-header/index.style.tsx (3)
HeaderWrapper(5-19)LogoWrapper(21-27)Logo(29-40)
🔇 Additional comments (3)
src/hooks/useModal/index.tsx (1)
8-10: P5: toggleModal 추가가 깔끔합니다!모달 토글 기능을 추가하신 것이 좋습니다. 기존
openModal,closeModal과 일관된 패턴으로 구현되었고,useCallback과 이전 상태 패턴(prev) => !prev을 사용하여 최적화도 잘 되어 있습니다.src/components/header/index.style.tsx (1)
43-54: P5: 언어 선택 UI 스타일링이 깔끔합니다!
LanguageButton과LanguageWrapper의 구현이 잘 되었습니다.좋은 점:
LanguageButton: 브라우저 기본 스타일을 제대로 리셋했습니다 (background, border, outline, padding)LanguageWrapper: relative positioning으로 내부 모달의 absolute positioning을 지원합니다- 기존 코드 스타일과 일관성을 유지했습니다
참고:
margin-right: 20px로 적절한 간격을 확보했습니다- focus outline 제거 시 키보드 접근성을 위한 대체 포커스 표시를 고려해주세요 (모달 오픈 시 포커스 트랩 등)
Also applies to: 90-93
src/App.tsx (1)
20-20: The import and i18n initialization are correctly configured.The
'./i18n'side-effect import in App.tsx is the proper pattern. Thesrc/i18n/index.tsfile exists and includes all necessary configurations:initReactI18nextintegration,LanguageDetectorsetup, proper resource definitions for ko/en/zh languages,fallbackLngset to 'ko', and correct interpolation settings. No changes needed.
|
This pull request is automatically being deployed by Amplify Hosting (learn more). |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (10)
src/components/header/index.style.tsx (1)
43-54: P4: 스타일링이 기능적으로 적절하나, 디자인 토큰 활용을 고려해보세요.
LanguageButton의 기본 버튼 리셋 스타일링은 잘 작성되었습니다. 다만 몇 가지 개선 제안을 드립니다:현재 구현의 장점:
- 불필요한 기본 스타일 제거가 깔끔합니다
cursor: pointer로 상호작용 명확개선 제안:
export const LanguageButton = styled.button` background: none; border: none; outline: none; cursor: pointer; padding: 0; margin-right: ${({ theme }) => theme.spacing.md || '20px'}; // 테마 시스템 활용 &:focus-visible { // 접근성 개선 outline: 2px solid ${({ theme }) => theme.colors.focus || '#4A90E2'}; outline-offset: 2px; } `;이유:
- 테마/디자인 토큰: 하드코딩된
20px대신 테마 시스템을 활용하면 일관성 유지가 쉽습니다- 접근성:
:focus-visible을 사용하면 키보드 사용자에게 포커스 표시를 제공하면서 마우스 클릭 시 outline은 표시하지 않습니다참고 문서:
현재 구조로도 충분히 동작하므로 선택적으로 반영하시면 됩니다.
src/pages/main/content/index.tsx (1)
35-60: P3: i18n 통합이 잘 되었으나, 메인 타이틀도 국제화를 고려해보세요.이전 리뷰에서 지적된 하드코딩 텍스트들이 모두
t()함수로 대체되어 완전한 다국어 지원이 구현되었습니다. 훌륭합니다! 👍확인된 변경사항:
- ✅ Line 35:
t('home.description')- ✅ Line 39:
t('home.feature_button')- ✅ Line 43:
t('home.start_button')- ✅ Line 52-53: KeyChain 섹션 국제화
- ✅ Line 59-60: SSH 섹션 국제화
추가 고려사항:
Line 33의"HUSK: Help Use Shell Kindly"타이틀이 여전히 하드코딩되어 있습니다.<Title>HUSK: Help Use Shell Kindly</Title>질문:
- 이것이 브랜드 네임으로 모든 언어에서 영문 유지가 의도된 것인가요?
- 아니면 각 언어별로 번역이 필요한가요?
만약 번역이 필요하다면:
// locale 파일에 추가 { "home": { "title": "HUSK: Help Use Shell Kindly", // en "title": "HUSK: 쉽고 친절한 쉘 사용", // ko "title": "HUSK: 帮助友好使用Shell", // zh ... } } // 컴포넌트에서 사용 <Title>{t('home.title')}</Title>만약 영문 유지가 의도라면:
주석으로 명시하면 향후 유지보수 시 혼란을 방지할 수 있습니다:{/* Brand name - intentionally kept in English across all languages */} <Title>HUSK: Help Use Shell Kindly</Title>현재 구현도 충분히 훌륭하므로, 이는 제품 기획에 따라 결정하시면 됩니다.
src/i18n/index.ts (1)
15-26: P4: i18n 초기화 설정이 견고하나, LanguageDetector 옵션 명시를 고려해보세요.i18n 초기화 설정이 전반적으로 잘 되어 있습니다.
잘된 점:
- LanguageDetector 사용: 사용자 브라우저 언어 자동 감지
- fallbackLng: 'ko': 기본 언어 지정
- interpolation.escapeValue: false: React는 자체적으로 XSS 방어하므로 올바른 설정
- debug: false: 프로덕션 성능 고려
개선 제안 (선택사항):
LanguageDetector의 감지 순서를 명시적으로 지정하면 더 예측 가능합니다:i18n .use(LanguageDetector) .use(initReactI18next) .init({ resources, fallbackLng: 'ko', detection: { // 감지 순서 명시: localStorage → querystring → navigator order: ['localStorage', 'querystring', 'navigator'], caches: ['localStorage'], // 사용자 선택 언어 저장 lookupQuerystring: 'lang', // ?lang=en 같은 쿼리 파라미터 }, interpolation: { escapeValue: false, }, debug: false, });이유:
- 사용자가 언어를 변경하면 localStorage에 저장되어 다음 방문 시 유지
- URL 쿼리로 언어 테스트 가능 (예:
?lang=en)- 명시적 순서로 동작 예측성 향상
참고 문서:
현재 설정으로도 충분히 동작하므로 선택적으로 반영하세요.
src/components/header/modal/index.tsx (2)
15-27: P3:selectedLanguage로컬 상태가 중복됩니다.
i18n.resolvedLanguage또는i18n.language를 직접 사용하면 로컬 상태를 제거하고 불필요한 리렌더링을 방지할 수 있습니다.현재 구현에서는:
selectedLanguage상태 관리useEffect로i18n.resolvedLanguage동기화selectLanguage에서 다시setSelectedLanguage호출이 모든 것이
i18n.language를 직접 참조하면 불필요해집니다.const LanguageModal = ({ onClose }: LanguageModalProps) => { const { i18n } = useTranslation(); - const [selectedLanguage, setSelectedLanguage] = useState<Lang>('ko'); - - useEffect(() => { - if (i18n.resolvedLanguage) { - setSelectedLanguage(i18n.resolvedLanguage as Lang); - } - }, [i18n.resolvedLanguage]); + const currentLanguage = (i18n.resolvedLanguage || 'ko') as Lang; const selectLanguage = (lang: Lang) => { i18n.changeLanguage(lang); - setSelectedLanguage(lang); onClose(); };이후 JSX에서
selectedLanguage대신currentLanguage를 사용하세요.
17-21: P4: 타입 캐스팅 안전성 검토가 필요합니다.
i18n.resolvedLanguage as Lang캐스팅은 브라우저 언어 감지 등으로 예상치 못한 언어 코드가 반환될 경우 런타임에 문제가 될 수 있습니다.+const isValidLang = (lang: string | undefined): lang is Lang => { + return lang === 'ko' || lang === 'en' || lang === 'zh'; +}; + useEffect(() => { - if (i18n.resolvedLanguage) { - setSelectedLanguage(i18n.resolvedLanguage as Lang); + if (isValidLang(i18n.resolvedLanguage)) { + setSelectedLanguage(i18n.resolvedLanguage); } }, [i18n.resolvedLanguage]);또는 앞선 리팩터링 제안을 적용할 경우:
const currentLanguage: Lang = isValidLang(i18n.resolvedLanguage) ? i18n.resolvedLanguage : 'ko';src/components/header/index.tsx (2)
18-18: P3:useContext비-널 단언(!)은 런타임 에러 위험이 있습니다.
AuthContext가 Provider 외부에서 사용되면undefined가 되어 앱이 크래시됩니다. 방어적인 처리를 권장합니다.-const { logout, isLoggedIn, accessToken, refreshToken } = useContext(AuthContext)!; +const authContext = useContext(AuthContext); + +if (!authContext) { + throw new Error('Header must be used within AuthProvider'); +} + +const { logout, isLoggedIn, accessToken, refreshToken } = authContext;또는 커스텀 훅으로 추출:
// useAuth.ts export const useAuth = () => { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; };
33-35: P5:onclickFunction래퍼 함수가 불필요합니다.단순히
toggleModal()을 호출하는 래퍼 함수는 직접 참조로 대체할 수 있습니다.const { isOpen, toggleModal, closeModal } = useModal(); -const onclickFunction = () => { - toggleModal(); -}; // JSX에서: -<LanguageButton onClick={onclickFunction} aria-label="언어 변경"> +<LanguageButton onClick={toggleModal} aria-label="언어 변경">함수명도
onclickFunction보다 명확한 이름(예:handleLanguageButtonClick)이 권장되지만, 직접toggleModal을 사용하면 더 간결합니다.src/components/header/modal/index.style.tsx (3)
1-1: P5: 파일명 주석이 실제 파일명과 다릅니다.주석에
index.style.ts로 되어 있지만 실제 파일명은index.style.tsx입니다.-// index.style.ts +// index.style.tsx
19-21: P4: 하드코딩된 위치 값은 유지보수성을 저해할 수 있습니다.
top: 70px,right: 54px는 현재 헤더 높이(80px)와 레이아웃에 의존합니다. 헤더 스타일이 변경되면 모달 위치가 어긋날 수 있습니다.가능한 대안:
- CSS 변수를 사용하여 헤더 높이를 공유
- 모달을
LanguageWrapper내부에 상대 위치로 배치/* 예시: CSS 변수 활용 */ :root { --header-height: 80px; } /* ModalContainer에서 */ top: calc(var(--header-height) - 10px);현재 구현도 동작하므로, 향후 리팩터링 시 고려해 주세요.
47-65: P3: 키보드 접근성을 위한 포커스 스타일이 누락되었습니다.
:hover스타일은 있지만:focus스타일이 없어 키보드 사용자가 현재 포커스된 옵션을 시각적으로 확인하기 어렵습니다.export const LanguageOptionButton = styled.button<{ $active: boolean }>` /* ... existing styles ... */ &:hover { border-radius: 5px; background-color: #f3f4f6; } + + &:focus { + outline: 2px solid #3b82f6; + outline-offset: -2px; + border-radius: 5px; + } + + &:focus:not(:focus-visible) { + outline: none; + } `;
:focus-visible을 사용하면 마우스 클릭 시에는 outline이 보이지 않고, 키보드 탐색 시에만 표시됩니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
package-lock.jsonis excluded by!**/package-lock.json,!**/package-lock.jsonsrc/assets/Language.svgis excluded by!**/*.svg
📒 Files selected for processing (12)
package.json(1 hunks)src/App.tsx(1 hunks)src/components/header/index.style.tsx(2 hunks)src/components/header/index.tsx(1 hunks)src/components/header/modal/index.style.tsx(1 hunks)src/components/header/modal/index.tsx(1 hunks)src/hooks/useModal/index.tsx(1 hunks)src/i18n/index.ts(1 hunks)src/locales/en/common.json(1 hunks)src/locales/ko/common.json(1 hunks)src/locales/zh/common.json(1 hunks)src/pages/main/content/index.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.{ts,tsx}
⚙️ CodeRabbit configuration file
src/**/*.{ts,tsx}: 1. 코드에서의 오타, 불필요한 로직, 중복 코드, 서비스/도메인 설계, 예외 처리를 확인해주세요. 2. 많은 양의 코드리뷰는 피로감을 줄 수 있으므로 꼭 필요한 부분에만 집중해주고, 나머지는 캡션으로 설명해주세요. 3. Pn룰을 사용합니다. 모든 코멘트에는Pn:을 붙여주세요. 중요도에 따라 숫자를 반영해주세요. (P1: 꼭 반영해주세요 (Request changes) / P2: 적극적으로 고려해주세요 (Request changes) / P3: 웬만하면 반영해 주세요 (Comment) / P4: 반영해도 좋고 넘어가도 좋습니다 (Approve) / P5: 그냥 사소한 의견입니다 (Approve)) 4. 코드에 대한 리뷰를 남길 때는 해당 라인 범위에 코멘트를 작성해주세요. 5. 리뷰 포인트별로 문제점, 대안, 장단점을 논리적이고 명확하게 제시하고 경우에 따라 단순하지만 효율적인 예시 코드도 추가해주세요. 6. 타입, 컴포넌트, 클래스명은 PascalCase로 작성합니다. 7. 멤버, 함수명은 camelCase로 작성합니다. 8. React + TypeScript 코드의 경우, 불필요한 리렌더링, 훅 의존성 배열, 과도한 any 사용 여부도 함께 확인해주세요.
Files:
src/i18n/index.tssrc/App.tsxsrc/components/header/index.style.tsxsrc/components/header/modal/index.tsxsrc/pages/main/content/index.tsxsrc/components/header/index.tsxsrc/components/header/modal/index.style.tsxsrc/hooks/useModal/index.tsx
src/components/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (README.md)
Store reusable UI components (buttons, input fields, cards, etc.) in the
componentsdirectory
Files:
src/components/header/index.style.tsxsrc/components/header/modal/index.tsxsrc/components/header/index.tsxsrc/components/header/modal/index.style.tsx
src/pages/**/*.{jsx,tsx}
📄 CodeRabbit inference engine (README.md)
Store routable page components (homepage, login page, profile page, etc.) in the
pagesdirectory as top-level components for each route
Files:
src/pages/main/content/index.tsx
src/hooks/**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (README.md)
Store custom React hooks for state management and side effect handling in the
hooksdirectory
Files:
src/hooks/useModal/index.tsx
🧬 Code graph analysis (3)
src/components/header/modal/index.tsx (1)
src/components/header/modal/index.style.tsx (5)
ModalOverlay(4-9)ModalContainer(11-35)LanguageList(37-41)LanguageListItem(43-45)LanguageOptionButton(47-65)
src/pages/main/content/index.tsx (2)
src/api/context/use-auth/index.tsx (1)
useAuth(4-10)src/pages/main/content/index.style.tsx (12)
Banner(19-32)Title(34-40)Description(42-48)ButtonContainer(50-53)FunctionButton(55-59)StartButton(61-70)MainContent(72-79)ContentContainer(89-97)ContentImage(81-87)TextContainer(99-103)MainTitle(105-109)MainDescription(111-116)
src/components/header/index.tsx (4)
src/api/context/auth-context/index.tsx (1)
AuthContext(12-12)src/components/dashboard/modals/register-modal/index.tsx (1)
useModal(61-61)src/components/dashboard/modals/update-password/index.tsx (1)
useModal(104-104)src/components/header/index.style.tsx (6)
HeaderWrapper(5-19)LogoWrapper(82-88)Logo(21-33)LanguageWrapper(90-93)LanguageButton(43-54)LogoutButton(56-66)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: AWS Amplify Console Web Preview
🔇 Additional comments (12)
src/locales/ko/common.json (1)
1-18: P5: 번역 키 구조가 완성되었습니다! 👍이전 리뷰에서 요청된 추가 번역 키들이 모두 반영되었습니다.
home.feature_button,home.start_button,home.keychain.*,home.ssh.*등이 적절하게 추가되어src/pages/main/content/index.tsx에서 사용하는 모든 키를 커버하고 있습니다.작은 제안:
향후 번역 파일이 커질 것을 대비해 다음을 고려해보세요:
- 각 섹션별로 파일을 분리 (예:
home.json,features.json)- 또는 네임스페이스 활용 (i18next의 namespace 기능)
하지만 현재 규모에서는 단일 파일 구조가 충분히 관리 가능합니다.
src/locales/en/common.json (1)
1-18: P5: 영문 번역이 자연스럽고 완성도가 높습니다.한국어 locale과 동일한 키 구조를 유지하고 있어 일관성이 좋습니다. 영문 표현도 자연스럽고 전문적입니다.
참고:
- Line 3의 "web-based terminal service" 표현이 명확합니다.
- Line 10의 "View your key list at a glance" 같은 표현이 사용자 친화적입니다.
세 언어(ko, en, zh) 간 키 일치도를 유지하는 것이 중요한데, 현재 잘 지켜지고 있습니다.
src/components/header/index.style.tsx (1)
90-93: P5: LanguageWrapper의 포지셔닝이 적절합니다.
position: relative와display: inline-block조합은 언어 선택 모달을 absolute 포지셔닝할 수 있는 기준점을 제공합니다. 일반적인 드롭다운 패턴에 부합하는 구조입니다.src/App.tsx (1)
20-20: P5: i18n 초기화 방식이 적절합니다.
import './i18n';사이드 이펙트 임포트는 i18n 초기화에 권장되는 패턴입니다.이 방식의 장점:
- 명시적 초기화: 앱 진입점에서 i18n이 초기화됨을 명확히 표현
- 실행 순서 보장: 컴포넌트 렌더링 전에 i18n 설정 완료
- Tree shaking 방지: 번들러가 i18n 코드를 제거하지 않음
공식 문서 참고:
이런 초기화 패턴은 i18next 공식 문서에서도 권장하는 방식입니다.
src/locales/zh/common.json (1)
1-18: P5: 중국어 번역이 완성되어 세 언어 지원이 일관되게 구현되었습니다.한국어, 영어와 동일한 키 구조를 유지하여 locale 간 일관성이 확보되었습니다.
확인된 장점:
- 모든 locale 파일(ko, en, zh)이 동일한 키 세트를 가짐
- 중국어 간체 번역이 자연스러움 (Line 3: "基于网页的终端服务")
- 기술 용어(KeyChain, SSH)는 영문 유지로 일관성 확보
유지보수 팁:
향후 새로운 번역 키를 추가할 때는 세 파일을 동시에 업데이트하여 누락을 방지하세요. TypeScript의 타입 안전성을 활용하면 이를 강제할 수 있습니다:// i18n/index.ts에서 타입 정의 활용 type TranslationKeys = typeof ko; // 모든 locale이 동일한 키를 갖도록 타입 체크src/pages/main/content/index.tsx (1)
6-6: P5: useTranslation 훅 사용이 적절합니다.
react-i18next의useTranslation훅을 올바르게 import하고 사용하고 있습니다.이 패턴의 장점:
- 함수형 컴포넌트에서 권장되는 i18n 사용법
- 타입 안전성 확보 가능 (향후 개선 시)
- 언어 변경 시 자동 리렌더링
Also applies to: 12-12
src/i18n/index.ts (2)
1-13: P5: i18n 리소스 설정이 타입 안전하게 구성되었습니다.
as const어설션을 사용하여 타입 안전성을 확보한 점이 좋습니다.현재 구조의 장점:
const resources = { ko: { translation: ko }, en: { translation: en }, zh: { translation: zh }, } as const;
as const: 리터럴 타입 추론으로 타입 안전성 향상- 일관된 구조: 모든 locale이 'translation' 네임스페이스 사용
향후 확장 시 고려사항:
프로젝트가 커지면 네임스페이스 분리를 고려해보세요:const resources = { ko: { common: ko, dashboard: koDashboard, auth: koAuth }, en: { common: en, dashboard: enDashboard, auth: enAuth }, } as const;하지만 현재 규모에서는 단일 네임스페이스로 충분합니다.
24-25: P5: 디버그 설정 관리가 좋습니다.주석 처리된 개발 환경 디버그 옵션과 명시적인
debug: false설정이 좋습니다.작은 팁:
개발 시 번역 누락을 빠르게 발견하려면 주석을 해제하면 됩니다:debug: process.env.NODE_ENV === 'development',이렇게 하면 개발 환경에서만 디버그 로그가 출력되어 번역 키 오타나 누락을 쉽게 찾을 수 있습니다.
package.json (1)
22-23: Current i18n dependency versions are compatible.The version combination in package.json is compatible:
i18next@^23.16.8satisfiesreact-i18next@^13.5.0peer dependency requirement (which specifiesi18next >= 23.2.3)- No TypeScript peer dependency constraints are declared by these packages, so
typescript@^4.9.5poses no issuesi18next-browser-languagedetector@^8.2.0has no conflicting peer dependenciesNo changes needed for version compatibility.
Likely an incorrect or invalid review comment.
src/hooks/useModal/index.tsx (1)
8-10: P5: LGTM! toggleModal 구현이 올바릅니다.
toggleModal함수가 함수형 업데이트 패턴(prev) => !prev를 사용하여 이전 상태를 기반으로 토글하고 있습니다.useCallback의 의존성 배열도 비어있어 적절합니다.src/components/header/modal/index.tsx (1)
29-54: P5: Portal 및 이벤트 처리 구현이 적절합니다.
createPortal을 사용하여 모달을document.body에 렌더링ModalOverlay클릭 시onClose호출ModalContainer에서e.stopPropagation()으로 버블링 방지모달 패턴이 올바르게 구현되어 있습니다.
src/components/header/index.tsx (1)
44-49: P5: 이전 리뷰 피드백이 잘 반영되었습니다.
onClick이 버튼 요소에 직접 연결됨aria-label="언어 변경"으로 접근성 개선- SVG에서 인라인
cursor: 'point'오타 제거됨키보드 및 스크린 리더 사용자 경험이 개선되었습니다. 👍
Motivation
Problem Solving
react-i18next,i18next기반 i18n 초기 설정 진행(a72d10d)i18n.changeLanguage()호출 시 즉시 반영되도록 상태 연동To Reviewer
i18next,react-i18next설치 과정에서 TypeScript 버전 불일치 문제가 발생했습니다.react-i18next의 peerDependencies 요구사항: TypeScript5 이상이를 해결하기 위해 i18next 버전을 TS 4.x에 맞게 다운그레이드했습니다.
자세한 내용은 Jira에서 보시면 됩니다! : https://team-dopamine.atlassian.net/browse/HK-173
Summary by CodeRabbit
릴리스 노트
New Features
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.