fix: show target-language-specific translations in preview + rename migration
Some checks failed
Deploy to Production / Build and Deploy (push) Has been cancelled

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>
This commit is contained in:
2026-05-31 23:22:50 +02:00
parent a79ce0fc9b
commit b4096fd2ca
4 changed files with 434 additions and 11 deletions

View File

@@ -117,3 +117,211 @@ async def send_email_async(to: str, subject: str, body: str) -> bool:
except Exception as e:
logger.error(f"Failed to send email (async) to {to}: {e}")
return False
async def send_subscription_email_async(
to_email: str,
user_name: str,
event_type: str,
details: dict
) -> bool:
"""
Send subscription transactional email in French.
event_type can be: 'activated', 'cancelled', 'ended', 'payment_failed', 'credits_purchased'
"""
display_name = user_name or to_email.split("@")[0]
frontend_url = os.getenv("FRONTEND_URL", "http://localhost:3000")
dashboard_url = f"{frontend_url}/dashboard"
profile_url = f"{frontend_url}/dashboard/profile?tab=subscription"
pricing_url = f"{frontend_url}/pricing"
subject = "Notification Office Translator"
header_title = "Office Translator"
content_html = ""
if event_type == "activated":
plan_name = details.get("plan_name", "Premium").capitalize()
ends_at = details.get("ends_at", "")
subject = "Votre abonnement Office Translator est activé ! 🚀"
header_title = "Bienvenue chez Office Translator"
content_html = f"""
<p>Bonjour {display_name},</p>
<p>Nous sommes ravis de vous compter parmi nos membres Premium ! Votre abonnement au forfait <strong>{plan_name}</strong> est désormais actif.</p>
<div class="info-box">
<div class="info-title">Détails de votre abonnement :</div>
<p style="margin: 4px 0;"><strong>Forfait :</strong> {plan_name}</p>
{"<p style='margin: 4px 0;'><strong>Date de renouvellement :</strong> " + ends_at + "</p>" if ends_at else ""}
</div>
<p>Vous pouvez dès à présent profiter de toutes les fonctionnalités premium, y compris la traduction illimitée de documents et l'accès à nos meilleurs modèles d'IA.</p>
<div class="button-container">
<a href="{dashboard_url}" class="button">Accéder à mon tableau de bord</a>
</div>
"""
elif event_type == "cancelled":
ends_at = details.get("ends_at", "")
subject = "Confirmation d'annulation de votre abonnement - Office Translator"
header_title = "Abonnement annulé"
content_html = f"""
<p>Bonjour {display_name},</p>
<p>Nous vous confirmons que l'annulation de votre abonnement a bien été prise en compte à votre demande.</p>
<div class="info-box">
<div class="info-title">Informations importantes :</div>
{"<p style='margin: 4px 0;'><strong>Accès actif jusqu'au :</strong> " + ends_at + "</p>" if ends_at else ""}
<p style="margin: 4px 0;">Aucun autre prélèvement ne sera effectué.</p>
</div>
<p>Vous conservez un accès complet à toutes les fonctionnalités premium de votre forfait jusqu'à la fin de la période de facturation en cours.</p>
<p>Si vous changez d'avis, vous pouvez réactiver votre abonnement à tout moment depuis votre profil.</p>
<div class="button-container">
<a href="{profile_url}" class="button">Gérer mon compte</a>
</div>
"""
elif event_type == "ended":
subject = "Votre accès premium a expiré - Office Translator"
header_title = "Accès premium terminé"
content_html = f"""
<p>Bonjour {display_name},</p>
<p>Votre abonnement Office Translator a pris fin et votre compte a été basculé vers le forfait <strong>Gratuit</strong>.</p>
<p>Vous pouvez toujours traduire des documents dans la limite de votre quota gratuit. Vos documents et votre historique restent disponibles.</p>
<p>Pour retrouver l'accès à la traduction rapide, sans limites et à nos modèles IA avancés, vous pouvez vous réabonner en un clic.</p>
<div class="button-container">
<a href="{pricing_url}" class="button">Voir nos forfaits</a>
</div>
"""
elif event_type == "payment_failed":
subject = "Échec de paiement - Action requise - Office Translator ⚠️"
header_title = "Problème de paiement"
content_html = f"""
<p>Bonjour {display_name},</p>
<p>Nous n'avons pas pu prélever le règlement de votre abonnement Office Translator. Notre tentative de paiement a échoué.</p>
<p>Pour éviter toute interruption de votre service premium, nous vous invitons à mettre à jour vos coordonnées de paiement dès que possible.</p>
<div class="button-container">
<a href="{profile_url}" class="button">Mettre à jour mon moyen de paiement</a>
</div>
<p>Si vous avez des questions, n'hésitez pas à répondre à cet e-mail.</p>
"""
elif event_type == "credits_purchased":
credits = details.get("credits", 0)
subject = "Achat de crédits réussi - Office Translator 💳"
header_title = "Crédits ajoutés"
content_html = f"""
<p>Bonjour {display_name},</p>
<p>Nous vous remercions pour votre achat ! Vos crédits supplémentaires ont bien été crédités sur votre compte.</p>
<div class="info-box">
<div class="info-title">Détails de l'achat :</div>
<p style="margin: 4px 0;"><strong>Crédits achetés :</strong> +{credits}</p>
<p style="margin: 4px 0;">Chaque crédit vous permet de traduire une page de document supplémentaire.</p>
</div>
<p>Vous pouvez suivre votre solde de crédits et l'historique de vos traductions depuis votre tableau de bord.</p>
<div class="button-container">
<a href="{dashboard_url}" class="button">Traduire un document</a>
</div>
"""
else:
return False
full_html = f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {{
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #f8fafc;
color: #334155;
margin: 0;
padding: 0;
}}
.wrapper {{
width: 100%;
background-color: #f8fafc;
padding: 40px 0;
}}
.container {{
max-width: 600px;
margin: 0 auto;
background: #ffffff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
border: 1px solid #e2e8f0;
}}
.header {{
background: linear-gradient(135deg, #4f46e5, #06b6d4);
padding: 32px;
text-align: center;
color: #ffffff;
}}
.header h1 {{
margin: 0;
font-size: 24px;
font-weight: 700;
letter-spacing: -0.025em;
}}
.content {{
padding: 32px;
line-height: 1.6;
}}
.content p {{
margin-top: 0;
margin-bottom: 16px;
}}
.button-container {{
text-align: center;
margin: 32px 0;
}}
.button {{
background-color: #4f46e5;
color: #ffffff !important;
padding: 12px 32px;
text-decoration: none;
font-weight: 600;
border-radius: 8px;
display: inline-block;
box-shadow: 0 4px 6px -1px rgba(79, 70, 229, 0.2);
}}
.footer {{
background-color: #f1f5f9;
padding: 24px;
text-align: center;
font-size: 12px;
color: #64748b;
border-top: 1px solid #e2e8f0;
}}
.footer p {{
margin: 4px 0;
}}
.info-box {{
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 16px;
margin-bottom: 24px;
}}
.info-title {{
font-weight: 600;
color: #1e293b;
margin-bottom: 8px;
}}
</style>
</head>
<body>
<div class="wrapper">
<div class="container">
<div class="header">
<h1>{header_title}</h1>
</div>
<div class="content">
{content_html}
</div>
<div class="footer">
<p>Cet e-mail automatique a été envoyé par <strong>Office Translator</strong>.</p>
<p>&copy; {datetime.now().year} Office Translator. Tous droits réservés.</p>
</div>
</div>
</div>
</body>
</html>
"""
return await send_email_async(to_email, subject, full_html)