diff --git a/components/calendar/calendar-app.tsx b/components/calendar/calendar-app.tsx
index 291c715b6..08d229ff5 100644
--- a/components/calendar/calendar-app.tsx
+++ b/components/calendar/calendar-app.tsx
@@ -125,6 +125,8 @@ import { calendarShortcutAction } from "@/lib/calendar-shortcuts";
import { isAppShortcutBlocked, isArrowKeyWidgetTarget } from "@/lib/keyboard";
import { isContextMenuOpen } from "@/components/ui/context-menu";
import { useRegisterCommands } from "@/lib/command-registry";
+import { useAppCommands } from "@/lib/app-commands";
+import { useDesktopCommands } from "@/lib/desktop/commands";
import { useConfig } from "@/hooks/use-config";
import { CalendarPrint, type CalendarPrintContent } from "@/components/calendar/calendar-print";
import { usePrintListener, usePrinting } from "@/components/print/print-root";
@@ -1666,6 +1668,28 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps =
{ mode: "agenda", key: "a" },
{ mode: "tasks", key: "k" },
];
+ // File > New event and New task in the desktop app's menus, from any app.
+ useAppCommands({
+ "new-event": () => openCreateModal(),
+ "new-task": () => { if (enableCalendarTasks) openCreateTaskModal(); },
+ }, true);
+ // Its View menu's Day, Week, Month and Agenda, and Edit > Search events.
+ const viewFromMenu = (mode: CalendarViewMode) => () => { setMobileReturnToMonth(false); leaveSearch(); setViewMode(mode); };
+ useDesktopCommands({
+ "view.calendar.day": viewFromMenu("day"),
+ "view.calendar.week": viewFromMenu("week"),
+ "view.calendar.month": viewFromMenu("month"),
+ "view.calendar.agenda": viewFromMenu("agenda"),
+ "edit.search": openSearch,
+ }, {
+ "view.calendar.day": { enabled: true, checked: normalizedViewMode === "day" },
+ "view.calendar.week": { enabled: true, checked: normalizedViewMode === "week" },
+ "view.calendar.month": { enabled: true, checked: normalizedViewMode === "month" },
+ "view.calendar.agenda": { enabled: true, checked: normalizedViewMode === "agenda" },
+ "edit.search": normalizedViewMode !== "tasks",
+ "file.newTask": enableCalendarTasks,
+ });
+
useRegisterCommands(tSidebar("calendar"), [
{ id: "new-event", label: t("events.new_event"), icon: Plus, shortcut: "n", run: () => openCreateModal() },
{ id: "new-task", label: t("events.new_task"), icon: ListTodo, run: openCreateTaskModal, when: () => enableCalendarTasks },
diff --git a/components/contacts/contacts-app.tsx b/components/contacts/contacts-app.tsx
index 83e2619d7..3a668a710 100644
--- a/components/contacts/contacts-app.tsx
+++ b/components/contacts/contacts-app.tsx
@@ -60,6 +60,7 @@ import { useDocumentTitle } from "@/hooks/use-document-title";
import { ContactsSkeleton, ContactsSkeletonBody } from "@/components/boot-skeleton/contacts-skeleton";
import { useSkeletonLabels } from "@/components/boot-skeleton/use-skeleton-labels";
import { useRegisterCommands } from "@/lib/command-registry";
+import { useAppCommands } from "@/lib/app-commands";
import { useCloseOnBack } from "@/hooks/use-close-on-back";
import { useLeaveGuard } from "@/lib/leave-guard";
import { useDeleteCategory } from "@/hooks/use-delete-category";
@@ -968,6 +969,9 @@ function ContactsAppContent({ linkSegments: routeSegments }: ContactsAppProps =
}
};
+ // File > New contact in the desktop app's menus, from any app.
+ useAppCommands({ "new-contact": () => guard(handleCreateNew)() }, contactsEnabled);
+
// The command palette's Contacts group (Ctrl/⌘+K).
useRegisterCommands(tSidebar("contacts"), [
{ id: "new-contact", label: t("create_new"), icon: UserPlus, run: () => guard(handleCreateNew)() },
diff --git a/components/desktop/__tests__/desktop-host.test.tsx b/components/desktop/__tests__/desktop-host.test.tsx
index 2d757cbf2..9f3bae5ba 100644
--- a/components/desktop/__tests__/desktop-host.test.tsx
+++ b/components/desktop/__tests__/desktop-host.test.tsx
@@ -12,7 +12,8 @@ import { useOfflineStore } from '@/lib/offline/state';
import { offerReturnedComposer, takeReturnedComposer } from '@/lib/popout/return-to-main';
const push = vi.fn();
-vi.mock('@/i18n/navigation', () => ({ useRouter: () => ({ push }) }));
+const route = vi.hoisted(() => ({ pathname: '/' }));
+vi.mock('@/i18n/navigation', () => ({ useRouter: () => ({ push }), usePathname: () => route.pathname }));
// The catalog on screen: its language, and text that says which one it came from.
const intl = vi.hoisted(() => ({ locale: 'en', prefix: '', messages: {} as object }));
@@ -76,6 +77,7 @@ function fakeBridge() {
const original = useAuthStore.getState();
beforeEach(() => {
+ route.pathname = '/';
Object.assign(intl, { locale: 'en', prefix: '', messages: {} });
push.mockClear();
resolveExternalLink.mockClear();
@@ -106,6 +108,33 @@ describe('DesktopHost', () => {
expect(() => render()).not.toThrow();
});
+ it('File > New event, task and contact open that app with its editor; Settings opens in this window', async () => {
+ const { peekParkedAppCommand } = await import('@/lib/app-commands');
+ const shell = fakeBridge();
+ render();
+ shell.run('file.newEvent');
+ expect(push).toHaveBeenLastCalledWith('/calendar');
+ expect(peekParkedAppCommand()).toBe('new-event');
+ shell.run('file.newTask');
+ expect(push).toHaveBeenLastCalledWith('/calendar');
+ expect(peekParkedAppCommand()).toBe('new-task');
+ shell.run('file.newContact');
+ expect(push).toHaveBeenLastCalledWith('/contacts');
+ expect(peekParkedAppCommand()).toBe('new-contact');
+ shell.run('file.settings');
+ expect(push).toHaveBeenLastCalledWith('/settings');
+ });
+
+ it('tells the menus which part of the app is on screen, and offers the New lines once signed in', async () => {
+ route.pathname = '/calendar';
+ const shell = fakeBridge();
+ render();
+ await waitFor(() => expect(shell.setState).toHaveBeenCalled());
+ const state = shell.setState.mock.lastCall![0];
+ expect(state).toMatchObject({ 'section.calendar': true, 'file.newEvent': true, 'file.newContact': true, 'file.settings': true, 'file.import': true });
+ expect(state['section.mail']).toBeUndefined();
+ });
+
it('runs a command from what\'s on the page first', () => {
const shell = fakeBridge();
render();
diff --git a/components/desktop/desktop-host.tsx b/components/desktop/desktop-host.tsx
index b80396b80..ef58df992 100644
--- a/components/desktop/desktop-host.tsx
+++ b/components/desktop/desktop-host.tsx
@@ -3,7 +3,7 @@
import { useEffect, useRef } from "react";
import { useLocale, useMessages, useTranslations } from "next-intl";
import { useStrictestDevices } from "@/hooks/use-device-policy";
-import { useRouter } from "@/i18n/navigation";
+import { usePathname, useRouter } from "@/i18n/navigation";
import { openKeyboardShortcuts, parkAppCommand, requestAppCommand, type AppCommand } from "@/lib/app-commands";
import { onComposerOpened, composerIsOpen } from "@/lib/compose-flush-registry";
import { getDesktopBridge } from "@/lib/desktop/bridge";
@@ -26,6 +26,14 @@ import { useLocaleStore } from "@/stores/locale-store";
import { useSettingsStore } from "@/stores/settings-store";
import { useThemeStore } from "@/stores/theme-store";
import { toast } from "@/stores/toast-store";
+import { openMailImport } from "@/stores/mail-transfer-store";
+
+/** The part of the app a path shows, for the menus: Calendar has its own View lines, and no Message menu. */
+export function desktopSectionOf(pathname: string): "mail" | "calendar" | "contacts" | "files" | "settings" {
+ const first = pathname.split("/").filter(Boolean)[0] ?? "";
+ if (first === "calendar" || first === "contacts" || first === "files" || first === "settings") return first;
+ return "mail";
+}
/**
* The webmail's side of what every desktop window needs: runs menu commands nothing
@@ -35,6 +43,7 @@ import { toast } from "@/stores/toast-store";
*/
export function DesktopHost() {
const router = useRouter();
+ const pathname = usePathname();
const routerRef = useRef(router);
routerRef.current = router;
const t = useTranslations();
@@ -68,6 +77,25 @@ export function DesktopHost() {
else toMail("compose");
},
"edit.search": () => toMail("search"),
+ // The other apps' New items, run once that app is on screen.
+ "file.newEvent": () => {
+ if (requestAppCommand("new-event")) return;
+ parkAppCommand("new-event");
+ routerRef.current.push("/calendar");
+ },
+ "file.newTask": () => {
+ if (requestAppCommand("new-task")) return;
+ parkAppCommand("new-task");
+ routerRef.current.push("/calendar");
+ },
+ "file.newContact": () => {
+ if (requestAppCommand("new-contact")) return;
+ parkAppCommand("new-contact");
+ routerRef.current.push("/contacts");
+ },
+ "file.import": () => openMailImport(),
+ // One way to Settings: in the main window, as the rail's button opens them.
+ "file.settings": () => routerRef.current.push("/settings"),
"go.inbox": () => routerRef.current.push("/"),
"go.mail": () => routerRef.current.push("/"),
"go.calendar": () => routerRef.current.push("/calendar"),
@@ -101,6 +129,7 @@ export function DesktopHost() {
const canPrint = canPrintAnywhere();
const signedIn = isAuthenticated;
const popout = isPopoutWindow();
+ const section = desktopSectionOf(pathname ?? "/");
setDesktopBaseState({
"link.check": true,
"shortcuts.singleKeys": singleKeys,
@@ -108,6 +137,12 @@ export function DesktopHost() {
"file.print": canPrint,
"file.savePdf": canPrint,
"file.newMessage": signedIn,
+ "file.newEvent": signedIn,
+ "file.newTask": signedIn,
+ "file.newContact": signedIn,
+ "file.import": signedIn && !popout,
+ "file.settings": signedIn,
+ [`section.${section}`]: true,
"edit.search": signedIn && !popout,
"go.inbox": signedIn,
"go.mail": signedIn,
@@ -116,7 +151,7 @@ export function DesktopHost() {
"go.files": signedIn,
"help.shortcuts": true,
});
- }, [isAuthenticated, singleKeys, strictest]);
+ }, [isAuthenticated, singleKeys, strictest, pathname]);
// Menus, the tray and spellcheck follow the interface: the language its text is in now,
// with that text. The stored choice isn't it: on the first render the catalog can still
diff --git a/components/email/email-composer.tsx b/components/email/email-composer.tsx
index 0826f1069..5a002e077 100644
--- a/components/email/email-composer.tsx
+++ b/components/email/email-composer.tsx
@@ -175,6 +175,7 @@ import { usePolicyStore } from "@/stores/policy-store";
import { isAppLockedNow } from "@/lib/app-lock/state";
import { registerComposeFlush, registerComposeSnapshot } from "@/lib/compose-flush-registry";
import { getDesktopBridge } from "@/lib/desktop/bridge";
+import { useDesktopCommands } from "@/lib/desktop/commands";
import { noteComposerLanguage } from "@/lib/desktop/languages";
import { noteOpenDraft } from "@/lib/desktop/window-restore";
import type { RescuedDraft } from "@/lib/popout/channel";
@@ -4694,6 +4695,18 @@ export function EmailComposer({
}
};
+ // The desktop app's Message menu while this composer is open: the same handlers as
+ // its buttons and keys (lib/desktop/commands.ts). Nothing in a browser.
+ useDesktopCommands({
+ 'compose.send': () => { if (!isSavingAndClosing) void handleSend(); },
+ 'compose.saveDraft': () => { void saveDraft({ announce: true }); },
+ 'compose.attach': () => fileInputRef.current?.click(),
+ }, {
+ 'compose.send': !!canSend && !isSending && !isSavingAndClosing,
+ 'compose.saveDraft': !isSavingAndClosing,
+ 'compose.attach': !isSavingAndClosing,
+ });
+
return (
) =>
menuGoCalendar: t('sidebar.calendar'),
menuGoContacts: t('sidebar.contacts'),
menuGoFiles: t('desktopUi.menu.files'),
+ menuNewEvent: t('calendar.events.new_event'),
+ menuNewTask: t('calendar.events.new_task'),
+ menuNewContact: t('contacts.create_new'),
+ menuImport: t('mail_import.menu_import'),
+ menuSearchEvents: t('calendar.search.title'),
+ menuDay: t('calendar.views.day'),
+ menuWeek: t('calendar.views.week'),
+ menuMonth: t('calendar.views.month'),
+ menuAgenda: t('calendar.views.agenda'),
+ menuSend: t('email_composer.send'),
+ menuSaveDraft: t('desktopMenus.save_draft'),
+ menuAttach: t('desktopMenus.attach_files'),
menuGoBack: t('desktopUi.menu.back'),
menuGoForward: t('desktopUi.menu.forward'),
menuMinimize: t('desktopUi.menu.minimize'),
diff --git a/locales/ar/common.json b/locales/ar/common.json
index 326794bbe..5666071ad 100644
--- a/locales/ar/common.json
+++ b/locales/ar/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "إنهاء Bulwark",
"tray_exit": "الخروج من Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "حفظ المسودة",
+ "attach_files": "إرفاق ملفات…",
+ "shortcut_apps": "الانتقال إلى البريد أو التقويم أو جهات الاتصال أو الملفات"
+ },
"appLock": {
"section_title": "قفل التطبيق",
"section_desc": "يطلب رمز PIN بعد فترة دون استخدام، في كل نافذة وعلامة تبويب. أثناء القفل يبقى البريد على هذا الجهاز مشفرًا ولا يملك التطبيق مفتاحه.",
diff --git a/locales/ca/common.json b/locales/ca/common.json
index 6017b3260..dcba6799d 100644
--- a/locales/ca/common.json
+++ b/locales/ca/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Surt de Bulwark",
"tray_exit": "Surt de Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Desa l'esborrany",
+ "attach_files": "Adjunta fitxers…",
+ "shortcut_apps": "Ves a Correu, Calendari, Contactes o Fitxers"
+ },
"appLock": {
"section_title": "Bloqueig de l'app",
"section_desc": "Demana un PIN després d'un temps sense ús, a cada finestra i pestanya. Mentre està bloquejada, el correu d'aquest dispositiu continua xifrat i l'app no en té la clau.",
diff --git a/locales/cs/common.json b/locales/cs/common.json
index 61859734f..541387d19 100644
--- a/locales/cs/common.json
+++ b/locales/cs/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Ukončit Bulwark",
"tray_exit": "Ukončit Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Uložit koncept",
+ "attach_files": "Přiložit soubory…",
+ "shortcut_apps": "Přejít na Poštu, Kalendář, Kontakty nebo Soubory"
+ },
"appLock": {
"section_title": "Zámek aplikace",
"section_desc": "Po čase bez použití žádá o PIN, v každém okně a na každé kartě. Dokud je zamčeno, zůstává pošta na tomto zařízení zašifrovaná a aplikace k ní nemá klíč.",
diff --git a/locales/da/common.json b/locales/da/common.json
index 9968f27f7..72abbd1ae 100644
--- a/locales/da/common.json
+++ b/locales/da/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Afslut Bulwark",
"tray_exit": "Afslut Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Gem kladde",
+ "attach_files": "Vedhæft filer…",
+ "shortcut_apps": "Gå til Mail, Kalender, Kontakter eller Filer"
+ },
"appLock": {
"section_title": "Applås",
"section_desc": "Beder om en pinkode efter en tid uden brug, i alle vinduer og faner. Mens den er låst, forbliver mail på denne enhed krypteret, og appen har ingen nøgle til den.",
diff --git a/locales/de/common.json b/locales/de/common.json
index 50b8325ec..9b25379a4 100644
--- a/locales/de/common.json
+++ b/locales/de/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Bulwark beenden",
"tray_exit": "Bulwark beenden"
},
+ "desktopMenus": {
+ "save_draft": "Entwurf speichern",
+ "attach_files": "Dateien anhängen…",
+ "shortcut_apps": "Zu E-Mail, Kalender, Kontakten oder Dateien wechseln"
+ },
"appLock": {
"section_title": "App-Sperre",
"section_desc": "Fragt nach einer Zeit ohne Nutzung in jedem Fenster und Tab nach einer PIN. Solange gesperrt ist, bleiben E-Mails auf diesem Gerät verschlüsselt und die App hat keinen Schlüssel dafür.",
diff --git a/locales/en/common.json b/locales/en/common.json
index 504fb4515..41f9c6360 100644
--- a/locales/en/common.json
+++ b/locales/en/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Quit Bulwark",
"tray_exit": "Exit Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Save draft",
+ "attach_files": "Attach files…",
+ "shortcut_apps": "Go to Mail, Calendar, Contacts or Files"
+ },
"appLock": {
"section_title": "App lock",
"section_desc": "Asks for a PIN after a time without use, in every window and tab. While it's locked, mail on this device stays encrypted and the app has no key for it.",
diff --git a/locales/es/common.json b/locales/es/common.json
index 842d458fd..3a8bfa917 100644
--- a/locales/es/common.json
+++ b/locales/es/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Salir de Bulwark",
"tray_exit": "Salir de Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Guardar borrador",
+ "attach_files": "Adjuntar archivos…",
+ "shortcut_apps": "Ir a Correo, Calendario, Contactos o Archivos"
+ },
"appLock": {
"section_title": "Bloqueo de la app",
"section_desc": "Pide un PIN tras un tiempo sin uso, en cada ventana y pestaña. Mientras está bloqueada, el correo de este dispositivo sigue cifrado y la app no tiene la clave.",
diff --git a/locales/et/common.json b/locales/et/common.json
index e49454d86..58ea19008 100644
--- a/locales/et/common.json
+++ b/locales/et/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Sulge Bulwark",
"tray_exit": "Välju Bulwarkist"
},
+ "desktopMenus": {
+ "save_draft": "Salvesta mustand",
+ "attach_files": "Lisa failid…",
+ "shortcut_apps": "Mine e-posti, kalendrisse, kontaktidesse või failidesse"
+ },
"appLock": {
"section_title": "Rakenduse lukk",
"section_desc": "Küsib pärast kasutamata aega PIN-koodi, igas aknas ja vahekaardil. Lukus olles jääb selle seadme e-post krüpteerituks ja rakendusel pole selle võtit.",
diff --git a/locales/fa/common.json b/locales/fa/common.json
index 7a9fe6a96..9a455a1f0 100644
--- a/locales/fa/common.json
+++ b/locales/fa/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "بستن Bulwark",
"tray_exit": "خروج از Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "ذخیره پیشنویس",
+ "attach_files": "پیوست فایلها…",
+ "shortcut_apps": "رفتن به ایمیل، تقویم، مخاطبان یا فایلها"
+ },
"appLock": {
"section_title": "قفل برنامه",
"section_desc": "پس از مدتی بیاستفاده، در هر پنجره و زبانه پین میخواهد. تا وقتی قفل است، ایمیلهای این دستگاه رمزگذاریشده میمانند و برنامه کلید آنها را ندارد.",
diff --git a/locales/fr/common.json b/locales/fr/common.json
index 005e58256..5c0cda7a0 100644
--- a/locales/fr/common.json
+++ b/locales/fr/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Quitter Bulwark",
"tray_exit": "Quitter Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Enregistrer le brouillon",
+ "attach_files": "Joindre des fichiers…",
+ "shortcut_apps": "Aller à Mail, Calendrier, Contacts ou Fichiers"
+ },
"appLock": {
"section_title": "Verrouillage de l'app",
"section_desc": "Demande un code PIN après un temps sans utilisation, dans chaque fenêtre et onglet. Tant que l'app est verrouillée, les e-mails sur cet appareil restent chiffrés et l'app n'a pas de clé pour les lire.",
diff --git a/locales/he/common.json b/locales/he/common.json
index 96edffe4d..db4d35585 100644
--- a/locales/he/common.json
+++ b/locales/he/common.json
@@ -6843,6 +6843,11 @@
"tray_quit": "סגירת Bulwark",
"tray_exit": "יציאה מ-Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "שמירת טיוטה",
+ "attach_files": "צירוף קבצים…",
+ "shortcut_apps": "מעבר לדואר, ליומן, לאנשי הקשר או לקבצים"
+ },
"appLock": {
"section_title": "נעילת אפליקציה",
"section_desc": "מבקשת קוד PIN אחרי זמן ללא שימוש, בכל חלון וכרטיסייה. בזמן הנעילה הדואר במכשיר הזה נשאר מוצפן ולאפליקציה אין מפתח אליו.",
diff --git a/locales/hu/common.json b/locales/hu/common.json
index 8555a845c..435651051 100644
--- a/locales/hu/common.json
+++ b/locales/hu/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Kilépés a Bulwarkból",
"tray_exit": "Kilépés a Bulwarkból"
},
+ "desktopMenus": {
+ "save_draft": "Piszkozat mentése",
+ "attach_files": "Fájlok csatolása…",
+ "shortcut_apps": "Ugrás a Levelekre, a Naptárra, a Névjegyekre vagy a Fájlokra"
+ },
"appLock": {
"section_title": "Alkalmazászár",
"section_desc": "Használat nélküli idő után PIN-kódot kér, minden ablakban és lapon. Amíg zárolva van, az eszközön lévő levelek titkosítva maradnak, és az alkalmazásnak nincs hozzájuk kulcsa.",
diff --git a/locales/it/common.json b/locales/it/common.json
index c114d652f..73f08d1dd 100644
--- a/locales/it/common.json
+++ b/locales/it/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Esci da Bulwark",
"tray_exit": "Esci da Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Salva bozza",
+ "attach_files": "Allega file…",
+ "shortcut_apps": "Vai a Posta, Calendario, Contatti o File"
+ },
"appLock": {
"section_title": "Blocco dell'app",
"section_desc": "Chiede un PIN dopo un periodo senza utilizzo, in ogni finestra e scheda. Finché è bloccata, la posta su questo dispositivo resta cifrata e l'app non ha la chiave.",
diff --git a/locales/ja/common.json b/locales/ja/common.json
index 574aae063..fb6615bd6 100644
--- a/locales/ja/common.json
+++ b/locales/ja/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Bulwark を終了",
"tray_exit": "Bulwark を終了"
},
+ "desktopMenus": {
+ "save_draft": "下書きを保存",
+ "attach_files": "ファイルを添付…",
+ "shortcut_apps": "メール、カレンダー、連絡先、ファイルに移動"
+ },
"appLock": {
"section_title": "アプリのロック",
"section_desc": "一定時間使わないと、すべてのウィンドウとタブで PIN を求めます。ロック中、このデバイスのメールは暗号化されたままで、アプリには鍵がありません。",
diff --git a/locales/ko/common.json b/locales/ko/common.json
index 4ebc7fdd3..4fb4e425e 100644
--- a/locales/ko/common.json
+++ b/locales/ko/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Bulwark 종료",
"tray_exit": "Bulwark 종료"
},
+ "desktopMenus": {
+ "save_draft": "임시 저장",
+ "attach_files": "파일 첨부…",
+ "shortcut_apps": "메일, 캘린더, 연락처, 파일로 이동"
+ },
"appLock": {
"section_title": "앱 잠금",
"section_desc": "일정 시간 사용하지 않으면 모든 창과 탭에서 PIN을 요청합니다. 잠겨 있는 동안 이 기기의 메일은 암호화된 상태로 유지되며 앱에는 키가 없습니다.",
diff --git a/locales/lv/common.json b/locales/lv/common.json
index 78ad6d1c9..4cd9e16d8 100644
--- a/locales/lv/common.json
+++ b/locales/lv/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Aizvērt Bulwark",
"tray_exit": "Iziet no Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Saglabāt melnrakstu",
+ "attach_files": "Pievienot failus…",
+ "shortcut_apps": "Pāriet uz pastu, kalendāru, kontaktiem vai failiem"
+ },
"appLock": {
"section_title": "Lietotnes bloķēšana",
"section_desc": "Pēc laika bez lietošanas prasa PIN kodu katrā logā un cilnē. Kamēr lietotne ir bloķēta, e-pasts šajā ierīcē paliek šifrēts, un lietotnei nav tā atslēgas.",
diff --git a/locales/mn/common.json b/locales/mn/common.json
index 6789c3c66..6920128c3 100644
--- a/locales/mn/common.json
+++ b/locales/mn/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Bulwark-г хаах",
"tray_exit": "Bulwark-аас гарах"
},
+ "desktopMenus": {
+ "save_draft": "Ноорог хадгалах",
+ "attach_files": "Файл хавсаргах…",
+ "shortcut_apps": "Шуудан, Хуанли, Харилцагчид эсвэл Файлууд руу очих"
+ },
"appLock": {
"section_title": "Аппын түгжээ",
"section_desc": "Хэсэг хугацаанд ашиглаагүй бол цонх, таб бүрт ПИН код асууна. Түгжээтэй үед энэ төхөөрөмжийн имэйл шифрлэгдсэн хэвээр байх бөгөөд аппад түлхүүр нь байхгүй.",
diff --git a/locales/nb/common.json b/locales/nb/common.json
index 5d5c495d5..1936a347c 100644
--- a/locales/nb/common.json
+++ b/locales/nb/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Avslutt Bulwark",
"tray_exit": "Avslutt Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Lagre utkast",
+ "attach_files": "Legg ved filer…",
+ "shortcut_apps": "Gå til E-post, Kalender, Kontakter eller Filer"
+ },
"appLock": {
"section_title": "Applås",
"section_desc": "Ber om en PIN-kode etter en tid uten bruk, i alle vinduer og faner. Mens den er låst, forblir e-post på denne enheten kryptert, og appen har ingen nøkkel til den.",
diff --git a/locales/nl/common.json b/locales/nl/common.json
index 9c47cf53a..32a84a78b 100644
--- a/locales/nl/common.json
+++ b/locales/nl/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Bulwark afsluiten",
"tray_exit": "Bulwark afsluiten"
},
+ "desktopMenus": {
+ "save_draft": "Concept opslaan",
+ "attach_files": "Bestanden bijvoegen…",
+ "shortcut_apps": "Naar E-mail, Agenda, Contacten of Bestanden"
+ },
"appLock": {
"section_title": "App-vergrendeling",
"section_desc": "Vraagt na een tijd zonder gebruik om een pincode, in elk venster en tabblad. Zolang de app vergrendeld is, blijft de mail op dit apparaat versleuteld en heeft de app er geen sleutel voor.",
diff --git a/locales/pl/common.json b/locales/pl/common.json
index ba79e7ae2..083d90175 100644
--- a/locales/pl/common.json
+++ b/locales/pl/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Zakończ Bulwark",
"tray_exit": "Zakończ Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Zapisz wersję roboczą",
+ "attach_files": "Załącz pliki…",
+ "shortcut_apps": "Przejdź do Poczty, Kalendarza, Kontaktów lub Plików"
+ },
"appLock": {
"section_title": "Blokada aplikacji",
"section_desc": "Prosi o PIN po czasie bezczynności, w każdym oknie i na każdej karcie. Gdy aplikacja jest zablokowana, poczta na tym urządzeniu pozostaje zaszyfrowana, a aplikacja nie ma do niej klucza.",
diff --git a/locales/pt/common.json b/locales/pt/common.json
index 5fa535e59..615117a46 100644
--- a/locales/pt/common.json
+++ b/locales/pt/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Sair do Bulwark",
"tray_exit": "Sair do Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Salvar rascunho",
+ "attach_files": "Anexar arquivos…",
+ "shortcut_apps": "Ir para E-mail, Calendário, Contatos ou Arquivos"
+ },
"appLock": {
"section_title": "Bloqueio da app",
"section_desc": "Pede um PIN após um tempo sem uso, em todas as janelas e separadores. Enquanto está bloqueada, o correio neste dispositivo fica cifrado e a app não tem a chave.",
diff --git a/locales/ro/common.json b/locales/ro/common.json
index 44b8a11d6..912a85086 100644
--- a/locales/ro/common.json
+++ b/locales/ro/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Închide Bulwark",
"tray_exit": "Ieșire din Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Salvează ciorna",
+ "attach_files": "Atașează fișiere…",
+ "shortcut_apps": "Mergi la E-mail, Calendar, Contacte sau Fișiere"
+ },
"appLock": {
"section_title": "Blocarea aplicației",
"section_desc": "Cere un PIN după un timp fără utilizare, în fiecare fereastră și filă. Cât timp e blocată, e-mailurile de pe acest dispozitiv rămân criptate și aplicația nu are cheia lor.",
diff --git a/locales/ru/common.json b/locales/ru/common.json
index 7570abc97..1922e3429 100644
--- a/locales/ru/common.json
+++ b/locales/ru/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Закрыть Bulwark",
"tray_exit": "Выйти из Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Сохранить черновик",
+ "attach_files": "Прикрепить файлы…",
+ "shortcut_apps": "Перейти в Почту, Календарь, Контакты или Файлы"
+ },
"appLock": {
"section_title": "Блокировка приложения",
"section_desc": "Запрашивает PIN-код после периода бездействия, в каждом окне и вкладке. Пока приложение заблокировано, почта на этом устройстве остаётся зашифрованной, и у приложения нет ключа к ней.",
diff --git a/locales/sk/common.json b/locales/sk/common.json
index 9a7dc6511..a40036b62 100644
--- a/locales/sk/common.json
+++ b/locales/sk/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Ukončiť Bulwark",
"tray_exit": "Ukončiť Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Uložiť koncept",
+ "attach_files": "Priložiť súbory…",
+ "shortcut_apps": "Prejsť na Poštu, Kalendár, Kontakty alebo Súbory"
+ },
"appLock": {
"section_title": "Zámok aplikácie",
"section_desc": "Po čase bez používania žiada o PIN, v každom okne a na každej karte. Kým je zamknuté, pošta na tomto zariadení zostáva šifrovaná a aplikácia k nej nemá kľúč.",
diff --git a/locales/th/common.json b/locales/th/common.json
index dead36948..bc306d787 100644
--- a/locales/th/common.json
+++ b/locales/th/common.json
@@ -6850,6 +6850,11 @@
"tray_quit": "ปิด Bulwark",
"tray_exit": "ออกจาก Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "บันทึกฉบับร่าง",
+ "attach_files": "แนบไฟล์…",
+ "shortcut_apps": "ไปที่อีเมล ปฏิทิน รายชื่อติดต่อ หรือไฟล์"
+ },
"appLock": {
"section_title": "ล็อกแอป",
"section_desc": "ขอ PIN หลังจากไม่ได้ใช้งานระยะหนึ่ง ในทุกหน้าต่างและแท็บ ระหว่างล็อก อีเมลบนอุปกรณ์นี้ยังคงเข้ารหัสอยู่และแอปไม่มีกุญแจ",
diff --git a/locales/tr/common.json b/locales/tr/common.json
index 3cfb73f50..959fb493b 100644
--- a/locales/tr/common.json
+++ b/locales/tr/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Bulwark'tan çık",
"tray_exit": "Bulwark'tan çık"
},
+ "desktopMenus": {
+ "save_draft": "Taslağı kaydet",
+ "attach_files": "Dosya ekle…",
+ "shortcut_apps": "Posta, Takvim, Kişiler veya Dosyalar'a git"
+ },
"appLock": {
"section_title": "Uygulama kilidi",
"section_desc": "Bir süre kullanılmadığında her pencerede ve sekmede PIN ister. Kilitliyken bu cihazdaki e-postalar şifreli kalır ve uygulamada anahtarı olmaz.",
diff --git a/locales/uk/common.json b/locales/uk/common.json
index 678343366..6fc7ed0ba 100644
--- a/locales/uk/common.json
+++ b/locales/uk/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "Закрити Bulwark",
"tray_exit": "Вийти з Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Зберегти чернетку",
+ "attach_files": "Прикріпити файли…",
+ "shortcut_apps": "Перейти до Пошти, Календаря, Контактів або Файлів"
+ },
"appLock": {
"section_title": "Блокування застосунку",
"section_desc": "Запитує PIN-код після часу без використання, у кожному вікні й вкладці. Поки застосунок заблоковано, пошта на цьому пристрої залишається зашифрованою, і застосунок не має ключа до неї.",
diff --git a/locales/vi/common.json b/locales/vi/common.json
index 76524b705..269dc2984 100644
--- a/locales/vi/common.json
+++ b/locales/vi/common.json
@@ -6854,6 +6854,11 @@
"tray_quit": "Thoát Bulwark",
"tray_exit": "Thoát Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "Lưu bản nháp",
+ "attach_files": "Đính kèm tệp…",
+ "shortcut_apps": "Đi tới Thư, Lịch, Danh bạ hoặc Tệp"
+ },
"appLock": {
"section_title": "Khóa ứng dụng",
"section_desc": "Yêu cầu mã PIN sau một thời gian không dùng, trong mọi cửa sổ và thẻ. Khi đang khóa, thư trên thiết bị này vẫn được mã hóa và ứng dụng không có khóa của chúng.",
diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json
index e429a6be7..c19fa3e05 100644
--- a/locales/zh-TW/common.json
+++ b/locales/zh-TW/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "結束 Bulwark",
"tray_exit": "結束 Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "儲存草稿",
+ "attach_files": "附加檔案…",
+ "shortcut_apps": "前往郵件、行事曆、聯絡人或檔案"
+ },
"appLock": {
"section_title": "應用程式鎖定",
"section_desc": "一段時間未使用後,在每個視窗和分頁中要求輸入 PIN。鎖定期間,此裝置上的郵件保持加密,應用程式沒有金鑰。",
diff --git a/locales/zh/common.json b/locales/zh/common.json
index bc11b6877..fb29c985a 100644
--- a/locales/zh/common.json
+++ b/locales/zh/common.json
@@ -6842,6 +6842,11 @@
"tray_quit": "退出 Bulwark",
"tray_exit": "退出 Bulwark"
},
+ "desktopMenus": {
+ "save_draft": "保存草稿",
+ "attach_files": "附加文件…",
+ "shortcut_apps": "转到邮件、日历、联系人或文件"
+ },
"appLock": {
"section_title": "应用锁",
"section_desc": "一段时间未使用后,在每个窗口和标签页中要求输入 PIN。锁定期间,此设备上的邮件保持加密,应用没有密钥。",