mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
feat: put offline, privacy and about on settings pages of their own and widen the settings search
Offline sat in the middle of Backup & reset, privacy settings were spread
over Reading, Composing and Message list, and the version card took room
from the page list in the sidebar's footer. The search matched letters
inside words ("lock" found Time format and Send Checks) and knew no other
words for a setting ("offline", "autoreply", "logout" found nothing).
- Offline is a page in the top group: Keep mail on this device, what is
kept, Remove everything from this device, and the Outbox as its own
section. Old `/settings/backup/offline` links and the offline notice
land there.
- Privacy holds remote images and trusted senders, read receipts, sender
pictures from the web, the desktop app's usage info and crash reports,
and a link to the push relay on Notifications. Reading, Composing and
Message list keep a link row; old links to those sections land on
Privacy.
- About is the last page and holds the version card; the footer keeps the
Advanced switch.
- The search matches whole words and word starts (substrings only in
Chinese, Japanese and Thai), and each row can carry synonyms in the
user's language. A page matches by its name and keywords or when one of
its rows has every word. Account gets a Sign out row so "logout" leads
somewhere.
- The index test now also fails when a page draws a section the index
doesn't know, and a new test fails on two indexed rows with the same
name. Tests that rendered remote content and sender pictures on Reading
and Message list render Privacy instead, since that's where the rows are.
This commit is contained in:
@@ -208,7 +208,10 @@
|
||||
[data-boot-settings-tab="contacts"] [data-sk-settings-tab="contacts"],
|
||||
[data-boot-settings-tab="files"] [data-sk-settings-tab="files"],
|
||||
[data-boot-settings-tab="addons"] [data-sk-settings-tab="addons"],
|
||||
[data-boot-settings-tab="backup"] [data-sk-settings-tab="backup"]
|
||||
[data-boot-settings-tab="offline"] [data-sk-settings-tab="offline"],
|
||||
[data-boot-settings-tab="privacy"] [data-sk-settings-tab="privacy"],
|
||||
[data-boot-settings-tab="backup"] [data-sk-settings-tab="backup"],
|
||||
[data-boot-settings-tab="about"] [data-sk-settings-tab="about"]
|
||||
));
|
||||
|
||||
/* Settings pages the nav listed last time (bulwark-boot-hints). Without
|
||||
@@ -221,6 +224,7 @@
|
||||
@custom-variant boot-no-settings-contacts (&:where([data-boot-settings-tabs]:not([data-boot-settings-tabs~="contacts"]) *));
|
||||
@custom-variant boot-no-settings-files (&:where([data-boot-settings-tabs]:not([data-boot-settings-tabs~="files"]) *));
|
||||
@custom-variant boot-no-settings-addons (&:where([data-boot-settings-tabs]:not([data-boot-settings-tabs~="addons"]) *));
|
||||
@custom-variant boot-no-settings-offline (&:where([data-boot-settings-tabs]:not([data-boot-settings-tabs~="offline"]) *));
|
||||
@custom-variant boot-settings-recovery (&:where([data-boot-settings-tabs~="recovery"] *));
|
||||
|
||||
/* Settings pages with one section have no section jump row (SectionNav). */
|
||||
|
||||
@@ -55,7 +55,7 @@ export function OfflineSection({ draft }: { draft: OrgPolicyDraft }) {
|
||||
<SettingsSection
|
||||
id="offline"
|
||||
title="Offline mode"
|
||||
description="People can keep mail on a device to read and write it without a connection. In browsers it is off until someone turns on “Keep mail on this device” in Settings > Backup & reset. In the Bulwark desktop app it is on unless you forbid it here."
|
||||
description="People can keep mail on a device to read and write it without a connection. In browsers it is off until someone turns on “Keep mail on this device” in Settings > Offline. In the Bulwark desktop app it is on unless you forbid it here."
|
||||
>
|
||||
<SettingItem
|
||||
label="Don't let people keep mail on their devices"
|
||||
|
||||
@@ -28,6 +28,8 @@ const OPTIONAL: Partial<Record<SettingsSearchTab, string>> = {
|
||||
contacts: "boot-no-settings-contacts:hidden",
|
||||
files: "boot-no-files:hidden boot-no-settings-files:hidden",
|
||||
addons: "boot-no-settings-addons:hidden",
|
||||
// Not in the static Lite build.
|
||||
offline: "boot-no-settings-offline:hidden",
|
||||
};
|
||||
|
||||
/** A line of text as tall as its line box, so rows keep the real height. */
|
||||
|
||||
@@ -31,7 +31,8 @@ vi.stubGlobal('ResizeObserver', class { observe() {} unobserve() {} disconnect()
|
||||
|
||||
const { EmailViewer } = await import('../email-viewer');
|
||||
const { ThreadConversationView } = await import('../thread-conversation-view');
|
||||
const { ReadingSettings } = await import('@/components/settings/reading-settings');
|
||||
// Remote content and trusted senders moved from Reading to Privacy (settings-24, 2026-11-04).
|
||||
const { PrivacySettings } = await import('@/components/settings/privacy-settings');
|
||||
|
||||
const mailboxes = [{ id: 'inbox', name: 'Inbox', role: 'inbox', parentId: null, myRights: { mayAddItems: true } }] as unknown as Mailbox[];
|
||||
const email = {
|
||||
@@ -123,7 +124,7 @@ describe('before the policy has loaded', () => {
|
||||
|
||||
describe('trusted senders settings', () => {
|
||||
it('lets users add senders and switch the address book when nothing blocks', async () => {
|
||||
render(<ReadingSettings />);
|
||||
render(<PrivacySettings />);
|
||||
const toggle = screen.getByText('trusted_senders.use_address_book_label').closest('[class]')!.parentElement!;
|
||||
expect(toggle.querySelector('[inert]')).toBeNull();
|
||||
fireEvent.click(screen.getByText('trusted_senders.count_zero'));
|
||||
@@ -132,7 +133,7 @@ describe('trusted senders settings', () => {
|
||||
|
||||
it('shows the list read-only and the address book switch as the organisation\'s while it blocks', async () => {
|
||||
restrict({ locked: true, value: 'block' });
|
||||
render(<ReadingSettings />);
|
||||
render(<PrivacySettings />);
|
||||
expect(screen.getAllByText('trusted_senders_note').length).toBeGreaterThan(0);
|
||||
const switchRow = screen.getByText('trusted_senders.use_address_book_label').closest('div')!.parentElement!.parentElement!;
|
||||
expect(switchRow.querySelector('[inert]')).not.toBeNull();
|
||||
@@ -145,7 +146,7 @@ describe('trusted senders settings', () => {
|
||||
it('still lets users take a sender off the list while it blocks', async () => {
|
||||
restrict({ locked: true, value: 'block' });
|
||||
useSettingsStore.setState({ trustedSenders: ['news@shop.example'], trustedSendersAddressBook: false });
|
||||
render(<ReadingSettings />);
|
||||
render(<PrivacySettings />);
|
||||
fireEvent.click(screen.getByText('trusted_senders.count_one'));
|
||||
await act(async () => { await new Promise((r) => setTimeout(r, 10)); });
|
||||
fireEvent.click(screen.getByTitle('remove'));
|
||||
|
||||
@@ -57,7 +57,7 @@ async function freeRoom(): Promise<number | null> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Settings > Backup & reset > Offline > Export kept mail: from the local store, also with no server. */
|
||||
/** Settings > Offline > Export kept mail: from the local store, also with no server. */
|
||||
export function LocalExportDialog({ request }: { request: LocalExportRequest }) {
|
||||
const t = useTranslations("offlineExport");
|
||||
const tExport = useTranslations("mail_export");
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { formatFileSize } from '@/lib/utils';
|
||||
import { useKeptFiles } from '@/hooks/use-kept-files';
|
||||
|
||||
/** The files this account keeps until told otherwise, in Settings > Backup & reset > Offline. */
|
||||
/** The files this account keeps until told otherwise, in Settings > Offline. */
|
||||
export function KeptFiles({ account }: { account: string }) {
|
||||
const t = useTranslations('offlineStorage');
|
||||
const { pinned, unpin } = useKeptFiles(account);
|
||||
|
||||
@@ -43,7 +43,7 @@ export function OfflineHost() {
|
||||
useEffect(() => {
|
||||
const show = (notice: OfflineNotice) => {
|
||||
const tr = tRef.current;
|
||||
const openStorage = { label: tStorageRef.current('open_settings'), onClick: () => routerRef.current.push(buildSettingsPath('backup', 'offline')) };
|
||||
const openStorage = { label: tStorageRef.current('open_settings'), onClick: () => routerRef.current.push(buildSettingsPath('offline', 'offline')) };
|
||||
switch (notice.kind) {
|
||||
case 'queued':
|
||||
toast.info(tr('toast.queued'));
|
||||
|
||||
@@ -19,13 +19,14 @@ import { KeyProtectionNotes } from './key-protection-notes';
|
||||
import { WipeDeviceSection } from './wipe-device-section';
|
||||
|
||||
/**
|
||||
* Settings > Backup & reset > Offline (#523): for each account signed in here,
|
||||
* whether it keeps mail on this device, how much, what that takes, and the way
|
||||
* to clear it. Above that what the browser may do to it, below it the outbox
|
||||
* and whatever didn't go through. Not in Lite.
|
||||
* Settings > Offline (#523), a page of its own: for each account signed in
|
||||
* here, whether it keeps mail on this device, how much, what that takes, and
|
||||
* the way to clear it. Above that what the browser may do to it; in a section
|
||||
* below it the outbox and whatever didn't go through. Not in Lite.
|
||||
*/
|
||||
export function OfflineSettings() {
|
||||
const t = useTranslations('offline.settings');
|
||||
const tOutbox = useTranslations('offline.outbox');
|
||||
const tStorage = useTranslations('offlineStorage');
|
||||
const active = useAuthStore((s) => s.activeAccountId);
|
||||
useAuthStore((s) => s.connectedAccountsRevision);
|
||||
@@ -55,39 +56,43 @@ export function OfflineSettings() {
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsSection id="offline" title={t('title')} description={t('description')}>
|
||||
{blocked ? (
|
||||
<p className="text-sm text-muted-foreground" data-testid="offline-private">{tNotes('store_private')}</p>
|
||||
) : !offlineSupported() ? (
|
||||
<p className="text-sm text-muted-foreground" data-testid="offline-unsupported">{t('unsupported')}</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-900 dark:text-amber-200">
|
||||
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
||||
<p data-testid="offline-warning">{isDesktopApp() ? tStorage('warning_desktop') : t('warning')}</p>
|
||||
</div>
|
||||
<KeyProtectionNotes browser />
|
||||
{enabledCount > 0 && persist === 'refused' && (
|
||||
<div className="flex flex-col gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-900 dark:text-amber-200 sm:flex-row sm:items-center sm:justify-between" role="status" data-testid="offline-persist-refused">
|
||||
<p className="min-w-0">{tStorage('persist_refused')}</p>
|
||||
<Button variant="outline" size="sm" className="shrink-0" disabled={asking} onClick={() => void askAgain()} data-testid="offline-persist-retry">{tStorage('persist_retry')}</Button>
|
||||
<div className="space-y-8">
|
||||
<SettingsSection id="offline" title={t('title')} description={t('description')}>
|
||||
{blocked ? (
|
||||
<p className="text-sm text-muted-foreground" data-testid="offline-private">{tNotes('store_private')}</p>
|
||||
) : !offlineSupported() ? (
|
||||
<p className="text-sm text-muted-foreground" data-testid="offline-unsupported">{t('unsupported')}</p>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-900 dark:text-amber-200">
|
||||
<AlertTriangle className="mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
||||
<p data-testid="offline-warning">{isDesktopApp() ? tStorage('warning_desktop') : t('warning')}</p>
|
||||
</div>
|
||||
)}
|
||||
{enabledCount > 0 && safariMayDelete() && (
|
||||
<p className="rounded-md border border-border bg-muted px-3 py-2 text-xs text-foreground" data-testid="offline-safari-hint">{tStorage('safari_hint')}</p>
|
||||
)}
|
||||
{accounts.map((account, index) => (
|
||||
<AccountStorage
|
||||
key={account}
|
||||
account={account}
|
||||
primary={index === 0}
|
||||
heading={several ? labels.find((entry) => entry.id === account)?.email || labels.find((entry) => entry.id === account)?.label || account : null}
|
||||
/>
|
||||
))}
|
||||
{[active, ...accounts].some((account) => offlineAllowed(account)) && <WipeDeviceSection />}
|
||||
</>
|
||||
)}
|
||||
<OutboxList />
|
||||
</SettingsSection>
|
||||
<KeyProtectionNotes browser />
|
||||
{enabledCount > 0 && persist === 'refused' && (
|
||||
<div className="flex flex-col gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-xs text-amber-900 dark:text-amber-200 sm:flex-row sm:items-center sm:justify-between" role="status" data-testid="offline-persist-refused">
|
||||
<p className="min-w-0">{tStorage('persist_refused')}</p>
|
||||
<Button variant="outline" size="sm" className="shrink-0" disabled={asking} onClick={() => void askAgain()} data-testid="offline-persist-retry">{tStorage('persist_retry')}</Button>
|
||||
</div>
|
||||
)}
|
||||
{enabledCount > 0 && safariMayDelete() && (
|
||||
<p className="rounded-md border border-border bg-muted px-3 py-2 text-xs text-foreground" data-testid="offline-safari-hint">{tStorage('safari_hint')}</p>
|
||||
)}
|
||||
{accounts.map((account, index) => (
|
||||
<AccountStorage
|
||||
key={account}
|
||||
account={account}
|
||||
primary={index === 0}
|
||||
heading={several ? labels.find((entry) => entry.id === account)?.email || labels.find((entry) => entry.id === account)?.label || account : null}
|
||||
/>
|
||||
))}
|
||||
{[active, ...accounts].some((account) => offlineAllowed(account)) && <WipeDeviceSection />}
|
||||
</>
|
||||
)}
|
||||
</SettingsSection>
|
||||
<SettingsSection id="outbox" title={tOutbox('title')}>
|
||||
<OutboxList heading={false} />
|
||||
</SettingsSection>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export function WipeDeviceSection() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 border-t border-border py-3" data-testid="wipe-device-section">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 border-t border-border py-3" data-testid="wipe-device-section" data-search-label={t('settings_title')}>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-sm font-medium text-foreground">{t('settings_title')}</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">{t('settings_description')}</p>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { afterEach, describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { MessageListSettings } from '../message-list-settings';
|
||||
import { NotificationSettings } from '../notification-settings';
|
||||
import { PluginsSettings } from '../plugins-settings';
|
||||
import { ReadingSettings } from '../reading-settings';
|
||||
import { PrivacySettings } from '../privacy-settings';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { usePolicyStore } from '@/stores/policy-store';
|
||||
import { usePluginStore } from '@/stores/plugin-store';
|
||||
@@ -13,7 +13,11 @@ import type { InstalledPlugin } from '@/lib/plugin-types';
|
||||
|
||||
// #903: the settings pages hide or explain what the no-third-party mode turns off.
|
||||
|
||||
vi.mock('next-intl', () => ({ useTranslations: () => (key: string) => key, useLocale: () => 'en' }));
|
||||
vi.mock('next-intl', () => ({
|
||||
useTranslations: () => (key: string) => key,
|
||||
useLocale: () => 'en',
|
||||
useFormatter: () => ({ dateTime: String, relativeTime: String, number: String }),
|
||||
}));
|
||||
vi.mock('@/hooks/use-config', () => ({ useConfig: () => ({ demoMode: false }) }));
|
||||
vi.mock('../settings-section', async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import('../settings-section')>()),
|
||||
@@ -46,14 +50,22 @@ beforeEach(() => {
|
||||
});
|
||||
afterEach(() => setNoThirdPartyTraffic(false));
|
||||
|
||||
describe('Settings > Message list', () => {
|
||||
// Sender pictures, remote content and trusted senders moved from Message
|
||||
// list and Reading to Privacy (settings-24, 2026-11-04).
|
||||
describe('Settings > Privacy', () => {
|
||||
it('offers sender favicons only while third-party traffic is allowed', () => {
|
||||
const { unmount } = render(<MessageListSettings />);
|
||||
const { unmount } = render(<PrivacySettings />);
|
||||
expect(screen.getByText('sender_favicons.label')).toBeInTheDocument();
|
||||
unmount();
|
||||
setNoThirdPartyTraffic(true);
|
||||
render(<PrivacySettings />);
|
||||
expect(screen.queryByText('sender_favicons.label')).toBeNull();
|
||||
});
|
||||
|
||||
it('leaves a link to Privacy on Message list', () => {
|
||||
render(<MessageListSettings />);
|
||||
expect(screen.queryByText('sender_favicons.label')).toBeNull();
|
||||
expect(screen.getByText('senders_label')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -91,14 +103,14 @@ describe('Settings > Plugins', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Settings > Reading', () => {
|
||||
describe('Settings > Privacy: remote content', () => {
|
||||
function externalContentRow() {
|
||||
return screen.getByText('external_content.label').closest('[data-search-label]') as HTMLElement;
|
||||
}
|
||||
|
||||
it('lets the user pick the external content policy without the mode', () => {
|
||||
useSettingsStore.setState({ externalContentPolicy: 'ask' });
|
||||
render(<ReadingSettings />);
|
||||
render(<PrivacySettings />);
|
||||
expect(externalContentRow().textContent).not.toContain('org_locked');
|
||||
expect(externalContentRow().querySelector('select')?.value).toBe('ask');
|
||||
});
|
||||
@@ -106,7 +118,7 @@ describe('Settings > Reading', () => {
|
||||
it('shows the policy as blocked and set by the organisation in the mode', () => {
|
||||
setNoThirdPartyTraffic(true);
|
||||
useSettingsStore.setState({ externalContentPolicy: 'allow' });
|
||||
render(<ReadingSettings />);
|
||||
render(<PrivacySettings />);
|
||||
const row = externalContentRow();
|
||||
expect(row.textContent).toContain('org_locked');
|
||||
expect(row.querySelector('select')?.value).toBe('block');
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'fake-indexeddb/auto';
|
||||
import { render } from '@testing-library/react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { NextIntlClientProvider } from 'next-intl';
|
||||
@@ -8,6 +9,8 @@ import en from '@/locales/en/common.json';
|
||||
// off and in the situations some rows need (touch screen, Mountain View,
|
||||
// several accounts), and checks that each entry of the search index has a
|
||||
// row marked with its label, and is advanced exactly when the page says so.
|
||||
// settings-8 (2026-11-04): and that every section a page draws can be found,
|
||||
// so a new section (offline, app lock) can't be left out of the index.
|
||||
|
||||
vi.unmock('next-intl');
|
||||
vi.mock('@/components/plugins/plugin-iframe-slot', () => ({ PluginIframeSlot: () => null }));
|
||||
@@ -33,7 +36,8 @@ const { useEmailStore } = await import('@/stores/email-store');
|
||||
const { useAccountStore } = await import('@/stores/account-store');
|
||||
const { TourProvider } = await import('@/components/tour/tour-provider');
|
||||
const { AdvancedSettingsProvider } = await import('../settings-section');
|
||||
const { tabSearchEntries, getByPath } = await import('@/lib/settings-search');
|
||||
const { tabSearchEntries, getByPath, withAppName, settingsTabLabelKey } = await import('@/lib/settings-search');
|
||||
const { setStoreBackend } = await import('@/lib/store/client/api');
|
||||
const pages = {
|
||||
account: (await import('../account-settings')).AccountSettings,
|
||||
general: (await import('../general-settings')).GeneralSettings,
|
||||
@@ -56,6 +60,9 @@ const pages = {
|
||||
files: (await import('../files-settings')).FilesSettingsComponent,
|
||||
addons: (await import('../addons-settings')).AddonsSettings,
|
||||
backup: (await import('../about-data-settings')).AboutDataSettings,
|
||||
offline: (await import('@/components/offline/offline-settings')).OfflineSettings,
|
||||
privacy: (await import('../privacy-settings')).PrivacySettings,
|
||||
about: (await import('../about-data-settings')).AboutSettings,
|
||||
};
|
||||
|
||||
const OVERRIDES: Record<string, () => unknown> = {
|
||||
@@ -77,19 +84,22 @@ const TAB_PAGES: Record<string, string[]> = {
|
||||
message_list: ['message_list'], reading: ['reading'], composing: ['composing'], vacation: ['vacation'],
|
||||
filters: ['filters'], organize: ['organize'], security: ['security'], sharing: ['sharing'], recovery: ['recovery'], calendar: ['calendar', 'calendar2'],
|
||||
contacts: ['contacts', 'contacts2'], files: ['files'], addons: ['addons'], backup: ['backup'],
|
||||
offline: ['offline'], privacy: ['privacy'], about: ['about'],
|
||||
};
|
||||
|
||||
describe('settings search index', () => {
|
||||
it('has a row for every entry on its page', { timeout: 120_000 }, () => {
|
||||
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('no network')));
|
||||
useAuthStore.setState({ isAuthenticated: true, authMode: 'basic', client: CLIENT } as never);
|
||||
useAuthStore.setState({ isAuthenticated: true, authMode: 'basic', client: CLIENT, activeAccountId: 'acc' } as never);
|
||||
useVacationStore.setState({ isSupported: true, isLoading: false, forwardAvailable: true, fetchVacationResponse: vi.fn(async () => {}) } as never);
|
||||
useFilterStore.setState({ isSupported: true, isLoading: false, rules: [], selectAccount: vi.fn(async () => {}) } as never);
|
||||
useAccountSecurityStore.setState({ isStalwart: true, isProbing: false, probe: vi.fn(async () => true), fetchAll: vi.fn(), fetchAuthInfo: vi.fn(), fetchPublicKeys: vi.fn(), fetchCryptoInfo: vi.fn() } as never);
|
||||
useCalendarStore.setState({ calendars: [{ id: 'c', name: 'Work', color: '#3b82f6', isDefault: false, isShared: false, shareWith: null, myRights: { mayShare: true, mayDelete: true, mayWriteAll: true } }], icalSubscriptions: [] } as never);
|
||||
useContactStore.setState({ addressBooks: [{ id: 'b', name: 'Club', isDefault: true, isShared: false, shareWith: null, myRights: { mayRead: true, mayWrite: true, mayDelete: true } }], contacts: [], fetchAddressBooks: vi.fn(async () => {}) } as never);
|
||||
useEmailStore.setState({ fetchMailboxes: vi.fn(async () => {}) } as never);
|
||||
const out: Record<string, Record<string, { on: string[]; off: string[]; error?: string }>> = {};
|
||||
// jsdom has no OPFS: the store says it's there, as a browser would, so Offline draws its rows.
|
||||
setStoreBackend({ supported: () => true } as never);
|
||||
const out: Record<string, Record<string, { on: string[]; off: string[]; sections: string[]; error?: string }>> = {};
|
||||
const realMatchMedia = window.matchMedia;
|
||||
const variants: Record<string, () => void> = {
|
||||
base: () => {},
|
||||
@@ -125,13 +135,16 @@ describe('settings search index', () => {
|
||||
</NextIntlClientProvider>,
|
||||
);
|
||||
const found = Array.from(container.querySelectorAll('[data-search-label]')).map((el) => el.getAttribute('data-search-label') ?? '');
|
||||
const sections = Array.from(container.querySelectorAll('[data-settings-section]')).map((el) => el.getAttribute('data-search-label') ?? '');
|
||||
unmount();
|
||||
return found;
|
||||
return { found, sections };
|
||||
};
|
||||
try {
|
||||
out[variant][tab] = { on: labels(true), off: labels(false) };
|
||||
const on = labels(true);
|
||||
const off = labels(false);
|
||||
out[variant][tab] = { on: on.found, off: off.found, sections: on.sections };
|
||||
} catch (e) {
|
||||
out[variant][tab] = { on: [], off: [], error: String(e) };
|
||||
out[variant][tab] = { on: [], off: [], sections: [], error: String(e) };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +153,7 @@ describe('settings search index', () => {
|
||||
const problems: string[] = [];
|
||||
for (const [tab, entries] of Object.entries(tabSearchEntries)) {
|
||||
for (const entry of entries) {
|
||||
const text = getByPath(en, entry.label) as string;
|
||||
const text = withAppName(getByPath(en, entry.label), 'Bulwark') as string;
|
||||
const variant = out[entry.when ?? 'base'];
|
||||
const parts = TAB_PAGES[tab].map((page) => variant[page]);
|
||||
const errors = parts.filter((part) => part.error).map((part) => part.error);
|
||||
@@ -153,6 +166,17 @@ describe('settings search index', () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Every section a page draws has an entry, so searching its name finds it.
|
||||
for (const [tab, entries] of Object.entries(tabSearchEntries)) {
|
||||
const indexed = new Set(entries.map((entry) => withAppName(getByPath(en, entry.label), 'Bulwark') as string));
|
||||
// A section named like its page is the page itself, which the search lists anyway.
|
||||
indexed.add(getByPath(en, settingsTabLabelKey(tab as never)) as string);
|
||||
for (const page of TAB_PAGES[tab]) {
|
||||
for (const section of out.base[page].sections) {
|
||||
if (!indexed.has(section)) problems.push(`${tab}: section "${section}" has no search entry`);
|
||||
}
|
||||
}
|
||||
}
|
||||
expect([...new Set(problems)]).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,6 +25,24 @@ vi.mock('@/components/plugins/app-top-banner-slot', () => ({ AppTopBannerSlot: (
|
||||
vi.mock('@/components/settings/about-data-settings', () => ({
|
||||
AboutDataSettings: () => null,
|
||||
AboutFooter: () => <div data-testid="about-footer" />,
|
||||
AboutSettings: () => <div data-testid="about-footer" />,
|
||||
}));
|
||||
vi.mock('@/components/offline/offline-settings', () => ({
|
||||
OfflineSettings: () => (
|
||||
<div>
|
||||
<div data-settings-section="offline"><h3>Offline</h3></div>
|
||||
<div data-settings-section="outbox"><h3>Outbox</h3></div>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
vi.mock('@/components/settings/privacy-settings', () => ({
|
||||
PrivacyLinkRow: () => null,
|
||||
PrivacySettings: () => (
|
||||
<div>
|
||||
<div data-settings-section="images"><h3>Images</h3></div>
|
||||
<div data-settings-section="receipts"><h3>Read receipts</h3></div>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
vi.mock('@/components/settings/appearance-settings',() => ({ AppearanceSettings: () => <div>appearance pane</div> }));
|
||||
vi.mock('@/components/settings/accessibility-settings', () => ({ AccessibilitySettings: () => <div>accessibility pane</div> }));
|
||||
@@ -135,10 +153,44 @@ describe('settings section links', () => {
|
||||
await waitFor(() => expect(scrolled).toContain('downloads'));
|
||||
});
|
||||
|
||||
it('shows the version footer in the sidebar only with Advanced on', () => {
|
||||
// settings-32 (2026-11-04): the version card in the sidebar's footer took
|
||||
// room from the page list on short screens; it's a page at the end now.
|
||||
it('shows the version on an About page, not in the sidebar footer', () => {
|
||||
render(<SettingsApp />);
|
||||
expect(screen.queryByTestId('about-footer')).toBeNull();
|
||||
fireEvent.click(screen.getByTestId('settings-advanced-switch'));
|
||||
expect(screen.queryByTestId('about-footer')).toBeNull();
|
||||
fireEvent.click(screen.getByTestId('settings-tab-about'));
|
||||
expect(screen.getByTestId('settings-tab-about')).toHaveAttribute('aria-current', 'page');
|
||||
expect(screen.getByTestId('about-footer')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// settings-7 and settings-24 (2026-11-04): Offline is a page of its own and
|
||||
// remote images, read receipts and sender pictures moved to Privacy. Old
|
||||
// links, from notices and bookmarks, land there.
|
||||
it('lands an old /settings/backup/offline link on the Offline page', async () => {
|
||||
render(<SettingsApp linkSegments={['backup', 'offline']} />);
|
||||
expect(screen.getByTestId('settings-tab-offline')).toHaveAttribute('aria-current', 'page');
|
||||
await waitFor(() => expect(scrolled).toContain('offline'));
|
||||
});
|
||||
|
||||
it('lands old links to remote images and read receipts on Privacy', async () => {
|
||||
const first = render(<SettingsApp linkSegments={['reading', 'images']} />);
|
||||
expect(screen.getByTestId('settings-tab-privacy')).toHaveAttribute('aria-current', 'page');
|
||||
await waitFor(() => expect(scrolled).toContain('images'));
|
||||
first.unmount();
|
||||
render(<SettingsApp linkSegments={['content_senders']} />);
|
||||
expect(screen.getByTestId('settings-tab-privacy')).toHaveAttribute('aria-current', 'page');
|
||||
act(() => { window.dispatchEvent(new CustomEvent('settings-tab-change', { detail: { tab: 'composing', section: 'receipts' } })); });
|
||||
expect(screen.getByTestId('settings-tab-privacy')).toHaveAttribute('aria-current', 'page');
|
||||
await waitFor(() => expect(scrolled).toContain('receipts'));
|
||||
});
|
||||
|
||||
it('lists Offline and Privacy among the first pages, and About last', () => {
|
||||
render(<SettingsApp />);
|
||||
const ids = Array.from(document.querySelectorAll('[data-testid^="settings-tab-"]')).map((el) => el.getAttribute('data-testid'));
|
||||
expect(ids).toContain('settings-tab-offline');
|
||||
expect(ids.indexOf('settings-tab-privacy')).toBeLessThan(ids.indexOf('settings-tab-message_list'));
|
||||
expect(ids.indexOf('settings-tab-offline')).toBeLessThan(ids.indexOf('settings-tab-message_list'));
|
||||
expect(ids[ids.length - 1]).toBe('settings-tab-about');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ConfirmDialog } from '@/components/ui/confirm-dialog';
|
||||
import { toast } from '@/stores/toast-store';
|
||||
import { DebugSettings } from './debug-settings';
|
||||
import { MailBackupSettings } from '@/components/mail-transfer/mail-backup-settings';
|
||||
import { OfflineSettings } from '@/components/offline/offline-settings';
|
||||
import { productName } from '@/lib/product-name';
|
||||
|
||||
const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0";
|
||||
const GIT_COMMIT = process.env.NEXT_PUBLIC_GIT_COMMIT || "unknown";
|
||||
@@ -55,7 +55,7 @@ function VersionUpdateTag() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The About card, in the settings sidebar's footer: logo, version with the
|
||||
* The About card, on Settings > About and at the foot of the admin's list: logo, version with the
|
||||
* update tag, and the source link. A modified build must offer its own
|
||||
* source (AGPL-3.0 §13), so a configured URL replaces the upstream link.
|
||||
* Three clicks on the logo open Spam Siege, as before.
|
||||
@@ -126,9 +126,28 @@ export function AboutFooter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The Backup & reset page: keep settings (sync, export, import), fix a stale
|
||||
* view, start over, and (Advanced, where the admin allows it) debug logging.
|
||||
* The About card moved to the settings sidebar's footer.
|
||||
* Settings > About, the last entry of the page list: the About card with the
|
||||
* version and the source link. It was in the sidebar's footer, where it took
|
||||
* room from the page list on short screens.
|
||||
*/
|
||||
export function AboutSettings() {
|
||||
const t = useTranslations('uxs_structure.about');
|
||||
const appName = productName(useConfig().appName);
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<SettingsSection id="about" title={t('title', { appName })}>
|
||||
<div className="-mx-3">
|
||||
<AboutFooter />
|
||||
</div>
|
||||
</SettingsSection>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Backup & reset page: keep settings (sync, export, import), bring mail
|
||||
* in and save it as files, fix a stale view, start over, and (Advanced, where
|
||||
* the admin allows it) debug logging. Offline has a page of its own.
|
||||
*/
|
||||
export function AboutDataSettings() {
|
||||
const t = useTranslations('settings.advanced');
|
||||
@@ -218,8 +237,6 @@ export function AboutDataSettings() {
|
||||
|
||||
<MailBackupSettings />
|
||||
|
||||
<OfflineSettings />
|
||||
|
||||
<SettingsSection id="fix" title={t('sections.fix')} description={t('sections.fix_description')}>
|
||||
<SettingItem label={t('refresh_cache.label')} description={t('refresh_cache.description')} term="cache">
|
||||
<Button variant="outline" size="sm" onClick={() => setShowRefreshConfirm(true)}>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { IS_LITE } from '@/lib/lite';
|
||||
import { isPasswordSession } from '@/lib/auth/password-session';
|
||||
import { usePolicyStore } from '@/stores/policy-store';
|
||||
import { AccountArchiveSettings } from './account-archive-settings';
|
||||
import { SignOutRow } from './sign-out-row';
|
||||
import { DisplayNameSection } from './account-security-settings';
|
||||
import { EmailAddressesSection } from './identity-settings';
|
||||
import { MultipleAccountsSection } from './layout-settings';
|
||||
@@ -216,6 +217,8 @@ export function AccountSettings() {
|
||||
</span>
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
<SignOutRow />
|
||||
</SettingsSection>
|
||||
|
||||
<EmailAddressesSection />
|
||||
|
||||
@@ -9,13 +9,14 @@ import { usePolicyStore } from '@/stores/policy-store';
|
||||
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
|
||||
import { TemplateSettings } from './template-settings';
|
||||
import { ProofreadingSettings } from './proofreading-settings';
|
||||
import { PrivacyLinkRow } from './privacy-settings';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { X, ChevronRight } from '@/components/icons';
|
||||
|
||||
/**
|
||||
* The Composing page: the editor, replying, what happens before a send,
|
||||
* read receipts, templates (their own tab before) and a pointer to where
|
||||
* signatures live, since people look for them here.
|
||||
* templates (their own tab before) and pointers to where signatures and
|
||||
* read receipts live, since people look for them here.
|
||||
*/
|
||||
export function ComposingSettings() {
|
||||
const t = useTranslations('settings.email_behavior');
|
||||
@@ -42,8 +43,6 @@ export function ComposingSettings() {
|
||||
signatureNewMailEnabled,
|
||||
signatureReplyForwardEnabled,
|
||||
signatureSeparatorEnabled,
|
||||
requestReadReceiptDefault,
|
||||
readReceiptResponse,
|
||||
updateSetting,
|
||||
} = useSettingsStore();
|
||||
const { client } = useAuthStore();
|
||||
@@ -246,27 +245,8 @@ export function ComposingSettings() {
|
||||
onChange={(checked) => updateSetting('placeholderWarningEnabled', checked)}
|
||||
/>
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection id="receipts" title={tPage('sections.receipts')}>
|
||||
<SettingItem label={t('request_read_receipt.label')} description={t('request_read_receipt.description')}>
|
||||
<ToggleSwitch
|
||||
checked={requestReadReceiptDefault}
|
||||
onChange={(checked) => updateSetting('requestReadReceiptDefault', checked)}
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t('read_receipt_response.label')} description={t('read_receipt_response.description')}>
|
||||
<Select
|
||||
value={readReceiptResponse}
|
||||
onChange={(value) => updateSetting('readReceiptResponse', value as 'ask' | 'always' | 'never')}
|
||||
options={[
|
||||
{ value: 'ask', label: t('read_receipt_response.ask') },
|
||||
{ value: 'always', label: t('read_receipt_response.always') },
|
||||
{ value: 'never', label: t('read_receipt_response.never') },
|
||||
]}
|
||||
/>
|
||||
</SettingItem>
|
||||
<PrivacyLinkRow topic="receipts" />
|
||||
</SettingsSection>
|
||||
|
||||
{templatesEnabled && <TemplateSettings />}
|
||||
|
||||
@@ -9,37 +9,30 @@ import type { ArchiveMode, HoverAction, MvInboxType, SwipeAction } from '@/store
|
||||
import { ALL_HOVER_ACTIONS } from '@/stores/settings-store';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { useEmailStore } from '@/stores/email-store';
|
||||
import { useFaviconRefreshStore } from '@/stores/favicon-refresh-store';
|
||||
import { toast } from '@/stores/toast-store';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { IS_LITE } from '@/lib/lite';
|
||||
import { useNoThirdPartyTraffic } from '@/hooks/use-third-party-mode';
|
||||
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
|
||||
import { MessageListOrderSettings } from './message-list-order-settings';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { AlertTriangle, FolderSync, Loader2, RefreshCw } from '@/components/icons';
|
||||
import { PrivacyLinkRow } from './privacy-settings';
|
||||
import { AlertTriangle, FolderSync, Loader2 } from '@/components/icons';
|
||||
import { usePolicyStore } from '@/stores/policy-store';
|
||||
import { allowEmailMove, createMailboxWithHooks, emitEmailMove } from '@/lib/plugin-mail-hooks';
|
||||
import type { Mailbox } from '@/lib/jmap/types';
|
||||
import type { SearchDefaultScope } from '@/lib/search-default-scope';
|
||||
|
||||
/**
|
||||
* The Message list page: what the list shows, who the senders look like,
|
||||
* what one hover or swipe does, and what Delete and Archive mean. These
|
||||
* rows used to be spread over Reading, Layout and Appearance.
|
||||
* The Message list page: what the list shows, what one hover or swipe does,
|
||||
* and what Delete and Archive mean. These rows used to be spread over
|
||||
* Reading, Layout and Appearance. Sender pictures from the web are on
|
||||
* Privacy, with a link here.
|
||||
*/
|
||||
export function MessageListSettings() {
|
||||
const t = useTranslations('settings.email_behavior');
|
||||
// Sender icons come from an outside service: no setting for them in the mode (#903).
|
||||
const faviconsAllowed = !useNoThirdPartyTraffic();
|
||||
const tPage = useTranslations('settings.message_list');
|
||||
const tAppearance = useTranslations('settings.appearance');
|
||||
const tAdvanced = useTranslations('settings.advanced');
|
||||
const [isReorganizing, setIsReorganizing] = useState(false);
|
||||
const [reorganizeResult, setReorganizeResult] = useState<string | null>(null);
|
||||
const { isSettingLocked, isSettingHidden, isFeatureEnabled } = usePolicyStore();
|
||||
const archiveActionEnabled = isFeatureEnabled('archiveActionEnabled');
|
||||
const refreshFavicons = useFaviconRefreshStore((s) => s.refresh);
|
||||
// Swipes exist on touch screens only; a mouse user never sees these rows.
|
||||
const touchDevice = useMediaQuery('(pointer: coarse)');
|
||||
// With the Archive action off, a stored 'archive' swipe does nothing (the row
|
||||
@@ -75,8 +68,6 @@ export function MessageListSettings() {
|
||||
interfaceLayout,
|
||||
mvInboxType,
|
||||
tintListRowsByTag,
|
||||
senderFavicons,
|
||||
showAvatarsInJunk,
|
||||
updateSetting,
|
||||
} = useSettingsStore();
|
||||
|
||||
@@ -217,33 +208,8 @@ export function MessageListSettings() {
|
||||
onChange={(checked) => updateSetting('showVerificationCodes', checked)}
|
||||
/>
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
|
||||
{/* Sender favicons come from the /api/favicon proxy, absent in the static Lite build (components/ui/avatar.tsx). */}
|
||||
<SettingsSection id="senders" title={tPage('sections.senders')}>
|
||||
{!IS_LITE && faviconsAllowed && (
|
||||
<SettingItem label={tAdvanced('sender_favicons.label')} description={tAdvanced('sender_favicons.description')}>
|
||||
<ToggleSwitch checked={senderFavicons} onChange={(checked) => updateSetting('senderFavicons', checked)} />
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
{!IS_LITE && faviconsAllowed && senderFavicons && (
|
||||
<SettingItem nested label={tAdvanced('sender_favicons.refresh_label')} description={tAdvanced('sender_favicons.refresh_description')}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => { refreshFavicons(); toast.success(tAdvanced('sender_favicons.refresh_done')); }}
|
||||
className="text-xs h-7"
|
||||
>
|
||||
<RefreshCw className="w-3.5 h-3.5 me-1" />
|
||||
{tAdvanced('sender_favicons.refresh_button')}
|
||||
</Button>
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
<SettingItem label={tAdvanced('show_avatars_in_junk.label')} description={tAdvanced('show_avatars_in_junk.description')} advanced>
|
||||
<ToggleSwitch checked={showAvatarsInJunk} onChange={(checked) => updateSetting('showAvatarsInJunk', checked)} />
|
||||
</SettingItem>
|
||||
<PrivacyLinkRow topic="senders" />
|
||||
</SettingsSection>
|
||||
|
||||
{(isFeatureEnabled('hoverActionsConfigEnabled') || touchDevice) && (
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useSettingsStore } from '@/stores/settings-store';
|
||||
import { usePolicyStore } from '@/stores/policy-store';
|
||||
import { useContactStore } from '@/stores/contact-store';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { useFaviconRefreshStore } from '@/stores/favicon-refresh-store';
|
||||
import { toast } from '@/stores/toast-store';
|
||||
import { useNoThirdPartyTraffic, useRemoteContentBlockedByOrg } from '@/hooks/use-third-party-mode';
|
||||
import { IS_LITE } from '@/lib/lite';
|
||||
import { TrustedSendersModal } from '@/components/trusted-senders-modal';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ChevronRight, RefreshCw } from '@/components/icons';
|
||||
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
|
||||
import { DesktopTelemetrySettings } from './desktop-telemetry-settings';
|
||||
|
||||
/** Opens another settings page (and section) the way deep links do. */
|
||||
export function goToSettings(tab: string, section?: string) {
|
||||
window.dispatchEvent(new CustomEvent('settings-tab-change', { detail: section ? { tab, section } : tab }));
|
||||
}
|
||||
|
||||
/**
|
||||
* A row that only points to the page holding a setting now, so people who
|
||||
* look for it in the old place still find it (one setting, one place).
|
||||
*/
|
||||
export function SettingsLinkRow({ label, description, button, onClick }: { label: string; description: string; button: string; onClick: () => void }) {
|
||||
return (
|
||||
<SettingItem label={label} description={description}>
|
||||
<Button variant="outline" size="sm" onClick={onClick}>
|
||||
{button}
|
||||
<ChevronRight className="w-4 h-4 ms-1 rtl:rotate-180" />
|
||||
</Button>
|
||||
</SettingItem>
|
||||
);
|
||||
}
|
||||
|
||||
/** The link to Privacy on the pages these settings came from. */
|
||||
export function PrivacyLinkRow({ topic }: { topic: 'images' | 'receipts' | 'senders' }) {
|
||||
const t = useTranslations('uxs_structure.privacy_links');
|
||||
return (
|
||||
<SettingsLinkRow
|
||||
label={t(`${topic}_label`)}
|
||||
description={t('description')}
|
||||
button={t('button')}
|
||||
onClick={() => goToSettings('privacy', topic)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/** Remote images and trusted senders (the former Content & Senders tab, then a section of Reading). */
|
||||
function RemoteContentSection() {
|
||||
// No third-party traffic (#903), or the organisation locked "block":
|
||||
// remote content never loads, so the setting shows as the organisation's,
|
||||
// set to block, and trusting senders has no effect.
|
||||
const remoteBlockedByOrg = useRemoteContentBlockedByOrg();
|
||||
const tLock = useTranslations('remote_content_lock');
|
||||
const t = useTranslations('settings.email_behavior');
|
||||
const tPage = useTranslations('settings.reading_page');
|
||||
const [showTrustedModal, setShowTrustedModal] = useState(false);
|
||||
const { isSettingLocked, isSettingHidden, isFeatureEnabled } = usePolicyStore();
|
||||
const { externalContentPolicy, trustedSenders, trustedSendersAddressBook, updateSetting } = useSettingsStore();
|
||||
const { trustedSenderEmails, trustedSendersLoaded, loadTrustedSendersBook } = useContactStore();
|
||||
const client = useAuthStore((state) => state.client);
|
||||
|
||||
// Load the address book so the count reflects the synced senders, not 0.
|
||||
useEffect(() => {
|
||||
if (trustedSendersAddressBook && client && !trustedSendersLoaded) {
|
||||
loadTrustedSendersBook(client);
|
||||
}
|
||||
}, [trustedSendersAddressBook, client, trustedSendersLoaded, loadTrustedSendersBook]);
|
||||
|
||||
const trustedCount = () => {
|
||||
const count = trustedSendersAddressBook ? trustedSenderEmails.length : trustedSenders.length;
|
||||
if (count === 0) return t('trusted_senders.count_zero');
|
||||
if (count === 1) return t('trusted_senders.count_one');
|
||||
return t('trusted_senders.count_other', { count });
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsSection id="images" title={tPage('sections.images')} description={tPage('sections.images_description')}>
|
||||
{isFeatureEnabled('externalContentEnabled') && !isSettingHidden('externalContentPolicy') && (
|
||||
<SettingItem label={t('external_content.label')} description={t('external_content.description')} locked={isSettingLocked('externalContentPolicy') || remoteBlockedByOrg}>
|
||||
<Select
|
||||
value={remoteBlockedByOrg ? 'block' : externalContentPolicy}
|
||||
onChange={(value) => updateSetting('externalContentPolicy', value as 'ask' | 'block' | 'allow')}
|
||||
options={[
|
||||
{ value: 'ask', label: t('external_content.ask') },
|
||||
{ value: 'block', label: t('external_content.block') },
|
||||
{ value: 'allow', label: t('external_content.allow') },
|
||||
]}
|
||||
/>
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
<SettingItem
|
||||
label={t('trusted_senders.label')}
|
||||
description={remoteBlockedByOrg ? tLock('trusted_senders_note') : t('trusted_senders.description')}
|
||||
>
|
||||
<button
|
||||
onClick={() => setShowTrustedModal(true)}
|
||||
className="flex items-center gap-2 px-3 py-1.5 bg-muted hover:bg-accent rounded-md transition-colors"
|
||||
>
|
||||
<span className="text-sm text-foreground">{trustedCount()}</span>
|
||||
<ChevronRight className="w-4 h-4 text-muted-foreground rtl:rotate-180" />
|
||||
</button>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem nested label={t('trusted_senders.use_address_book_label')} description={t('trusted_senders.use_address_book_description')} locked={remoteBlockedByOrg}>
|
||||
<ToggleSwitch
|
||||
checked={!!trustedSendersAddressBook}
|
||||
onChange={(checked) => updateSetting('trustedSendersAddressBook', checked)}
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<TrustedSendersModal
|
||||
isOpen={showTrustedModal}
|
||||
onClose={() => setShowTrustedModal(false)}
|
||||
readOnly={remoteBlockedByOrg}
|
||||
/>
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
|
||||
/** Asking for read receipts and answering them (a section of Composing before). */
|
||||
function ReadReceiptsSection() {
|
||||
const t = useTranslations('settings.email_behavior');
|
||||
const tPage = useTranslations('settings.composing_page');
|
||||
const { requestReadReceiptDefault, readReceiptResponse, updateSetting } = useSettingsStore();
|
||||
return (
|
||||
<SettingsSection id="receipts" title={tPage('sections.receipts')}>
|
||||
<SettingItem label={t('request_read_receipt.label')} description={t('request_read_receipt.description')}>
|
||||
<ToggleSwitch
|
||||
checked={requestReadReceiptDefault}
|
||||
onChange={(checked) => updateSetting('requestReadReceiptDefault', checked)}
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem label={t('read_receipt_response.label')} description={t('read_receipt_response.description')}>
|
||||
<Select
|
||||
value={readReceiptResponse}
|
||||
onChange={(value) => updateSetting('readReceiptResponse', value as 'ask' | 'always' | 'never')}
|
||||
options={[
|
||||
{ value: 'ask', label: t('read_receipt_response.ask') },
|
||||
{ value: 'always', label: t('read_receipt_response.always') },
|
||||
{ value: 'never', label: t('read_receipt_response.never') },
|
||||
]}
|
||||
/>
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sender pictures fetched from senders' websites (a section of Message list
|
||||
* before). They come from the /api/favicon proxy, absent in the static Lite
|
||||
* build, and from an outside service, so not in the no-third-party mode (#903).
|
||||
*/
|
||||
function SenderPicturesSection() {
|
||||
const tPage = useTranslations('settings.message_list');
|
||||
const tAdvanced = useTranslations('settings.advanced');
|
||||
const faviconsAllowed = !useNoThirdPartyTraffic();
|
||||
const refreshFavicons = useFaviconRefreshStore((s) => s.refresh);
|
||||
const { senderFavicons, showAvatarsInJunk, updateSetting } = useSettingsStore();
|
||||
return (
|
||||
<SettingsSection id="senders" title={tPage('sections.senders')}>
|
||||
{!IS_LITE && faviconsAllowed && (
|
||||
<SettingItem label={tAdvanced('sender_favicons.label')} description={tAdvanced('sender_favicons.description')}>
|
||||
<ToggleSwitch checked={senderFavicons} onChange={(checked) => updateSetting('senderFavicons', checked)} />
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
{!IS_LITE && faviconsAllowed && senderFavicons && (
|
||||
<SettingItem nested label={tAdvanced('sender_favicons.refresh_label')} description={tAdvanced('sender_favicons.refresh_description')}>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => { refreshFavicons(); toast.success(tAdvanced('sender_favicons.refresh_done')); }}
|
||||
className="text-xs h-7"
|
||||
>
|
||||
<RefreshCw className="w-3.5 h-3.5 me-1" />
|
||||
{tAdvanced('sender_favicons.refresh_button')}
|
||||
</Button>
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
<SettingItem label={tAdvanced('show_avatars_in_junk.label')} description={tAdvanced('show_avatars_in_junk.description')} advanced>
|
||||
<ToggleSwitch checked={showAvatarsInJunk} onChange={(checked) => updateSetting('showAvatarsInJunk', checked)} />
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings > Privacy: what tells others about you or fetches from outside.
|
||||
* Remote content and trusted senders, read receipts, sender pictures from
|
||||
* the web, and in the desktop app usage info and crash reports. Background
|
||||
* notifications through the push relay stay on Notifications, with a link.
|
||||
*/
|
||||
export function PrivacySettings() {
|
||||
const t = useTranslations('uxs_structure.privacy');
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<RemoteContentSection />
|
||||
<ReadReceiptsSection />
|
||||
<SenderPicturesSection />
|
||||
<DesktopTelemetrySettings />
|
||||
<SettingsSection id="push" title={t('push_title')}>
|
||||
<SettingsLinkRow
|
||||
label={t('push_link.label')}
|
||||
description={t('push_link.description')}
|
||||
button={t('push_link.button')}
|
||||
onClick={() => goToSettings('notifications', 'push')}
|
||||
/>
|
||||
</SettingsSection>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,33 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useSettingsStore, type AttachmentPosition } from '@/stores/settings-store';
|
||||
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
|
||||
import { DownloadsSettings } from './downloads-settings';
|
||||
import { TrustedSendersModal } from '@/components/trusted-senders-modal';
|
||||
import { PrivacyLinkRow } from './privacy-settings';
|
||||
import { ChevronRight } from '@/components/icons';
|
||||
import { usePolicyStore } from '@/stores/policy-store';
|
||||
import { useContactStore } from '@/stores/contact-store';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { useRemoteContentBlockedByOrg } from '@/hooks/use-third-party-mode';
|
||||
|
||||
/**
|
||||
* The Reading page: the open message. Opening it, how its body is drawn,
|
||||
* remote images and trusted senders (the former Content & Senders tab), and
|
||||
* attachments, and (Advanced) how downloads are named, the former Downloads
|
||||
* tab. List, delete and swipe rows live on the Message list page.
|
||||
* tab. Remote images and trusted senders are on Privacy, with a link here;
|
||||
* list, delete and swipe rows live on the Message list page.
|
||||
*/
|
||||
export function ReadingSettings() {
|
||||
// No third-party traffic (#903), or the organisation locked "block":
|
||||
// remote content never loads, so the setting shows as the organisation's,
|
||||
// set to block, and trusting senders has no effect.
|
||||
const remoteBlockedByOrg = useRemoteContentBlockedByOrg();
|
||||
const tLock = useTranslations('remote_content_lock');
|
||||
const t = useTranslations('settings.email_behavior');
|
||||
const tPage = useTranslations('settings.reading_page');
|
||||
const [showTrustedModal, setShowTrustedModal] = useState(false);
|
||||
const { isSettingLocked, isSettingHidden, isFeatureEnabled } = usePolicyStore();
|
||||
const { isSettingLocked, isSettingHidden } = usePolicyStore();
|
||||
|
||||
const {
|
||||
markAsReadDelay,
|
||||
@@ -37,28 +27,9 @@ export function ReadingSettings() {
|
||||
hideInlineImageAttachments,
|
||||
attachmentImagePreviewsEnabled,
|
||||
messageSpacing,
|
||||
externalContentPolicy,
|
||||
emailAlwaysLightMode,
|
||||
trustedSenders,
|
||||
trustedSendersAddressBook,
|
||||
updateSetting,
|
||||
} = useSettingsStore();
|
||||
const { trustedSenderEmails, trustedSendersLoaded, loadTrustedSendersBook } = useContactStore();
|
||||
const client = useAuthStore((state) => state.client);
|
||||
|
||||
// Load the address book so the count reflects the synced senders, not 0.
|
||||
useEffect(() => {
|
||||
if (trustedSendersAddressBook && client && !trustedSendersLoaded) {
|
||||
loadTrustedSendersBook(client);
|
||||
}
|
||||
}, [trustedSendersAddressBook, client, trustedSendersLoaded, loadTrustedSendersBook]);
|
||||
|
||||
const getTrustedSendersCount = () => {
|
||||
const count = trustedSendersAddressBook ? trustedSenderEmails.length : trustedSenders.length;
|
||||
if (count === 0) return t('trusted_senders.count_zero');
|
||||
if (count === 1) return t('trusted_senders.count_one');
|
||||
return t('trusted_senders.count_other', { count });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
@@ -119,50 +90,8 @@ export function ReadingSettings() {
|
||||
onChange={(checked) => updateSetting('emailAlwaysLightMode', checked)}
|
||||
/>
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection id="images" title={tPage('sections.images')} description={tPage('sections.images_description')}>
|
||||
{isFeatureEnabled('externalContentEnabled') && !isSettingHidden('externalContentPolicy') && (
|
||||
<SettingItem label={t('external_content.label')} description={t('external_content.description')} locked={isSettingLocked('externalContentPolicy') || remoteBlockedByOrg}>
|
||||
<Select
|
||||
value={remoteBlockedByOrg ? 'block' : externalContentPolicy}
|
||||
onChange={(value) =>
|
||||
updateSetting('externalContentPolicy', value as 'ask' | 'block' | 'allow')
|
||||
}
|
||||
options={[
|
||||
{ value: 'ask', label: t('external_content.ask') },
|
||||
{ value: 'block', label: t('external_content.block') },
|
||||
{ value: 'allow', label: t('external_content.allow') },
|
||||
]}
|
||||
/>
|
||||
</SettingItem>
|
||||
)}
|
||||
|
||||
<SettingItem
|
||||
label={t('trusted_senders.label')}
|
||||
description={remoteBlockedByOrg ? tLock('trusted_senders_note') : t('trusted_senders.description')}
|
||||
>
|
||||
<button
|
||||
onClick={() => setShowTrustedModal(true)}
|
||||
className="flex items-center gap-2 px-3 py-1.5 bg-muted hover:bg-accent rounded-md transition-colors"
|
||||
>
|
||||
<span className="text-sm text-foreground">{getTrustedSendersCount()}</span>
|
||||
<ChevronRight className="w-4 h-4 text-muted-foreground" />
|
||||
</button>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem nested label={t('trusted_senders.use_address_book_label')} description={t('trusted_senders.use_address_book_description')} locked={remoteBlockedByOrg}>
|
||||
<ToggleSwitch
|
||||
checked={!!trustedSendersAddressBook}
|
||||
onChange={(checked) => updateSetting('trustedSendersAddressBook', checked)}
|
||||
/>
|
||||
</SettingItem>
|
||||
|
||||
<TrustedSendersModal
|
||||
isOpen={showTrustedModal}
|
||||
onClose={() => setShowTrustedModal(false)}
|
||||
readOnly={remoteBlockedByOrg}
|
||||
/>
|
||||
<PrivacyLinkRow topic="images" />
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection id="attachments" title={tPage('sections.attachments')}>
|
||||
|
||||
@@ -34,7 +34,10 @@ import { CalendarSettings } from '@/components/settings/calendar-settings';
|
||||
import { CalendarManagementSettings } from '@/components/settings/calendar-management-settings';
|
||||
import { AddressBookManagementSettings } from '@/components/settings/address-book-management-settings';
|
||||
import { FilterSettings } from '@/components/settings/filter-settings';
|
||||
import { AboutDataSettings, AboutFooter } from '@/components/settings/about-data-settings';
|
||||
import { AboutDataSettings, AboutSettings } from '@/components/settings/about-data-settings';
|
||||
import { PrivacySettings } from '@/components/settings/privacy-settings';
|
||||
import { OfflineSettings } from '@/components/offline/offline-settings';
|
||||
import { IS_LITE } from '@/lib/lite';
|
||||
import { HelpLinks } from '@/components/layout/help-links';
|
||||
import { OrganizeSettings } from '@/components/settings/organize-settings';
|
||||
import { AccountSecuritySettings } from '@/components/settings/account-security-settings';
|
||||
@@ -82,6 +85,8 @@ import {
|
||||
getByPath,
|
||||
matchesAllWords,
|
||||
settingsSubResults,
|
||||
settingsTabLabelKey,
|
||||
subResultText,
|
||||
tabKeywords,
|
||||
tabSearchEntries,
|
||||
ARCHIVE_ACTION_SEARCH_PATHS,
|
||||
@@ -96,8 +101,8 @@ import { scrollBehavior } from '@/lib/reduced-motion';
|
||||
|
||||
type Tab = SettingsSearchTab;
|
||||
|
||||
// `top` and `bottom` have no heading: the first five pages, and Backup & reset
|
||||
// after a rule at the end.
|
||||
// `top` and `bottom` have no heading: the first pages, and Backup & reset and
|
||||
// About after a rule at the end.
|
||||
type TabGroup = SettingsTabGroup;
|
||||
|
||||
// A plugin that exposes a `settings-section` slot gets its own first-class
|
||||
@@ -153,13 +158,25 @@ interface SettingsTarget {
|
||||
section: string | null;
|
||||
}
|
||||
|
||||
// Sections that moved to a page of their own or to Privacy (2026-11), so a
|
||||
// link to `/settings/backup/offline` or `/settings/reading/images` still
|
||||
// lands on the section.
|
||||
const MOVED_SECTIONS: Record<string, Record<string, string>> = {
|
||||
backup: { offline: 'offline' },
|
||||
reading: { images: 'privacy' },
|
||||
composing: { receipts: 'privacy' },
|
||||
message_list: { senders: 'privacy' },
|
||||
};
|
||||
|
||||
/**
|
||||
* Migrates a legacy tab id to the page and section that hold it now. An
|
||||
* explicit section wins over the legacy one.
|
||||
*/
|
||||
function migrateTarget(raw: string, section: string | null = null): { tab: string; section: string | null } {
|
||||
const tab = raw in LEGACY_TAB_MAP ? LEGACY_TAB_MAP[raw] : raw;
|
||||
return { tab, section: section ?? (raw in LEGACY_SECTION_MAP ? LEGACY_SECTION_MAP[raw] : null) };
|
||||
const target = section ?? (raw in LEGACY_SECTION_MAP ? LEGACY_SECTION_MAP[raw] : null);
|
||||
const moved = target ? MOVED_SECTIONS[tab]?.[target] : undefined;
|
||||
return { tab: moved ?? tab, section: target };
|
||||
}
|
||||
|
||||
function hasOneShotDeepLink(): boolean {
|
||||
@@ -231,6 +248,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
const router = useRouter();
|
||||
const t = useTranslations('settings');
|
||||
const tSidebar = useTranslations('sidebar');
|
||||
const tRoot = useTranslations();
|
||||
const tShell = useTranslations('shell');
|
||||
const tDelegates = useTranslations('delegates');
|
||||
const tPalette = useTranslations('command_palette');
|
||||
@@ -337,7 +355,6 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
// plugins/themes/sidebar apps).
|
||||
const touchScreen = useMediaQuery('(pointer: coarse)');
|
||||
const mountainViewLayout = useSettingsStore((s) => s.interfaceLayout === 'mountain-view');
|
||||
const showAdvanced = useSettingsStore((s) => s.showAdvancedSettings);
|
||||
const multiAccount = useAccountStore((s) => s.accounts.length > 0);
|
||||
const { tabSearchHaystacks, tabSubResults } = useMemo(() => {
|
||||
const haystacks: Partial<Record<Tab, string>> = {};
|
||||
@@ -351,7 +368,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
return true;
|
||||
};
|
||||
for (const tabId of Object.keys(tabSearchEntries) as Tab[]) {
|
||||
const tabName = getByPath(messages, `settings.tabs.${tabId}`);
|
||||
const tabName = getByPath(messages, settingsTabLabelKey(tabId));
|
||||
const list = settingsSubResults(messages, tabId, appName, shown)
|
||||
// A section titled like its page is the page itself.
|
||||
.filter((r) => r.label !== tabName);
|
||||
@@ -364,7 +381,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
haystacks[tabId] = [
|
||||
tabId.replace(/_/g, ' '),
|
||||
tabKeywords[tabId] ?? '',
|
||||
...list.flatMap((r) => [r.label, r.description ?? '']),
|
||||
...list.map(subResultText),
|
||||
].join(' ').toLowerCase();
|
||||
}
|
||||
if (installedPlugins.length) {
|
||||
@@ -378,7 +395,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
}).join(' ');
|
||||
haystacks.addons = `${haystacks.addons ?? ''} ${haystackText}`.toLowerCase();
|
||||
const pluginSubs: SubResult[] = installedPlugins.flatMap((p) => {
|
||||
const items: SubResult[] = [{ label: p.name, description: p.description }];
|
||||
const items: SubResult[] = [{ label: p.name, description: p.description, keywords: p.author }];
|
||||
if (p.settingsSchema) {
|
||||
for (const schema of Object.values(p.settingsSchema)) {
|
||||
items.push({
|
||||
@@ -398,7 +415,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
haystacks.appearance = `${haystacks.appearance ?? ''} ${text}`.toLowerCase();
|
||||
subs.appearance = [
|
||||
...(subs.appearance ?? []),
|
||||
...installedThemes.map((th) => ({ label: th.name, description: th.description })),
|
||||
...installedThemes.map((th) => ({ label: th.name, description: th.description, keywords: th.author })),
|
||||
];
|
||||
}
|
||||
if (sidebarAppsList.length) {
|
||||
@@ -668,9 +685,12 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
// You and the app
|
||||
{ id: 'account', label: t('tabs.account'), icon: tabIcons.account, group: 'top' },
|
||||
...(stalwartFeaturesEnabled ? [{ id: 'security' as Tab, label: t('tabs.security'), icon: tabIcons.security, group: 'top' as TabGroup }] : []),
|
||||
{ id: 'privacy', label: tRoot(settingsTabLabelKey('privacy')), icon: tabIcons.privacy, group: 'top' },
|
||||
...(supportsSharing ? [{ id: 'sharing' as Tab, label: tDelegates('tab'), icon: tabIcons.sharing, group: 'top' as TabGroup }] : []),
|
||||
{ id: 'notifications', label: t('tabs.notifications'), icon: tabIcons.notifications, group: 'top' },
|
||||
{ id: 'general', label: t('tabs.general'), icon: tabIcons.general, group: 'top' },
|
||||
// Keeping mail on this device; the static Lite build has no offline mode.
|
||||
...(!IS_LITE ? [{ id: 'offline' as Tab, label: tRoot(settingsTabLabelKey('offline')), icon: tabIcons.offline, group: 'top' as TabGroup }] : []),
|
||||
{ id: 'appearance', label: t('tabs.appearance'), icon: tabIcons.appearance, group: 'top' },
|
||||
{ id: 'accessibility', label: t('tabs.accessibility'), icon: tabIcons.accessibility, group: 'top' },
|
||||
|
||||
@@ -709,6 +729,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
|
||||
// After a rule at the end, with no heading.
|
||||
{ id: 'backup', label: t('tabs.backup'), icon: tabIcons.backup, group: 'bottom' },
|
||||
{ id: 'about', label: tRoot(settingsTabLabelKey('about')), icon: tabIcons.about, group: 'bottom' },
|
||||
];
|
||||
|
||||
// In scoped (shared-account) mode, restrict to the account-relevant tabs the
|
||||
@@ -736,20 +757,24 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
}))
|
||||
.filter((g) => g.items.length > 0);
|
||||
|
||||
// Every word of the query counts, in any order, like the admin search.
|
||||
// Every word of the query starts a word, in any order. A page matches by
|
||||
// its own name and keywords, or when one of its rows has all the words:
|
||||
// words spread over unrelated rows don't make a match.
|
||||
const trimmedQuery = searchQuery.trim().toLowerCase();
|
||||
const matchesQuery = (tab: TabDef) => {
|
||||
if (!trimmedQuery) return true;
|
||||
return matchesAllWords(`${tab.label} ${tabSearchHaystacks[tab.id as Tab] ?? ''}`, trimmedQuery);
|
||||
const pageText = `${tab.label} ${String(tab.id).replace(/_/g, ' ')} ${tabKeywords[tab.id as Tab] ?? ''}`;
|
||||
return matchesAllWords(pageText, trimmedQuery)
|
||||
|| (tabSubResults[tab.id as Tab] ?? []).some((r) => matchesAllWords(subResultText(r), trimmedQuery));
|
||||
};
|
||||
|
||||
const subResultsForTab = (tabId: SettingsTabId): SubResult[] => {
|
||||
if (!trimmedQuery) return [];
|
||||
const list = tabSubResults[tabId as Tab] ?? [];
|
||||
// Rows whose name matches come before rows matched by their description.
|
||||
// Rows whose name matches come before rows matched by their description or synonyms.
|
||||
const byName = (r: SubResult) => (matchesAllWords(r.label, trimmedQuery) ? 0 : 1);
|
||||
return list
|
||||
.filter((r) => matchesAllWords(`${r.label} ${r.description ?? ''}`, trimmedQuery))
|
||||
.filter((r) => matchesAllWords(subResultText(r), trimmedQuery))
|
||||
.sort((a, b) => byName(a) - byName(b))
|
||||
.slice(0, 6);
|
||||
};
|
||||
@@ -890,7 +915,10 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
)}
|
||||
{effectiveActiveTab === 'files' && <FilesSettingsComponent />}
|
||||
{effectiveActiveTab === 'addons' && <AddonsSettings />}
|
||||
{effectiveActiveTab === 'privacy' && <PrivacySettings />}
|
||||
{effectiveActiveTab === 'offline' && <OfflineSettings />}
|
||||
{effectiveActiveTab === 'backup' && <AboutDataSettings />}
|
||||
{effectiveActiveTab === 'about' && <AboutSettings />}
|
||||
{effectiveActiveTab.startsWith('plugin:') && (
|
||||
<PluginIframeSlot
|
||||
key={effectiveActiveTab}
|
||||
@@ -1043,7 +1071,6 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
|
||||
<div className="border-t border-border px-2 py-2">
|
||||
<HelpLinks className="px-3 py-2" />
|
||||
{showAdvanced && <AboutFooter />}
|
||||
<AdvancedSettingsSwitch />
|
||||
</div>
|
||||
<div className="border-t border-border px-5 py-3">
|
||||
@@ -1208,9 +1235,11 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
{/* Only the Advanced switch (and the operator's help links, when set):
|
||||
the About card is a page at the end of the list, so the footer
|
||||
doesn't take room from the pages on short screens. */}
|
||||
<div className="border-t border-border px-1 py-1">
|
||||
<HelpLinks className="px-3 py-2" />
|
||||
{showAdvanced && <AboutFooter />}
|
||||
<AdvancedSettingsSwitch />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,9 @@ import {
|
||||
Database,
|
||||
Users,
|
||||
RotateCcw,
|
||||
CloudOff,
|
||||
EyeOff,
|
||||
Info,
|
||||
type AppIcon,
|
||||
} from '@/components/icons';
|
||||
import type { SettingsSearchTab } from '@/lib/settings-search';
|
||||
@@ -46,7 +49,10 @@ export const SETTINGS_TAB_ICONS: Record<SettingsSearchTab, AppIcon> = {
|
||||
contacts: BookUser,
|
||||
files: HardDrive,
|
||||
addons: Puzzle,
|
||||
offline: CloudOff,
|
||||
privacy: EyeOff,
|
||||
backup: Database,
|
||||
about: Info,
|
||||
};
|
||||
|
||||
export type SettingsTabGroup = 'top' | 'mail' | 'apps' | 'bottom';
|
||||
@@ -58,8 +64,8 @@ export type SettingsTabGroup = 'top' | 'mail' | 'apps' | 'bottom';
|
||||
* from the last session's hints.
|
||||
*/
|
||||
export const SETTINGS_NAV_GROUPS: readonly { group: SettingsTabGroup; tabs: readonly SettingsSearchTab[] }[] = [
|
||||
{ group: 'top', tabs: ['account', 'security', 'sharing', 'notifications', 'general', 'appearance', 'accessibility'] },
|
||||
{ group: 'top', tabs: ['account', 'security', 'privacy', 'sharing', 'notifications', 'general', 'offline', 'appearance', 'accessibility'] },
|
||||
{ group: 'mail', tabs: ['message_list', 'reading', 'composing', 'organize', 'filters', 'vacation', 'recovery'] },
|
||||
{ group: 'apps', tabs: ['calendar', 'contacts', 'files', 'addons'] },
|
||||
{ group: 'bottom', tabs: ['backup'] },
|
||||
{ group: 'bottom', tabs: ['backup', 'about'] },
|
||||
];
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { LogOut } from '@/components/icons';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useAuthStore } from '@/stores/auth-store';
|
||||
import { SettingItem } from './settings-section';
|
||||
|
||||
/**
|
||||
* "Sign out" on the Account page, so searching Settings for "logout" or
|
||||
* "sign out" leads somewhere. It does what the account menu's Sign out does.
|
||||
*/
|
||||
export function SignOutRow() {
|
||||
const tSidebar = useTranslations('sidebar');
|
||||
const t = useTranslations('uxs_structure.sign_out');
|
||||
const logout = useAuthStore((s) => s.logout);
|
||||
return (
|
||||
<SettingItem label={tSidebar('sign_out')} description={t('description')}>
|
||||
<Button variant="outline" size="sm" onClick={() => logout()} data-testid="settings-sign-out">
|
||||
<LogOut className="w-4 h-4 me-1" />
|
||||
{tSidebar('sign_out')}
|
||||
</Button>
|
||||
</SettingItem>
|
||||
);
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
matchesAllWords,
|
||||
tabKeywords,
|
||||
settingsSubResults,
|
||||
subResultText,
|
||||
tabSearchEntries,
|
||||
withAppName,
|
||||
} from '../settings-search';
|
||||
@@ -37,9 +38,11 @@ describe('settings search index (English messages)', () => {
|
||||
expect(labelsFor('calendar')).toContain('Free scrolling');
|
||||
expect(labelsFor('calendar')).toContain('Show week numbers');
|
||||
expect(labelsFor('notifications')).toContain('Unread count on the tab and app icon');
|
||||
expect(labelsFor('message_list')).toContain('Sender favicons');
|
||||
// settings-24 (2026-11-04): sender pictures, remote images and trusted
|
||||
// senders moved from Message list and Reading to the new Privacy page.
|
||||
expect(labelsFor('privacy')).toContain('Sender favicons');
|
||||
expect(labelsFor('message_list')).toContain('Clear search when switching folders');
|
||||
expect(labelsFor('reading')).toContain('Trusted senders');
|
||||
expect(labelsFor('privacy')).toContain('Trusted senders');
|
||||
expect(labelsFor('composing')).toContain('Undo send / send delay');
|
||||
expect(labelsFor('general')).toContain('Time zone');
|
||||
expect(labelsFor('organize')).toContain('Nested tags');
|
||||
@@ -86,7 +89,92 @@ describe('settings search index (English messages)', () => {
|
||||
it('marks the rows the Advanced switch hides', () => {
|
||||
const advanced = settingsSubResults(en, 'message_list', 'Bulwark').filter((r) => r.advanced).map((r) => r.label);
|
||||
expect(advanced).toContain('Emails per page');
|
||||
expect(advanced).not.toContain('Sender favicons');
|
||||
expect(settingsSubResults(en, 'privacy', 'Bulwark').filter((r) => r.advanced).map((r) => r.label)).not.toContain('Sender favicons');
|
||||
});
|
||||
});
|
||||
|
||||
// settings-8 (2026-11-04): "offline", "autoreply" and "logout" found nothing,
|
||||
// and "lock" found "Time format" (clock) and "Send Checks" (block).
|
||||
describe('settings search: words, word starts and synonyms', () => {
|
||||
/** Every row of every page that the query finds, as "page: label". */
|
||||
const everywhere = (query: string) => (Object.keys(tabSearchEntries) as SettingsSearchTab[]).flatMap((tab) =>
|
||||
settingsSubResults(en, tab, 'Bulwark')
|
||||
.filter((r) => matchesAllWords(subResultText(r), query))
|
||||
.map((r) => `${tab}: ${r.label}`));
|
||||
|
||||
it('matches whole words and the start of words, not the inside of a word', () => {
|
||||
expect(matchesAllWords('App lock', 'lock')).toBe(true);
|
||||
expect(matchesAllWords('Clock format', 'lock')).toBe(false);
|
||||
expect(matchesAllWords('Block senders', 'lock')).toBe(false);
|
||||
expect(matchesAllWords('Notifications', 'notif')).toBe(true);
|
||||
expect(matchesAllWords('Two-factor authentication', 'two factor')).toBe(true);
|
||||
expect(matchesAllWords('Mention recipients', 'pin')).toBe(false);
|
||||
});
|
||||
|
||||
it('still finds words inside Chinese, Japanese and Thai text, which has no spaces', () => {
|
||||
expect(matchesAllWords('自动回复设置', '回复')).toBe(true);
|
||||
expect(matchesAllWords('ตอบกลับอัตโนมัติ', 'อัตโนมัติ')).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['offline', 'offline: Keep mail on this device'],
|
||||
['autoreply', 'vacation: Reply automatically'],
|
||||
['out of office', 'vacation: Reply automatically'],
|
||||
['vacation', 'vacation: Reply automatically'],
|
||||
['logout', 'account: Sign out'],
|
||||
['sign out', 'account: Sign out'],
|
||||
['2fa', 'security: TOTP authentication'],
|
||||
['two-factor', 'security: TOTP authentication'],
|
||||
['password', 'security: Change password'],
|
||||
['signature', 'composing: Signatures'],
|
||||
['font size', 'accessibility: Text size'],
|
||||
['outbox', 'offline: Outbox'],
|
||||
['s/mime', 'security: Encryption'],
|
||||
])('finds "%s"', (query, row) => {
|
||||
expect(everywhere(query)).toContain(row);
|
||||
});
|
||||
|
||||
it('finds the app lock for "pin", and nothing that only has "pin" inside a word', () => {
|
||||
expect(everywhere('pin')).toEqual(['general: Lock with a PIN']);
|
||||
});
|
||||
|
||||
it('finds the Privacy page by its name and keywords', () => {
|
||||
expect(matchesAllWords(`Privacy ${tabKeywords.privacy}`, 'privacy')).toBe(true);
|
||||
expect(matchesAllWords(`Privacy ${tabKeywords.privacy}`, 'tracking')).toBe(true);
|
||||
expect(matchesAllWords(tabKeywords.reading, 'privacy')).toBe(false);
|
||||
});
|
||||
|
||||
it('finds the app lock for "lock", not the clock or blocked senders', () => {
|
||||
const found = everywhere('lock');
|
||||
expect(found).toContain('general: App lock');
|
||||
// "12-hour or 24-hour clock" and "block" in their descriptions.
|
||||
expect(found).not.toContain('general: Time format');
|
||||
expect(found).not.toContain('privacy: Images & external content');
|
||||
});
|
||||
});
|
||||
|
||||
// Decision 9 (2026-11-04): one setting, one place. Two rows called the same
|
||||
// ("Notification sound" twice on Accessibility) made people change one and
|
||||
// find the other "changed by itself".
|
||||
describe('settings search: no two rows with the same name', () => {
|
||||
// Renamed on other branches of the same fix (settings-27 on the
|
||||
// notification sound and mail rows, the Default view rows of Calendar and
|
||||
// Files still to decide). Empty this list once they're merged.
|
||||
const PENDING_RENAMES = new Set(['email notifications', 'notification sound', 'default view']);
|
||||
|
||||
it('names every indexed row and section differently, across all pages', () => {
|
||||
const seen = new Map<string, string>();
|
||||
const duplicates: string[] = [];
|
||||
for (const [tab, entries] of Object.entries(tabSearchEntries)) {
|
||||
for (const entry of entries) {
|
||||
const text = (withAppName(getByPath(en, entry.label), 'Bulwark') as string).trim().toLowerCase();
|
||||
if (PENDING_RENAMES.has(text)) continue;
|
||||
const where = `${tab}: ${entry.label}`;
|
||||
if (seen.has(text)) duplicates.push(`"${text}" at ${seen.get(text)} and ${where}`);
|
||||
else seen.set(text, where);
|
||||
}
|
||||
}
|
||||
expect(duplicates).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ const SETTINGS_TABS: readonly (SettingsSearchTab | typeof LEGACY_SETTINGS_TABS[n
|
||||
'account', 'general', 'notifications', 'appearance', 'message_list', 'reading',
|
||||
'composing', 'vacation', 'filters',
|
||||
'organize', 'security', 'calendar', 'contacts',
|
||||
'files', 'addons', 'backup',
|
||||
'files', 'addons', 'offline', 'privacy', 'backup', 'about',
|
||||
...LEGACY_SETTINGS_TABS,
|
||||
];
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import { settingsHooks } from '../../plugin-hooks';
|
||||
import { noThirdPartyTraffic } from '../../third-party-mode';
|
||||
import { toastDurationAtLeast } from '../../accessibility-settings';
|
||||
import { detectBrowserLocale } from '@/i18n/detect-locale';
|
||||
import { getByPath } from '../../settings-search';
|
||||
import { getByPath, settingsTabLabelKey } from '../../settings-search';
|
||||
import {
|
||||
DENIED_SETTINGS, PAGE_GATES, SETTINGS_SCHEMA, isAllowedSettingValue, settingValueLabelKey,
|
||||
type SettingSchemaEntry,
|
||||
@@ -195,7 +195,7 @@ export async function doDescribeSettings(arg: unknown): Promise<Record<string, S
|
||||
key,
|
||||
label: format(messages, entry.label) ?? key,
|
||||
page: entry.page,
|
||||
pageLabel: format(messages, `settings.tabs.${entry.page}`) ?? entry.page,
|
||||
pageLabel: format(messages, settingsTabLabelKey(entry.page)) ?? entry.page,
|
||||
current,
|
||||
currentLabel: settingValueLabel(entry, current, messages, locale),
|
||||
...(next !== undefined ? { next: next as SettingValue, nextLabel: settingValueLabel(entry, next, messages, locale) } : {}),
|
||||
|
||||
@@ -15,8 +15,9 @@ import { useAuthStore } from '@/stores/auth-store';
|
||||
import { usePolicyStore } from '@/stores/policy-store';
|
||||
import { fetchConfig, type ConfigData } from '@/hooks/use-config';
|
||||
import { productName } from '@/lib/product-name';
|
||||
import { IS_LITE } from '@/lib/lite';
|
||||
import {
|
||||
getByPath, settingsSubResults, tabKeywords, tabSearchEntries, type SettingsSearchTab,
|
||||
getByPath, settingsSubResults, settingsTabLabelKey, tabKeywords, tabSearchEntries, type SettingsSearchTab,
|
||||
} from '@/lib/settings-search';
|
||||
import { currentSessionLimits } from '@/lib/session-policy/limits';
|
||||
import type { AppSettingsView, SettingsSearchResult } from '../plugin-types';
|
||||
@@ -44,7 +45,8 @@ function availablePages(config: ConfigData | null): Set<SettingsSearchTab> {
|
||||
try { return policy.isFeatureEnabled(feature); } catch { return true; }
|
||||
};
|
||||
const stalwart = config?.stalwartFeaturesEnabled ?? true;
|
||||
const pages = new Set<SettingsSearchTab>(['account', 'notifications', 'general', 'appearance', 'message_list', 'reading', 'composing', 'organize', 'backup']);
|
||||
const pages = new Set<SettingsSearchTab>(['account', 'privacy', 'notifications', 'general', 'appearance', 'message_list', 'reading', 'composing', 'organize', 'backup', 'about']);
|
||||
if (!IS_LITE) pages.add('offline');
|
||||
if (stalwart) pages.add('security');
|
||||
if (client?.supportsSieve?.()) pages.add('filters');
|
||||
if (client?.supportsVacationResponse?.()) pages.add('vacation');
|
||||
@@ -56,7 +58,7 @@ function availablePages(config: ConfigData | null): Set<SettingsSearchTab> {
|
||||
}
|
||||
|
||||
function pageName(messages: Messages, tab: SettingsSearchTab): string {
|
||||
const name = getByPath(messages, `settings.tabs.${tab}`);
|
||||
const name = getByPath(messages, settingsTabLabelKey(tab));
|
||||
return typeof name === 'string' ? name : tab;
|
||||
}
|
||||
|
||||
@@ -91,7 +93,7 @@ export async function doAppSearchSettings(query: unknown, options?: unknown): Pr
|
||||
const theirs = settingsSubResults(english, tab, appName);
|
||||
mine.forEach((entry, i) => {
|
||||
const en = theirs[i];
|
||||
const text = `${entry.label} ${entry.description ?? ''} ${en?.label ?? ''} ${en?.description ?? ''}`.toLowerCase();
|
||||
const text = `${entry.label} ${entry.description ?? ''} ${entry.keywords ?? ''} ${en?.label ?? ''} ${en?.description ?? ''} ${en?.keywords ?? ''}`.toLowerCase();
|
||||
let score = 0;
|
||||
for (const word of words) {
|
||||
if (text.includes(word)) score += 2;
|
||||
|
||||
@@ -215,8 +215,8 @@ const ENTRIES: SettingSchemaEntry[] = [
|
||||
bool('tintListRowsByTag', 'message_list', `${AP}tint_list_rows.label`),
|
||||
bool('showVerificationCodes', 'message_list', `${EB}show_verification_codes.label`),
|
||||
// Both make the server fetch the sender's domain: the assistant may only switch them off.
|
||||
bool('senderFavicons', 'message_list', 'settings.advanced.sender_favicons.label', { thirdParty: true, only: false }),
|
||||
bool('showAvatarsInJunk', 'message_list', 'settings.advanced.show_avatars_in_junk.label', { thirdParty: true, only: false }),
|
||||
bool('senderFavicons', 'privacy', 'settings.advanced.sender_favicons.label', { thirdParty: true, only: false }),
|
||||
bool('showAvatarsInJunk', 'privacy', 'settings.advanced.show_avatars_in_junk.label', { thirdParty: true, only: false }),
|
||||
entry('hoverActions', 'message_list', `${EB}hover_actions.label`, {
|
||||
type: 'list',
|
||||
options: HOVER_ACTION_IDS.map(([value, labelKey]) => ({
|
||||
@@ -295,9 +295,9 @@ const ENTRIES: SettingSchemaEntry[] = [
|
||||
{ type: 'words', max: 50, maxLength: 40, lowercase: true }),
|
||||
bool('emptySubjectWarningEnabled', 'composing', `${EB}empty_subject_warning.label`),
|
||||
bool('placeholderWarningEnabled', 'composing', `${EB}placeholder_warning.label`),
|
||||
bool('requestReadReceiptDefault', 'composing', `${EB}request_read_receipt.label`),
|
||||
bool('requestReadReceiptDefault', 'privacy', `${EB}request_read_receipt.label`),
|
||||
// No "always": that sends a receipt mail by itself.
|
||||
choice('readReceiptResponse', 'composing', `${EB}read_receipt_response.label`,
|
||||
choice('readReceiptResponse', 'privacy', `${EB}read_receipt_response.label`,
|
||||
opts(`${EB}read_receipt_response.`, ['ask', 'never'])),
|
||||
|
||||
// --- Notifications ---
|
||||
|
||||
+95
-40
@@ -21,7 +21,10 @@ export type SettingsSearchTab =
|
||||
| 'contacts'
|
||||
| 'files'
|
||||
| 'addons'
|
||||
| 'backup';
|
||||
| 'offline'
|
||||
| 'privacy'
|
||||
| 'backup'
|
||||
| 'about';
|
||||
|
||||
type Tab = SettingsSearchTab;
|
||||
|
||||
@@ -45,8 +48,16 @@ export interface SettingsSearchEntry {
|
||||
advanced?: true;
|
||||
/** The page draws the row only then. */
|
||||
when?: SettingsSearchCondition;
|
||||
/**
|
||||
* Translation key of other words people use for this setting (synonyms,
|
||||
* abbreviations), in the user's language. Searched like the label.
|
||||
*/
|
||||
synonyms?: string;
|
||||
}
|
||||
|
||||
/** Key of a row's synonyms in the locale files. */
|
||||
const syn = (id: string) => `uxs_structure.search.${id}`;
|
||||
|
||||
// The settings each page draws, as the search lists them. Only labels a page
|
||||
// renders belong here: settings-search-anchors.test.tsx renders every page
|
||||
// and fails for an entry without its row. A description is found next to
|
||||
@@ -57,11 +68,12 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
{ label: 'settings.account.email.label' },
|
||||
{ label: 'settings.account.auth_method_label' },
|
||||
{ label: 'settings.account.server.label', advanced: true },
|
||||
{ label: 'settings.account.sections.addresses' },
|
||||
{ label: 'settings.account.sections.addresses', synonyms: syn('signature') },
|
||||
{ label: 'settings.identities.identities_count.label' },
|
||||
{ label: 'settings.identities.sub_addressing.label' },
|
||||
{ label: 'settings.email_behavior.sub_address_delimiter.label', advanced: true },
|
||||
{ label: 'settings.account.accounts.title', when: 'multiAccount' },
|
||||
{ label: 'sidebar.sign_out', synonyms: syn('sign_out') },
|
||||
],
|
||||
general: [
|
||||
{ label: 'settings.language_region.title' },
|
||||
@@ -72,7 +84,7 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
{ label: 'settings.language_region.time_zone.label' },
|
||||
{ label: 'settings.language_region.first_day.label' },
|
||||
{ label: 'appLock.section_title' },
|
||||
{ label: 'appLock.enable_label' },
|
||||
{ label: 'appLock.enable_label', synonyms: syn('app_lock') },
|
||||
{ label: 'protocol_handlers.title' },
|
||||
{ label: 'protocol_handlers.mailto_label' },
|
||||
{ label: 'protocol_handlers.webcal_label' },
|
||||
@@ -116,7 +128,7 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
{ label: 'settings.accessibility.presets.title' },
|
||||
{ label: 'settings.accessibility.quick_button.label' },
|
||||
{ label: 'settings.accessibility.sections.text' },
|
||||
{ label: 'settings.accessibility.text_size.label' },
|
||||
{ label: 'settings.accessibility.text_size.label', synonyms: syn('text_size') },
|
||||
{ label: 'settings.accessibility.font.label' },
|
||||
{ label: 'settings.accessibility.plain_text_font.label' },
|
||||
{ label: 'settings.accessibility.line_spacing.label' },
|
||||
@@ -126,15 +138,12 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
{ label: 'settings.accessibility.reading_width.label' },
|
||||
{ label: 'settings.accessibility.reading_mask.label' },
|
||||
{ label: 'settings.accessibility.plain_language.label' },
|
||||
{ label: 'settings.appearance.list_density.label' },
|
||||
{ label: 'settings.appearance.toolbar_labels.label' },
|
||||
{ label: 'settings.accessibility.sections.contrast' },
|
||||
{ label: 'settings.accessibility.contrast.label' },
|
||||
{ label: 'settings.accessibility.transparency.label' },
|
||||
{ label: 'settings.accessibility.underline_links.label' },
|
||||
{ label: 'settings.accessibility.color_names.label' },
|
||||
{ label: 'settings.accessibility.cvd_palette.label' },
|
||||
{ label: 'settings.email_behavior.always_light_mode.label' },
|
||||
{ label: 'settings.accessibility.theme_link.label' },
|
||||
{ label: 'settings.accessibility.sections.motion' },
|
||||
{ label: 'settings.accessibility.reduce_motion.label' },
|
||||
@@ -148,7 +157,6 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
{ label: 'settings.accessibility.large_targets.label' },
|
||||
{ label: 'settings.accessibility.large_cursor.label' },
|
||||
{ label: 'settings.accessibility.open_on_hover.label' },
|
||||
{ label: 'calendar.settings.hover_preview' },
|
||||
{ label: 'settings.accessibility.row_action_button.label' },
|
||||
{ label: 'settings.accessibility.native_scrollbars.label' },
|
||||
{ label: 'settings.accessibility.row_actions_link.label' },
|
||||
@@ -161,24 +169,14 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
{ label: 'settings.accessibility.read_aloud_rate.label' },
|
||||
{ label: 'settings.accessibility.compose_font.label' },
|
||||
{ label: 'settings.accessibility.compose_size.label' },
|
||||
{ label: 'settings.email_behavior.group_conversations.label' },
|
||||
{ label: 'settings.email_behavior.show_preview.label' },
|
||||
{ label: 'settings.email_behavior.mark_read.label' },
|
||||
{ label: 'settings.email_behavior.return_to_list_after_action.label' },
|
||||
{ label: 'settings.accessibility.sections.timing' },
|
||||
{ label: 'settings.accessibility.toast_duration.label' },
|
||||
{ label: 'settings.email_behavior.send_delay.label' },
|
||||
{ label: 'settings.accessibility.confirm_send.label' },
|
||||
{ label: 'settings.accessibility.confirm_delete.label' },
|
||||
{ label: 'settings.accessibility.confirm_archive.label' },
|
||||
{ label: 'settings.email_behavior.confirm_bulk_delete.label' },
|
||||
{ label: 'settings.accessibility.send_warnings_link.label' },
|
||||
{ label: 'settings.accessibility.sections.alerts' },
|
||||
{ label: 'settings.accessibility.visual_alerts.label' },
|
||||
{ label: 'settings.notifications.email.sound' },
|
||||
{ label: 'settings.notifications.calendar.sound' },
|
||||
{ label: 'settings.notifications.sound_selection.choose' },
|
||||
{ label: 'settings.appearance.favicon_unread_badge.label' },
|
||||
{ label: 'settings.accessibility.unread_counts.label' },
|
||||
{ label: 'settings.accessibility.do_not_disturb.label' },
|
||||
{ label: 'settings.accessibility.sections.screen_readers' },
|
||||
@@ -195,10 +193,6 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
{ label: 'settings.appearance.message_list_order.scope.label' },
|
||||
{ label: 'settings.appearance.tint_list_rows.label' },
|
||||
{ label: 'settings.email_behavior.show_verification_codes.label' },
|
||||
{ label: 'settings.message_list.sections.senders' },
|
||||
{ label: 'settings.advanced.sender_favicons.label' },
|
||||
{ label: 'settings.advanced.sender_favicons.refresh_label' },
|
||||
{ label: 'settings.advanced.show_avatars_in_junk.label', advanced: true },
|
||||
{ label: 'settings.message_list.sections.actions' },
|
||||
{ label: 'settings.email_behavior.hover_actions.label' },
|
||||
{ label: 'settings.message_list.sections.delete' },
|
||||
@@ -221,10 +215,6 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
{ label: 'settings.email_behavior.message_spacing.label' },
|
||||
{ label: 'settings.reading_page.plain_text_font_link.label' },
|
||||
{ label: 'settings.email_behavior.always_light_mode.label' },
|
||||
{ label: 'settings.reading_page.sections.images' },
|
||||
{ label: 'settings.email_behavior.external_content.label' },
|
||||
{ label: 'settings.email_behavior.trusted_senders.label' },
|
||||
{ label: 'settings.email_behavior.trusted_senders.use_address_book_label' },
|
||||
{ label: 'settings.reading_page.sections.attachments' },
|
||||
{ label: 'settings.email_behavior.attachment_position.label' },
|
||||
{ label: 'settings.email_behavior.attachment_click_action.label' },
|
||||
@@ -260,18 +250,15 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
{ label: 'settings.email_behavior.attachment_reminder.label' },
|
||||
{ label: 'settings.email_behavior.empty_subject_warning.label' },
|
||||
{ label: 'settings.email_behavior.placeholder_warning.label' },
|
||||
{ label: 'settings.composing_page.sections.receipts' },
|
||||
{ label: 'settings.email_behavior.request_read_receipt.label' },
|
||||
{ label: 'settings.email_behavior.read_receipt_response.label' },
|
||||
{ label: 'settings.templates.title' },
|
||||
{ label: 'settings.templates.export_import', advanced: true },
|
||||
{ label: 'settings.email_behavior.signatures_link.label' },
|
||||
{ label: 'settings.email_behavior.signatures_link.label', synonyms: syn('signature') },
|
||||
],
|
||||
vacation: [
|
||||
{ label: 'settings.vacation.date_range.start' },
|
||||
{ label: 'settings.vacation.date_range.end' },
|
||||
{ label: 'settings.vacation.auto_reply.title' },
|
||||
{ label: 'settings.vacation.status.label' },
|
||||
{ label: 'settings.vacation.status.label', synonyms: syn('vacation') },
|
||||
{ label: 'settings.vacation.message.subject_label' },
|
||||
{ label: 'settings.vacation.message.html_label' },
|
||||
{ label: 'settings.vacation.forward.title' },
|
||||
@@ -296,10 +283,10 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
],
|
||||
security: [
|
||||
{ label: 'settings.security.title' },
|
||||
{ label: 'settings.security.totp.label' },
|
||||
{ label: 'settings.security.totp.label', synonyms: syn('two_factor') },
|
||||
{ label: 'settings.security.sections.devices' },
|
||||
{ label: 'settings.security.sections.encryption', advanced: true },
|
||||
{ label: 'settings.security.password.title' },
|
||||
{ label: 'settings.security.sections.encryption', advanced: true, synonyms: syn('encryption') },
|
||||
{ label: 'settings.security.password.title', synonyms: syn('password') },
|
||||
{ label: 'settings.security.totp.section_title' },
|
||||
{ label: 'settings.security.app_passwords.title' },
|
||||
{ label: 'settings.security.api_keys.title', advanced: true },
|
||||
@@ -357,17 +344,56 @@ export const tabSearchEntries: Record<Tab, SettingsSearchEntry[]> = {
|
||||
{ label: 'settings.sidebar_apps.title' },
|
||||
{ label: 'settings.sidebar_apps.keep_loaded', advanced: true },
|
||||
],
|
||||
offline: [
|
||||
{ label: 'offline.settings.title' },
|
||||
{ label: 'offline.settings.keep_label', synonyms: syn('offline') },
|
||||
{ label: 'offlineWipe.settings_title', synonyms: syn('wipe') },
|
||||
{ label: 'offline.outbox.title', synonyms: syn('outbox') },
|
||||
],
|
||||
privacy: [
|
||||
{ label: 'settings.reading_page.sections.images', synonyms: syn('remote_content') },
|
||||
{ label: 'settings.email_behavior.external_content.label' },
|
||||
{ label: 'settings.email_behavior.trusted_senders.label' },
|
||||
{ label: 'settings.email_behavior.trusted_senders.use_address_book_label' },
|
||||
{ label: 'settings.composing_page.sections.receipts' },
|
||||
{ label: 'settings.email_behavior.request_read_receipt.label' },
|
||||
{ label: 'settings.email_behavior.read_receipt_response.label' },
|
||||
{ label: 'settings.message_list.sections.senders' },
|
||||
{ label: 'settings.advanced.sender_favicons.label' },
|
||||
{ label: 'settings.advanced.sender_favicons.refresh_label' },
|
||||
{ label: 'settings.advanced.show_avatars_in_junk.label', advanced: true },
|
||||
{ label: 'uxs_structure.privacy.push_title' },
|
||||
{ label: 'uxs_structure.privacy.push_link.label', synonyms: syn('push') },
|
||||
],
|
||||
backup: [
|
||||
{ label: 'settings.advanced.sections.backup' },
|
||||
{ label: 'settings.advanced.export_settings.label' },
|
||||
{ label: 'settings.advanced.import_settings.label' },
|
||||
{ label: 'mail_import.section_title' },
|
||||
{ label: 'mail_import.settings_label' },
|
||||
{ label: 'mail_export.settings_label' },
|
||||
{ label: 'settings.advanced.sections.fix' },
|
||||
{ label: 'settings.advanced.refresh_cache.label' },
|
||||
{ label: 'settings.advanced.reset_settings.label' },
|
||||
{ label: 'settings.advanced.debug_mode.label', advanced: true },
|
||||
],
|
||||
about: [
|
||||
{ label: 'uxs_structure.about.title', synonyms: syn('version') },
|
||||
],
|
||||
};
|
||||
|
||||
/** Pages added after `settings.tabs` keep their names in their own namespace. */
|
||||
const NEW_TAB_LABELS: Partial<Record<Tab, string>> = {
|
||||
offline: 'uxs_structure.tabs.offline',
|
||||
privacy: 'uxs_structure.tabs.privacy',
|
||||
about: 'uxs_structure.tabs.about',
|
||||
};
|
||||
|
||||
/** Translation key of a settings page's name. */
|
||||
export function settingsTabLabelKey(tab: SettingsSearchTab): string {
|
||||
return NEW_TAB_LABELS[tab] ?? `settings.tabs.${tab}`;
|
||||
}
|
||||
|
||||
// Extra English keywords per tab so common search terms hit even when the
|
||||
// translation doesn't contain the literal word.
|
||||
/** Settings shown only while the admin has the Archive action on (#1257). */
|
||||
@@ -380,7 +406,7 @@ export const tabKeywords: Record<Tab, string> = {
|
||||
appearance: 'theme dark light font size accent color animation density custom theme css skin toolbar sidebar account switcher icons rail layout reading pane',
|
||||
accessibility: 'a11y accessible vision blind low vision screen reader contrast motion animation reduce zoom text size font dyslexia spacing keyboard shortcuts focus outline cursor pointer targets colour blind color sound visual alert hearing deaf simple plain language read aloud',
|
||||
message_list: 'inbox list preview thread conversation grouping sort order columns archive delete swipe hover favicon avatar tag color',
|
||||
reading: 'mark read attachment open monospace mono font plain text images remote content block sender privacy tracking dark light download filename template eml save export',
|
||||
reading: 'mark read attachment open monospace mono font plain text dark light download filename template eml save export',
|
||||
composing: 'editor signature plain text reply forward draft compose template snippet quick reply mention undo send receipt attachment reminder',
|
||||
vacation: 'auto reply away out of office holiday responder',
|
||||
filters: 'sieve rules add create new block junk forward',
|
||||
@@ -392,7 +418,10 @@ export const tabKeywords: Record<Tab, string> = {
|
||||
contacts: 'address book contact',
|
||||
files: 'attachments cloud drive storage upload',
|
||||
addons: 'apps webview iframe plugins extensions addons sidebar',
|
||||
backup: 'export import storage quota privacy backup reset sync cache about version debug logs developer console diagnostic',
|
||||
offline: 'offline connection storage device sync outbox local kept',
|
||||
privacy: 'privacy tracking remote images content read receipts favicons pictures telemetry crash reports',
|
||||
backup: 'export import backup reset sync cache debug logs developer console diagnostic',
|
||||
about: 'about version update source code github license',
|
||||
};
|
||||
|
||||
// Some settings texts name the app with {appName} (#1314). The index keeps
|
||||
@@ -410,6 +439,8 @@ export function withAppName(node: unknown, appName: string): unknown {
|
||||
export interface SubResult {
|
||||
label: string;
|
||||
description?: string;
|
||||
/** Other words for the setting (its synonyms), searched like the label. */
|
||||
keywords?: string;
|
||||
// For plugin setting fields: the id of the plugin whose card needs to be
|
||||
// expanded before the field becomes visible in the DOM.
|
||||
pluginId?: string;
|
||||
@@ -450,20 +481,44 @@ export function settingsSubResults(
|
||||
if (typeof label !== 'string') continue;
|
||||
const descriptionKey = descriptionKeyFor(entry.label, messages);
|
||||
const description = descriptionKey ? (getByPath(messages, descriptionKey) as string) : undefined;
|
||||
const keywords = entry.synonyms ? getByPath(messages, entry.synonyms) : undefined;
|
||||
list.push({
|
||||
label: withAppName(label, appName) as string,
|
||||
...(description ? { description: withAppName(description, appName) as string } : {}),
|
||||
...(typeof keywords === 'string' && keywords ? { keywords } : {}),
|
||||
...(entry.advanced ? { advanced: true } : {}),
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/** Every word of the query is somewhere in the text, in any order. */
|
||||
// Scripts written without spaces between words: a query word there can
|
||||
// only be found inside the text, not at the start of a word.
|
||||
const NO_WORD_BREAKS = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Thai}]/u;
|
||||
|
||||
/** The words of a text or query: letters and digits, lower case. */
|
||||
export function searchWords(text: string): string[] {
|
||||
return text.toLowerCase().split(/[^\p{L}\p{N}\p{M}]+/u).filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Every word of the query starts a word of the text, in any order ("lock"
|
||||
* finds "App lock", not "Clock" or "Block"). In Chinese, Japanese and Thai,
|
||||
* which don't put spaces between words, a query word may sit anywhere.
|
||||
*/
|
||||
export function matchesAllWords(text: string, query: string): boolean {
|
||||
const haystack = text.toLowerCase();
|
||||
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
||||
return words.length > 0 && words.every((word) => haystack.includes(word));
|
||||
const words = searchWords(query);
|
||||
if (words.length === 0) return false;
|
||||
const haystack = searchWords(text);
|
||||
const flat = text.toLowerCase();
|
||||
return words.every((word) => (
|
||||
NO_WORD_BREAKS.test(word) ? flat.includes(word) : haystack.some((candidate) => candidate.startsWith(word))
|
||||
));
|
||||
}
|
||||
|
||||
/** The text a row is found by: its name, what it says under the name, and its synonyms. */
|
||||
export function subResultText(result: SubResult): string {
|
||||
return [result.label, result.description ?? '', result.keywords ?? ''].join(' ');
|
||||
}
|
||||
|
||||
export function getByPath(obj: unknown, path: string): unknown {
|
||||
|
||||
@@ -8,7 +8,7 @@ export const SETTINGS_TAB_IDS: readonly SettingsSearchTab[] = [
|
||||
'account', 'general', 'notifications', 'appearance', 'accessibility',
|
||||
'message_list', 'reading', 'composing', 'vacation', 'filters', 'organize',
|
||||
'security', 'sharing', 'recovery', 'calendar', 'contacts', 'files',
|
||||
'addons', 'backup',
|
||||
'addons', 'offline', 'privacy', 'backup', 'about',
|
||||
];
|
||||
|
||||
// Map legacy tab IDs to current ones; runs once on read of localStorage and
|
||||
@@ -20,7 +20,7 @@ export const LEGACY_TAB_MAP: Record<string, SettingsSearchTab> = {
|
||||
advanced: 'backup',
|
||||
layout: 'appearance',
|
||||
themes: 'appearance',
|
||||
content_senders: 'reading',
|
||||
content_senders: 'privacy',
|
||||
identities: 'account',
|
||||
language: 'general',
|
||||
protocol_handlers: 'general',
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "دون اتصال",
|
||||
"privacy": "الخصوصية",
|
||||
"about": "حول"
|
||||
},
|
||||
"search": {
|
||||
"signature": "توقيع توقيع البريد تذييل",
|
||||
"sign_out": "تسجيل الخروج خروج logout",
|
||||
"app_lock": "PIN قفل رمز شاشة القفل",
|
||||
"text_size": "حجم الخط نص أكبر تكبير",
|
||||
"vacation": "رد تلقائي خارج المكتب غائب إجازة عطلة autoreply",
|
||||
"two_factor": "2FA ثنائي العوامل خطوتين MFA OTP المصادقة رمز لمرة واحدة",
|
||||
"encryption": "S/MIME smime PGP OpenPGP تشفير شهادة",
|
||||
"password": "كلمة المرور تغيير كلمة المرور كلمة مرور جديدة",
|
||||
"offline": "دون اتصال offline بلا إنترنت تنزيل تخزين مزامنة",
|
||||
"wipe": "إزالة حذف مسح الجهاز البيانات",
|
||||
"outbox": "صندوق الصادر غير مرسل في الانتظار قائمة",
|
||||
"remote_content": "صور بعيدة محتوى خارجي تتبع بكسلات مرسلون موثوقون",
|
||||
"push": "push مرحل الخلفية إشعارات",
|
||||
"version": "إصدار تحديث حول الشيفرة المصدرية GitHub ترخيص"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "الإشعارات",
|
||||
"push_link": {
|
||||
"label": "مرحّل الإشعارات",
|
||||
"description": "تمر الإشعارات أثناء إغلاق التطبيق عبر مرحّل دفع لا يرى بريدك أبدًا. تختاره في صفحة الإشعارات.",
|
||||
"button": "الإشعارات"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "الصور البعيدة والمرسلون الموثوقون",
|
||||
"receipts_label": "إيصالات القراءة",
|
||||
"senders_label": "صور المرسلين",
|
||||
"description": "هذه الإعدادات موجودة في صفحة الخصوصية.",
|
||||
"button": "الخصوصية"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "الكثافة وتسميات الأزرار",
|
||||
"description": "تضبط تقارب صفوف القائمة وإظهار أسماء الأزرار في صفحة المظهر."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "خلفية فاتحة للرسائل",
|
||||
"description": "تضبط عرض الرسائل على خلفية فاتحة في الوضع الداكن في صفحة القراءة."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "معاينة الأحداث في التقويم",
|
||||
"description": "تضبط إظهار تفاصيل الحدث عند الإشارة إليه في صفحة التقويم."
|
||||
},
|
||||
"list": {
|
||||
"label": "المحادثات وأسطر المعاينة",
|
||||
"description": "تضبط التجميع حسب المحادثة وسطر المعاينة تحت كل رسالة في صفحة قائمة الرسائل."
|
||||
},
|
||||
"reading": {
|
||||
"label": "التعليم كمقروء",
|
||||
"description": "تضبط متى تُعد الرسالة مقروءة وإلى أين تنتقل بعد حذفها أو نقلها في صفحة القراءة."
|
||||
},
|
||||
"send_warnings_description": "التراجع عن الإرسال والتحذيرات من موضوع فارغ أو مرفق منسي أو نص مؤقت.",
|
||||
"bulk_delete": {
|
||||
"label": "السؤال قبل حذف عدة رسائل",
|
||||
"description": "يُضبط هذا في صفحة قائمة الرسائل."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "الأصوات وعداد غير المقروء",
|
||||
"description": "تضبط أصوات الإشعارات وعدد غير المقروء في علامة التبويب في صفحة الإشعارات."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "حول {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "تسجيل الخروج من هذا الحساب على هذا الجهاز."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "إعداد Bulwark Webmail",
|
||||
"subtitle": "اضبط نسخة البريد الإلكتروني على الويب من المتصفح.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Fora de línia",
|
||||
"privacy": "Privadesa",
|
||||
"about": "Quant a"
|
||||
},
|
||||
"search": {
|
||||
"signature": "signatura signatura de correu peu de missatge",
|
||||
"sign_out": "tancar la sessió sortir desconnectar logout",
|
||||
"app_lock": "PIN bloqueig codi bloquejar pantalla",
|
||||
"text_size": "mida de la lletra text més gran zoom ampliar",
|
||||
"vacation": "resposta automàtica fora de l’oficina absent vacances autoreply",
|
||||
"two_factor": "2FA dos factors dos passos MFA OTP autenticador codi d’un sol ús",
|
||||
"encryption": "S/MIME smime PGP OpenPGP xifratge certificat",
|
||||
"password": "contrasenya canviar contrasenya nova contrasenya",
|
||||
"offline": "fora de línia sense connexió sense internet baixar emmagatzematge sincronitzar",
|
||||
"wipe": "treure esborrar eliminar dispositiu dades",
|
||||
"outbox": "safata de sortida no enviat en espera cua",
|
||||
"remote_content": "imatges remotes contingut extern seguiment píxels remitents de confiança",
|
||||
"push": "push repetidor segon pla notificacions",
|
||||
"version": "versió actualització quant a codi font GitHub llicència"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Notificacions",
|
||||
"push_link": {
|
||||
"label": "Repetidor de notificacions",
|
||||
"description": "Les notificacions amb l’aplicació tancada passen per un repetidor push, que mai no veu el vostre correu. El trieu a la pàgina Notificacions.",
|
||||
"button": "Notificacions"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Imatges remotes i remitents de confiança",
|
||||
"receipts_label": "Confirmacions de lectura",
|
||||
"senders_label": "Imatges dels remitents",
|
||||
"description": "Aquests paràmetres són a la pàgina Privadesa.",
|
||||
"button": "Privadesa"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Densitat i etiquetes de la barra",
|
||||
"description": "La separació de les files de la llista i si els botons mostren el nom es configuren a la pàgina Aparença."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Fons clar per als missatges",
|
||||
"description": "Mostrar els missatges amb fons clar en mode fosc es configura a la pàgina Lectura."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Previsualització d’esdeveniments al calendari",
|
||||
"description": "Si en assenyalar un esdeveniment se’n mostren els detalls es configura a la pàgina Calendari."
|
||||
},
|
||||
"list": {
|
||||
"label": "Converses i línies de previsualització",
|
||||
"description": "L’agrupació per conversa i la línia de previsualització de cada missatge es configuren a la pàgina Llista de missatges."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Marcar com a llegit",
|
||||
"description": "Quan un missatge compta com a llegit, i on aneu després d’esborrar-lo o moure’l, es configura a la pàgina Lectura."
|
||||
},
|
||||
"send_warnings_description": "Desfer l’enviament i els avisos d’assumpte buit, adjunt oblidat o text provisional.",
|
||||
"bulk_delete": {
|
||||
"label": "Preguntar abans d’esborrar diversos missatges",
|
||||
"description": "Això es configura a la pàgina Llista de missatges."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Sons i comptador de no llegits",
|
||||
"description": "Els sons de notificació i el nombre de no llegits a la pestanya es configuren a la pàgina Notificacions."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Quant a {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Tanca la sessió d’aquest compte en aquest dispositiu."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configuració de Bulwark Webmail",
|
||||
"subtitle": "Configureu la vostra instància de correu web des del navegador.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Soukromí",
|
||||
"about": "O aplikaci"
|
||||
},
|
||||
"search": {
|
||||
"signature": "podpis e-mailový podpis zápatí",
|
||||
"sign_out": "odhlásit odhlášení logout",
|
||||
"app_lock": "PIN zámek kód uzamknout obrazovka",
|
||||
"text_size": "velikost písma větší text zoom zvětšit",
|
||||
"vacation": "automatická odpověď mimo kancelář nepřítomnost dovolená autoreply",
|
||||
"two_factor": "2FA dvoufázové dvoufaktorové MFA OTP autentikátor jednorázový kód",
|
||||
"encryption": "S/MIME smime PGP OpenPGP šifrování certifikát",
|
||||
"password": "heslo změnit heslo nové heslo",
|
||||
"offline": "offline bez připojení bez internetu stáhnout úložiště synchronizace",
|
||||
"wipe": "odebrat smazat vymazat zařízení data",
|
||||
"outbox": "k odeslání neodeslané čekající fronta",
|
||||
"remote_content": "vzdálené obrázky externí obsah sledování pixely důvěryhodní odesílatelé",
|
||||
"push": "push relay na pozadí oznámení",
|
||||
"version": "verze aktualizace o aplikaci zdrojový kód GitHub licence"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Oznámení",
|
||||
"push_link": {
|
||||
"label": "Relay pro oznámení",
|
||||
"description": "Oznámení při zavřené aplikaci jdou přes push relay, který nikdy nevidí vaši poštu. Vyberete ho na stránce Oznámení.",
|
||||
"button": "Oznámení"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Vzdálené obrázky a důvěryhodní odesílatelé",
|
||||
"receipts_label": "Potvrzení o přečtení",
|
||||
"senders_label": "Obrázky odesílatelů",
|
||||
"description": "Tato nastavení najdete na stránce Soukromí.",
|
||||
"button": "Soukromí"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Hustota a popisky panelu",
|
||||
"description": "Jak těsně jsou řádky seznamu a zda tlačítka ukazují svůj název, nastavíte na stránce Vzhled."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Světlé pozadí zpráv",
|
||||
"description": "Zobrazení zpráv na světlém pozadí v tmavém režimu nastavíte na stránce Čtení."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Náhled událostí v kalendáři",
|
||||
"description": "Zda najetí na událost ukáže její podrobnosti, nastavíte na stránce Kalendář."
|
||||
},
|
||||
"list": {
|
||||
"label": "Konverzace a řádky náhledu",
|
||||
"description": "Seskupení podle konverzace a řádek náhledu pod každou zprávou nastavíte na stránce Seznam zpráv."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Označení jako přečtené",
|
||||
"description": "Kdy se zpráva počítá jako přečtená a kam se dostanete po smazání nebo přesunu, nastavíte na stránce Čtení."
|
||||
},
|
||||
"send_warnings_description": "Vrácení odeslání a upozornění na chybějící předmět, zapomenutou přílohu nebo zástupný text.",
|
||||
"bulk_delete": {
|
||||
"label": "Dotaz před smazáním více zpráv",
|
||||
"description": "To nastavíte na stránce Seznam zpráv."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Zvuky a počet nepřečtených",
|
||||
"description": "Zvuky oznámení a počet nepřečtených na kartě nastavíte na stránce Oznámení."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "O aplikaci {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Odhlásit tento účet na tomto zařízení."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Nastavení Bulwark Webmail",
|
||||
"subtitle": "Nastavte svou instanci webmailu v prohlížeči.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Privatliv",
|
||||
"about": "Om"
|
||||
},
|
||||
"search": {
|
||||
"signature": "signatur e-mailsignatur sidefod",
|
||||
"sign_out": "log ud logout afmeld",
|
||||
"app_lock": "PIN lås kode låseskærm",
|
||||
"text_size": "skriftstørrelse større tekst zoom forstør",
|
||||
"vacation": "autosvar automatisk svar ikke på kontoret fraværende ferie autoreply",
|
||||
"two_factor": "2FA totrins to-faktor MFA OTP godkendelsesapp engangskode",
|
||||
"encryption": "S/MIME smime PGP OpenPGP kryptering certifikat",
|
||||
"password": "adgangskode skift adgangskode ny adgangskode",
|
||||
"offline": "offline uden forbindelse uden internet hent lager synkroniser",
|
||||
"wipe": "fjern slet ryd enhed data",
|
||||
"outbox": "udbakke ikke sendt venter kø",
|
||||
"remote_content": "eksterne billeder eksternt indhold sporing pixels betroede afsendere",
|
||||
"push": "push relæ baggrund notifikationer",
|
||||
"version": "version opdatering om kildekode GitHub licens"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Notifikationer",
|
||||
"push_link": {
|
||||
"label": "Relæ til notifikationer",
|
||||
"description": "Notifikationer mens appen er lukket går gennem et push-relæ, som aldrig ser din mail. Du vælger det på siden Notifikationer.",
|
||||
"button": "Notifikationer"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Eksterne billeder og betroede afsendere",
|
||||
"receipts_label": "Læsebekræftelser",
|
||||
"senders_label": "Afsenderbilleder",
|
||||
"description": "Disse indstillinger findes på siden Privatliv.",
|
||||
"button": "Privatliv"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Tæthed og knapnavne",
|
||||
"description": "Hvor tæt listens rækker står, og om knapperne viser deres navn, indstilles på siden Udseende."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Lys baggrund til beskeder",
|
||||
"description": "At vise beskeder på lys baggrund i mørk tilstand indstilles på siden Læsning."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Forhåndsvisning af begivenheder i kalenderen",
|
||||
"description": "Om det viser detaljer at pege på en begivenhed, indstilles på siden Kalender."
|
||||
},
|
||||
"list": {
|
||||
"label": "Samtaler og eksempellinjer",
|
||||
"description": "Gruppering efter samtale og eksempellinjen under hver besked indstilles på siden Beskedliste."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Markér som læst",
|
||||
"description": "Hvornår en besked tæller som læst, og hvor du kommer hen efter at have slettet eller flyttet den, indstilles på siden Læsning."
|
||||
},
|
||||
"send_warnings_description": "Fortryd afsendelse og advarslerne om manglende emne, glemt vedhæftning eller pladsholdertekst.",
|
||||
"bulk_delete": {
|
||||
"label": "Spørg før sletning af flere beskeder",
|
||||
"description": "Det indstilles på siden Beskedliste."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Lyde og tæller for ulæste",
|
||||
"description": "Notifikationslyde og antallet af ulæste i fanen indstilles på siden Notifikationer."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Om {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Log ud af denne konto på denne enhed."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Opsætning af Bulwark Webmail",
|
||||
"subtitle": "Opsæt din webmailinstans i browseren.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Datenschutz",
|
||||
"about": "Über"
|
||||
},
|
||||
"search": {
|
||||
"signature": "Signatur E-Mail-Signatur Grußformel Fußzeile signature",
|
||||
"sign_out": "abmelden ausloggen Abmeldung logout sign out",
|
||||
"app_lock": "PIN Sperre Code Sperrbildschirm sperren",
|
||||
"text_size": "Schriftgröße größere Schrift Zoom vergrößern",
|
||||
"vacation": "Abwesenheit Abwesenheitsnotiz automatische Antwort Urlaub abwesend Autoreply out of office",
|
||||
"two_factor": "2FA Zwei-Faktor zweistufig MFA OTP Authenticator Einmalcode",
|
||||
"encryption": "S/MIME smime PGP OpenPGP Verschlüsselung Zertifikat",
|
||||
"password": "Passwort Kennwort ändern neues Passwort",
|
||||
"offline": "offline ohne Verbindung ohne Internet Mails herunterladen Speicher synchronisieren",
|
||||
"wipe": "entfernen löschen Gerät Daten zurücksetzen",
|
||||
"outbox": "Postausgang ungesendet wartend Warteschlange",
|
||||
"remote_content": "externe Bilder externe Inhalte Tracking Zählpixel vertrauenswürdige Absender",
|
||||
"push": "Push Relay Hintergrund Benachrichtigungen",
|
||||
"version": "Version Update Info Quellcode GitHub Lizenz"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Benachrichtigungen",
|
||||
"push_link": {
|
||||
"label": "Relay für Benachrichtigungen",
|
||||
"description": "Benachrichtigungen bei geschlossener App laufen über ein Push-Relay, das Ihre E-Mails nie sieht. Sie wählen es auf der Seite Benachrichtigungen.",
|
||||
"button": "Benachrichtigungen"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Externe Bilder und vertrauenswürdige Absender",
|
||||
"receipts_label": "Lesebestätigungen",
|
||||
"senders_label": "Absenderbilder",
|
||||
"description": "Diese Einstellungen finden Sie auf der Seite Datenschutz.",
|
||||
"button": "Datenschutz"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Dichte und Beschriftung der Symbolleiste",
|
||||
"description": "Wie eng die Zeilen der Liste stehen und ob Schaltflächen ihren Namen zeigen, stellen Sie auf der Seite Darstellung ein."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Heller Hintergrund für E-Mails",
|
||||
"description": "Ob E-Mails im dunklen Modus auf hellem Hintergrund erscheinen, stellen Sie auf der Seite Lesen ein."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Terminvorschau im Kalender",
|
||||
"description": "Ob beim Zeigen auf einen Termin seine Details erscheinen, stellen Sie auf der Seite Kalender ein."
|
||||
},
|
||||
"list": {
|
||||
"label": "Unterhaltungen und Vorschauzeilen",
|
||||
"description": "Die Gruppierung nach Unterhaltung und die Vorschauzeile unter jeder E-Mail stellen Sie auf der Seite Nachrichtenliste ein."
|
||||
},
|
||||
"reading": {
|
||||
"label": "E-Mails als gelesen markieren",
|
||||
"description": "Wann eine E-Mail als gelesen gilt und wohin Sie nach dem Löschen oder Verschieben kommen, stellen Sie auf der Seite Lesen ein."
|
||||
},
|
||||
"send_warnings_description": "Senden rückgängig machen und die Warnungen vor fehlendem Betreff, vergessenem Anhang oder Platzhaltertext.",
|
||||
"bulk_delete": {
|
||||
"label": "Nachfragen vor dem Löschen mehrerer E-Mails",
|
||||
"description": "Das stellen Sie auf der Seite Nachrichtenliste ein."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Töne und Zähler für Ungelesenes",
|
||||
"description": "Benachrichtigungstöne und die Zahl ungelesener E-Mails im Tab stellen Sie auf der Seite Benachrichtigungen ein."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Über {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Von diesem Konto auf diesem Gerät abmelden."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Einrichtung von Bulwark Webmail",
|
||||
"subtitle": "Richten Sie Ihre Webmail-Instanz im Browser ein.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Privacy",
|
||||
"about": "About"
|
||||
},
|
||||
"search": {
|
||||
"signature": "signature email signature sign-off footer",
|
||||
"sign_out": "sign out log out logout log off sign off",
|
||||
"app_lock": "PIN lock code passcode lock screen",
|
||||
"text_size": "font size bigger text larger text zoom",
|
||||
"vacation": "autoreply auto-reply automatic reply out of office away vacation holiday",
|
||||
"two_factor": "2FA two-factor two-step MFA OTP authenticator one-time code",
|
||||
"encryption": "S/MIME smime PGP OpenPGP encryption certificate",
|
||||
"password": "password change password new password",
|
||||
"offline": "offline without connection no internet download mail storage sync",
|
||||
"wipe": "remove erase wipe device data",
|
||||
"outbox": "outbox unsent waiting queued",
|
||||
"remote_content": "remote images external content tracking pixels trusted senders",
|
||||
"push": "push relay background notifications",
|
||||
"version": "version update about source code GitHub license"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Notifications",
|
||||
"push_link": {
|
||||
"label": "Relay for notifications",
|
||||
"description": "Notifications while the app is closed go through a push relay, which never sees your mail. You choose it on the Notifications page.",
|
||||
"button": "Notifications"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Remote images and trusted senders",
|
||||
"receipts_label": "Read receipts",
|
||||
"senders_label": "Sender pictures",
|
||||
"description": "These settings are on the Privacy page.",
|
||||
"button": "Privacy"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Density and toolbar labels",
|
||||
"description": "How close together list rows are, and whether toolbar buttons show their names, is set on the Appearance page."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Light background for messages",
|
||||
"description": "Showing messages on a light background in dark mode is set on the Reading page."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Event previews in the calendar",
|
||||
"description": "Whether pointing at an event shows its details is set on the Calendar page."
|
||||
},
|
||||
"list": {
|
||||
"label": "Conversations and preview lines",
|
||||
"description": "Grouping by conversation and the preview line under each message are set on the Message list page."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Marking mail as read",
|
||||
"description": "When a message counts as read, and where you go after deleting or moving it, is set on the Reading page."
|
||||
},
|
||||
"send_warnings_description": "Undo send, and the warnings about a missing subject, a forgotten attachment or placeholder text.",
|
||||
"bulk_delete": {
|
||||
"label": "Asking before deleting several messages",
|
||||
"description": "This is set on the Message list page."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Sounds and the unread badge",
|
||||
"description": "Notification sounds and the unread count on the tab are set on the Notifications page."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "About {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Sign out of this account on this device."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Bulwark Webmail setup",
|
||||
"subtitle": "Configure your webmail instance from the browser.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Sin conexión",
|
||||
"privacy": "Privacidad",
|
||||
"about": "Acerca de"
|
||||
},
|
||||
"search": {
|
||||
"signature": "firma firma de correo pie de mensaje",
|
||||
"sign_out": "cerrar sesión salir desconectar logout",
|
||||
"app_lock": "PIN bloqueo código bloquear pantalla",
|
||||
"text_size": "tamaño de letra texto más grande zoom ampliar",
|
||||
"vacation": "respuesta automática fuera de la oficina ausente vacaciones autoreply",
|
||||
"two_factor": "2FA dos factores dos pasos MFA OTP autenticador código de un solo uso",
|
||||
"encryption": "S/MIME smime PGP OpenPGP cifrado certificado",
|
||||
"password": "contraseña cambiar contraseña nueva contraseña",
|
||||
"offline": "sin conexión offline sin internet descargar almacenamiento sincronizar",
|
||||
"wipe": "quitar borrar eliminar dispositivo datos",
|
||||
"outbox": "bandeja de salida sin enviar en espera cola",
|
||||
"remote_content": "imágenes remotas contenido externo rastreo píxeles remitentes de confianza",
|
||||
"push": "push relé segundo plano notificaciones",
|
||||
"version": "versión actualización acerca de código fuente GitHub licencia"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Notificaciones",
|
||||
"push_link": {
|
||||
"label": "Relé de notificaciones",
|
||||
"description": "Las notificaciones con la aplicación cerrada pasan por un relé push, que nunca ve tu correo. Lo eliges en la página Notificaciones.",
|
||||
"button": "Notificaciones"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Imágenes remotas y remitentes de confianza",
|
||||
"receipts_label": "Confirmaciones de lectura",
|
||||
"senders_label": "Imágenes de remitentes",
|
||||
"description": "Estos ajustes están en la página Privacidad.",
|
||||
"button": "Privacidad"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Densidad y etiquetas de la barra",
|
||||
"description": "La separación de las filas de la lista y si los botones muestran su nombre se ajustan en la página Apariencia."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Fondo claro para los mensajes",
|
||||
"description": "Mostrar los mensajes con fondo claro en modo oscuro se ajusta en la página Lectura."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Vista previa de eventos en el calendario",
|
||||
"description": "Si al señalar un evento se muestran sus detalles se ajusta en la página Calendario."
|
||||
},
|
||||
"list": {
|
||||
"label": "Conversaciones y líneas de vista previa",
|
||||
"description": "La agrupación por conversación y la línea de vista previa de cada mensaje se ajustan en la página Lista de mensajes."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Marcar como leído",
|
||||
"description": "Cuándo un mensaje cuenta como leído, y adónde vas tras borrarlo o moverlo, se ajusta en la página Lectura."
|
||||
},
|
||||
"send_warnings_description": "Deshacer envío y los avisos de asunto vacío, adjunto olvidado o texto de relleno.",
|
||||
"bulk_delete": {
|
||||
"label": "Preguntar antes de borrar varios mensajes",
|
||||
"description": "Esto se ajusta en la página Lista de mensajes."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Sonidos y contador de no leídos",
|
||||
"description": "Los sonidos de notificación y el número de no leídos en la pestaña se ajustan en la página Notificaciones."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Acerca de {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Cerrar sesión de esta cuenta en este dispositivo."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configuración de Bulwark Webmail",
|
||||
"subtitle": "Configura tu instancia de webmail desde el navegador.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Võrguühenduseta",
|
||||
"privacy": "Privaatsus",
|
||||
"about": "Teave"
|
||||
},
|
||||
"search": {
|
||||
"signature": "allkiri e-posti allkiri jalus",
|
||||
"sign_out": "logi välja väljalogimine logout",
|
||||
"app_lock": "PIN lukk kood lukustuskuva",
|
||||
"text_size": "fondi suurus suurem tekst suum suurendus",
|
||||
"vacation": "automaatvastus kontorist väljas eemal puhkus autoreply",
|
||||
"two_factor": "2FA kaheastmeline kahefaktoriline MFA OTP autentija ühekordne kood",
|
||||
"encryption": "S/MIME smime PGP OpenPGP krüpteerimine sertifikaat",
|
||||
"password": "parool muuda parooli uus parool",
|
||||
"offline": "võrguühenduseta offline ilma ühenduseta ilma internetita laadi alla salvestus sünkroonimine",
|
||||
"wipe": "eemalda kustuta tühjenda seade andmed",
|
||||
"outbox": "väljuvad saatmata ootel järjekord",
|
||||
"remote_content": "kaugpildid väline sisu jälgimine pikslid usaldusväärsed saatjad",
|
||||
"push": "push relee taustal teavitused",
|
||||
"version": "versioon uuendus teave lähtekood GitHub litsents"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Teavitused",
|
||||
"push_link": {
|
||||
"label": "Teavituste relee",
|
||||
"description": "Teavitused suletud rakenduse ajal käivad läbi push-relee, mis ei näe kunagi sinu kirju. Valid selle lehel Teavitused.",
|
||||
"button": "Teavitused"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Kaugpildid ja usaldusväärsed saatjad",
|
||||
"receipts_label": "Lugemiskinnitused",
|
||||
"senders_label": "Saatjate pildid",
|
||||
"description": "Need seaded on lehel Privaatsus.",
|
||||
"button": "Privaatsus"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Tihedus ja nuppude nimed",
|
||||
"description": "Kui tihedalt loendi read on ja kas nupud näitavad oma nime, seadistad lehel Välimus."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Hele taust kirjadele",
|
||||
"description": "Kirjade näitamise heledal taustal tumedas režiimis seadistad lehel Lugemine."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Sündmuste eelvaade kalendris",
|
||||
"description": "Kas sündmusele osutamine näitab üksikasju, seadistad lehel Kalender."
|
||||
},
|
||||
"list": {
|
||||
"label": "Vestlused ja eelvaate read",
|
||||
"description": "Vestluste kaupa rühmitamise ja iga kirja all oleva eelvaate rea seadistad lehel Kirjade loend."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Loetuks märkimine",
|
||||
"description": "Millal kiri loetakse loetuks ja kuhu jõuad pärast kustutamist või teisaldamist, seadistad lehel Lugemine."
|
||||
},
|
||||
"send_warnings_description": "Saatmise tagasivõtmine ja hoiatused puuduva teema, unustatud manuse või kohatäiteteksti kohta.",
|
||||
"bulk_delete": {
|
||||
"label": "Küsi enne mitme kirja kustutamist",
|
||||
"description": "Selle seadistad lehel Kirjade loend."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Helid ja lugemata kirjade arv",
|
||||
"description": "Teavitushelid ja lugemata kirjade arvu vahekaardil seadistad lehel Teavitused."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Teave: {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Logi sellest kontost selles seadmes välja."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Bulwark Webmaili seadistamine",
|
||||
"subtitle": "Seadista oma veebipostiteenus brauseris.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "آفلاین",
|
||||
"privacy": "حریم خصوصی",
|
||||
"about": "درباره"
|
||||
},
|
||||
"search": {
|
||||
"signature": "امضا امضای ایمیل پانویس",
|
||||
"sign_out": "خروج خروج از حساب logout",
|
||||
"app_lock": "PIN قفل کد صفحه قفل",
|
||||
"text_size": "اندازه قلم متن بزرگتر بزرگنمایی زوم",
|
||||
"vacation": "پاسخ خودکار خارج از دفتر غایب مرخصی تعطیلات autoreply",
|
||||
"two_factor": "2FA دومرحلهای دوعاملی MFA OTP احراز هویت کد یکبارمصرف",
|
||||
"encryption": "S/MIME smime PGP OpenPGP رمزگذاری گواهی",
|
||||
"password": "گذرواژه رمز عبور تغییر گذرواژه گذرواژه جدید",
|
||||
"offline": "آفلاین offline بدون اتصال بدون اینترنت دانلود فضای ذخیره همگامسازی",
|
||||
"wipe": "حذف پاک کردن دستگاه دادهها",
|
||||
"outbox": "صندوق خروجی ارسالنشده در انتظار صف",
|
||||
"remote_content": "تصاویر راه دور محتوای خارجی ردیابی پیکسل فرستندگان مورد اعتماد",
|
||||
"push": "push رله پسزمینه اعلانها",
|
||||
"version": "نسخه بهروزرسانی درباره کد منبع GitHub مجوز"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "اعلانها",
|
||||
"push_link": {
|
||||
"label": "رله اعلانها",
|
||||
"description": "اعلانها وقتی برنامه بسته است از یک رله پوش میگذرند که هرگز ایمیل شما را نمیبیند. آن را در صفحه اعلانها انتخاب میکنید.",
|
||||
"button": "اعلانها"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "تصاویر راه دور و فرستندگان مورد اعتماد",
|
||||
"receipts_label": "رسید خواندن",
|
||||
"senders_label": "تصاویر فرستندگان",
|
||||
"description": "این تنظیمات در صفحه حریم خصوصی هستند.",
|
||||
"button": "حریم خصوصی"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "تراکم و برچسب دکمهها",
|
||||
"description": "فاصله ردیفهای فهرست و نمایش نام دکمهها در صفحه ظاهر تنظیم میشود."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "پسزمینه روشن برای پیامها",
|
||||
"description": "نمایش پیامها روی پسزمینه روشن در حالت تیره در صفحه خواندن تنظیم میشود."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "پیشنمایش رویدادها در تقویم",
|
||||
"description": "نمایش جزئیات با اشاره به یک رویداد در صفحه تقویم تنظیم میشود."
|
||||
},
|
||||
"list": {
|
||||
"label": "گفتگوها و خطوط پیشنمایش",
|
||||
"description": "گروهبندی بر اساس گفتگو و خط پیشنمایش زیر هر پیام در صفحه فهرست پیامها تنظیم میشود."
|
||||
},
|
||||
"reading": {
|
||||
"label": "علامتگذاری بهعنوان خواندهشده",
|
||||
"description": "اینکه پیام کی خواندهشده حساب شود و پس از حذف یا انتقال به کجا بروید، در صفحه خواندن تنظیم میشود."
|
||||
},
|
||||
"send_warnings_description": "لغو ارسال و هشدارها درباره موضوع خالی، پیوست فراموششده یا متن جایگزین.",
|
||||
"bulk_delete": {
|
||||
"label": "پرسیدن پیش از حذف چند پیام",
|
||||
"description": "این در صفحه فهرست پیامها تنظیم میشود."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "صداها و شمارنده خواندهنشده",
|
||||
"description": "صداهای اعلان و تعداد خواندهنشدهها روی زبانه در صفحه اعلانها تنظیم میشود."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "درباره {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "خروج از این حساب در این دستگاه."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "راهاندازی Bulwark Webmail",
|
||||
"subtitle": "نمونهٔ وبمیل خود را از مرورگر پیکربندی کنید.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Hors ligne",
|
||||
"privacy": "Confidentialité",
|
||||
"about": "À propos"
|
||||
},
|
||||
"search": {
|
||||
"signature": "signature signature e-mail pied de message",
|
||||
"sign_out": "déconnexion se déconnecter quitter logout",
|
||||
"app_lock": "PIN verrou code verrouillage écran",
|
||||
"text_size": "taille de police texte plus grand zoom agrandir",
|
||||
"vacation": "réponse automatique absence congés vacances absent autoreply",
|
||||
"two_factor": "2FA double authentification deux facteurs MFA OTP authentificateur code unique",
|
||||
"encryption": "S/MIME smime PGP OpenPGP chiffrement certificat",
|
||||
"password": "mot de passe changer nouveau mot de passe",
|
||||
"offline": "hors ligne sans connexion sans internet télécharger stockage synchronisation",
|
||||
"wipe": "supprimer effacer appareil données",
|
||||
"outbox": "boîte d’envoi non envoyé en attente file",
|
||||
"remote_content": "images distantes contenu externe pistage pixels expéditeurs de confiance",
|
||||
"push": "push relais arrière-plan notifications",
|
||||
"version": "version mise à jour à propos code source GitHub licence"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Notifications",
|
||||
"push_link": {
|
||||
"label": "Relais des notifications",
|
||||
"description": "Les notifications reçues quand l’application est fermée passent par un relais push, qui ne voit jamais vos e-mails. Vous le choisissez sur la page Notifications.",
|
||||
"button": "Notifications"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Images distantes et expéditeurs de confiance",
|
||||
"receipts_label": "Accusés de lecture",
|
||||
"senders_label": "Images des expéditeurs",
|
||||
"description": "Ces réglages se trouvent sur la page Confidentialité.",
|
||||
"button": "Confidentialité"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Densité et libellés de la barre d’outils",
|
||||
"description": "L’espacement des lignes de la liste et l’affichage du nom des boutons se règlent sur la page Apparence."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Fond clair pour les messages",
|
||||
"description": "L’affichage des messages sur fond clair en mode sombre se règle sur la page Lecture."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Aperçu des événements dans le calendrier",
|
||||
"description": "L’affichage des détails en pointant un événement se règle sur la page Calendrier."
|
||||
},
|
||||
"list": {
|
||||
"label": "Conversations et lignes d’aperçu",
|
||||
"description": "Le regroupement par conversation et la ligne d’aperçu sous chaque message se règlent sur la page Liste des messages."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Marquer comme lu",
|
||||
"description": "Le moment où un message compte comme lu, et la page affichée après une suppression ou un déplacement, se règlent sur la page Lecture."
|
||||
},
|
||||
"send_warnings_description": "Annulation de l’envoi, et les avertissements pour un objet manquant, une pièce jointe oubliée ou un texte provisoire.",
|
||||
"bulk_delete": {
|
||||
"label": "Confirmer avant de supprimer plusieurs messages",
|
||||
"description": "Ce réglage se trouve sur la page Liste des messages."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Sons et badge des non lus",
|
||||
"description": "Les sons de notification et le nombre de non lus dans l’onglet se règlent sur la page Notifications."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "À propos de {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Se déconnecter de ce compte sur cet appareil."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configuration de Bulwark Webmail",
|
||||
"subtitle": "Configurez votre instance de webmail depuis le navigateur.",
|
||||
|
||||
@@ -3311,6 +3311,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "לא מקוון",
|
||||
"privacy": "פרטיות",
|
||||
"about": "אודות"
|
||||
},
|
||||
"search": {
|
||||
"signature": "חתימה חתימת דואר כותרת תחתונה",
|
||||
"sign_out": "התנתקות יציאה logout",
|
||||
"app_lock": "PIN נעילה קוד מסך נעילה",
|
||||
"text_size": "גודל גופן טקסט גדול יותר זום הגדלה",
|
||||
"vacation": "מענה אוטומטי מחוץ למשרד לא זמין חופשה autoreply",
|
||||
"two_factor": "2FA דו־שלבי דו־גורמי MFA OTP מאמת קוד חד־פעמי",
|
||||
"encryption": "S/MIME smime PGP OpenPGP הצפנה תעודה",
|
||||
"password": "סיסמה שינוי סיסמה סיסמה חדשה",
|
||||
"offline": "לא מקוון offline ללא חיבור ללא אינטרנט הורדה אחסון סנכרון",
|
||||
"wipe": "הסרה מחיקה ניקוי מכשיר נתונים",
|
||||
"outbox": "דואר יוצא לא נשלח ממתין תור",
|
||||
"remote_content": "תמונות מרוחקות תוכן חיצוני מעקב פיקסלים שולחים מהימנים",
|
||||
"push": "push ממסר רקע התראות",
|
||||
"version": "גרסה עדכון אודות קוד מקור GitHub רישיון"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "התראות",
|
||||
"push_link": {
|
||||
"label": "ממסר להתראות",
|
||||
"description": "התראות כשהאפליקציה סגורה עוברות דרך ממסר push שלעולם אינו רואה את הדואר שלך. בוחרים אותו בדף התראות.",
|
||||
"button": "התראות"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "תמונות מרוחקות ושולחים מהימנים",
|
||||
"receipts_label": "אישורי קריאה",
|
||||
"senders_label": "תמונות השולחים",
|
||||
"description": "ההגדרות האלה נמצאות בדף פרטיות.",
|
||||
"button": "פרטיות"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "צפיפות ותוויות כפתורים",
|
||||
"description": "עד כמה צפופות שורות הרשימה והאם הכפתורים מציגים את שמם נקבע בדף מראה."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "רקע בהיר להודעות",
|
||||
"description": "הצגת הודעות על רקע בהיר במצב כהה נקבעת בדף קריאה."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "תצוגה מקדימה של אירועים בלוח השנה",
|
||||
"description": "האם הצבעה על אירוע מציגה את פרטיו נקבע בדף לוח שנה."
|
||||
},
|
||||
"list": {
|
||||
"label": "שיחות ושורות תצוגה מקדימה",
|
||||
"description": "קיבוץ לפי שיחה ושורת התצוגה המקדימה מתחת לכל הודעה נקבעים בדף רשימת הודעות."
|
||||
},
|
||||
"reading": {
|
||||
"label": "סימון כנקרא",
|
||||
"description": "מתי הודעה נחשבת לנקראה ולאן עוברים אחרי מחיקה או העברה נקבע בדף קריאה."
|
||||
},
|
||||
"send_warnings_description": "ביטול שליחה והאזהרות על נושא חסר, קובץ מצורף שנשכח או טקסט ממלא מקום.",
|
||||
"bulk_delete": {
|
||||
"label": "לשאול לפני מחיקת כמה הודעות",
|
||||
"description": "זה נקבע בדף רשימת הודעות."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "צלילים ומונה הלא נקראו",
|
||||
"description": "צלילי ההתראות ומספר הלא נקראו בכרטיסייה נקבעים בדף התראות."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "אודות {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "התנתקות מהחשבון הזה במכשיר הזה."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "הגדרת Bulwark Webmail",
|
||||
"subtitle": "הגדירו את מופע דואר הרשת שלכם מהדפדפן.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Adatvédelem",
|
||||
"about": "Névjegy"
|
||||
},
|
||||
"search": {
|
||||
"signature": "aláírás e-mail aláírás lábléc",
|
||||
"sign_out": "kijelentkezés kilépés logout",
|
||||
"app_lock": "PIN zár kód zárolás képernyő",
|
||||
"text_size": "betűméret nagyobb szöveg nagyítás zoom",
|
||||
"vacation": "automatikus válasz házon kívül távol szabadság autoreply",
|
||||
"two_factor": "2FA kétlépcsős kéttényezős MFA OTP hitelesítő egyszeri kód",
|
||||
"encryption": "S/MIME smime PGP OpenPGP titkosítás tanúsítvány",
|
||||
"password": "jelszó jelszócsere új jelszó",
|
||||
"offline": "offline kapcsolat nélkül internet nélkül letöltés tárhely szinkronizálás",
|
||||
"wipe": "eltávolítás törlés kiürítés eszköz adatok",
|
||||
"outbox": "kimenő elküldetlen várakozó sor",
|
||||
"remote_content": "távoli képek külső tartalom követés pixelek megbízható feladók",
|
||||
"push": "push továbbító háttér értesítések",
|
||||
"version": "verzió frissítés névjegy forráskód GitHub licenc"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Értesítések",
|
||||
"push_link": {
|
||||
"label": "Értesítési továbbító",
|
||||
"description": "A bezárt alkalmazás értesítései egy push továbbítón mennek át, amely soha nem látja a leveleit. Az Értesítések oldalon választhatja ki.",
|
||||
"button": "Értesítések"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Távoli képek és megbízható feladók",
|
||||
"receipts_label": "Olvasási visszaigazolások",
|
||||
"senders_label": "Feladók képei",
|
||||
"description": "Ezek a beállítások az Adatvédelem oldalon vannak.",
|
||||
"button": "Adatvédelem"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Sűrűség és gombfeliratok",
|
||||
"description": "A lista sorainak sűrűségét és azt, hogy a gombok mutatják-e a nevüket, a Megjelenés oldalon állíthatja be."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Világos háttér az üzeneteknek",
|
||||
"description": "Az üzenetek világos háttéren való megjelenítését sötét módban az Olvasás oldalon állíthatja be."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Események előnézete a naptárban",
|
||||
"description": "Azt, hogy egy eseményre mutatva megjelennek-e a részletei, a Naptár oldalon állíthatja be."
|
||||
},
|
||||
"list": {
|
||||
"label": "Beszélgetések és előnézeti sorok",
|
||||
"description": "A beszélgetések szerinti csoportosítást és az üzenetek alatti előnézeti sort az Üzenetlista oldalon állíthatja be."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Olvasottnak jelölés",
|
||||
"description": "Azt, hogy mikor számít olvasottnak egy üzenet, és hová kerül törlés vagy áthelyezés után, az Olvasás oldalon állíthatja be."
|
||||
},
|
||||
"send_warnings_description": "Küldés visszavonása és a figyelmeztetések hiányzó tárgy, elfelejtett melléklet vagy helykitöltő szöveg esetén.",
|
||||
"bulk_delete": {
|
||||
"label": "Kérdés több üzenet törlése előtt",
|
||||
"description": "Ezt az Üzenetlista oldalon állíthatja be."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Hangok és az olvasatlanok száma",
|
||||
"description": "Az értesítési hangokat és a lapon látható olvasatlan számot az Értesítések oldalon állíthatja be."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Névjegy: {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Kijelentkezés ebből a fiókból ezen az eszközön."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "A Bulwark Webmail beállítása",
|
||||
"subtitle": "Állítsa be a webmail-példányát a böngészőből.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Privacy",
|
||||
"about": "Informazioni"
|
||||
},
|
||||
"search": {
|
||||
"signature": "firma firma email piè di pagina",
|
||||
"sign_out": "esci disconnetti logout uscita",
|
||||
"app_lock": "PIN blocco codice blocca schermo",
|
||||
"text_size": "dimensione carattere testo più grande zoom ingrandire",
|
||||
"vacation": "risposta automatica fuori sede assente ferie vacanza autoreply",
|
||||
"two_factor": "2FA due fattori due passaggi MFA OTP autenticatore codice monouso",
|
||||
"encryption": "S/MIME smime PGP OpenPGP crittografia certificato",
|
||||
"password": "password cambia password nuova password",
|
||||
"offline": "offline senza connessione senza internet scaricare archiviazione sincronizzare",
|
||||
"wipe": "rimuovi cancella elimina dispositivo dati",
|
||||
"outbox": "posta in uscita non inviati in attesa coda",
|
||||
"remote_content": "immagini remote contenuti esterni tracciamento pixel mittenti attendibili",
|
||||
"push": "push relay in background notifiche",
|
||||
"version": "versione aggiornamento informazioni codice sorgente GitHub licenza"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Notifiche",
|
||||
"push_link": {
|
||||
"label": "Relay per le notifiche",
|
||||
"description": "Le notifiche ad app chiusa passano da un relay push, che non vede mai la tua posta. Lo scegli nella pagina Notifiche.",
|
||||
"button": "Notifiche"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Immagini remote e mittenti attendibili",
|
||||
"receipts_label": "Conferme di lettura",
|
||||
"senders_label": "Immagini dei mittenti",
|
||||
"description": "Queste impostazioni sono nella pagina Privacy.",
|
||||
"button": "Privacy"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Densità ed etichette della barra",
|
||||
"description": "La distanza tra le righe dell’elenco e la presenza del nome sui pulsanti si impostano nella pagina Aspetto."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Sfondo chiaro per i messaggi",
|
||||
"description": "Mostrare i messaggi su sfondo chiaro in modalità scura si imposta nella pagina Lettura."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Anteprima degli eventi nel calendario",
|
||||
"description": "Se puntando un evento compaiono i dettagli si imposta nella pagina Calendario."
|
||||
},
|
||||
"list": {
|
||||
"label": "Conversazioni e righe di anteprima",
|
||||
"description": "Il raggruppamento per conversazione e la riga di anteprima sotto ogni messaggio si impostano nella pagina Elenco messaggi."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Segnare come letto",
|
||||
"description": "Quando un messaggio conta come letto, e dove vai dopo averlo eliminato o spostato, si imposta nella pagina Lettura."
|
||||
},
|
||||
"send_warnings_description": "Annulla invio e gli avvisi per oggetto mancante, allegato dimenticato o testo segnaposto.",
|
||||
"bulk_delete": {
|
||||
"label": "Chiedere prima di eliminare più messaggi",
|
||||
"description": "Si imposta nella pagina Elenco messaggi."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Suoni e contatore dei non letti",
|
||||
"description": "I suoni delle notifiche e il numero di non letti nella scheda si impostano nella pagina Notifiche."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Informazioni su {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Esci da questo account su questo dispositivo."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configurazione di Bulwark Webmail",
|
||||
"subtitle": "Configura la tua istanza webmail dal browser.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "オフライン",
|
||||
"privacy": "プライバシー",
|
||||
"about": "情報"
|
||||
},
|
||||
"search": {
|
||||
"signature": "署名 メール署名 フッター",
|
||||
"sign_out": "サインアウト ログアウト logout",
|
||||
"app_lock": "PIN ロック コード ロック画面",
|
||||
"text_size": "文字サイズ フォントサイズ 大きい文字 拡大 ズーム",
|
||||
"vacation": "自動返信 不在 休暇 外出中 autoreply",
|
||||
"two_factor": "2FA 二要素 二段階 MFA OTP 認証アプリ ワンタイムコード",
|
||||
"encryption": "S/MIME smime PGP OpenPGP 暗号化 証明書",
|
||||
"password": "パスワード パスワード変更 新しいパスワード",
|
||||
"offline": "オフライン offline 接続なし インターネットなし ダウンロード ストレージ 同期",
|
||||
"wipe": "削除 消去 デバイス データ",
|
||||
"outbox": "送信トレイ 未送信 送信待ち キュー",
|
||||
"remote_content": "外部画像 外部コンテンツ トラッキング ピクセル 信頼できる差出人",
|
||||
"push": "プッシュ push リレー バックグラウンド 通知",
|
||||
"version": "バージョン 更新 情報 ソースコード GitHub ライセンス"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "通知",
|
||||
"push_link": {
|
||||
"label": "通知のリレー",
|
||||
"description": "アプリを閉じているときの通知はプッシュリレーを通りますが、リレーがメールを見ることはありません。通知ページで選択します。",
|
||||
"button": "通知"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "外部画像と信頼できる差出人",
|
||||
"receipts_label": "開封確認",
|
||||
"senders_label": "差出人の画像",
|
||||
"description": "これらの設定はプライバシーページにあります。",
|
||||
"button": "プライバシー"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "表示密度とボタンのラベル",
|
||||
"description": "リストの行の間隔とボタンに名前を表示するかどうかは、外観ページで設定します。"
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "メッセージの明るい背景",
|
||||
"description": "ダークモードでメッセージを明るい背景で表示するかどうかは、閲覧ページで設定します。"
|
||||
},
|
||||
"calendar": {
|
||||
"label": "カレンダーの予定のプレビュー",
|
||||
"description": "予定にポインターを合わせたときに詳細を表示するかどうかは、カレンダーページで設定します。"
|
||||
},
|
||||
"list": {
|
||||
"label": "スレッドとプレビュー行",
|
||||
"description": "スレッドでまとめるかどうかと、各メッセージの下のプレビュー行は、メッセージ一覧ページで設定します。"
|
||||
},
|
||||
"reading": {
|
||||
"label": "既読にする",
|
||||
"description": "メッセージをいつ既読にするか、削除や移動の後にどこへ戻るかは、閲覧ページで設定します。"
|
||||
},
|
||||
"send_warnings_description": "送信の取り消しと、件名なし、添付忘れ、仮の文字列の警告。",
|
||||
"bulk_delete": {
|
||||
"label": "複数のメッセージを削除する前に確認",
|
||||
"description": "これはメッセージ一覧ページで設定します。"
|
||||
},
|
||||
"sounds": {
|
||||
"label": "サウンドと未読バッジ",
|
||||
"description": "通知音とタブの未読数は、通知ページで設定します。"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "{appName} について"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "このデバイスでこのアカウントからサインアウトします。"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Bulwark Webmail のセットアップ",
|
||||
"subtitle": "ブラウザーから Web メールのインスタンスを設定します。",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "오프라인",
|
||||
"privacy": "개인정보",
|
||||
"about": "정보"
|
||||
},
|
||||
"search": {
|
||||
"signature": "서명 이메일 서명 바닥글",
|
||||
"sign_out": "로그아웃 로그 아웃 logout 나가기",
|
||||
"app_lock": "PIN 잠금 코드 잠금 화면",
|
||||
"text_size": "글꼴 크기 큰 글자 확대 줌",
|
||||
"vacation": "자동 응답 부재중 휴가 외근 autoreply",
|
||||
"two_factor": "2FA 2단계 2요소 MFA OTP 인증 앱 일회용 코드",
|
||||
"encryption": "S/MIME smime PGP OpenPGP 암호화 인증서",
|
||||
"password": "비밀번호 암호 비밀번호 변경 새 비밀번호",
|
||||
"offline": "오프라인 offline 연결 없음 인터넷 없음 다운로드 저장 공간 동기화",
|
||||
"wipe": "제거 삭제 지우기 기기 데이터",
|
||||
"outbox": "보낼 편지함 보내지 않음 대기 중 대기열",
|
||||
"remote_content": "원격 이미지 외부 콘텐츠 추적 픽셀 신뢰하는 보낸 사람",
|
||||
"push": "푸시 push 릴레이 백그라운드 알림",
|
||||
"version": "버전 업데이트 정보 소스 코드 GitHub 라이선스"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "알림",
|
||||
"push_link": {
|
||||
"label": "알림 릴레이",
|
||||
"description": "앱이 닫혀 있을 때의 알림은 푸시 릴레이를 거치며, 릴레이는 메일을 절대 보지 않아요. 알림 페이지에서 선택해요.",
|
||||
"button": "알림"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "원격 이미지와 신뢰하는 보낸 사람",
|
||||
"receipts_label": "읽음 확인",
|
||||
"senders_label": "보낸 사람 사진",
|
||||
"description": "이 설정은 개인정보 페이지에 있어요.",
|
||||
"button": "개인정보"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "밀도와 버튼 이름",
|
||||
"description": "목록 줄 간격과 버튼에 이름을 표시할지는 모양 페이지에서 설정해요."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "메시지의 밝은 배경",
|
||||
"description": "다크 모드에서 메시지를 밝은 배경으로 보여줄지는 읽기 페이지에서 설정해요."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "캘린더의 일정 미리 보기",
|
||||
"description": "일정을 가리킬 때 자세한 내용을 보여줄지는 캘린더 페이지에서 설정해요."
|
||||
},
|
||||
"list": {
|
||||
"label": "대화와 미리 보기 줄",
|
||||
"description": "대화별 묶기와 각 메시지 아래의 미리 보기 줄은 메시지 목록 페이지에서 설정해요."
|
||||
},
|
||||
"reading": {
|
||||
"label": "읽음으로 표시",
|
||||
"description": "메시지를 언제 읽음으로 볼지, 삭제하거나 옮긴 뒤 어디로 갈지는 읽기 페이지에서 설정해요."
|
||||
},
|
||||
"send_warnings_description": "보내기 취소, 그리고 제목 없음, 첨부 파일 누락, 자리 표시 텍스트 경고.",
|
||||
"bulk_delete": {
|
||||
"label": "여러 메시지를 삭제하기 전에 묻기",
|
||||
"description": "메시지 목록 페이지에서 설정해요."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "소리와 읽지 않음 배지",
|
||||
"description": "알림 소리와 탭의 읽지 않은 개수는 알림 페이지에서 설정해요."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "{appName} 정보"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "이 기기에서 이 계정을 로그아웃해요."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Bulwark Webmail 설정",
|
||||
"subtitle": "브라우저에서 웹메일 인스턴스를 설정하세요.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Bezsaistē",
|
||||
"privacy": "Privātums",
|
||||
"about": "Par"
|
||||
},
|
||||
"search": {
|
||||
"signature": "paraksts e-pasta paraksts kājene",
|
||||
"sign_out": "izrakstīties iziet logout",
|
||||
"app_lock": "PIN slēdzene kods bloķēšanas ekrāns",
|
||||
"text_size": "fonta izmērs lielāks teksts tālummaiņa palielināt",
|
||||
"vacation": "automātiskā atbilde ārpus biroja prombūtne atvaļinājums autoreply",
|
||||
"two_factor": "2FA divpakāpju divfaktoru MFA OTP autentifikators vienreizējs kods",
|
||||
"encryption": "S/MIME smime PGP OpenPGP šifrēšana sertifikāts",
|
||||
"password": "parole mainīt paroli jauna parole",
|
||||
"offline": "bezsaistē offline bez savienojuma bez interneta lejupielādēt krātuve sinhronizēt",
|
||||
"wipe": "noņemt dzēst notīrīt ierīce dati",
|
||||
"outbox": "izsūtne nenosūtīts gaida rinda",
|
||||
"remote_content": "attāli attēli ārējs saturs izsekošana pikseļi uzticami sūtītāji",
|
||||
"push": "push retranslators fonā paziņojumi",
|
||||
"version": "versija atjauninājums par pirmkods GitHub licence"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Paziņojumi",
|
||||
"push_link": {
|
||||
"label": "Paziņojumu retranslators",
|
||||
"description": "Paziņojumi, kamēr lietotne ir aizvērta, iet caur push retranslatoru, kas nekad neredz jūsu pastu. To izvēlaties lapā Paziņojumi.",
|
||||
"button": "Paziņojumi"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Attāli attēli un uzticami sūtītāji",
|
||||
"receipts_label": "Izlasīšanas apstiprinājumi",
|
||||
"senders_label": "Sūtītāju attēli",
|
||||
"description": "Šie iestatījumi ir lapā Privātums.",
|
||||
"button": "Privātums"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Blīvums un pogu nosaukumi",
|
||||
"description": "Cik cieši ir saraksta rindas un vai pogas rāda nosaukumu, iestatāt lapā Izskats."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Gaišs fons vēstulēm",
|
||||
"description": "Vēstuļu rādīšanu uz gaiša fona tumšajā režīmā iestatāt lapā Lasīšana."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Notikumu priekšskatījums kalendārā",
|
||||
"description": "Vai, norādot uz notikumu, tiek rādīta informācija, iestatāt lapā Kalendārs."
|
||||
},
|
||||
"list": {
|
||||
"label": "Sarunas un priekšskatījuma rindas",
|
||||
"description": "Grupēšanu pa sarunām un priekšskatījuma rindu zem katras vēstules iestatāt lapā Vēstuļu saraksts."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Atzīmēt kā izlasītu",
|
||||
"description": "Kad vēstule skaitās izlasīta un kur nonākat pēc dzēšanas vai pārvietošanas, iestatāt lapā Lasīšana."
|
||||
},
|
||||
"send_warnings_description": "Sūtīšanas atsaukšana un brīdinājumi par trūkstošu tēmu, aizmirstu pielikumu vai vietturu tekstu.",
|
||||
"bulk_delete": {
|
||||
"label": "Jautāt pirms vairāku vēstuļu dzēšanas",
|
||||
"description": "To iestatāt lapā Vēstuļu saraksts."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Skaņas un nelasīto skaits",
|
||||
"description": "Paziņojumu skaņas un nelasīto skaitu cilnē iestatāt lapā Paziņojumi."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Par {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Izrakstīties no šī konta šajā ierīcē."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Bulwark Webmail iestatīšana",
|
||||
"subtitle": "Iestatiet savu tīmekļa pasta instanci pārlūkā.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Офлайн",
|
||||
"privacy": "Нууцлал",
|
||||
"about": "Тухай"
|
||||
},
|
||||
"search": {
|
||||
"signature": "гарын үсэг имэйлийн гарын үсэг хөл хэсэг",
|
||||
"sign_out": "гарах системээс гарах logout",
|
||||
"app_lock": "PIN түгжээ код түгжих дэлгэц",
|
||||
"text_size": "үсгийн хэмжээ том бичиг томруулах zoom",
|
||||
"vacation": "автомат хариу оффисоос гадуур эзгүй амралт autoreply",
|
||||
"two_factor": "2FA хоёр шатлалт хоёр хүчин зүйлт MFA OTP баталгаажуулагч нэг удаагийн код",
|
||||
"encryption": "S/MIME smime PGP OpenPGP шифрлэлт гэрчилгээ",
|
||||
"password": "нууц үг нууц үг солих шинэ нууц үг",
|
||||
"offline": "офлайн offline холболтгүй интернэтгүй татах хадгалах сан синк",
|
||||
"wipe": "устгах арилгах цэвэрлэх төхөөрөмж өгөгдөл",
|
||||
"outbox": "илгээх хайрцаг илгээгээгүй хүлээгдэж буй дараалал",
|
||||
"remote_content": "алсын зураг гадаад агуулга мөрдөх пиксел итгэмжлэгдсэн илгээгч",
|
||||
"push": "push реле ар тал мэдэгдэл",
|
||||
"version": "хувилбар шинэчлэл тухай эх код GitHub лиценз"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Мэдэгдэл",
|
||||
"push_link": {
|
||||
"label": "Мэдэгдлийн реле",
|
||||
"description": "Апп хаалттай үеийн мэдэгдэл push релеээр дамждаг бөгөөд реле таны захидлыг хэзээ ч хардаггүй. Үүнийг Мэдэгдэл хуудсанд сонгоно.",
|
||||
"button": "Мэдэгдэл"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Алсын зураг ба итгэмжлэгдсэн илгээгчид",
|
||||
"receipts_label": "Уншсан баталгаа",
|
||||
"senders_label": "Илгээгчийн зураг",
|
||||
"description": "Эдгээр тохиргоо Нууцлал хуудсанд байна.",
|
||||
"button": "Нууцлал"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Нягтрал ба товчны нэр",
|
||||
"description": "Жагсаалтын мөрүүдийн зай болон товчнууд нэрээ харуулах эсэхийг Харагдах байдал хуудсанд тохируулна."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Захидлын цайвар дэвсгэр",
|
||||
"description": "Бараан горимд захидлыг цайвар дэвсгэр дээр харуулахыг Унших хуудсанд тохируулна."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Хуанли дахь үйл явдлын урьдчилсан харагдац",
|
||||
"description": "Үйл явдал руу заахад дэлгэрэнгүйг харуулах эсэхийг Хуанли хуудсанд тохируулна."
|
||||
},
|
||||
"list": {
|
||||
"label": "Харилцан яриа ба урьдчилсан мөр",
|
||||
"description": "Харилцан яриагаар бүлэглэх болон захидал бүрийн доорх урьдчилсан мөрийг Захидлын жагсаалт хуудсанд тохируулна."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Уншсан гэж тэмдэглэх",
|
||||
"description": "Захидал хэзээ уншсанд тооцогдох, устгах эсвэл зөөсний дараа хаашаа очихыг Унших хуудсанд тохируулна."
|
||||
},
|
||||
"send_warnings_description": "Илгээхийг буцаах, мөн гарчиггүй, хавсралт мартсан эсвэл орлуулагч бичвэрийн анхааруулга.",
|
||||
"bulk_delete": {
|
||||
"label": "Олон захидал устгахаас өмнө асуух",
|
||||
"description": "Үүнийг Захидлын жагсаалт хуудсанд тохируулна."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Дуу ба уншаагүйн тоолуур",
|
||||
"description": "Мэдэгдлийн дуу болон таб дээрх уншаагүйн тоог Мэдэгдэл хуудсанд тохируулна."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "{appName}-ийн тухай"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Энэ төхөөрөмж дээр энэ бүртгэлээс гарах."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Bulwark Webmail-ийн тохиргоо",
|
||||
"subtitle": "Вэб шуудангийн инстансаа хөтчөөс тохируулна уу.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Frakoblet",
|
||||
"privacy": "Personvern",
|
||||
"about": "Om"
|
||||
},
|
||||
"search": {
|
||||
"signature": "signatur e-postsignatur bunntekst",
|
||||
"sign_out": "logg ut logout avlogging",
|
||||
"app_lock": "PIN lås kode låseskjerm",
|
||||
"text_size": "skriftstørrelse større tekst zoom forstørr",
|
||||
"vacation": "autosvar automatisk svar fraværende ferie ikke på kontoret autoreply",
|
||||
"two_factor": "2FA totrinns tofaktor MFA OTP autentiseringsapp engangskode",
|
||||
"encryption": "S/MIME smime PGP OpenPGP kryptering sertifikat",
|
||||
"password": "passord endre passord nytt passord",
|
||||
"offline": "frakoblet offline uten tilkobling uten internett last ned lagring synkroniser",
|
||||
"wipe": "fjern slett tøm enhet data",
|
||||
"outbox": "utboks ikke sendt venter kø",
|
||||
"remote_content": "eksterne bilder eksternt innhold sporing piksler klarerte avsendere",
|
||||
"push": "push relé bakgrunn varsler",
|
||||
"version": "versjon oppdatering om kildekode GitHub lisens"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Varsler",
|
||||
"push_link": {
|
||||
"label": "Relé for varsler",
|
||||
"description": "Varsler mens appen er lukket går gjennom et push-relé, som aldri ser e-posten din. Du velger det på siden Varsler.",
|
||||
"button": "Varsler"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Eksterne bilder og klarerte avsendere",
|
||||
"receipts_label": "Lesebekreftelser",
|
||||
"senders_label": "Avsenderbilder",
|
||||
"description": "Disse innstillingene finner du på siden Personvern.",
|
||||
"button": "Personvern"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Tetthet og knappenavn",
|
||||
"description": "Hvor tett radene i listen står, og om knappene viser navnet sitt, stiller du inn på siden Utseende."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Lys bakgrunn for meldinger",
|
||||
"description": "Å vise meldinger på lys bakgrunn i mørk modus stiller du inn på siden Lesing."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Forhåndsvisning av hendelser i kalenderen",
|
||||
"description": "Om det å peke på en hendelse viser detaljene, stiller du inn på siden Kalender."
|
||||
},
|
||||
"list": {
|
||||
"label": "Samtaler og forhåndsvisningslinjer",
|
||||
"description": "Gruppering etter samtale og forhåndsvisningslinjen under hver melding stiller du inn på siden Meldingsliste."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Merke som lest",
|
||||
"description": "Når en melding regnes som lest, og hvor du havner etter å ha slettet eller flyttet den, stiller du inn på siden Lesing."
|
||||
},
|
||||
"send_warnings_description": "Angre sending og varslene om manglende emne, glemt vedlegg eller plassholdertekst.",
|
||||
"bulk_delete": {
|
||||
"label": "Spør før sletting av flere meldinger",
|
||||
"description": "Dette stiller du inn på siden Meldingsliste."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Lyder og telleren for uleste",
|
||||
"description": "Varsellyder og antall uleste i fanen stiller du inn på siden Varsler."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Om {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Logg ut av denne kontoen på denne enheten."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Oppsett av Bulwark Webmail",
|
||||
"subtitle": "Sett opp webmail-instansen din i nettleseren.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Privacy",
|
||||
"about": "Over"
|
||||
},
|
||||
"search": {
|
||||
"signature": "handtekening e-mailhandtekening ondertekening voettekst",
|
||||
"sign_out": "afmelden uitloggen logout",
|
||||
"app_lock": "PIN vergrendeling code vergrendelscherm",
|
||||
"text_size": "lettergrootte grotere tekst zoom vergroten",
|
||||
"vacation": "automatisch antwoord afwezig vakantie buiten kantoor autoreply",
|
||||
"two_factor": "2FA tweestaps twee-factor MFA OTP authenticator eenmalige code",
|
||||
"encryption": "S/MIME smime PGP OpenPGP versleuteling certificaat",
|
||||
"password": "wachtwoord wijzigen nieuw wachtwoord",
|
||||
"offline": "offline zonder verbinding zonder internet downloaden opslag synchroniseren",
|
||||
"wipe": "verwijderen wissen apparaat gegevens",
|
||||
"outbox": "postvak uit niet verzonden wachtend wachtrij",
|
||||
"remote_content": "externe afbeeldingen externe inhoud tracking pixels vertrouwde afzenders",
|
||||
"push": "push relay achtergrond meldingen",
|
||||
"version": "versie update over broncode GitHub licentie"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Meldingen",
|
||||
"push_link": {
|
||||
"label": "Relay voor meldingen",
|
||||
"description": "Meldingen terwijl de app dicht is gaan via een push-relay, die uw mail nooit ziet. U kiest die op de pagina Meldingen.",
|
||||
"button": "Meldingen"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Externe afbeeldingen en vertrouwde afzenders",
|
||||
"receipts_label": "Leesbevestigingen",
|
||||
"senders_label": "Afzenderafbeeldingen",
|
||||
"description": "Deze instellingen staan op de pagina Privacy.",
|
||||
"button": "Privacy"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Dichtheid en knoplabels",
|
||||
"description": "Hoe dicht de rijen in de lijst staan en of knoppen hun naam tonen, stelt u in op de pagina Weergave."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Lichte achtergrond voor berichten",
|
||||
"description": "Berichten op een lichte achtergrond tonen in donkere modus stelt u in op de pagina Lezen."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Voorbeeld van afspraken in de agenda",
|
||||
"description": "Of wijzen naar een afspraak de details toont, stelt u in op de pagina Agenda."
|
||||
},
|
||||
"list": {
|
||||
"label": "Gesprekken en voorbeeldregels",
|
||||
"description": "Groeperen per gesprek en de voorbeeldregel onder elk bericht stelt u in op de pagina Berichtenlijst."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Als gelezen markeren",
|
||||
"description": "Wanneer een bericht als gelezen telt, en waar u na verwijderen of verplaatsen heen gaat, stelt u in op de pagina Lezen."
|
||||
},
|
||||
"send_warnings_description": "Verzenden ongedaan maken en de waarschuwingen voor een ontbrekend onderwerp, een vergeten bijlage of opvultekst.",
|
||||
"bulk_delete": {
|
||||
"label": "Vragen voor het verwijderen van meerdere berichten",
|
||||
"description": "Dit stelt u in op de pagina Berichtenlijst."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Geluiden en de teller voor ongelezen",
|
||||
"description": "Meldingsgeluiden en het aantal ongelezen op het tabblad stelt u in op de pagina Meldingen."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Over {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Dit account op dit apparaat afmelden."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Bulwark Webmail instellen",
|
||||
"subtitle": "Stel je webmailinstantie in vanuit de browser.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Prywatność",
|
||||
"about": "Informacje"
|
||||
},
|
||||
"search": {
|
||||
"signature": "podpis podpis e-mail stopka",
|
||||
"sign_out": "wyloguj wylogowanie logout",
|
||||
"app_lock": "PIN blokada kod zablokuj ekran",
|
||||
"text_size": "rozmiar czcionki większy tekst powiększenie zoom",
|
||||
"vacation": "autoodpowiedź automatyczna odpowiedź poza biurem nieobecność urlop autoreply",
|
||||
"two_factor": "2FA dwuetapowe dwuskładnikowe MFA OTP uwierzytelniacz kod jednorazowy",
|
||||
"encryption": "S/MIME smime PGP OpenPGP szyfrowanie certyfikat",
|
||||
"password": "hasło zmień hasło nowe hasło",
|
||||
"offline": "offline bez połączenia bez internetu pobierz pamięć synchronizacja",
|
||||
"wipe": "usuń wyczyść wymaż urządzenie dane",
|
||||
"outbox": "skrzynka nadawcza niewysłane oczekujące kolejka",
|
||||
"remote_content": "zdalne obrazy treści zewnętrzne śledzenie piksele zaufani nadawcy",
|
||||
"push": "push przekaźnik w tle powiadomienia",
|
||||
"version": "wersja aktualizacja informacje kod źródłowy GitHub licencja"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Powiadomienia",
|
||||
"push_link": {
|
||||
"label": "Przekaźnik powiadomień",
|
||||
"description": "Powiadomienia przy zamkniętej aplikacji idą przez przekaźnik push, który nigdy nie widzi Twojej poczty. Wybierasz go na stronie Powiadomienia.",
|
||||
"button": "Powiadomienia"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Zdalne obrazy i zaufani nadawcy",
|
||||
"receipts_label": "Potwierdzenia przeczytania",
|
||||
"senders_label": "Obrazy nadawców",
|
||||
"description": "Te ustawienia są na stronie Prywatność.",
|
||||
"button": "Prywatność"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Gęstość i etykiety paska",
|
||||
"description": "Odstępy między wierszami listy i to, czy przyciski pokazują nazwy, ustawisz na stronie Wygląd."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Jasne tło wiadomości",
|
||||
"description": "Wyświetlanie wiadomości na jasnym tle w trybie ciemnym ustawisz na stronie Czytanie."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Podgląd wydarzeń w kalendarzu",
|
||||
"description": "To, czy wskazanie wydarzenia pokazuje szczegóły, ustawisz na stronie Kalendarz."
|
||||
},
|
||||
"list": {
|
||||
"label": "Wątki i wiersze podglądu",
|
||||
"description": "Grupowanie w wątki i wiersz podglądu pod każdą wiadomością ustawisz na stronie Lista wiadomości."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Oznaczanie jako przeczytane",
|
||||
"description": "Kiedy wiadomość liczy się jako przeczytana i dokąd trafiasz po usunięciu lub przeniesieniu, ustawisz na stronie Czytanie."
|
||||
},
|
||||
"send_warnings_description": "Cofnięcie wysłania i ostrzeżenia o braku tematu, zapomnianym załączniku lub tekście zastępczym.",
|
||||
"bulk_delete": {
|
||||
"label": "Pytanie przed usunięciem wielu wiadomości",
|
||||
"description": "To ustawisz na stronie Lista wiadomości."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Dźwięki i licznik nieprzeczytanych",
|
||||
"description": "Dźwięki powiadomień i liczbę nieprzeczytanych na karcie ustawisz na stronie Powiadomienia."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Informacje o {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Wyloguj to konto na tym urządzeniu."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Konfiguracja Bulwark Webmail",
|
||||
"subtitle": "Skonfiguruj swoją instancję poczty w przeglądarce.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Privacidade",
|
||||
"about": "Sobre"
|
||||
},
|
||||
"search": {
|
||||
"signature": "assinatura assinatura de email rodapé",
|
||||
"sign_out": "sair terminar sessão desconectar logout",
|
||||
"app_lock": "PIN bloqueio código bloquear ecrã tela",
|
||||
"text_size": "tamanho da fonte texto maior zoom ampliar",
|
||||
"vacation": "resposta automática fora do escritório ausente férias autoreply",
|
||||
"two_factor": "2FA dois fatores duas etapas MFA OTP autenticador código único",
|
||||
"encryption": "S/MIME smime PGP OpenPGP criptografia certificado",
|
||||
"password": "senha palavra-passe alterar senha nova senha",
|
||||
"offline": "offline sem conexão sem internet baixar armazenamento sincronizar",
|
||||
"wipe": "remover apagar excluir dispositivo dados",
|
||||
"outbox": "caixa de saída não enviado em espera fila",
|
||||
"remote_content": "imagens remotas conteúdo externo rastreamento pixels remetentes confiáveis",
|
||||
"push": "push retransmissor segundo plano notificações",
|
||||
"version": "versão atualização sobre código-fonte GitHub licença"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Notificações",
|
||||
"push_link": {
|
||||
"label": "Retransmissor de notificações",
|
||||
"description": "As notificações com o app fechado passam por um retransmissor push, que nunca vê seus e-mails. Você o escolhe na página Notificações.",
|
||||
"button": "Notificações"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Imagens remotas e remetentes confiáveis",
|
||||
"receipts_label": "Confirmações de leitura",
|
||||
"senders_label": "Imagens dos remetentes",
|
||||
"description": "Estas configurações estão na página Privacidade.",
|
||||
"button": "Privacidade"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Densidade e rótulos da barra",
|
||||
"description": "O espaçamento das linhas da lista e se os botões mostram o nome são definidos na página Aparência."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Fundo claro para as mensagens",
|
||||
"description": "Mostrar as mensagens com fundo claro no modo escuro é definido na página Leitura."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Pré-visualização de eventos no calendário",
|
||||
"description": "Se apontar para um evento mostra os detalhes é definido na página Calendário."
|
||||
},
|
||||
"list": {
|
||||
"label": "Conversas e linhas de pré-visualização",
|
||||
"description": "O agrupamento por conversa e a linha de pré-visualização de cada mensagem são definidos na página Lista de mensagens."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Marcar como lido",
|
||||
"description": "Quando uma mensagem conta como lida, e para onde você vai depois de apagá-la ou movê-la, é definido na página Leitura."
|
||||
},
|
||||
"send_warnings_description": "Desfazer envio e os avisos de assunto vazio, anexo esquecido ou texto provisório.",
|
||||
"bulk_delete": {
|
||||
"label": "Perguntar antes de apagar várias mensagens",
|
||||
"description": "Isto é definido na página Lista de mensagens."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Sons e contador de não lidas",
|
||||
"description": "Os sons de notificação e o número de não lidas na aba são definidos na página Notificações."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Sobre o {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Sair desta conta neste dispositivo."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configuração do Bulwark Webmail",
|
||||
"subtitle": "Configure a sua instância de webmail a partir do navegador.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Confidențialitate",
|
||||
"about": "Despre"
|
||||
},
|
||||
"search": {
|
||||
"signature": "semnătură semnătură e-mail subsol",
|
||||
"sign_out": "deconectare ieșire logout",
|
||||
"app_lock": "PIN blocare cod ecran de blocare",
|
||||
"text_size": "mărime font text mai mare zoom mărire",
|
||||
"vacation": "răspuns automat în afara biroului absent concediu vacanță autoreply",
|
||||
"two_factor": "2FA doi factori doi pași MFA OTP autentificator cod unic",
|
||||
"encryption": "S/MIME smime PGP OpenPGP criptare certificat",
|
||||
"password": "parolă schimbă parola parolă nouă",
|
||||
"offline": "offline fără conexiune fără internet descărcare stocare sincronizare",
|
||||
"wipe": "elimină șterge golește dispozitiv date",
|
||||
"outbox": "căsuța de ieșire netrimis în așteptare coadă",
|
||||
"remote_content": "imagini la distanță conținut extern urmărire pixeli expeditori de încredere",
|
||||
"push": "push releu fundal notificări",
|
||||
"version": "versiune actualizare despre cod sursă GitHub licență"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Notificări",
|
||||
"push_link": {
|
||||
"label": "Releu pentru notificări",
|
||||
"description": "Notificările cu aplicația închisă trec printr-un releu push, care nu vă vede niciodată e-mailurile. Îl alegeți pe pagina Notificări.",
|
||||
"button": "Notificări"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Imagini la distanță și expeditori de încredere",
|
||||
"receipts_label": "Confirmări de citire",
|
||||
"senders_label": "Imaginile expeditorilor",
|
||||
"description": "Aceste setări sunt pe pagina Confidențialitate.",
|
||||
"button": "Confidențialitate"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Densitate și etichetele barei",
|
||||
"description": "Cât de apropiate sunt rândurile listei și dacă butoanele își arată numele se setează pe pagina Aspect."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Fundal deschis pentru mesaje",
|
||||
"description": "Afișarea mesajelor pe fundal deschis în modul întunecat se setează pe pagina Citire."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Previzualizarea evenimentelor în calendar",
|
||||
"description": "Dacă indicarea unui eveniment îi arată detaliile se setează pe pagina Calendar."
|
||||
},
|
||||
"list": {
|
||||
"label": "Conversații și rânduri de previzualizare",
|
||||
"description": "Gruparea pe conversații și rândul de previzualizare de sub fiecare mesaj se setează pe pagina Lista de mesaje."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Marcare ca citit",
|
||||
"description": "Când un mesaj contează ca citit și unde ajungeți după ce îl ștergeți sau îl mutați se setează pe pagina Citire."
|
||||
},
|
||||
"send_warnings_description": "Anularea trimiterii și avertismentele pentru subiect lipsă, atașament uitat sau text substituent.",
|
||||
"bulk_delete": {
|
||||
"label": "Întrebare înainte de a șterge mai multe mesaje",
|
||||
"description": "Aceasta se setează pe pagina Lista de mesaje."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Sunete și numărul de necitite",
|
||||
"description": "Sunetele notificărilor și numărul de necitite din filă se setează pe pagina Notificări."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Despre {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Deconectați acest cont pe acest dispozitiv."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Configurarea Bulwark Webmail",
|
||||
"subtitle": "Configurați instanța de webmail din browser.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Офлайн",
|
||||
"privacy": "Конфиденциальность",
|
||||
"about": "О программе"
|
||||
},
|
||||
"search": {
|
||||
"signature": "подпись подпись письма нижний колонтитул",
|
||||
"sign_out": "выйти выход logout разлогиниться",
|
||||
"app_lock": "PIN блокировка код экран блокировки",
|
||||
"text_size": "размер шрифта крупный текст масштаб увеличить",
|
||||
"vacation": "автоответ автоответчик нет на месте отпуск отсутствие autoreply",
|
||||
"two_factor": "2FA двухфакторная двухэтапная MFA OTP аутентификатор одноразовый код",
|
||||
"encryption": "S/MIME smime PGP OpenPGP шифрование сертификат",
|
||||
"password": "пароль сменить пароль новый пароль",
|
||||
"offline": "офлайн без сети без интернета скачать хранилище синхронизация",
|
||||
"wipe": "удалить стереть очистить устройство данные",
|
||||
"outbox": "исходящие неотправленные ожидание очередь",
|
||||
"remote_content": "внешние изображения внешний контент отслеживание пиксели доверенные отправители",
|
||||
"push": "push ретранслятор фоновые уведомления",
|
||||
"version": "версия обновление о программе исходный код GitHub лицензия"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Уведомления",
|
||||
"push_link": {
|
||||
"label": "Ретранслятор уведомлений",
|
||||
"description": "Уведомления при закрытом приложении идут через push-ретранслятор, который никогда не видит вашу почту. Он выбирается на странице «Уведомления».",
|
||||
"button": "Уведомления"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Внешние изображения и доверенные отправители",
|
||||
"receipts_label": "Уведомления о прочтении",
|
||||
"senders_label": "Изображения отправителей",
|
||||
"description": "Эти настройки находятся на странице «Конфиденциальность».",
|
||||
"button": "Конфиденциальность"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Плотность и подписи кнопок",
|
||||
"description": "Насколько плотно идут строки списка и показывают ли кнопки названия, настраивается на странице «Оформление»."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Светлый фон для писем",
|
||||
"description": "Показ писем на светлом фоне в тёмном режиме настраивается на странице «Чтение»."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Предпросмотр событий в календаре",
|
||||
"description": "Показывать ли подробности при наведении на событие, настраивается на странице «Календарь»."
|
||||
},
|
||||
"list": {
|
||||
"label": "Цепочки и строки предпросмотра",
|
||||
"description": "Группировка по цепочкам и строка предпросмотра под каждым письмом настраиваются на странице «Список писем»."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Отметка о прочтении",
|
||||
"description": "Когда письмо считается прочитанным и куда вы попадаете после удаления или перемещения, настраивается на странице «Чтение»."
|
||||
},
|
||||
"send_warnings_description": "Отмена отправки и предупреждения о пустой теме, забытом вложении или тексте-заполнителе.",
|
||||
"bulk_delete": {
|
||||
"label": "Спрашивать перед удалением нескольких писем",
|
||||
"description": "Это настраивается на странице «Список писем»."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Звуки и счётчик непрочитанных",
|
||||
"description": "Звуки уведомлений и число непрочитанных на вкладке настраиваются на странице «Уведомления»."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "О программе {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Выйти из этой учётной записи на этом устройстве."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Настройка Bulwark Webmail",
|
||||
"subtitle": "Настройте свой экземпляр веб-почты в браузере.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Offline",
|
||||
"privacy": "Súkromie",
|
||||
"about": "O aplikácii"
|
||||
},
|
||||
"search": {
|
||||
"signature": "podpis e-mailový podpis päta",
|
||||
"sign_out": "odhlásiť odhlásenie logout",
|
||||
"app_lock": "PIN zámok kód uzamknúť obrazovka",
|
||||
"text_size": "veľkosť písma väčší text zoom zväčšiť",
|
||||
"vacation": "automatická odpoveď mimo kancelárie neprítomnosť dovolenka autoreply",
|
||||
"two_factor": "2FA dvojfázové dvojfaktorové MFA OTP autentifikátor jednorazový kód",
|
||||
"encryption": "S/MIME smime PGP OpenPGP šifrovanie certifikát",
|
||||
"password": "heslo zmeniť heslo nové heslo",
|
||||
"offline": "offline bez pripojenia bez internetu stiahnuť úložisko synchronizácia",
|
||||
"wipe": "odstrániť zmazať vymazať zariadenie údaje",
|
||||
"outbox": "na odoslanie neodoslané čakajúce front",
|
||||
"remote_content": "vzdialené obrázky externý obsah sledovanie pixely dôveryhodní odosielatelia",
|
||||
"push": "push relay na pozadí upozornenia",
|
||||
"version": "verzia aktualizácia o aplikácii zdrojový kód GitHub licencia"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Upozornenia",
|
||||
"push_link": {
|
||||
"label": "Relay pre upozornenia",
|
||||
"description": "Upozornenia pri zatvorenej aplikácii idú cez push relay, ktorý nikdy nevidí vašu poštu. Vyberiete ho na stránke Upozornenia.",
|
||||
"button": "Upozornenia"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Vzdialené obrázky a dôveryhodní odosielatelia",
|
||||
"receipts_label": "Potvrdenia o prečítaní",
|
||||
"senders_label": "Obrázky odosielateľov",
|
||||
"description": "Tieto nastavenia nájdete na stránke Súkromie.",
|
||||
"button": "Súkromie"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Hustota a popisy panela",
|
||||
"description": "Ako tesne sú riadky zoznamu a či tlačidlá ukazujú svoj názov, nastavíte na stránke Vzhľad."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Svetlé pozadie správ",
|
||||
"description": "Zobrazenie správ na svetlom pozadí v tmavom režime nastavíte na stránke Čítanie."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Náhľad udalostí v kalendári",
|
||||
"description": "Či ukázanie na udalosť zobrazí jej podrobnosti, nastavíte na stránke Kalendár."
|
||||
},
|
||||
"list": {
|
||||
"label": "Konverzácie a riadky náhľadu",
|
||||
"description": "Zoskupenie podľa konverzácie a riadok náhľadu pod každou správou nastavíte na stránke Zoznam správ."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Označenie ako prečítané",
|
||||
"description": "Kedy sa správa počíta ako prečítaná a kam sa dostanete po zmazaní alebo presune, nastavíte na stránke Čítanie."
|
||||
},
|
||||
"send_warnings_description": "Zrušenie odoslania a upozornenia na chýbajúci predmet, zabudnutú prílohu alebo zástupný text.",
|
||||
"bulk_delete": {
|
||||
"label": "Opýtať sa pred zmazaním viacerých správ",
|
||||
"description": "To nastavíte na stránke Zoznam správ."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Zvuky a počet neprečítaných",
|
||||
"description": "Zvuky upozornení a počet neprečítaných na karte nastavíte na stránke Upozornenia."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "O aplikácii {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Odhlásiť tento účet na tomto zariadení."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Nastavenie Bulwark Webmail",
|
||||
"subtitle": "Nastavte svoju inštanciu webmailu v prehliadači.",
|
||||
|
||||
@@ -6271,6 +6271,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "ออฟไลน์",
|
||||
"privacy": "ความเป็นส่วนตัว",
|
||||
"about": "เกี่ยวกับ"
|
||||
},
|
||||
"search": {
|
||||
"signature": "ลายเซ็น ลายเซ็นอีเมล ส่วนท้าย",
|
||||
"sign_out": "ออกจากระบบ ลงชื่อออก logout",
|
||||
"app_lock": "PIN ล็อก รหัส หน้าจอล็อก",
|
||||
"text_size": "ขนาดตัวอักษร ตัวอักษรใหญ่ ซูม ขยาย",
|
||||
"vacation": "ตอบกลับอัตโนมัติ ไม่อยู่ที่สำนักงาน ไม่อยู่ ลาพักร้อน วันหยุด autoreply",
|
||||
"two_factor": "2FA สองปัจจัย สองขั้นตอน MFA OTP แอปยืนยันตัวตน รหัสใช้ครั้งเดียว",
|
||||
"encryption": "S/MIME smime PGP OpenPGP การเข้ารหัส ใบรับรอง",
|
||||
"password": "รหัสผ่าน เปลี่ยนรหัสผ่าน รหัสผ่านใหม่",
|
||||
"offline": "ออฟไลน์ offline ไม่มีการเชื่อมต่อ ไม่มีอินเทอร์เน็ต ดาวน์โหลด พื้นที่จัดเก็บ ซิงค์",
|
||||
"wipe": "ลบ ล้าง อุปกรณ์ ข้อมูล",
|
||||
"outbox": "กล่องขาออก ยังไม่ส่ง รอส่ง คิว",
|
||||
"remote_content": "รูปภาพภายนอก เนื้อหาภายนอก การติดตาม พิกเซล ผู้ส่งที่เชื่อถือ",
|
||||
"push": "พุช push รีเลย์ เบื้องหลัง การแจ้งเตือน",
|
||||
"version": "เวอร์ชัน อัปเดต เกี่ยวกับ ซอร์สโค้ด GitHub สัญญาอนุญาต"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "การแจ้งเตือน",
|
||||
"push_link": {
|
||||
"label": "รีเลย์การแจ้งเตือน",
|
||||
"description": "การแจ้งเตือนขณะปิดแอปจะผ่านรีเลย์พุช ซึ่งไม่เคยเห็นอีเมลของคุณ คุณเลือกได้ในหน้าการแจ้งเตือน",
|
||||
"button": "การแจ้งเตือน"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "รูปภาพภายนอกและผู้ส่งที่เชื่อถือ",
|
||||
"receipts_label": "ใบตอบรับการอ่าน",
|
||||
"senders_label": "รูปภาพผู้ส่ง",
|
||||
"description": "การตั้งค่าเหล่านี้อยู่ในหน้าความเป็นส่วนตัว",
|
||||
"button": "ความเป็นส่วนตัว"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "ความหนาแน่นและป้ายปุ่ม",
|
||||
"description": "ระยะห่างของแถวในรายการและการแสดงชื่อบนปุ่ม ตั้งค่าได้ในหน้ารูปลักษณ์"
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "พื้นหลังสว่างสำหรับข้อความ",
|
||||
"description": "การแสดงข้อความบนพื้นหลังสว่างในโหมดมืด ตั้งค่าได้ในหน้าการอ่าน"
|
||||
},
|
||||
"calendar": {
|
||||
"label": "ตัวอย่างกิจกรรมในปฏิทิน",
|
||||
"description": "การแสดงรายละเอียดเมื่อชี้ที่กิจกรรม ตั้งค่าได้ในหน้าปฏิทิน"
|
||||
},
|
||||
"list": {
|
||||
"label": "การสนทนาและบรรทัดตัวอย่าง",
|
||||
"description": "การจัดกลุ่มตามการสนทนาและบรรทัดตัวอย่างใต้แต่ละข้อความ ตั้งค่าได้ในหน้ารายการข้อความ"
|
||||
},
|
||||
"reading": {
|
||||
"label": "ทำเครื่องหมายว่าอ่านแล้ว",
|
||||
"description": "เมื่อใดข้อความจะนับว่าอ่านแล้ว และจะไปที่ใดหลังลบหรือย้าย ตั้งค่าได้ในหน้าการอ่าน"
|
||||
},
|
||||
"send_warnings_description": "การยกเลิกการส่ง และคำเตือนเรื่องไม่มีหัวเรื่อง ลืมแนบไฟล์ หรือข้อความตัวยึดตำแหน่ง",
|
||||
"bulk_delete": {
|
||||
"label": "ถามก่อนลบหลายข้อความ",
|
||||
"description": "ตั้งค่าได้ในหน้ารายการข้อความ"
|
||||
},
|
||||
"sounds": {
|
||||
"label": "เสียงและตัวนับที่ยังไม่อ่าน",
|
||||
"description": "เสียงแจ้งเตือนและจำนวนที่ยังไม่อ่านบนแท็บ ตั้งค่าได้ในหน้าการแจ้งเตือน"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "เกี่ยวกับ {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "ออกจากระบบบัญชีนี้บนอุปกรณ์นี้"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "ตั้งค่า Bulwark Webmail",
|
||||
"subtitle": "ตั้งค่าอินสแตนซ์เว็บเมลของคุณจากเบราว์เซอร์",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Çevrimdışı",
|
||||
"privacy": "Gizlilik",
|
||||
"about": "Hakkında"
|
||||
},
|
||||
"search": {
|
||||
"signature": "imza e-posta imzası alt bilgi",
|
||||
"sign_out": "oturumu kapat çıkış yap logout",
|
||||
"app_lock": "PIN kilit kod kilit ekranı",
|
||||
"text_size": "yazı tipi boyutu büyük metin yakınlaştır zoom",
|
||||
"vacation": "otomatik yanıt ofis dışında uzakta tatil izin autoreply",
|
||||
"two_factor": "2FA iki faktörlü iki adımlı MFA OTP doğrulayıcı tek kullanımlık kod",
|
||||
"encryption": "S/MIME smime PGP OpenPGP şifreleme sertifika",
|
||||
"password": "parola şifre parolayı değiştir yeni parola",
|
||||
"offline": "çevrimdışı bağlantısız internetsiz indir depolama eşitleme",
|
||||
"wipe": "kaldır sil temizle cihaz veriler",
|
||||
"outbox": "giden kutusu gönderilmemiş bekleyen kuyruk",
|
||||
"remote_content": "uzak resimler harici içerik izleme pikseller güvenilir gönderenler",
|
||||
"push": "push aktarıcı arka plan bildirimler",
|
||||
"version": "sürüm güncelleme hakkında kaynak kodu GitHub lisans"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Bildirimler",
|
||||
"push_link": {
|
||||
"label": "Bildirim aktarıcısı",
|
||||
"description": "Uygulama kapalıyken gelen bildirimler, e-postanızı asla görmeyen bir push aktarıcısından geçer. Bunu Bildirimler sayfasında seçersiniz.",
|
||||
"button": "Bildirimler"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Uzak resimler ve güvenilir gönderenler",
|
||||
"receipts_label": "Okundu bilgileri",
|
||||
"senders_label": "Gönderen resimleri",
|
||||
"description": "Bu ayarlar Gizlilik sayfasındadır.",
|
||||
"button": "Gizlilik"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Yoğunluk ve düğme etiketleri",
|
||||
"description": "Liste satırlarının ne kadar sık olduğu ve düğmelerin adını gösterip göstermediği Görünüm sayfasında ayarlanır."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "İletiler için açık arka plan",
|
||||
"description": "Koyu modda iletilerin açık arka planda gösterilmesi Okuma sayfasında ayarlanır."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Takvimde etkinlik önizlemesi",
|
||||
"description": "Bir etkinliğin üzerine gelince ayrıntılarının görünüp görünmeyeceği Takvim sayfasında ayarlanır."
|
||||
},
|
||||
"list": {
|
||||
"label": "Konuşmalar ve önizleme satırları",
|
||||
"description": "Konuşmaya göre gruplama ve her iletinin altındaki önizleme satırı İleti listesi sayfasında ayarlanır."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Okundu olarak işaretleme",
|
||||
"description": "Bir iletinin ne zaman okunmuş sayılacağı ve silip taşıdıktan sonra nereye gideceğiniz Okuma sayfasında ayarlanır."
|
||||
},
|
||||
"send_warnings_description": "Göndermeyi geri alma ve eksik konu, unutulan ek veya yer tutucu metin uyarıları.",
|
||||
"bulk_delete": {
|
||||
"label": "Birden çok iletiyi silmeden önce sor",
|
||||
"description": "Bu, İleti listesi sayfasında ayarlanır."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Sesler ve okunmamış sayacı",
|
||||
"description": "Bildirim sesleri ve sekmedeki okunmamış sayısı Bildirimler sayfasında ayarlanır."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "{appName} hakkında"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Bu cihazda bu hesabın oturumunu kapatın."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Bulwark Webmail kurulumu",
|
||||
"subtitle": "Webmail örneğinizi tarayıcıdan yapılandırın.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Офлайн",
|
||||
"privacy": "Конфіденційність",
|
||||
"about": "Про програму"
|
||||
},
|
||||
"search": {
|
||||
"signature": "підпис підпис листа нижній колонтитул",
|
||||
"sign_out": "вийти вихід logout",
|
||||
"app_lock": "PIN блокування код екран блокування",
|
||||
"text_size": "розмір шрифту більший текст масштаб збільшити",
|
||||
"vacation": "автовідповідь немає на місці відпустка відсутність autoreply",
|
||||
"two_factor": "2FA двофакторна двоетапна MFA OTP автентифікатор одноразовий код",
|
||||
"encryption": "S/MIME smime PGP OpenPGP шифрування сертифікат",
|
||||
"password": "пароль змінити пароль новий пароль",
|
||||
"offline": "офлайн без мережі без інтернету завантажити сховище синхронізація",
|
||||
"wipe": "видалити стерти очистити пристрій дані",
|
||||
"outbox": "вихідні невідправлені очікування черга",
|
||||
"remote_content": "зовнішні зображення зовнішній вміст відстеження пікселі довірені відправники",
|
||||
"push": "push ретранслятор фонові сповіщення",
|
||||
"version": "версія оновлення про програму вихідний код GitHub ліцензія"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Сповіщення",
|
||||
"push_link": {
|
||||
"label": "Ретранслятор сповіщень",
|
||||
"description": "Сповіщення при закритому застосунку йдуть через push-ретранслятор, який ніколи не бачить вашої пошти. Його можна вибрати на сторінці «Сповіщення».",
|
||||
"button": "Сповіщення"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Зовнішні зображення і довірені відправники",
|
||||
"receipts_label": "Сповіщення про прочитання",
|
||||
"senders_label": "Зображення відправників",
|
||||
"description": "Ці налаштування на сторінці «Конфіденційність».",
|
||||
"button": "Конфіденційність"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Щільність і підписи кнопок",
|
||||
"description": "Наскільки щільно йдуть рядки списку і чи показують кнопки назви, налаштовується на сторінці «Вигляд»."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Світле тло для листів",
|
||||
"description": "Показ листів на світлому тлі в темному режимі налаштовується на сторінці «Читання»."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Попередній перегляд подій у календарі",
|
||||
"description": "Чи показувати подробиці при наведенні на подію, налаштовується на сторінці «Календар»."
|
||||
},
|
||||
"list": {
|
||||
"label": "Ланцюжки і рядки попереднього перегляду",
|
||||
"description": "Групування за ланцюжками і рядок попереднього перегляду під кожним листом налаштовуються на сторінці «Список листів»."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Позначення прочитаним",
|
||||
"description": "Коли лист вважається прочитаним і куди ви потрапляєте після видалення чи переміщення, налаштовується на сторінці «Читання»."
|
||||
},
|
||||
"send_warnings_description": "Скасування надсилання і попередження про порожню тему, забуте вкладення чи текст-заповнювач.",
|
||||
"bulk_delete": {
|
||||
"label": "Питати перед видаленням кількох листів",
|
||||
"description": "Це налаштовується на сторінці «Список листів»."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Звуки й лічильник непрочитаних",
|
||||
"description": "Звуки сповіщень і кількість непрочитаних на вкладці налаштовуються на сторінці «Сповіщення»."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Про {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Вийти з цього облікового запису на цьому пристрої."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Налаштування Bulwark Webmail",
|
||||
"subtitle": "Налаштуйте свій екземпляр вебпошти в браузері.",
|
||||
|
||||
@@ -3379,6 +3379,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "Ngoại tuyến",
|
||||
"privacy": "Quyền riêng tư",
|
||||
"about": "Giới thiệu"
|
||||
},
|
||||
"search": {
|
||||
"signature": "chữ ký chữ ký email chân thư",
|
||||
"sign_out": "đăng xuất thoát logout",
|
||||
"app_lock": "PIN khóa mã màn hình khóa",
|
||||
"text_size": "cỡ chữ chữ to hơn phóng to thu phóng",
|
||||
"vacation": "trả lời tự động vắng mặt nghỉ phép kỳ nghỉ ngoài văn phòng autoreply",
|
||||
"two_factor": "2FA hai lớp hai bước MFA OTP ứng dụng xác thực mã dùng một lần",
|
||||
"encryption": "S/MIME smime PGP OpenPGP mã hóa chứng chỉ",
|
||||
"password": "mật khẩu đổi mật khẩu mật khẩu mới",
|
||||
"offline": "ngoại tuyến offline không có kết nối không có mạng tải xuống bộ nhớ đồng bộ",
|
||||
"wipe": "xóa gỡ dọn thiết bị dữ liệu",
|
||||
"outbox": "hộp thư đi chưa gửi đang chờ hàng đợi",
|
||||
"remote_content": "hình ảnh từ xa nội dung bên ngoài theo dõi pixel người gửi tin cậy",
|
||||
"push": "đẩy push chuyển tiếp nền thông báo",
|
||||
"version": "phiên bản cập nhật giới thiệu mã nguồn GitHub giấy phép"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "Thông báo",
|
||||
"push_link": {
|
||||
"label": "Máy chuyển tiếp thông báo",
|
||||
"description": "Thông báo khi ứng dụng đóng đi qua một máy chuyển tiếp đẩy, máy này không bao giờ thấy thư của bạn. Bạn chọn nó trên trang Thông báo.",
|
||||
"button": "Thông báo"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "Hình ảnh từ xa và người gửi tin cậy",
|
||||
"receipts_label": "Xác nhận đã đọc",
|
||||
"senders_label": "Ảnh người gửi",
|
||||
"description": "Các cài đặt này nằm trên trang Quyền riêng tư.",
|
||||
"button": "Quyền riêng tư"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "Mật độ và nhãn nút",
|
||||
"description": "Khoảng cách các dòng trong danh sách và việc nút có hiện tên hay không được đặt trên trang Giao diện."
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "Nền sáng cho thư",
|
||||
"description": "Hiển thị thư trên nền sáng ở chế độ tối được đặt trên trang Đọc thư."
|
||||
},
|
||||
"calendar": {
|
||||
"label": "Xem trước sự kiện trong lịch",
|
||||
"description": "Việc trỏ vào sự kiện có hiện chi tiết hay không được đặt trên trang Lịch."
|
||||
},
|
||||
"list": {
|
||||
"label": "Cuộc trò chuyện và dòng xem trước",
|
||||
"description": "Nhóm theo cuộc trò chuyện và dòng xem trước dưới mỗi thư được đặt trên trang Danh sách thư."
|
||||
},
|
||||
"reading": {
|
||||
"label": "Đánh dấu đã đọc",
|
||||
"description": "Khi nào thư được tính là đã đọc, và bạn đi đâu sau khi xóa hoặc di chuyển thư, được đặt trên trang Đọc thư."
|
||||
},
|
||||
"send_warnings_description": "Hoàn tác gửi và các cảnh báo khi thiếu tiêu đề, quên tệp đính kèm hoặc còn văn bản giữ chỗ.",
|
||||
"bulk_delete": {
|
||||
"label": "Hỏi trước khi xóa nhiều thư",
|
||||
"description": "Mục này được đặt trên trang Danh sách thư."
|
||||
},
|
||||
"sounds": {
|
||||
"label": "Âm thanh và số thư chưa đọc",
|
||||
"description": "Âm thanh thông báo và số thư chưa đọc trên thẻ được đặt trên trang Thông báo."
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "Giới thiệu {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "Đăng xuất tài khoản này trên thiết bị này."
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Thiết lập webmail Bulwark",
|
||||
"subtitle": "Định cấu hình phiên bản webmail của bạn từ trình duyệt.",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "離線",
|
||||
"privacy": "隱私",
|
||||
"about": "關於"
|
||||
},
|
||||
"search": {
|
||||
"signature": "簽名 郵件簽名 頁尾",
|
||||
"sign_out": "登出 退出 logout",
|
||||
"app_lock": "PIN 鎖定 密碼鎖 鎖定畫面",
|
||||
"text_size": "字型大小 字級 大字 放大 縮放",
|
||||
"vacation": "自動回覆 不在辦公室 外出 休假 假期 autoreply",
|
||||
"two_factor": "2FA 雙重驗證 兩步驟驗證 MFA OTP 驗證器 一次性驗證碼",
|
||||
"encryption": "S/MIME smime PGP OpenPGP 加密 憑證",
|
||||
"password": "密碼 變更密碼 新密碼",
|
||||
"offline": "離線 offline 無網路 無連線 下載 儲存空間 同步",
|
||||
"wipe": "移除 刪除 清除 裝置 資料",
|
||||
"outbox": "寄件匣 未寄出 等待中 佇列",
|
||||
"remote_content": "遠端圖片 外部內容 追蹤 像素 信任的寄件者",
|
||||
"push": "推播 push 中繼 背景 通知",
|
||||
"version": "版本 更新 關於 原始碼 GitHub 授權"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "通知",
|
||||
"push_link": {
|
||||
"label": "通知中繼",
|
||||
"description": "應用程式關閉時的通知會經過推播中繼,中繼永遠看不到你的郵件。可在「通知」頁面選擇。",
|
||||
"button": "通知"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "遠端圖片和信任的寄件者",
|
||||
"receipts_label": "讀取回條",
|
||||
"senders_label": "寄件者圖片",
|
||||
"description": "這些設定在「隱私」頁面。",
|
||||
"button": "隱私"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "密度和按鈕標籤",
|
||||
"description": "清單列的疏密以及按鈕是否顯示名稱,在「外觀」頁面設定。"
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "郵件使用淺色背景",
|
||||
"description": "深色模式下郵件是否使用淺色背景,在「閱讀」頁面設定。"
|
||||
},
|
||||
"calendar": {
|
||||
"label": "行事曆中的活動預覽",
|
||||
"description": "指向活動時是否顯示詳細資料,在「行事曆」頁面設定。"
|
||||
},
|
||||
"list": {
|
||||
"label": "對話和預覽列",
|
||||
"description": "依對話分組以及每封郵件下方的預覽列,在「郵件清單」頁面設定。"
|
||||
},
|
||||
"reading": {
|
||||
"label": "標示為已讀",
|
||||
"description": "郵件何時算作已讀,以及刪除或移動後跳到哪裡,在「閱讀」頁面設定。"
|
||||
},
|
||||
"send_warnings_description": "復原傳送,以及缺少主旨、忘記附件或預留文字的提醒。",
|
||||
"bulk_delete": {
|
||||
"label": "刪除多封郵件前詢問",
|
||||
"description": "在「郵件清單」頁面設定。"
|
||||
},
|
||||
"sounds": {
|
||||
"label": "音效和未讀標記",
|
||||
"description": "通知音效和分頁上的未讀數,在「通知」頁面設定。"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "關於 {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "在此裝置上登出此帳號。"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Bulwark Webmail 設定",
|
||||
"subtitle": "在瀏覽器中設定你的網頁郵件執行個體。",
|
||||
|
||||
@@ -3367,6 +3367,81 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"uxs_structure": {
|
||||
"tabs": {
|
||||
"offline": "离线",
|
||||
"privacy": "隐私",
|
||||
"about": "关于"
|
||||
},
|
||||
"search": {
|
||||
"signature": "签名 邮件签名 页脚",
|
||||
"sign_out": "退出登录 注销 登出 logout",
|
||||
"app_lock": "PIN 锁定 密码锁 锁屏",
|
||||
"text_size": "字体大小 字号 大字 放大 缩放",
|
||||
"vacation": "自动回复 不在办公室 外出 休假 假期 autoreply",
|
||||
"two_factor": "2FA 双重验证 两步验证 MFA OTP 身份验证器 一次性验证码",
|
||||
"encryption": "S/MIME smime PGP OpenPGP 加密 证书",
|
||||
"password": "密码 修改密码 新密码",
|
||||
"offline": "离线 offline 无网络 无连接 下载 存储 同步",
|
||||
"wipe": "移除 删除 清除 设备 数据",
|
||||
"outbox": "发件箱 未发送 等待 队列",
|
||||
"remote_content": "远程图片 外部内容 跟踪 像素 受信任的发件人",
|
||||
"push": "推送 push 中继 后台 通知",
|
||||
"version": "版本 更新 关于 源代码 GitHub 许可证"
|
||||
},
|
||||
"privacy": {
|
||||
"push_title": "通知",
|
||||
"push_link": {
|
||||
"label": "通知中继",
|
||||
"description": "应用关闭时的通知通过推送中继发送,中继永远看不到你的邮件。可在“通知”页面选择。",
|
||||
"button": "通知"
|
||||
}
|
||||
},
|
||||
"privacy_links": {
|
||||
"images_label": "远程图片和受信任的发件人",
|
||||
"receipts_label": "已读回执",
|
||||
"senders_label": "发件人头像",
|
||||
"description": "这些设置在“隐私”页面。",
|
||||
"button": "隐私"
|
||||
},
|
||||
"a11y_links": {
|
||||
"text": {
|
||||
"label": "密度和按钮标签",
|
||||
"description": "列表行的疏密以及按钮是否显示名称,在“外观”页面设置。"
|
||||
},
|
||||
"light_mode": {
|
||||
"label": "邮件使用浅色背景",
|
||||
"description": "深色模式下邮件是否使用浅色背景,在“阅读”页面设置。"
|
||||
},
|
||||
"calendar": {
|
||||
"label": "日历中的事件预览",
|
||||
"description": "指向事件时是否显示详情,在“日历”页面设置。"
|
||||
},
|
||||
"list": {
|
||||
"label": "会话和预览行",
|
||||
"description": "按会话分组以及每封邮件下方的预览行,在“邮件列表”页面设置。"
|
||||
},
|
||||
"reading": {
|
||||
"label": "标记为已读",
|
||||
"description": "邮件何时算作已读,以及删除或移动后跳到哪里,在“阅读”页面设置。"
|
||||
},
|
||||
"send_warnings_description": "撤销发送,以及缺少主题、忘记附件或占位文字的提醒。",
|
||||
"bulk_delete": {
|
||||
"label": "删除多封邮件前询问",
|
||||
"description": "在“邮件列表”页面设置。"
|
||||
},
|
||||
"sounds": {
|
||||
"label": "声音和未读标记",
|
||||
"description": "通知声音和标签页上的未读数,在“通知”页面设置。"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
"title": "关于 {appName}"
|
||||
},
|
||||
"sign_out": {
|
||||
"description": "在此设备上退出此账户。"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
"title": "Bulwark Webmail 设置",
|
||||
"subtitle": "在浏览器中配置你的网页邮箱实例。",
|
||||
|
||||
Reference in New Issue
Block a user