refactor: combine sidebar apps and plugins into add-ons, about and debug into backup and reset

This commit is contained in:
Linus Rath
2026-10-19 18:03:42 +02:00
parent 0b3bd26c9c
commit dc521e11ca
36 changed files with 385 additions and 141 deletions
+68 -52
View File
@@ -13,6 +13,7 @@ import { cn } from '@/lib/utils';
import { getPathPrefix, withBasePath } from '@/lib/browser-navigation';
import { clearCachedData } from '@/lib/clear-cached-data';
import { SpamSiegeGame } from './spam-siege-game';
import { DebugSettings } from './debug-settings';
const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || "0.0.0";
const GIT_COMMIT = process.env.NEXT_PUBLIC_GIT_COMMIT || "unknown";
@@ -49,24 +50,20 @@ function VersionUpdateTag() {
);
}
export function AboutDataSettings() {
/**
* The About card, in the settings sidebar's footer: 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.
*/
export function AboutFooter() {
const t = useTranslations('settings.advanced');
const tCommon = useTranslations('common');
const tSettings = useTranslations('settings');
const { settingsSyncDisabled, updateSetting, resetToDefaults, exportSettings, importSettings } =
useSettingsStore();
// A modified build must offer its own source (AGPL-3.0 §13), so a
// configured URL replaces the upstream repository link.
const { settingsSyncEnabled, sourceCodeUrl, appName, appLogoLightUrl, appLogoDarkUrl } = useConfig();
const { sourceCodeUrl, appName, appLogoLightUrl, appLogoDarkUrl } = useConfig();
// A branded deployment (APP_NAME / APP_LOGO_*) shows its own name and logo
// here; without them the card keeps the Bulwark mark and title. (#1160)
const branded = appName && appName !== 'Webmail';
const logoLight = withBasePath(appLogoLightUrl || appLogoDarkUrl) || `${getPathPrefix()}/branding/Bulwark_Logo_Color.svg`;
const logoDark = withBasePath(appLogoDarkUrl || appLogoLightUrl) || `${getPathPrefix()}/branding/Bulwark_Logo_White.svg`;
const [showResetConfirm, setShowResetConfirm] = useState(false);
const [showRefreshConfirm, setShowRefreshConfirm] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const { isFeatureEnabled } = usePolicyStore();
const [showGame, setShowGame] = useState(false);
const logoClickCount = useRef(0);
const logoClickTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
@@ -82,6 +79,58 @@ export function AboutDataSettings() {
}
};
return (
<div data-testid="settings-about" className="px-3 py-2">
{showGame && <SpamSiegeGame onClose={() => setShowGame(false)} />}
<div className="flex items-center gap-3">
<button onClick={handleLogoClick} className="flex items-center gap-3 flex-1 min-w-0 text-start focus:outline-none group/about cursor-pointer" aria-label="About">
<img
src={logoLight}
alt={branded ? appName : 'Bulwark'}
className="w-7 h-7 shrink-0 object-contain dark:hidden group-active/about:scale-95 transition-transform"
/>
<img
src={logoDark}
alt={branded ? appName : 'Bulwark'}
className="w-7 h-7 shrink-0 object-contain hidden dark:block group-active/about:scale-95 transition-transform"
/>
<div className="min-w-0">
<p className="text-xs font-medium text-foreground truncate">{branded ? appName : t('about.title')}</p>
<p className="text-[11px] text-muted-foreground truncate">
v{APP_VERSION} <span className="text-muted-foreground/60">({GIT_COMMIT})</span>
<VersionUpdateTag />
</p>
</div>
</button>
<a
href={sourceCodeUrl || UPSTREAM_SOURCE_URL}
target="_blank"
rel="noopener noreferrer"
className="shrink-0 inline-flex items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground transition-colors"
>
{sourceCodeUrl ? t('about.source_code') : 'GitHub'} <ExternalLink className="w-3 h-3" />
</a>
</div>
</div>
);
}
/**
* 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.
*/
export function AboutDataSettings() {
const t = useTranslations('settings.advanced');
const tCommon = useTranslations('common');
const tSettings = useTranslations('settings');
const { settingsSyncDisabled, updateSetting, resetToDefaults, exportSettings, importSettings } =
useSettingsStore();
const { settingsSyncEnabled } = useConfig();
const [showResetConfirm, setShowResetConfirm] = useState(false);
const [showRefreshConfirm, setShowRefreshConfirm] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const { isFeatureEnabled } = usePolicyStore();
const handleExport = () => {
const settingsJson = exportSettings();
const blob = new Blob([settingsJson], { type: 'application/json' });
@@ -137,45 +186,8 @@ export function AboutDataSettings() {
};
return (
<>
{showGame && <SpamSiegeGame onClose={() => setShowGame(false)} />}
<div className="rounded-lg border border-border bg-card p-5 mb-6">
<div className="flex items-center gap-4">
<button onClick={handleLogoClick} className="flex items-center gap-4 flex-1 text-start focus:outline-none group/about cursor-pointer" aria-label="About">
<div className="shrink-0">
<img
src={logoLight}
alt={branded ? appName : 'Bulwark'}
className="w-12 h-12 object-contain dark:hidden group-hover/about:scale-105 group-active/about:scale-95 transition-transform"
/>
<img
src={logoDark}
alt={branded ? appName : 'Bulwark'}
className="w-12 h-12 object-contain hidden dark:block group-hover/about:scale-105 group-active/about:scale-95 transition-transform"
/>
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-foreground">
{branded ? appName : t('about.title')}
</p>
<p className="text-xs text-muted-foreground group-hover/about:translate-x-0.5 group-active/about:translate-y-px transition-transform">
v{APP_VERSION} <span className="text-muted-foreground/60">({GIT_COMMIT})</span>
<VersionUpdateTag />
</p>
</div>
</button>
<a
href={sourceCodeUrl || UPSTREAM_SOURCE_URL}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors"
>
{sourceCodeUrl ? t('about.source_code') : 'GitHub'} <ExternalLink className="w-3 h-3" />
</a>
</div>
</div>
<SettingsSection title={t('title')} description={t('description')}>
<div className="space-y-8">
<SettingsSection id="backup" title={t('sections.backup')} description={t('sections.backup_description')}>
{settingsSyncEnabled && (
<SettingItem label={t('settings_sync.label')} description={t('settings_sync.description')}>
<ToggleSwitch checked={!settingsSyncDisabled} onChange={(checked) => updateSetting('settingsSyncDisabled', !checked)} />
@@ -206,7 +218,9 @@ export function AboutDataSettings() {
</>
</SettingItem>
)}
</SettingsSection>
<SettingsSection id="fix" title={t('sections.fix')} description={t('sections.fix_description')}>
<SettingItem label={t('refresh_cache.label')} description={t('refresh_cache.description')}>
<Button
variant={showRefreshConfirm ? 'default' : 'outline'}
@@ -227,6 +241,8 @@ export function AboutDataSettings() {
</Button>
</SettingItem>
</SettingsSection>
</>
<DebugSettings />
</div>
);
}
+20
View File
@@ -0,0 +1,20 @@
"use client";
import { SidebarAppsSettings } from './sidebar-apps-settings';
import { PluginsSettings } from './plugins-settings';
import { usePolicyStore } from '@/stores/policy-store';
/**
* The Add-ons page: sidebar apps and plugins, the two ways to add things to
* Bulwark. Plugins with a page of their own appear under this entry in the
* sidebar instead of forming a group.
*/
export function AddonsSettings() {
const { isFeatureEnabled } = usePolicyStore();
return (
<div className="space-y-8">
{isFeatureEnabled('sidebarAppsEnabled') && <SidebarAppsSettings />}
{isFeatureEnabled('pluginsEnabled') && <PluginsSettings />}
</div>
);
}
+4 -8
View File
@@ -10,16 +10,12 @@ export function DebugSettings() {
const { debugMode, debugCategories, updateSetting } = useSettingsStore();
const { isSettingLocked, isSettingHidden, isFeatureEnabled } = usePolicyStore();
if (isSettingHidden('debugMode') || !isFeatureEnabled('debugModeEnabled')) {
return (
<SettingsSection title={t('debug_mode.label')} description={t('debug_mode.description')}>
<p className="text-sm text-muted-foreground py-2">{t('debug_mode.description')}</p>
</SettingsSection>
);
}
// A section of the Backup & reset page now; nothing to show when the admin
// keeps debug logging off.
if (isSettingHidden('debugMode') || !isFeatureEnabled('debugModeEnabled')) return null;
return (
<SettingsSection title={t('debug_mode.label')} description={t('debug_mode.description')}>
<SettingsSection id="debug" advanced title={t('debug_mode.label')} description={t('debug_mode.description')}>
<SettingItem label={t('debug_mode.label')} description={t('debug_mode.description')} locked={isSettingLocked('debugMode')}>
<ToggleSwitch checked={debugMode} onChange={(checked) => updateSetting('debugMode', checked)} />
</SettingItem>
+1
View File
@@ -112,6 +112,7 @@ export function PluginsSettings() {
return (
<SettingsSection
id="plugins"
title={t('title')}
description={demoMode ? t('description_demo') : uploadsAllowed ? t('description_uploads') : t('description')}
>
+21 -25
View File
@@ -18,14 +18,12 @@ import {
FolderOpen,
HardDrive,
BookUser,
PanelLeftClose,
Bell,
Puzzle,
BookOpen,
PenLine,
LayoutList,
Info,
Bug,
Database,
X,
type AppIcon,
} from '@/components/icons';
@@ -43,15 +41,13 @@ 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 } from '@/components/settings/about-data-settings';
import { DebugSettings } from '@/components/settings/debug-settings';
import { AboutDataSettings, AboutFooter } from '@/components/settings/about-data-settings';
import { OrganizeSettings } from '@/components/settings/organize-settings';
import { AccountSecuritySettings } from '@/components/settings/account-security-settings';
import { FilesSettingsComponent } from '@/components/settings/files-settings';
import { ContactsSettings } from '@/components/settings/contacts-settings';
import { SidebarAppsSettings } from '@/components/settings/sidebar-apps-settings';
import { AddonsSettings } from '@/components/settings/addons-settings';
import { NotificationSettings } from '@/components/settings/notification-settings';
import { PluginsSettings } from '@/components/settings/plugins-settings';
import { PluginIframeSlot } from '@/components/plugins/plugin-iframe-slot';
import { offersForSlot as pluginOffersForSlot, subscribe as pluginRegistrySubscribe, get as getActivePlugin } from '@/lib/plugin-sandbox/registry';
import { AdvancedSettingsProvider, AdvancedSettingsSwitch } from '@/components/settings/settings-section';
@@ -128,10 +124,8 @@ const tabIcons: Record<Tab, AppIcon> = {
calendar: Calendar,
contacts: BookUser,
files: HardDrive,
sidebar_apps: PanelLeftClose,
about_data: Info,
plugins: Puzzle,
debug: Bug,
addons: Puzzle,
backup: Database,
};
const tabGroupOrder: TabGroup[] = ['general', 'appearance', 'mail', 'privacy', 'apps', 'plugins', 'advanced'];
@@ -141,7 +135,7 @@ const tabGroupOrder: TabGroup[] = ['general', 'appearance', 'mail', 'privacy', '
// land on the page that now holds the setting.
export const LEGACY_TAB_MAP: Record<string, Tab> = {
email: 'reading',
advanced: 'about_data',
advanced: 'backup',
layout: 'appearance',
themes: 'appearance',
content_senders: 'reading',
@@ -152,6 +146,10 @@ export const LEGACY_TAB_MAP: Record<string, Tab> = {
folders: 'organize',
keywords: 'organize',
downloads: 'reading',
sidebar_apps: 'addons',
plugins: 'addons',
about_data: 'backup',
debug: 'backup',
};
function readPersistedTab(): SettingsTabId {
@@ -323,7 +321,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
: '';
return `${p.name} ${p.description} ${p.author} ${fieldText}`;
}).join(' ');
haystacks.plugins = `${haystacks.plugins ?? ''} ${haystackText}`.toLowerCase();
haystacks.addons = `${haystacks.addons ?? ''} ${haystackText}`.toLowerCase();
const pluginSubs: SubResult[] = installedPlugins.flatMap((p) => {
const items: SubResult[] = [{ label: p.name, description: p.description }];
if (p.settingsSchema) {
@@ -337,7 +335,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
}
return items;
});
subs.plugins = [...(subs.plugins ?? []), ...pluginSubs];
subs.addons = [...(subs.addons ?? []), ...pluginSubs];
}
// Themes are a section of Appearance now, so installed themes match there.
if (installedThemes.length) {
@@ -350,9 +348,9 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
}
if (sidebarAppsList.length) {
const text = sidebarAppsList.map((a) => `${a.name} ${a.url}`).join(' ');
haystacks.sidebar_apps = `${haystacks.sidebar_apps ?? ''} ${text}`.toLowerCase();
subs.sidebar_apps = [
...(subs.sidebar_apps ?? []),
haystacks.addons = `${haystacks.addons ?? ''} ${text}`.toLowerCase();
subs.addons = [
...(subs.addons ?? []),
...sidebarAppsList.map((a) => ({ label: a.name, description: a.url })),
];
}
@@ -569,7 +567,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
...(supportsCalendar && isFeatureEnabled('calendarEnabled') ? [{ id: 'calendar' as Tab, label: t('tabs.calendar'), icon: tabIcons.calendar, group: 'apps' as TabGroup }] : []),
...(isFeatureEnabled('contactsEnabled') ? [{ id: 'contacts' as Tab, label: t('tabs.contacts'), icon: tabIcons.contacts, group: 'apps' as TabGroup }] : []),
...(supportsFiles && isFeatureEnabled('filesEnabled') ? [{ id: 'files' as Tab, label: t('tabs.files'), icon: tabIcons.files, group: 'apps' as TabGroup }] : []),
...(isFeatureEnabled('sidebarAppsEnabled') ? [{ id: 'sidebar_apps' as Tab, label: t('tabs.sidebar_apps'), icon: tabIcons.sidebar_apps, group: 'apps' as TabGroup }] : []),
...(isFeatureEnabled('sidebarAppsEnabled') || isFeatureEnabled('pluginsEnabled') ? [{ id: 'addons' as Tab, label: t('tabs.addons'), icon: tabIcons.addons, group: 'apps' as TabGroup }] : []),
// Plugin-contributed settings pages: one entry per active plugin that
// offers a `settings-section` slot (e.g. S/MIME key & certificate manager).
// A plugin that also has a page of its own gets that entry only, with the
@@ -590,9 +588,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
})),
// Advanced
{ id: 'about_data', label: t('tabs.about_data'), icon: tabIcons.about_data, group: 'advanced' },
...(isFeatureEnabled('pluginsEnabled') ? [{ id: 'plugins' as Tab, label: 'Plugins', icon: tabIcons.plugins, group: 'advanced' as TabGroup }] : []),
...(isFeatureEnabled('debugModeEnabled') ? [{ id: 'debug' as Tab, label: t('tabs.debug'), icon: tabIcons.debug, group: 'advanced' as TabGroup }] : []),
{ id: 'backup', label: t('tabs.backup'), icon: tabIcons.backup, group: 'advanced' },
];
// In scoped (shared-account) mode, restrict to the account-relevant tabs the
@@ -713,10 +709,8 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
: <><ContactsSettings /><div className="mt-8"><AddressBookManagementSettings /></div></>
)}
{effectiveActiveTab === 'files' && <FilesSettingsComponent />}
{effectiveActiveTab === 'sidebar_apps' && <SidebarAppsSettings />}
{effectiveActiveTab === 'about_data' && <AboutDataSettings />}
{effectiveActiveTab === 'plugins' && <PluginsSettings />}
{effectiveActiveTab === 'debug' && <DebugSettings />}
{effectiveActiveTab === 'addons' && <AddonsSettings />}
{effectiveActiveTab === 'backup' && <AboutDataSettings />}
{effectiveActiveTab.startsWith('plugin:') && (
<PluginIframeSlot
key={effectiveActiveTab}
@@ -875,6 +869,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
<div className="border-t border-border px-2 py-2">
<AdvancedSettingsSwitch />
<AboutFooter />
</div>
<div className="border-t border-border px-5 py-3">
<button
@@ -1033,6 +1028,7 @@ export function SettingsApp({ linkSegments: routeSegments }: SettingsAppProps =
</div>
<div className="border-t border-border px-1 py-1">
<AdvancedSettingsSwitch />
<AboutFooter />
</div>
</div>
+11 -13
View File
@@ -194,7 +194,7 @@ function ManagedAppsSection() {
if (managedApps.length === 0) return null;
return (
<SettingsSection title={t("managed_title")} description={t("managed_description")}>
<SettingsSection id="managed-apps" title={t("managed_title")} description={t("managed_description")}>
<div className="space-y-3">
{managedApps.map((app) => {
const AppIcon = iconForName(app.icon);
@@ -289,18 +289,7 @@ export function SidebarAppsSettings() {
return (
<>
<SettingsSection title={t("title")} description={t("description")}>
<SettingItem label={t("keep_loaded")} description={t("keep_loaded_description")}>
<ToggleSwitch
checked={keepAppsLoaded}
onChange={(v) => updateSetting("keepAppsLoaded", v)}
/>
</SettingItem>
</SettingsSection>
<ManagedAppsSection />
<SettingsSection title={t("manage_title")} description={t("manage_description")}>
<SettingsSection id="sidebar-apps" title={t("title")} description={t("description")}>
<div className="space-y-3">
{sidebarApps.length === 0 && !showAddForm && (
<p className="text-sm text-muted-foreground py-4 text-center">{tApps("no_apps_hint")}</p>
@@ -387,8 +376,17 @@ export function SidebarAppsSettings() {
</Button>
)}
</div>
<SettingItem advanced label={t("keep_loaded")} description={t("keep_loaded_description")}>
<ToggleSwitch
checked={keepAppsLoaded}
onChange={(v) => updateSetting("keepAppsLoaded", v)}
/>
</SettingItem>
</SettingsSection>
<ManagedAppsSection />
<ConfirmDialog {...confirmDialogProps} />
</>
);
+2 -3
View File
@@ -38,13 +38,12 @@ import { RELEASE_VERSION } from '@/lib/release-version';
* opens the page that holds the setting now (the settings page migrates
* the id). Listed so an unknown tab is refused here.
*/
const LEGACY_SETTINGS_TABS = ['layout', 'themes', 'content_senders', 'identities', 'language', 'protocol_handlers', 'templates', 'folders', 'keywords', 'downloads'] as const;
const LEGACY_SETTINGS_TABS = ['layout', 'themes', 'content_senders', 'identities', 'language', 'protocol_handlers', 'templates', 'folders', 'keywords', 'downloads', 'sidebar_apps', 'about_data', 'plugins', 'debug'] as const;
const SETTINGS_TABS: readonly (SettingsSearchTab | typeof LEGACY_SETTINGS_TABS[number])[] = [
'account', 'general', 'notifications', 'appearance', 'message_list', 'reading',
'composing', 'vacation', 'filters',
'organize', 'security', 'calendar', 'contacts',
'files', 'sidebar_apps', 'about_data',
'plugins', 'debug',
'files', 'addons', 'backup',
...LEGACY_SETTINGS_TABS,
];
+6 -12
View File
@@ -17,10 +17,8 @@ export type SettingsSearchTab =
| 'calendar'
| 'contacts'
| 'files'
| 'sidebar_apps'
| 'about_data'
| 'plugins'
| 'debug';
| 'addons'
| 'backup';
type Tab = SettingsSearchTab;
@@ -125,10 +123,8 @@ export const tabSearchPaths: Record<Tab, string[]> = {
calendar: ['calendar.settings', 'calendar.management'],
contacts: ['settings.contacts', 'contacts'],
files: ['settings.files'],
sidebar_apps: ['settings.sidebar_apps', 'sidebar_apps'],
about_data: ['settings.advanced'],
plugins: [],
debug: ['settings.advanced'],
addons: ['settings.sidebar_apps', 'sidebar_apps', 'plugins.settings'],
backup: ['settings.advanced'],
};
// Extra English keywords per tab so common search terms hit even when the
@@ -151,10 +147,8 @@ export const tabKeywords: Record<Tab, string> = {
calendar: 'event schedule appointment meeting timezone invitation ics tasks birthday working hours',
contacts: 'address book contact',
files: 'attachments cloud drive storage upload',
sidebar_apps: 'apps webview iframe',
about_data: 'export import storage quota privacy backup',
plugins: 'extensions addons',
debug: 'logs developer console diagnostic',
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',
};
export function flattenStrings(node: unknown, sink: string[]): void {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "التصحيح",
"message_list": "قائمة الرسائل",
"general": "عام",
"organize": "المجلدات والوسوم"
"organize": "المجلدات والوسوم",
"addons": "الإضافات",
"backup": "النسخ الاحتياطي وإعادة الضبط"
},
"themes": {
"title": "السمات",
@@ -2236,6 +2238,12 @@
"delivered": "مُسلَّمة",
"mistakes": "أخطاء"
}
},
"sections": {
"backup": "نسخة احتياطية من الإعدادات",
"backup_description": "احتفظ بإعداداتك عبر المتصفحات والأجهزة.",
"fix": "إصلاح المشكلات",
"fix_description": "عندما يبدو العرض قديمًا أو خاطئًا، أو عندما تريد البدء من جديد."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "Depuració",
"message_list": "Llista de missatges",
"general": "General",
"organize": "Carpetes i etiquetes"
"organize": "Carpetes i etiquetes",
"addons": "Complements",
"backup": "Còpia de seguretat i restabliment"
},
"themes": {
"title": "Temes",
@@ -2236,6 +2238,12 @@
"delivered": "Lliurats",
"mistakes": "Errors"
}
},
"sections": {
"backup": "Còpia de seguretat de la configuració",
"backup_description": "Conserveu la configuració entre navegadors i dispositius.",
"fix": "Solucionar problemes",
"fix_description": "Quan una vista sembla antiga o incorrecta, o voleu començar de nou."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Ladění",
"message_list": "Seznam zpráv",
"general": "Obecné",
"organize": "Složky a štítky"
"organize": "Složky a štítky",
"addons": "Doplňky",
"backup": "Záloha a obnovení"
},
"themes": {
"title": "Motivy",
@@ -2229,6 +2231,12 @@
"delivered": "Doručeno",
"mistakes": "Chyby"
}
},
"sections": {
"backup": "Záloha nastavení",
"backup_description": "Zachovejte nastavení napříč prohlížeči a zařízeními.",
"fix": "Řešení potíží",
"fix_description": "Když zobrazení vypadá zastarale nebo chybně, nebo chcete začít znovu."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "Debug",
"message_list": "Beskedliste",
"general": "Generelt",
"organize": "Mapper og tags"
"organize": "Mapper og tags",
"addons": "Tilføjelser",
"backup": "Sikkerhedskopi og nulstilling"
},
"themes": {
"title": "Temaer",
@@ -2232,6 +2234,12 @@
"delivered": "Leveret",
"mistakes": "Fejl"
}
},
"sections": {
"backup": "Sikkerhedskopi af indstillinger",
"backup_description": "Behold dine indstillinger på tværs af browsere og enheder.",
"fix": "Løs problemer",
"fix_description": "Når en visning ser forældet eller forkert ud, eller du vil starte forfra."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Debug",
"message_list": "Nachrichtenliste",
"general": "Allgemein",
"organize": "Ordner & Tags"
"organize": "Ordner & Tags",
"addons": "Erweiterungen",
"backup": "Sicherung & Zurücksetzen"
},
"themes": {
"title": "Designs",
@@ -2229,6 +2231,12 @@
"delivered": "Zugestellt",
"mistakes": "Fehler"
}
},
"sections": {
"backup": "Einstellungen sichern",
"backup_description": "Ihre Einstellungen über Browser und Geräte hinweg behalten.",
"fix": "Probleme beheben",
"fix_description": "Wenn eine Ansicht veraltet oder falsch aussieht oder Sie neu anfangen möchten."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "Debug",
"message_list": "Message list",
"general": "General",
"organize": "Folders & tags"
"organize": "Folders & tags",
"addons": "Add-ons",
"backup": "Backup & reset"
},
"themes": {
"title": "Themes",
@@ -2236,6 +2238,12 @@
"delivered": "Delivered",
"mistakes": "Mistakes"
}
},
"sections": {
"backup": "Settings backup",
"backup_description": "Keep your settings across browsers and devices.",
"fix": "Fix problems",
"fix_description": "When a view looks stale or wrong, or you want to start over."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Depuración",
"message_list": "Lista de mensajes",
"general": "General",
"organize": "Carpetas y etiquetas"
"organize": "Carpetas y etiquetas",
"addons": "Complementos",
"backup": "Copia de seguridad y restablecer"
},
"themes": {
"title": "Temas",
@@ -2229,6 +2231,12 @@
"delivered": "Entregados",
"mistakes": "Errores"
}
},
"sections": {
"backup": "Copia de seguridad de los ajustes",
"backup_description": "Conserve sus ajustes entre navegadores y dispositivos.",
"fix": "Solucionar problemas",
"fix_description": "Cuando una vista parece desactualizada o incorrecta, o quiere empezar de cero."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "Silumine",
"message_list": "Kirjade loend",
"general": "Üldine",
"organize": "Kaustad ja sildid"
"organize": "Kaustad ja sildid",
"addons": "Lisad",
"backup": "Varundus ja lähtestamine"
},
"themes": {
"title": "Teemad",
@@ -2236,6 +2238,12 @@
"delivered": "Kohale jõudnud",
"mistakes": "Vead"
}
},
"sections": {
"backup": "Seadete varundus",
"backup_description": "Säilita oma seaded brauserite ja seadmete vahel.",
"fix": "Probleemide lahendamine",
"fix_description": "Kui vaade tundub aegunud või vale või soovid otsast alustada."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "اشکال‌زدایی",
"message_list": "فهرست پیام‌ها",
"general": "عمومی",
"organize": "پوشه‌ها و برچسب‌ها"
"organize": "پوشه‌ها و برچسب‌ها",
"addons": "افزونه‌ها",
"backup": "پشتیبان‌گیری و بازنشانی"
},
"themes": {
"title": "پوسته‌ها",
@@ -2236,6 +2238,12 @@
"delivered": "تحویل‌شده",
"mistakes": "اشتباه‌ها"
}
},
"sections": {
"backup": "پشتیبان تنظیمات",
"backup_description": "تنظیمات خود را در مرورگرها و دستگاه‌های مختلف حفظ کنید.",
"fix": "رفع مشکلات",
"fix_description": "وقتی نمایی قدیمی یا نادرست به نظر می‌رسد یا می‌خواهید از نو شروع کنید."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Débogage",
"message_list": "Liste des messages",
"general": "Général",
"organize": "Dossiers et étiquettes"
"organize": "Dossiers et étiquettes",
"addons": "Extensions",
"backup": "Sauvegarde et réinitialisation"
},
"themes": {
"title": "Thèmes",
@@ -2229,6 +2231,12 @@
"delivered": "Distribués",
"mistakes": "Erreurs"
}
},
"sections": {
"backup": "Sauvegarde des paramètres",
"backup_description": "Conservez vos paramètres d'un navigateur et d'un appareil à l'autre.",
"fix": "Résoudre les problèmes",
"fix_description": "Quand une vue semble obsolète ou incorrecte, ou pour repartir de zéro."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1015,7 +1015,9 @@
"debug": "ניפוי שגיאות",
"message_list": "רשימת הודעות",
"general": "כללי",
"organize": "תיקיות ותגיות"
"organize": "תיקיות ותגיות",
"addons": "תוספים",
"backup": "גיבוי ואיפוס"
},
"themes": {
"title": "ערכות נושא",
@@ -2195,6 +2197,12 @@
"show_avatars_in_junk": {
"label": "הצג אווטרים בתיקייה זבל",
"description": "הצג תמונות פרופיל וייקוני אתרים עבור שולחים בתיקייה זבל. מושבת כברירת מחדל כדי להימנע מהענקת לגיטימציה חזותית לניסיונות דיוג."
},
"sections": {
"backup": "גיבוי הגדרות",
"backup_description": "שמירת ההגדרות שלך בין דפדפנים ומכשירים.",
"fix": "פתרון בעיות",
"fix_description": "כשתצוגה נראית מיושנת או שגויה, או כשרוצים להתחיל מחדש."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "Hibakeresés",
"message_list": "Üzenetlista",
"general": "Általános",
"organize": "Mappák és címkék"
"organize": "Mappák és címkék",
"addons": "Bővítmények",
"backup": "Biztonsági mentés és visszaállítás"
},
"themes": {
"title": "Témák",
@@ -2232,6 +2234,12 @@
"delivered": "Kézbesítve",
"mistakes": "Hibák"
}
},
"sections": {
"backup": "Beállítások mentése",
"backup_description": "Őrizze meg beállításait böngészők és eszközök között.",
"fix": "Hibák elhárítása",
"fix_description": "Ha egy nézet elavultnak vagy hibásnak tűnik, vagy elölről szeretné kezdeni."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Debug",
"message_list": "Elenco messaggi",
"general": "Generale",
"organize": "Cartelle ed etichette"
"organize": "Cartelle ed etichette",
"addons": "Componenti aggiuntivi",
"backup": "Backup e ripristino"
},
"themes": {
"title": "Temi",
@@ -2229,6 +2231,12 @@
"delivered": "Consegnati",
"mistakes": "Errori"
}
},
"sections": {
"backup": "Backup delle impostazioni",
"backup_description": "Conserva le impostazioni tra browser e dispositivi.",
"fix": "Risolvere i problemi",
"fix_description": "Quando una vista sembra obsoleta o sbagliata, o vuoi ricominciare da capo."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "デバッグ",
"message_list": "メッセージ一覧",
"general": "一般",
"organize": "フォルダーとタグ"
"organize": "フォルダーとタグ",
"addons": "アドオン",
"backup": "バックアップとリセット"
},
"themes": {
"title": "テーマ",
@@ -2229,6 +2231,12 @@
"delivered": "配信",
"mistakes": "ミス"
}
},
"sections": {
"backup": "設定のバックアップ",
"backup_description": "ブラウザーやデバイスをまたいで設定を保持します。",
"fix": "問題の解決",
"fix_description": "表示が古い、または正しくないとき、あるいは最初からやり直したいときに。"
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "디버그",
"message_list": "메시지 목록",
"general": "일반",
"organize": "폴더 및 태그"
"organize": "폴더 및 태그",
"addons": "추가 기능",
"backup": "백업 및 초기화"
},
"themes": {
"title": "테마",
@@ -2229,6 +2231,12 @@
"delivered": "배달",
"mistakes": "실수"
}
},
"sections": {
"backup": "설정 백업",
"backup_description": "브라우저와 기기 간에 설정을 유지합니다.",
"fix": "문제 해결",
"fix_description": "보기가 오래되었거나 잘못되었을 때, 또는 처음부터 다시 시작하고 싶을 때."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Atkļūdošana",
"message_list": "Ziņu saraksts",
"general": "Vispārīgi",
"organize": "Mapes un tagi"
"organize": "Mapes un tagi",
"addons": "Papildinājumi",
"backup": "Dublēšana un atiestatīšana"
},
"themes": {
"title": "Tēmas",
@@ -2229,6 +2231,12 @@
"delivered": "Piegādātas",
"mistakes": "Kļūdas"
}
},
"sections": {
"backup": "Iestatījumu dublējums",
"backup_description": "Saglabājiet iestatījumus dažādās pārlūkprogrammās un ierīcēs.",
"fix": "Problēmu novēršana",
"fix_description": "Ja skats izskatās novecojis vai nepareizs, vai vēlaties sākt no jauna."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "Дабаглах",
"message_list": "Мессежийн жагсаалт",
"general": "Ерөнхий",
"organize": "Хавтас ба шошго"
"organize": "Хавтас ба шошго",
"addons": "Нэмэлтүүд",
"backup": "Нөөцлөлт ба дахин тохируулах"
},
"themes": {
"title": "Сэдэв",
@@ -2236,6 +2238,12 @@
"delivered": "Хүргэсэн",
"mistakes": "Алдаа"
}
},
"sections": {
"backup": "Тохиргооны нөөцлөлт",
"backup_description": "Хөтөч болон төхөөрөмж хооронд тохиргоогоо хадгална.",
"fix": "Асуудал засах",
"fix_description": "Харагдац хуучирсан эсвэл буруу харагдах, эсвэл шинээр эхлэхийг хүсвэл."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "Feilsøking",
"message_list": "Meldingsliste",
"general": "Generelt",
"organize": "Mapper og etiketter"
"organize": "Mapper og etiketter",
"addons": "Tillegg",
"backup": "Sikkerhetskopi og tilbakestilling"
},
"themes": {
"title": "Temaer",
@@ -2236,6 +2238,12 @@
"delivered": "Levert",
"mistakes": "Feil"
}
},
"sections": {
"backup": "Sikkerhetskopi av innstillinger",
"backup_description": "Behold innstillingene dine på tvers av nettlesere og enheter.",
"fix": "Løs problemer",
"fix_description": "Når en visning ser utdatert eller feil ut, eller du vil starte på nytt."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Debuggen",
"message_list": "Berichtenlijst",
"general": "Algemeen",
"organize": "Mappen en tags"
"organize": "Mappen en tags",
"addons": "Add-ons",
"backup": "Back-up en herstel"
},
"themes": {
"title": "Thema's",
@@ -2229,6 +2231,12 @@
"delivered": "Bezorgd",
"mistakes": "Fouten"
}
},
"sections": {
"backup": "Back-up van instellingen",
"backup_description": "Bewaar uw instellingen over browsers en apparaten heen.",
"fix": "Problemen oplossen",
"fix_description": "Als een weergave verouderd of onjuist lijkt, of u opnieuw wilt beginnen."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Debugowanie",
"message_list": "Lista wiadomości",
"general": "Ogólne",
"organize": "Foldery i tagi"
"organize": "Foldery i tagi",
"addons": "Dodatki",
"backup": "Kopia zapasowa i reset"
},
"themes": {
"title": "Motywy",
@@ -2229,6 +2231,12 @@
"delivered": "Dostarczone",
"mistakes": "Błędy"
}
},
"sections": {
"backup": "Kopia zapasowa ustawień",
"backup_description": "Zachowaj ustawienia w różnych przeglądarkach i na różnych urządzeniach.",
"fix": "Rozwiązywanie problemów",
"fix_description": "Gdy widok wygląda na nieaktualny lub błędny albo chcesz zacząć od nowa."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Depuração",
"message_list": "Lista de mensagens",
"general": "Geral",
"organize": "Pastas e etiquetas"
"organize": "Pastas e etiquetas",
"addons": "Extras",
"backup": "Cópia de segurança e reposição"
},
"themes": {
"title": "Temas",
@@ -2229,6 +2231,12 @@
"delivered": "Entregues",
"mistakes": "Erros"
}
},
"sections": {
"backup": "Cópia de segurança das definições",
"backup_description": "Mantenha as suas definições entre navegadores e dispositivos.",
"fix": "Resolver problemas",
"fix_description": "Quando uma vista parece desatualizada ou errada, ou quer recomeçar."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "Depanare",
"message_list": "Lista de mesaje",
"general": "General",
"organize": "Dosare și etichete"
"organize": "Dosare și etichete",
"addons": "Suplimente",
"backup": "Copie de rezervă și resetare"
},
"themes": {
"title": "Teme",
@@ -2236,6 +2238,12 @@
"delivered": "Livrate",
"mistakes": "Greșeli"
}
},
"sections": {
"backup": "Copie de rezervă a setărilor",
"backup_description": "Păstrați-vă setările între browsere și dispozitive.",
"fix": "Rezolvarea problemelor",
"fix_description": "Când o vizualizare pare învechită sau greșită, sau vreți să o luați de la capăt."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Отладка",
"message_list": "Список писем",
"general": "Общие",
"organize": "Папки и метки"
"organize": "Папки и метки",
"addons": "Дополнения",
"backup": "Резервная копия и сброс"
},
"themes": {
"title": "Темы",
@@ -2229,6 +2231,12 @@
"delivered": "Доставлено",
"mistakes": "Ошибки"
}
},
"sections": {
"backup": "Резервная копия настроек",
"backup_description": "Сохраняйте настройки в разных браузерах и на разных устройствах.",
"fix": "Устранение проблем",
"fix_description": "Если данные выглядят устаревшими или неверными, или вы хотите начать заново."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "Ladenie",
"message_list": "Zoznam správ",
"general": "Všeobecné",
"organize": "Priečinky a štítky"
"organize": "Priečinky a štítky",
"addons": "Doplnky",
"backup": "Záloha a obnovenie"
},
"themes": {
"title": "Motívy",
@@ -2236,6 +2238,12 @@
"delivered": "Doručené",
"mistakes": "Chyby"
}
},
"sections": {
"backup": "Záloha nastavení",
"backup_description": "Zachovajte nastavenia naprieč prehliadačmi a zariadeniami.",
"fix": "Riešenie problémov",
"fix_description": "Keď zobrazenie vyzerá zastarano alebo chybne, alebo chcete začať odznova."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Hata Ayıklama",
"message_list": "İleti listesi",
"general": "Genel",
"organize": "Klasörler ve etiketler"
"organize": "Klasörler ve etiketler",
"addons": "Eklentiler",
"backup": "Yedekleme ve sıfırlama"
},
"themes": {
"title": "Temalar",
@@ -2229,6 +2231,12 @@
"delivered": "Teslim edilen",
"mistakes": "Hatalar"
}
},
"sections": {
"backup": "Ayar yedeği",
"backup_description": "Ayarlarınızı tarayıcılar ve cihazlar arasında koruyun.",
"fix": "Sorunları giderme",
"fix_description": "Bir görünüm eski ya da yanlış göründüğünde ya da baştan başlamak istediğinizde."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "Налагодження",
"message_list": "Список листів",
"general": "Загальні",
"organize": "Теки та мітки"
"organize": "Теки та мітки",
"addons": "Додатки",
"backup": "Резервна копія та скидання"
},
"themes": {
"title": "Теми",
@@ -2229,6 +2231,12 @@
"delivered": "Доставлено",
"mistakes": "Помилки"
}
},
"sections": {
"backup": "Резервна копія налаштувань",
"backup_description": "Зберігайте налаштування в різних браузерах і на різних пристроях.",
"fix": "Усунення проблем",
"fix_description": "Якщо дані виглядають застарілими чи неправильними, або ви хочете почати спочатку."
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1053,7 +1053,9 @@
"debug": "除錯",
"message_list": "郵件清單",
"general": "一般",
"organize": "資料夾與標籤"
"organize": "資料夾與標籤",
"addons": "附加元件",
"backup": "備份與重設"
},
"themes": {
"title": "主題",
@@ -2236,6 +2238,12 @@
"delivered": "已送達",
"mistakes": "失誤"
}
},
"sections": {
"backup": "設定備份",
"backup_description": "在瀏覽器與裝置之間保留您的設定。",
"fix": "修正問題",
"fix_description": "當檢視看起來過時或有誤,或者您想重新開始時。"
}
},
"sidebar_apps": {
+9 -1
View File
@@ -1050,7 +1050,9 @@
"debug": "调试",
"message_list": "邮件列表",
"general": "常规",
"organize": "文件夹与标签"
"organize": "文件夹与标签",
"addons": "附加组件",
"backup": "备份与重置"
},
"themes": {
"title": "主题",
@@ -2229,6 +2231,12 @@
"delivered": "已送达",
"mistakes": "失误"
}
},
"sections": {
"backup": "设置备份",
"backup_description": "在浏览器和设备之间保留您的设置。",
"fix": "修复问题",
"fix_description": "当视图看起来过时或有误,或者您想重新开始时。"
}
},
"sidebar_apps": {