Merge branch 'dev' of https://github.com/bulwarkmail/webmail into dev
This commit is contained in:
@@ -51,9 +51,9 @@ export function EmailListItem({ email, selected, onClick, onContextMenu, onToggl
|
|||||||
const isFocusedMailLayout = mailLayout === 'focus';
|
const isFocusedMailLayout = mailLayout === 'focus';
|
||||||
const inlinePreview = showPreview && email.preview ? ` ${email.preview}` : '';
|
const inlinePreview = showPreview && email.preview ? ` ${email.preview}` : '';
|
||||||
|
|
||||||
// Resolve color tags using keyword definitions from settings
|
// Resolve color tags using keyword definitions from settings; unknown tags fall back to gray
|
||||||
const colorTagIds = getEmailColorTags(email.keywords);
|
const colorTagIds = getEmailColorTags(email.keywords);
|
||||||
const keywordDefs = colorTagIds.map(id => emailKeywords.find(k => k.id === id)).filter(Boolean) as typeof emailKeywords;
|
const keywordDefs = colorTagIds.map(id => emailKeywords.find(k => k.id === id) ?? { id, label: id, color: 'gray' });
|
||||||
// Use first tag for background coloring
|
// Use first tag for background coloring
|
||||||
const keywordDef = keywordDefs[0] ?? null;
|
const keywordDef = keywordDefs[0] ?? null;
|
||||||
const colorTag = keywordDef ? KEYWORD_PALETTE[keywordDef.color]?.bg ?? null : null;
|
const colorTag = keywordDef ? KEYWORD_PALETTE[keywordDef.color]?.bg ?? null : null;
|
||||||
|
|||||||
@@ -3070,13 +3070,13 @@ export function EmailViewer({
|
|||||||
<>
|
<>
|
||||||
<span className="flex items-center gap-0.5">
|
<span className="flex items-center gap-0.5">
|
||||||
{currentColors.slice(0, 3).map((tagId) => {
|
{currentColors.slice(0, 3).map((tagId) => {
|
||||||
const kw = emailKeywords.find(k => k.id === tagId);
|
const kw = emailKeywords.find(k => k.id === tagId) ?? { id: tagId, label: tagId, color: 'gray' };
|
||||||
return kw ? <span key={tagId} className={cn("w-3 h-3 rounded-full", KEYWORD_PALETTE[kw.color]?.dot)} /> : null;
|
return <span key={tagId} className={cn("w-3 h-3 rounded-full", KEYWORD_PALETTE[kw.color]?.dot || 'bg-gray-500')} />;
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
{showToolbarLabels && currentColors.length === 1 && (
|
{showToolbarLabels && currentColors.length === 1 && (
|
||||||
<span className="text-xs font-medium text-foreground">
|
<span className="text-xs font-medium text-foreground">
|
||||||
{emailKeywords.find(k => k.id === currentColors[0])?.label}
|
{emailKeywords.find(k => k.id === currentColors[0])?.label ?? currentColors[0]}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@@ -3674,11 +3674,11 @@ export function EmailViewer({
|
|||||||
{currentColors.length > 0 && (
|
{currentColors.length > 0 && (
|
||||||
<span className="flex items-center gap-0.5">
|
<span className="flex items-center gap-0.5">
|
||||||
{currentColors.map((tagId) => {
|
{currentColors.map((tagId) => {
|
||||||
const kw = emailKeywords.find(k => k.id === tagId);
|
const kw = emailKeywords.find(k => k.id === tagId) ?? { id: tagId, label: tagId, color: 'gray' };
|
||||||
const dotClass = kw ? KEYWORD_PALETTE[kw.color]?.dot : null;
|
const dotClass = KEYWORD_PALETTE[kw.color]?.dot || 'bg-gray-500';
|
||||||
return dotClass ? (
|
return (
|
||||||
<span key={tagId} className={cn("w-2.5 h-2.5 rounded-full flex-shrink-0", dotClass)} title={kw!.label} />
|
<span key={tagId} className={cn("w-2.5 h-2.5 rounded-full flex-shrink-0", dotClass)} title={kw.label} />
|
||||||
) : null;
|
);
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -67,9 +67,9 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
|
|||||||
const isFocusedMailLayout = mailLayout === 'focus';
|
const isFocusedMailLayout = mailLayout === 'focus';
|
||||||
const inlinePreview = showPreview && email.preview ? ` ${email.preview}` : '';
|
const inlinePreview = showPreview && email.preview ? ` ${email.preview}` : '';
|
||||||
|
|
||||||
// Resolve color tags using keyword definitions
|
// Resolve color tags using keyword definitions; unknown tags fall back to gray
|
||||||
const tagIds = getEmailColorTags(email.keywords);
|
const tagIds = getEmailColorTags(email.keywords);
|
||||||
const resolvedKeywordDefs = tagIds.map(id => emailKeywords.find(k => k.id === id)).filter(Boolean) as typeof emailKeywords;
|
const resolvedKeywordDefs = tagIds.map(id => emailKeywords.find(k => k.id === id) ?? { id, label: id, color: 'gray' });
|
||||||
const resolvedKeywordDef = resolvedKeywordDefs[0] ?? null;
|
const resolvedKeywordDef = resolvedKeywordDefs[0] ?? null;
|
||||||
const resolvedColorTag = (() => {
|
const resolvedColorTag = (() => {
|
||||||
if (colorTag) return colorTag;
|
if (colorTag) return colorTag;
|
||||||
@@ -375,7 +375,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
|
|||||||
|
|
||||||
const threadColor = getThreadColorTag(thread.emails);
|
const threadColor = getThreadColorTag(thread.emails);
|
||||||
const emailKeywordDefs = useSettingsStore((state) => state.emailKeywords);
|
const emailKeywordDefs = useSettingsStore((state) => state.emailKeywords);
|
||||||
const keywordDef = threadColor ? emailKeywordDefs.find(k => k.id === threadColor) : null;
|
const keywordDef = threadColor ? (emailKeywordDefs.find(k => k.id === threadColor) ?? { id: threadColor, label: threadColor, color: 'gray' }) : null;
|
||||||
const colorTag = keywordDef ? KEYWORD_PALETTE[keywordDef.color]?.bg ?? null : null;
|
const colorTag = keywordDef ? KEYWORD_PALETTE[keywordDef.color]?.bg ?? null : null;
|
||||||
|
|
||||||
const isSelected = selectedEmailId === latestEmail.id ||
|
const isSelected = selectedEmailId === latestEmail.id ||
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import type {
|
|||||||
} from "@/lib/jmap/sieve-types";
|
} from "@/lib/jmap/sieve-types";
|
||||||
import type { Mailbox } from "@/lib/jmap/types";
|
import type { Mailbox } from "@/lib/jmap/types";
|
||||||
import { buildMailboxTree, flattenMailboxTree, type MailboxNode, generateUUID } from "@/lib/utils";
|
import { buildMailboxTree, flattenMailboxTree, type MailboxNode, generateUUID } from "@/lib/utils";
|
||||||
|
import { useSettingsStore } from "@/stores/settings-store";
|
||||||
|
|
||||||
interface FilterRuleModalProps {
|
interface FilterRuleModalProps {
|
||||||
rule?: FilterRule;
|
rule?: FilterRule;
|
||||||
@@ -58,6 +59,7 @@ export function FilterRuleModal({
|
|||||||
}: FilterRuleModalProps) {
|
}: FilterRuleModalProps) {
|
||||||
const t = useTranslations("settings.filters");
|
const t = useTranslations("settings.filters");
|
||||||
const isEdit = !!rule;
|
const isEdit = !!rule;
|
||||||
|
const emailKeywords = useSettingsStore((state) => state.emailKeywords);
|
||||||
|
|
||||||
const [name, setName] = useState(rule?.name || "");
|
const [name, setName] = useState(rule?.name || "");
|
||||||
const [matchType, setMatchType] = useState<"all" | "any">(rule?.matchType || "all");
|
const [matchType, setMatchType] = useState<"all" | "any">(rule?.matchType || "all");
|
||||||
@@ -375,12 +377,17 @@ export function FilterRuleModal({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{action.type === "add_label" && (
|
{action.type === "add_label" && (
|
||||||
<Input
|
<select
|
||||||
value={action.value || ""}
|
value={action.value || ""}
|
||||||
onChange={(e) => updateAction(index, { value: e.target.value })}
|
onChange={(e) => updateAction(index, { value: e.target.value })}
|
||||||
placeholder={t("label_placeholder")}
|
className={`${selectClass} flex-1 min-w-[140px]`}
|
||||||
className="flex-1 min-w-[140px]"
|
aria-label={t("label_placeholder")}
|
||||||
/>
|
>
|
||||||
|
<option value="">{t("label_placeholder")}</option>
|
||||||
|
{emailKeywords.map((kw) => (
|
||||||
|
<option key={kw.id} value={kw.id}>{kw.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
+27
-2
@@ -14,6 +14,7 @@ import type {
|
|||||||
AdminPageSection,
|
AdminPageSection,
|
||||||
CalendarEventAction,
|
CalendarEventAction,
|
||||||
SlotName,
|
SlotName,
|
||||||
|
PluginI18n,
|
||||||
} from './plugin-types';
|
} from './plugin-types';
|
||||||
import { IMPLICIT_PERMISSIONS as IMPLICIT } from './plugin-types';
|
import { IMPLICIT_PERMISSIONS as IMPLICIT } from './plugin-types';
|
||||||
import {
|
import {
|
||||||
@@ -22,8 +23,9 @@ import {
|
|||||||
taskHooks, templateHooks, smimeHooks, vacationHooks,
|
taskHooks, templateHooks, smimeHooks, vacationHooks,
|
||||||
uiHooks, themeHooks, toastHooks, dragDropHooks,
|
uiHooks, themeHooks, toastHooks, dragDropHooks,
|
||||||
keyboardHooks, appLifecycleHooks, accountSecurityHooks,
|
keyboardHooks, appLifecycleHooks, accountSecurityHooks,
|
||||||
sidebarAppHooks, avatarHooks,
|
sidebarAppHooks, avatarHooks, renderHooks,
|
||||||
} from './plugin-hooks';
|
} from './plugin-hooks';
|
||||||
|
import { createPluginI18n } from './plugin-i18n';
|
||||||
import { toast as appToast } from '@/stores/toast-store';
|
import { toast as appToast } from '@/stores/toast-store';
|
||||||
import { useAuthStore } from '@/stores/auth-store';
|
import { useAuthStore } from '@/stores/auth-store';
|
||||||
|
|
||||||
@@ -109,6 +111,8 @@ function createPluginLogger(pluginId: string) {
|
|||||||
|
|
||||||
export interface PluginAPI {
|
export interface PluginAPI {
|
||||||
plugin: { id: string; version: string; settings: Record<string, unknown> };
|
plugin: { id: string; version: string; settings: Record<string, unknown> };
|
||||||
|
/** Localisation API — register translations and call t() to get strings */
|
||||||
|
i18n: PluginI18n;
|
||||||
ui: {
|
ui: {
|
||||||
registerToolbarAction: (action: ToolbarAction) => Disposable;
|
registerToolbarAction: (action: ToolbarAction) => Disposable;
|
||||||
registerEmailBanner: (factory: BannerFactory) => Disposable;
|
registerEmailBanner: (factory: BannerFactory) => Disposable;
|
||||||
@@ -149,6 +153,8 @@ export interface PluginHooksAPI {
|
|||||||
onEmailClose: (handler: () => void) => Disposable;
|
onEmailClose: (handler: () => void) => Disposable;
|
||||||
onEmailContentRender: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onEmailContentRender: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onThreadExpand: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onThreadExpand: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
|
/** Intercept — receives ComposeOptions, may mutate fields, return false to cancel */
|
||||||
|
onBeforeCompose: (handler: (options: import('./plugin-types').ComposeOptions) => boolean | void | Promise<boolean | void>) => Disposable;
|
||||||
onComposerOpen: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onComposerOpen: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onBeforeEmailSend: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onBeforeEmailSend: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onAfterEmailSend: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onAfterEmailSend: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
@@ -157,6 +163,10 @@ export interface PluginHooksAPI {
|
|||||||
onAfterEmailDelete: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onAfterEmailDelete: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onBeforeEmailMove: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onBeforeEmailMove: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onAfterEmailMove: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onAfterEmailMove: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
|
/** Emitted after emails are moved to the Archive mailbox */
|
||||||
|
onEmailArchive: (handler: (emailIds: string[]) => void) => Disposable;
|
||||||
|
/** Emitted after emails are moved out of the Archive mailbox */
|
||||||
|
onEmailUnarchive: (handler: (emailIds: string[]) => void) => Disposable;
|
||||||
onEmailReadStateChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onEmailReadStateChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onEmailStarToggle: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onEmailStarToggle: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onEmailSpamToggle: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onEmailSpamToggle: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
@@ -173,6 +183,8 @@ export interface PluginHooksAPI {
|
|||||||
onNewEmailReceived: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onNewEmailReceived: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onPushConnectionChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onPushConnectionChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onQuotaChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onQuotaChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
|
/** Intercept — receives MailtoContext, return false to prevent the system mail client */
|
||||||
|
onMailtoIntercept: (handler: (ctx: import('./plugin-types').MailtoContext) => boolean | void | Promise<boolean | void>) => Disposable;
|
||||||
// Calendar
|
// Calendar
|
||||||
onCalendarEventOpen: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onCalendarEventOpen: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onBeforeEventCreate: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onBeforeEventCreate: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
@@ -215,6 +227,8 @@ export interface PluginHooksAPI {
|
|||||||
onDirectoryCreate: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onDirectoryCreate: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onBeforeFileDelete: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onBeforeFileDelete: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onAfterFileDelete: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onAfterFileDelete: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
|
/** Intercept — receives { file: FileResourceView, newName: string }, return false to cancel */
|
||||||
|
onBeforeFileRename: (handler: (ctx: { file: import('./plugin-types').FileResourceView; newName: string }) => boolean | void | Promise<boolean | void>) => Disposable;
|
||||||
onFileRename: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onFileRename: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onFileMove: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onFileMove: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
onFileCopy: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onFileCopy: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
@@ -316,6 +330,9 @@ export interface PluginHooksAPI {
|
|||||||
onSidebarAppChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onSidebarAppChange: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
// Avatar
|
// Avatar
|
||||||
onAvatarResolve: (handler: (...args: unknown[]) => unknown) => Disposable;
|
onAvatarResolve: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
|
// Render — transform hook for email list row badges
|
||||||
|
// Handler: (badges: EmailListBadge[], ctx: { emailId: string; email: EmailReadView }) => EmailListBadge[]
|
||||||
|
onEmailListItemRender: (handler: (...args: unknown[]) => unknown) => Disposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Permission mapping for hooks ----------------------------
|
// --- Permission mapping for hooks ----------------------------
|
||||||
@@ -324,14 +341,17 @@ const HOOK_PERMISSIONS: Record<string, Permission> = {
|
|||||||
// Email
|
// Email
|
||||||
onEmailOpen: 'email:read', onEmailClose: 'email:read',
|
onEmailOpen: 'email:read', onEmailClose: 'email:read',
|
||||||
onEmailContentRender: 'email:read', onThreadExpand: 'email:read',
|
onEmailContentRender: 'email:read', onThreadExpand: 'email:read',
|
||||||
onComposerOpen: 'email:read', onDraftAutoSave: 'email:read',
|
onBeforeCompose: 'email:read', onComposerOpen: 'email:read',
|
||||||
|
onDraftAutoSave: 'email:read',
|
||||||
onMailboxChange: 'email:read', onMailboxesRefresh: 'email:read',
|
onMailboxChange: 'email:read', onMailboxesRefresh: 'email:read',
|
||||||
onSearch: 'email:read', onSearchResults: 'email:read',
|
onSearch: 'email:read', onSearchResults: 'email:read',
|
||||||
onEmailSelectionChange: 'email:read', onNewEmailReceived: 'email:read',
|
onEmailSelectionChange: 'email:read', onNewEmailReceived: 'email:read',
|
||||||
onPushConnectionChange: 'email:read', onQuotaChange: 'email:read',
|
onPushConnectionChange: 'email:read', onQuotaChange: 'email:read',
|
||||||
|
onMailtoIntercept: 'email:read', onEmailListItemRender: 'email:read',
|
||||||
onBeforeEmailSend: 'email:send', onAfterEmailSend: 'email:send',
|
onBeforeEmailSend: 'email:send', onAfterEmailSend: 'email:send',
|
||||||
onBeforeEmailDelete: 'email:write', onAfterEmailDelete: 'email:write',
|
onBeforeEmailDelete: 'email:write', onAfterEmailDelete: 'email:write',
|
||||||
onBeforeEmailMove: 'email:write', onAfterEmailMove: 'email:write',
|
onBeforeEmailMove: 'email:write', onAfterEmailMove: 'email:write',
|
||||||
|
onEmailArchive: 'email:write', onEmailUnarchive: 'email:write',
|
||||||
onEmailReadStateChange: 'email:write', onEmailStarToggle: 'email:write',
|
onEmailReadStateChange: 'email:write', onEmailStarToggle: 'email:write',
|
||||||
onEmailSpamToggle: 'email:write', onEmailKeywordChange: 'email:write',
|
onEmailSpamToggle: 'email:write', onEmailKeywordChange: 'email:write',
|
||||||
onMailboxCreate: 'email:write', onMailboxRename: 'email:write',
|
onMailboxCreate: 'email:write', onMailboxRename: 'email:write',
|
||||||
@@ -358,6 +378,7 @@ const HOOK_PERMISSIONS: Record<string, Permission> = {
|
|||||||
onBeforeFileUpload: 'files:write', onAfterFileUpload: 'files:write',
|
onBeforeFileUpload: 'files:write', onAfterFileUpload: 'files:write',
|
||||||
onFileUploadCancel: 'files:write', onDirectoryCreate: 'files:write',
|
onFileUploadCancel: 'files:write', onDirectoryCreate: 'files:write',
|
||||||
onBeforeFileDelete: 'files:write', onAfterFileDelete: 'files:write',
|
onBeforeFileDelete: 'files:write', onAfterFileDelete: 'files:write',
|
||||||
|
onBeforeFileRename: 'files:write',
|
||||||
onFileRename: 'files:write', onFileMove: 'files:write', onFileCopy: 'files:write',
|
onFileRename: 'files:write', onFileMove: 'files:write', onFileCopy: 'files:write',
|
||||||
onFileDuplicate: 'files:write', onFileFavoriteToggle: 'files:write', onFileUndo: 'files:write',
|
onFileDuplicate: 'files:write', onFileFavoriteToggle: 'files:write', onFileUndo: 'files:write',
|
||||||
// Auth
|
// Auth
|
||||||
@@ -469,6 +490,8 @@ const HOOK_BUSES: Record<string, { register: (pluginId: string, handler: (...arg
|
|||||||
...Object.fromEntries(Object.entries(sidebarAppHooks)),
|
...Object.fromEntries(Object.entries(sidebarAppHooks)),
|
||||||
// Avatar
|
// Avatar
|
||||||
...Object.fromEntries(Object.entries(avatarHooks)),
|
...Object.fromEntries(Object.entries(avatarHooks)),
|
||||||
|
// Render
|
||||||
|
...Object.fromEntries(Object.entries(renderHooks)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Slot registration bridge --------------------------------
|
// --- Slot registration bridge --------------------------------
|
||||||
@@ -530,6 +553,8 @@ export function createPluginAPI(plugin: InstalledPlugin): PluginAPI {
|
|||||||
settings: { ...plugin.settings },
|
settings: { ...plugin.settings },
|
||||||
},
|
},
|
||||||
|
|
||||||
|
i18n: createPluginI18n(plugin.id),
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
registerToolbarAction: (action: ToolbarAction) => {
|
registerToolbarAction: (action: ToolbarAction) => {
|
||||||
requirePermission(plugin, 'ui:toolbar');
|
requirePermission(plugin, 'ui:toolbar');
|
||||||
|
|||||||
+26
-1
@@ -172,6 +172,10 @@ export const emailHooks = {
|
|||||||
onEmailClose: new HookBus(),
|
onEmailClose: new HookBus(),
|
||||||
onEmailContentRender: new HookBus(),
|
onEmailContentRender: new HookBus(),
|
||||||
onThreadExpand: new HookBus(),
|
onThreadExpand: new HookBus(),
|
||||||
|
// Intercept hook — fires before the composer opens.
|
||||||
|
// Handlers receive ComposeOptions and may mutate fields in place.
|
||||||
|
// Return false to cancel opening the composer.
|
||||||
|
onBeforeCompose: new HookBus(),
|
||||||
onComposerOpen: new HookBus(),
|
onComposerOpen: new HookBus(),
|
||||||
onBeforeEmailSend: new HookBus(),
|
onBeforeEmailSend: new HookBus(),
|
||||||
onAfterEmailSend: new HookBus(),
|
onAfterEmailSend: new HookBus(),
|
||||||
@@ -180,6 +184,10 @@ export const emailHooks = {
|
|||||||
onAfterEmailDelete: new HookBus(),
|
onAfterEmailDelete: new HookBus(),
|
||||||
onBeforeEmailMove: new HookBus(),
|
onBeforeEmailMove: new HookBus(),
|
||||||
onAfterEmailMove: new HookBus(),
|
onAfterEmailMove: new HookBus(),
|
||||||
|
// Fired after one or more emails are archived to the Archive mailbox
|
||||||
|
onEmailArchive: new HookBus(),
|
||||||
|
// Fired after one or more emails are moved out of the Archive mailbox
|
||||||
|
onEmailUnarchive: new HookBus(),
|
||||||
onEmailReadStateChange: new HookBus(),
|
onEmailReadStateChange: new HookBus(),
|
||||||
onEmailStarToggle: new HookBus(),
|
onEmailStarToggle: new HookBus(),
|
||||||
onEmailSpamToggle: new HookBus(),
|
onEmailSpamToggle: new HookBus(),
|
||||||
@@ -196,6 +204,9 @@ export const emailHooks = {
|
|||||||
onNewEmailReceived: new HookBus(),
|
onNewEmailReceived: new HookBus(),
|
||||||
onPushConnectionChange: new HookBus(),
|
onPushConnectionChange: new HookBus(),
|
||||||
onQuotaChange: new HookBus(),
|
onQuotaChange: new HookBus(),
|
||||||
|
// Intercept hook — fired when a mailto: link is clicked.
|
||||||
|
// Return false to prevent the browser from opening the system mail client.
|
||||||
|
onMailtoIntercept: new HookBus(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// §7.2 Calendar Hooks
|
// §7.2 Calendar Hooks
|
||||||
@@ -250,6 +261,10 @@ export const fileHooks = {
|
|||||||
onDirectoryCreate: new HookBus(),
|
onDirectoryCreate: new HookBus(),
|
||||||
onBeforeFileDelete: new HookBus(),
|
onBeforeFileDelete: new HookBus(),
|
||||||
onAfterFileDelete: new HookBus(),
|
onAfterFileDelete: new HookBus(),
|
||||||
|
// Intercept hook — fires before a file is renamed.
|
||||||
|
// Receives { file: FileResourceView, newName: string }.
|
||||||
|
// Return false to cancel the rename.
|
||||||
|
onBeforeFileRename: new HookBus(),
|
||||||
onFileRename: new HookBus(),
|
onFileRename: new HookBus(),
|
||||||
onFileMove: new HookBus(),
|
onFileMove: new HookBus(),
|
||||||
onFileCopy: new HookBus(),
|
onFileCopy: new HookBus(),
|
||||||
@@ -406,6 +421,16 @@ export const avatarHooks = {
|
|||||||
onAvatarResolve: new HookBus(),
|
onAvatarResolve: new HookBus(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// §7.22 Render Hooks
|
||||||
|
export const renderHooks = {
|
||||||
|
// Transform hook — runs for each visible email list row.
|
||||||
|
// Initial value: EmailListBadge[] (always starts as [])
|
||||||
|
// Second argument: { emailId: string; email: EmailReadView }
|
||||||
|
// Handlers return a new (or extended) badges array.
|
||||||
|
// Rendered by the email list row component next to the subject line.
|
||||||
|
onEmailListItemRender: new HookBus(),
|
||||||
|
};
|
||||||
|
|
||||||
// ─── Aggregate: remove all handlers for a plugin across all buses ───
|
// ─── Aggregate: remove all handlers for a plugin across all buses ───
|
||||||
|
|
||||||
const allHookGroups = [
|
const allHookGroups = [
|
||||||
@@ -414,7 +439,7 @@ const allHookGroups = [
|
|||||||
taskHooks, templateHooks, smimeHooks, vacationHooks,
|
taskHooks, templateHooks, smimeHooks, vacationHooks,
|
||||||
uiHooks, themeHooks, toastHooks, dragDropHooks,
|
uiHooks, themeHooks, toastHooks, dragDropHooks,
|
||||||
keyboardHooks, appLifecycleHooks, accountSecurityHooks, sidebarAppHooks,
|
keyboardHooks, appLifecycleHooks, accountSecurityHooks, sidebarAppHooks,
|
||||||
avatarHooks,
|
avatarHooks, renderHooks,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function removeAllPluginHooks(pluginId: string): void {
|
export function removeAllPluginHooks(pluginId: string): void {
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
// Plugin i18n registry — manages per-plugin translation tables
|
||||||
|
//
|
||||||
|
// Each plugin gets its own namespace keyed by:
|
||||||
|
// pluginId → locale → { messageKey → translated string }
|
||||||
|
//
|
||||||
|
// Resolution order when calling t(key):
|
||||||
|
// 1. Exact locale match ("fr-CA")
|
||||||
|
// 2. Language-prefix match ("fr" from "fr-CA")
|
||||||
|
// 3. English fallback ("en")
|
||||||
|
// 4. Raw key (plugin is never broken by missing strings)
|
||||||
|
//
|
||||||
|
// Interpolation uses {paramName} placeholders.
|
||||||
|
|
||||||
|
// ─── Registry ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** pluginId → locale → key → translated string */
|
||||||
|
const registry = new Map<string, Map<string, Record<string, string>>>();
|
||||||
|
|
||||||
|
let currentLocale = 'en';
|
||||||
|
|
||||||
|
// ─── Locale sync (called by plugin-loader) ───────────────────
|
||||||
|
|
||||||
|
/** Keep the registry in sync with the app locale */
|
||||||
|
export function setPluginI18nLocale(locale: string): void {
|
||||||
|
currentLocale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPluginI18nLocale(): string {
|
||||||
|
return currentLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Cleanup ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Remove all translations for a plugin (called on deactivation) */
|
||||||
|
export function clearPluginI18nTranslations(pluginId: string): void {
|
||||||
|
registry.delete(pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Helpers ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function interpolate(template: string, params?: Record<string, string | number>): string {
|
||||||
|
if (!params) return template;
|
||||||
|
return template.replace(/\{(\w+)\}/g, (_, key) => String(params[key] ?? `{${key}}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolve(pluginId: string, key: string): string | undefined {
|
||||||
|
const byLocale = registry.get(pluginId);
|
||||||
|
if (!byLocale) return undefined;
|
||||||
|
|
||||||
|
// 1. Exact locale (e.g. "fr-CA")
|
||||||
|
const exact = byLocale.get(currentLocale)?.[key];
|
||||||
|
if (exact !== undefined) return exact;
|
||||||
|
|
||||||
|
// 2. Language prefix (e.g. "fr" from "fr-CA")
|
||||||
|
const lang = currentLocale.split('-')[0];
|
||||||
|
if (lang !== currentLocale) {
|
||||||
|
const langMatch = byLocale.get(lang)?.[key];
|
||||||
|
if (langMatch !== undefined) return langMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. English fallback
|
||||||
|
return byLocale.get('en')?.[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Public API factory ──────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the i18n API object exposed as `api.i18n` inside each plugin.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // In your plugin activate():
|
||||||
|
* api.i18n.addTranslations('en', { 'banner.title': 'Hello' });
|
||||||
|
* api.i18n.addTranslations('de', { 'banner.title': 'Hallo' });
|
||||||
|
*
|
||||||
|
* // Later, in any React component the plugin renders:
|
||||||
|
* const title = api.i18n.t('banner.title');
|
||||||
|
* const greeting = api.i18n.t('welcome', { name: 'Alice' }); // 'Hello, {name}!'
|
||||||
|
*/
|
||||||
|
export function createPluginI18n(pluginId: string) {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Register translations for one locale.
|
||||||
|
* Multiple calls for the same locale are merged (last-write-wins on key collision).
|
||||||
|
*
|
||||||
|
* @param locale BCP-47 locale tag, e.g. "en", "de", "fr-CA"
|
||||||
|
* @param strings Key → translated string map. Use {paramName} for interpolation.
|
||||||
|
*/
|
||||||
|
addTranslations(locale: string, strings: Record<string, string>): void {
|
||||||
|
let byLocale = registry.get(pluginId);
|
||||||
|
if (!byLocale) {
|
||||||
|
byLocale = new Map<string, Record<string, string>>();
|
||||||
|
registry.set(pluginId, byLocale);
|
||||||
|
}
|
||||||
|
const existing = byLocale.get(locale) ?? {};
|
||||||
|
byLocale.set(locale, { ...existing, ...strings });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a key using the current app locale.
|
||||||
|
* Falls back through: exact locale → language prefix → 'en' → raw key.
|
||||||
|
*
|
||||||
|
* @param key Translation key, e.g. `'banner.title'`
|
||||||
|
* @param params Optional interpolation values, e.g. `{ count: 3 }`
|
||||||
|
*/
|
||||||
|
t(key: string, params?: Record<string, string | number>): string {
|
||||||
|
const template = resolve(pluginId, key);
|
||||||
|
if (template !== undefined) return interpolate(template, params);
|
||||||
|
return key; // never throw — just return the key
|
||||||
|
},
|
||||||
|
|
||||||
|
/** The current app locale (e.g. "en", "de", "fr") */
|
||||||
|
getLocale(): string {
|
||||||
|
return currentLocale;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PluginI18nInstance = ReturnType<typeof createPluginI18n>;
|
||||||
+25
-1
@@ -1,15 +1,18 @@
|
|||||||
// Plugin Loader — loads and activates plugins via blob URL dynamic import
|
// Plugin Loader — loads and activates plugins via blob URL dynamic import
|
||||||
|
|
||||||
import type { InstalledPlugin, Disposable } from './plugin-types';
|
import type { InstalledPlugin, Disposable } from './plugin-types';
|
||||||
import { pluginStorage } from './plugin-storage';
|
import { pluginStorage } from './plugin-storage';
|
||||||
import { createPluginAPI, type PluginAPI } from './plugin-api';
|
import { createPluginAPI, type PluginAPI } from './plugin-api';
|
||||||
import { removeAllPluginHooks, pluginErrorTracker } from './plugin-hooks';
|
import { removeAllPluginHooks, pluginErrorTracker } from './plugin-hooks';
|
||||||
|
import { setPluginI18nLocale, clearPluginI18nTranslations } from './plugin-i18n';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import * as ReactJSX from 'react/jsx-runtime';
|
import * as ReactJSX from 'react/jsx-runtime';
|
||||||
|
|
||||||
// --- Shared React (window.__PLUGIN_EXTERNALS__) -------------
|
// --- Shared React (window.__PLUGIN_EXTERNALS__) -------------
|
||||||
|
|
||||||
|
let localeSyncInitialised = false;
|
||||||
|
|
||||||
export function exposePluginExternals(): void {
|
export function exposePluginExternals(): void {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@@ -18,6 +21,16 @@ export function exposePluginExternals(): void {
|
|||||||
ReactDOM,
|
ReactDOM,
|
||||||
ReactJSX,
|
ReactJSX,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sync plugin i18n with the app locale (runs once per page load)
|
||||||
|
if (!localeSyncInitialised) {
|
||||||
|
localeSyncInitialised = true;
|
||||||
|
// Dynamic import avoids a circular dependency chain at module evaluation time
|
||||||
|
import('@/stores/locale-store').then(({ useLocaleStore }) => {
|
||||||
|
setPluginI18nLocale(useLocaleStore.getState().locale);
|
||||||
|
useLocaleStore.subscribe((state) => setPluginI18nLocale(state.locale));
|
||||||
|
}).catch(() => {/* locale sync is best-effort */});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Active plugin tracking ----------------------------------
|
// --- Active plugin tracking ----------------------------------
|
||||||
@@ -78,6 +91,14 @@ export async function loadPlugin(plugin: InstalledPlugin): Promise<void> {
|
|||||||
// 4. Build sandboxed API
|
// 4. Build sandboxed API
|
||||||
const api = createPluginAPI(plugin);
|
const api = createPluginAPI(plugin);
|
||||||
|
|
||||||
|
// 4b. Auto-register translations bundled in the manifest (plugin.locales)
|
||||||
|
// Plugins may still call api.i18n.addTranslations() in activate() to add more.
|
||||||
|
if (plugin.locales) {
|
||||||
|
for (const [locale, strings] of Object.entries(plugin.locales)) {
|
||||||
|
api.i18n.addTranslations(locale, strings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 5. Call activate
|
// 5. Call activate
|
||||||
const disposable = await mod.activate(api);
|
const disposable = await mod.activate(api);
|
||||||
|
|
||||||
@@ -119,6 +140,9 @@ export function deactivatePlugin(pluginId: string): void {
|
|||||||
// Remove all hook subscriptions for this plugin
|
// Remove all hook subscriptions for this plugin
|
||||||
removeAllPluginHooks(pluginId);
|
removeAllPluginHooks(pluginId);
|
||||||
|
|
||||||
|
// Clear cached translations (avoids memory leak on repeated enable/disable cycles)
|
||||||
|
clearPluginI18nTranslations(pluginId);
|
||||||
|
|
||||||
// Reset error tracker
|
// Reset error tracker
|
||||||
pluginErrorTracker.reset(pluginId);
|
pluginErrorTracker.reset(pluginId);
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ export interface PluginManifest {
|
|||||||
entrypoint: string;
|
entrypoint: string;
|
||||||
minAppVersion?: string;
|
minAppVersion?: string;
|
||||||
settingsSchema?: Record<string, SettingFieldSchema>;
|
settingsSchema?: Record<string, SettingFieldSchema>;
|
||||||
|
/**
|
||||||
|
* Bundled translations shipped inside the plugin ZIP.
|
||||||
|
* Keyed by BCP-47 locale tag ("en", "de", "fr-CA", …).
|
||||||
|
* The loader auto-registers these before calling activate(),
|
||||||
|
* so plugins can use api.i18n.t() without calling addTranslations() first.
|
||||||
|
*/
|
||||||
|
locales?: Record<string, Record<string, string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingFieldSchema {
|
export interface SettingFieldSchema {
|
||||||
@@ -83,6 +90,8 @@ export interface InstalledPlugin {
|
|||||||
adminApproved?: boolean;
|
adminApproved?: boolean;
|
||||||
settingsSchema?: Record<string, SettingFieldSchema>;
|
settingsSchema?: Record<string, SettingFieldSchema>;
|
||||||
settings: Record<string, unknown>;
|
settings: Record<string, unknown>;
|
||||||
|
/** Bundled translations, carried over from the manifest on install. */
|
||||||
|
locales?: Record<string, Record<string, string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── UI Slots ────────────────────────────────────────────────
|
// ─── UI Slots ────────────────────────────────────────────────
|
||||||
@@ -388,6 +397,86 @@ export interface ComposerContext {
|
|||||||
originalSubject?: string;
|
originalSubject?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── New hook context types ──────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passed to onBeforeCompose handlers.
|
||||||
|
* Handlers may mutate the object in place to pre-fill fields; returning false cancels the compose.
|
||||||
|
*/
|
||||||
|
export interface ComposeOptions {
|
||||||
|
to: string[];
|
||||||
|
cc: string[];
|
||||||
|
subject: string;
|
||||||
|
body: string;
|
||||||
|
mode: 'new' | 'reply' | 'reply-all' | 'forward';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A small visual indicator injected into an email list row via onEmailListItemRender.
|
||||||
|
*/
|
||||||
|
export interface EmailListBadge {
|
||||||
|
/** Stable unique key within the plugin — used as React key */
|
||||||
|
key: string;
|
||||||
|
/** Short label text displayed in the badge */
|
||||||
|
label: string;
|
||||||
|
/** CSS color value for the badge background, e.g. "#e74c3c" or "var(--color-warning)" */
|
||||||
|
color?: string;
|
||||||
|
/** Tooltip / aria-label */
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passed to onMailtoIntercept handlers.
|
||||||
|
* Return false to prevent the browser from opening the system mail client.
|
||||||
|
*/
|
||||||
|
export interface MailtoContext {
|
||||||
|
/** The raw href, e.g. "mailto:alice@example.com?subject=Hello" */
|
||||||
|
href: string;
|
||||||
|
/** Parsed list of recipient addresses */
|
||||||
|
to: string[];
|
||||||
|
subject?: string;
|
||||||
|
body?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Plugin i18n API ─────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Localisation API exposed as `api.i18n` inside every plugin.
|
||||||
|
*
|
||||||
|
* Plugins ship their own translation tables; the app locale is tracked
|
||||||
|
* automatically so `t()` always returns the right string without any
|
||||||
|
* extra setup from the plugin side.
|
||||||
|
*/
|
||||||
|
export interface PluginI18n {
|
||||||
|
/**
|
||||||
|
* Register translations for one locale.
|
||||||
|
* Multiple calls for the same locale are merged (last-write-wins per key).
|
||||||
|
*
|
||||||
|
* @param locale BCP-47 tag, e.g. "en", "de", "fr-CA"
|
||||||
|
* @param strings Key → translated string map. Use {paramName} for interpolation.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* api.i18n.addTranslations('en', { 'banner.title': 'Tracking blocked' });
|
||||||
|
* api.i18n.addTranslations('de', { 'banner.title': 'Tracking blockiert' });
|
||||||
|
*/
|
||||||
|
addTranslations(locale: string, strings: Record<string, string>): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the translated string for `key` using the current app locale,
|
||||||
|
* with optional {param} interpolation.
|
||||||
|
*
|
||||||
|
* Falls back: exact locale → language prefix → "en" → raw key.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* api.i18n.t('banner.title')
|
||||||
|
* api.i18n.t('items_found', { count: 3 }) // 'Found {count} items' → 'Found 3 items'
|
||||||
|
*/
|
||||||
|
t(key: string, params?: Record<string, string | number>): string;
|
||||||
|
|
||||||
|
/** The current app locale string (e.g. "en", "de", "fr") */
|
||||||
|
getLocale(): string;
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Permission Reference ────────────────────────────────────
|
// ─── Permission Reference ────────────────────────────────────
|
||||||
|
|
||||||
export const ALL_PERMISSIONS = [
|
export const ALL_PERMISSIONS = [
|
||||||
|
|||||||
@@ -194,9 +194,9 @@ describe('generateScript', () => {
|
|||||||
expect(script).toContain('addflag "\\\\Flagged";');
|
expect(script).toContain('addflag "\\\\Flagged";');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates add_label as addflag $Label', () => {
|
it('generates add_label as addflag $label:Label', () => {
|
||||||
const script = generateScript([makeRule({ actions: [{ type: 'add_label', value: 'Important' }] })]);
|
const script = generateScript([makeRule({ actions: [{ type: 'add_label', value: 'Important' }] })]);
|
||||||
expect(script).toContain('addflag "$Important";');
|
expect(script).toContain('addflag "$label:Important";');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generates discard', () => {
|
it('generates discard', () => {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ function generateActions(actions: FilterAction[]): string[] {
|
|||||||
case 'star':
|
case 'star':
|
||||||
return 'addflag "\\\\Flagged";';
|
return 'addflag "\\\\Flagged";';
|
||||||
case 'add_label':
|
case 'add_label':
|
||||||
return `addflag "$${escapeString(action.value || '')}";`;
|
return `addflag "$label:${escapeString(action.value || '')}";`;
|
||||||
case 'discard':
|
case 'discard':
|
||||||
return 'discard;';
|
return 'discard;';
|
||||||
case 'reject':
|
case 'reject':
|
||||||
|
|||||||
+12
-12
@@ -657,7 +657,7 @@
|
|||||||
"filters": "Filter",
|
"filters": "Filter",
|
||||||
"templates": "Vorlagen",
|
"templates": "Vorlagen",
|
||||||
"folders": "Ordner",
|
"folders": "Ordner",
|
||||||
"keywords": "Schlüsselwörter",
|
"keywords": "Labels",
|
||||||
"security": "Sicherheit",
|
"security": "Sicherheit",
|
||||||
"encryption": "Verschlüsselung",
|
"encryption": "Verschlüsselung",
|
||||||
"files": "Dateien",
|
"files": "Dateien",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "E-Mail-Schlüsselwörter",
|
"title": "E-Mail-Labels",
|
||||||
"description": "Definieren Sie Schlüsselwörter (Labels/Tags) zum Organisieren Ihrer E-Mails mit Farben.",
|
"description": "Labels definieren, um Ihre E-Mails mit Farben zu organisieren. Diese werden als JMAP-Keywords auf dem Server gespeichert.",
|
||||||
"add_keyword": "Schlüsselwort hinzufügen",
|
"add_keyword": "Label hinzufügen",
|
||||||
"reset_defaults": "Auf Standard zurücksetzen",
|
"reset_defaults": "Auf Standard zurücksetzen",
|
||||||
"label_field": "Anzeigename",
|
"label_field": "Anzeigename",
|
||||||
"label_placeholder": "z.B. Arbeit, Privat, Dringend",
|
"label_placeholder": "z.B. Arbeit, Privat, Dringend",
|
||||||
"id_field": "Schlüsselwort-ID",
|
"id_field": "Label-ID",
|
||||||
"id_placeholder": "z.B. arbeit, privat",
|
"id_placeholder": "z.B. arbeit, privat",
|
||||||
"color_field": "Farbe",
|
"color_field": "Farbe",
|
||||||
"id_exists": "Diese Schlüsselwort-ID existiert bereits",
|
"id_exists": "Diese Label-ID existiert bereits",
|
||||||
"edit": "Schlüsselwort bearbeiten",
|
"edit": "Label bearbeiten",
|
||||||
"delete": "Schlüsselwort löschen",
|
"delete": "Label löschen",
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"add": "Hinzufügen",
|
"add": "Hinzufügen",
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"migrating": "Schlüsselwort bei bestehenden E-Mails aktualisieren…",
|
"migrating": "Label auf vorhandenen E-Mails aktualisieren…",
|
||||||
"migration_error": "Schlüsselwort konnte bei bestehenden E-Mails nicht aktualisiert werden"
|
"migration_error": "Label auf vorhandenen E-Mails konnte nicht aktualisiert werden"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "Benachrichtigungston testen",
|
"test_sound": "Benachrichtigungston testen",
|
||||||
@@ -923,7 +923,7 @@
|
|||||||
"star": "Markieren / Markierung aufheben",
|
"star": "Markieren / Markierung aufheben",
|
||||||
"mark_read": "Als gelesen / ungelesen markieren",
|
"mark_read": "Als gelesen / ungelesen markieren",
|
||||||
"archive": "Archivieren",
|
"archive": "Archivieren",
|
||||||
"tag": "Schlagwort",
|
"tag": "Label",
|
||||||
"spam": "Als Spam markieren",
|
"spam": "Als Spam markieren",
|
||||||
"none_selected": "Keine Aktionen ausgewählt",
|
"none_selected": "Keine Aktionen ausgewählt",
|
||||||
"mode_label": "Anzeigemodus",
|
"mode_label": "Anzeigemodus",
|
||||||
@@ -1350,7 +1350,7 @@
|
|||||||
"reject_message": "Ablehnungsnachricht",
|
"reject_message": "Ablehnungsnachricht",
|
||||||
"reject_placeholder": "Ihre E-Mail wurde abgelehnt",
|
"reject_placeholder": "Ihre E-Mail wurde abgelehnt",
|
||||||
"label_name": "Label-Name",
|
"label_name": "Label-Name",
|
||||||
"label_placeholder": "z.B. wichtig",
|
"label_placeholder": "Label auswählen",
|
||||||
"header_name": "Header-Name",
|
"header_name": "Header-Name",
|
||||||
"header_placeholder": "z.B. X-Mailing-List",
|
"header_placeholder": "z.B. X-Mailing-List",
|
||||||
"size_bytes": "Größe in Bytes",
|
"size_bytes": "Größe in Bytes",
|
||||||
|
|||||||
+15
-15
@@ -657,7 +657,7 @@
|
|||||||
"filters": "Filters",
|
"filters": "Filters",
|
||||||
"templates": "Templates",
|
"templates": "Templates",
|
||||||
"folders": "Folders",
|
"folders": "Folders",
|
||||||
"keywords": "Keywords",
|
"keywords": "Tags",
|
||||||
"security": "Security",
|
"security": "Security",
|
||||||
"files": "Files",
|
"files": "Files",
|
||||||
"contacts": "Contacts",
|
"contacts": "Contacts",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "Email Keywords",
|
"title": "Email Tags",
|
||||||
"description": "Define keywords (labels/tags) to organize your emails with colors. These are stored as JMAP keywords on the server.",
|
"description": "Define tags to organize your emails with colors. These are stored as JMAP keywords on the server.",
|
||||||
"add_keyword": "Add Keyword",
|
"add_keyword": "Add Tag",
|
||||||
"reset_defaults": "Reset to Defaults",
|
"reset_defaults": "Reset to Defaults",
|
||||||
"label_field": "Display Name",
|
"label_field": "Display Name",
|
||||||
"label_placeholder": "e.g. Work, Personal, Urgent",
|
"label_placeholder": "e.g. Work, Personal, Urgent",
|
||||||
"id_field": "Keyword ID",
|
"id_field": "Tag ID",
|
||||||
"id_placeholder": "e.g. work, personal",
|
"id_placeholder": "e.g. work, personal",
|
||||||
"color_field": "Color",
|
"color_field": "Color",
|
||||||
"id_exists": "This keyword ID already exists",
|
"id_exists": "This tag ID already exists",
|
||||||
"edit": "Edit keyword",
|
"edit": "Edit tag",
|
||||||
"delete": "Delete keyword",
|
"delete": "Delete tag",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"migrating": "Updating keyword on existing emails…",
|
"migrating": "Updating tag on existing emails…",
|
||||||
"migration_error": "Failed to update keyword on existing emails"
|
"migration_error": "Failed to update tag on existing emails"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "Test notification sound",
|
"test_sound": "Test notification sound",
|
||||||
@@ -1337,7 +1337,7 @@
|
|||||||
"forward": "Forward to",
|
"forward": "Forward to",
|
||||||
"mark_read": "Mark as read",
|
"mark_read": "Mark as read",
|
||||||
"star": "Star message",
|
"star": "Star message",
|
||||||
"add_label": "Add label",
|
"add_label": "Add tag",
|
||||||
"discard": "Discard (delete silently)",
|
"discard": "Discard (delete silently)",
|
||||||
"reject": "Reject with message",
|
"reject": "Reject with message",
|
||||||
"keep": "Keep in inbox",
|
"keep": "Keep in inbox",
|
||||||
@@ -1349,8 +1349,8 @@
|
|||||||
"forward_placeholder": "email@example.com",
|
"forward_placeholder": "email@example.com",
|
||||||
"reject_message": "Rejection message",
|
"reject_message": "Rejection message",
|
||||||
"reject_placeholder": "Your email has been rejected",
|
"reject_placeholder": "Your email has been rejected",
|
||||||
"label_name": "Label name",
|
"label_name": "Tag name",
|
||||||
"label_placeholder": "e.g., important",
|
"label_placeholder": "Select tag",
|
||||||
"header_name": "Header name",
|
"header_name": "Header name",
|
||||||
"header_placeholder": "e.g., X-Mailing-List",
|
"header_placeholder": "e.g., X-Mailing-List",
|
||||||
"size_bytes": "Size in bytes",
|
"size_bytes": "Size in bytes",
|
||||||
@@ -1524,8 +1524,8 @@
|
|||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"mark_as_spam": "Report spam",
|
"mark_as_spam": "Report spam",
|
||||||
"not_spam": "Not spam",
|
"not_spam": "Not spam",
|
||||||
"color_tag": "Label",
|
"color_tag": "Tag",
|
||||||
"remove_color": "Remove Label",
|
"remove_color": "Remove tag",
|
||||||
"items_selected": "{count} emails selected",
|
"items_selected": "{count} emails selected",
|
||||||
"edit_draft": "Edit Draft"
|
"edit_draft": "Edit Draft"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -657,7 +657,7 @@
|
|||||||
"filters": "Filtros",
|
"filters": "Filtros",
|
||||||
"templates": "Plantillas",
|
"templates": "Plantillas",
|
||||||
"folders": "Carpetas",
|
"folders": "Carpetas",
|
||||||
"keywords": "Palabras clave",
|
"keywords": "Etiquetas",
|
||||||
"security": "Seguridad",
|
"security": "Seguridad",
|
||||||
"encryption": "Cifrado",
|
"encryption": "Cifrado",
|
||||||
"files": "Archivos",
|
"files": "Archivos",
|
||||||
@@ -725,18 +725,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "Palabras clave de correo",
|
"title": "Etiquetas de correo",
|
||||||
"description": "Define palabras clave (etiquetas) para organizar tus correos con colores.",
|
"description": "Define etiquetas para organizar tus correos con colores. Se almacenan como palabras clave JMAP en el servidor.",
|
||||||
"add_keyword": "Añadir palabra clave",
|
"add_keyword": "Añadir etiqueta",
|
||||||
"reset_defaults": "Restablecer valores predeterminados",
|
"reset_defaults": "Restablecer valores predeterminados",
|
||||||
"label_field": "Nombre para mostrar",
|
"label_field": "Nombre para mostrar",
|
||||||
"label_placeholder": "ej. Trabajo, Personal, Urgente",
|
"label_placeholder": "ej. Trabajo, Personal, Urgente",
|
||||||
"id_field": "ID de palabra clave",
|
"id_field": "ID de etiqueta",
|
||||||
"id_placeholder": "ej. trabajo, personal",
|
"id_placeholder": "ej. trabajo, personal",
|
||||||
"color_field": "Color",
|
"color_field": "Color",
|
||||||
"id_exists": "Esta ID de palabra clave ya existe",
|
"id_exists": "Este ID de etiqueta ya existe",
|
||||||
"edit": "Editar palabra clave",
|
"edit": "Editar etiqueta",
|
||||||
"delete": "Eliminar palabra clave",
|
"delete": "Eliminar etiqueta",
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
"add": "Añadir",
|
"add": "Añadir",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
@@ -1350,7 +1350,7 @@
|
|||||||
"reject_message": "Mensaje de rechazo",
|
"reject_message": "Mensaje de rechazo",
|
||||||
"reject_placeholder": "Su correo ha sido rechazado",
|
"reject_placeholder": "Su correo ha sido rechazado",
|
||||||
"label_name": "Nombre de la etiqueta",
|
"label_name": "Nombre de la etiqueta",
|
||||||
"label_placeholder": "ej. importante",
|
"label_placeholder": "Seleccionar etiqueta",
|
||||||
"header_name": "Nombre del encabezado",
|
"header_name": "Nombre del encabezado",
|
||||||
"header_placeholder": "ej. X-Mailing-List",
|
"header_placeholder": "ej. X-Mailing-List",
|
||||||
"size_bytes": "Tamaño en bytes",
|
"size_bytes": "Tamaño en bytes",
|
||||||
|
|||||||
+13
-13
@@ -657,7 +657,7 @@
|
|||||||
"filters": "Filtres",
|
"filters": "Filtres",
|
||||||
"templates": "Modèles",
|
"templates": "Modèles",
|
||||||
"folders": "Dossiers",
|
"folders": "Dossiers",
|
||||||
"keywords": "Mots-clés",
|
"keywords": "Étiquettes",
|
||||||
"security": "Sécurité",
|
"security": "Sécurité",
|
||||||
"encryption": "Chiffrement",
|
"encryption": "Chiffrement",
|
||||||
"files": "Fichiers",
|
"files": "Fichiers",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "Mots-clés des e-mails",
|
"title": "Étiquettes de messagerie",
|
||||||
"description": "Définissez des mots-clés (étiquettes) pour organiser vos e-mails avec des couleurs.",
|
"description": "Définissez des étiquettes pour organiser vos e-mails avec des couleurs. Elles sont stockées sous forme de mots-clés JMAP sur le serveur.",
|
||||||
"add_keyword": "Ajouter un mot-clé",
|
"add_keyword": "Ajouter une étiquette",
|
||||||
"reset_defaults": "Réinitialiser par défaut",
|
"reset_defaults": "Réinitialiser par défaut",
|
||||||
"label_field": "Nom d'affichage",
|
"label_field": "Nom d'affichage",
|
||||||
"label_placeholder": "ex. Travail, Personnel, Urgent",
|
"label_placeholder": "ex. Travail, Personnel, Urgent",
|
||||||
"id_field": "ID du mot-clé",
|
"id_field": "ID d'étiquette",
|
||||||
"id_placeholder": "ex. travail, personnel",
|
"id_placeholder": "ex. travail, personnel",
|
||||||
"color_field": "Couleur",
|
"color_field": "Couleur",
|
||||||
"id_exists": "Cet ID de mot-clé existe déjà",
|
"id_exists": "Cet ID d'étiquette existe déjà",
|
||||||
"edit": "Modifier le mot-clé",
|
"edit": "Éditer l'étiquette",
|
||||||
"delete": "Supprimer le mot-clé",
|
"delete": "Supprimer l'étiquette",
|
||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
"add": "Ajouter",
|
"add": "Ajouter",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"migrating": "Mise à jour du mot-clé sur les e-mails existants…",
|
"migrating": "Mise à jour de l'étiquette sur les e-mails existants…",
|
||||||
"migration_error": "Échec de la mise à jour du mot-clé sur les e-mails existants"
|
"migration_error": "Impossible de mettre à jour l'étiquette sur les e-mails existants"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "Tester le son de notification",
|
"test_sound": "Tester le son de notification",
|
||||||
@@ -1337,7 +1337,7 @@
|
|||||||
"forward": "Transférer à",
|
"forward": "Transférer à",
|
||||||
"mark_read": "Marquer comme lu",
|
"mark_read": "Marquer comme lu",
|
||||||
"star": "Marquer d'une étoile",
|
"star": "Marquer d'une étoile",
|
||||||
"add_label": "Ajouter un libellé",
|
"add_label": "Ajouter une étiquette",
|
||||||
"discard": "Supprimer silencieusement",
|
"discard": "Supprimer silencieusement",
|
||||||
"reject": "Rejeter avec un message",
|
"reject": "Rejeter avec un message",
|
||||||
"keep": "Conserver dans la boîte de réception",
|
"keep": "Conserver dans la boîte de réception",
|
||||||
@@ -1349,8 +1349,8 @@
|
|||||||
"forward_placeholder": "email@exemple.com",
|
"forward_placeholder": "email@exemple.com",
|
||||||
"reject_message": "Message de rejet",
|
"reject_message": "Message de rejet",
|
||||||
"reject_placeholder": "Votre e-mail a été rejeté",
|
"reject_placeholder": "Votre e-mail a été rejeté",
|
||||||
"label_name": "Nom du libellé",
|
"label_name": "Nom de l'étiquette",
|
||||||
"label_placeholder": "ex. important",
|
"label_placeholder": "Sélectionner une étiquette",
|
||||||
"header_name": "Nom de l'en-tête",
|
"header_name": "Nom de l'en-tête",
|
||||||
"header_placeholder": "ex. X-Mailing-List",
|
"header_placeholder": "ex. X-Mailing-List",
|
||||||
"size_bytes": "Taille en octets",
|
"size_bytes": "Taille en octets",
|
||||||
|
|||||||
+11
-11
@@ -657,7 +657,7 @@
|
|||||||
"filters": "Filtri",
|
"filters": "Filtri",
|
||||||
"templates": "Modelli",
|
"templates": "Modelli",
|
||||||
"folders": "Cartelle",
|
"folders": "Cartelle",
|
||||||
"keywords": "Parole chiave",
|
"keywords": "Etichette",
|
||||||
"security": "Sicurezza",
|
"security": "Sicurezza",
|
||||||
"encryption": "Cifratura",
|
"encryption": "Cifratura",
|
||||||
"files": "File",
|
"files": "File",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "Parole chiave e-mail",
|
"title": "Etichette e-mail",
|
||||||
"description": "Definisci parole chiave (etichette) per organizzare le tue e-mail con colori.",
|
"description": "Definisci etichette per organizzare le tue e-mail con i colori. Vengono archiviate come parole chiave JMAP sul server.",
|
||||||
"add_keyword": "Aggiungi parola chiave",
|
"add_keyword": "Aggiungi etichetta",
|
||||||
"reset_defaults": "Ripristina predefiniti",
|
"reset_defaults": "Ripristina predefiniti",
|
||||||
"label_field": "Nome visualizzato",
|
"label_field": "Nome visualizzato",
|
||||||
"label_placeholder": "es. Lavoro, Personale, Urgente",
|
"label_placeholder": "es. Lavoro, Personale, Urgente",
|
||||||
"id_field": "ID parola chiave",
|
"id_field": "ID etichetta",
|
||||||
"id_placeholder": "es. lavoro, personale",
|
"id_placeholder": "es. lavoro, personale",
|
||||||
"color_field": "Colore",
|
"color_field": "Colore",
|
||||||
"id_exists": "Questo ID parola chiave esiste già",
|
"id_exists": "Questo ID etichetta esiste già",
|
||||||
"edit": "Modifica parola chiave",
|
"edit": "Modifica etichetta",
|
||||||
"delete": "Elimina parola chiave",
|
"delete": "Elimina etichetta",
|
||||||
"save": "Salva",
|
"save": "Salva",
|
||||||
"add": "Aggiungi",
|
"add": "Aggiungi",
|
||||||
"cancel": "Annulla",
|
"cancel": "Annulla",
|
||||||
"migrating": "Aggiornamento parola chiave sulle email esistenti…",
|
"migrating": "Aggiornamento dell'etichetta nelle e-mail esistenti…",
|
||||||
"migration_error": "Impossibile aggiornare la parola chiave sulle email esistenti"
|
"migration_error": "Impossibile aggiornare l'etichetta nelle e-mail esistenti"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "Testa il suono di notifica",
|
"test_sound": "Testa il suono di notifica",
|
||||||
@@ -1350,7 +1350,7 @@
|
|||||||
"reject_message": "Messaggio di rifiuto",
|
"reject_message": "Messaggio di rifiuto",
|
||||||
"reject_placeholder": "La tua email è stata rifiutata",
|
"reject_placeholder": "La tua email è stata rifiutata",
|
||||||
"label_name": "Nome dell'etichetta",
|
"label_name": "Nome dell'etichetta",
|
||||||
"label_placeholder": "es. importante",
|
"label_placeholder": "Seleziona etichetta",
|
||||||
"header_name": "Nome dell'intestazione",
|
"header_name": "Nome dell'intestazione",
|
||||||
"header_placeholder": "es. X-Mailing-List",
|
"header_placeholder": "es. X-Mailing-List",
|
||||||
"size_bytes": "Dimensione in byte",
|
"size_bytes": "Dimensione in byte",
|
||||||
|
|||||||
+12
-12
@@ -657,7 +657,7 @@
|
|||||||
"filters": "フィルター",
|
"filters": "フィルター",
|
||||||
"templates": "テンプレート",
|
"templates": "テンプレート",
|
||||||
"folders": "フォルダー",
|
"folders": "フォルダー",
|
||||||
"keywords": "キーワード",
|
"keywords": "ラベル",
|
||||||
"security": "セキュリティ",
|
"security": "セキュリティ",
|
||||||
"encryption": "暗号化",
|
"encryption": "暗号化",
|
||||||
"files": "ファイル",
|
"files": "ファイル",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "メールキーワード",
|
"title": "メールラベル",
|
||||||
"description": "色でメールを整理するためのキーワード(ラベル/タグ)を定義します。",
|
"description": "メールをカラーで整理するためのラベルを定義します。サーバーにJMAPキーワードとして保存されます。",
|
||||||
"add_keyword": "キーワードを追加",
|
"add_keyword": "ラベルを追加",
|
||||||
"reset_defaults": "デフォルトに戻す",
|
"reset_defaults": "デフォルトに戻す",
|
||||||
"label_field": "表示名",
|
"label_field": "表示名",
|
||||||
"label_placeholder": "例:仕事、個人、緊急",
|
"label_placeholder": "例:仕事、個人、緊急",
|
||||||
"id_field": "キーワードID",
|
"id_field": "ラベルID",
|
||||||
"id_placeholder": "例:work、personal",
|
"id_placeholder": "例:work、personal",
|
||||||
"color_field": "色",
|
"color_field": "色",
|
||||||
"id_exists": "このキーワードIDは既に存在します",
|
"id_exists": "このラベルIDは既に存在します",
|
||||||
"edit": "キーワードを編集",
|
"edit": "ラベルを編集",
|
||||||
"delete": "キーワードを削除",
|
"delete": "ラベルを削除",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"add": "追加",
|
"add": "追加",
|
||||||
"cancel": "キャンセル",
|
"cancel": "キャンセル",
|
||||||
"migrating": "既存のメールでキーワードを更新中…",
|
"migrating": "既存のメールのラベルを更新中…",
|
||||||
"migration_error": "既存のメールでのキーワード更新に失敗しました"
|
"migration_error": "既存のメールのラベルの更新に失敗しました"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "通知音をテスト",
|
"test_sound": "通知音をテスト",
|
||||||
@@ -923,7 +923,7 @@
|
|||||||
"star": "スター付け / 解除",
|
"star": "スター付け / 解除",
|
||||||
"mark_read": "既読 / 未読にする",
|
"mark_read": "既読 / 未読にする",
|
||||||
"archive": "アーカイブ",
|
"archive": "アーカイブ",
|
||||||
"tag": "タグ",
|
"tag": "ラベル",
|
||||||
"spam": "スパムとしてマーク",
|
"spam": "スパムとしてマーク",
|
||||||
"none_selected": "アクションが選択されていません",
|
"none_selected": "アクションが選択されていません",
|
||||||
"mode_label": "表示モード",
|
"mode_label": "表示モード",
|
||||||
@@ -1350,7 +1350,7 @@
|
|||||||
"reject_message": "拒否メッセージ",
|
"reject_message": "拒否メッセージ",
|
||||||
"reject_placeholder": "あなたのメールは拒否されました",
|
"reject_placeholder": "あなたのメールは拒否されました",
|
||||||
"label_name": "ラベル名",
|
"label_name": "ラベル名",
|
||||||
"label_placeholder": "例:重要",
|
"label_placeholder": "ラベルを選択",
|
||||||
"header_name": "ヘッダー名",
|
"header_name": "ヘッダー名",
|
||||||
"header_placeholder": "例:X-Mailing-List",
|
"header_placeholder": "例:X-Mailing-List",
|
||||||
"size_bytes": "サイズ(バイト)",
|
"size_bytes": "サイズ(バイト)",
|
||||||
|
|||||||
+15
-15
@@ -657,7 +657,7 @@
|
|||||||
"filters": "필터",
|
"filters": "필터",
|
||||||
"templates": "템플릿",
|
"templates": "템플릿",
|
||||||
"folders": "폴더",
|
"folders": "폴더",
|
||||||
"keywords": "키워드",
|
"keywords": "태그",
|
||||||
"security": "보안",
|
"security": "보안",
|
||||||
"files": "파일",
|
"files": "파일",
|
||||||
"contacts": "연락처",
|
"contacts": "연락처",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "이메일 키워드",
|
"title": "이메일 태그",
|
||||||
"description": "이메일을 분류할 키워드(라벨/태그)를 설정해 보세요. 설정한 키워드는 서버에 저장돼요.",
|
"description": "색상으로 이메일을 정리하기 위한 태그를 정의합니다. 서버에 JMAP 키워드로 저장됩니다.",
|
||||||
"add_keyword": "키워드 추가",
|
"add_keyword": "태그 추가",
|
||||||
"reset_defaults": "기본값으로 초기화",
|
"reset_defaults": "기본값으로 초기화",
|
||||||
"label_field": "표시 이름",
|
"label_field": "표시 이름",
|
||||||
"label_placeholder": "예: 업무, 개인, 긴급",
|
"label_placeholder": "예: 업무, 개인, 긴급",
|
||||||
"id_field": "키워드 ID",
|
"id_field": "태그 ID",
|
||||||
"id_placeholder": "예: work, personal",
|
"id_placeholder": "예: work, personal",
|
||||||
"color_field": "색상",
|
"color_field": "색상",
|
||||||
"id_exists": "이미 존재하는 키워드 ID예요",
|
"id_exists": "이 태그 ID는 이미 존재합니다",
|
||||||
"edit": "키워드 수정",
|
"edit": "태그 편집",
|
||||||
"delete": "키워드 삭제",
|
"delete": "태그 삭제",
|
||||||
"save": "저장",
|
"save": "저장",
|
||||||
"add": "추가",
|
"add": "추가",
|
||||||
"cancel": "취소",
|
"cancel": "취소",
|
||||||
"migrating": "기존 이메일의 키워드를 업데이트하는 중...",
|
"migrating": "기존 이메일의 태그 업데이트 중…",
|
||||||
"migration_error": "기존 이메일의 키워드를 업데이트하지 못했어요"
|
"migration_error": "기존 이메일의 태그 업데이트에 실패했습니다"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "알림음 테스트",
|
"test_sound": "알림음 테스트",
|
||||||
@@ -1337,7 +1337,7 @@
|
|||||||
"forward": "다음으로 전달",
|
"forward": "다음으로 전달",
|
||||||
"mark_read": "읽은 상태로 표시",
|
"mark_read": "읽은 상태로 표시",
|
||||||
"star": "별표 달기",
|
"star": "별표 달기",
|
||||||
"add_label": "라벨(태그) 추가",
|
"add_label": "태그 추가",
|
||||||
"discard": "삭제 (조용히 지움)",
|
"discard": "삭제 (조용히 지움)",
|
||||||
"reject": "메시지와 함께 수신 거부",
|
"reject": "메시지와 함께 수신 거부",
|
||||||
"keep": "받은편지함에 유지",
|
"keep": "받은편지함에 유지",
|
||||||
@@ -1349,8 +1349,8 @@
|
|||||||
"forward_placeholder": "email@example.com",
|
"forward_placeholder": "email@example.com",
|
||||||
"reject_message": "거부 메시지",
|
"reject_message": "거부 메시지",
|
||||||
"reject_placeholder": "메일 수신이 거부되었습니다",
|
"reject_placeholder": "메일 수신이 거부되었습니다",
|
||||||
"label_name": "라벨 이름",
|
"label_name": "태그 이름",
|
||||||
"label_placeholder": "예: important",
|
"label_placeholder": "태그 선택",
|
||||||
"header_name": "헤더 이름",
|
"header_name": "헤더 이름",
|
||||||
"header_placeholder": "예: X-Mailing-List",
|
"header_placeholder": "예: X-Mailing-List",
|
||||||
"size_bytes": "크기 (바이트)",
|
"size_bytes": "크기 (바이트)",
|
||||||
@@ -1524,8 +1524,8 @@
|
|||||||
"delete": "삭제",
|
"delete": "삭제",
|
||||||
"mark_as_spam": "스팸 신고",
|
"mark_as_spam": "스팸 신고",
|
||||||
"not_spam": "정상 메일",
|
"not_spam": "정상 메일",
|
||||||
"color_tag": "라벨 지정",
|
"color_tag": "태그",
|
||||||
"remove_color": "라벨 제거",
|
"remove_color": "태그 제거",
|
||||||
"items_selected": "{count}개의 메일 선택됨",
|
"items_selected": "{count}개의 메일 선택됨",
|
||||||
"edit_draft": "임시보관 메일 수정"
|
"edit_draft": "임시보관 메일 수정"
|
||||||
},
|
},
|
||||||
|
|||||||
+15
-15
@@ -657,7 +657,7 @@
|
|||||||
"filters": "Filtri",
|
"filters": "Filtri",
|
||||||
"templates": "Veidnes",
|
"templates": "Veidnes",
|
||||||
"folders": "Mapes",
|
"folders": "Mapes",
|
||||||
"keywords": "Atslēgvārdi",
|
"keywords": "Tagi",
|
||||||
"security": "Drošība",
|
"security": "Drošība",
|
||||||
"files": "Faili",
|
"files": "Faili",
|
||||||
"contacts": "Kontakti",
|
"contacts": "Kontakti",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "Vēstuļu atslēgvārdi",
|
"title": "E-pasta tagi",
|
||||||
"description": "Definējiet atslēgvārdus (etiķetes/tagus) vēstuļu organizēšanai ar krāsām. Tie tiek saglabāti serverī kā JMAP atslēgvārdi.",
|
"description": "Definējiet tagus, lai organizētu e-pastus ar krāsām. Tie tiek saglabāti kā JMAP atslēgvārdi serverī.",
|
||||||
"add_keyword": "Pievienot atslēgvārdu",
|
"add_keyword": "Pievienot tagu",
|
||||||
"reset_defaults": "Atiestatīt noklusējumu",
|
"reset_defaults": "Atiestatīt noklusējumu",
|
||||||
"label_field": "Redzamais nosaukums",
|
"label_field": "Redzamais nosaukums",
|
||||||
"label_placeholder": "piem., Darbs, Personīgi, Steidzami",
|
"label_placeholder": "piem., Darbs, Personīgi, Steidzami",
|
||||||
"id_field": "Atslēgvārda identifikators",
|
"id_field": "Taga identifikators",
|
||||||
"id_placeholder": "piem., darbs, personigi",
|
"id_placeholder": "piem., darbs, personigi",
|
||||||
"color_field": "Krāsa",
|
"color_field": "Krāsa",
|
||||||
"id_exists": "Šāds atslēgvārda identifikators jau eksistē",
|
"id_exists": "Šāds taga identifikators jau pastāv",
|
||||||
"edit": "Rediģēt atslēgvārdu",
|
"edit": "Rediģēt tagu",
|
||||||
"delete": "Dzēst atslēgvārdu",
|
"delete": "Dzēst tagu",
|
||||||
"save": "Saglabāt",
|
"save": "Saglabāt",
|
||||||
"add": "Pievienot",
|
"add": "Pievienot",
|
||||||
"cancel": "Atcelt",
|
"cancel": "Atcelt",
|
||||||
"migrating": "Atjaunina atslēgvārdu esošajās vēstulēs...",
|
"migrating": "Taga atjaunināšana esošajos e-pastos…",
|
||||||
"migration_error": "Neizdevās atjaunināt atslēgvārdu esošajās vēstulēs"
|
"migration_error": "Neizdevās atjaunināt tagu esošajos e-pastos"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "Pārbaudīt paziņojuma skaņu",
|
"test_sound": "Pārbaudīt paziņojuma skaņu",
|
||||||
@@ -1337,7 +1337,7 @@
|
|||||||
"forward": "Pārsūtīt uz",
|
"forward": "Pārsūtīt uz",
|
||||||
"mark_read": "Atzīmēt kā izlasītu",
|
"mark_read": "Atzīmēt kā izlasītu",
|
||||||
"star": "Pievienot zvaigznīti",
|
"star": "Pievienot zvaigznīti",
|
||||||
"add_label": "Pievienot etiķeti",
|
"add_label": "Pievienot tagu",
|
||||||
"discard": "Dzēst (bez paziņojuma)",
|
"discard": "Dzēst (bez paziņojuma)",
|
||||||
"reject": "Noraidīt ar ziņojumu",
|
"reject": "Noraidīt ar ziņojumu",
|
||||||
"keep": "Atstāt iesūtnē",
|
"keep": "Atstāt iesūtnē",
|
||||||
@@ -1349,8 +1349,8 @@
|
|||||||
"forward_placeholder": "lietotajs@piemers.lv",
|
"forward_placeholder": "lietotajs@piemers.lv",
|
||||||
"reject_message": "Noraidīšanas ziņojums",
|
"reject_message": "Noraidīšanas ziņojums",
|
||||||
"reject_placeholder": "Jūsu e-pasts tika noraidīts",
|
"reject_placeholder": "Jūsu e-pasts tika noraidīts",
|
||||||
"label_name": "Etiķetes nosaukums",
|
"label_name": "Taga nosaukums",
|
||||||
"label_placeholder": "piem., svarigi",
|
"label_placeholder": "Izvēlēties tagu",
|
||||||
"header_name": "Galvenes nosaukums",
|
"header_name": "Galvenes nosaukums",
|
||||||
"header_placeholder": "piem., X-Mailing-List",
|
"header_placeholder": "piem., X-Mailing-List",
|
||||||
"size_bytes": "Izmērs baitos",
|
"size_bytes": "Izmērs baitos",
|
||||||
@@ -1524,8 +1524,8 @@
|
|||||||
"delete": "Dzēst",
|
"delete": "Dzēst",
|
||||||
"mark_as_spam": "Atzīmēt kā mēstuli",
|
"mark_as_spam": "Atzīmēt kā mēstuli",
|
||||||
"not_spam": "Nav mēstule",
|
"not_spam": "Nav mēstule",
|
||||||
"color_tag": "Etiķete",
|
"color_tag": "Tags",
|
||||||
"remove_color": "Noņemt etiķeti",
|
"remove_color": "Noņemt tagu",
|
||||||
"items_selected": "{count} vēstules atlasītas",
|
"items_selected": "{count} vēstules atlasītas",
|
||||||
"edit_draft": "Rediģēt melnrakstu"
|
"edit_draft": "Rediģēt melnrakstu"
|
||||||
},
|
},
|
||||||
|
|||||||
+11
-11
@@ -657,7 +657,7 @@
|
|||||||
"filters": "Filters",
|
"filters": "Filters",
|
||||||
"templates": "Sjablonen",
|
"templates": "Sjablonen",
|
||||||
"folders": "Mappen",
|
"folders": "Mappen",
|
||||||
"keywords": "Sleutelwoorden",
|
"keywords": "Labels",
|
||||||
"security": "Beveiliging",
|
"security": "Beveiliging",
|
||||||
"encryption": "Versleuteling",
|
"encryption": "Versleuteling",
|
||||||
"files": "Bestanden",
|
"files": "Bestanden",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "E-mail trefwoorden",
|
"title": "E-maillabels",
|
||||||
"description": "Definieer trefwoorden (labels/tags) om uw e-mails met kleuren te organiseren.",
|
"description": "Definieer labels om uw e-mails met kleuren te organiseren. Deze worden opgeslagen als JMAP-trefwoorden op de server.",
|
||||||
"add_keyword": "Trefwoord toevoegen",
|
"add_keyword": "Label toevoegen",
|
||||||
"reset_defaults": "Standaardwaarden herstellen",
|
"reset_defaults": "Standaardwaarden herstellen",
|
||||||
"label_field": "Weergavenaam",
|
"label_field": "Weergavenaam",
|
||||||
"label_placeholder": "bijv. Werk, Persoonlijk, Urgent",
|
"label_placeholder": "bijv. Werk, Persoonlijk, Urgent",
|
||||||
"id_field": "Trefwoord-ID",
|
"id_field": "Label-ID",
|
||||||
"id_placeholder": "bijv. werk, persoonlijk",
|
"id_placeholder": "bijv. werk, persoonlijk",
|
||||||
"color_field": "Kleur",
|
"color_field": "Kleur",
|
||||||
"id_exists": "Dit trefwoord-ID bestaat al",
|
"id_exists": "Deze label-ID bestaat al",
|
||||||
"edit": "Trefwoord bewerken",
|
"edit": "Label bewerken",
|
||||||
"delete": "Trefwoord verwijderen",
|
"delete": "Label verwijderen",
|
||||||
"save": "Opslaan",
|
"save": "Opslaan",
|
||||||
"add": "Toevoegen",
|
"add": "Toevoegen",
|
||||||
"cancel": "Annuleren",
|
"cancel": "Annuleren",
|
||||||
"migrating": "Trefwoord bijwerken op bestaande e-mails…",
|
"migrating": "Label bijwerken op bestaande e-mails…",
|
||||||
"migration_error": "Kan trefwoord niet bijwerken op bestaande e-mails"
|
"migration_error": "Label bijwerken op bestaande e-mails mislukt"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "Meldingsgeluid testen",
|
"test_sound": "Meldingsgeluid testen",
|
||||||
@@ -1350,7 +1350,7 @@
|
|||||||
"reject_message": "Afwijzingsbericht",
|
"reject_message": "Afwijzingsbericht",
|
||||||
"reject_placeholder": "Uw e-mail is afgewezen",
|
"reject_placeholder": "Uw e-mail is afgewezen",
|
||||||
"label_name": "Labelnaam",
|
"label_name": "Labelnaam",
|
||||||
"label_placeholder": "bijv. belangrijk",
|
"label_placeholder": "Label kiezen",
|
||||||
"header_name": "Headernaam",
|
"header_name": "Headernaam",
|
||||||
"header_placeholder": "bijv. X-Mailing-List",
|
"header_placeholder": "bijv. X-Mailing-List",
|
||||||
"size_bytes": "Grootte in bytes",
|
"size_bytes": "Grootte in bytes",
|
||||||
|
|||||||
+11
-11
@@ -657,7 +657,7 @@
|
|||||||
"filters": "Filtry",
|
"filters": "Filtry",
|
||||||
"templates": "Szablony",
|
"templates": "Szablony",
|
||||||
"folders": "Foldery",
|
"folders": "Foldery",
|
||||||
"keywords": "Słowa kluczowe",
|
"keywords": "Etykiety",
|
||||||
"security": "Bezpieczeństwo",
|
"security": "Bezpieczeństwo",
|
||||||
"files": "Pliki",
|
"files": "Pliki",
|
||||||
"contacts": "Kontakty",
|
"contacts": "Kontakty",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "Słowa kluczowe wiadomości e-mail",
|
"title": "Etykiety e-mail",
|
||||||
"description": "Zdefiniuj słowa kluczowe (etykiety/tagi), aby porządkować wiadomości e-mail kolorami. Są one przechowywane na serwerze jako słowa kluczowe JMAP.",
|
"description": "Zdefiniuj etykiety do organizowania e-maili za pomocą kolorów. Są one przechowywane jako słowa kluczowe JMAP na serwerze.",
|
||||||
"add_keyword": "Dodaj słowo kluczowe",
|
"add_keyword": "Dodaj etykietę",
|
||||||
"reset_defaults": "Przywróć domyślne",
|
"reset_defaults": "Przywróć domyślne",
|
||||||
"label_field": "Nazwa wyświetlana",
|
"label_field": "Nazwa wyświetlana",
|
||||||
"label_placeholder": "np. Praca, Osobiste, Pilne",
|
"label_placeholder": "np. Praca, Osobiste, Pilne",
|
||||||
"id_field": "Identyfikator słowa kluczowego",
|
"id_field": "ID etykiety",
|
||||||
"id_placeholder": "np. praca, osobiste",
|
"id_placeholder": "np. praca, osobiste",
|
||||||
"color_field": "Kolor",
|
"color_field": "Kolor",
|
||||||
"id_exists": "Ten identyfikator słowa kluczowego już istnieje",
|
"id_exists": "Ten ID etykiety już istnieje",
|
||||||
"edit": "Edytuj słowo kluczowe",
|
"edit": "Edytuj etykietę",
|
||||||
"delete": "Usuń słowo kluczowe",
|
"delete": "Usuń etykietę",
|
||||||
"save": "Zapisz",
|
"save": "Zapisz",
|
||||||
"add": "Dodaj",
|
"add": "Dodaj",
|
||||||
"cancel": "Anuluj",
|
"cancel": "Anuluj",
|
||||||
"migrating": "Aktualizowanie słowa kluczowego w istniejących wiadomościach e-mail…",
|
"migrating": "Aktualizowanie etykiety w istniejących e-mailach…",
|
||||||
"migration_error": "Nie udało się zaktualizować słowa kluczowego w istniejących wiadomościach e-mail"
|
"migration_error": "Nie udało się zaktualizować etykiety w istniejących e-mailach"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "Przetestuj dźwięk powiadomienia",
|
"test_sound": "Przetestuj dźwięk powiadomienia",
|
||||||
@@ -1350,7 +1350,7 @@
|
|||||||
"reject_message": "Wiadomość odrzucenia",
|
"reject_message": "Wiadomość odrzucenia",
|
||||||
"reject_placeholder": "Twoja wiadomość e-mail została odrzucona",
|
"reject_placeholder": "Twoja wiadomość e-mail została odrzucona",
|
||||||
"label_name": "Nazwa etykiety",
|
"label_name": "Nazwa etykiety",
|
||||||
"label_placeholder": "np. ważne",
|
"label_placeholder": "Wybierz etykietę",
|
||||||
"header_name": "Nazwa nagłówka",
|
"header_name": "Nazwa nagłówka",
|
||||||
"header_placeholder": "np. X-Mailing-List",
|
"header_placeholder": "np. X-Mailing-List",
|
||||||
"size_bytes": "Rozmiar w bajtach",
|
"size_bytes": "Rozmiar w bajtach",
|
||||||
|
|||||||
+12
-12
@@ -657,7 +657,7 @@
|
|||||||
"filters": "Filtros",
|
"filters": "Filtros",
|
||||||
"templates": "Modelos",
|
"templates": "Modelos",
|
||||||
"folders": "Pastas",
|
"folders": "Pastas",
|
||||||
"keywords": "Palavras-chave",
|
"keywords": "Etiquetas",
|
||||||
"security": "Segurança",
|
"security": "Segurança",
|
||||||
"encryption": "Criptografia",
|
"encryption": "Criptografia",
|
||||||
"files": "Arquivos",
|
"files": "Arquivos",
|
||||||
@@ -725,22 +725,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "Palavras-chave de e-mail",
|
"title": "Etiquetas de e-mail",
|
||||||
"description": "Defina palavras-chave (rótulos/tags) para organizar seus e-mails com cores.",
|
"description": "Defina etiquetas para organizar os seus e-mails com cores. São armazenadas como palavras-chave JMAP no servidor.",
|
||||||
"add_keyword": "Adicionar palavra-chave",
|
"add_keyword": "Adicionar etiqueta",
|
||||||
"reset_defaults": "Restaurar padrões",
|
"reset_defaults": "Restaurar padrões",
|
||||||
"label_field": "Nome de exibição",
|
"label_field": "Nome de exibição",
|
||||||
"label_placeholder": "ex. Trabalho, Pessoal, Urgente",
|
"label_placeholder": "ex. Trabalho, Pessoal, Urgente",
|
||||||
"id_field": "ID da palavra-chave",
|
"id_field": "ID da etiqueta",
|
||||||
"id_placeholder": "ex. trabalho, pessoal",
|
"id_placeholder": "ex. trabalho, pessoal",
|
||||||
"color_field": "Cor",
|
"color_field": "Cor",
|
||||||
"id_exists": "Este ID de palavra-chave já existe",
|
"id_exists": "Este ID de etiqueta já existe",
|
||||||
"edit": "Editar palavra-chave",
|
"edit": "Editar etiqueta",
|
||||||
"delete": "Excluir palavra-chave",
|
"delete": "Eliminar etiqueta",
|
||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
"add": "Adicionar",
|
"add": "Adicionar",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"migrating": "Atualizando etiqueta nos e-mails existentes…",
|
"migrating": "A atualizar etiqueta nos e-mails existentes…",
|
||||||
"migration_error": "Falha ao atualizar etiqueta nos e-mails existentes"
|
"migration_error": "Falha ao atualizar etiqueta nos e-mails existentes"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
@@ -1337,7 +1337,7 @@
|
|||||||
"forward": "Encaminhar para",
|
"forward": "Encaminhar para",
|
||||||
"mark_read": "Marcar como lido",
|
"mark_read": "Marcar como lido",
|
||||||
"star": "Destacar mensagem",
|
"star": "Destacar mensagem",
|
||||||
"add_label": "Adicionar rótulo",
|
"add_label": "Adicionar etiqueta",
|
||||||
"discard": "Descartar (excluir silenciosamente)",
|
"discard": "Descartar (excluir silenciosamente)",
|
||||||
"reject": "Rejeitar com mensagem",
|
"reject": "Rejeitar com mensagem",
|
||||||
"keep": "Manter na caixa de entrada",
|
"keep": "Manter na caixa de entrada",
|
||||||
@@ -1349,8 +1349,8 @@
|
|||||||
"forward_placeholder": "email@exemplo.com",
|
"forward_placeholder": "email@exemplo.com",
|
||||||
"reject_message": "Mensagem de rejeição",
|
"reject_message": "Mensagem de rejeição",
|
||||||
"reject_placeholder": "Seu e-mail foi rejeitado",
|
"reject_placeholder": "Seu e-mail foi rejeitado",
|
||||||
"label_name": "Nome do rótulo",
|
"label_name": "Nome da etiqueta",
|
||||||
"label_placeholder": "ex. importante",
|
"label_placeholder": "Selecionar etiqueta",
|
||||||
"header_name": "Nome do cabeçalho",
|
"header_name": "Nome do cabeçalho",
|
||||||
"header_placeholder": "ex. X-Mailing-List",
|
"header_placeholder": "ex. X-Mailing-List",
|
||||||
"size_bytes": "Tamanho em bytes",
|
"size_bytes": "Tamanho em bytes",
|
||||||
|
|||||||
+15
-15
@@ -657,7 +657,7 @@
|
|||||||
"filters": "Фильтры",
|
"filters": "Фильтры",
|
||||||
"templates": "Шаблоны",
|
"templates": "Шаблоны",
|
||||||
"folders": "Папки",
|
"folders": "Папки",
|
||||||
"keywords": "Ключевые слова",
|
"keywords": "Теги",
|
||||||
"security": "Безопасность",
|
"security": "Безопасность",
|
||||||
"files": "Файлы",
|
"files": "Файлы",
|
||||||
"contacts": "Контакты",
|
"contacts": "Контакты",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "Ключевые слова писем",
|
"title": "Теги электронной почты",
|
||||||
"description": "Определите ключевые слова (метки/теги) для организации писем с помощью цветов. Они хранятся как ключевые слова JMAP на сервере.",
|
"description": "Определите теги для организации электронных писем с помощью цветов. Они хранятся как ключевые слова JMAP на сервере.",
|
||||||
"add_keyword": "Добавить ключевое слово",
|
"add_keyword": "Добавить тег",
|
||||||
"reset_defaults": "Сбросить по умолчанию",
|
"reset_defaults": "Сбросить по умолчанию",
|
||||||
"label_field": "Отображаемое название",
|
"label_field": "Отображаемое название",
|
||||||
"label_placeholder": "напр., Работа, Личное, Срочно",
|
"label_placeholder": "напр., Работа, Личное, Срочно",
|
||||||
"id_field": "Идентификатор ключевого слова",
|
"id_field": "Идентификатор тега",
|
||||||
"id_placeholder": "напр., work, personal",
|
"id_placeholder": "напр., work, personal",
|
||||||
"color_field": "Цвет",
|
"color_field": "Цвет",
|
||||||
"id_exists": "Этот идентификатор ключевого слова уже существует",
|
"id_exists": "Этот идентификатор тега уже существует",
|
||||||
"edit": "Редактировать ключевое слово",
|
"edit": "Редактировать тег",
|
||||||
"delete": "Удалить ключевое слово",
|
"delete": "Удалить тег",
|
||||||
"save": "Сохранить",
|
"save": "Сохранить",
|
||||||
"add": "Добавить",
|
"add": "Добавить",
|
||||||
"cancel": "Отмена",
|
"cancel": "Отмена",
|
||||||
"migrating": "Обновление ключевого слова в существующих письмах…",
|
"migrating": "Обновление тега в существующих письмах…",
|
||||||
"migration_error": "Не удалось обновить ключевое слово в существующих письмах"
|
"migration_error": "Не удалось обновить тег в существующих письмах"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "Проверить звук уведомления",
|
"test_sound": "Проверить звук уведомления",
|
||||||
@@ -1337,7 +1337,7 @@
|
|||||||
"forward": "Переслать на",
|
"forward": "Переслать на",
|
||||||
"mark_read": "Отметить прочитанным",
|
"mark_read": "Отметить прочитанным",
|
||||||
"star": "Пометить сообщение",
|
"star": "Пометить сообщение",
|
||||||
"add_label": "Добавить метку",
|
"add_label": "Добавить тег",
|
||||||
"discard": "Удалить (без уведомления)",
|
"discard": "Удалить (без уведомления)",
|
||||||
"reject": "Отклонить с сообщением",
|
"reject": "Отклонить с сообщением",
|
||||||
"keep": "Оставить во входящих",
|
"keep": "Оставить во входящих",
|
||||||
@@ -1349,8 +1349,8 @@
|
|||||||
"forward_placeholder": "user@пример.рф",
|
"forward_placeholder": "user@пример.рф",
|
||||||
"reject_message": "Сообщение об отклонении",
|
"reject_message": "Сообщение об отклонении",
|
||||||
"reject_placeholder": "Ваше письмо было отклонено",
|
"reject_placeholder": "Ваше письмо было отклонено",
|
||||||
"label_name": "Название метки",
|
"label_name": "Название тега",
|
||||||
"label_placeholder": "напр., важное",
|
"label_placeholder": "Выбрать тег",
|
||||||
"header_name": "Имя заголовка",
|
"header_name": "Имя заголовка",
|
||||||
"header_placeholder": "напр., X-Mailing-List",
|
"header_placeholder": "напр., X-Mailing-List",
|
||||||
"size_bytes": "Размер в байтах",
|
"size_bytes": "Размер в байтах",
|
||||||
@@ -1524,8 +1524,8 @@
|
|||||||
"delete": "Удалить",
|
"delete": "Удалить",
|
||||||
"mark_as_spam": "Отметить как спам",
|
"mark_as_spam": "Отметить как спам",
|
||||||
"not_spam": "Не спам",
|
"not_spam": "Не спам",
|
||||||
"color_tag": "Метка",
|
"color_tag": "Тег",
|
||||||
"remove_color": "Убрать метку",
|
"remove_color": "Удалить тег",
|
||||||
"items_selected": "{count} писем выбрано",
|
"items_selected": "{count} писем выбрано",
|
||||||
"edit_draft": "Редактировать черновик"
|
"edit_draft": "Редактировать черновик"
|
||||||
},
|
},
|
||||||
|
|||||||
+12
-12
@@ -657,7 +657,7 @@
|
|||||||
"filters": "过滤器",
|
"filters": "过滤器",
|
||||||
"templates": "模板",
|
"templates": "模板",
|
||||||
"folders": "文件夹",
|
"folders": "文件夹",
|
||||||
"keywords": "关键词",
|
"keywords": "标签",
|
||||||
"security": "安全",
|
"security": "安全",
|
||||||
"files": "文件",
|
"files": "文件",
|
||||||
"contacts": "联系人",
|
"contacts": "联系人",
|
||||||
@@ -725,23 +725,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"keywords": {
|
"keywords": {
|
||||||
"title": "邮件关键字",
|
"title": "电子邮件标签",
|
||||||
"description": "定义关键字(标签)并用颜色整理邮件。这些关键字会作为 JMAP 关键字保存在服务器上。",
|
"description": "定义标签以使用颜色组织您的电子邮件。这些标签作为JMAP关键词存储在服务器上。",
|
||||||
"add_keyword": "添加关键字",
|
"add_keyword": "添加标签",
|
||||||
"reset_defaults": "重置为默认值",
|
"reset_defaults": "重置为默认值",
|
||||||
"label_field": "显示名称",
|
"label_field": "显示名称",
|
||||||
"label_placeholder": "例如工作、个人、紧急",
|
"label_placeholder": "例如工作、个人、紧急",
|
||||||
"id_field": "关键字 ID",
|
"id_field": "标签ID",
|
||||||
"id_placeholder": "例如工作、个人",
|
"id_placeholder": "例如工作、个人",
|
||||||
"color_field": "颜色",
|
"color_field": "颜色",
|
||||||
"id_exists": "该关键字 ID 已存在",
|
"id_exists": "此标签ID已存在",
|
||||||
"edit": "编辑关键字",
|
"edit": "编辑标签",
|
||||||
"delete": "删除关键字",
|
"delete": "删除标签",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"add": "添加",
|
"add": "添加",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"migrating": "正在更新现有邮件的关键字...",
|
"migrating": "正在更新现有邮件的标签…",
|
||||||
"migration_error": "无法更新现有邮件的关键字"
|
"migration_error": "更新现有邮件的标签失败"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"test_sound": "测试通知声音",
|
"test_sound": "测试通知声音",
|
||||||
@@ -1350,7 +1350,7 @@
|
|||||||
"reject_message": "拒绝留言",
|
"reject_message": "拒绝留言",
|
||||||
"reject_placeholder": "您的邮件已被拒绝",
|
"reject_placeholder": "您的邮件已被拒绝",
|
||||||
"label_name": "标签名称",
|
"label_name": "标签名称",
|
||||||
"label_placeholder": "例如,重要",
|
"label_placeholder": "选择标签",
|
||||||
"header_name": "标头名称",
|
"header_name": "标头名称",
|
||||||
"header_placeholder": "例如,X-Mailing-List",
|
"header_placeholder": "例如,X-Mailing-List",
|
||||||
"size_bytes": "大小(以字节为单位)",
|
"size_bytes": "大小(以字节为单位)",
|
||||||
@@ -1525,7 +1525,7 @@
|
|||||||
"mark_as_spam": "举报垃圾邮件",
|
"mark_as_spam": "举报垃圾邮件",
|
||||||
"not_spam": "不是垃圾邮件",
|
"not_spam": "不是垃圾邮件",
|
||||||
"color_tag": "标签",
|
"color_tag": "标签",
|
||||||
"remove_color": "移除标签",
|
"remove_color": "删除标签",
|
||||||
"items_selected": "已选择 {count} 封邮件",
|
"items_selected": "已选择 {count} 封邮件",
|
||||||
"edit_draft": "编辑草稿"
|
"edit_draft": "编辑草稿"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user