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
This commit is contained in:
Darren Yu 2025-08-26 11:03:12 +08:00 committed by GitHub
parent 63c7abf214
commit c786f843f9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 99 additions and 20 deletions

View File

@ -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</0> verwenden, um den E-Mail-Betreff anzupassen.",
"emailBody": "E-Mail-Inhalt",
"emailBodyDes": "HTML-Inhalt der E-Mail. Sie können <0>magische Variablen</0> verwenden, um den E-Mail-Inhalt anzupassen.",
"orderTitle": "Bestelltitel",

View File

@ -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</0> to customize the email subject.",
"emailBody": "Email body",
"emailBodyDes": "HTML content of the email. You can use <0>magic variables</0> to customize the email content.",
"orderTitle": "Order title",

View File

@ -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</0> para personalizar el asunto del email.",
"emailBody": "Cuerpo del email",
"emailBodyDes": "Contenido HTML del email. Puedes usar <0>variables mágicas</0> para personalizar el contenido del email.",
"orderTitle": "Título de la orden",

View File

@ -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</0> 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</0> pour personnaliser le contenu de l'e-mail.",
"orderTitle": "Titre de la commande",

View File

@ -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</0> per personalizzare l'oggetto dell'email.",
"emailBody": "Corpo email",
"emailBodyDes": "Contenuto HTML dell'email. Puoi usare <0>variabili magiche</0> per personalizzare il contenuto dell'email.",
"orderTitle": "Titolo ordine",

View File

@ -566,10 +566,14 @@
"quotaExceededEmailTemplateDes": "ユーザーがストレージクォータを超過した際にユーザーに送信されるメールテンプレート。",
"resetPasswordEmailTemplate": "パスワードリセットテンプレート",
"resetPasswordEmailTemplateDes": "ユーザーがパスワードのリセットを要求した際にユーザーに送信されるメールテンプレート。",
"preferredLanguage": "優先言語",
"setAsPreferredLanguage": "優先言語に設定",
"setAsPreferredLanguageDes": "ユーザーの言語設定が取得できない場合、優先言語のメールテンプレートが使用されます。",
"alreadyAsPreferredLanguageDes": "現在の言語は既に優先言語として設定されています。ユーザーの言語設定が取得できない場合、優先言語のメールテンプレートが使用されます。",
"addLanguage": "言語を追加",
"languageCodeDes": "追加する言語を選択してください。",
"emailSubject": "メール件名",
"emailSubjectDes": "メールの件名。",
"emailSubjectDes": "メールの件名。<0>魔法変数</0> を使用して件名をカスタマイズできます。",
"emailBody": "メール本文",
"emailBodyDes": "メールの本文です。<0>魔法変数</0> を使用して本文をカスタマイズできます。",
"orderTitle": "注文タイトル",

View File

@ -566,10 +566,14 @@
"quotaExceededEmailTemplateDes": "사용자가 저장 할당량을 초과할 때 보내는 이메일 템플릿",
"resetPasswordEmailTemplate": "비밀번호 재설정 템플릿",
"resetPasswordEmailTemplateDes": "사용자가 비밀번호 재설정을 요청할 때 보내는 이메일 템플릿",
"preferredLanguage": "선호 언어",
"setAsPreferredLanguage": "선호 언어로 설정",
"setAsPreferredLanguageDes": "사용자의 언어 선호도를 가져올 수 없는 경우 선호 언어의 이메일 템플릿이 사용됩니다.",
"alreadyAsPreferredLanguageDes": "현재 언어가 선호 언어로 설정되어 있습니다. 사용자의 언어 선호도를 가져올 수 없는 경우 이 이메일 템플릿이 사용됩니다.",
"addLanguage": "언어 추가",
"languageCodeDes": "추가할 언어를 선택해 주세요.",
"emailSubject": "이메일 제목",
"emailSubjectDes": "이메일의 제목",
"emailSubjectDes": "이메일의 제목. <0>매직 변수</0>를 사용하여 사용자 정의할 수 있습니다.",
"emailBody": "이메일 내용",
"emailBodyDes": "이메일의 내용입니다. <0>매직 변수</0>를 사용하여 이메일 내용을 사용자 정의할 수 있습니다.",
"orderTitle": "주문 제목",

View File

@ -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</0> para personalizar o assunto do email.",
"emailBody": "Corpo do email",
"emailBodyDes": "Conteúdo HTML do email. Você pode usar <0>variáveis mágicas</0> para personalizar o conteúdo do email.",
"orderTitle": "Título do pedido",

View File

@ -567,10 +567,14 @@
"quotaExceededEmailTemplateDes": "Шаблон электронного письма, отправляемого пользователю при превышении квоты хранилища.",
"resetPasswordEmailTemplate": "Шаблон сброса пароля",
"resetPasswordEmailTemplateDes": "Шаблон электронного письма, отправляемого пользователю при запросе сброса пароля.",
"preferredLanguage": "Предпочитаемый язык",
"setAsPreferredLanguage": "Установить как предпочитаемый язык",
"setAsPreferredLanguageDes": "Если язык пользователя не может быть определен, будет использоваться предпочитаемый язык.",
"alreadyAsPreferredLanguageDes": "Этот язык уже установлен как предпочитаемый. Если язык пользователя не может быть определен, будет использоваться этот шаблон письма.",
"addLanguage": "Добавить язык",
"languageCodeDes": "Пожалуйста, выберите язык для добавления.",
"emailSubject": "Тема письма",
"emailSubjectDes": "Тема электронного письма.",
"emailSubjectDes": "Тема электронного письма. Вы можете использовать <0>магические переменные</0> для настройки темы письма.",
"emailBody": "Содержимое письма",
"emailBodyDes": "Содержимое электронного письма. Вы можете использовать <0>магические переменные</0> для настройки содержимого письма.",
"orderTitle": "Название заказа",

