From c786f843f916d7ac9476a8ca3e72e7bcf123fa3c Mon Sep 17 00:00:00 2001 From: Darren Yu Date: Tue, 26 Aug 2025 11:03:12 +0800 Subject: [PATCH] feat(email): fix and enhance email template setting (#2813) (#301) * feat(email): auto switch for existing language templates * feat(email): use friendly lang display name * fix(email): prevent infinite loop when updating templates * feat(email): adjust email template order by drag-and-drop * revoke(email): remove drag-and-drop for email template order * feat(email): support magic variables in email title * feat(email): add preferred language setting in email template editor --- public/locales/de-DE/dashboard.json | 6 ++- public/locales/en-US/dashboard.json | 6 ++- public/locales/es-ES/dashboard.json | 6 ++- public/locales/fr-FR/dashboard.json | 6 ++- public/locales/it-IT/dashboard.json | 6 ++- public/locales/ja-JP/dashboard.json | 6 ++- public/locales/ko-KR/dashboard.json | 6 ++- public/locales/pt-BR/dashboard.json | 6 ++- public/locales/ru-RU/dashboard.json | 6 ++- public/locales/zh-CN/dashboard.json | 6 ++- public/locales/zh-TW/dashboard.json | 6 ++- .../Settings/Email/EmailTemplateEditor.tsx | 53 +++++++++++++++---- 12 files changed, 99 insertions(+), 20 deletions(-) diff --git a/public/locales/de-DE/dashboard.json b/public/locales/de-DE/dashboard.json index df3234b..7f67c3d 100644 --- a/public/locales/de-DE/dashboard.json +++ b/public/locales/de-DE/dashboard.json @@ -567,10 +567,14 @@ "quotaExceededEmailTemplateDes": "E-Mail-Vorlage, die an Benutzer gesendet wird, wenn sie ihr Speicherkontingent überschreiten.", "resetPasswordEmailTemplate": "Passwort-Reset-Vorlage", "resetPasswordEmailTemplateDes": "E-Mail-Vorlage, die an Benutzer gesendet wird, wenn sie ein Passwort-Reset anfordern.", + "preferredLanguage": "Bevorzugte Sprache", + "setAsPreferredLanguage": "Als bevorzugte Sprache festlegen", + "setAsPreferredLanguageDes": "Wenn die Sprache des Benutzers nicht festgelegt ist, wird diese bevorzugte Sprache verwendet.", + "alreadyAsPreferredLanguageDes": "Die aktuelle Sprache ist bereits als bevorzugte Sprache festgelegt. Wenn die Sprache des Benutzers nicht festgelegt ist, wird diese bevorzugte Sprache verwendet.", "addLanguage": "Sprache hinzufügen", "languageCodeDes": "Bitte wählen Sie die Sprache aus, die Sie hinzufügen möchten.", "emailSubject": "E-Mail-Betreff", - "emailSubjectDes": "Die Betreffzeile der E-Mail.", + "emailSubjectDes": "Die Betreffzeile der E-Mail. Sie können <0>magische Variablen verwenden, um den E-Mail-Betreff anzupassen.", "emailBody": "E-Mail-Inhalt", "emailBodyDes": "HTML-Inhalt der E-Mail. Sie können <0>magische Variablen verwenden, um den E-Mail-Inhalt anzupassen.", "orderTitle": "Bestelltitel", diff --git a/public/locales/en-US/dashboard.json b/public/locales/en-US/dashboard.json index c0c8889..358c188 100644 --- a/public/locales/en-US/dashboard.json +++ b/public/locales/en-US/dashboard.json @@ -566,10 +566,14 @@ "quotaExceededEmailTemplateDes": "Email template sent to users when they exceed their storage quota.", "resetPasswordEmailTemplate": "Password reset template", "resetPasswordEmailTemplateDes": "Email template sent to users when they request a password reset.", + "preferredLanguage": "Preferred language", + "setAsPreferredLanguage": "Set as preferred language", + "setAsPreferredLanguageDes": "This language will be used as the default email template if the user's language preference cannot be determined.", + "alreadyAsPreferredLanguageDes": "This language is already set as the preferred language. If the user's language preference cannot be determined, this email template will be used.", "addLanguage": "Add language", "languageCodeDes": "Please select the language you want to add.", "emailSubject": "Email saveChanges", - "emailSubjectDes": "The subject line of the email.", + "emailSubjectDes": "The subject line of the email. You can use <0>magic variables to customize the email subject.", "emailBody": "Email body", "emailBodyDes": "HTML content of the email. You can use <0>magic variables to customize the email content.", "orderTitle": "Order title", diff --git a/public/locales/es-ES/dashboard.json b/public/locales/es-ES/dashboard.json index d328f37..d9a0f86 100644 --- a/public/locales/es-ES/dashboard.json +++ b/public/locales/es-ES/dashboard.json @@ -567,10 +567,14 @@ "quotaExceededEmailTemplateDes": "Plantilla de email enviada a los usuarios cuando exceden su cuota de almacenamiento.", "resetPasswordEmailTemplate": "Plantilla de restablecimiento de contraseña", "resetPasswordEmailTemplateDes": "Plantilla de email enviada a los usuarios cuando solicitan un restablecimiento de contraseña.", + "preferredLanguage": "Idioma preferido", + "setAsPreferredLanguage": "Establecer como idioma preferido", + "setAsPreferredLanguageDes": "Si no se puede obtener la preferencia de idioma del usuario, se utilizará la plantilla de correo electrónico del idioma preferido.", + "alreadyAsPreferredLanguageDes": "El idioma actual ya está establecido como preferido. Si no se puede obtener la preferencia de idioma del usuario, se utilizará esta plantilla de correo electrónico.", "addLanguage": "Agregar idioma", "languageCodeDes": "Por favor selecciona el idioma que quieres agregar.", "emailSubject": "Asunto del email", - "emailSubjectDes": "La línea de asunto del email.", + "emailSubjectDes": "La línea de asunto del email. Puedes usar <0>variables mágicas para personalizar el asunto del email.", "emailBody": "Cuerpo del email", "emailBodyDes": "Contenido HTML del email. Puedes usar <0>variables mágicas para personalizar el contenido del email.", "orderTitle": "Título de la orden", diff --git a/public/locales/fr-FR/dashboard.json b/public/locales/fr-FR/dashboard.json index 9fe4703..264e6da 100644 --- a/public/locales/fr-FR/dashboard.json +++ b/public/locales/fr-FR/dashboard.json @@ -567,10 +567,14 @@ "quotaExceededEmailTemplateDes": "Modèle d'e-mail envoyé aux utilisateurs lorsqu'ils dépassent leur quota de stockage.", "resetPasswordEmailTemplate": "Modèle de réinitialisation de mot de passe", "resetPasswordEmailTemplateDes": "Modèle d'e-mail envoyé aux utilisateurs lorsqu'ils demandent une réinitialisation de mot de passe.", + "preferredLanguage": "Langue préférée", + "setAsPreferredLanguage": "Définir comme langue préférée", + "setAsPreferredLanguageDes": "Si la préférence de langue de l'utilisateur ne peut pas être obtenue, le modèle d'e-mail de la langue préférée sera utilisé.", + "alreadyAsPreferredLanguageDes": "La langue actuelle est déjà définie comme langue préférée. Si la préférence de langue de l'utilisateur ne peut pas être obtenue, ce modèle d'e-mail sera utilisé.", "addLanguage": "Ajouter une langue", "languageCodeDes": "Veuillez sélectionner la langue que vous souhaitez ajouter.", "emailSubject": "Sujet de l'e-mail", - "emailSubjectDes": "La ligne d'objet de l'e-mail.", + "emailSubjectDes": "La ligne d'objet de l'e-mail. Vous pouvez utiliser des <0>variables magiques pour personnaliser le sujet de l'e-mail.", "emailBody": "Corps de l'e-mail", "emailBodyDes": "Contenu HTML de l'e-mail. Vous pouvez utiliser des <0>variables magiques pour personnaliser le contenu de l'e-mail.", "orderTitle": "Titre de la commande", diff --git a/public/locales/it-IT/dashboard.json b/public/locales/it-IT/dashboard.json index be34489..29e5dc2 100644 --- a/public/locales/it-IT/dashboard.json +++ b/public/locales/it-IT/dashboard.json @@ -567,10 +567,14 @@ "quotaExceededEmailTemplateDes": "Template email inviato agli utenti quando superano la loro quota di archiviazione.", "resetPasswordEmailTemplate": "Template reset password", "resetPasswordEmailTemplateDes": "Template email inviato agli utenti quando richiedono un reset della password.", + "preferredLanguage": "Lingua preferita", + "setAsPreferredLanguage": "Imposta come lingua preferita", + "setAsPreferredLanguageDes": "Se non è possibile ottenere le preferenze linguistiche dell'utente, verrà utilizzato il template email della lingua preferita.", + "alreadyAsPreferredLanguageDes": "La lingua corrente è già impostata come preferita. Se non è possibile ottenere le preferenze linguistiche dell'utente, verrà utilizzato il template email della lingua corrente.", "addLanguage": "Aggiungi lingua", "languageCodeDes": "Seleziona la lingua che vuoi aggiungere.", "emailSubject": "Oggetto email", - "emailSubjectDes": "L'oggetto dell'email.", + "emailSubjectDes": "L'oggetto dell'email. Puoi usare <0>variabili magiche per personalizzare l'oggetto dell'email.", "emailBody": "Corpo email", "emailBodyDes": "Contenuto HTML dell'email. Puoi usare <0>variabili magiche per personalizzare il contenuto dell'email.", "orderTitle": "Titolo ordine", diff --git a/public/locales/ja-JP/dashboard.json b/public/locales/ja-JP/dashboard.json index 54362ed..18fc69a 100644 --- a/public/locales/ja-JP/dashboard.json +++ b/public/locales/ja-JP/dashboard.json @@ -566,10 +566,14 @@ "quotaExceededEmailTemplateDes": "ユーザーがストレージクォータを超過した際にユーザーに送信されるメールテンプレート。", "resetPasswordEmailTemplate": "パスワードリセットテンプレート", "resetPasswordEmailTemplateDes": "ユーザーがパスワードのリセットを要求した際にユーザーに送信されるメールテンプレート。", + "preferredLanguage": "優先言語", + "setAsPreferredLanguage": "優先言語に設定", + "setAsPreferredLanguageDes": "ユーザーの言語設定が取得できない場合、優先言語のメールテンプレートが使用されます。", + "alreadyAsPreferredLanguageDes": "現在の言語は既に優先言語として設定されています。ユーザーの言語設定が取得できない場合、優先言語のメールテンプレートが使用されます。", "addLanguage": "言語を追加", "languageCodeDes": "追加する言語を選択してください。", "emailSubject": "メール件名", - "emailSubjectDes": "メールの件名。", + "emailSubjectDes": "メールの件名。<0>魔法変数 を使用して件名をカスタマイズできます。", "emailBody": "メール本文", "emailBodyDes": "メールの本文です。<0>魔法変数 を使用して本文をカスタマイズできます。", "orderTitle": "注文タイトル", diff --git a/public/locales/ko-KR/dashboard.json b/public/locales/ko-KR/dashboard.json index 7c027b6..9612c27 100644 --- a/public/locales/ko-KR/dashboard.json +++ b/public/locales/ko-KR/dashboard.json @@ -566,10 +566,14 @@ "quotaExceededEmailTemplateDes": "사용자가 저장 할당량을 초과할 때 보내는 이메일 템플릿", "resetPasswordEmailTemplate": "비밀번호 재설정 템플릿", "resetPasswordEmailTemplateDes": "사용자가 비밀번호 재설정을 요청할 때 보내는 이메일 템플릿", + "preferredLanguage": "선호 언어", + "setAsPreferredLanguage": "선호 언어로 설정", + "setAsPreferredLanguageDes": "사용자의 언어 선호도를 가져올 수 없는 경우 선호 언어의 이메일 템플릿이 사용됩니다.", + "alreadyAsPreferredLanguageDes": "현재 언어가 선호 언어로 설정되어 있습니다. 사용자의 언어 선호도를 가져올 수 없는 경우 이 이메일 템플릿이 사용됩니다.", "addLanguage": "언어 추가", "languageCodeDes": "추가할 언어를 선택해 주세요.", "emailSubject": "이메일 제목", - "emailSubjectDes": "이메일의 제목", + "emailSubjectDes": "이메일의 제목. <0>매직 변수를 사용하여 사용자 정의할 수 있습니다.", "emailBody": "이메일 내용", "emailBodyDes": "이메일의 내용입니다. <0>매직 변수를 사용하여 이메일 내용을 사용자 정의할 수 있습니다.", "orderTitle": "주문 제목", diff --git a/public/locales/pt-BR/dashboard.json b/public/locales/pt-BR/dashboard.json index 36f38bb..a121ccd 100644 --- a/public/locales/pt-BR/dashboard.json +++ b/public/locales/pt-BR/dashboard.json @@ -567,10 +567,14 @@ "quotaExceededEmailTemplateDes": "Modelo de email enviado aos usuários quando excedem sua cota de armazenamento.", "resetPasswordEmailTemplate": "Modelo de redefinição de senha", "resetPasswordEmailTemplateDes": "Modelo de email enviado aos usuários quando solicitam redefinição de senha.", + "preferredLanguage": "Idioma preferido", + "setAsPreferredLanguage": "Definir como idioma preferido", + "setAsPreferredLanguageDes": "Se o idioma preferido do usuário não puder ser obtido, o modelo de email deste idioma será usado.", + "alreadyAsPreferredLanguageDes": "O idioma atual já está definido como preferido. Se o idioma preferido do usuário não puder ser obtido, o modelo de email deste idioma será usado.", "addLanguage": "Adicionar idioma", "languageCodeDes": "Selecione o idioma que deseja adicionar.", "emailSubject": "Assunto do email", - "emailSubjectDes": "A linha de assunto do email.", + "emailSubjectDes": "A linha de assunto do email. Você pode usar <0>variáveis mágicas para personalizar o assunto do email.", "emailBody": "Corpo do email", "emailBodyDes": "Conteúdo HTML do email. Você pode usar <0>variáveis mágicas para personalizar o conteúdo do email.", "orderTitle": "Título do pedido", diff --git a/public/locales/ru-RU/dashboard.json b/public/locales/ru-RU/dashboard.json index 16828c5..78f8295 100644 --- a/public/locales/ru-RU/dashboard.json +++ b/public/locales/ru-RU/dashboard.json @@ -567,10 +567,14 @@ "quotaExceededEmailTemplateDes": "Шаблон электронного письма, отправляемого пользователю при превышении квоты хранилища.", "resetPasswordEmailTemplate": "Шаблон сброса пароля", "resetPasswordEmailTemplateDes": "Шаблон электронного письма, отправляемого пользователю при запросе сброса пароля.", + "preferredLanguage": "Предпочитаемый язык", + "setAsPreferredLanguage": "Установить как предпочитаемый язык", + "setAsPreferredLanguageDes": "Если язык пользователя не может быть определен, будет использоваться предпочитаемый язык.", + "alreadyAsPreferredLanguageDes": "Этот язык уже установлен как предпочитаемый. Если язык пользователя не может быть определен, будет использоваться этот шаблон письма.", "addLanguage": "Добавить язык", "languageCodeDes": "Пожалуйста, выберите язык для добавления.", "emailSubject": "Тема письма", - "emailSubjectDes": "Тема электронного письма.", + "emailSubjectDes": "Тема электронного письма. Вы можете использовать <0>магические переменные для настройки темы письма.", "emailBody": "Содержимое письма", "emailBodyDes": "Содержимое электронного письма. Вы можете использовать <0>магические переменные для настройки содержимого письма.", "orderTitle": "Название заказа", diff --git a/public/locales/zh-CN/dashboard.json b/public/locales/zh-CN/dashboard.json index 6c3a975..76be053 100644 --- a/public/locales/zh-CN/dashboard.json +++ b/public/locales/zh-CN/dashboard.json @@ -566,10 +566,14 @@ "quotaExceededEmailTemplateDes": "当用户超出存储配额时发送给用户的邮件模板。", "resetPasswordEmailTemplate": "密码重置模板", "resetPasswordEmailTemplateDes": "当用户请求重置密码时发送给用户的邮件模板。", + "preferredLanguage": "首选语言", + "setAsPreferredLanguage": "设为首选语言", + "setAsPreferredLanguageDes": "如果无法获取用户的语言偏好,将使用首选语言的邮件模板。", + "alreadyAsPreferredLanguageDes": "当前语言已设为首选语言。如果无法获取用户的语言偏好,将使用此邮件模板。", "addLanguage": "添加语言", "languageCodeDes": "请选择要添加的语言。", "emailSubject": "邮件主题", - "emailSubjectDes": "邮件的主题。", + "emailSubjectDes": "邮件的主题。你可以使用 <0>魔法变量 来定制邮件主题。", "emailBody": "邮件内容", "emailBodyDes": "邮件的内容。你可以使用 <0>魔法变量 来定制邮件内容。", "orderTitle": "订单标题", diff --git a/public/locales/zh-TW/dashboard.json b/public/locales/zh-TW/dashboard.json index 933924f..8377506 100644 --- a/public/locales/zh-TW/dashboard.json +++ b/public/locales/zh-TW/dashboard.json @@ -566,10 +566,14 @@ "quotaExceededEmailTemplateDes": "當使用者超出儲存配額時傳送給使用者的郵件模板。", "resetPasswordEmailTemplate": "密碼重置模板", "resetPasswordEmailTemplateDes": "當使用者請求重置密碼時傳送給使用者的郵件模板。", + "preferredLanguage": "首選語言", + "setAsPreferredLanguage": "設為首選語言", + "setAsPreferredLanguageDes": "如果無法獲取使用者的語言偏好,將使用首選語言的郵件模板。", + "alreadyAsPreferredLanguageDes": "當前語言已設為首選語言。如果無法獲取使用者的語言偏好,將使用此郵件模板。", "addLanguage": "新增語言", "languageCodeDes": "請選擇要新增的語言。", "emailSubject": "郵件主題", - "emailSubjectDes": "郵件的主題。", + "emailSubjectDes": "郵件的主題。你可以使用 <0>魔法變數 來定製郵件主題。", "emailBody": "郵件內容", "emailBodyDes": "郵件的內容。你可以使用 <0>魔法變數 來定製郵件內容。", "orderTitle": "訂單標題", diff --git a/src/component/Admin/Settings/Email/EmailTemplateEditor.tsx b/src/component/Admin/Settings/Email/EmailTemplateEditor.tsx index 752e702..dd8bc63 100644 --- a/src/component/Admin/Settings/Email/EmailTemplateEditor.tsx +++ b/src/component/Admin/Settings/Email/EmailTemplateEditor.tsx @@ -1,4 +1,3 @@ -import { Add } from "@mui/icons-material"; import { Box, Button, @@ -15,7 +14,8 @@ import React, { lazy, Suspense, useCallback, useEffect, useRef, useState } from import { Trans, useTranslation } from "react-i18next"; import { languages } from "../../../../i18n.ts"; import CircularProgress from "../../../Common/CircularProgress.tsx"; -import { DenseFilledTextField, DenseSelect } from "../../../Common/StyledComponents.tsx"; +import { DenseFilledTextField, DenseSelect, SecondaryButton } from "../../../Common/StyledComponents.tsx"; +import Add from "../../../Icons/Add"; import DraggableDialog from "../../../Dialogs/DraggableDialog.tsx"; import { SquareMenuItem } from "../../../FileManager/ContextMenu/ContextMenu.tsx"; import SettingForm from "../../../Pages/Setting/SettingForm.tsx"; @@ -57,13 +57,16 @@ const EmailTemplateEditor: React.FC = ({ value, onChan if (parsedTemplates.length === 0) { setTemplates([{ language: "en-US", title: "", body: "" }]); } + if (currentTab > parsedTemplates.length) { + setCurrentTab(0); + } } catch (e) { console.error("Failed to parse email template:", e); setTemplates([{ language: "en-US", title: "", body: "" }]); } finally { // Use setTimeout to ensure this runs after React finishes the update setTimeout(() => { - isUpdatingFromProp.current = false; + isUpdatingFromProp.current = true; // Prevent infinite loop }, 0); } }, [value]); @@ -93,10 +96,12 @@ const EmailTemplateEditor: React.FC = ({ value, onChan if (!newLanguageCode.trim()) return; // Check if language already exists - if (templates.some((t) => t.language === newLanguageCode)) { - // Could show an error message here + const langTemplateIndex = templates.findIndex((l) => l.language === newLanguageCode); + if (langTemplateIndex !== -1) { setNewLanguageCode(""); setAddLanguageOpen(false); + + setCurrentTab(langTemplateIndex); return; } @@ -112,6 +117,12 @@ const EmailTemplateEditor: React.FC = ({ value, onChan setCurrentTab(templates.length); }; + const setPreferredLanguage = (index: number) => { + isUpdatingFromProp.current = false; // Ensure this is a user interaction + setTemplates([templates[index], ...templates.filter((_, i) => i !== index)]); + setCurrentTab(0); // Switch to the first tab as the preferred language is now at the top + }; + const openMagicVar = useCallback((e: React.MouseEvent) => { setMagicVarOpen(true); e.stopPropagation(); @@ -128,9 +139,10 @@ const EmailTemplateEditor: React.FC = ({ value, onChan scrollButtons="auto" sx={{ flexGrow: 1 }} > - {templates.map((template, index) => ( - - ))} + {templates.map((template, index) => { + const lang = languages.find((l) => l.code === template.language); + return ; + })}