v1.0.0 🎉 — Production release. i18n Espagnol + Allemand (launcher + emails).
Première release MAJEURE après ~30 itérations 0.x. Le système est complet et stable pour la production : distribution PROSERVE multi-channels, install launcher cross-fleet, monitoring santé VR, sécurité (signatures Ed25519, HMAC URLs présignées, settings lock per-license, RFC1918 filter cache LAN). i18n nouvelles langues (Espagnol + Allemand) : - Strings.cs : signature T() étendue avec 2 params optionnels (es?/de?) en plus des 5 obligatoires. Fallback automatique sur l'anglais quand non traduit → infrastructure prête immédiatement, traduction progressive sur les ~317 strings du launcher selon les besoins terrain. - ~30 strings critiques traduites maintenant (top bar, body, status badges, actions principales Launch/Install/Cancel/Yes/No, MessageBox titles). - Available[] inclut es/Español + de/Deutsch ; auto-détection Windows pour es-* / de-* funcionne au premier launch. - Init() whitelist mise à jour. Emails release announce localisés par license : - Migration 005 : ajout `language VARCHAR(8) NULL` sur licenses (whitelist fr/en/es/de/zh/th/ar, NULL = fallback English). - getEmailStrings() PHP étendu : dictionnaire 7 langues × 14 strings. Traduction complète es + de (gérable : 28 nouvelles strings, vs 634 pour traduire le launcher en intégralité). - notify_release boucle par license : récupère language + locale fallback 'en' + render avec la bonne locale → chaque destinataire reçoit son email dans la langue de SA license, indépendamment des autres clients. - Subject localisé aussi (« PROSERVE v1.5.4 ya está disponible » / « PROSERVE v1.5.4 ist jetzt verfügbar »). - Release notes body reste TOUJOURS en anglais (évite d'avoir à maintenir N traductions du Markdown des notes). - Direction RTL préservée pour l'arabe (dir="rtl" sur <html> + <table>). Admin UI : dropdown langue dans la modal Contacts d'une license + whitelist backend (set_contact_emails accepte fr/en/es/de/zh/th/ar). Bumps : 0.29.10 → 1.0.0 — milestone V1, premier release majeur en prod. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -102,33 +102,212 @@ function find_entry_index_by_id(array $manifest, string $id): ?int
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit le corps HTML de l'email de release announce. Message en anglais
|
||||
* (= langue par défaut du parc client international VR PROSERVE), inclut les
|
||||
* release notes brutes en Markdown (sans rendu — les MUA modernes Markdown
|
||||
* basique en clair reste très lisible et évite la dépendance Markdig PHP).
|
||||
* Le owner_name de la license est utilisé pour personnaliser le greeting.
|
||||
* Le logo (optionnel via <c>notifications.logo_url</c>) est inséré dans le
|
||||
* bandeau d'en-tête bleu — hotlinkable depuis n'importe quel MUA (pas de CID
|
||||
* embed pour rester compatible avec Gmail/Outlook qui bloquent les data URIs).
|
||||
* Strings localisées pour l'email de release announce. Couvre les 5 langues
|
||||
* supportées par le launcher (Strings.cs côté .NET) : fr, en, zh, th, ar.
|
||||
* Le release notes BODY lui-même reste TOUJOURS en anglais — seul le texte
|
||||
* d'accompagnement (greeting, instructions, CTAs) est localisé. Permet de
|
||||
* notifier des clients internationaux dans leur langue sans avoir à
|
||||
* maintenir N traductions du Markdown des notes.
|
||||
*
|
||||
* Placeholders dans les chaînes :
|
||||
* {version} → numéro de version (ex. "1.5.4.16")
|
||||
* {owner} → nom du client (ex. "Asterion VR")
|
||||
*
|
||||
* Fallback : si la locale demandée n'est pas en table → 'en' (= comportement
|
||||
* conservateur, le marché VR international a globalement l'anglais comme
|
||||
* lingua franca tech).
|
||||
*
|
||||
* @return array<string, string> dictionnaire de strings prêtes à interpoler
|
||||
*/
|
||||
function renderReleaseAnnounceEmail(string $version, string $ownerName, string $notesMarkdown, string $baseUrl, string $logoSrc = ''): string
|
||||
function getEmailStrings(string $locale): array
|
||||
{
|
||||
static $cache = null;
|
||||
if ($cache !== null) {
|
||||
return $cache[$locale] ?? $cache['en'];
|
||||
}
|
||||
$cache = [
|
||||
'en' => [
|
||||
'subject' => 'PROSERVE v{version} is now available',
|
||||
'title' => 'PROSERVE v{version} is available!',
|
||||
'subtitle' => 'A new version is ready to install via your PROSERVE Launcher.',
|
||||
'greeting' => 'Hi {owner},',
|
||||
'intro' => 'We\'re happy to let you know that <strong>PROSERVE version {version}</strong> has just been published and is now available for download.',
|
||||
'instructions' => 'If you already have the <strong>PROSERVE Launcher</strong> installed, simply open it on any of your machines, click <em>« Check for updates »</em>, and install the new version with one click.',
|
||||
'firstInstallTitle' => 'First install or new workstation?',
|
||||
'firstInstallBody' => 'If this is your first time, or you\'re setting up a new PC, download the PROSERVE Launcher installer below. <strong>You\'ll need to run it on EACH workstation</strong> where PROSERVE will be used.',
|
||||
'downloadButton' => '⬇ Download PROSERVE Launcher (Windows)',
|
||||
'directLink' => 'Direct link:',
|
||||
'releaseNotesTitle' => 'Release notes',
|
||||
'releaseNotesNone' => 'No release notes for this version.',
|
||||
'support' => 'As always, if you run into any issue (download failure, install error, anything weird in-game), don\'t hesitate to reach out — we\'re here to help.',
|
||||
'closing' => 'Happy training!',
|
||||
'signature' => 'The ASTERION VR team',
|
||||
'footer' => 'This automated notification was sent because your contact email is registered with one of your PROSERVE licenses.<br/>To stop receiving these, ask your ASTERION VR account manager to remove your address from the license contacts.',
|
||||
],
|
||||
'fr' => [
|
||||
'subject' => 'PROSERVE v{version} est disponible',
|
||||
'title' => 'PROSERVE v{version} est disponible !',
|
||||
'subtitle' => 'Une nouvelle version est prête à installer via votre PROSERVE Launcher.',
|
||||
'greeting' => 'Bonjour {owner},',
|
||||
'intro' => 'Nous avons le plaisir de vous annoncer que <strong>la version {version} de PROSERVE</strong> vient d\'être publiée et est désormais disponible au téléchargement.',
|
||||
'instructions' => 'Si vous avez déjà le <strong>PROSERVE Launcher</strong> installé, ouvrez-le simplement sur n\'importe lequel de vos postes, cliquez sur <em>« Vérifier les mises à jour »</em> et installez la nouvelle version en un clic.',
|
||||
'firstInstallTitle' => 'Première installation ou nouveau poste ?',
|
||||
'firstInstallBody' => 'S\'il s\'agit de votre première installation, ou si vous configurez un nouveau PC, téléchargez le PROSERVE Launcher ci-dessous. <strong>Il doit être lancé sur CHAQUE poste</strong> où PROSERVE sera utilisé.',
|
||||
'downloadButton' => '⬇ Télécharger PROSERVE Launcher (Windows)',
|
||||
'directLink' => 'Lien direct :',
|
||||
'releaseNotesTitle' => 'Notes de version',
|
||||
'releaseNotesNone' => 'Pas de notes de version pour cette release.',
|
||||
'support' => 'Comme toujours, en cas de problème (échec de téléchargement, erreur d\'installation, comportement inattendu en VR), n\'hésitez pas à nous contacter — nous sommes là pour vous aider.',
|
||||
'closing' => 'Bon entraînement !',
|
||||
'signature' => 'L\'équipe ASTERION VR',
|
||||
'footer' => 'Cette notification automatique a été envoyée parce que votre email est enregistré comme contact sur l\'une de vos licenses PROSERVE.<br/>Pour ne plus recevoir ces emails, demandez à votre gestionnaire de compte ASTERION VR de retirer votre adresse des contacts license.',
|
||||
],
|
||||
'zh' => [
|
||||
'subject' => 'PROSERVE v{version} 现已发布',
|
||||
'title' => 'PROSERVE v{version} 已发布!',
|
||||
'subtitle' => '新版本已准备好通过您的 PROSERVE Launcher 安装。',
|
||||
'greeting' => '你好 {owner},',
|
||||
'intro' => '我们很高兴地通知您,<strong>PROSERVE 版本 {version}</strong> 刚刚发布,现已可供下载。',
|
||||
'instructions' => '如果您已经安装了 <strong>PROSERVE Launcher</strong>,只需在任何一台机器上打开它,单击<em>「检查更新」</em>,然后单击「安装」即可完成新版本的安装。',
|
||||
'firstInstallTitle' => '首次安装或新工作站?',
|
||||
'firstInstallBody' => '如果这是您的第一次使用,或者您正在配置一台新 PC,请在下方下载 PROSERVE Launcher 安装程序。<strong>您需要在使用 PROSERVE 的每台工作站上运行它</strong>。',
|
||||
'downloadButton' => '⬇ 下载 PROSERVE Launcher (Windows)',
|
||||
'directLink' => '直接链接:',
|
||||
'releaseNotesTitle' => '版本说明',
|
||||
'releaseNotesNone' => '此版本没有版本说明。',
|
||||
'support' => '一如既往,如果您遇到任何问题(下载失败、安装错误、游戏中出现异常情况),请随时联系我们 — 我们随时为您提供帮助。',
|
||||
'closing' => '训练愉快!',
|
||||
'signature' => 'ASTERION VR 团队',
|
||||
'footer' => '此自动通知已发送,因为您的联系电子邮件已注册到您的某个 PROSERVE 许可证中。<br/>要停止接收这些通知,请要求您的 ASTERION VR 客户经理从许可证联系人中删除您的地址。',
|
||||
],
|
||||
'th' => [
|
||||
'subject' => 'PROSERVE v{version} พร้อมใช้งานแล้ว',
|
||||
'title' => 'PROSERVE v{version} พร้อมใช้งาน!',
|
||||
'subtitle' => 'เวอร์ชันใหม่พร้อมติดตั้งผ่าน PROSERVE Launcher ของคุณ',
|
||||
'greeting' => 'สวัสดี {owner},',
|
||||
'intro' => 'เรายินดีที่จะแจ้งให้คุณทราบว่า <strong>PROSERVE เวอร์ชัน {version}</strong> เพิ่งได้รับการเผยแพร่และพร้อมให้ดาวน์โหลดแล้ว',
|
||||
'instructions' => 'หากคุณติดตั้ง <strong>PROSERVE Launcher</strong> ไว้แล้ว เพียงเปิดบนเครื่องใดก็ได้ของคุณ คลิก <em>« ตรวจสอบการอัปเดต »</em> แล้วติดตั้งเวอร์ชันใหม่ด้วยคลิกเดียว',
|
||||
'firstInstallTitle' => 'ติดตั้งครั้งแรก หรือเวิร์กสเตชันใหม่?',
|
||||
'firstInstallBody' => 'หากเป็นครั้งแรกของคุณ หรือคุณกำลังตั้งค่า PC ใหม่ ดาวน์โหลด PROSERVE Launcher ด้านล่าง <strong>คุณต้องเรียกใช้บนเวิร์กสเตชันทุกเครื่อง</strong>ที่จะใช้ PROSERVE',
|
||||
'downloadButton' => '⬇ ดาวน์โหลด PROSERVE Launcher (Windows)',
|
||||
'directLink' => 'ลิงก์โดยตรง:',
|
||||
'releaseNotesTitle' => 'บันทึกการเผยแพร่',
|
||||
'releaseNotesNone' => 'ไม่มีบันทึกการเผยแพร่สำหรับเวอร์ชันนี้',
|
||||
'support' => 'เช่นเคย หากคุณพบปัญหาใดๆ (ดาวน์โหลดล้มเหลว ติดตั้งผิดพลาด อะไรแปลกๆ ในเกม) อย่าลังเลที่จะติดต่อ — เราพร้อมช่วยเหลือ',
|
||||
'closing' => 'ฝึกซ้อมให้สนุก!',
|
||||
'signature' => 'ทีม ASTERION VR',
|
||||
'footer' => 'การแจ้งเตือนอัตโนมัตินี้ถูกส่งเนื่องจากอีเมลติดต่อของคุณถูกลงทะเบียนกับหนึ่งในใบอนุญาต PROSERVE ของคุณ<br/>เพื่อหยุดรับการแจ้งเตือนเหล่านี้ ขอให้ผู้จัดการบัญชี ASTERION VR ของคุณลบที่อยู่ของคุณออกจากผู้ติดต่อใบอนุญาต',
|
||||
],
|
||||
'ar' => [
|
||||
'subject' => 'PROSERVE v{version} متوفر الآن',
|
||||
'title' => 'PROSERVE v{version} متوفر!',
|
||||
'subtitle' => 'إصدار جديد جاهز للتثبيت عبر PROSERVE Launcher الخاص بك.',
|
||||
'greeting' => 'مرحباً {owner}،',
|
||||
'intro' => 'يسعدنا أن نعلمك بأن <strong>الإصدار {version} من PROSERVE</strong> قد تم نشره للتو وأصبح متاحاً للتنزيل.',
|
||||
'instructions' => 'إذا كان لديك <strong>PROSERVE Launcher</strong> مثبتاً بالفعل، فما عليك سوى فتحه على أي من أجهزتك، والنقر فوق <em>«التحقق من التحديثات»</em>، وتثبيت الإصدار الجديد بنقرة واحدة.',
|
||||
'firstInstallTitle' => 'تثبيت لأول مرة أو محطة عمل جديدة؟',
|
||||
'firstInstallBody' => 'إذا كانت هذه المرة الأولى لك، أو إذا كنت تعد جهاز كمبيوتر جديداً، فقم بتنزيل PROSERVE Launcher أدناه. <strong>ستحتاج إلى تشغيله على كل محطة عمل</strong> سيتم استخدام PROSERVE فيها.',
|
||||
'downloadButton' => '⬇ تنزيل PROSERVE Launcher (Windows)',
|
||||
'directLink' => 'رابط مباشر:',
|
||||
'releaseNotesTitle' => 'ملاحظات الإصدار',
|
||||
'releaseNotesNone' => 'لا توجد ملاحظات إصدار لهذه النسخة.',
|
||||
'support' => 'كما هو الحال دائماً، إذا واجهت أي مشكلة (فشل التنزيل، خطأ في التثبيت، أي شيء غريب في اللعبة)، فلا تتردد في التواصل معنا — نحن هنا للمساعدة.',
|
||||
'closing' => 'تدريب سعيد!',
|
||||
'signature' => 'فريق ASTERION VR',
|
||||
'footer' => 'تم إرسال هذا الإشعار التلقائي لأن بريدك الإلكتروني للاتصال مسجل في أحد تراخيص PROSERVE الخاصة بك.<br/>للتوقف عن تلقي هذه الإشعارات، اطلب من مدير حسابك في ASTERION VR إزالة عنوانك من جهات اتصال الترخيص.',
|
||||
],
|
||||
'es' => [
|
||||
'subject' => 'PROSERVE v{version} ya está disponible',
|
||||
'title' => '¡PROSERVE v{version} ya está disponible!',
|
||||
'subtitle' => 'Una nueva versión está lista para instalar a través de tu PROSERVE Launcher.',
|
||||
'greeting' => 'Hola {owner},',
|
||||
'intro' => 'Nos complace informarte que <strong>la versión {version} de PROSERVE</strong> acaba de publicarse y ya está disponible para descargar.',
|
||||
'instructions' => 'Si ya tienes el <strong>PROSERVE Launcher</strong> instalado, simplemente ábrelo en cualquiera de tus máquinas, haz clic en <em>«Buscar actualizaciones»</em> e instala la nueva versión con un solo clic.',
|
||||
'firstInstallTitle' => '¿Primera instalación o nueva estación de trabajo?',
|
||||
'firstInstallBody' => 'Si es la primera vez, o si estás configurando un PC nuevo, descarga el instalador de PROSERVE Launcher abajo. <strong>Deberás ejecutarlo en CADA estación de trabajo</strong> donde se utilice PROSERVE.',
|
||||
'downloadButton' => '⬇ Descargar PROSERVE Launcher (Windows)',
|
||||
'directLink' => 'Enlace directo:',
|
||||
'releaseNotesTitle' => 'Notas de versión',
|
||||
'releaseNotesNone' => 'No hay notas de versión para esta release.',
|
||||
'support' => 'Como siempre, si encuentras algún problema (fallo de descarga, error de instalación, comportamiento inesperado en el juego), no dudes en contactarnos — estamos aquí para ayudar.',
|
||||
'closing' => '¡Buen entrenamiento!',
|
||||
'signature' => 'El equipo ASTERION VR',
|
||||
'footer' => 'Esta notificación automática se envió porque tu dirección de correo está registrada como contacto en una de tus licencias PROSERVE.<br/>Para dejar de recibir estos correos, pide a tu gestor de cuenta ASTERION VR que elimine tu dirección de los contactos de la licencia.',
|
||||
],
|
||||
'de' => [
|
||||
'subject' => 'PROSERVE v{version} ist jetzt verfügbar',
|
||||
'title' => 'PROSERVE v{version} ist verfügbar!',
|
||||
'subtitle' => 'Eine neue Version ist bereit zur Installation über deinen PROSERVE Launcher.',
|
||||
'greeting' => 'Hallo {owner},',
|
||||
'intro' => 'Wir freuen uns, dir mitzuteilen, dass <strong>die Version {version} von PROSERVE</strong> soeben veröffentlicht wurde und jetzt zum Download bereitsteht.',
|
||||
'instructions' => 'Wenn du den <strong>PROSERVE Launcher</strong> bereits installiert hast, öffne ihn einfach auf einer beliebigen deiner Maschinen, klicke auf <em>«Updates suchen»</em> und installiere die neue Version mit einem Klick.',
|
||||
'firstInstallTitle' => 'Erstinstallation oder neue Arbeitsstation?',
|
||||
'firstInstallBody' => 'Wenn dies das erste Mal ist oder du einen neuen PC einrichtest, lade den PROSERVE Launcher-Installer unten herunter. <strong>Du musst ihn auf JEDER Arbeitsstation ausführen</strong>, auf der PROSERVE verwendet wird.',
|
||||
'downloadButton' => '⬇ PROSERVE Launcher herunterladen (Windows)',
|
||||
'directLink' => 'Direkter Link:',
|
||||
'releaseNotesTitle' => 'Versionshinweise',
|
||||
'releaseNotesNone' => 'Keine Versionshinweise für diese Version.',
|
||||
'support' => 'Wie immer, wenn du auf Probleme stößt (Download-Fehler, Installationsfehler, irgendetwas Seltsames im Spiel), zögere nicht, uns zu kontaktieren — wir sind hier, um zu helfen.',
|
||||
'closing' => 'Viel Spaß beim Training!',
|
||||
'signature' => 'Das ASTERION VR Team',
|
||||
'footer' => 'Diese automatische Benachrichtigung wurde gesendet, weil deine Kontakt-E-Mail bei einer deiner PROSERVE-Lizenzen registriert ist.<br/>Um diese E-Mails nicht mehr zu erhalten, bitte deinen ASTERION VR Account Manager, deine Adresse aus den Lizenz-Kontakten zu entfernen.',
|
||||
],
|
||||
];
|
||||
return $cache[$locale] ?? $cache['en'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit le corps HTML de l'email de release announce, dans la langue
|
||||
* <c>$locale</c> demandée (fallback 'en'). Le contenu de la release note
|
||||
* elle-même (Markdown) reste en anglais — seul le texte d'accompagnement
|
||||
* (greeting, instructions, CTAs) est localisé via <see cref="getEmailStrings"/>.
|
||||
* Le owner_name personnalise le greeting. Le logo (optionnel via CID embed
|
||||
* <c>notifications.logo_path</c>) est inséré dans le bandeau d'en-tête navy.
|
||||
*
|
||||
* Pour l'arabe : on ajoute <c>dir="rtl"</c> sur le wrapper table → Outlook,
|
||||
* Gmail et Apple Mail rendent correctement le texte droit→gauche, et les
|
||||
* sous-éléments inline (HTML tags, code, URLs) restent affichés LTR comme
|
||||
* attendu (mixed bidi handling natif).
|
||||
*/
|
||||
function renderReleaseAnnounceEmail(string $version, string $ownerName, string $notesMarkdown, string $baseUrl, string $logoSrc = '', string $locale = 'en'): string
|
||||
{
|
||||
// Récupère le dictionnaire de strings localisées + interpolation simple
|
||||
// des placeholders {version} et {owner}. On échappe APRÈS interpolation
|
||||
// pour ne pas double-encoder les < > de balises HTML déjà dans les
|
||||
// strings (cf. <strong> dans les templates de getEmailStrings).
|
||||
$s = getEmailStrings($locale);
|
||||
$versionEsc = htmlspecialchars($version, ENT_QUOTES, 'UTF-8');
|
||||
$ownerEsc = htmlspecialchars($ownerName !== '' ? $ownerName : 'team', ENT_QUOTES, 'UTF-8');
|
||||
$notesEsc = htmlspecialchars(trim($notesMarkdown), ENT_QUOTES, 'UTF-8');
|
||||
// $logoSrc peut être une URL https://… (hotlink) OU un cid:xxx (inline embed).
|
||||
// Le caller décide selon ce qu'il préfère ; on échappe juste pour mettre dans src=.
|
||||
$logoEsc = htmlspecialchars($logoSrc, ENT_QUOTES, 'UTF-8');
|
||||
// Direction RTL pour l'arabe. Les autres langues sont LTR par défaut.
|
||||
// Note : on POSITIONNE rtl sur la table racine — les sub-blocks restent
|
||||
// align="center" / align="left" selon le contexte (les tags HTML inline
|
||||
// gardent leur direction naturelle via bidi-isolate du browser).
|
||||
$dirAttr = ($locale === 'ar') ? ' dir="rtl"' : '';
|
||||
|
||||
// Helper d'interpolation : remplace {version} et {owner} dans une string
|
||||
// localisée. NOTE : on ne réencode PAS — les strings dans getEmailStrings
|
||||
// contiennent volontairement des <strong>, <em>, etc. à rendre comme HTML.
|
||||
$tr = function (string $key) use ($s, $versionEsc, $ownerEsc): string {
|
||||
return strtr($s[$key] ?? '', ['{version}' => $versionEsc, '{owner}' => $ownerEsc]);
|
||||
};
|
||||
|
||||
// Wrap les release notes en <pre> pour préserver le Markdown formatting
|
||||
// (indentation, listes, line breaks). Pas de rendu HTML — l'admin sait que
|
||||
// ses notes sont en Markdown et l'opérateur peut lire le format brut.
|
||||
// IMPORTANT : le titre "Release notes" est localisé MAIS le contenu reste
|
||||
// toujours en anglais (= ce que l'admin a saisi dans la modal Notes).
|
||||
$notesBlock = $notesEsc !== ''
|
||||
? "<h3 style=\"color: #1F2937; margin: 24px 0 8px;\">Release notes</h3>\n"
|
||||
? "<h3 style=\"color: #1F2937; margin: 24px 0 8px;\">" . htmlspecialchars($s['releaseNotesTitle'], ENT_QUOTES, 'UTF-8') . "</h3>\n"
|
||||
. "<pre style=\"background: #F3F4F6; padding: 16px; border-radius: 6px; "
|
||||
. "font-family: 'Cascadia Code', Consolas, monospace; font-size: 13px; "
|
||||
. "white-space: pre-wrap; word-wrap: break-word; line-height: 1.5; "
|
||||
. "direction: ltr; text-align: left; "
|
||||
. "border-left: 3px solid #3B82F6;\">{$notesEsc}</pre>"
|
||||
: '<p style="color: #6B7280; font-style: italic;">No release notes for this version.</p>';
|
||||
: '<p style="color: #6B7280; font-style: italic;">' . htmlspecialchars($s['releaseNotesNone'], ENT_QUOTES, 'UTF-8') . '</p>';
|
||||
|
||||
// Logo block (optionnel). Image hotlinkée + alt fallback : si le MUA bloque
|
||||
// les images (Outlook protégé par défaut), l'alt "ASTERION VR" s'affiche
|
||||
@@ -158,38 +337,50 @@ function renderReleaseAnnounceEmail(string $version, string $ownerName, string $
|
||||
: 'https://asterionvr.com/PS_Launcher/installer/PS_Launcher-Setup.exe';
|
||||
$installerUrlEsc = htmlspecialchars($installerUrl, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// Toutes les strings utilisateur sont passées par $tr() qui interpole
|
||||
// {version}/{owner} dans le template localisé pour la $locale demandée.
|
||||
$titleStr = $tr('title');
|
||||
$subtitleStr = $tr('subtitle');
|
||||
$greetingStr = $tr('greeting');
|
||||
$introStr = $tr('intro');
|
||||
$instructionsStr = $tr('instructions');
|
||||
$fiTitleStr = $tr('firstInstallTitle');
|
||||
$fiBodyStr = $tr('firstInstallBody');
|
||||
$downloadBtnStr = $tr('downloadButton');
|
||||
$directLinkStr = $tr('directLink');
|
||||
$supportStr = $tr('support');
|
||||
$closingStr = $tr('closing');
|
||||
$signatureStr = $tr('signature');
|
||||
$footerStr = $tr('footer');
|
||||
|
||||
return <<<HTML
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html{$dirAttr}>
|
||||
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"></head>
|
||||
<body style="margin:0; padding:24px 12px; background-color:#F3F4F6; font-family:'Segoe UI',Arial,sans-serif; color:#1F2937;">
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" align="center" width="640"
|
||||
style="max-width:640px; margin:0 auto; border-collapse:collapse;">
|
||||
style="max-width:640px; margin:0 auto; border-collapse:collapse;"{$dirAttr}>
|
||||
<tr>
|
||||
<td bgcolor="#0F172A" align="center"
|
||||
style="background-color:#0F172A; padding:32px 24px; color:#FFFFFF; border-radius:8px 8px 0 0;">
|
||||
<div style="margin-bottom:16px;">{$logoBlock}</div>
|
||||
<h1 style="margin:0; font-size:24px; color:#FFFFFF; font-weight:600;">
|
||||
PROSERVE v{$versionEsc} is available!
|
||||
{$titleStr}
|
||||
</h1>
|
||||
<p style="margin:8px 0 0; color:#94A3B8; font-size:14px;">
|
||||
A new version is ready to install via your PROSERVE Launcher.
|
||||
{$subtitleStr}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td bgcolor="#FFFFFF"
|
||||
style="background-color:#FFFFFF; padding:28px 28px; border:1px solid #E5E7EB; border-top:none; border-radius:0 0 8px 8px;">
|
||||
<p style="margin:0 0 14px;">Hi {$ownerEsc},</p>
|
||||
<p style="margin:0 0 14px;">{$greetingStr}</p>
|
||||
<p style="margin:0 0 14px;">
|
||||
We're happy to let you know that
|
||||
<strong>PROSERVE version {$versionEsc}</strong>
|
||||
has just been published and is now available for download.
|
||||
{$introStr}
|
||||
</p>
|
||||
<p style="margin:0 0 14px;">
|
||||
If you already have the <strong>PROSERVE Launcher</strong> installed,
|
||||
simply open it on any of your machines, click
|
||||
<em>« Check for updates »</em>, and install the new version with one click.
|
||||
{$instructionsStr}
|
||||
</p>
|
||||
|
||||
<!-- Section « Don't have the launcher yet ? » : bouton de téléchargement
|
||||
@@ -200,13 +391,10 @@ function renderReleaseAnnounceEmail(string $version, string $ownerName, string $
|
||||
<tr>
|
||||
<td style="padding:18px 0;">
|
||||
<p style="margin:0 0 12px; font-weight:600; color:#1F2937;">
|
||||
First install or new workstation?
|
||||
{$fiTitleStr}
|
||||
</p>
|
||||
<p style="margin:0 0 16px; color:#4B5563;">
|
||||
If this is your first time, or you're setting up a new PC,
|
||||
download the PROSERVE Launcher installer below.
|
||||
<strong>You'll need to run it on EACH workstation</strong>
|
||||
where PROSERVE will be used.
|
||||
{$fiBodyStr}
|
||||
</p>
|
||||
<!-- Bouton stylé via table + bgcolor (vs CSS button) pour
|
||||
compat Outlook qui ne supporte ni border-radius CSS sur
|
||||
@@ -222,13 +410,13 @@ function renderReleaseAnnounceEmail(string $version, string $ownerName, string $
|
||||
color:#FFFFFF; text-decoration:none;
|
||||
font-family:'Segoe UI',Arial,sans-serif;
|
||||
font-size:14px; font-weight:600;">
|
||||
⬇ Download PROSERVE Launcher (Windows)
|
||||
{$downloadBtnStr}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="margin:12px 0 0; color:#9CA3AF; font-size:11px; text-align:center;">
|
||||
Direct link:
|
||||
{$directLinkStr}
|
||||
<a href="{$installerUrlEsc}" style="color:#3B82F6; text-decoration:underline; word-break:break-all;">{$installerUrlEsc}</a>
|
||||
</p>
|
||||
</td>
|
||||
@@ -237,19 +425,17 @@ function renderReleaseAnnounceEmail(string $version, string $ownerName, string $
|
||||
|
||||
{$notesBlock}
|
||||
<p style="margin:24px 0 14px;">
|
||||
As always, if you run into any issue (download failure, install error,
|
||||
anything weird in-game), don't hesitate to reach out — we're here to help.
|
||||
{$supportStr}
|
||||
</p>
|
||||
<p style="margin:0;">
|
||||
Happy training!<br/>
|
||||
<strong>The ASTERION VR team</strong>
|
||||
{$closingStr}<br/>
|
||||
<strong>{$signatureStr}</strong>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding:16px 8px; color:#9CA3AF; font-size:11px; line-height:1.5;">
|
||||
This automated notification was sent because your contact email is registered with one of your PROSERVE licenses.<br/>
|
||||
To stop receiving these, ask your ASTERION VR account manager to remove your address from the license contacts.
|
||||
{$footerStr}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -547,10 +733,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$notesFile = "$notesDir/{$entryId}.md";
|
||||
$notesBody = is_file($notesFile) ? file_get_contents($notesFile) : '';
|
||||
|
||||
// Récupère les licenses éligibles
|
||||
// Récupère les licenses éligibles. On inclut maintenant `language`
|
||||
// pour pouvoir localiser l'email — fallback 'en' si NULL ou inconnu.
|
||||
$db = Db::get($config);
|
||||
$stmt = $db->prepare(
|
||||
'SELECT id, owner_name, channel, can_see_betas, contact_emails, download_entitlement_until
|
||||
'SELECT id, owner_name, channel, can_see_betas, contact_emails, language, download_entitlement_until
|
||||
FROM licenses
|
||||
WHERE revoked_at IS NULL
|
||||
AND download_entitlement_until >= ?
|
||||
@@ -603,7 +790,15 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (empty($emails)) continue;
|
||||
$licsTouched++;
|
||||
|
||||
$subject = "PROSERVE v{$version} is now available";
|
||||
// Locale par license : whitelist contre les langues supportées,
|
||||
// fallback 'en' pour les licenses sans préférence définie.
|
||||
$locale = trim((string)($lic['language'] ?? ''));
|
||||
if (!in_array($locale, ['fr', 'en', 'zh', 'th', 'ar'], true)) {
|
||||
$locale = 'en';
|
||||
}
|
||||
// Sujet localisé via le même dictionnaire que le corps.
|
||||
$subjectTemplate = getEmailStrings($locale)['subject'] ?? 'PROSERVE v{version} is now available';
|
||||
$subject = str_replace('{version}', $version, $subjectTemplate);
|
||||
// Logo via CID inline si fichier disponible sur disque, sinon
|
||||
// chaîne vide → le template tombera sur le fallback texte
|
||||
// "ASTERION VR" en blanc sur le bandeau dark navy. On NE
|
||||
@@ -617,7 +812,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$lic['owner_name'] ?? '',
|
||||
$notesBody,
|
||||
$config['base_url'] ?? '',
|
||||
$logoSrc
|
||||
$logoSrc,
|
||||
$locale
|
||||
);
|
||||
|
||||
foreach ($emails as $email) {
|
||||
|
||||
Reference in New Issue
Block a user