View File

@ -566,10 +566,14 @@
"quotaExceededEmailTemplateDes": "当用户超出存储配额时发送给用户的邮件模板。",
"resetPasswordEmailTemplate": "密码重置模板",
"resetPasswordEmailTemplateDes": "当用户请求重置密码时发送给用户的邮件模板。",
"preferredLanguage": "首选语言",
"setAsPreferredLanguage": "设为首选语言",
"setAsPreferredLanguageDes": "如果无法获取用户的语言偏好,将使用首选语言的邮件模板。",
"alreadyAsPreferredLanguageDes": "当前语言已设为首选语言。如果无法获取用户的语言偏好,将使用此邮件模板。",
"addLanguage": "添加语言",
"languageCodeDes": "请选择要添加的语言。",
"emailSubject": "邮件主题",
"emailSubjectDes": "邮件的主题。",
"emailSubjectDes": "邮件的主题。你可以使用 <0>魔法变量</0> 来定制邮件主题。",
"emailBody": "邮件内容",
"emailBodyDes": "邮件的内容。你可以使用 <0>魔法变量</0> 来定制邮件内容。",
"orderTitle": "订单标题",

View File

@ -566,10 +566,14 @@
"quotaExceededEmailTemplateDes": "當使用者超出儲存配額時傳送給使用者的郵件模板。",
"resetPasswordEmailTemplate": "密碼重置模板",
"resetPasswordEmailTemplateDes": "當使用者請求重置密碼時傳送給使用者的郵件模板。",
"preferredLanguage": "首選語言",
"setAsPreferredLanguage": "設為首選語言",
"setAsPreferredLanguageDes": "如果無法獲取使用者的語言偏好,將使用首選語言的郵件模板。",
"alreadyAsPreferredLanguageDes": "當前語言已設為首選語言。如果無法獲取使用者的語言偏好,將使用此郵件模板。",
"addLanguage": "新增語言",
"languageCodeDes": "請選擇要新增的語言。",
"emailSubject": "郵件主題",
"emailSubjectDes": "郵件的主題。",
"emailSubjectDes": "郵件的主題。你可以使用 <0>魔法變數</0> 來定製郵件主題。",
"emailBody": "郵件內容",
"emailBodyDes": "郵件的內容。你可以使用 <0>魔法變數</0> 來定製郵件內容。",
"orderTitle": "訂單標題",

View File

@ -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<EmailTemplateEditorProps> = ({ 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<EmailTemplateEditorProps> = ({ 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<EmailTemplateEditorProps> = ({ 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<HTMLElement>) => {
setMagicVarOpen(true);
e.stopPropagation();
@ -128,9 +139,10 @@ const EmailTemplateEditor: React.FC<EmailTemplateEditorProps> = ({ value, onChan
scrollButtons="auto"
sx={{ flexGrow: 1 }}
>
{templates.map((template, index) => (
<Tab key={index} label={template.language} />
))}
{templates.map((template, index) => {
const lang = languages.find((l) => l.code === template.language);
return <Tab key={index} label={lang ? lang.displayName : template.language} />;
})}
</Tabs>
<Button
startIcon={<Add />}
@ -151,6 +163,23 @@ const EmailTemplateEditor: React.FC<EmailTemplateEditorProps> = ({ value, onChan
>
{currentTab === index && (
<Box>
<FormControl fullWidth sx={{ mb: 2 }}>
<Typography variant="subtitle2" sx={{ mb: 1 }}>
{t("settings.preferredLanguage")}
</Typography>
<Box>
<SecondaryButton
variant="contained"
onClick={() => (index === 0 ? undefined : setPreferredLanguage(index))}
>
{t("settings.setAsPreferredLanguage")}
</SecondaryButton>
</Box>
<NoMarginHelperText>
{t(index === 0 ? "settings.alreadyAsPreferredLanguageDes" : "settings.setAsPreferredLanguageDes")}
</NoMarginHelperText>
</FormControl>
<FormControl fullWidth sx={{ mb: 2 }}>
<Typography variant="subtitle2" sx={{ mb: 1 }}>
{t("settings.emailSubject")}
@ -160,7 +189,13 @@ const EmailTemplateEditor: React.FC<EmailTemplateEditorProps> = ({ value, onChan
value={template.title}
onChange={(e) => updateTemplate(index, "title", e.target.value)}
/>
<NoMarginHelperText>{t("settings.emailSubjectDes")}</NoMarginHelperText>
<NoMarginHelperText>
<Trans
i18nKey={"settings.emailSubjectDes"}
ns={"dashboard"}
components={[<Link onClick={openMagicVar} href={"#"} />]}
/>
</NoMarginHelperText>
</FormControl>
<Typography variant="subtitle2" sx={{ mb: 1 }}>