From 03031fcb13430afe12b9e9d60c1cc0409ef4823f Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 17 Oct 2026 01:41:18 +0200 Subject: [PATCH] feat: jump to the next unread message with n #514 --- components/mail/mail-app.tsx | 12 ++++++ .../__tests__/use-keyboard-shortcuts.test.ts | 42 +++++++++++++++++- hooks/use-keyboard-shortcuts.ts | 11 +++++ lib/__tests__/next-unread-email.test.ts | 43 +++++++++++++++++++ lib/next-unread-email.ts | 19 ++++++++ locales/ar/common.json | 4 +- locales/ca/common.json | 4 +- locales/cs/common.json | 4 +- locales/da/common.json | 4 +- locales/de/common.json | 4 +- locales/en/common.json | 4 +- 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 +- 32 files changed, 207 insertions(+), 28 deletions(-) create mode 100644 lib/__tests__/next-unread-email.test.ts create mode 100644 lib/next-unread-email.ts diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx index 2672d322e..912964b73 100644 --- a/components/mail/mail-app.tsx +++ b/components/mail/mail-app.tsx @@ -53,6 +53,7 @@ import { useDeviceDetection } from "@/hooks/use-media-query"; import { usePaneSize } from "@/hooks/use-pane-size"; import { usePaneId } from "@/hooks/use-pane-context"; import { useKeyboardShortcuts, type GoToMailboxTarget } from "@/hooks/use-keyboard-shortcuts"; +import { findNextUnreadIndex } from "@/lib/next-unread-email"; import { useRefreshGesture } from "@/hooks/use-refresh-gesture"; import { useConfirmDialog } from "@/hooks/use-confirm-dialog"; import { usePromptDialog } from "@/hooks/use-prompt-dialog"; @@ -849,6 +850,17 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { if (mvShell) setCursorOnly(true); } }, + // `n`: open the next unread message below the current one, in the order + // the list has loaded. Only the loaded rows count; say so when none is + // left instead of wrapping to the top. (#514) + onNextUnread: () => { + const nextIndex = findNextUnreadIndex(activeEmails, selectedEmail?.id); + if (nextIndex === -1) { + toast.info(t('shortcuts.navigation.no_more_unread')); + return; + } + void handleEmailSelect(activeEmails[nextIndex]); + }, onOpenEmail: () => { // Outside Mountain View the email is already open by virtue of being // selected; under it this is the step that promotes cursor to open. diff --git a/hooks/__tests__/use-keyboard-shortcuts.test.ts b/hooks/__tests__/use-keyboard-shortcuts.test.ts index 9d20d9e0b..4e75a0dc4 100644 --- a/hooks/__tests__/use-keyboard-shortcuts.test.ts +++ b/hooks/__tests__/use-keyboard-shortcuts.test.ts @@ -1,6 +1,6 @@ import { renderHook } from '@testing-library/react'; import { describe, it, expect, vi } from 'vitest'; -import { useKeyboardShortcuts } from '../use-keyboard-shortcuts'; +import { useKeyboardShortcuts, KEYBOARD_SHORTCUTS } from '../use-keyboard-shortcuts'; // #683: the help modal advertises `x` for thread expansion and the hook binds // it; the handler must actually be invoked when a message is selected. @@ -90,3 +90,43 @@ describe('useKeyboardShortcuts: Shift + arrow extends the selection', () => { expect(onExtendSelection).not.toHaveBeenCalled(); }); }); + +// #514: `n` jumps to the next unread message, with or without one open. +describe('useKeyboardShortcuts: n opens the next unread message', () => { + function pressN(shiftKey = false) { + document.dispatchEvent(new KeyboardEvent('keydown', { key: shiftKey ? 'N' : 'n', code: 'KeyN', shiftKey, bubbles: true })); + } + + it('calls onNextUnread with a message open', () => { + const onNextUnread = vi.fn(); + renderHook(() => + useKeyboardShortcuts({ enabled: true, emails: [], selectedEmailId: 'e1', handlers: { onNextUnread } }), + ); + + pressN(); + + expect(onNextUnread).toHaveBeenCalledTimes(1); + }); + + it('calls onNextUnread with nothing open', () => { + const onNextUnread = vi.fn(); + renderHook(() => useKeyboardShortcuts({ enabled: true, emails: [], handlers: { onNextUnread } })); + + pressN(); + + expect(onNextUnread).toHaveBeenCalledTimes(1); + }); + + it('ignores Shift + N', () => { + const onNextUnread = vi.fn(); + renderHook(() => useKeyboardShortcuts({ enabled: true, emails: [], handlers: { onNextUnread } })); + + pressN(true); + + expect(onNextUnread).not.toHaveBeenCalled(); + }); + + it('is listed in the help modal under navigation', () => { + expect(KEYBOARD_SHORTCUTS.navigation).toContainEqual({ key: 'n', description: 'shortcuts.navigation.next_unread' }); + }); +}); diff --git a/hooks/use-keyboard-shortcuts.ts b/hooks/use-keyboard-shortcuts.ts index 30e9de777..06c81e3e7 100644 --- a/hooks/use-keyboard-shortcuts.ts +++ b/hooks/use-keyboard-shortcuts.ts @@ -12,6 +12,8 @@ export interface KeyboardShortcutHandlers { onPreviousEmail?: () => void; onOpenEmail?: () => void; onCloseEmail?: () => void; + /** `n`: open the next unread message below the current one (#514). */ + onNextUnread?: () => void; // Email actions onReply?: () => void; @@ -319,6 +321,14 @@ export function useKeyboardShortcuts({ run("toggle-thread-expansion", h.onToggleThreadExpansion); } break; + + // Navigation: next unread, as in Thunderbird (#514) + case "n": + if (!event.shiftKey) { + event.preventDefault(); + run("next-unread", h.onNextUnread); + } + break; } }, [selectedEmailId, selectionCount, mountainViewKeys] @@ -387,6 +397,7 @@ export const KEYBOARD_SHORTCUTS = { { key: "k / ↑", description: "shortcuts.navigation.previous_email" }, { key: "Enter / o", description: "shortcuts.navigation.open_email" }, { key: "Esc", description: "shortcuts.navigation.close_email" }, + { key: "n", description: "shortcuts.navigation.next_unread" }, { key: "g → i / s / t / d / a", description: "shortcuts.navigation.go_to_folder", layout: "mountain-view" }, ], actions: [ diff --git a/lib/__tests__/next-unread-email.test.ts b/lib/__tests__/next-unread-email.test.ts new file mode 100644 index 000000000..48b5c3f8e --- /dev/null +++ b/lib/__tests__/next-unread-email.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect } from 'vitest'; +import { findNextUnreadIndex } from '../next-unread-email'; + +// #514: `n` opens the next unread message below the current one. + +const mail = (id: string, seen: boolean): { id: string; keywords: Record } => + ({ id, keywords: seen ? { $seen: true } : {} }); + +describe('findNextUnreadIndex', () => { + const list = [mail('a', false), mail('b', true), mail('c', true), mail('d', false), mail('e', true)]; + + it('finds the first unread message after the current one', () => { + expect(findNextUnreadIndex(list, 'b')).toBe(3); + }); + + it('skips the current message even when it is unread', () => { + expect(findNextUnreadIndex(list, 'a')).toBe(3); + }); + + it('starts at the top when nothing is open', () => { + expect(findNextUnreadIndex(list, undefined)).toBe(0); + expect(findNextUnreadIndex(list, null)).toBe(0); + }); + + it('starts at the top when the open message is not in the list', () => { + expect(findNextUnreadIndex(list, 'gone')).toBe(0); + }); + + it('does not wrap around to unread mail above', () => { + expect(findNextUnreadIndex(list, 'd')).toBe(-1); + expect(findNextUnreadIndex(list, 'e')).toBe(-1); + }); + + it('treats a message without keywords as unread', () => { + const rows = [mail('a', true), { id: 'b' } as { id: string; keywords: Record }]; + expect(findNextUnreadIndex(rows, 'a')).toBe(1); + }); + + it('returns -1 for an empty or all-read list', () => { + expect(findNextUnreadIndex([], undefined)).toBe(-1); + expect(findNextUnreadIndex([mail('a', true), mail('b', true)], undefined)).toBe(-1); + }); +}); diff --git a/lib/next-unread-email.ts b/lib/next-unread-email.ts new file mode 100644 index 000000000..58bbe268d --- /dev/null +++ b/lib/next-unread-email.ts @@ -0,0 +1,19 @@ +import type { Email } from "@/lib/jmap/types"; + +/** + * Index of the first unread message after `currentId` in the loaded list, or + * -1 when there is none below it. With nothing open (or the open message no + * longer in the list) the search starts at the top. It does not wrap around: + * unread mail above the current one is left for the user to scroll back to. + * (#514) + */ +export function findNextUnreadIndex( + emails: readonly Pick[], + currentId: string | null | undefined, +): number { + const currentIndex = currentId ? emails.findIndex((e) => e.id === currentId) : -1; + for (let i = currentIndex + 1; i < emails.length; i++) { + if (!emails[i].keywords?.$seen) return i; + } + return -1; +} diff --git a/locales/ar/common.json b/locales/ar/common.json index 1789c06be..52e7c4ae3 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -2615,7 +2615,9 @@ "previous_email": "الرسالة السابقة", "open_email": "فتح الرسالة", "close_email": "إغلاق / إلغاء التحديد", - "go_to_folder": "الانتقال إلى المجلد" + "go_to_folder": "الانتقال إلى المجلد", + "next_unread": "الرسالة غير المقروءة التالية", + "no_more_unread": "لا توجد رسائل غير مقروءة أسفل القائمة" }, "actions": { "reply": "رد", diff --git a/locales/ca/common.json b/locales/ca/common.json index 646530d1c..b66f0d338 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Correu anterior", "open_email": "Obre el correu", "close_email": "Tanca / Desselecciona", - "go_to_folder": "Vés a la carpeta" + "go_to_folder": "Vés a la carpeta", + "next_unread": "Següent correu no llegit", + "no_more_unread": "No hi ha més correus no llegits més avall a la llista" }, "actions": { "reply": "Respon", diff --git a/locales/cs/common.json b/locales/cs/common.json index 387d381f9..d9f14d837 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Předchozí zpráva", "open_email": "Otevřít zprávu", "close_email": "Zavřít / Zrušit výběr", - "go_to_folder": "Přejít do složky" + "go_to_folder": "Přejít do složky", + "next_unread": "Další nepřečtený e-mail", + "no_more_unread": "Níže v seznamu už nejsou žádné nepřečtené e-maily" }, "actions": { "reply": "Odpovědět", diff --git a/locales/da/common.json b/locales/da/common.json index 24adbcaa3..c406e216e 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Forrige e-mail", "open_email": "Åbn e-mail", "close_email": "Luk / Fravaælg", - "go_to_folder": "Gå til mappe" + "go_to_folder": "Gå til mappe", + "next_unread": "Næste ulæste e-mail", + "no_more_unread": "Der er ikke flere ulæste e-mails længere nede på listen" }, "actions": { "reply": "Svar", diff --git a/locales/de/common.json b/locales/de/common.json index 3832be9df..df865c10d 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Vorherige E-Mail", "open_email": "E-Mail öffnen", "close_email": "Schließen / Auswahl aufheben", - "go_to_folder": "Zum Ordner springen" + "go_to_folder": "Zum Ordner springen", + "next_unread": "Nächste ungelesene E-Mail", + "no_more_unread": "Weiter unten in der Liste gibt es keine ungelesenen E-Mails" }, "actions": { "reply": "Antworten", diff --git a/locales/en/common.json b/locales/en/common.json index 4b3d3e2ce..89021953b 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Previous email", "open_email": "Open email", "close_email": "Close / Deselect", - "go_to_folder": "Go to folder" + "go_to_folder": "Go to folder", + "next_unread": "Next unread email", + "no_more_unread": "No unread email further down the list" }, "actions": { "reply": "Reply", diff --git a/locales/es/common.json b/locales/es/common.json index ebc26351b..451be606f 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Correo anterior", "open_email": "Abrir correo", "close_email": "Cerrar / Deseleccionar", - "go_to_folder": "Ir a la carpeta" + "go_to_folder": "Ir a la carpeta", + "next_unread": "Siguiente correo no leído", + "no_more_unread": "No hay más correos no leídos más abajo en la lista" }, "actions": { "reply": "Responder", diff --git a/locales/fa/common.json b/locales/fa/common.json index 40782c9f8..2803e2e97 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -2615,7 +2615,9 @@ "previous_email": "ایمیل قبلی", "open_email": "باز کردن ایمیل", "close_email": "بستن / لغو انتخاب", - "go_to_folder": "رفتن به پوشه" + "go_to_folder": "رفتن به پوشه", + "next_unread": "ایمیل خوانده‌نشده بعدی", + "no_more_unread": "ایمیل خوانده‌نشده‌ای پایین‌تر در فهرست نیست" }, "actions": { "reply": "پاسخ", diff --git a/locales/fr/common.json b/locales/fr/common.json index 4c9e05c1e..ae4ea1299 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Email précédent", "open_email": "Ouvrir l'email", "close_email": "Fermer / Désélectionner", - "go_to_folder": "Aller au dossier" + "go_to_folder": "Aller au dossier", + "next_unread": "E-mail non lu suivant", + "no_more_unread": "Aucun e-mail non lu plus bas dans la liste" }, "actions": { "reply": "Répondre", diff --git a/locales/he/common.json b/locales/he/common.json index bd8fd8b74..55f9c4739 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -2533,7 +2533,9 @@ "previous_email": "אימייל קודם", "open_email": "פתח אימייל", "close_email": "סגור / בטל בחירה", - "go_to_folder": "מעבר לתיקייה" + "go_to_folder": "מעבר לתיקייה", + "next_unread": "ההודעה הבאה שלא נקראה", + "no_more_unread": "אין הודעות שלא נקראו בהמשך הרשימה" }, "actions": { "reply": "השב", diff --git a/locales/hu/common.json b/locales/hu/common.json index 8fd6531a1..5622e3d09 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Előző e-mail", "open_email": "E-mail megnyitása", "close_email": "Bezárás / Kijelölés megszüntetése", - "go_to_folder": "Ugrás mappára" + "go_to_folder": "Ugrás mappára", + "next_unread": "Következő olvasatlan e-mail", + "no_more_unread": "Lejjebb a listában nincs több olvasatlan e-mail" }, "actions": { "reply": "Válasz", diff --git a/locales/it/common.json b/locales/it/common.json index 794d42c7d..0bfa4e586 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Messaggio precedente", "open_email": "Apri messaggio", "close_email": "Chiudi / Deseleziona", - "go_to_folder": "Vai alla cartella" + "go_to_folder": "Vai alla cartella", + "next_unread": "Prossima email non letta", + "no_more_unread": "Nessuna email non letta più in basso nell'elenco" }, "actions": { "reply": "Rispondi", diff --git a/locales/ja/common.json b/locales/ja/common.json index ec1791639..5285f6b64 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -2615,7 +2615,9 @@ "previous_email": "前のメール", "open_email": "メールを開く", "close_email": "閉じる / 選択解除", - "go_to_folder": "フォルダーに移動" + "go_to_folder": "フォルダーに移動", + "next_unread": "次の未読メール", + "no_more_unread": "リストのこれより下に未読メールはありません" }, "actions": { "reply": "返信", diff --git a/locales/ko/common.json b/locales/ko/common.json index 8db0c3a2c..4946d2e3f 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -2615,7 +2615,9 @@ "previous_email": "이전 메일", "open_email": "메일 열기", "close_email": "닫기 / 선택 해제", - "go_to_folder": "폴더로 이동" + "go_to_folder": "폴더로 이동", + "next_unread": "다음 읽지 않은 메일", + "no_more_unread": "목록 아래쪽에 읽지 않은 메일이 더 없습니다" }, "actions": { "reply": "답장", diff --git a/locales/lv/common.json b/locales/lv/common.json index 7680004f8..dcd00a33d 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Iepriekšējā vēstule", "open_email": "Atvērt vēstuli", "close_email": "Aizvērt / Noņemt atlasi", - "go_to_folder": "Pāriet uz mapi" + "go_to_folder": "Pāriet uz mapi", + "next_unread": "Nākamais nelasītais e-pasts", + "no_more_unread": "Sarakstā zemāk vairs nav nelasītu e-pastu" }, "actions": { "reply": "Atbildēt", diff --git a/locales/mn/common.json b/locales/mn/common.json index e6bdf1ce7..f90defdcc 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Өмнөх имэйл", "open_email": "Имэйл нээх", "close_email": "Хаах / Сонголтыг цуцлах", - "go_to_folder": "Хавтас руу очих" + "go_to_folder": "Хавтас руу очих", + "next_unread": "Дараагийн уншаагүй имэйл", + "no_more_unread": "Жагсаалтын доод хэсэгт уншаагүй имэйл алга" }, "actions": { "reply": "Хариулах", diff --git a/locales/nb/common.json b/locales/nb/common.json index 65b65dd22..aaa8816da 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Forrige e-post", "open_email": "Åpne e-post", "close_email": "Lukk / fjern valget", - "go_to_folder": "Gå til mappe" + "go_to_folder": "Gå til mappe", + "next_unread": "Neste uleste e-post", + "no_more_unread": "Det er ingen flere uleste e-poster lenger ned i listen" }, "actions": { "reply": "Svar", diff --git a/locales/nl/common.json b/locales/nl/common.json index 653b92374..bef09aa9b 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Vorige e-mail", "open_email": "E-mail openen", "close_email": "Sluiten / Deselecteren", - "go_to_folder": "Ga naar map" + "go_to_folder": "Ga naar map", + "next_unread": "Volgende ongelezen e-mail", + "no_more_unread": "Verderop in de lijst staan geen ongelezen e-mails meer" }, "actions": { "reply": "Beantwoorden", diff --git a/locales/pl/common.json b/locales/pl/common.json index 93918746b..a88c5b2b6 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Poprzednia wiadomość", "open_email": "Otwórz wiadomość", "close_email": "Zamknij / Odznacz", - "go_to_folder": "Przejdź do folderu" + "go_to_folder": "Przejdź do folderu", + "next_unread": "Następna nieprzeczytana wiadomość", + "no_more_unread": "Niżej na liście nie ma już nieprzeczytanych wiadomości" }, "actions": { "reply": "Odpowiedz", diff --git a/locales/pt/common.json b/locales/pt/common.json index da81c662a..844ed196d 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -2615,7 +2615,9 @@ "previous_email": "E-mail anterior", "open_email": "Abrir e-mail", "close_email": "Fechar / Desselecionar", - "go_to_folder": "Ir para a pasta" + "go_to_folder": "Ir para a pasta", + "next_unread": "Próximo e-mail não lido", + "no_more_unread": "Não há mais e-mails não lidos mais abaixo na lista" }, "actions": { "reply": "Responder", diff --git a/locales/ro/common.json b/locales/ro/common.json index d8ef29c9e..0d346fba7 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -2615,7 +2615,9 @@ "previous_email": "E-mailul anterior", "open_email": "Deschide e-mailul", "close_email": "Închide / Deselectează", - "go_to_folder": "Mergi la dosar" + "go_to_folder": "Mergi la dosar", + "next_unread": "Următorul e-mail necitit", + "no_more_unread": "Nu mai sunt e-mailuri necitite mai jos în listă" }, "actions": { "reply": "Răspunde", diff --git a/locales/ru/common.json b/locales/ru/common.json index 49f609c7a..9c5b9a018 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Предыдущее письмо", "open_email": "Открыть письмо", "close_email": "Закрыть / Отменить выбор", - "go_to_folder": "Перейти к папке" + "go_to_folder": "Перейти к папке", + "next_unread": "Следующее непрочитанное письмо", + "no_more_unread": "Ниже в списке больше нет непрочитанных писем" }, "actions": { "reply": "Ответить", diff --git a/locales/sk/common.json b/locales/sk/common.json index 967d8ccce..7cc66a8f3 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Predchádzajúci e-mail", "open_email": "Otvoriť e-mail", "close_email": "Zavrieť / Zrušiť výber", - "go_to_folder": "Prejsť do priečinka" + "go_to_folder": "Prejsť do priečinka", + "next_unread": "Ďalší neprečítaný e-mail", + "no_more_unread": "Nižšie v zozname už nie sú žiadne neprečítané e-maily" }, "actions": { "reply": "Odpovedať", diff --git a/locales/tr/common.json b/locales/tr/common.json index e360c1795..e68ef8d08 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Önceki e-posta", "open_email": "E-postayı aç", "close_email": "Kapat / Seçimi kaldır", - "go_to_folder": "Klasöre git" + "go_to_folder": "Klasöre git", + "next_unread": "Sonraki okunmamış e-posta", + "no_more_unread": "Listenin aşağısında okunmamış e-posta yok" }, "actions": { "reply": "Yanıtla", diff --git a/locales/uk/common.json b/locales/uk/common.json index 70ac98e37..d83acfc7e 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -2615,7 +2615,9 @@ "previous_email": "Попередній електронний лист", "open_email": "Відкрийте електронну пошту", "close_email": "Закрити / Скасувати вибір", - "go_to_folder": "Перейти до теки" + "go_to_folder": "Перейти до теки", + "next_unread": "Наступний непрочитаний лист", + "no_more_unread": "Нижче в списку більше немає непрочитаних листів" }, "actions": { "reply": "Відповісти", diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 004f12c2d..5e5e562bd 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -2615,7 +2615,9 @@ "previous_email": "上一封郵件", "open_email": "開啟郵件", "close_email": "關閉/取消選取", - "go_to_folder": "跳至資料夾" + "go_to_folder": "跳至資料夾", + "next_unread": "下一封未讀郵件", + "no_more_unread": "清單下方沒有更多未讀郵件" }, "actions": { "reply": "回覆", diff --git a/locales/zh/common.json b/locales/zh/common.json index 1b079b2f4..deb193881 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -2615,7 +2615,9 @@ "previous_email": "上一封邮件", "open_email": "打开邮件", "close_email": "关闭/取消选择", - "go_to_folder": "跳转到文件夹" + "go_to_folder": "跳转到文件夹", + "next_unread": "下一封未读邮件", + "no_more_unread": "列表下方没有更多未读邮件" }, "actions": { "reply": "回复",