Le groupement par template_id etait faux sur la prod :
- Les doublons historiques ont template_id=NULL (crees avant la migration)
- Deux glossaires 'Finance - FR->Anglais' et 'Finance - FR->Multilingue'
partagent le meme template_id mais DOIVENT etre conserves separement.
Changements :
- Groupement par (user_id, name) -> c'est ce que l'utilisateur voit dans l'UI
et la definition reelle d'un doublon.
- Les glossaires multilingues ('-> Multilingue') ont un nom distinct des
versions '-> Anglais' : ils ne sont jamais fusionnes (preserve par design).
- Fallback automatique si la colonne template_id est absente du schema
(dev DB) : warning + requete sans la colonne, aucun crash.
- Suppression du flag --allow-missing-template-id devenu inutile.
- Nettoyage des imports ORM inutiles (text brut uniquement, plus rapide).
- scripts/backup_duplicate_glossaries.py : exporte en JSON les doublons
(meme user_id + template_id) sans rien supprimer. Schema validation,
tri stable, mode degrade si colonne template_id absente.
- scripts/delete_duplicate_glossaries.py : lit un backup JSON et supprime
les doublons listes. Validation IDs, confirmation interactive,
commit par user, mode --dry-run / --yes.
- .gitea/workflows/cleanup-glossaries.yml : workflow_dispatch qui SSH
sur le serveur de prod et execute le script dans le conteneur backend
(postgres demarre, .env charge, env_file docker-compose).
- Add template_id column to Glossary model (nullable, indexed)
- Backend: return 409 Conflict if user already imported a template
- Frontend: disable preset cards already imported, show 'Imported' badge
- Fix duplicated text in GlossarySelector source warning (hardcoded FR text removed)
- Complete i18n migration for glossaries page and GlossarySelector
- Add glossaries.presets.alreadyImported key in all 13 locales
47 new i18n keys added across all 13 locales (en, fr, es, de, pt, it,
nl, ru, ja, ko, zh, ar, fa). English and French are fully translated,
remaining locales use French as placeholder.
Files migrated:
- EditGlossaryDialog.tsx (18 strings)
- DeleteGlossaryDialog.tsx (7 strings)
- ProUpgradePrompt.tsx (10 strings)
- WebhookSnippet.tsx (4 strings)
- TranslationModeToggle.tsx (8 strings)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When the user changes the target language dropdown in the edit dialog,
terms now remap to show the language-specific translation from the
translations dict. E.g. changing to Spanish shows 'servidor' instead
of 'server'. Falls back to default English if no translation exists.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Users can now change the language pair when editing a glossary:
- EditGlossaryDialog has source/target language dropdowns
- Default target_language changed from 'en' to 'multi' in create dialog
- onSave passes source_language and target_language to the backend
- Backend PATCH endpoint already supports updating these fields
Also fixes:
- CreateGlossaryDialog defaults to 'multi' instead of 'en'
- SUPPORTED_LANGUAGES now includes 'multi' option for target
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
18 glossaries accumulated from multiple imports. This migration:
1. Deletes ALL glossaries with target_language='en' (old stale imports)
2. Deduplicates multilingual glossaries keeping only the newest per name
Result: 8 clean glossaries (one per template), all marked as multilingual.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The /dashboard/glossaries page had two issues:
1. SUPPORTED_LANGUAGES had no 'multi' code, so multilingual glossaries
showed '🌐 undefined' instead of '🌐 Multilingue'
2. The Compatible/Autre cible badge compared target_language directly
with currentTargetLang, so 'multi' never matched → always showed
'Autre cible'. Now 'multi' is treated as compatible with any target.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Old glossaries imported before enrichment have target_language='en' and no
translations dict — they're stale duplicates. This migration:
1. Deletes old FR→EN glossaries with empty/null translations (no Persian, etc.)
2. Renames multilingual glossaries from 'Anglais' to 'Multilingue'
3. Sets ALL remaining FR glossaries to target_language='multi'
4. Users re-import from enriched templates to get Persian/Arabic/etc. terms
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two issues found in screenshot review:
1. Glossary preview always showed English 'target' field (foie → liver)
instead of the language-specific translation. Now looks up
term.translations[targetLang] first, falls back to term.target.
When target is Persian, preview shows 'foie → کبد' (Persian).
2. Previous migration b7c8d9e0f1a2 was already applied on server before
the rename was added. New migration c8d9e0f1a2b3 handles the rename
separately: glossaries with target_language='multi' get their name
changed from 'Anglais' to 'Multilingue'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Full audit found 18 issues across backend, frontend, and data files.
Root cause: target_language had no single source of truth — 'en' was
hardcoded as default in 6+ places while templates are actually multilingual.
Fixes applied:
- routes/glossary_routes.py: list_glossaries() fallback 'en' → 'multi'
- routes/glossary_routes.py: import reads target_lang from index.json
(source of truth) instead of template file
- data/glossaries/*.json: all 8 template files target_lang 'en' → 'multi'
- services/glossary_service.py: get_glossary_terms() now returns
target_language field (was missing entirely)
- schemas/glossary_schemas.py: all defaults 'en' → 'multi'
(GlossaryCreate, GlossaryResponse, GlossaryListItem)
- useTranslationConfig.ts: only reset glossary on sourceLang change,
not targetLang (multilingual glossaries work with any target)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The migration already sets target_language='multi' but the glossary name
in the DB still said 'Français → Anglais'. Now it renames them to
'Français → Multilingue' so users aren't confused when translating to
Persian, Arabic, etc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous migration used revision 'a1b2c3d4e5f6' which was already
taken by 005_add_reset_token_to_users.py, causing a cycle. Also both
f6a7b8c9d0e1 and the new migration pointed to the same down_revision.
Fixed:
- New unique revision ID: b7c8d9e0f1a2
- down_revision points to f6a7b8c9d0e1 (current head)
- Chain: e5b2c9d1f4a8 → f6a7b8c9d0e1 → b7c8d9e0f1a2
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Templates enriched by enrich_glossary_templates.py already contain
translations for de, es, it, pt, nl, ru, ja, ko, zh, ar, fa (including
Persian). But they were labeled FR→EN, causing incorrect filtering and
warnings when translating to other languages.
Changes:
- index.json: set target_lang='multi' for all 8 templates
- GlossarySelector: treat target_language='multi' as compatible with
any target language (no false warnings, auto-select works)
- GlossarySelector: display '🌐 MULTILINGUE' badge instead of EN flag
- glossary_routes: default target_language to 'multi' instead of 'en'
- Migration: detect existing multilingual glossaries in DB (5+ keys in
translations JSON) and set their target_language to 'multi'
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When activating the glossary toggle, only auto-select a glossary if it
matches BOTH source and target language. If no matching glossary exists
(e.g. translating to Persian but only have FR→EN glossaries), leave the
selector empty so the user picks manually instead of forcing an
incompatible glossary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- GlossarySelector: filteredGlossaries now sorts matching target_language
first so FR→FA appears before FR→EN when translating to Persian
- GlossarySelector: toggle auto-select prefers glossary matching
targetLang, falls back to first available
- useTranslationConfig: reset glossaryId when targetLang changes (was
only resetting on sourceLang change)
- glossary_routes: set target_language from template data on import
(was missing, defaulted to None/en)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Create Stripe products/prices (Starter/Pro/Business monthly+yearly)
- Fix CRITICAL bug: add subscription_ends_at + cancel_at_period_end columns to users table
- Alembic migration: f6a7b8c9d0e1_add_subscription_ends_at_cancel_at_period_end
- Fix: implement handle_payment_failed() to set subscription_status=PAST_DUE
- Fix: harmonize .env.production Stripe variable names to match pricing_config.py
- Fix: add missing FRONTEND_URL and STRIPE_PUBLISHABLE_KEY to .env.production
- Add all Stripe Price IDs (test mode) to .env.production
- Wire credit purchase buttons to /api/v1/auth/create-credits-checkout
- Dashboard sync post-checkout was already implemented (no change needed)
Stripe test keys: configured in .env.production
Webhook: must be configured on server via stripe CLI or Stripe Dashboard
Webhook URL: https://wordly.art/api/v1/auth/webhook/stripe
- US1: Replace exposed provider names (DeepSeek V3 → IA Express, MiniMax → IA Avancée)
in legacy_routes.py, remove 52 DeepSeek references from i18n pricing/landing keys,
add generic modelName key across all 13 languages
- US2: Fix glossary selector filtering (fallback to all glossaries when source lang
filter returns empty), show templates by default, fix 3 broken presets
(hvac/construction/automotive → hr/scientific/ecommerce)
- US3: Replace 15 hardcoded French strings with i18n t() calls, increase font sizes
from 8-9px to 11px, improve text contrast (opacity /20→/40, /25→/45, /30→/50)
- US4: Add missing provider labels in admin ProviderStatus (deepseek, minimax, zai,
google_cloud, openrouter, openrouter_premium)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Google Cloud Translation API was returning "Requests to this API are
blocked" which got wrapped as a misleading "Erreur lors de la lecture
du fichier PowerPoint". Now probes the key once (cached 10min) and
falls back to deep_translator (free) when the Cloud key is invalid.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Restructure right sidebar as flex-col with scrollable config and
fixed translate button (never scrolls away)
- Replace expanded GlossarySelector list with compact dropdown (~80px
instead of ~400px), collapsible templates section, click-outside close
- Add visible Classic/IA mode badge after provider selector with hint
about glossary availability for Pro users in classic mode
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>