perf+security: fix build, secure downloads, dedupe translations, refactor i18n
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:
2026-06-14 16:44:18 +02:00
parent eda6821632
commit fa637abff0
365 changed files with 23910 additions and 14955 deletions

View 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": "هوش مصنوعی · آگاه به زمینه",
"dashboard.translate.provider.llmDividerPro": "هوش مصنوعی · آگاه به زمینه (حرفه‌ای)",
"dashboard.translate.provider.upgrade": "ارتقا به حرفه‌ای",
"dashboard.translate.provider.upgradeSuffix": "برای دسترسی به ترجمه هوش مصنوعی",
"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 نامعتبر یا ناموجود است. با مدیر تماس بگیرید تا کلیدها را پیکربندی کند.",
"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": "حالت هوش مصنوعی",
"dashboard.translate.modeClassic": "حالت کلاسیک",
"dashboard.translate.glossaryLLMHint": "واژه‌نامه‌ها در حالت هوش مصنوعی موجودند",
"dashboard.translate.submitting": "در حال ارسال...",
"dashboard.translate.submit": "شروع ترجمه",
"dashboard.translate.noFile": "ابتدا یک فایل آپلود کنید",
"dashboard.translate.noTargetLang": "زبان مقصد را انتخاب کنید",
"dashboard.topbar.interfaceLabel": "رابط ترجمه",
"dashboard.topbar.premiumAccess": "دسترسی ویژه",
"dashboard.checkoutSyncError": "خطا در همگام‌سازی پرداخت.",
"dashboard.networkRefresh": "خطای شبکه. لطفاً صفحه را تازه‌سازی کنید.",
"dashboard.continueToTranslate": "ادامه به ترجمه"
}