From fe4f809092fc95b3c8d59592feadcae8e24852da Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 14 Oct 2026 03:00:46 +0200 Subject: [PATCH] fix: keep a plugin side panel from squeezing the message too narrow to read --- .../viewer-plugin-panel-width.test.tsx | 78 +++++++++++++++++++ components/email/email-viewer.tsx | 41 ++++++++-- locales/ar/common.json | 4 +- locales/ca/common.json | 4 +- locales/cs/common.json | 4 +- locales/da/common.json | 4 +- locales/de/common.json | 22 +++--- locales/en/common.json | 22 +++--- locales/es/common.json | 4 +- locales/fa/common.json | 4 +- locales/fr/common.json | 4 +- locales/he/common.json | 4 +- locales/hu/common.json | 4 +- locales/it/common.json | 4 +- locales/ja/common.json | 4 +- locales/ko/common.json | 4 +- locales/lv/common.json | 4 +- locales/mn/common.json | 4 +- locales/nb/common.json | 4 +- locales/nl/common.json | 4 +- locales/pl/common.json | 4 +- locales/pt/common.json | 4 +- locales/ro/common.json | 4 +- locales/ru/common.json | 4 +- locales/sk/common.json | 4 +- locales/tr/common.json | 4 +- locales/uk/common.json | 4 +- locales/zh-TW/common.json | 4 +- locales/zh/common.json | 4 +- 29 files changed, 212 insertions(+), 51 deletions(-) create mode 100644 components/email/__tests__/viewer-plugin-panel-width.test.tsx diff --git a/components/email/__tests__/viewer-plugin-panel-width.test.tsx b/components/email/__tests__/viewer-plugin-panel-width.test.tsx new file mode 100644 index 000000000..a16703c47 --- /dev/null +++ b/components/email/__tests__/viewer-plugin-panel-width.test.tsx @@ -0,0 +1,78 @@ +import { render, screen, act, fireEvent } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import React from 'react'; +import type { Email, Mailbox } from '@/lib/jmap/types'; + +// With a plugin panel open at 280 px next to a 300 px message (three panes +// on a laptop), the subject broke after every few letters. The panel now +// starts collapsed when it would leave the message under 480 px. + +vi.mock('@/hooks/use-media-query', () => ({ + useDeviceDetection: () => ({ isMobile: false, isTablet: false, isDesktop: true }), + useMediaQuery: () => false, +})); +vi.mock('@/components/tour/tour-provider', () => ({ + useTour: () => ({ activeTour: null, startTour: vi.fn(), endTour: vi.fn(), registerStep: vi.fn() }), +})); +vi.mock('@/components/plugins/plugin-slot', () => ({ + PluginSlot: ({ name }: { name: string }) => React.createElement('div', { 'data-testid': `slot-${name}` }), +})); +vi.mock('@/hooks/use-plugin-slot-offers', () => ({ + usePluginSlotOffers: (slot: string) => (slot === 'email-detail-sidebar' ? [{ pluginId: 'notes', order: 100 }] : []), +})); + +let reportWidth = 0; +const observers: Array<(entries: Array<{ contentRect: { width: number } }>) => void> = []; +class FakeResizeObserver { + constructor(private cb: (entries: Array<{ contentRect: { width: number } }>) => void) { observers.push(cb); } + observe() { this.cb([{ contentRect: { width: reportWidth } }]); } + unobserve() {} + disconnect() {} +} +vi.stubGlobal('ResizeObserver', FakeResizeObserver); + +const { EmailViewer } = await import('../email-viewer'); + +const mailboxes = [ + { id: 'inbox', name: 'Inbox', role: 'inbox', parentId: null, myRights: { mayAddItems: true } }, +] as unknown as Mailbox[]; +const email = { + id: 'e1', blobId: 'b1', threadId: 't1', mailboxIds: { inbox: true }, keywords: { $seen: true }, + from: [{ name: 'Alice', email: 'alice@example.com' }], to: [], subject: 'Hello', receivedAt: '2026-01-01T00:00:00Z', + preview: '', hasAttachment: false, size: 1, textBody: [], htmlBody: [], attachments: [], bodyValues: {}, +} as unknown as Email; + +function renderViewer() { + return render(); +} + +beforeEach(() => { + localStorage.clear(); + observers.length = 0; +}); + +describe('plugin detail panel and narrow messages', () => { + it('starts collapsed when it would leave the message too narrow', () => { + reportWidth = 640; + renderViewer(); + + expect(screen.queryByTestId('slot-email-detail-sidebar')).not.toBeInTheDocument(); + expect(screen.getByLabelText('plugin_panel_show')).toBeInTheDocument(); + }); + + it('opens anyway when the user asks', () => { + reportWidth = 640; + renderViewer(); + + act(() => { fireEvent.click(screen.getByLabelText('plugin_panel_show')); }); + + expect(screen.getByTestId('slot-email-detail-sidebar')).toBeInTheDocument(); + }); + + it('is open when there is room', () => { + reportWidth = 1100; + renderViewer(); + + expect(screen.getByTestId('slot-email-detail-sidebar')).toBeInTheDocument(); + }); +}); diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index 030af2e87..8fc8e9118 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -961,6 +961,9 @@ function DetailsCategorySlot({ email, category }: { email: EmailReadView | null; return ; } +/** Narrowest the message may get next to a plugin panel before the panel collapses. */ +const MIN_READER_WIDTH = 480; + function SidebarSection({ icon: Icon, title, children }: { icon: React.ComponentType<{ className?: string }>; title: string; children: React.ReactNode }) { return (
@@ -1422,6 +1425,25 @@ export function EmailViewer({ } catch { return 280; } }); const detailSidebarWidthRef = useRef(detailSidebarWidth); + // The panel takes its width from the message. When that would leave the + // message too narrow to read (three panes on a laptop), the panel starts + // collapsed; opening it anyway is still the user's call. + const viewerRootRef = useRef(null); + const [viewerWidth, setViewerWidth] = useState(0); + const [detailSidebarForcedOpen, setDetailSidebarForcedOpen] = useState(false); + useEffect(() => { + if (!hasDetailSidebar || typeof ResizeObserver === 'undefined') return; + const el = viewerRootRef.current; + if (!el) return; + const ro = new ResizeObserver((entries) => { + const width = Math.round(entries[0]?.contentRect.width ?? 0); + setViewerWidth(width); + }); + ro.observe(el); + return () => ro.disconnect(); + }, [hasDetailSidebar]); + const readerTooNarrow = viewerWidth > 0 && viewerWidth - detailSidebarWidth < MIN_READER_WIDTH; + const sidebarCollapsed = detailSidebarCollapsed || (readerTooNarrow && !detailSidebarForcedOpen); useEffect(() => { try { localStorage.setItem('emailDetailSidebarCollapsed', detailSidebarCollapsed ? '1' : '0'); } catch { /* ignore */ } @@ -3993,6 +4015,7 @@ export function EmailViewer({ return (
@@ -6073,20 +6096,23 @@ export function EmailViewer({ {hasDetailSidebar && !isMobile && ( <> {/* Collapse toggle when sidebar is collapsed */} - {detailSidebarCollapsed && ( + {sidebarCollapsed && (
)} - {!detailSidebarCollapsed && ( + {!sidebarCollapsed && ( <> { detailSidebarWidthRef.current = detailSidebarWidth; }} @@ -6105,9 +6131,12 @@ export function EmailViewer({ diff --git a/locales/ar/common.json b/locales/ar/common.json index 8ffea83c2..24f3f9268 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -620,7 +620,9 @@ "copied": "تم نسخ الرمز", "copy_failed": "تعذّر نسخ الرمز" }, - "mv_position": "{index} من {total}" + "mv_position": "{index} من {total}", + "plugin_panel_show": "إظهار لوحة الإضافة", + "plugin_panel_hide": "إخفاء لوحة الإضافة" }, "email_composer": { "minimize": "تصغير", diff --git a/locales/ca/common.json b/locales/ca/common.json index 5531a18fe..d9ea2a1b8 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -620,7 +620,9 @@ "copied": "Codi copiat", "copy_failed": "No s'ha pogut copiar el codi" }, - "mv_position": "{index} de {total}" + "mv_position": "{index} de {total}", + "plugin_panel_show": "Mostra el tauler del connector", + "plugin_panel_hide": "Amaga el tauler del connector" }, "email_composer": { "minimize": "Minimitza", diff --git a/locales/cs/common.json b/locales/cs/common.json index c618351a8..aec9ada4e 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -620,7 +620,9 @@ "copied": "Kód zkopírován", "copy_failed": "Kód se nepodařilo zkopírovat" }, - "mv_position": "{index} z {total}" + "mv_position": "{index} z {total}", + "plugin_panel_show": "Zobrazit panel pluginu", + "plugin_panel_hide": "Skrýt panel pluginu" }, "email_composer": { "minimize": "Minimalizovat", diff --git a/locales/da/common.json b/locales/da/common.json index d263b1602..e099ca36a 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -620,7 +620,9 @@ "copied": "Kode kopieret", "copy_failed": "Koden kunne ikke kopieres" }, - "mv_position": "{index} af {total}" + "mv_position": "{index} af {total}", + "plugin_panel_show": "Vis pluginpanel", + "plugin_panel_hide": "Skjul pluginpanel" }, "email_composer": { "minimize": "Minimer", diff --git a/locales/de/common.json b/locales/de/common.json index 12eb3a71a..2ba2ed0de 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -359,15 +359,15 @@ "external_content_warning": "Bilder und externe Inhalte wurden blockiert", "load_external_content": "Bilder laden", "trust_sender": "Diesem Absender immer vertrauen", - "sender_check": { - "unverified_label": "Absender nicht bestätigt", - "failed_label": "Absenderprüfung fehlgeschlagen", - "unverified": "Diese Nachricht gibt vor, von {domain} zu kommen, aber nichts bestätigt das. Sie trägt keine gültige Signatur von {domain} und kam nicht von einem Server, den {domain} zulässt.", - "unverified_sent_from": "Diese Nachricht gibt vor, von {domain} zu kommen, wurde aber von {host} verschickt und trägt keine gültige Signatur von {domain}.", - "failed": "Diese Nachricht gibt vor, von {domain} zu kommen, hat aber die Absenderprüfung dieser Domain nicht bestanden. Sie ist möglicherweise gefälscht.", - "failed_sent_from": "Diese Nachricht gibt vor, von {domain} zu kommen, wurde aber von {host} verschickt und hat die Absenderprüfung von {domain} nicht bestanden. Sie ist möglicherweise gefälscht.", - "caution": "Seien Sie vorsichtig mit Links, Anhängen und Fragen nach Passwörtern oder Zahlungsdaten." - }, + "sender_check": { + "unverified_label": "Absender nicht bestätigt", + "failed_label": "Absenderprüfung fehlgeschlagen", + "unverified": "Diese Nachricht gibt vor, von {domain} zu kommen, aber nichts bestätigt das. Sie trägt keine gültige Signatur von {domain} und kam nicht von einem Server, den {domain} zulässt.", + "unverified_sent_from": "Diese Nachricht gibt vor, von {domain} zu kommen, wurde aber von {host} verschickt und trägt keine gültige Signatur von {domain}.", + "failed": "Diese Nachricht gibt vor, von {domain} zu kommen, hat aber die Absenderprüfung dieser Domain nicht bestanden. Sie ist möglicherweise gefälscht.", + "failed_sent_from": "Diese Nachricht gibt vor, von {domain} zu kommen, wurde aber von {host} verschickt und hat die Absenderprüfung von {domain} nicht bestanden. Sie ist möglicherweise gefälscht.", + "caution": "Seien Sie vorsichtig mit Links, Anhängen und Fragen nach Passwörtern oder Zahlungsdaten." + }, "back_to_list": "Zurück zur Liste", "view_contact": "Kontakt anzeigen", "message_details": "Nachrichtendetails", @@ -620,7 +620,9 @@ "copied": "Code kopiert", "copy_failed": "Code konnte nicht kopiert werden" }, - "mv_position": "{index} von {total}" + "mv_position": "{index} von {total}", + "plugin_panel_show": "Plugin-Bereich einblenden", + "plugin_panel_hide": "Plugin-Bereich ausblenden" }, "email_composer": { "minimize": "Minimieren", diff --git a/locales/en/common.json b/locales/en/common.json index 685016f2d..942d372a4 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -359,15 +359,15 @@ "external_content_warning": "Images and external content have been blocked", "load_external_content": "Load images", "trust_sender": "Always trust this sender", - "sender_check": { - "unverified_label": "Unverified sender", - "failed_label": "Sender check failed", - "unverified": "This message claims to be from {domain}, but nothing confirms that. It has no valid signature from {domain} and didn't come from a server {domain} authorizes.", - "unverified_sent_from": "This message claims to be from {domain}, but it was sent from {host} and has no valid signature from {domain}.", - "failed": "This message claims to be from {domain}, but it failed that domain's sender checks. It may be forged.", - "failed_sent_from": "This message claims to be from {domain}, but it was sent from {host} and failed {domain}'s sender checks. It may be forged.", - "caution": "Be careful with links, attachments and requests for passwords or payment details." - }, + "sender_check": { + "unverified_label": "Unverified sender", + "failed_label": "Sender check failed", + "unverified": "This message claims to be from {domain}, but nothing confirms that. It has no valid signature from {domain} and didn't come from a server {domain} authorizes.", + "unverified_sent_from": "This message claims to be from {domain}, but it was sent from {host} and has no valid signature from {domain}.", + "failed": "This message claims to be from {domain}, but it failed that domain's sender checks. It may be forged.", + "failed_sent_from": "This message claims to be from {domain}, but it was sent from {host} and failed {domain}'s sender checks. It may be forged.", + "caution": "Be careful with links, attachments and requests for passwords or payment details." + }, "back_to_list": "Back to list", "view_contact": "View contact", "message_details": "Message Details", @@ -620,7 +620,9 @@ "copied": "Code copied", "copy_failed": "Could not copy the code" }, - "mv_position": "{index} of {total}" + "mv_position": "{index} of {total}", + "plugin_panel_show": "Show plugin panel", + "plugin_panel_hide": "Hide plugin panel" }, "email_composer": { "minimize": "Minimize", diff --git a/locales/es/common.json b/locales/es/common.json index 603052683..4a7136e37 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -620,7 +620,9 @@ "copied": "Código copiado", "copy_failed": "No se pudo copiar el código" }, - "mv_position": "{index} de {total}" + "mv_position": "{index} de {total}", + "plugin_panel_show": "Mostrar el panel del complemento", + "plugin_panel_hide": "Ocultar el panel del complemento" }, "email_composer": { "minimize": "Minimizar", diff --git a/locales/fa/common.json b/locales/fa/common.json index 6726945b8..2343dac11 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -620,7 +620,9 @@ "copied": "کد کپی شد", "copy_failed": "کپی کد ممکن نشد" }, - "mv_position": "{index} از {total}" + "mv_position": "{index} از {total}", + "plugin_panel_show": "نمایش پنل افزونه", + "plugin_panel_hide": "پنهان کردن پنل افزونه" }, "email_composer": { "minimize": "کوچک کردن", diff --git a/locales/fr/common.json b/locales/fr/common.json index 27ed6886d..3527665e8 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -620,7 +620,9 @@ "copied": "Code copié", "copy_failed": "Impossible de copier le code" }, - "mv_position": "{index} sur {total}" + "mv_position": "{index} sur {total}", + "plugin_panel_show": "Afficher le panneau du plugin", + "plugin_panel_hide": "Masquer le panneau du plugin" }, "email_composer": { "minimize": "Réduire", diff --git a/locales/he/common.json b/locales/he/common.json index f1f7fdfcb..10b638bb2 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -585,7 +585,9 @@ "copied": "הקוד הועתק", "copy_failed": "לא ניתן להעתיק את הקוד" }, - "mv_position": "{index} מתוך {total}" + "mv_position": "{index} מתוך {total}", + "plugin_panel_show": "הצגת חלונית התוסף", + "plugin_panel_hide": "הסתרת חלונית התוסף" }, "email_composer": { "minimize": "מזעור", diff --git a/locales/hu/common.json b/locales/hu/common.json index b2b779e6f..550c4292e 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -620,7 +620,9 @@ "copied": "Kód másolva", "copy_failed": "Nem sikerült másolni a kódot" }, - "mv_position": "{index} / {total}" + "mv_position": "{index} / {total}", + "plugin_panel_show": "Bővítménypanel megjelenítése", + "plugin_panel_hide": "Bővítménypanel elrejtése" }, "email_composer": { "minimize": "Kis méret", diff --git a/locales/it/common.json b/locales/it/common.json index beb5e13a9..a738930ef 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -620,7 +620,9 @@ "copied": "Codice copiato", "copy_failed": "Impossibile copiare il codice" }, - "mv_position": "{index} di {total}" + "mv_position": "{index} di {total}", + "plugin_panel_show": "Mostra il pannello del plugin", + "plugin_panel_hide": "Nascondi il pannello del plugin" }, "email_composer": { "minimize": "Riduci a icona", diff --git a/locales/ja/common.json b/locales/ja/common.json index 5288d58b1..b97e9101e 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -620,7 +620,9 @@ "copied": "コードをコピーしました", "copy_failed": "コードをコピーできませんでした" }, - "mv_position": "{index} / {total}" + "mv_position": "{index} / {total}", + "plugin_panel_show": "プラグインパネルを表示", + "plugin_panel_hide": "プラグインパネルを隠す" }, "email_composer": { "minimize": "最小化", diff --git a/locales/ko/common.json b/locales/ko/common.json index 0ce740141..195cf5797 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -620,7 +620,9 @@ "copied": "코드를 복사했습니다", "copy_failed": "코드를 복사할 수 없습니다" }, - "mv_position": "{index} / {total}" + "mv_position": "{index} / {total}", + "plugin_panel_show": "플러그인 패널 표시", + "plugin_panel_hide": "플러그인 패널 숨기기" }, "email_composer": { "minimize": "최소화", diff --git a/locales/lv/common.json b/locales/lv/common.json index bc8638beb..cf298dfeb 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -620,7 +620,9 @@ "copied": "Kods nokopēts", "copy_failed": "Neizdevās nokopēt kodu" }, - "mv_position": "{index} no {total}" + "mv_position": "{index} no {total}", + "plugin_panel_show": "Rādīt spraudņa paneli", + "plugin_panel_hide": "Slēpt spraudņa paneli" }, "email_composer": { "minimize": "Minimizēt", diff --git a/locales/mn/common.json b/locales/mn/common.json index 97b68f571..21ba2eed0 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -620,7 +620,9 @@ "copied": "Код хуулагдлаа", "copy_failed": "Кодыг хуулж чадсангүй" }, - "mv_position": "{total}-с {index}" + "mv_position": "{total}-с {index}", + "plugin_panel_show": "Залгаасын самбарыг харуулах", + "plugin_panel_hide": "Залгаасын самбарыг нуух" }, "email_composer": { "minimize": "Багасгах", diff --git a/locales/nb/common.json b/locales/nb/common.json index c042d86d2..0ea3afcd0 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -620,7 +620,9 @@ "copied": "Kode kopiert", "copy_failed": "Kunne ikke kopiere koden" }, - "mv_position": "{index} av {total}" + "mv_position": "{index} av {total}", + "plugin_panel_show": "Vis tilleggspanel", + "plugin_panel_hide": "Skjul tilleggspanel" }, "email_composer": { "minimize": "Minimer", diff --git a/locales/nl/common.json b/locales/nl/common.json index 4b2213065..12acefe93 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -620,7 +620,9 @@ "copied": "Code gekopieerd", "copy_failed": "Kan de code niet kopiëren" }, - "mv_position": "{index} van {total}" + "mv_position": "{index} van {total}", + "plugin_panel_show": "Plug-inpaneel tonen", + "plugin_panel_hide": "Plug-inpaneel verbergen" }, "email_composer": { "minimize": "Minimaliseren", diff --git a/locales/pl/common.json b/locales/pl/common.json index d396f0356..c0d941186 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -620,7 +620,9 @@ "copied": "Skopiowano kod", "copy_failed": "Nie udało się skopiować kodu" }, - "mv_position": "{index} z {total}" + "mv_position": "{index} z {total}", + "plugin_panel_show": "Pokaż panel wtyczki", + "plugin_panel_hide": "Ukryj panel wtyczki" }, "email_composer": { "minimize": "Minimalizuj", diff --git a/locales/pt/common.json b/locales/pt/common.json index 7cd725830..4b0c0702b 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -620,7 +620,9 @@ "copied": "Código copiado", "copy_failed": "Não foi possível copiar o código" }, - "mv_position": "{index} de {total}" + "mv_position": "{index} de {total}", + "plugin_panel_show": "Mostrar o painel do plugin", + "plugin_panel_hide": "Ocultar o painel do plugin" }, "email_composer": { "minimize": "Minimizar", diff --git a/locales/ro/common.json b/locales/ro/common.json index c71303c6b..9252a4761 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -620,7 +620,9 @@ "copied": "Cod copiat", "copy_failed": "Codul nu a putut fi copiat" }, - "mv_position": "{index} din {total}" + "mv_position": "{index} din {total}", + "plugin_panel_show": "Afișează panoul pluginului", + "plugin_panel_hide": "Ascunde panoul pluginului" }, "email_composer": { "minimize": "Minimizează", diff --git a/locales/ru/common.json b/locales/ru/common.json index 4596ec388..359f85ecb 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -620,7 +620,9 @@ "copied": "Код скопирован", "copy_failed": "Не удалось скопировать код" }, - "mv_position": "{index} из {total}" + "mv_position": "{index} из {total}", + "plugin_panel_show": "Показать панель плагина", + "plugin_panel_hide": "Скрыть панель плагина" }, "email_composer": { "minimize": "Свернуть", diff --git a/locales/sk/common.json b/locales/sk/common.json index 22f4c5530..e63d2a6e9 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -620,7 +620,9 @@ "copied": "Kód skopírovaný", "copy_failed": "Kód sa nepodarilo skopírovať" }, - "mv_position": "{index} z {total}" + "mv_position": "{index} z {total}", + "plugin_panel_show": "Zobraziť panel doplnku", + "plugin_panel_hide": "Skryť panel doplnku" }, "email_composer": { "minimize": "Minimalizovať", diff --git a/locales/tr/common.json b/locales/tr/common.json index acf237629..18127549b 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -620,7 +620,9 @@ "copied": "Kod kopyalandı", "copy_failed": "Kod kopyalanamadı" }, - "mv_position": "{index} / {total}" + "mv_position": "{index} / {total}", + "plugin_panel_show": "Eklenti panelini göster", + "plugin_panel_hide": "Eklenti panelini gizle" }, "email_composer": { "minimize": "Simge durumuna küçült", diff --git a/locales/uk/common.json b/locales/uk/common.json index 8ce2a0b12..d28f9e356 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -620,7 +620,9 @@ "copied": "Код скопійовано", "copy_failed": "Не вдалося скопіювати код" }, - "mv_position": "{index} з {total}" + "mv_position": "{index} з {total}", + "plugin_panel_show": "Показати панель плагіна", + "plugin_panel_hide": "Сховати панель плагіна" }, "email_composer": { "minimize": "Згорнути", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 1415806e0..787a71aba 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -620,7 +620,9 @@ "copied": "驗證碼已複製", "copy_failed": "無法複製驗證碼" }, - "mv_position": "第 {index} 封,共 {total} 封" + "mv_position": "第 {index} 封,共 {total} 封", + "plugin_panel_show": "顯示外掛程式面板", + "plugin_panel_hide": "隱藏外掛程式面板" }, "email_composer": { "minimize": "最小化", diff --git a/locales/zh/common.json b/locales/zh/common.json index 10342d03d..ec47531a7 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -620,7 +620,9 @@ "copied": "验证码已复制", "copy_failed": "无法复制验证码" }, - "mv_position": "第 {index} 封,共 {total} 封" + "mv_position": "第 {index} 封,共 {total} 封", + "plugin_panel_show": "显示插件面板", + "plugin_panel_hide": "隐藏插件面板" }, "email_composer": { "minimize": "最小化",