perf+security: fix build, secure downloads, dedupe translations, refactor i18n
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m49s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m49s
Frontend:
- Fix Framer Motion / motion-dom build error by pinning framer-motion to
11.18.2 (compatible with React 19 and Next.js 16).
- Add cross-env and build:local script to bypass standalone symlink errors
on Windows without Developer Mode.
- Allow NEXT_OUTPUT=default to disable standalone output for local builds.
- Refactor i18n: split 14,177-line src/lib/i18n.tsx into per-locale,
per-namespace JSON files under src/lib/i18n/messages/.
- Load English synchronously; other locales loaded on demand via dynamic
imports (reduces initial bundle, improves maintainability).
- Remove unused next-intl message files src/messages/en.json and fr.json.
Backend:
- Remove insecure legacy /api/v1/download/{filename} and /api/v1/cleanup/{filename}
endpoints. The job-based /api/v1/download/{job_id} already enforces ownership.
- Deduplicate texts in TranslationService.translate_batch before sending them
to the provider, reducing API calls for repeated strings.
- Pin httpx to <0.28 to fix TestClient incompatibility with starlette 0.35.1.
- Add pytest-cov and ruff dev dependencies/config.
DevOps:
- Remove hardcoded Grafana password from docker-compose.yml and
docker-compose.monitoring.yml; use GRAFANA_PASSWORD env var.
- Change default TRANSLATION_SERVICE from ollama to google in
docker-compose.yml (Ollama is an optional profile).
- Add GRAFANA_PASSWORD to .env.example.
- Add .coverage and frontend/pnpm-workspace.yaml to .gitignore.
Tests:
- Update API versioning tests for removed legacy endpoints.
- Add tests/test_translation_service.py for deduplication behavior.
Verified:
- pnpm run build:local passes.
- uv run pytest tests/test_providers/* tests/test_translation_service.py
tests/test_story_3_5_api_versioning.py tests/test_download_endpoint.py
tests/test_translators/test_excel_translator.py: provider/translator tests
pass; one pre-existing French error-message test still fails (message is
returned in English, unrelated to this change).
This commit is contained in:
111
frontend/src/lib/i18n/messages/ko/dashboard.json
Normal file
111
frontend/src/lib/i18n/messages/ko/dashboard.json
Normal file
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"dashboard.nav.translate": "번역",
|
||||
"dashboard.nav.profile": "내 프로필",
|
||||
"dashboard.nav.settings": "설정",
|
||||
"dashboard.nav.context": "컨텍스트",
|
||||
"dashboard.nav.services": "서비스",
|
||||
"dashboard.nav.apiKeys": "API 키",
|
||||
"dashboard.nav.glossaries": "용어집",
|
||||
"dashboard.header.title": "대시보드",
|
||||
"dashboard.header.subtitle": "번역 관리",
|
||||
"dashboard.header.toggleMenu": "메뉴",
|
||||
"dashboard.header.profileTitle": "내 프로필",
|
||||
"dashboard.sidebar.theme": "테마",
|
||||
"dashboard.sidebar.signOut": "로그아웃",
|
||||
"dashboard.sidebar.backHome": "홈으로 돌아가기",
|
||||
"dashboard.sidebar.upgradeToPro": "Pro로 업그레이드 →",
|
||||
"dashboard.translate.pageTitle": "문서 번역",
|
||||
"dashboard.translate.pageSubtitle": "파일을 가져오고 대상 언어를 선택하세요",
|
||||
"dashboard.translate.errorNotificationTitle": "오류",
|
||||
"dashboard.translate.dropzone.uploadAria": "파일 드롭 영역",
|
||||
"dashboard.translate.dropzone.title": "여기에 파일을 드래그 앤 드롭하세요",
|
||||
"dashboard.translate.dropzone.subtitle": "또는 클릭하여 선택 (DOCX, XLSX, PPTX, PDF)",
|
||||
"dashboard.translate.dropzone.replaceFile": "파일 교체",
|
||||
"dashboard.translate.language.source": "원본 언어",
|
||||
"dashboard.translate.language.target": "대상 언어",
|
||||
"dashboard.translate.language.loading": "언어 로딩 중…",
|
||||
"dashboard.translate.language.autoDetect": "자동 감지",
|
||||
"dashboard.translate.language.selectPlaceholder": "선택…",
|
||||
"dashboard.translate.language.loadErrorPrefix": "언어 로딩 실패",
|
||||
"dashboard.translate.provider.loading": "제공업체 로딩 중…",
|
||||
"dashboard.translate.provider.noneConfigured": "구성된 제공업체 없음",
|
||||
"dashboard.translate.provider.modelTitle": "모델",
|
||||
"dashboard.translate.provider.sectionTitle": "제공업체",
|
||||
"dashboard.translate.provider.llmDivider": "AI · 문맥 인식",
|
||||
"dashboard.translate.provider.llmDividerPro": "AI · 문맥 인식 (Pro)",
|
||||
"dashboard.translate.provider.upgrade": "Pro로 업그레이드",
|
||||
"dashboard.translate.provider.upgradeSuffix": "AI 번역 잠금 해제",
|
||||
"dashboard.translate.trust.zeroRetention": "데이터 미보관",
|
||||
"dashboard.translate.trust.deletedAfter": "처리 후 파일 삭제",
|
||||
"dashboard.translate.actions.uploading": "업로드 중…",
|
||||
"dashboard.translate.actions.translate": "번역",
|
||||
"dashboard.translate.actions.filePrefix": "파일: ",
|
||||
"dashboard.translate.actions.cancel": "취소",
|
||||
"dashboard.translate.actions.tryAgain": "다시 시도",
|
||||
"dashboard.translate.steps.uploading": "파일 업로드 중…",
|
||||
"dashboard.translate.steps.starting": "번역 시작 중…",
|
||||
"dashboard.translate.complete.title": "번역 완료!",
|
||||
"dashboard.translate.complete.descNamed": "파일 {name}의 번역이 성공적으로 완료되었습니다.",
|
||||
"dashboard.translate.complete.descGeneric": "파일의 번역이 성공적으로 완료되었습니다.",
|
||||
"dashboard.translate.complete.downloading": "다운로드 중…",
|
||||
"dashboard.translate.complete.download": "다운로드",
|
||||
"dashboard.translate.complete.newTranslation": "새 번역",
|
||||
"dashboard.translate.complete.toastOkTitle": "성공",
|
||||
"dashboard.translate.complete.toastOkDesc": "{name} 다운로드가 완료되었습니다.",
|
||||
"dashboard.translate.complete.toastFailTitle": "실패",
|
||||
"dashboard.translate.complete.toastFailDesc": "번역에 실패했습니다. 다시 시도해 주세요.",
|
||||
"dashboard.translate.sourceDocument": "원본 문서",
|
||||
"dashboard.translate.configuration": "설정",
|
||||
"dashboard.translate.translating": "번역 진행 중",
|
||||
"dashboard.translate.liveMonitor": "실시간 모니터",
|
||||
"dashboard.translate.summary": "요약",
|
||||
"dashboard.translate.engine": "엔진",
|
||||
"dashboard.translate.confidence": "신뢰도",
|
||||
"dashboard.translate.cancel": "취소",
|
||||
"dashboard.translate.segments": "세그먼트",
|
||||
"dashboard.translate.characters": "문자",
|
||||
"dashboard.translate.elapsed": "경과",
|
||||
"dashboard.translate.segPerMin": "세그/분",
|
||||
"dashboard.translate.highQuality": "고품질",
|
||||
"dashboard.translate.quality": "품질",
|
||||
"dashboard.translate.completed": "번역 완료",
|
||||
"dashboard.translate.replace": "교체",
|
||||
"dashboard.translate.pdfMode.title": "PDF 번역 모드",
|
||||
"dashboard.translate.pdfMode.preserveLayout": "레이아웃 유지",
|
||||
"dashboard.translate.pdfMode.textOnly": "텍스트만",
|
||||
"dashboard.translate.pdfMode.preserveLayoutDesc": "이미지, 표 및 서식을 유지합니다. 간단한 PDF에 적합.",
|
||||
"dashboard.translate.pdfMode.textOnlyDesc": "모든 텍스트를 완벽하게 번역합니다. 깔끔한 출력, 레이아웃 문제 없음.",
|
||||
"dashboard.translate.pipeline.upload": "업로드",
|
||||
"dashboard.translate.pipeline.analyze": "분석",
|
||||
"dashboard.translate.pipeline.translate": "번역",
|
||||
"dashboard.translate.pipeline.rebuild": "재구성",
|
||||
"dashboard.translate.pipeline.finalize": "완료",
|
||||
"dashboard.translate.progress.processingFallback": "처리 중…",
|
||||
"dashboard.translate.progress.connectionLost": "연결이 끊어졌습니다. 재시도 중…",
|
||||
"dashboard.translate.progress.failedTitle": "번역 실패",
|
||||
"dashboard.translate.error.unexpected": "예기치 않은 오류가 발생했습니다. 다시 시도해 주세요.",
|
||||
"dashboard.translate.error.noResult": "번역 결과가 없습니다. 문서에 텍스트가 포함되어 있는지 확인하고 다시 시도하거나 다른 엔진을 선택하세요.",
|
||||
"dashboard.translate.error.apiKey": "API 키가 유효하지 않거나 없습니다. 관리자에게 문의하여 API 키를 설정하세요.",
|
||||
"dashboard.translate.error.quota": "사용 한도에 도달했습니다. 몇 분 후에 다시 시도하거나 다른 엔진을 선택하세요.",
|
||||
"dashboard.translate.error.timeout": "번역 서비스 연결 시간이 초과되었습니다. 네트워크를 확인하고 다시 시도하세요.",
|
||||
"dashboard.translate.error.sessionExpired": "세션이 만료되었습니다. 다시 시도를 클릭하여 번역을 재시작하세요.",
|
||||
"dashboard.translate.error.empty": "문서가 비어 있거나 번역 가능한 텍스트가 없습니다 (스캔 PDF 이미지?).",
|
||||
"dashboard.translate.error.unsupported": "지원되지 않는 파일 형식이거나 손상된 파일입니다.",
|
||||
"dashboard.translate.error.connection": "연결이 끊겼습니다. 네트워크를 확인하고 다시 시도하세요.",
|
||||
"dashboard.translate.error.generic": "번역 실패: {detail}",
|
||||
"dashboard.translate.error.title": "번역 실패",
|
||||
"dashboard.translate.retry": "번역 다시 시도",
|
||||
"dashboard.translate.newFile": "새 파일",
|
||||
"dashboard.translate.modeAI": "AI 모드",
|
||||
"dashboard.translate.modeClassic": "클래식 모드",
|
||||
"dashboard.translate.glossaryLLMHint": "AI 모드에서 용어집 사용 가능",
|
||||
"dashboard.translate.submitting": "제출 중...",
|
||||
"dashboard.translate.submit": "번역 시작",
|
||||
"dashboard.translate.noFile": "먼저 파일을 업로드하세요",
|
||||
"dashboard.translate.noTargetLang": "대상 언어를 선택하세요",
|
||||
"dashboard.topbar.interfaceLabel": "번역 인터페이스",
|
||||
"dashboard.topbar.premiumAccess": "프리미엄 액세스",
|
||||
"dashboard.checkoutSyncError": "결제 동기화 오류.",
|
||||
"dashboard.networkRefresh": "네트워크 오류. 페이지를 새로 고치세요.",
|
||||
"dashboard.continueToTranslate": "번역으로 계속"
|
||||
}
|
||||
Reference in New Issue
Block a user