feat: enhance settings page with tab icons and grouping
feat(files-settings): improve file settings preview and layout feat(locales): update translations for tab groups and add preview labels
This commit is contained in:
+125
-38
@@ -3,7 +3,26 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from '@/i18n/navigation';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { ArrowLeft, ChevronRight, LogOut, Settings as SettingsIcon } from 'lucide-react';
|
||||
import {
|
||||
ArrowLeft,
|
||||
ChevronRight,
|
||||
LogOut,
|
||||
Settings as SettingsIcon,
|
||||
Palette,
|
||||
Mail,
|
||||
User,
|
||||
Shield,
|
||||
UserPen,
|
||||
PalmtreeIcon,
|
||||
Calendar,
|
||||
Filter,
|
||||
FileText,
|
||||
FolderOpen,
|
||||
Tags,
|
||||
HardDrive,
|
||||
Wrench,
|
||||
type LucideIcon,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { AppearanceSettings } from '@/components/settings/appearance-settings';
|
||||
import { EmailSettings } from '@/components/settings/email-settings';
|
||||
@@ -27,6 +46,32 @@ import { useConfig } from '@/hooks/use-config';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type Tab = 'appearance' | 'email' | 'account' | 'security' | 'identities' | 'vacation' | 'calendar' | 'filters' | 'templates' | 'folders' | 'keywords' | 'files' | 'advanced';
|
||||
type TabGroup = 'general' | 'account' | 'organization' | 'apps' | 'system';
|
||||
|
||||
interface TabDef {
|
||||
id: Tab;
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
group: TabGroup;
|
||||
}
|
||||
|
||||
const tabIcons: Record<Tab, LucideIcon> = {
|
||||
appearance: Palette,
|
||||
email: Mail,
|
||||
account: User,
|
||||
security: Shield,
|
||||
identities: UserPen,
|
||||
vacation: PalmtreeIcon,
|
||||
calendar: Calendar,
|
||||
filters: Filter,
|
||||
templates: FileText,
|
||||
folders: FolderOpen,
|
||||
keywords: Tags,
|
||||
files: HardDrive,
|
||||
advanced: Wrench,
|
||||
};
|
||||
|
||||
const tabGroupOrder: TabGroup[] = ['general', 'account', 'organization', 'apps', 'system'];
|
||||
|
||||
export default function SettingsPage() {
|
||||
const router = useRouter();
|
||||
@@ -69,22 +114,31 @@ export default function SettingsPage() {
|
||||
const supportsSieve = client?.supportsSieve() ?? false;
|
||||
const supportsFiles = client?.supportsFiles() ?? false;
|
||||
|
||||
const tabs: { id: Tab; label: string }[] = [
|
||||
{ id: 'appearance', label: t('tabs.appearance') },
|
||||
{ id: 'email', label: t('tabs.email') },
|
||||
{ id: 'account', label: t('tabs.account') },
|
||||
...(stalwartFeaturesEnabled ? [{ id: 'security' as Tab, label: t('tabs.security') }] : []),
|
||||
{ id: 'identities', label: t('tabs.identities') },
|
||||
...(supportsVacation ? [{ id: 'vacation' as Tab, label: t('tabs.vacation') }] : []),
|
||||
...(supportsCalendar ? [{ id: 'calendar' as Tab, label: t('tabs.calendar') }] : []),
|
||||
...(supportsSieve ? [{ id: 'filters' as Tab, label: t('tabs.filters') }] : []),
|
||||
{ id: 'templates', label: t('tabs.templates') },
|
||||
{ id: 'folders', label: t('tabs.folders') },
|
||||
{ id: 'keywords', label: t('tabs.keywords') },
|
||||
...(supportsFiles ? [{ id: 'files' as Tab, label: t('tabs.files') }] : []),
|
||||
{ id: 'advanced', label: t('tabs.advanced') },
|
||||
const tabs: TabDef[] = [
|
||||
{ id: 'appearance', label: t('tabs.appearance'), icon: tabIcons.appearance, group: 'general' },
|
||||
{ id: 'email', label: t('tabs.email'), icon: tabIcons.email, group: 'general' },
|
||||
{ id: 'account', label: t('tabs.account'), icon: tabIcons.account, group: 'account' },
|
||||
...(stalwartFeaturesEnabled ? [{ id: 'security' as Tab, label: t('tabs.security'), icon: tabIcons.security, group: 'account' as TabGroup }] : []),
|
||||
{ id: 'identities', label: t('tabs.identities'), icon: tabIcons.identities, group: 'account' },
|
||||
...(supportsVacation ? [{ id: 'vacation' as Tab, label: t('tabs.vacation'), icon: tabIcons.vacation, group: 'account' as TabGroup }] : []),
|
||||
...(supportsSieve ? [{ id: 'filters' as Tab, label: t('tabs.filters'), icon: tabIcons.filters, group: 'organization' as TabGroup }] : []),
|
||||
{ id: 'templates', label: t('tabs.templates'), icon: tabIcons.templates, group: 'organization' },
|
||||
{ id: 'folders', label: t('tabs.folders'), icon: tabIcons.folders, group: 'organization' },
|
||||
{ id: 'keywords', label: t('tabs.keywords'), icon: tabIcons.keywords, group: 'organization' },
|
||||
...(supportsCalendar ? [{ id: 'calendar' as Tab, label: t('tabs.calendar'), icon: tabIcons.calendar, group: 'apps' as TabGroup }] : []),
|
||||
...(supportsFiles ? [{ id: 'files' as Tab, label: t('tabs.files'), icon: tabIcons.files, group: 'apps' as TabGroup }] : []),
|
||||
{ id: 'advanced', label: t('tabs.advanced'), icon: tabIcons.advanced, group: 'system' },
|
||||
];
|
||||
|
||||
// Group tabs by category
|
||||
const groupedTabs = tabGroupOrder
|
||||
.map((group) => ({
|
||||
group,
|
||||
label: t(`tab_groups.${group}`),
|
||||
items: tabs.filter((tab) => tab.group === group),
|
||||
}))
|
||||
.filter((g) => g.items.length > 0);
|
||||
|
||||
const handleTabSelect = (tabId: Tab) => {
|
||||
setActiveTab(tabId);
|
||||
try { localStorage.setItem('settings-active-tab', tabId); } catch { /* ignore */ }
|
||||
@@ -167,15 +221,31 @@ export default function SettingsPage() {
|
||||
{/* Tab list */}
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<div className="py-2">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => handleTabSelect(tab.id)}
|
||||
className="w-full flex items-center justify-between px-5 py-3.5 text-sm text-foreground hover:bg-muted transition-colors duration-150"
|
||||
>
|
||||
<span>{tab.label}</span>
|
||||
<ChevronRight className="w-4 h-4 text-muted-foreground" />
|
||||
</button>
|
||||
{groupedTabs.map((group, groupIndex) => (
|
||||
<div key={group.group}>
|
||||
{groupIndex > 0 && <div className="mx-5 my-2 border-t border-border" />}
|
||||
<div className="px-5 pt-3 pb-1.5">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{group.label}
|
||||
</span>
|
||||
</div>
|
||||
{group.items.map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => handleTabSelect(tab.id)}
|
||||
className="w-full flex items-center justify-between px-5 py-3.5 text-sm text-foreground hover:bg-muted transition-colors duration-150"
|
||||
>
|
||||
<span className="flex items-center gap-3">
|
||||
<Icon className="w-4 h-4 text-muted-foreground" />
|
||||
{tab.label}
|
||||
</span>
|
||||
<ChevronRight className="w-4 h-4 text-muted-foreground" />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -227,20 +297,37 @@ export default function SettingsPage() {
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex-1 overflow-y-auto py-2">
|
||||
<div className="px-2 space-y-1">
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={cn(
|
||||
'w-full text-left px-3 py-2 rounded-md text-sm transition-colors duration-150',
|
||||
activeTab === tab.id
|
||||
? 'bg-accent text-accent-foreground font-medium'
|
||||
: 'hover:bg-muted text-foreground'
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
<div className="px-2 space-y-0.5">
|
||||
{groupedTabs.map((group, groupIndex) => (
|
||||
<div key={group.group}>
|
||||
{groupIndex > 0 && <div className="mx-1 my-2 border-t border-border" />}
|
||||
<div className="px-3 pt-2.5 pb-1">
|
||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{group.label}
|
||||
</span>
|
||||
</div>
|
||||
{group.items.map((tab) => {
|
||||
const Icon = tab.icon;
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={cn(
|
||||
'w-full text-left px-3 py-2 rounded-md text-sm transition-colors duration-150 flex items-center gap-2.5',
|
||||
activeTab === tab.id
|
||||
? 'bg-accent text-accent-foreground font-medium'
|
||||
: 'hover:bg-muted text-foreground'
|
||||
)}
|
||||
>
|
||||
<Icon className={cn(
|
||||
'w-4 h-4 shrink-0',
|
||||
activeTab === tab.id ? 'text-accent-foreground' : 'text-muted-foreground'
|
||||
)} />
|
||||
{tab.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,173 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Folder, FolderOpen, FileText, FileCode, ImageIcon, FileAudio, File, Home, ChevronRight, ChevronDown } from "lucide-react";
|
||||
import { SettingsSection, SettingItem, ToggleSwitch, RadioGroup } from "./settings-section";
|
||||
import { loadFilesSettings, saveFilesSettings, type FilesSettings, type FolderLayout } from "@/components/files/files-settings-dialog";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SampleFile {
|
||||
name: string;
|
||||
isFolder: boolean;
|
||||
size: number;
|
||||
modified: string;
|
||||
hidden?: boolean;
|
||||
thumbnailUrl?: string;
|
||||
}
|
||||
|
||||
const SAMPLE_FILES: SampleFile[] = [
|
||||
{ name: "Documents", isFolder: true, size: 0, modified: "2026-03-10" },
|
||||
{ name: "Photos", isFolder: true, size: 0, modified: "2026-03-14" },
|
||||
{ name: "report.pdf", isFolder: false, size: 245000, modified: "2026-03-15" },
|
||||
{ name: "notes.md", isFolder: false, size: 1200, modified: "2026-03-12" },
|
||||
{ name: "vacation.jpg", isFolder: false, size: 3400000, modified: "2026-03-08", thumbnailUrl: "/branding/Bulwark_Logo_Color.png" },
|
||||
{ name: "song.mp3", isFolder: false, size: 5200000, modified: "2026-03-01" },
|
||||
{ name: ".config", isFolder: false, size: 340, modified: "2026-02-20", hidden: true },
|
||||
];
|
||||
|
||||
function getPreviewIcon(file: SampleFile, colored: boolean, size: "sm" | "lg") {
|
||||
const cls = size === "sm" ? "w-4 h-4 flex-shrink-0" : "w-8 h-8 flex-shrink-0";
|
||||
|
||||
if (file.isFolder) {
|
||||
return <Folder className={cn(cls, colored ? "text-blue-500" : "text-muted-foreground")} />;
|
||||
}
|
||||
|
||||
const ext = file.name.split(".").pop()?.toLowerCase();
|
||||
switch (ext) {
|
||||
case "jpg": case "png": case "gif":
|
||||
return <ImageIcon className={cn(cls, colored ? "text-emerald-500" : "text-muted-foreground")} />;
|
||||
case "mp3": case "wav":
|
||||
return <FileAudio className={cn(cls, colored ? "text-purple-500" : "text-muted-foreground")} />;
|
||||
case "pdf":
|
||||
return <FileText className={cn(cls, colored ? "text-red-600" : "text-muted-foreground")} />;
|
||||
case "md": case "json": case "js": case "ts":
|
||||
return <FileCode className={cn(cls, colored ? "text-yellow-600" : "text-muted-foreground")} />;
|
||||
default:
|
||||
return <File className={cn(cls, "text-muted-foreground")} />;
|
||||
}
|
||||
}
|
||||
|
||||
function formatSize(bytes: number): string {
|
||||
if (bytes === 0) return "—";
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function FilesSettingsPreview({ settings }: { settings: FilesSettings }) {
|
||||
const sortedFiles = useMemo(() => {
|
||||
let files = SAMPLE_FILES.filter((f) => {
|
||||
if (!settings.showHiddenFiles && f.hidden) return false;
|
||||
if (settings.folderLayout === "sidebar" && f.isFolder) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
files.sort((a, b) => {
|
||||
// Folders first
|
||||
if (a.isFolder !== b.isFolder) return a.isFolder ? -1 : 1;
|
||||
|
||||
let cmp = 0;
|
||||
switch (settings.defaultSortKey) {
|
||||
case "name": cmp = a.name.localeCompare(b.name); break;
|
||||
case "size": cmp = a.size - b.size; break;
|
||||
case "modified": cmp = a.modified.localeCompare(b.modified); break;
|
||||
}
|
||||
return settings.defaultSortDir === "desc" ? -cmp : cmp;
|
||||
});
|
||||
|
||||
return files;
|
||||
}, [settings.showHiddenFiles, settings.folderLayout, settings.defaultSortKey, settings.defaultSortDir]);
|
||||
|
||||
const listView = (
|
||||
<div className="flex-1 min-w-0 overflow-hidden">
|
||||
<div className="flex items-center gap-3 px-2 py-1 text-[10px] font-medium text-muted-foreground border-b border-border bg-muted/50">
|
||||
<span className="flex-1 min-w-0">Name</span>
|
||||
<span className="w-14 text-right">Size</span>
|
||||
<span className="w-16 text-right">Modified</span>
|
||||
</div>
|
||||
{sortedFiles.map((file) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className={cn(
|
||||
"flex items-center gap-2 px-2 py-1.5 border-b border-border last:border-b-0 transition-colors hover:bg-muted/50",
|
||||
file.hidden && "opacity-50"
|
||||
)}
|
||||
>
|
||||
{settings.showThumbnails && file.thumbnailUrl ? (
|
||||
<img src={file.thumbnailUrl} alt="" className="w-4 h-4 rounded object-cover flex-shrink-0" />
|
||||
) : settings.showIcons ? (
|
||||
getPreviewIcon(file, settings.coloredIcons, "sm")
|
||||
) : null}
|
||||
<span className={cn("flex-1 min-w-0 truncate text-[11px]", file.isFolder && "font-medium")}>
|
||||
{file.name}
|
||||
</span>
|
||||
<span className="w-14 text-right text-[10px] text-muted-foreground tabular-nums">
|
||||
{formatSize(file.size)}
|
||||
</span>
|
||||
<span className="w-16 text-right text-[10px] text-muted-foreground tabular-nums">
|
||||
{file.modified.slice(5)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const gridView = (
|
||||
<div className="flex-1 min-w-0 p-2">
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(4.5rem,1fr))] gap-1.5">
|
||||
{sortedFiles.map((file) => (
|
||||
<div
|
||||
key={file.name}
|
||||
className={cn(
|
||||
"flex flex-col items-center gap-1 p-2 rounded-md transition-colors hover:bg-muted/50",
|
||||
file.hidden && "opacity-50"
|
||||
)}
|
||||
>
|
||||
{settings.showThumbnails && file.thumbnailUrl ? (
|
||||
<img src={file.thumbnailUrl} alt="" className="w-8 h-8 rounded object-cover flex-shrink-0" />
|
||||
) : settings.showIcons ? (
|
||||
getPreviewIcon(file, settings.coloredIcons, "lg")
|
||||
) : (
|
||||
<div className="w-8 h-8" />
|
||||
)}
|
||||
<span className={cn("text-[9px] truncate w-full text-center", file.isFolder && "font-medium")}>
|
||||
{file.name}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const sidebar = settings.folderLayout === "sidebar" && (
|
||||
<div className="w-24 border-r border-border bg-muted/30 py-1.5 flex-shrink-0">
|
||||
<div className="flex items-center gap-1 px-2 py-0.5 text-[10px] font-medium text-foreground">
|
||||
<Home className="w-3 h-3 flex-shrink-0" />
|
||||
<span className="truncate">Files</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 px-2 py-0.5 text-[10px] text-foreground bg-accent rounded-sm mx-1">
|
||||
<ChevronDown className="w-2.5 h-2.5 flex-shrink-0" />
|
||||
<FolderOpen className="w-3 h-3 flex-shrink-0 text-blue-500" />
|
||||
<span className="truncate">Documents</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 px-2 py-0.5 text-[10px] text-muted-foreground" style={{ paddingLeft: "1.25rem" }}>
|
||||
<ChevronRight className="w-2.5 h-2.5 flex-shrink-0" />
|
||||
<Folder className="w-3 h-3 flex-shrink-0 text-blue-500" />
|
||||
<span className="truncate">Photos</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-4 rounded-lg border border-border overflow-hidden bg-background text-xs select-none">
|
||||
<div className="flex" style={{ minHeight: "10rem" }}>
|
||||
{sidebar}
|
||||
{settings.defaultViewMode === "grid" ? gridView : listView}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function FilesSettingsComponent() {
|
||||
const t = useTranslations("settings.files");
|
||||
@@ -29,8 +193,14 @@ export function FilesSettingsComponent() {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<SettingsSection title={t("display.title")} description={t("display.description")}>
|
||||
<div>
|
||||
<div className="sticky top-0 z-10 bg-background pb-4 -mx-4 px-4 -mt-4 pt-4 lg:-mx-6 lg:px-6 lg:-mt-6 lg:pt-6 border-b border-border mb-6">
|
||||
<p className="text-sm font-medium text-foreground mb-1">{t("preview.label")}</p>
|
||||
<FilesSettingsPreview settings={settings} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-8">
|
||||
<SettingsSection title={t("display.title")} description={t("display.description")}>
|
||||
<SettingItem label={t("folder_layout.label")} description={t("folder_layout.description")}>
|
||||
<RadioGroup
|
||||
value={settings.folderLayout}
|
||||
@@ -104,6 +274,7 @@ export function FilesSettingsComponent() {
|
||||
/>
|
||||
</SettingItem>
|
||||
</SettingsSection>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -504,8 +504,10 @@
|
||||
},
|
||||
"tab_groups": {
|
||||
"general": "Allgemein",
|
||||
"account": "Konto",
|
||||
"organization": "Organisation"
|
||||
"account": "Konto & Identität",
|
||||
"organization": "E-Mail-Organisation",
|
||||
"apps": "Apps",
|
||||
"system": "System"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Darstellung",
|
||||
@@ -1112,6 +1114,9 @@
|
||||
"show_hidden": {
|
||||
"label": "Versteckte Dateien anzeigen",
|
||||
"description": "Dateien und Ordner anzeigen, die mit einem Punkt beginnen"
|
||||
},
|
||||
"preview": {
|
||||
"label": "Vorschau"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -504,8 +504,10 @@
|
||||
},
|
||||
"tab_groups": {
|
||||
"general": "General",
|
||||
"account": "Account",
|
||||
"organization": "Organization"
|
||||
"account": "Account & Identity",
|
||||
"organization": "Mail Organization",
|
||||
"apps": "Apps",
|
||||
"system": "System"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Appearance",
|
||||
@@ -1118,6 +1120,9 @@
|
||||
"show_hidden": {
|
||||
"label": "Show Hidden Files",
|
||||
"description": "Display files and folders that start with a dot"
|
||||
},
|
||||
"preview": {
|
||||
"label": "Preview"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -504,8 +504,10 @@
|
||||
},
|
||||
"tab_groups": {
|
||||
"general": "General",
|
||||
"account": "Cuenta",
|
||||
"organization": "Organización"
|
||||
"account": "Cuenta e identidad",
|
||||
"organization": "Organización del correo",
|
||||
"apps": "Aplicaciones",
|
||||
"system": "Sistema"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Apariencia",
|
||||
@@ -1112,6 +1114,9 @@
|
||||
"show_hidden": {
|
||||
"label": "Mostrar archivos ocultos",
|
||||
"description": "Mostrar archivos y carpetas que comienzan con un punto"
|
||||
},
|
||||
"preview": {
|
||||
"label": "Vista previa"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -504,8 +504,10 @@
|
||||
},
|
||||
"tab_groups": {
|
||||
"general": "Général",
|
||||
"account": "Compte",
|
||||
"organization": "Organisation"
|
||||
"account": "Compte & Identité",
|
||||
"organization": "Organisation des e-mails",
|
||||
"apps": "Applications",
|
||||
"system": "Système"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Apparence",
|
||||
@@ -1112,6 +1114,9 @@
|
||||
"show_hidden": {
|
||||
"label": "Afficher les fichiers cachés",
|
||||
"description": "Afficher les fichiers et dossiers commençant par un point"
|
||||
},
|
||||
"preview": {
|
||||
"label": "Aperçu"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -504,8 +504,10 @@
|
||||
},
|
||||
"tab_groups": {
|
||||
"general": "Generale",
|
||||
"account": "Account",
|
||||
"organization": "Organizzazione"
|
||||
"account": "Account e identità",
|
||||
"organization": "Organizzazione e-mail",
|
||||
"apps": "Applicazioni",
|
||||
"system": "Sistema"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Aspetto",
|
||||
@@ -1112,6 +1114,9 @@
|
||||
"show_hidden": {
|
||||
"label": "Mostra file nascosti",
|
||||
"description": "Visualizza file e cartelle che iniziano con un punto"
|
||||
},
|
||||
"preview": {
|
||||
"label": "Anteprima"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -504,8 +504,10 @@
|
||||
},
|
||||
"tab_groups": {
|
||||
"general": "一般",
|
||||
"account": "アカウント",
|
||||
"organization": "整理"
|
||||
"account": "アカウントと身元",
|
||||
"organization": "メール整理",
|
||||
"apps": "アプリ",
|
||||
"system": "システム"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "外観",
|
||||
@@ -1112,6 +1114,9 @@
|
||||
"show_hidden": {
|
||||
"label": "隠しファイルを表示",
|
||||
"description": "ドットで始まるファイルとフォルダーを表示します"
|
||||
},
|
||||
"preview": {
|
||||
"label": "プレビュー"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -504,8 +504,10 @@
|
||||
},
|
||||
"tab_groups": {
|
||||
"general": "Algemeen",
|
||||
"account": "Account",
|
||||
"organization": "Organisatie"
|
||||
"account": "Account & identiteit",
|
||||
"organization": "E-mailorganisatie",
|
||||
"apps": "Apps",
|
||||
"system": "Systeem"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Uiterlijk",
|
||||
@@ -1112,6 +1114,9 @@
|
||||
"show_hidden": {
|
||||
"label": "Verborgen bestanden tonen",
|
||||
"description": "Bestanden en mappen weergeven die beginnen met een punt"
|
||||
},
|
||||
"preview": {
|
||||
"label": "Voorbeeld"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -504,8 +504,10 @@
|
||||
},
|
||||
"tab_groups": {
|
||||
"general": "Geral",
|
||||
"account": "Conta",
|
||||
"organization": "Organização"
|
||||
"account": "Conta e identidade",
|
||||
"organization": "Organização de e-mail",
|
||||
"apps": "Aplicativos",
|
||||
"system": "Sistema"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Aparência",
|
||||
@@ -1112,6 +1114,9 @@
|
||||
"show_hidden": {
|
||||
"label": "Mostrar arquivos ocultos",
|
||||
"description": "Exibir arquivos e pastas que começam com um ponto"
|
||||
},
|
||||
"preview": {
|
||||
"label": "Visualização"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user