Files
office_translator/frontend/src/lib/i18n/messages/ar/glossaries.json
sepehr fa637abff0
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m49s
perf+security: fix build, secure downloads, dedupe translations, refactor i18n
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).
2026-06-14 16:44:18 +02:00

190 lines
13 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"glossaries.yourGlossaries": "معاجمك",
"glossaries.title": "المعاجم والسياق",
"glossaries.description": "قم بإدارة معاجمك وتعليمات السياق للحصول على ترجمات أكثر دقة.",
"glossaries.createNew": "إنشاء جديد",
"glossaries.empty": "لا توجد معاجم بعد",
"glossaries.emptyDesc": "أنشئ أول معجم لك أو حمّل إعدادًا مسبقًا احترافيًا أعلاه",
"glossaries.defineTerms": "مصطلحات",
"glossaries.aboutTitle": "حول المعاجم",
"glossaries.aboutDesc": "تتيح لك المعاجم تحديد ترجمات دقيقة لمصطلحات معينة. عند الترجمة، يتم استخدام مصطلحات المعجم لضمان ترجمات متسقة ودقيقة.",
"glossaries.aboutFormat": "كل مصطلح له كلمة مصدر وترجمات بلغات متعددة. حدد معجمًا في صفحة الترجمة لتطبيقه.",
"glossaries.toast.created": "تم إنشاء المعجم",
"glossaries.toast.createdDesc": "تم إنشاء المعجم \\\"{name}\\\".",
"glossaries.toast.imported": "تم استيراد المعجم",
"glossaries.toast.importedDesc": "تم استيراد المعجم \\\"{name}\\\".",
"glossaries.toast.updated": "تم تحديث المعجم",
"glossaries.toast.updatedDesc": "تم تحديث المعجم \\\"{name}\\\".",
"glossaries.toast.deleted": "تم حذف المعجم",
"glossaries.toast.deletedDesc": "تم حذف المعجم.",
"glossaries.toast.error": "خطأ",
"glossaries.toast.errorCreate": "فشل إنشاء المعجم",
"glossaries.toast.errorImport": "فشل استيراد المعجم",
"glossaries.toast.errorUpdate": "فشل تحديث المعجم",
"glossaries.toast.errorDelete": "فشل حذف المعجم",
"glossaries.dialog.title": "معجم جديد",
"glossaries.dialog.description": "أنشئ معجمًا لترجماتك",
"glossaries.dialog.nameLabel": "الاسم",
"glossaries.dialog.namePlaceholder": "معجمي",
"glossaries.dialog.tabTemplates": "القوالب",
"glossaries.dialog.tabFile": "ملف",
"glossaries.dialog.tabManual": "يدوي",
"glossaries.dialog.cancel": "إلغاء",
"glossaries.dialog.creating": "جارٍ الإنشاء…",
"glossaries.dialog.importing": "جارٍ الاستيراد…",
"glossaries.dialog.importBtn": "استيراد",
"glossaries.dialog.selectPrompt": "اختيار",
"glossaries.dialog.createBtn": "إنشاء",
"glossaries.dialog.createEmpty": "إنشاء فارغ",
"glossaries.dialog.terms": "مصطلحات",
"glossaries.dialog.templatesDesc": "اختر قالبًا مُعدًّا مسبقًا",
"glossaries.dialog.templatesEmpty": "لا توجد قوالب متاحة",
"glossaries.dialog.dropTitle": "اسحب ملف CSV هنا",
"glossaries.dialog.dropOr": "أو",
"glossaries.dialog.dropFormats": "CSV, TSV, TXT",
"glossaries.termEditor.addTerm": "إضافة مصطلح",
"glossaries.termEditor.maxReached": "تم بلوغ الحد الأقصى {max} من المصطلحات لكل قاموس.",
"glossaries.dialog.formatTitle": "التنسيق",
"glossaries.dialog.formatDesc": "المصدر،الهدف (واحدة لكل سطر)",
"glossaries.dialog.formatNote": "يُتجاهل السطر الأول عند اكتشاف ترويسة",
"glossaries.dialog.errorFormat": "تنسيق غير مدعوم",
"glossaries.dialog.errorSize": "الملف كبير جدًا",
"glossaries.dialog.errorEmpty": "ملف فارغ",
"glossaries.dialog.errorRead": "خطأ في القراءة",
"glossaries.dialog.parsing": "جارٍ التحليل…",
"glossaries.dialog.termsImported": "مصطلحات مستوردة",
"glossaries.dialog.changeFile": "تغيير الملف",
"glossaries.dialog.retry": "إعادة المحاولة",
"glossaries.edit.title": "Modifier le glossaire",
"glossaries.edit.description": "Modifiez le nom, la paire de langues et les termes du glossaire.",
"glossaries.edit.nameLabel": "Nom du glossaire",
"glossaries.edit.namePlaceholder": "Entrez le nom du glossaire...",
"glossaries.edit.sourceLang": "Langue source",
"glossaries.edit.targetLang": "Langue cible",
"glossaries.edit.termsLabel": "Termes ({count} valides)",
"glossaries.edit.exportCsv": "Exporter CSV",
"glossaries.edit.importCsv": "Importer CSV",
"glossaries.edit.cancel": "Annuler",
"glossaries.edit.saving": "Enregistrement...",
"glossaries.edit.saveChanges": "Enregistrer les modifications",
"glossaries.edit.importFailedTitle": "Échec de l'importation",
"glossaries.edit.importFailedMaxDesc": "Le CSV contient {count} termes, le maximum est de {max}. Veuillez réduire le nombre de termes.",
"glossaries.edit.importSuccessTitle": "Importation réussie",
"glossaries.edit.importSuccessDesc": "{count} termes importés avec succès.",
"glossaries.edit.importFailedEmptyDesc": "Aucun terme valide trouvé dans le fichier CSV.",
"glossaries.edit.importFailedReadDesc": "Impossible de lire le fichier CSV.",
"glossaries.delete.title": "Supprimer le glossaire",
"glossaries.delete.description": "Êtes-vous sûr de vouloir supprimer ce glossaire ?",
"glossaries.delete.warning": "Cette action est irréversible",
"glossaries.delete.warningDesc": "Toutes les paires de termes seront définitivement supprimées.",
"glossaries.delete.cancel": "Annuler",
"glossaries.delete.deleting": "Suppression...",
"glossaries.delete.deleteBtn": "Supprimer",
"glossaries.upgrade.title": "Glossaires",
"glossaries.upgrade.description": "Personnalisez vos traductions avec une terminologie personnalisée",
"glossaries.upgrade.feature1": "Créez plusieurs glossaires",
"glossaries.upgrade.feature2": "Définissez des paires de termes source→cible",
"glossaries.upgrade.feature3": "Importez/exportez via CSV",
"glossaries.upgrade.feature4": "Appliquez aux traductions LLM",
"glossaries.upgrade.proFeatureBefore": "Les glossaires sont une fonctionnalité ",
"glossaries.upgrade.proFeatureAfter": ". Passez à un forfait supérieur pour débloquer la terminologie personnalisée.",
"glossaries.upgrade.proLabel": "Pro",
"glossaries.upgrade.upgradeBtn": "Passer à Pro",
"glossaries.loading": "Chargement...",
"glossaries.howItWorks.title": "Comment ces paramètres sont utilisés",
"glossaries.howItWorks.step1Title": "Configurez ici",
"glossaries.howItWorks.step1Desc": "Rédigez vos instructions de contexte ou créez/importez un glossaire de termes.",
"glossaries.howItWorks.step2Title": "Activez dans Traduire",
"glossaries.howItWorks.step2Desc": "Sur la page de traduction, dans la colonne de droite, sélectionnez votre glossaire.",
"glossaries.howItWorks.warning": "Les instructions de contexte s'appliquent automatiquement à toutes vos traductions IA une fois enregistrées. Les glossaires doivent être sélectionnés manuellement sur la page Traduire.",
"glossaries.howItWorks.goToTranslate": "Aller à Traduire",
"glossaries.status.unsaved": "Non enregistré",
"glossaries.status.active": "Actif · s'applique à toutes les traductions IA",
"glossaries.status.inactive": "Inactif",
"glossaries.instructions.whatForBold": "À quoi ça sert ?",
"glossaries.instructions.whatForDesc": "Ces instructions sont envoyées automatiquement à l'IA avant chaque traduction, sans que vous ayez besoin de faire quoi que ce soit sur la page Traduire. Utilisez-les pour guider le style, le registre ou la terminologie générale.",
"glossaries.instructions.example": "Exemple : « Vous traduisez des rapports financiers. Soyez formel, précis et conservez tous les chiffres. »",
"glossaries.instructions.charCount": "{count} caractères",
"glossaries.instructions.emptyHint": "Vide — aucune instruction envoyée à l'IA",
"glossaries.instructions.clearAll": "Tout effacer",
"glossaries.instructions.saving": "Enregistrement…",
"glossaries.instructions.saved": "Enregistré",
"glossaries.presets.whatForBold": "À quoi ça sert ?",
"glossaries.presets.whatForDesc": "Cliquer sur une carte crée un glossaire pré-rempli avec les termes spécialisés du domaine. Ce glossaire apparaîtra dans vos glossaires ci-dessous, et vous pourrez le sélectionner manuellement sur la page Traduire pour forcer des traductions de termes précis.",
"glossaries.presets.clickHint": "Cliquez sur une carte → glossaire créé → sélectionnez-le dans Traduire",
"glossaries.presets.creating": "Création…",
"glossaries.presets.alreadyImported": "Déjà importé",
"glossaries.presets.it.title": "IT / Logiciel",
"glossaries.presets.it.desc": "Développement, infrastructure, DevOps",
"glossaries.presets.legal.title": "Juridique / Contrats",
"glossaries.presets.legal.desc": "Droit des affaires, contentieux",
"glossaries.presets.medical.title": "Médical / Santé",
"glossaries.presets.medical.desc": "Pharmacologie, chirurgie, diagnostic",
"glossaries.presets.finance.title": "Finance / Comptabilité",
"glossaries.presets.finance.desc": "IFRS, bilans, fiscalité",
"glossaries.presets.marketing.title": "Marketing / Publicité",
"glossaries.presets.marketing.desc": "Digital, branding, analytics",
"glossaries.presets.hr.title": "RH / Ressources Humaines",
"glossaries.presets.hr.desc": "Contrats, politiques, recrutement",
"glossaries.presets.scientific.title": "Scientifique / Recherche",
"glossaries.presets.scientific.desc": "Publications, thèses, articles",
"glossaries.presets.ecommerce.title": "E-commerce / Vente",
"glossaries.presets.ecommerce.desc": "Boutiques en ligne, catalogues, CRM",
"glossaries.grid.title": "Vos",
"glossaries.grid.titleHighlight": "glossaires",
"glossaries.grid.countWithAction": "{count} glossaire({plural}) — cliquez sur une carte pour la modifier",
"glossaries.grid.emptyAction": "Créez votre premier glossaire ou importez un preset ci-dessus",
"glossaries.grid.activeTranslation": "Traduction active :",
"glossaries.grid.goToTranslate": "Aller à Traduire pour activer",
"glossaries.badge.compatible": "Compatible",
"glossaries.badge.otherTarget": "Autre cible",
"glossaries.card.editTerms": "Modifier les termes",
"glossaries.card.created": "تم الإنشاء",
"glossaries.card.term": "مصطلح",
"glossaries.card.delete": "Supprimer",
"glossaries.grid.searchPlaceholder": "Search a glossary…",
"glossaries.grid.noResults": "No results for this search.",
"glossaries.detail.backToList": "Back to glossaries",
"glossaries.detail.save": "Save",
"glossaries.detail.savedTitle": "Saved",
"glossaries.detail.savedDesc": "The glossary has been updated.",
"glossaries.detail.settingsTitle": "Settings",
"glossaries.detail.sourceLang": "Source language",
"glossaries.detail.targetLang": "Target language",
"glossaries.detail.termsTitle": "Terms",
"glossaries.detail.terms": "terms",
"glossaries.detail.searchTerms": "Filter…",
"glossaries.detail.noTerms": "No terms yet.",
"glossaries.detail.addFirstTerm": "Add the first term",
"glossaries.detail.addTerm": "Add a term",
"glossaries.detail.maxReached": "Maximum limit reached",
"glossaries.detail.source": "Source",
"glossaries.detail.target": "Target",
"glossaries.detail.sourcePlaceholder": "source term",
"glossaries.detail.targetPlaceholder": "target term",
"glossaries.detail.csvTitle": "CSV",
"glossaries.detail.csvDesc": "Export your terms as CSV or import new ones (replaces the current list).",
"glossaries.detail.export": "Export",
"glossaries.detail.import": "Import",
"glossaries.detail.dangerTitle": "Danger zone",
"glossaries.detail.dangerDesc": "Deletion is permanent. All associated terms will be lost.",
"glossaries.detail.deleteGlossary": "Delete this glossary",
"glossaries.detail.confirmDelete": "Confirm deletion?",
"glossaries.detail.confirm": "Confirm",
"glossaries.detail.cancel": "Cancel",
"glossaries.detail.sourceLangNote": "'The original source is in French. For other languages, we read the term's translations field (if available).'",
"glossaries.detail.sourceLocked": "fixed",
"glossaries.detail.sourceLockedNote": "Templates only store the source in French. Multilingual source is on the roadmap.",
"glossaries.detail.targetLangNote": "Pick a language to see the matching translations, or « Multilingual » for the default value.",
"glossaries.detail.notFoundTitle": "Glossary not found",
"glossaries.detail.notFoundDesc": "This glossary does not exist or you don't have access to it.",
"glossaries.detail.maxTermsTitle": "Limit reached",
"glossaries.detail.maxTermsDesc": "Maximum {max} terms per glossary.",
"glossaries.detail.importEmptyTitle": "Empty file",
"glossaries.detail.importEmptyDesc": "No terms detected in this file.",
"glossaries.detail.importedTitle": "Imported",
"glossaries.detail.importedDesc": "{count} terms imported.",
"glossaries.detail.importErrorTitle": "Read error",
"glossaries.detail.importErrorDesc": "Unable to read the file."
}