diff --git a/pages/src/components/Footer.tsx b/pages/src/components/Footer.tsx index 4ecf0ca3b..681bee664 100644 --- a/pages/src/components/Footer.tsx +++ b/pages/src/components/Footer.tsx @@ -12,6 +12,7 @@ const LANG_OPTIONS: { value: Language; label: string }[] = [ { value: 'en', label: 'English' }, { value: 'zh', label: '中文' }, // allow-non-english: language options are labelled in their own language { value: 'ja', label: '日本語' }, // allow-non-english: language options are labelled in their own language + { value: 'ko', label: '한국어' }, // allow-non-english: language options are labelled in their own language { value: 'ru', label: 'Русский' }, // allow-non-english: language options are labelled in their own language ]; diff --git a/pages/src/components/Navbar.tsx b/pages/src/components/Navbar.tsx index 74a435c10..9213b5897 100644 --- a/pages/src/components/Navbar.tsx +++ b/pages/src/components/Navbar.tsx @@ -14,6 +14,7 @@ const LANG_OPTIONS: { value: Language; label: string }[] = [ { value: 'en', label: 'English' }, { value: 'zh', label: '中文' }, // allow-non-english: language options are labelled in their own language { value: 'ja', label: '日本語' }, // allow-non-english: language options are labelled in their own language + { value: 'ko', label: '한국어' }, // allow-non-english: language options are labelled in their own language { value: 'ru', label: 'Русский' }, // allow-non-english: language options are labelled in their own language ]; @@ -21,9 +22,19 @@ const LANG_BADGE: Record = { en: 'En', zh: '中', // allow-non-english: single-glyph locale badge ja: 'あ', // allow-non-english: single-glyph locale badge + ko: '한', // allow-non-english: single-glyph locale badge ru: 'Ru', }; +// Locales whose badge glyph needs a script-specific face; everything else uses +// the default stack below. +const LANG_BADGE_FONT: Partial> = { + ja: "'Hiragino Sans', sans-serif", + ko: "'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif", +}; + +const DEFAULT_LANG_BADGE_FONT = "'PingFang SC', -apple-system, sans-serif"; + const navTabs = [ { path: '/features', labelKey: 'navbar.features' }, { path: '/benchmark', labelKey: 'navbar.benchmark' }, @@ -152,7 +163,7 @@ const Navbar: React.FC = () => { lineHeight: '18px', textAlign: 'center' as const, width: '100%', - fontFamily: language === 'ja' ? "'Hiragino Sans', sans-serif" : "'PingFang SC', -apple-system, sans-serif", + fontFamily: LANG_BADGE_FONT[language] ?? DEFAULT_LANG_BADGE_FONT, }}> {LANG_BADGE[language]} diff --git a/pages/src/content/docs/index.ts b/pages/src/content/docs/index.ts index 7a4e05de6..b4be1b378 100644 --- a/pages/src/content/docs/index.ts +++ b/pages/src/content/docs/index.ts @@ -177,6 +177,8 @@ const docsMap: Record = { en: enDocs, zh: zhDocs, ja: jaDocs, + // Korean UI strings ship first; doc pages fall back to English until ko/ exists. + ko: {}, ru: ruDocs, }; diff --git a/pages/src/i18n/context.tsx b/pages/src/i18n/context.tsx index 601a11b8a..9dc51bce3 100644 --- a/pages/src/i18n/context.tsx +++ b/pages/src/i18n/context.tsx @@ -6,9 +6,10 @@ import { Language, TranslationKeys } from './types'; import { en } from './en'; import { zh } from './zh'; import { ja } from './ja'; +import { ko } from './ko'; import { ru } from './ru'; -const translations: Record = { en, zh, ja, ru }; +const translations: Record = { en, zh, ja, ko, ru }; interface LanguageContextValue { language: Language; @@ -20,7 +21,7 @@ const LanguageContext = createContext(null); const STORAGE_KEY = 'ocr-lang'; -const SUPPORTED_LANGUAGES: Language[] = ['en', 'zh', 'ja', 'ru']; +const SUPPORTED_LANGUAGES: Language[] = ['en', 'zh', 'ja', 'ko', 'ru']; function detectBrowserLanguage(): Language | null { try { diff --git a/pages/src/i18n/ko.ts b/pages/src/i18n/ko.ts new file mode 100644 index 000000000..27050e691 --- /dev/null +++ b/pages/src/i18n/ko.ts @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2026 alibaba/open-code-review Contributors + +import { TranslationKeys } from './types'; + +export const ko: TranslationKeys = { + // Navbar + 'navbar.features': '기능', + 'navbar.benchmark': '벤치마크', + 'navbar.quickstart': '빠른 시작', + 'navbar.docs': '문서', + 'navbar.blog': '블로그', + 'navbar.getStarted': '시작하기', + + // Hero + 'hero.title': 'AI 코드 리뷰\n수백만 건의 실제 작업으로 검증되었습니다', + 'hero.description': 'Open Code Review는 Alibaba에서 실전 검증된 코드 리뷰 Agent를 그대로 워크플로에 옮겨 놓습니다. 원하는 LLM을 연결하고, 데이터는 외부로 나가지 않으며, 개발자가 실제로 반영하는 리뷰 코멘트를 받습니다.', + 'hero.quickStart': '빠른 시작', + 'hero.learnMore': '자세히 보기', + 'hero.terminal': '터미널', + 'hero.copied': '복사되었습니다!', + 'hero.copyFailed': '복사 실패', + 'hero.installNpm': 'npm', + 'hero.installBrew': 'Homebrew', + 'hero.installMacPorts': 'MacPorts', + 'hero.installMacOS': 'MacOS', + 'hero.installLinux': 'Linux', + 'hero.installWindows': 'Windows', + 'hero.installMore': '더 보기', + 'hero.allInstallOptions': '모든 설치 방법', + + // Error boundary + 'error.pageLoadFailed': '페이지를 불러오지 못했습니다.', + 'error.reload': '새로고침', + + // Not found + 'notFound.title': '페이지를 찾을 수 없습니다', + 'notFound.description': '찾으시는 페이지가 존재하지 않거나 이동되었습니다.', + 'notFound.backHome': '홈으로 돌아가기', + + // Highlights + 'highlights.stat1Value': '20K+', + 'highlights.stat1Label': '사내 활성 사용자', + 'highlights.stat1Caption': 'Alibaba 그룹 내부에서 실전 검증', + 'highlights.stat2Value': '150K+', + 'highlights.stat2Label': 'NPM 커뮤니티 다운로드', + 'highlights.stat2Caption': '실제 npm 다운로드 수 · 최근 30일', + 'highlights.stat3Value': '3M+', + 'highlights.stat3Label': '실제 작업', + 'highlights.stat3Caption': '지금까지 수행한 코드 리뷰 작업', + 'highlights.stat4Value': '1/9', + 'highlights.stat4Label': '토큰 비용', + 'highlights.stat4Caption': 'Claude Code 대비 · PR 1,000건 기준', + 'highlights.stat5Value': '25.10%', + 'highlights.stat5Label': 'AACR-BENCH', + 'highlights.stat5Caption': 'SEM.F1 벤치마크 점수', + + // Use Cases + 'usecases.sectionLabel': '활용 사례', + 'usecases.title': 'Open Code Review는 누구를 위한 도구일까요?', + 'usecases.case1Title': '개인 개발자', + 'usecases.case1Desc': '로컬 AI 지원 개발 워크플로에 Open Code Review를 넣어 두면, 수준 높은 코드 리뷰 피드백을 즉시 받을 수 있습니다.', + 'usecases.case2Title': '플랫폼 팀', + 'usecases.case2Desc': '사내 시스템에 자연스럽게 통합하면서 데이터 흐름과 리뷰 정책을 온전히 통제할 수 있습니다.', + 'usecases.case3Title': 'ML 연구자', + 'usecases.case3Desc': '강화학습(RL) 훈련 파이프라인의 코드 품질 검증기로 활용해 코드 생성 모델에 신뢰할 수 있는 보상 신호를 제공합니다.', + + // Features + 'features.sectionBadge': '핵심 기능', + 'features.title': '코드 리뷰 전용으로 설계한 Agent 시스템', + 'features.subtitle': '파일을 읽고 코드베이스를 검색하고 맥락을 교차 확인하며, 세밀한 규칙을 적용해 깊이 있게 리뷰하는 AI Agent입니다.', + 'features.feat1Title': '하이브리드 아키텍처: 결정적 엔지니어링 + Agent', + 'features.feat1Desc': '결정적 엔지니어링(작업 분할, 파일 필터링, 라인 번호 위치 지정, 규칙 라우팅, 비동기 스케줄링)과 LLM Agent 추론(위험 탐지, 맥락 탐색, 이슈 분류)을 분리했습니다. 확실성이 필요한 부분은 엔지니어링 모듈이, 의미 판단은 Agent가 맡아 품질, 토큰 비용, 속도를 함께 최적화합니다.', + 'features.feat2Title': '정밀한 코멘트 위치 지정과 리플렉션', + 'features.feat2Desc': '독립적인 라인 단위 코멘트 위치 지정 모듈이 3단계 점진적 LLM 전략으로 각 코멘트를 정확한 라인 번호에 고정합니다. 별도의 리플렉션 모듈은 환각과 지식 드리프트를 초기에 걸러냅니다.', + 'features.feat3Title': '멀티 모델 프로토콜 지원', + 'features.feat3Desc': 'Anthropic Messages API, OpenAI Chat Completions API, OpenAI Responses API를 지원합니다. Anthropic, OpenAI, DashScope, DeepSeek, Z.AI 등 프리셋 프로바이더를 기본 제공해 바로 사용할 수 있고 자체 배포를 위한 커스텀 모델 엔드포인트도 연결할 수 있습니다.', + 'features.feat4Title': '동적 병렬 처리', + 'features.feat4Desc': '하위 작업을 동적으로 분할해 설정 가능한 goroutine 워커(기본값 8개)로 병렬 리뷰합니다. 변경 규모가 커도 빠르게 끝납니다.', + 'features.feat5Title': '스마트 메모리 압축', + 'features.feat5Desc': '코드 리뷰에 맞춰 설계한 메모리 압축 방식입니다. 컨텍스트를 3단계(동결/압축/활성)로 나눠 관리하며 토큰 한계를 넘어 깊이 있게 리뷰합니다.', + 'features.feat6Title': '내장 리뷰 규칙', + 'features.feat6Desc': '대규모 실제 시나리오에서 검증한 리뷰 규칙을 제공합니다. Java, TypeScript, Go, Python, Kotlin, Rust, C++, C 등 10개 이상의 언어를 지원하며 NPE, 스레드 안전성, XSS, SQL 인젝션 등에 특화된 규칙을 포함합니다.', + + // Benchmark + 'benchmark.sectionLabel': '오픈 벤치마크', + 'benchmark.title': '80명 이상의 시니어 엔지니어가 교차 검증', + 'benchmark.subtitle': '인기 오픈소스 저장소 50곳에서 선별한 실제 Pull Request 200건으로 구성한 실전 코드 리뷰 벤치마크입니다. 10개 프로그래밍 언어와 다양한 이슈 유형, 서로 다른 변경 규모를 포괄합니다.', + 'benchmark.colRank': '순위', + 'benchmark.colModel': '모델', + 'benchmark.colSource': '출처', + 'benchmark.colF1': 'F1', + 'benchmark.colPrecision': '정밀도', + 'benchmark.colRecall': '재현율', + 'benchmark.colAvgTime': '평균 시간', + 'benchmark.colAvgToken': '평균 토큰', + + // QuickStart + 'quickstart.sectionLabel': '빠른 시작', + 'quickstart.title': '몇 분이면 바로 시작할 수 있습니다', + 'quickstart.subtitle': '복잡한 설정이 필요 없습니다. 직접 고른 LLM 프로바이더 외에 별도의 서드파티 계정도 필요하지 않습니다.', + 'quickstart.step1Title': '설치', + 'quickstart.step1Desc': 'npm 명령 한 줄로 전역 설치합니다.', + 'quickstart.step1Label1': '설치', + 'quickstart.step1Label2': '설치 확인', + 'quickstart.step2Title': '설정', + 'quickstart.step2Desc': 'LLM 프로바이더를 대화형으로 설정하거나 수동으로 구성합니다.', + 'quickstart.step2Label1': '대화형 설정(권장)', + 'quickstart.step2Label2': '수동 설정(대안)', + 'quickstart.step2Label3': '설정 확인', + 'quickstart.step3Title': '리뷰 실행', + 'quickstart.step3Desc': '첫 코드 리뷰를 시작합니다.', + 'quickstart.step3Label1': '리뷰 명령어', + 'quickstart.commentReview': '# 현재 변경 사항 리뷰', + 'quickstart.commentBranch': '# 브랜치 간 diff 리뷰', + 'quickstart.commentCommit': '# 특정 커밋 리뷰', + 'quickstart.copied': '복사되었습니다!', + + // Footer + 'footer.brand': 'Open Code Review', + 'footer.copyright': '© Copyright 2026. All rights reserved.', + + // Docs Page + 'docs.toc': '목차', + 'docs.install': '설치', + 'docs.config': 'ocr config', + 'docs.review': 'ocr review', + 'docs.scan': 'ocr scan', + 'docs.viewer': 'ocr viewer', + 'docs.mcp': 'MCP 서버', + 'docs.env': 'Claude Code 연동', + 'docs.installTitle': '설치', + 'docs.installLabel': '설치', + 'docs.installVerifyLabel': '설치 확인', + 'docs.configTitle': '설정 및 확인', + 'docs.configDesc': '통합된 프로바이더 시스템으로 CLI 설정을 관리합니다. 여러 프로바이더를 기본 내장하고 있으며 자체 배포를 위한 커스텀 프로바이더도 지원합니다. 설정은 ~/.opencodereview/config.json에 저장됩니다.', + 'docs.configInteractive': '대화형 설정(권장)', + 'docs.configInteractiveDesc': 'Open Code Review를 설정하는 가장 쉬운 방법입니다. 내장 프로바이더를 고르거나 커스텀 프로바이더를 추가한 뒤 API 키를 입력하고 모델을 선택합니다.', + 'docs.configModelSelect': '모델 선택', + 'docs.configModelSelectDesc': '프로바이더를 고른 뒤 사용할 모델을 선택합니다.', + 'docs.configListProviders': '내장 프로바이더 목록 보기', + 'docs.configListProvidersDesc': '사용할 수 있는 모든 내장 LLM 프로바이더를 확인합니다.', + 'docs.configManual': '수동 설정', + 'docs.configManualBuiltin': '내장 프로바이더 사용', + 'docs.configManualBuiltinDesc': '내장 프로바이더에는 API URL과 프로토콜이 미리 설정되어 있습니다. API 키만 입력하면 됩니다.', + 'docs.configManualCustom': '커스텀 프로바이더 사용', + 'docs.configManualCustomDesc': '자체 배포 환경이나 호환되는 다른 엔드포인트에 연결합니다.', + 'docs.configManualCustomNote': '커스텀 프로바이더에는 url과 protocol이 반드시 필요합니다. 지원 프로토콜: anthropic, openai, openai-responses.', + 'docs.configKeys': '지원하는 설정 키', + 'docs.configKeyProvider': '현재 사용 중인 프로바이더 이름', + 'docs.configKeyProviderApiKey': '프로바이더 API 키', + 'docs.configKeyProviderModel': '모델 이름', + 'docs.configKeyProviderUrl': 'API URL 재정의', + 'docs.configKeyProviderExtraBody': '커스텀 요청 본문 필드(JSON)', + 'docs.configKeyProviderExtraHeaders': '커스텀 HTTP 헤더(key=value)', + 'docs.configKeyLanguage': '출력 언어', + 'docs.configKeyTelemetry': '텔레메트리', + 'docs.configVerify': '설정 확인', + 'docs.configVerifyDesc': '설정을 마친 뒤 이 명령을 실행해 LLM 연결을 테스트하고 설정이 올바른지 확인합니다.', + 'docs.reviewTitle': 'ocr review', + 'docs.reviewDesc': 'ocr review는 AI 코드 리뷰를 시작하는 핵심 명령입니다.', + 'docs.reviewModes': '리뷰 모드', + 'docs.reviewWorkspace': '워크스페이스 모드(기본값)', + 'docs.reviewWorkspaceDesc': '현재 워크스페이스의 스테이징된/스테이징되지 않은/추적되지 않은 변경을 리뷰합니다.', + 'docs.reviewBranch': '브랜치 diff 모드', + 'docs.reviewBranchDesc': '두 ref 사이의 diff를 리뷰합니다(merge-base 기준).', + 'docs.reviewCommit': '단일 커밋 모드', + 'docs.reviewCommitDesc': '특정 커밋을 리뷰합니다(부모 커밋과의 diff).', + 'docs.reviewAdvanced': '고급 사용법', + 'docs.reviewBackground': '요구사항 맥락과 함께 리뷰하기', + 'docs.reviewBackgroundDesc': '중요한 경계 조건을 포함한 요구사항 맥락을 함께 전달하면 리뷰 품질이 높아집니다.', + 'docs.reviewJson': 'JSON 출력', + 'docs.reviewJsonDesc': '결과를 구조화된 JSON 형식으로 출력해 프로그램에서 바로 처리할 수 있습니다.', + 'docs.reviewAgent': 'Agent 모드', + 'docs.reviewAgentDesc': '실행 과정 없이 최종 요약만 출력합니다. 자동화 파이프라인에 적합합니다.', + 'docs.reviewPreviewLabel': 'Dry-Run 미리보기', + 'docs.reviewPreviewDesc': '리뷰 대상 파일 목록과 통계(파일 수, 변경 라인 수)를 보여줍니다. LLM을 호출하지 않으므로 비용이 들지 않습니다. 본격적인 리뷰 전에 범위를 확인할 때 유용합니다.', + 'docs.reviewToggles': '리뷰 단계 토글: --no-filter', + 'docs.reviewTogglesFilterDesc': '리뷰 코멘트를 모두 남기고 파일 단위 REVIEW_FILTER_TASK LLM 후처리 호출을 건너뜁니다.', + 'docs.reviewFlags': '플래그 레퍼런스', + 'docs.reviewFlagCol1': '플래그', + 'docs.reviewFlagCol2': '설명', + 'docs.reviewFlagCol3': '기본값', + 'docs.reviewFlag1Desc': '리뷰할 커밋 해시 지정', + 'docs.reviewFlag2Desc': '시작 ref(diff 기준점)', + 'docs.reviewFlag3Desc': '대상 ref(diff 종료점)', + 'docs.reviewFlag4Desc': '출력 형식: text, json 또는 sarif', + 'docs.reviewFlag5Desc': 'Git 저장소 루트', + 'docs.reviewFlag5Default': '현재 디렉터리', + 'docs.reviewFlag6Desc': '리뷰 규칙 JSON 파일 경로', + 'docs.reviewFlag6Default': '내장', + 'docs.reviewFlag7Desc': '최대 동시 파일 리뷰 수', + 'docs.reviewFlag8Desc': '단일 작업 타임아웃(분)', + 'docs.reviewFlag9Desc': '출력 대상: human(과정 표시) 또는 agent(요약만)', + 'docs.reviewFlag10Desc': '파일당 최대 도구 호출 라운드 수. 템플릿 기본값보다 클 때만 적용됩니다', + 'docs.reviewFlag10Default': '내장', + 'docs.reviewFlag11Desc': '최대 동시 git 서브프로세스 수', + 'docs.reviewNote': '참고: --from/--to--commit은 함께 사용할 수 없습니다. --from을 지정하면 --to도 반드시 지정해야 합니다.', + 'docs.scanTitle': 'ocr scan', + 'docs.scanDesc': 'ocr scan은 diff가 아니라 파일 전체를 리뷰합니다. Git 히스토리나 의미 있는 diff가 없어도 됩니다. 익숙하지 않은 코드베이스를 감사하거나, 마이그레이션 전에 특정 디렉터리를 훑어보거나, 비교할 대상이 없는 프로젝트를 리뷰할 때 사용하세요.', + 'docs.scanVsTitle': 'ocr scan과 ocr review의 차이', + 'docs.scanVsReviewLabel': 'ocr review', + 'docs.scanVsReview': 'ocr review는 diff 기반입니다. 워크스페이스의 스테이징된/스테이징되지 않은/추적되지 않은 변경, 브랜치 범위, 단일 커밋 등 Git 변경 사항을 리뷰합니다. diff를 계산하려면 Git 히스토리가 필요합니다.', + 'docs.scanVsScanLabel': 'ocr scan', + 'docs.scanVsScan': 'ocr scan은 파일 전체 기반입니다. Git 히스토리와 무관하게 파일의 전체 내용을 리뷰합니다. git 저장소가 아닌 디렉터리에서도 동작하며, 이때는 .gitignore를 반영해 파일 시스템을 탐색합니다.', + 'docs.scanUsage': '기본 사용법', + 'docs.scanUsageWhole': '저장소 전체 스캔', + 'docs.scanUsageWholeDesc': '--path를 지정하지 않았을 때의 기본 동작입니다.', + 'docs.scanUsagePath': '특정 경로 스캔', + 'docs.scanUsagePathDesc': '저장소 기준 상대 디렉터리를 넘겨 스캔 범위를 좁힙니다.', + 'docs.scanUsageFile': '특정 파일 스캔', + 'docs.scanUsageFileDesc': '쉼표로 구분한 파일 경로도 사용할 수 있습니다.', + 'docs.scanUsagePreviewLabel': 'Dry-Run 미리보기', + 'docs.scanUsagePreviewDesc': '스캔 범위에 포함된 모든 파일을 나열하고 통계(전체 파일 수, 리뷰 대상과 제외 대상 및 제외 사유, 전체 라인 수)를 보고합니다. LLM을 호출하지 않으므로 비용이 들지 않습니다. 큰 저장소에서 토큰을 쓰기 전에 범위를 확인할 때 유용합니다.', + 'docs.scanBatching': '배치 전략', + 'docs.scanBatchingDesc': '--batch 플래그는 파일을 리뷰 배치로 묶는 방식을 정합니다. 스캔 템플릿의 기본값은 by-language이며 값이 비어 있거나 인식할 수 없으면 none으로 대체됩니다.', + 'docs.scanBatchingNone': 'none', + 'docs.scanBatchingNoneDesc': '파일마다 하나의 배치가 됩니다. 묶지 않습니다.', + 'docs.scanBatchingLang': 'by-language', + 'docs.scanBatchingLangDesc': '파일 확장자(소문자 기준)로 묶습니다. 템플릿 기본값입니다.', + 'docs.scanBatchingDir': 'by-directory', + 'docs.scanBatchingDirDesc': '저장소 루트 바로 아래의 1단계 디렉터리로 묶습니다. 루트에 있는 파일은 별도의 배치가 됩니다.', + 'docs.scanToggles': '단계 토글: --no-plan, --no-dedup, --no-summary', + 'docs.scanTogglesDesc': '스캔 실행에는 파일 단위 핵심 리뷰 외에 선택적인 세 단계가 있습니다. 각 단계를 개별적으로 끄면 리뷰 품질을 내주는 대신 속도를 높이고 토큰 비용을 줄일 수 있습니다.', + 'docs.scanTogglesPlanDesc': '리뷰에 앞서 집중할 영역을 정하는 파일 단위 PLAN_TASK 사전 단계를 건너뜁니다. 파일당 LLM 호출을 한 번 아낄 수 있지만 리뷰의 초점이 흐려질 수 있습니다.', + 'docs.scanTogglesDedupDesc': '배치 단위 DEDUP_TASK를 건너뜁니다. 중복 제거 단계에서 정리되었을 유사 코멘트까지 포함해 원본 코멘트를 모두 남깁니다.', + 'docs.scanTogglesSummaryDesc': '모든 배치가 끝난 뒤 저장소 단위 마크다운 요약을 만드는 PROJECT_SUMMARY_TASK를 건너뜁니다.', + 'docs.scanBudget': '토큰 예산: --max-tokens-budget', + 'docs.scanBudgetDesc': 'ocr scan은 작업을 전달하기 전에 대략적인 토큰 비용 추정치를 출력합니다. --max-tokens-budget은 LLM API가 보고한 입력+출력 토큰 사용량의 총합을 제한하며, 누적 사용량이 상한을 넘으면 다음 배치를 시작하기 전에 중단합니다. 기본값 0은 무제한을 의미합니다.', + 'docs.scanFlags': '플래그 레퍼런스', + 'docs.scanFlagCol1': '플래그', + 'docs.scanFlagCol2': '설명', + 'docs.scanFlagCol3': '기본값', + 'docs.scanFlag1Desc': '스캔할 저장소 기준 상대 디렉터리/파일(쉼표 구분)', + 'docs.scanFlag1Default': '저장소 전체', + 'docs.scanFlag2Desc': '제외할 gitignore 형식 패턴(쉼표 구분). rule.json의 excludes와 함께 적용됩니다', + 'docs.scanFlag3Desc': 'LLM을 실행하지 않고 스캔 대상 파일 미리보기', + 'docs.scanFlag4Desc': '전체 토큰 사용량(입력+출력) 상한. 초과하면 작업 전달을 중단합니다(0 = 무제한)', + 'docs.scanFlag5Desc': '파일 단위 PLAN_TASK 사전 단계 건너뛰기', + 'docs.scanFlag6Desc': '배치 단위 DEDUP_TASK 건너뛰기', + 'docs.scanFlag7Desc': '실행 후 PROJECT_SUMMARY_TASK 건너뛰기', + 'docs.scanFlag8Desc': '배치 전략: none | by-language | by-directory', + 'docs.scanFlag9Desc': '출력 형식: text, json 또는 sarif', + 'docs.scanFlag10Desc': '최대 동시 파일 스캔 수', + 'docs.scanFlag11Desc': '동시 작업 타임아웃(분)', + 'docs.scanFlag12Desc': '출력 대상: human(진행 상황 표시) 또는 agent(요약만)', + 'docs.scanFlag13Desc': '스캔에 참고할 요구사항 또는 비즈니스 맥락(선택)', + 'docs.scanFlag14Desc': '파일당 최대 도구 호출 라운드 수. 템플릿 기본값보다 클 때만 적용됩니다', + 'docs.scanFlag14Default': '내장', + 'docs.scanFlag15Desc': '최대 동시 git 서브프로세스 수', + 'docs.scanFlag16Desc': '커스텀 JSON 리뷰 규칙 파일 경로', + 'docs.scanFlag17Desc': '커스텀 JSON 도구 설정 파일 경로', + 'docs.scanFlag17Default': '내장', + 'docs.scanFlag18Desc': '스캔할 저장소 또는 디렉터리 루트', + 'docs.scanFlag18Default': '현재 디렉터리', + 'docs.scanNote': '참고: --max-tools는 스캔 템플릿의 파일당 도구 호출 예산을 올릴 때만 효과가 있습니다. 템플릿 기본값보다 낮게 설정하면 아무 영향이 없습니다.', + 'docs.viewerTitle': 'ocr viewer', + 'docs.viewerDesc': 'WebUI 세션 뷰어를 실행합니다. 웹 인터페이스에서 리뷰 세션 로그를 살펴볼 수 있습니다.', + 'docs.viewerNote': '명령을 실행하면 로컬 HTTP 서버가 시작되어 리뷰 세션 로그를 살펴볼 수 있는 시각적 인터페이스를 제공합니다.', + 'docs.mcpTitle': 'MCP 서버', + 'docs.mcpDesc': 'Open Code Review는 Model Context Protocol(MCP) 서버를 지원합니다. 리뷰 Agent는 stdio transport를 통해 코드 리뷰 중에 외부 도구를 사용할 수 있습니다.', + 'docs.mcpConfig': 'MCP 서버 설정', + 'docs.mcpConfigLocation': 'MCP 서버 설정은 ~/.opencodereview/config.json에 저장됩니다.', + 'docs.mcpDelete': 'MCP 서버 삭제', + 'docs.mcpFields': '설정 필드', + 'docs.mcpFieldCol': '필드', + 'docs.mcpRequiredCol': '필수', + 'docs.mcpDescCol': '설명', + 'docs.mcpYes': '예', + 'docs.mcpNo': '아니요', + 'docs.mcpFieldCommandDesc': 'MCP 서버를 실행할 명령어', + 'docs.mcpFieldArgsDesc': '서버에 전달할 커맨드라인 인자', + 'docs.mcpFieldEnvDesc': 'KEY=VALUE 형식의 환경 변수', + 'docs.mcpFieldToolsDesc': '허용할 도구 이름. 비워 두면 서버가 제공하는 모든 도구를 사용합니다', + 'docs.mcpFieldSetupDesc': '서버를 시작하기 전에 실행할 셸 명령. 예: 인덱스 구축', + 'docs.mcpNote': 'MCP 도구 이름이 내장 도구와 겹치면 경고와 함께 건너뜁니다. setup 명령의 타임아웃은 5분입니다.', + 'docs.mcpExample': '예시: CodeGraph를 추가해 코드 구조 분석하기', + 'docs.envTitle': 'Claude Code 연동', + 'docs.envDesc': '이미 Claude Code를 사용하면서 아래 환경 변수를 설정해 두었다면, Open Code Review가 이를 자동으로 인식하므로 추가 설정이 필요 없습니다:', + 'docs.envNote': 'ocr config로 이 설정을 덮어쓰거나 보완할 수도 있습니다.', + 'docs.copy': '복사', + + // Docs Sidebar + 'docs.sidebar.gettingStarted': '시작하기', + 'docs.sidebar.userGuide': '사용 가이드', + 'docs.sidebar.quickstart': '빠른 시작', + 'docs.sidebar.installation': '설치', + 'docs.sidebar.configuration': '설정', + 'docs.sidebar.cliReference': 'CLI 레퍼런스', + 'docs.sidebar.reviewRules': '리뷰 규칙', + 'docs.sidebar.architecture': '아키텍처', + 'docs.sidebar.tools': '도구', + 'docs.sidebar.mcp': 'MCP 서버', + 'docs.sidebar.viewer': '세션 뷰어', + 'docs.sidebar.telemetry': '텔레메트리', + 'docs.sidebar.integrations': '연동', + 'docs.sidebar.agentSkill': 'Agent Skill', + 'docs.sidebar.claudeCode': 'Command (Claude Code)', + 'docs.sidebar.delegate': '위임 모드', + 'docs.sidebar.cicd': 'CI/CD', + 'docs.sidebar.contributing': '기여하기', + 'docs.sidebar.faq': 'FAQ', + 'docs.search.placeholder': '검색...', + 'docs.search.noResults': '검색 결과가 없습니다', + 'docs.search.hint.select': '선택', + 'docs.search.hint.open': '열기', + 'docs.search.hint.close': '닫기', + + // Blog + 'blog.title': '블로그', + 'blog.allTags': '전체', + 'blog.backToList': '← 블로그로 돌아가기', + 'blog.toc': '목차', + 'blog.noPosts': '아직 게시글이 없습니다.', + 'blog.noPostsForTag': '이 태그에 해당하는 게시글이 없습니다.', + 'blog.search.placeholder': '게시글 검색...', + 'blog.search.noResults': '검색 결과가 없습니다', +}; diff --git a/pages/src/i18n/types.ts b/pages/src/i18n/types.ts index 85b48beab..66ceead4f 100644 --- a/pages/src/i18n/types.ts +++ b/pages/src/i18n/types.ts @@ -3,6 +3,6 @@ import type { en } from './en'; -export type Language = 'en' | 'zh' | 'ja' | 'ru'; +export type Language = 'en' | 'zh' | 'ja' | 'ko' | 'ru'; export type TranslationKey = keyof typeof en; export type TranslationKeys = Record; diff --git a/pages/src/pages/NotFoundPage.test.tsx b/pages/src/pages/NotFoundPage.test.tsx index 1537d5cac..7e8fc75a9 100644 --- a/pages/src/pages/NotFoundPage.test.tsx +++ b/pages/src/pages/NotFoundPage.test.tsx @@ -10,10 +10,11 @@ import { LanguageProvider } from '../i18n'; import { en } from '../i18n/en'; import { zh } from '../i18n/zh'; import { ja } from '../i18n/ja'; +import { ko } from '../i18n/ko'; import { ru } from '../i18n/ru'; import type { Language, TranslationKeys } from '../i18n/types'; -const translations: Record = { en, zh, ja, ru }; +const translations: Record = { en, zh, ja, ko, ru }; function installLocalStorageMock() { let store: Record = {}; diff --git a/pages/src/styles/index.css b/pages/src/styles/index.css index 34be891a2..2cf844f13 100644 --- a/pages/src/styles/index.css +++ b/pages/src/styles/index.css @@ -25,6 +25,17 @@ p { margin: 0; } +/* Korean separates words with spaces, so the browser default (which may break + between Hangul syllables) splits a word mid-token, leaving its tail alone on + the next line. Scoped to ko via the lang attribute set in i18n/context.tsx; + zh/ja rely on any-character line breaking and must keep the default. + overflow-wrap still lets an unbroken token (a long URL or identifier) wrap + instead of overflow. */ +:lang(ko) { + word-break: keep-all; + overflow-wrap: break-word; +} + a { text-decoration: none; color: inherit;