From 472ee53d88459c3ecf35b0cca2178be6a169fd5c Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 4 Nov 2026 08:36:25 +0100 Subject: [PATCH] fix: insert pasted and dropped images without a dialog, with an optional Add description chip The image description tests now open the dialog from the chip on the image, since a drop no longer opens it by itself. --- .../__tests__/rich-text-editor-a11y.test.tsx | 8 ++++- .../__tests__/rich-text-editor-drop.test.tsx | 31 ++++++++++++++++++- components/email/resizable-image.tsx | 25 +++++++++++++-- components/email/rich-text-editor.tsx | 8 +++-- locales/ar/common.json | 3 +- locales/ca/common.json | 3 +- locales/cs/common.json | 3 +- locales/da/common.json | 3 +- locales/de/common.json | 3 +- locales/en/common.json | 3 +- locales/es/common.json | 3 +- locales/et/common.json | 3 +- locales/fa/common.json | 3 +- locales/fr/common.json | 3 +- locales/he/common.json | 3 +- locales/hu/common.json | 3 +- locales/it/common.json | 3 +- locales/ja/common.json | 3 +- locales/ko/common.json | 3 +- locales/lv/common.json | 3 +- locales/mn/common.json | 3 +- locales/nb/common.json | 3 +- locales/nl/common.json | 3 +- locales/pl/common.json | 3 +- locales/pt/common.json | 3 +- locales/ro/common.json | 3 +- locales/ru/common.json | 3 +- locales/sk/common.json | 3 +- locales/th/common.json | 3 +- locales/tr/common.json | 3 +- locales/uk/common.json | 3 +- locales/vi/common.json | 3 +- locales/zh-TW/common.json | 3 +- locales/zh/common.json | 3 +- 34 files changed, 125 insertions(+), 37 deletions(-) diff --git a/components/email/__tests__/rich-text-editor-a11y.test.tsx b/components/email/__tests__/rich-text-editor-a11y.test.tsx index 12931f3a3..af348308e 100644 --- a/components/email/__tests__/rich-text-editor-a11y.test.tsx +++ b/components/email/__tests__/rich-text-editor-a11y.test.tsx @@ -164,8 +164,11 @@ describe('RichTextEditor image descriptions (MA-02)', () => { const altDialog = () => screen.getByRole('dialog', { name: 'title' }); const html = (editor: Editor) => editor.getHTML(); - it('asks for a description when an image is dropped in, and never uses the file name', async () => { + it('offers a description for a dropped image, and never uses the file name', async () => { const { editor } = await dropImage(); + // Nothing asks on the drop; the chip on the image does, when clicked. + expect(screen.queryByRole('dialog', { name: 'title' })).toBeNull(); + fireEvent.click(await screen.findByTestId('image-alt-add')); const dialog = await waitFor(() => altDialog()); expect(html(editor)).not.toContain('IMG_2041'); expect(html(editor)).not.toMatch(/alt=/); @@ -178,6 +181,9 @@ describe('RichTextEditor image descriptions (MA-02)', () => { it('marks an image decorative with an empty alt', async () => { const { editor } = await dropImage(); + // Nothing asks on the drop; the chip on the image does, when clicked. + expect(screen.queryByRole('dialog', { name: 'title' })).toBeNull(); + fireEvent.click(await screen.findByTestId('image-alt-add')); const dialog = await waitFor(() => altDialog()); fireEvent.click(within(dialog).getByLabelText('decorative')); expect(within(dialog).getByLabelText('label')).toBeDisabled(); diff --git a/components/email/__tests__/rich-text-editor-drop.test.tsx b/components/email/__tests__/rich-text-editor-drop.test.tsx index 235cffce7..215b153b2 100644 --- a/components/email/__tests__/rich-text-editor-drop.test.tsx +++ b/components/email/__tests__/rich-text-editor-drop.test.tsx @@ -1,4 +1,4 @@ -import { act, render, waitFor } from '@testing-library/react'; +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import React from 'react'; import type { Editor } from '@tiptap/core'; @@ -72,4 +72,33 @@ describe('RichTextEditor file drop', () => { expect(editor.getText()).toBe('Hello'); expect(onDrop).not.toHaveBeenCalled(); }); + + // A pasted or dropped image asks nothing: an optional chip on it offers a description. + it('opens no dialog for a dropped image, and offers "Add description" on it', async () => { + const { editor } = await setup(async () => ({ src: 'data:image/png;base64,AAAA' })); + const file = new File(['png'], 'photo.png', { type: 'image/png' }); + + act(() => { editor.view.dom.dispatchEvent(fileDrop(file)); }); + + const chip = await screen.findByTestId('image-alt-add'); + expect(chip).toHaveTextContent('add_description'); + expect(screen.queryByRole('dialog')).toBeNull(); + + fireEvent.click(chip); + expect(await screen.findByRole('dialog')).toBeInTheDocument(); + }); + + it('shows no chip on an image marked decorative', async () => { + let editor: Editor | null = null; + render( + Hi

'} + onChange={() => {}} + onImageUpload={async () => null} + onEditorReady={(ed) => { editor = ed as unknown as Editor; }} + /> + ); + await waitFor(() => expect(editor).not.toBeNull()); + expect(screen.queryByTestId('image-alt-add')).toBeNull(); + }); }); diff --git a/components/email/resizable-image.tsx b/components/email/resizable-image.tsx index bbed84d3c..fbe3ffc20 100644 --- a/components/email/resizable-image.tsx +++ b/components/email/resizable-image.tsx @@ -12,10 +12,14 @@ export interface ResizableImageOptions { onEditAlt?: (pos: number) => void; /** The name of the button that does it ("Image description"). */ editAltLabel?: string; + /** The chip on an image that has no description yet ("Add description"). */ + addAltLabel?: string; } function ResizableImageView({ node, updateAttributes, selected, extension, getPos }: NodeViewProps) { - const { onEditAlt, editAltLabel } = extension.options as ResizableImageOptions; + const { onEditAlt, editAltLabel, addAltLabel } = extension.options as ResizableImageOptions; + // No description and not marked decorative (""): an optional chip asks for one. + const undescribed = node.attrs.alt === null || node.attrs.alt === undefined; const imgRef = useRef(null); const [resizing, setResizing] = useState(false); // A remote image (a draft from another client, pasted web content) stays @@ -93,6 +97,23 @@ function ResizableImageView({ node, updateAttributes, selected, extension, getPo style={{ width: "100%", height: "auto", display: "block" }} draggable={false} /> + {undescribed && onEditAlt && addAltLabel && !selected && ( + // Optional, never in the way: pasting or dropping an image asks nothing. + + )} {selected && onEditAlt && ( // Edit what the image says to screen readers. Reachable with Tab // while the image is selected, and from the toolbar. @@ -140,7 +161,7 @@ export const ResizableImage = Node.create({ name: "image", addOptions() { - return { onEditAlt: undefined, editAltLabel: undefined }; + return { onEditAlt: undefined, editAltLabel: undefined, addAltLabel: undefined }; }, group: "inline", inline: true, diff --git a/components/email/rich-text-editor.tsx b/components/email/rich-text-editor.tsx index d4e4ac6cd..532291294 100644 --- a/components/email/rich-text-editor.tsx +++ b/components/email/rich-text-editor.tsx @@ -471,6 +471,7 @@ export function RichTextEditor({ }: RichTextEditorProps) { const rtlEditingSupport = useSettingsStore((st) => st.rtlEditingSupport); const tComposer = useTranslations("email_composer"); + const tUx = useTranslations("compose_ux"); const tQuote = useTranslations("email_viewer"); const onImageUploadRef = React.useRef(onImageUpload); onImageUploadRef.current = onImageUpload; @@ -521,6 +522,7 @@ export function RichTextEditor({ ResizableImage.configure({ onEditAlt: (pos: number) => setAltQueue((queue) => [...queue, { pos }]), editAltLabel: tComposer("image_alt.edit"), + addAltLabel: tUx("add_description"), }), Placeholder.configure({ placeholder, @@ -615,11 +617,11 @@ export function RichTextEditor({ if (result) { const { state } = view; const pos = view.posAtCoords({ left: event.clientX, top: event.clientY }); - // No alt yet (never the file name): the user is asked next. + // No alt yet (never the file name). const node = state.schema.nodes.image.create({ src: result.src, alt: null, cid: result.cid }); const tr = state.tr.insert(pos?.pos ?? state.selection.anchor, node); view.dispatch(tr); - setAltQueue((queue) => [...queue, { src: result.src }]); + // No dialog: the image offers "Add description" itself. } }); } @@ -638,7 +640,7 @@ export function RichTextEditor({ const node = state.schema.nodes.image.create({ src: result.src, alt: null, cid: result.cid }); const tr = state.tr.replaceSelectionWith(node); view.dispatch(tr); - setAltQueue((queue) => [...queue, { src: result.src }]); + // No dialog: the image offers "Add description" itself. } }); } diff --git a/locales/ar/common.json b/locales/ar/common.json index cb0736760..26c0c6673 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -1217,7 +1217,8 @@ "undo": "تراجع", "discard_draft": "تجاهل المسودة", "save_failed_close": "تعذّر حفظ المسودة. حاول مرة أخرى أو تجاهلها.", - "try_again": "حاول مرة أخرى" + "try_again": "حاول مرة أخرى", + "add_description": "إضافة وصف" }, "confirm_dialog": { "confirm": "تأكيد", diff --git a/locales/ca/common.json b/locales/ca/common.json index 28e6ac887..ccaf06e04 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -1217,7 +1217,8 @@ "undo": "Desfés", "discard_draft": "Descarta l'esborrany", "save_failed_close": "No s'ha pogut desar l'esborrany. Torneu-ho a provar o descarteu-lo.", - "try_again": "Torna-ho a provar" + "try_again": "Torna-ho a provar", + "add_description": "Afegeix una descripció" }, "confirm_dialog": { "confirm": "Confirma", diff --git a/locales/cs/common.json b/locales/cs/common.json index f1d8ed92f..151416cd7 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -1217,7 +1217,8 @@ "undo": "Zpět", "discard_draft": "Zahodit koncept", "save_failed_close": "Koncept se nepodařilo uložit. Zkuste to znovu, nebo ho zahoďte.", - "try_again": "Zkusit znovu" + "try_again": "Zkusit znovu", + "add_description": "Přidat popis" }, "confirm_dialog": { "confirm": "Potvrdit", diff --git a/locales/da/common.json b/locales/da/common.json index a820dfaf4..bd60f75bf 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -1217,7 +1217,8 @@ "undo": "Fortryd", "discard_draft": "Kassér kladde", "save_failed_close": "Kladden kunne ikke gemmes. Prøv igen, eller kassér den.", - "try_again": "Prøv igen" + "try_again": "Prøv igen", + "add_description": "Tilføj beskrivelse" }, "confirm_dialog": { "confirm": "Bekræft", diff --git a/locales/de/common.json b/locales/de/common.json index c23337444..b8a6eee95 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -1217,7 +1217,8 @@ "undo": "Rückgängig", "discard_draft": "Entwurf verwerfen", "save_failed_close": "Der Entwurf konnte nicht gespeichert werden. Versuchen Sie es erneut oder verwerfen Sie ihn.", - "try_again": "Erneut versuchen" + "try_again": "Erneut versuchen", + "add_description": "Beschreibung hinzufügen" }, "confirm_dialog": { "confirm": "Bestätigen", diff --git a/locales/en/common.json b/locales/en/common.json index ea741cca9..d5fffaad2 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -1217,7 +1217,8 @@ "undo": "Undo", "discard_draft": "Discard draft", "save_failed_close": "The draft could not be saved. Try again, or discard it.", - "try_again": "Try again" + "try_again": "Try again", + "add_description": "Add description" }, "confirm_dialog": { "confirm": "Confirm", diff --git a/locales/es/common.json b/locales/es/common.json index 0405a0dbf..98cd6c2d3 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -1217,7 +1217,8 @@ "undo": "Deshacer", "discard_draft": "Descartar borrador", "save_failed_close": "No se pudo guardar el borrador. Inténtelo de nuevo o descártelo.", - "try_again": "Reintentar" + "try_again": "Reintentar", + "add_description": "Añadir descripción" }, "confirm_dialog": { "confirm": "Confirmar", diff --git a/locales/et/common.json b/locales/et/common.json index 4e9f89304..3b9e3d6be 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -1217,7 +1217,8 @@ "undo": "Võta tagasi", "discard_draft": "Loobu mustandist", "save_failed_close": "Mustandit ei saanud salvestada. Proovige uuesti või loobuge sellest.", - "try_again": "Proovi uuesti" + "try_again": "Proovi uuesti", + "add_description": "Lisa kirjeldus" }, "confirm_dialog": { "confirm": "Kinnita", diff --git a/locales/fa/common.json b/locales/fa/common.json index bfe6d3cbb..a73b4df95 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -1217,7 +1217,8 @@ "undo": "واگرد", "discard_draft": "دور انداختن پیش‌نویس", "save_failed_close": "پیش‌نویس ذخیره نشد. دوباره تلاش کنید یا آن را دور بیندازید.", - "try_again": "تلاش دوباره" + "try_again": "تلاش دوباره", + "add_description": "افزودن توضیح" }, "confirm_dialog": { "confirm": "تأیید", diff --git a/locales/fr/common.json b/locales/fr/common.json index be7312951..5ddb3438e 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -1217,7 +1217,8 @@ "undo": "Annuler", "discard_draft": "Supprimer le brouillon", "save_failed_close": "Le brouillon n'a pas pu être enregistré. Réessayez ou supprimez-le.", - "try_again": "Réessayer" + "try_again": "Réessayer", + "add_description": "Ajouter une description" }, "confirm_dialog": { "confirm": "Confirmer", diff --git a/locales/he/common.json b/locales/he/common.json index 60853894b..a57187038 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -1160,7 +1160,8 @@ "undo": "ביטול", "discard_draft": "מחיקת הטיוטה", "save_failed_close": "לא ניתן היה לשמור את הטיוטה. נסו שוב או מחקו אותה.", - "try_again": "ניסיון נוסף" + "try_again": "ניסיון נוסף", + "add_description": "הוספת תיאור" }, "confirm_dialog": { "confirm": "אשר", diff --git a/locales/hu/common.json b/locales/hu/common.json index 79efccd53..cfccee90d 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -1217,7 +1217,8 @@ "undo": "Visszavonás", "discard_draft": "Piszkozat elvetése", "save_failed_close": "A piszkozatot nem sikerült menteni. Próbálja újra, vagy vesse el.", - "try_again": "Újra" + "try_again": "Újra", + "add_description": "Leírás hozzáadása" }, "confirm_dialog": { "confirm": "Megerősítés", diff --git a/locales/it/common.json b/locales/it/common.json index dbe2f779d..926421531 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -1217,7 +1217,8 @@ "undo": "Annulla", "discard_draft": "Elimina bozza", "save_failed_close": "Impossibile salvare la bozza. Riprova o eliminala.", - "try_again": "Riprova" + "try_again": "Riprova", + "add_description": "Aggiungi descrizione" }, "confirm_dialog": { "confirm": "Conferma", diff --git a/locales/ja/common.json b/locales/ja/common.json index 271e3d791..1a48da88b 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -1217,7 +1217,8 @@ "undo": "元に戻す", "discard_draft": "下書きを破棄", "save_failed_close": "下書きを保存できませんでした。もう一度試すか、破棄してください。", - "try_again": "再試行" + "try_again": "再試行", + "add_description": "説明を追加" }, "confirm_dialog": { "confirm": "確認", diff --git a/locales/ko/common.json b/locales/ko/common.json index 5e9bc4957..7fbd42927 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -1217,7 +1217,8 @@ "undo": "실행 취소", "discard_draft": "임시 보관 메일 삭제", "save_failed_close": "임시 보관 메일을 저장하지 못했습니다. 다시 시도하거나 삭제하세요.", - "try_again": "다시 시도" + "try_again": "다시 시도", + "add_description": "설명 추가" }, "confirm_dialog": { "confirm": "확인", diff --git a/locales/lv/common.json b/locales/lv/common.json index 57d08584b..a99b90bb1 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -1217,7 +1217,8 @@ "undo": "Atsaukt", "discard_draft": "Atmest melnrakstu", "save_failed_close": "Melnrakstu neizdevās saglabāt. Mēģiniet vēlreiz vai atmetiet to.", - "try_again": "Mēģināt vēlreiz" + "try_again": "Mēģināt vēlreiz", + "add_description": "Pievienot aprakstu" }, "confirm_dialog": { "confirm": "Apstiprināt", diff --git a/locales/mn/common.json b/locales/mn/common.json index 4d10e767b..204cc8105 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -1217,7 +1217,8 @@ "undo": "Буцаах", "discard_draft": "Ноорог устгах", "save_failed_close": "Ноорогийг хадгалж чадсангүй. Дахин оролдох эсвэл устгана уу.", - "try_again": "Дахин оролдох" + "try_again": "Дахин оролдох", + "add_description": "Тайлбар нэмэх" }, "confirm_dialog": { "confirm": "Баталгаажуулах", diff --git a/locales/nb/common.json b/locales/nb/common.json index d77f2071e..755bcbc45 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -1217,7 +1217,8 @@ "undo": "Angre", "discard_draft": "Forkast utkast", "save_failed_close": "Utkastet kunne ikke lagres. Prøv igjen, eller forkast det.", - "try_again": "Prøv igjen" + "try_again": "Prøv igjen", + "add_description": "Legg til beskrivelse" }, "confirm_dialog": { "confirm": "Bekreft", diff --git a/locales/nl/common.json b/locales/nl/common.json index a52de8a28..c9ed9c8dd 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -1217,7 +1217,8 @@ "undo": "Ongedaan maken", "discard_draft": "Concept verwijderen", "save_failed_close": "Het concept kon niet worden opgeslagen. Probeer het opnieuw of verwijder het.", - "try_again": "Opnieuw proberen" + "try_again": "Opnieuw proberen", + "add_description": "Beschrijving toevoegen" }, "confirm_dialog": { "confirm": "Bevestigen", diff --git a/locales/pl/common.json b/locales/pl/common.json index 886c5c7d0..74467a752 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -1217,7 +1217,8 @@ "undo": "Cofnij", "discard_draft": "Odrzuć wersję roboczą", "save_failed_close": "Nie udało się zapisać wersji roboczej. Spróbuj ponownie lub ją odrzuć.", - "try_again": "Spróbuj ponownie" + "try_again": "Spróbuj ponownie", + "add_description": "Dodaj opis" }, "confirm_dialog": { "confirm": "Potwierdź", diff --git a/locales/pt/common.json b/locales/pt/common.json index daaebc388..9e2b2d331 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -1217,7 +1217,8 @@ "undo": "Desfazer", "discard_draft": "Descartar rascunho", "save_failed_close": "Não foi possível guardar o rascunho. Tente novamente ou descarte-o.", - "try_again": "Tentar novamente" + "try_again": "Tentar novamente", + "add_description": "Adicionar descrição" }, "confirm_dialog": { "confirm": "Confirmar", diff --git a/locales/ro/common.json b/locales/ro/common.json index ba56d909e..db1ed7bce 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -1217,7 +1217,8 @@ "undo": "Anulează", "discard_draft": "Renunță la ciornă", "save_failed_close": "Ciorna nu a putut fi salvată. Încercați din nou sau renunțați la ea.", - "try_again": "Încearcă din nou" + "try_again": "Încearcă din nou", + "add_description": "Adaugă o descriere" }, "confirm_dialog": { "confirm": "Confirmare", diff --git a/locales/ru/common.json b/locales/ru/common.json index 6a1a7ff6a..810933d38 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -1217,7 +1217,8 @@ "undo": "Отменить", "discard_draft": "Удалить черновик", "save_failed_close": "Не удалось сохранить черновик. Попробуйте ещё раз или удалите его.", - "try_again": "Повторить" + "try_again": "Повторить", + "add_description": "Добавить описание" }, "confirm_dialog": { "confirm": "Подтвердить", diff --git a/locales/sk/common.json b/locales/sk/common.json index 0d700da9a..72badccba 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -1217,7 +1217,8 @@ "undo": "Späť", "discard_draft": "Zahodiť koncept", "save_failed_close": "Koncept sa nepodarilo uložiť. Skúste to znova alebo ho zahoďte.", - "try_again": "Skúsiť znova" + "try_again": "Skúsiť znova", + "add_description": "Pridať popis" }, "confirm_dialog": { "confirm": "Potvrdiť", diff --git a/locales/th/common.json b/locales/th/common.json index 467f5ebf4..75960886f 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -1565,7 +1565,8 @@ "undo": "เลิกทำ", "discard_draft": "ทิ้งฉบับร่าง", "save_failed_close": "บันทึกฉบับร่างไม่ได้ ลองอีกครั้งหรือทิ้งไป", - "try_again": "ลองอีกครั้ง" + "try_again": "ลองอีกครั้ง", + "add_description": "เพิ่มคำอธิบาย" }, "email_viewer": { "read_receipt": { diff --git a/locales/tr/common.json b/locales/tr/common.json index 6fed03150..c98b22e1a 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -1217,7 +1217,8 @@ "undo": "Geri al", "discard_draft": "Taslağı sil", "save_failed_close": "Taslak kaydedilemedi. Yeniden deneyin ya da silin.", - "try_again": "Yeniden dene" + "try_again": "Yeniden dene", + "add_description": "Açıklama ekle" }, "confirm_dialog": { "confirm": "Onayla", diff --git a/locales/uk/common.json b/locales/uk/common.json index 5a3b31482..61190aba0 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -1217,7 +1217,8 @@ "undo": "Скасувати", "discard_draft": "Видалити чернетку", "save_failed_close": "Не вдалося зберегти чернетку. Спробуйте ще раз або видаліть її.", - "try_again": "Спробувати ще раз" + "try_again": "Спробувати ще раз", + "add_description": "Додати опис" }, "confirm_dialog": { "confirm": "Підтвердити", diff --git a/locales/vi/common.json b/locales/vi/common.json index df09e67d0..7f1cdde96 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -1229,7 +1229,8 @@ "undo": "Hoàn tác", "discard_draft": "Hủy bản nháp", "save_failed_close": "Không lưu được bản nháp. Hãy thử lại hoặc hủy bản nháp.", - "try_again": "Thử lại" + "try_again": "Thử lại", + "add_description": "Thêm mô tả" }, "confirm_dialog": { "confirm": "Xác nhận", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index ede70a153..02b7f47bb 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -1217,7 +1217,8 @@ "undo": "復原", "discard_draft": "捨棄草稿", "save_failed_close": "無法儲存草稿。請再試一次或捨棄草稿。", - "try_again": "再試一次" + "try_again": "再試一次", + "add_description": "新增描述" }, "confirm_dialog": { "confirm": "確認", diff --git a/locales/zh/common.json b/locales/zh/common.json index ccd831040..0b033d382 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -1217,7 +1217,8 @@ "undo": "撤销", "discard_draft": "舍弃草稿", "save_failed_close": "无法保存草稿。请重试或舍弃草稿。", - "try_again": "重试" + "try_again": "重试", + "add_description": "添加描述" }, "confirm_dialog": { "confirm": "确认",