Use American English spelling throughout
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@
|
||||
theme, which a media query cannot do — it only knows what the OS prefers,
|
||||
not what the user picked here. There used to be two, both with media
|
||||
attributes, which meant the selector in applyTheme (:not([media])) matched
|
||||
neither and the colour never moved off whatever the OS implied.
|
||||
neither and the color never moved off whatever the OS implied.
|
||||
|
||||
The initial value is the default theme's background, so the browser chrome
|
||||
is right from the first paint rather than only once JS has run.
|
||||
|
||||
+10
-10
@@ -44,7 +44,7 @@ export function App() {
|
||||
* knowing its strings just changed. Rather than make every one of the
|
||||
* thousand call sites a subscriber -- which would turn extracting a string
|
||||
* from "wrap it" into "wrap it and add a hook" -- the whole tree is thrown
|
||||
* away and rebuilt when the catalogue changes. Picking a language is a
|
||||
* away and rebuilt when the catalog changes. Picking a language is a
|
||||
* once-in-an-account event; paying for it there is far cheaper than paying
|
||||
* for it on every render everywhere.
|
||||
*/
|
||||
@@ -54,9 +54,9 @@ export function App() {
|
||||
}, [bootstrap]);
|
||||
|
||||
/*
|
||||
* Wait for the catalogue before the first paint.
|
||||
* Wait for the catalog before the first paint.
|
||||
*
|
||||
* The tree is rebuilt when a catalogue lands, so components recover on
|
||||
* The tree is rebuilt when a catalog lands, so components recover on
|
||||
* their own -- but a string computed in an effect does not. A toast fired
|
||||
* in the gap is emitted in English and stays English, in an interface that
|
||||
* is otherwise not. The wait costs nothing visible: the session bootstrap
|
||||
@@ -129,7 +129,7 @@ function AuthedApp() {
|
||||
* or the sign-out that every deploy causes -- the first frame is the
|
||||
* defaults, and the defaults are English. Rendering then means anything
|
||||
* computed before the settings land is computed in the wrong language: not
|
||||
* the interface, which is rebuilt when the catalogue arrives, but a string
|
||||
* the interface, which is rebuilt when the catalog arrives, but a string
|
||||
* emitted once, like a toast. That is why the stale-folder toast came out
|
||||
* in English on an otherwise German screen.
|
||||
*
|
||||
@@ -148,14 +148,14 @@ function AuthedApp() {
|
||||
setReady(true);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
let canceled = false;
|
||||
void (async () => {
|
||||
/* Before the account's own settings, so both the seeding below and the
|
||||
enforcement inside `hydrate` have something to apply. */
|
||||
await loadSettingsPolicy();
|
||||
if (cancelled) return;
|
||||
if (canceled) return;
|
||||
const remote = await loadRemoteSettings();
|
||||
if (cancelled) return;
|
||||
if (canceled) return;
|
||||
if (remote) useSettings.getState().hydrate(remote);
|
||||
// No settings file: this account has never had settings of its own, so
|
||||
// the installation's defaults are what it starts on rather than
|
||||
@@ -174,10 +174,10 @@ function AuthedApp() {
|
||||
other: "Your administrator changed {n} settings",
|
||||
}), { action: { label: t("Settings"), onClick: () => { window.location.href = withBase("/settings/general"); } } });
|
||||
}
|
||||
// The catalogue for whatever language that turned out to be. Hydrating
|
||||
// The catalog for whatever language that turned out to be. Hydrating
|
||||
// asks for it; this is waiting for the answer.
|
||||
await whenLanguageReady();
|
||||
if (cancelled) return;
|
||||
if (canceled) return;
|
||||
setReady(true);
|
||||
// Pushes were held back until now so they could not race the load. A
|
||||
// change made while it was in flight was kept, and goes out here.
|
||||
@@ -187,7 +187,7 @@ function AuthedApp() {
|
||||
if (!remote && settingsSyncAvailable()) queueSettingsPush(syncedPart(useSettings.getState().settings));
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
canceled = true;
|
||||
};
|
||||
}, [accountId]);
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ export class JmapClient {
|
||||
else reject(new ApiError(xhr.status, (xhr.response as ApiErrorBody)?.error ?? "upload_failed", (xhr.response as ApiErrorBody)?.message ?? "Upload failed"));
|
||||
};
|
||||
xhr.onerror = () => reject(new ApiError(0, "network_error", "Network error during upload"));
|
||||
xhr.onabort = () => reject(new ApiError(0, "aborted", "Upload cancelled"));
|
||||
xhr.onabort = () => reject(new ApiError(0, "aborted", "Upload canceled"));
|
||||
opts.signal?.addEventListener("abort", () => xhr.abort());
|
||||
xhr.send(data);
|
||||
});
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("generated passwords", () => {
|
||||
expect(p).not.toMatch(/[01lIO]/);
|
||||
});
|
||||
|
||||
it("skip bytes that would favour the start of the alphabet", () => {
|
||||
it("skip bytes that would favor the start of the alphabet", () => {
|
||||
// 256 % 55 leaves 36 byte values over; a plain modulo would hand those to
|
||||
// the first 36 characters twice as often. Bytes of 220 and up are dropped
|
||||
// and more are drawn, so a batch of nothing but those costs a draw.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { client, JmapMethodError } from "@/jmap/client";
|
||||
import { balancedColumns, countObjects, isRefused, loadMetrics, summariseMetrics, type MetricRecord } from "@/lib/adminDashboard";
|
||||
import { balancedColumns, countObjects, isRefused, loadMetrics, summarizeMetrics, type MetricRecord } from "@/lib/adminDashboard";
|
||||
|
||||
const counter = (metric: string, count: number, timestamp = "2026-09-15T14:00:00Z"): MetricRecord => ({ "@type": "Counter", metric, count, timestamp });
|
||||
|
||||
describe("the dashboard's message numbers", () => {
|
||||
it("adds received and sent up over the metric names Stalwart's own dashboard uses", () => {
|
||||
const stats = summariseMetrics([
|
||||
const stats = summarizeMetrics([
|
||||
counter("queue.message-queued", 6),
|
||||
counter("queue.message-queued", 4, "2026-09-15T13:00:00Z"),
|
||||
counter("queue.authenticated-message-queued", 2),
|
||||
@@ -20,7 +20,7 @@ describe("the dashboard's message numbers", () => {
|
||||
});
|
||||
|
||||
it("reads memory from the newest gauge, not the first one listed", () => {
|
||||
const stats = summariseMetrics([
|
||||
const stats = summarizeMetrics([
|
||||
{ "@type": "Gauge", metric: "server.memory", count: 100, timestamp: "2026-09-15T12:00:00Z" },
|
||||
{ "@type": "Gauge", metric: "server.memory", count: 300, timestamp: "2026-09-15T14:00:00Z" },
|
||||
{ "@type": "Gauge", metric: "queue.count", count: 7, timestamp: "2026-09-15T15:00:00Z" },
|
||||
@@ -29,8 +29,8 @@ describe("the dashboard's message numbers", () => {
|
||||
});
|
||||
|
||||
it("tells a history that records nothing from a quiet day", () => {
|
||||
expect(summariseMetrics([]).recorded).toBe(false);
|
||||
const quiet = summariseMetrics([{ "@type": "Gauge", metric: "server.memory", count: 1, timestamp: "2026-09-15T14:00:00Z" }]);
|
||||
expect(summarizeMetrics([]).recorded).toBe(false);
|
||||
const quiet = summarizeMetrics([{ "@type": "Gauge", metric: "server.memory", count: 1, timestamp: "2026-09-15T14:00:00Z" }]);
|
||||
expect(quiet).toMatchObject({ recorded: true, received: 0, sent: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,7 +62,7 @@ describe("the account query", () => {
|
||||
* live server gave, or one its source says it gives.
|
||||
*/
|
||||
describe("refusals in the reader's language", () => {
|
||||
it("recognises the registry's validators and says it again, without the server's words", () => {
|
||||
it("recognizes the registry's validators and says it again, without the server's words", () => {
|
||||
// Live, 2026-09-13: a reserved TLD, and a catch-all without a domain.
|
||||
const domain = describeDirectoryError(new DirectoryError("invalidPatch", "Invalid domain name", ["name"]), "domain");
|
||||
expect(domain).toMatch(/isn't a valid domain name/);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { describeLinked, dkimAlgorithm, looksLikeDomain, normaliseDomain, parseZoneFile } from "@/lib/adminDomains";
|
||||
import { describeLinked, dkimAlgorithm, looksLikeDomain, normalizeDomain, parseZoneFile } from "@/lib/adminDomains";
|
||||
|
||||
/**
|
||||
* Written the way Stalwart's BIND serialiser writes it (dns-update's
|
||||
* Written the way Stalwart's BIND serializer writes it (dns-update's
|
||||
* `BindSerializer`): `name IN TYPE value`, and a TXT over 255 bytes as a
|
||||
* parenthesised run of quoted chunks.
|
||||
* parenthesized run of quoted chunks.
|
||||
*/
|
||||
const long = "v=DKIM1; k=rsa; h=sha256; p=" + "A".repeat(400);
|
||||
const zone = [
|
||||
@@ -46,7 +46,7 @@ describe("reading the zone file", () => {
|
||||
|
||||
describe("domain names", () => {
|
||||
it("are written back lower-case without the root dot", () => {
|
||||
expect(normaliseDomain(" Example.COM. ")).toBe("example.com");
|
||||
expect(normalizeDomain(" Example.COM. ")).toBe("example.com");
|
||||
});
|
||||
|
||||
it("are checked loosely before the server decides", () => {
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("the span an availability bar covers", () => {
|
||||
expect(w.span).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("marks a single day every three hours, labelling every six", () => {
|
||||
it("marks a single day every three hours, labeling every six", () => {
|
||||
const w = availabilityWindow(at("2026-09-02T09:00:00"), at("2026-09-02T10:00:00"));
|
||||
expect(w.scale).toBe("hours");
|
||||
expect(hours(w)).toEqual(["2@0", "2@3", "2@6", "2@9", "2@12", "2@15", "2@18", "2@21"]);
|
||||
|
||||
@@ -73,7 +73,7 @@ describe("birthdaysInRange", () => {
|
||||
expect(birthdaysInRange([card("c2", "", { month: 6, day: 15 })], s, e)).toEqual([]);
|
||||
});
|
||||
|
||||
it("falls back to a name built from components, then to the organisation", () => {
|
||||
it("falls back to a name built from components, then to the organization", () => {
|
||||
const [s, e] = range("2026-01-01", "2027-01-01");
|
||||
const parts = {
|
||||
id: "c1",
|
||||
@@ -115,7 +115,7 @@ describe("birthdaysInRange", () => {
|
||||
expect(out.map((b) => b.name)).toEqual(["Amy", "Zoe"]);
|
||||
});
|
||||
|
||||
it("gives each occurrence a stable, unique id that marks it as synthesised", () => {
|
||||
it("gives each occurrence a stable, unique id that marks it as synthesized", () => {
|
||||
const [s, e] = range("2025-01-01", "2027-01-01");
|
||||
const out = birthdaysInRange([card("c1", "Ada", { month: 6, day: 15 })], s, e);
|
||||
expect(new Set(out.map((b) => b.id)).size).toBe(out.length);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { DEFAULT_APP_NAME } from "@/lib/brand";
|
||||
*
|
||||
* `APP_NAME` is a runtime variable, so every place showing the name has to ask
|
||||
* the server rather than have it written in. The sign-in page did not (#236's
|
||||
* neighbour): it fetched `/api/config`, received the name and used only
|
||||
* neighbor): it fetched `/api/config`, received the name and used only
|
||||
* `sourceUrl`, so a rebranded instance still said "ihasmail" on the page a new
|
||||
* user meets first. These pin the shape of the answer rather than the name.
|
||||
*/
|
||||
|
||||
@@ -97,14 +97,14 @@ describe("explicit date formats", () => {
|
||||
});
|
||||
|
||||
describe("clock preference", () => {
|
||||
it("honours 24-hour regardless of locale", () => {
|
||||
it("honors 24-hour regardless of locale", () => {
|
||||
setDateTimePrefs({ locale: "en-US", timeFormat: "24" });
|
||||
expect(formatClock(SAMPLE)).toBe("18:23");
|
||||
expect(uses24Hour()).toBe(true);
|
||||
expect(formatHourLabel(13)).toBe("13");
|
||||
expect(formatHourLabel(9)).toBe("09");
|
||||
});
|
||||
it("honours 12-hour regardless of locale", () => {
|
||||
it("honors 12-hour regardless of locale", () => {
|
||||
setDateTimePrefs({ locale: "de-DE", timeFormat: "12" });
|
||||
expect(formatClock(SAMPLE)).toBe("6:23 PM");
|
||||
expect(uses24Hour()).toBe(false);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* The two sentence builders, which had no tests while they were building
|
||||
* English by concatenation -- and no test would have caught the thing wrong
|
||||
* with them, since the English output was correct. These pin the two
|
||||
* properties that matter now: every fragment goes through the catalogue, and
|
||||
* properties that matter now: every fragment goes through the catalog, and
|
||||
* the joining is Intl's rather than a hardcoded " and ".
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
@@ -12,7 +12,7 @@ import { setUiLanguageForFormatting } from "../datetime";
|
||||
import { setCatalog } from "../i18n";
|
||||
|
||||
describe("sieve describeRule", () => {
|
||||
it("names the header and operator through the catalogue", () => {
|
||||
it("names the header and operator through the catalog", () => {
|
||||
const s = describeSieve({
|
||||
id: "1", name: "r", join: "allof", enabled: true,
|
||||
tests: [{ type: "header", header: "subject", op: "contains", value: "invoice" }],
|
||||
@@ -50,7 +50,7 @@ describe("recurrence describeRule", () => {
|
||||
expect(describeRecurrence({ "@type": "RecurrenceRule", frequency: "daily", interval: 3 } as never)).toBe("Every 3 days");
|
||||
});
|
||||
|
||||
it("recognises Monday to Friday as every weekday", () => {
|
||||
it("recognizes Monday to Friday as every weekday", () => {
|
||||
const rule = {
|
||||
"@type": "RecurrenceRule", frequency: "weekly",
|
||||
byDay: ["mo", "tu", "we", "th", "fr"].map((day) => ({ "@type": "NDay", day })),
|
||||
@@ -80,12 +80,12 @@ describe("recurrence describeRule", () => {
|
||||
expect(names[0]).toBe("Montag");
|
||||
expect(names).toHaveLength(7);
|
||||
// The narrow forms collide in English ("T" for both Tuesday and Thursday),
|
||||
// which is why they cannot be catalogue keys and come from Intl instead.
|
||||
// which is why they cannot be catalog keys and come from Intl instead.
|
||||
expect(weekdayOptions().map((w) => w.short)).toHaveLength(7);
|
||||
setUiLanguageForFormatting(null);
|
||||
});
|
||||
|
||||
it("renders a translated rule through the catalogue", () => {
|
||||
it("renders a translated rule through the catalog", () => {
|
||||
setCatalog("de", { strings: { Daily: "Täglich" }, plurals: {} });
|
||||
expect(describeRecurrence({ "@type": "RecurrenceRule", frequency: "daily" } as never)).toBe("Täglich");
|
||||
setCatalog("en", { strings: {}, plurals: {} });
|
||||
|
||||
@@ -93,14 +93,14 @@ describe("canMoveFolderTo", () => {
|
||||
});
|
||||
|
||||
describe("folderColor", () => {
|
||||
it("returns the colour chosen for that folder, and null for the rest", () => {
|
||||
it("returns the color chosen for that folder, and null for the rest", () => {
|
||||
const colors = { work: "#7c3aed" };
|
||||
expect(folderColor(colors, "work")).toBe("#7c3aed");
|
||||
expect(folderColor(colors, "news")).toBeNull();
|
||||
expect(folderColor({}, "work")).toBeNull();
|
||||
});
|
||||
|
||||
it("is keyed by id, so a renamed folder keeps its colour", () => {
|
||||
it("is keyed by id, so a renamed folder keeps its color", () => {
|
||||
// The id is stable across a rename; the name and path are not.
|
||||
expect(folderColor({ mb1: "#0f766e" }, "mb1")).toBe("#0f766e");
|
||||
});
|
||||
|
||||
@@ -34,7 +34,7 @@ describe("sanitizeEmailHtml", () => {
|
||||
});
|
||||
|
||||
describe("htmlDeclaresColors", () => {
|
||||
it("is false for mail that brings no colours", () => {
|
||||
it("is false for mail that brings no colors", () => {
|
||||
expect(htmlDeclaresColors("<p>Hi there</p>")).toBe(false);
|
||||
expect(htmlDeclaresColors("<div><b>bold</b> and <i>italic</i></div>", "font-family:Arial")).toBe(false);
|
||||
expect(htmlDeclaresColors('<a href="https://x.io/?color=red">link</a>')).toBe(false);
|
||||
@@ -54,9 +54,9 @@ describe("htmlDeclaresColors", () => {
|
||||
/**
|
||||
* Forcing the theme onto mail that styles itself — issue #290.
|
||||
*
|
||||
* The switch above it leaves nearly all HTML mail alone, because one colour
|
||||
* The switch above it leaves nearly all HTML mail alone, because one color
|
||||
* anywhere opts a message out. What this half has to get right is telling a
|
||||
* sheet the design sits on from a surface painted on top of it: neutralise the
|
||||
* sheet the design sits on from a surface painted on top of it: neutralize the
|
||||
* first and the white card goes away, keep the second and a button keeps a
|
||||
* label you can still read.
|
||||
*/
|
||||
@@ -70,15 +70,15 @@ describe("relativeLuminance", () => {
|
||||
expect(relativeLuminance("rgba(255,255,255,0.5)")).toBeCloseTo(1, 5);
|
||||
});
|
||||
|
||||
it("has nothing to say about a colour it cannot read", () => {
|
||||
it("has nothing to say about a color it cannot read", () => {
|
||||
// Not a failure: the caller treats null as "no deliberate surface", which
|
||||
// is the safe way round — an unreadable colour must not keep a white sheet.
|
||||
// is the safe way round — an unreadable color must not keep a white sheet.
|
||||
expect(relativeLuminance("color-mix(in srgb, red, blue)")).toBeNull();
|
||||
expect(relativeLuminance("var(--brand)")).toBeNull();
|
||||
expect(relativeLuminance("")).toBeNull();
|
||||
});
|
||||
|
||||
it("treats a fully transparent colour as painting nothing", () => {
|
||||
it("treats a fully transparent color as painting nothing", () => {
|
||||
expect(relativeLuminance("rgba(0,0,0,0)")).toBeNull();
|
||||
expect(relativeLuminance("transparent")).toBeNull();
|
||||
});
|
||||
@@ -100,21 +100,21 @@ describe("markKeptSurfaces", () => {
|
||||
* Marking is only half of it — the other half is the rule in EMAIL_BASE_CSS
|
||||
* that reads the marks, and #310 was a bug in that half rather than in the
|
||||
* marking. So these assert what the reader actually sees: does the
|
||||
* neutraliser hit this element? The selector is lifted out of the stylesheet
|
||||
* neutralizer hit this element? The selector is lifted out of the stylesheet
|
||||
* rather than copied, so a test cannot quietly drift from the rule it checks.
|
||||
*/
|
||||
const NEUTRALISER = (() => {
|
||||
const NEUTRALIZER = (() => {
|
||||
const m = EMAIL_BASE_CSS.match(
|
||||
/\.ihm-email-root\.forced\s+(\*:not\([^{]*?)\s*\{\s*color: inherit/,
|
||||
);
|
||||
if (!m) throw new Error("could not find the neutraliser rule in EMAIL_BASE_CSS");
|
||||
if (!m) throw new Error("could not find the neutralizer rule in EMAIL_BASE_CSS");
|
||||
return m[1]!.trim();
|
||||
})();
|
||||
|
||||
/** True when the theme is forced onto this element rather than leaving it alone. */
|
||||
const neutralised = (el: Element) => el.matches(NEUTRALISER);
|
||||
const neutralized = (el: Element) => el.matches(NEUTRALIZER);
|
||||
|
||||
it("keeps a coloured button and drops the white sheet around it", () => {
|
||||
it("keeps a colored button and drops the white sheet around it", () => {
|
||||
// The shape reported in #290: a Shopify/Klaviyo template whose outer 600px
|
||||
// wrapper carries bgcolor="#ffffff" and whose CTA carries bgcolor="#1155CC".
|
||||
const d = frag('<table bgcolor="#ffffff"><tr><td bgcolor="#1155CC"><a style="color:#FFFFFF">Buy</a></td></tr></table>');
|
||||
@@ -127,7 +127,7 @@ describe("markKeptSurfaces", () => {
|
||||
expect(d.querySelector("a")!.hasAttribute("data-ihm-in-keep")).toBe(true);
|
||||
});
|
||||
|
||||
it("neutralises a light panel nested inside a dark painted card", () => {
|
||||
it("neutralizes a light panel nested inside a dark painted card", () => {
|
||||
// The shape reported in #310: a dark Klaviyo campaign whose 600px cards
|
||||
// are dark enough to be marked, with light content tables inside them.
|
||||
// Those tables used to inherit the card's exemption and render as beige
|
||||
@@ -153,11 +153,11 @@ describe("markKeptSurfaces", () => {
|
||||
// The fix, stated the way the reader experiences it: the nested sheet is
|
||||
// themed, and so is the copy inside it. Before #310 both were exempt for
|
||||
// being descendants of the card.
|
||||
expect(neutralised(nested)).toBe(true);
|
||||
expect(neutralised(d.querySelector("td")!)).toBe(true);
|
||||
expect(neutralized(nested)).toBe(true);
|
||||
expect(neutralized(d.querySelector("td")!)).toBe(true);
|
||||
// The card itself is still left alone, and the page surround still goes.
|
||||
expect(neutralised(card)).toBe(false);
|
||||
expect(neutralised(surround)).toBe(true);
|
||||
expect(neutralized(card)).toBe(false);
|
||||
expect(neutralized(surround)).toBe(true);
|
||||
});
|
||||
|
||||
it("still keeps a button that sits inside a nested light panel", () => {
|
||||
@@ -172,11 +172,11 @@ describe("markKeptSurfaces", () => {
|
||||
'</div>',
|
||||
);
|
||||
expect(markKeptSurfaces(d)).toBe(2);
|
||||
expect(neutralised(d.querySelector("table")!)).toBe(true);
|
||||
expect(neutralised(d.querySelector("td")!)).toBe(false);
|
||||
expect(neutralized(d.querySelector("table")!)).toBe(true);
|
||||
expect(neutralized(d.querySelector("td")!)).toBe(false);
|
||||
// The label keeps its white, which is the thing #294 bought and this must
|
||||
// not spend.
|
||||
expect(neutralised(d.querySelector("a")!)).toBe(false);
|
||||
expect(neutralized(d.querySelector("a")!)).toBe(false);
|
||||
});
|
||||
|
||||
it("leaves no light panel exempt across the whole reported specimen", () => {
|
||||
@@ -201,7 +201,7 @@ describe("markKeptSurfaces", () => {
|
||||
expect(panels.length).toBe(21);
|
||||
|
||||
expect(markKeptSurfaces(d)).toBe(7);
|
||||
expect(panels.filter((p) => !neutralised(p))).toHaveLength(0);
|
||||
expect(panels.filter((p) => !neutralized(p))).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("reads an inline background as well as the attribute", () => {
|
||||
@@ -233,7 +233,7 @@ describe("markKeptSurfaces", () => {
|
||||
* The control that actually stops it is layout containment on an ancestor of
|
||||
* the shadow host, which mail CSS has no selector for; that lives in app.css
|
||||
* and is asserted at the bottom of this file, because jsdom does no layout and
|
||||
* cannot prove it here. These cover the second line of defence.
|
||||
* cannot prove it here. These cover the second line of defense.
|
||||
*/
|
||||
describe("mail CSS cannot climb out of its card", () => {
|
||||
const render = (html: string) => sanitizeEmailHtml(html).html;
|
||||
@@ -271,7 +271,7 @@ describe("mail CSS cannot climb out of its card", () => {
|
||||
describe("the containment that mail CSS cannot override", () => {
|
||||
it("is still applied to the message body container", async () => {
|
||||
// jsdom does no layout, so this asserts the control is present rather than
|
||||
// that it works; the behaviour was verified in a real browser. Without it,
|
||||
// that it works; the behavior was verified in a real browser. Without it,
|
||||
// a message can cover the viewport regardless of what the sanitizer does.
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const { join } = await import("node:path");
|
||||
|
||||
@@ -36,7 +36,7 @@ describe("deciding whether a message has an HTML alternative", () => {
|
||||
expect(hasHtmlAlternative({ type: "text/htmlish" }, "<p>Hi</p>")).toBe(false);
|
||||
});
|
||||
|
||||
it("matches the type case-insensitively, since a header may be capitalised", () => {
|
||||
it("matches the type case-insensitively, since a header may be capitalized", () => {
|
||||
expect(hasHtmlAlternative({ type: "TEXT/HTML" }, "<p>Hi</p>")).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -29,12 +29,12 @@ describe("t", () => {
|
||||
expect(currentLanguage()).toBe("en");
|
||||
});
|
||||
|
||||
it("translates once a catalogue is in force", () => {
|
||||
it("translates once a catalog is in force", () => {
|
||||
setCatalog("de", de);
|
||||
expect(t("Archive")).toBe("Archivieren");
|
||||
});
|
||||
|
||||
it("falls back per string, not per catalogue", () => {
|
||||
it("falls back per string, not per catalog", () => {
|
||||
setCatalog("de", de);
|
||||
expect(t("Report spam")).toBe("Report spam");
|
||||
});
|
||||
@@ -60,7 +60,7 @@ describe("interpolation", () => {
|
||||
describe("plural", () => {
|
||||
const FORMS = { one: "{n} message", other: "{n} messages" };
|
||||
|
||||
it("picks the English form without a catalogue", () => {
|
||||
it("picks the English form without a catalog", () => {
|
||||
expect(plural(1, FORMS)).toBe("1 message");
|
||||
expect(plural(0, FORMS)).toBe("0 messages");
|
||||
expect(plural(5, FORMS)).toBe("5 messages");
|
||||
@@ -73,7 +73,7 @@ describe("plural", () => {
|
||||
expect(plural(7, FORMS)).toBe("7 сообщений"); // many
|
||||
});
|
||||
|
||||
it("falls back to `other` when the catalogue lacks the category", () => {
|
||||
it("falls back to `other` when the catalog lacks the category", () => {
|
||||
setCatalog("de", de);
|
||||
// German has no "few"; asking for 3 must not render undefined.
|
||||
expect(plural(3, FORMS)).toBe("3 Nachrichten");
|
||||
@@ -95,7 +95,7 @@ describe("tNode", () => {
|
||||
|
||||
it("lets a translator move the element", () => {
|
||||
// Splitting the sentence into two t() calls could not do this: the
|
||||
// fragments would render in the English order whatever the catalogue said.
|
||||
// fragments would render in the English order whatever the catalog said.
|
||||
setCatalog("de", de);
|
||||
expect(render(tNode("Open {scheme} links here", { scheme: <code>mailto:</code> })))
|
||||
.toBe("<code>mailto:</code>-Links hier öffnen");
|
||||
|
||||
@@ -111,7 +111,7 @@ describe("parseIcsDuration", () => {
|
||||
});
|
||||
|
||||
describe("looksLikeCalendar", () => {
|
||||
it("recognises a calendar and rejects an error page", () => {
|
||||
it("recognizes a calendar and rejects an error page", () => {
|
||||
expect(looksLikeCalendar("BEGIN:VCALENDAR\r\nEND:VCALENDAR")).toBe(true);
|
||||
expect(looksLikeCalendar("<!doctype html><title>404</title>")).toBe(false);
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ const find = (e: JSCalendarEvent[], prefix: string) => eventLines(e).filter((l)
|
||||
const one = (e: JSCalendarEvent, prefix: string) => find([e], prefix)[0];
|
||||
|
||||
describe("the document around the events", () => {
|
||||
it("is a calendar a reader will recognise", () => {
|
||||
it("is a calendar a reader will recognize", () => {
|
||||
const l = lines([base]);
|
||||
expect(l[0]).toBe("BEGIN:VCALENDAR");
|
||||
expect(l).toContain("VERSION:2.0");
|
||||
@@ -105,7 +105,7 @@ describe("recurrence", () => {
|
||||
expect(one(e, "RRULE")).toBe("RRULE:FREQ=MONTHLY;BYDAY=-1TH");
|
||||
});
|
||||
|
||||
it("turns a cancelled occurrence into an EXDATE", () => {
|
||||
it("turns a canceled occurrence into an EXDATE", () => {
|
||||
const e = { ...weekly, recurrenceOverrides: { "2026-09-09T09:00:00": null } };
|
||||
expect(one(e, "EXDATE")).toBe("EXDATE;TZID=Europe/Berlin:20260909T090000");
|
||||
expect(find([e], "BEGIN:VEVENT")).toHaveLength(1);
|
||||
@@ -166,7 +166,7 @@ describe("the rest of an event", () => {
|
||||
expect(one(e, "TRANSP")).toBe("TRANSP:TRANSPARENT");
|
||||
});
|
||||
|
||||
it("writes the organiser and the guests, with what each answered", () => {
|
||||
it("writes the organizer and the guests, with what each answered", () => {
|
||||
const e = {
|
||||
...base,
|
||||
organizerCalendarAddress: "mailto:[email protected]",
|
||||
|
||||
@@ -19,7 +19,7 @@ describe("resolveUiLanguage", () => {
|
||||
});
|
||||
|
||||
it("refuses a language whose strings are not shipped", () => {
|
||||
// The account travels between machines and can outlive a catalogue. A
|
||||
// The account travels between machines and can outlive a catalog. A
|
||||
// page that says lang="fr" while rendering English is worse than one that
|
||||
// admits to English: it stops the reader translating it themselves.
|
||||
// Derived rather than named, so shipping another language does not turn
|
||||
@@ -30,7 +30,7 @@ describe("resolveUiLanguage", () => {
|
||||
});
|
||||
|
||||
it("carries the Beta flag until a person has signed the language off", () => {
|
||||
// Not a completeness measure. A catalogue can be word-for-word finished
|
||||
// Not a completeness measure. A catalog can be word-for-word finished
|
||||
// and still read like a machine wrote it, which is what this marks.
|
||||
// Every shipped language except English is unreviewed, and stays marked
|
||||
// until a person says otherwise.
|
||||
@@ -40,12 +40,12 @@ describe("resolveUiLanguage", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("honours one that is", () => {
|
||||
it("honors one that is", () => {
|
||||
for (const l of UI_LANGUAGES) expect(resolveUiLanguage(l.tag)).toBe(l.tag);
|
||||
});
|
||||
|
||||
it("only offers languages that resolve to themselves", () => {
|
||||
// Guards the ordering mistake: adding a picker entry before its catalogue.
|
||||
// Guards the ordering mistake: adding a picker entry before its catalog.
|
||||
for (const l of UI_LANGUAGES) {
|
||||
expect(resolveUiLanguage(l.tag)).toBe(l.tag);
|
||||
expect(l.name.trim()).not.toBe("");
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("comparatorsFor, custom levels", () => {
|
||||
});
|
||||
|
||||
describe("optional sorts, which a server is allowed to refuse", () => {
|
||||
it("recognises the keyword properties", () => {
|
||||
it("recognizes the keyword properties", () => {
|
||||
expect(isOptionalSort({ property: "hasKeyword", keyword: "$seen" })).toBe(true);
|
||||
expect(isOptionalSort({ property: "someInThreadHaveKeyword", keyword: "$flagged" })).toBe(true);
|
||||
expect(isOptionalSort({ property: "receivedAt" })).toBe(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { isLocalisedName, mailboxDisplayName, mailboxDisplayPath } from "@/lib/mailboxName";
|
||||
import { isLocalizedName, mailboxDisplayName, mailboxDisplayPath } from "@/lib/mailboxName";
|
||||
import { setCatalog, type Catalog } from "@/lib/i18n";
|
||||
import type { Mailbox } from "@/jmap/types";
|
||||
|
||||
@@ -19,7 +19,7 @@ const mb = (id: string, name: string, role: string | null = null, parentId: stri
|
||||
afterEach(() => setCatalog("en", { strings: {}, plurals: {} }));
|
||||
|
||||
describe("mailboxDisplayName", () => {
|
||||
it("is the server's name until a catalogue says otherwise", () => {
|
||||
it("is the server's name until a catalog says otherwise", () => {
|
||||
expect(mailboxDisplayName(mb("1", "Deleted Items", "trash"))).toBe("Deleted Items");
|
||||
});
|
||||
|
||||
@@ -43,18 +43,18 @@ describe("mailboxDisplayName", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("isLocalisedName", () => {
|
||||
describe("isLocalizedName", () => {
|
||||
it("tells an editor when the name on screen is not the server's", () => {
|
||||
// A rename box prefilled with "Papierkorb" would rename the folder to that
|
||||
// the moment somebody pressed Save — a real change made by accident.
|
||||
expect(isLocalisedName(mb("1", "Deleted Items", "trash"))).toBe(true);
|
||||
expect(isLocalisedName(mb("2", "Newsletters"))).toBe(false);
|
||||
expect(isLocalisedName(mb("3", "Work", "subscribed"))).toBe(false);
|
||||
expect(isLocalizedName(mb("1", "Deleted Items", "trash"))).toBe(true);
|
||||
expect(isLocalizedName(mb("2", "Newsletters"))).toBe(false);
|
||||
expect(isLocalizedName(mb("3", "Work", "subscribed"))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mailboxDisplayPath", () => {
|
||||
it("localises each part that has a role and leaves the rest", () => {
|
||||
it("localizes each part that has a role and leaves the rest", () => {
|
||||
setCatalog("de", de);
|
||||
const all = { a: mb("a", "Inbox", "inbox"), b: mb("b", "Projects", null, "a") };
|
||||
expect(mailboxDisplayPath(all.b!, all)).toBe("Posteingang / Projects");
|
||||
|
||||
@@ -35,7 +35,7 @@ describe("renderMarkdown", () => {
|
||||
/*
|
||||
* Markdown passes raw HTML through by design, and the file came from
|
||||
* somewhere else -- an upload, or a share from another account. Every one of
|
||||
* these renders as a script tag without a sanitiser.
|
||||
* these renders as a script tag without a sanitizer.
|
||||
*/
|
||||
it("takes out anything that would execute", () => {
|
||||
const html = renderMarkdown("<script>alert(1)</script>\n\n<img src=x onerror=alert(1)>\n\n<iframe src='https://evil.example'></iframe>\n");
|
||||
|
||||
@@ -85,7 +85,7 @@ describe("the rest of the schema", () => {
|
||||
expect(phones).toContainEqual(expect.objectContaining({ number: "3", features: { pager: true } }));
|
||||
});
|
||||
|
||||
it("reads the organisation, its units and the job title", () => {
|
||||
it("reads the organization, its units and the job title", () => {
|
||||
const c = card("dn: cn=X\ncn: X\no: Example Corp\nou: Research\nou: Optics\ntitle: Lens Grinder\n")!;
|
||||
expect(values(c.organizations)[0]).toMatchObject({
|
||||
name: "Example Corp",
|
||||
|
||||
@@ -5,7 +5,7 @@ describe("the palettes themselves", () => {
|
||||
it("has a light and a dark half for every one of them", () => {
|
||||
// The reason there is no "this palette is dark only" machinery: there is
|
||||
// no such palette. ihasmail's own gained a light half, and the override,
|
||||
// the toggle's memory and a greyed-out control all went with it.
|
||||
// the toggle's memory and a grayed-out control all went with it.
|
||||
expect(PALETTES.map((p) => p.id)).toEqual([
|
||||
"default", "ihasmail", "dracula", "gruvbox", "rose-pine", "tokyo-night",
|
||||
"catppuccin", "solarized", "ayu", "kanagawa", "everforest", "primer",
|
||||
@@ -75,7 +75,7 @@ describe("legacyTheme, read by a device still on an older build", () => {
|
||||
});
|
||||
|
||||
describe("toggleTarget", () => {
|
||||
it("flips the mode and keeps the colours, whatever the palette", () => {
|
||||
it("flips the mode and keeps the colors, whatever the palette", () => {
|
||||
for (const palette of ["default", "ihasmail", "gruvbox", "dracula", "rose-pine", "tokyo-night"] as const) {
|
||||
expect(toggleTarget({ palette, mode: "dark" }, false)).toEqual({ palette, mode: "light" });
|
||||
expect(toggleTarget({ palette, mode: "light" }, false)).toEqual({ palette, mode: "dark" });
|
||||
|
||||
@@ -30,7 +30,7 @@ describe("permission labels", () => {
|
||||
* does not: a missing one would show English in the middle of a translated
|
||||
* picker, and a stale one would never be looked up.
|
||||
*/
|
||||
describe("the permission catalogues", () => {
|
||||
describe("the permission catalogs", () => {
|
||||
const modules = import.meta.glob<{ permissionCatalog: PermissionCatalog }>("../../locales/permissions/*.ts");
|
||||
const tagOf = (path: string) => path.split("/").pop()!.replace(/\.ts$/, "");
|
||||
const languages = UI_LANGUAGES.map((l) => l.tag).filter((tag) => tag !== "en");
|
||||
|
||||
@@ -7,7 +7,7 @@ import { settingsAlreadyLoadedFor, stopSettingsSync } from "../settingsSync";
|
||||
* Settings used to live only in localStorage, so nothing followed the user
|
||||
* between devices — issue #54, whose sharpest case is the default identity:
|
||||
* with none set, the address that sorts first wins, so mail goes out from an
|
||||
* address the recipient may not recognise.
|
||||
* address the recipient may not recognize.
|
||||
*
|
||||
* The split is written as a list of exceptions, which means the interesting
|
||||
* test is not "does this key sync" but "does a key added later sync without
|
||||
|
||||
@@ -109,7 +109,7 @@ describe("sharing a file", () => {
|
||||
await expect(shareFile(aFile())).resolves.toBe("unsupported");
|
||||
});
|
||||
|
||||
it("raises anything it does not recognise, so a real fault is still reported", async () => {
|
||||
it("raises anything it does not recognize, so a real fault is still reported", async () => {
|
||||
stubNavigator({
|
||||
share: vi.fn(async () => { throw new DOMException("boom", "DataError"); }),
|
||||
canShare: (() => true) as unknown as Navigator["canShare"],
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { buildMarkerSignature, byteLength, compactHtml, markerOf, signatureTooLong, SIGNATURE_LIMIT } from "../signatureHtml";
|
||||
|
||||
describe("signature compaction", () => {
|
||||
it("strips office cruft and non-essential styles but keeps colours and links", () => {
|
||||
it("strips office cruft and non-essential styles but keeps colors and links", () => {
|
||||
const src = `<!--[if gte mso 9]><xml>x</xml><![endif]--><div class="WordSection1" style="mso-margin-top-alt:auto;line-height:115%;font-family:'Calibri',sans-serif;color:windowtext"><p class="MsoNormal" style="margin:0cm;font-size:11pt"><span lang="EN-US" style="font-size:12pt;color:#1F4E79;mso-fareast-language:EN-US"><b>John Coffey</b></span><o:p></o:p></p><p><span></span></p><a href="https://linuxexpert.org" target="_blank" data-x="1">linuxexpert.org</a><img src="https://x/y.png" width="100" style="mso-foo:bar"></div>`;
|
||||
const out = compactHtml(src);
|
||||
expect(out).not.toContain("mso-");
|
||||
|
||||
@@ -6,8 +6,8 @@ import { catalog as de } from "@/locales/de";
|
||||
|
||||
/**
|
||||
* The briefing is the only thing standing between a notification action and a
|
||||
* button labelled in a language the reader does not use — the worker is plain
|
||||
* JavaScript outside the bundle and cannot reach a catalogue.
|
||||
* button labeled in a language the reader does not use — the worker is plain
|
||||
* JavaScript outside the bundle and cannot reach a catalog.
|
||||
*
|
||||
* It is also the only place the archive mailbox is named, and getting that
|
||||
* wrong does not fail visibly: a message would be filed somewhere, just not
|
||||
@@ -45,7 +45,7 @@ describe("the worker's briefing", () => {
|
||||
});
|
||||
|
||||
it("carries the worker's text in the language the tab is in", async () => {
|
||||
// The worker has no catalogue. Everything it will say has to be said here
|
||||
// The worker has no catalog. Everything it will say has to be said here
|
||||
// first, or a German reader gets English buttons on their lock screen.
|
||||
setCatalog("de", de);
|
||||
const { store } = fakeCaches();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { SWIPE_CHOICES, describeSwipe, type SwipeAction } from "../swipe";
|
||||
|
||||
/**
|
||||
* A swipe names what it is about to do on a coloured strip the reader sees for
|
||||
* A swipe names what it is about to do on a colored strip the reader sees for
|
||||
* about a third of a second before letting go. These check that the name is
|
||||
* true in the folder it is being read in — which is the whole reason the
|
||||
* descriptor exists rather than a fixed label per setting.
|
||||
|
||||
@@ -138,7 +138,7 @@ describe("remembering the palette you were on", () => {
|
||||
expect(toggleTarget(away, false)).toEqual({ palette: "ihasmail", mode: "dark" });
|
||||
});
|
||||
|
||||
it("keeps the colours when the palette has both sides", () => {
|
||||
it("keeps the colors when the palette has both sides", () => {
|
||||
const away = toggleTarget({ palette: "gruvbox", mode: "dark" }, false);
|
||||
expect(away.palette).toBe("gruvbox");
|
||||
expect(away.mode).toBe("light");
|
||||
|
||||
@@ -65,7 +65,7 @@ const file = (name: string, body: string, extra: Attr[] = []): Attr[] => [
|
||||
const text = (a: Uint8Array) => new TextDecoder().decode(a);
|
||||
|
||||
describe("isTnef", () => {
|
||||
it("recognises the types and the filename", () => {
|
||||
it("recognizes the types and the filename", () => {
|
||||
expect(isTnef("application/ms-tnef", null)).toBe(true);
|
||||
expect(isTnef("application/vnd.ms-tnef; name=winmail.dat", null)).toBe(true);
|
||||
expect(isTnef("application/octet-stream", "winmail.dat")).toBe(true);
|
||||
|
||||
@@ -27,7 +27,7 @@ describe("lockAxis", () => {
|
||||
expect(lockAxis(30, 25)).toBe("y");
|
||||
});
|
||||
|
||||
it("counts distance on either axis towards committing", () => {
|
||||
it("counts distance on either axis toward committing", () => {
|
||||
expect(lockAxis(0, AXIS_SLOP)).toBe("y");
|
||||
expect(lockAxis(AXIS_SLOP, 0)).toBe("x");
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { createRoot, type Root } from "react-dom/client";
|
||||
/*
|
||||
* The guard's answers, and which of them the dialog leans on.
|
||||
*
|
||||
* It shipped with "Discard changes" as the only choice carrying a colour, which
|
||||
* It shipped with "Discard changes" as the only choice carrying a color, which
|
||||
* made losing the work the easy thing to click on a dialog whose entire purpose
|
||||
* is to stop that (#175). The emphasis belongs on the safe answer; the
|
||||
* destructive one stays legible as destructive without being the loudest thing
|
||||
|
||||
@@ -42,7 +42,7 @@ const advertises = (account: AccountLike | undefined, cap: string): boolean =>
|
||||
Boolean(account && cap in (account.accountCapabilities ?? {}));
|
||||
|
||||
/**
|
||||
* The account to read and write for this capability, honouring the switcher.
|
||||
* The account to read and write for this capability, honoring the switcher.
|
||||
*
|
||||
* Use for anything the reader is looking at: their mail, a shared calendar,
|
||||
* somebody's files. Not for anything of the reader's own — see below.
|
||||
|
||||
@@ -107,7 +107,7 @@ export interface RoleDef {
|
||||
* more rights than its own and sign in as it. ihasmail refuses to offer that,
|
||||
* and treats such an account as read-only.
|
||||
*
|
||||
* It errs towards refusing. A role that cannot be read -- the viewer lacks
|
||||
* It errs toward refusing. A role that cannot be read -- the viewer lacks
|
||||
* `sysRoleGet`, or the id is not in the list -- counts as outranking, because
|
||||
* an unknown grant is not a grant the viewer can be shown to hold. What it
|
||||
* cannot see is tenancy: an "Administrator" account is a tenant administrator
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface MessageStats {
|
||||
recorded: boolean;
|
||||
}
|
||||
|
||||
export function summariseMetrics(records: readonly MetricRecord[]): MessageStats {
|
||||
export function summarizeMetrics(records: readonly MetricRecord[]): MessageStats {
|
||||
let received = 0;
|
||||
let sent = 0;
|
||||
let memory: MessageStats["memory"] = null;
|
||||
|
||||
@@ -244,7 +244,7 @@ export type DirectoryObject = "account" | "domain" | "group" | "list" | "role" |
|
||||
* Stalwart explains a refusal in English, and its words are never shown as
|
||||
* they are: an interface in German that answers in English reads as broken
|
||||
* even when the English is exact. Every type the registry returns has its
|
||||
* own message, and a value a validator refused is recognised by the
|
||||
* own message, and a value a validator refused is recognized by the
|
||||
* validator's wording and said again here.
|
||||
*
|
||||
* One exception, on purpose. A password policy is the server's to set -- a
|
||||
@@ -282,16 +282,16 @@ export function describeDirectoryError(err: unknown, object: DirectoryObject = "
|
||||
return t("One of the chosen domain, role or group can't be used for this account.");
|
||||
case "overQuota":
|
||||
return object === "domain"
|
||||
? t("Your organisation has reached the number of domains it is allowed.")
|
||||
? t("Your organization has reached the number of domains it is allowed.")
|
||||
: object === "group"
|
||||
? t("Your organisation has reached the number of groups it is allowed.")
|
||||
? t("Your organization has reached the number of groups it is allowed.")
|
||||
: object === "list"
|
||||
? t("Your organisation has reached the number of mailing lists it is allowed.")
|
||||
? t("Your organization has reached the number of mailing lists it is allowed.")
|
||||
: object === "role"
|
||||
? t("Your organisation has reached the number of roles it is allowed.")
|
||||
? t("Your organization has reached the number of roles it is allowed.")
|
||||
: object === "tenant"
|
||||
? t("The server allows no more tenants.")
|
||||
: t("Your organisation has reached the number of accounts it is allowed.");
|
||||
: t("Your organization has reached the number of accounts it is allowed.");
|
||||
case "objectIsLinked":
|
||||
return t("Something still depends on this, so the server kept it.");
|
||||
case "notFound":
|
||||
|
||||
@@ -127,18 +127,18 @@ export async function namesOf(object: "Tenant" | "DnsServer", ids: string[]): Pr
|
||||
}
|
||||
|
||||
/** Lower-case, no surrounding space or root dot: how a domain is written back. */
|
||||
export function normaliseDomain(name: string): string {
|
||||
export function normalizeDomain(name: string): string {
|
||||
return name.trim().toLowerCase().replace(/\.$/, "");
|
||||
}
|
||||
|
||||
/** Enough of a check to catch a typo before the server does; the server decides. */
|
||||
export function looksLikeDomain(name: string): boolean {
|
||||
return /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9-]{2,63}$/.test(normaliseDomain(name));
|
||||
return /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9-]{2,63}$/.test(normalizeDomain(name));
|
||||
}
|
||||
|
||||
export async function createDomain(input: { name: string; description: string }): Promise<string> {
|
||||
const res = await client.call<SetResponse>("x:Domain/set", {
|
||||
create: { n: { name: normaliseDomain(input.name), description: input.description.trim() || null } },
|
||||
create: { n: { name: normalizeDomain(input.name), description: input.description.trim() || null } },
|
||||
});
|
||||
refused(res, "notCreated");
|
||||
const id = (res.created?.n as { id?: string } | undefined)?.id;
|
||||
@@ -179,8 +179,8 @@ export interface DnsRecord {
|
||||
/**
|
||||
* Read the zone file Stalwart computes for a domain.
|
||||
*
|
||||
* Its serialiser writes one record per line as `name IN TYPE value`, and a TXT
|
||||
* record longer than 255 bytes as a parenthesised run of quoted strings, one
|
||||
* Its serializer writes one record per line as `name IN TYPE value`, and a TXT
|
||||
* record longer than 255 bytes as a parenthesized run of quoted strings, one
|
||||
* per line. A DNS provider's form wants the whole value, so the strings are
|
||||
* joined and unescaped; the original lines are kept for anyone pasting into a
|
||||
* zone. Anything that does not parse is kept too, as its own row, rather than
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function loadRoleDefaults(): Promise<RoleDefaults | null> {
|
||||
}
|
||||
}
|
||||
|
||||
/** Stalwart's labelled permission list, through ihasmail's server. */
|
||||
/** Stalwart's labeled permission list, through ihasmail's server. */
|
||||
export async function loadPermissionList(): Promise<PermissionInfo[]> {
|
||||
const res = await apiFetch<{ permissions: PermissionInfo[] }>("/api/admin/permissions");
|
||||
return res.permissions;
|
||||
|
||||
@@ -53,8 +53,8 @@ function bodyText(email: Email): string {
|
||||
* Everyone the message was between, as guests: the sender and the people it
|
||||
* was addressed to.
|
||||
*
|
||||
* The reader's own addresses come out -- they are the organiser, and an
|
||||
* organiser listed among their own guests is an event that invites you to your
|
||||
* The reader's own addresses come out -- they are the organizer, and an
|
||||
* organizer listed among their own guests is an event that invites you to your
|
||||
* own appointment. Bcc stays out too, on a message the reader sent themselves:
|
||||
* a blind recipient added to a guest list is visible to every other guest, and
|
||||
* turning a hidden copy into a public one is not something a menu item should
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* `import.meta.env.BASE_URL` is Vite's own copy of the `base` it built with,
|
||||
* and `vite.config.ts` sets that from `BASE_PATH` through the shared
|
||||
* normaliser -- so this is the same answer the server reached, not a second
|
||||
* normalizer -- so this is the same answer the server reached, not a second
|
||||
* guess at it. Reading it here rather than re-deriving it from
|
||||
* `window.location` matters because the app is a SPA: at `/mail/inbox/abc`
|
||||
* there is nothing in the address that says how much of it is the mount.
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface Birthday {
|
||||
age: number | null;
|
||||
}
|
||||
|
||||
/** The prefix marking a synthesised event, so nothing tries to save one. */
|
||||
/** The prefix marking a synthesized event, so nothing tries to save one. */
|
||||
export const BIRTHDAY_ID_PREFIX = "ihm-birthday:";
|
||||
|
||||
/** The virtual calendar's id. Not a JMAP id, and deliberately unlike one. */
|
||||
|
||||
@@ -64,7 +64,7 @@ export function withPrefs<T>(over: Partial<DateTimePrefs>, fn: () => T): T {
|
||||
}
|
||||
}
|
||||
|
||||
/** Locale reported by Stalwart for this account (normalised), or null. */
|
||||
/** Locale reported by Stalwart for this account (normalized), or null. */
|
||||
export function setServerLocale(raw: string | null | undefined): void {
|
||||
serverLocale = normalizeLocale(raw);
|
||||
}
|
||||
@@ -193,7 +193,7 @@ function num(value: number, digits: number): string {
|
||||
return f.format(value);
|
||||
}
|
||||
|
||||
/** Time-of-day options honouring the 12h/24h preference. */
|
||||
/** Time-of-day options honoring the 12h/24h preference. */
|
||||
export function timeOptions(): Intl.DateTimeFormatOptions {
|
||||
switch (prefs.timeFormat) {
|
||||
case "24":
|
||||
@@ -562,13 +562,13 @@ export function localeOptions(): LocaleOption[] {
|
||||
* Weekday names in the reader's locale, indexed by JSCalendar's two-letter day.
|
||||
*
|
||||
* These used to be a table of English strings with a `short` of "M", "T", "W"…
|
||||
* which could not become catalogue entries at all: "T" is both Tuesday and
|
||||
* which could not become catalog entries at all: "T" is both Tuesday and
|
||||
* Thursday and "S" is both Saturday and Sunday, so the key collides with
|
||||
* itself. A catalogue cannot hold two translations under one key, and no
|
||||
* itself. A catalog cannot hold two translations under one key, and no
|
||||
* amount of translating fixes that — the data was wrong, not the wiring.
|
||||
*
|
||||
* Intl has the names already, in every locale, in three widths, and gets the
|
||||
* plural and capitalisation conventions right without anybody maintaining a
|
||||
* plural and capitalization conventions right without anybody maintaining a
|
||||
* list. 2026-06-01 is a Monday; the rest follow from it.
|
||||
*/
|
||||
export type WeekdayKey = "mo" | "tu" | "we" | "th" | "fr" | "sa" | "su";
|
||||
|
||||
@@ -27,7 +27,7 @@ function unsafe(ch: string): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Long enough to stay recognisable, short enough to survive a 255-*byte* limit
|
||||
* Long enough to stay recognizable, short enough to survive a 255-*byte* limit
|
||||
* once a CJK subject is three bytes a character.
|
||||
*/
|
||||
const MAX = 80;
|
||||
|
||||
@@ -54,7 +54,7 @@ export function isShared(node: Pick<FileNode, "shareWith">): boolean {
|
||||
* legal moves behind a disabled drop. The server refuses those with a message
|
||||
* of its own, which is a better answer than a silent one.
|
||||
*/
|
||||
/** The MIME a dragged node is offered under, so a target can recognise it. */
|
||||
/** The MIME a dragged node is offered under, so a target can recognize it. */
|
||||
export const NODE_MIME = "application/x-ihasmail-filenode";
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,7 +66,7 @@ export function canMoveFolderTo(mailboxes: Record<Id, Mailbox>, id: Id, targetId
|
||||
return targetId === null || Boolean(mailboxes[targetId]?.myRights.mayCreateChild);
|
||||
}
|
||||
|
||||
/** The colour chosen for a folder, if any. Ids are used, so a rename keeps it. */
|
||||
/** The color chosen for a folder, if any. Ids are used, so a rename keeps it. */
|
||||
export function folderColor(colors: Record<string, string>, id: Id): string | null {
|
||||
return colors[id] ?? null;
|
||||
}
|
||||
|
||||
+16
-16
@@ -50,7 +50,7 @@ function ensureHooks() {
|
||||
* convincing fake over the whole app. The control that actually stops that is
|
||||
* layout containment on an ancestor of the shadow host (see `.message-body` in
|
||||
* app.css), which mail CSS has no selector for. This is the second line:
|
||||
* neutralise the declarations themselves, and defang `:host`, which is how mail
|
||||
* neutralize the declarations themselves, and defang `:host`, which is how mail
|
||||
* CSS would otherwise reach the host element.
|
||||
*/
|
||||
function hardenCss(css: string): string {
|
||||
@@ -183,7 +183,7 @@ export const EMAIL_BASE_CSS = `
|
||||
.ihm-email-root * { max-width:100%; box-sizing:border-box; }
|
||||
.ihm-email-root [style*="position:fixed"], .ihm-email-root [style*="position: fixed"] { position:static !important; }
|
||||
|
||||
/* "Follow the app theme" — only applied to mail that brings no colours of its
|
||||
/* "Follow the app theme" — only applied to mail that brings no colors of its
|
||||
own. The custom properties are inherited from the host document, so a theme
|
||||
switch repaints the message without re-rendering it. */
|
||||
.ihm-email-root.themed { color: var(--fg, #1f2937); background: var(--bg-elev, #fff); }
|
||||
@@ -193,7 +193,7 @@ export const EMAIL_BASE_CSS = `
|
||||
.ihm-email-root.themed img[data-ihm-blocked] { background: var(--bg-sunken, #f1f5f9) repeating-linear-gradient(45deg, var(--bg-hover, #e2e8f0) 0 6px, transparent 6px 12px); border-color: var(--border-strong, #cbd5e1); }
|
||||
|
||||
/* "Even mail that styles itself" — the second, opt-in switch, applied on top of
|
||||
.themed. Everything the sender coloured is neutralised except the surfaces
|
||||
.themed. Everything the sender colored is neutralized except the surfaces
|
||||
marked by markKeptSurfaces() and what it marked as sitting on them, so a
|
||||
white wrapper table
|
||||
stops being a bright card while a blue button keeps its white label. The
|
||||
@@ -205,7 +205,7 @@ export const EMAIL_BASE_CSS = `
|
||||
`;
|
||||
|
||||
/**
|
||||
* Does this message paint itself? Mail that sets a background or text colour
|
||||
* Does this message paint itself? Mail that sets a background or text color
|
||||
* has a design of its own, and forcing a dark palette on half of it is worse
|
||||
* than leaving it alone — so those keep the light card they were built for.
|
||||
*
|
||||
@@ -227,7 +227,7 @@ export function htmlDeclaresColors(html: string, bodyStyle = ""): boolean {
|
||||
/* ---------- forcing the theme onto mail that styles itself ---------- */
|
||||
|
||||
/**
|
||||
* Relative luminance per WCAG 2.x, or `null` when the colour cannot be read.
|
||||
* Relative luminance per WCAG 2.x, or `null` when the color cannot be read.
|
||||
*
|
||||
* Only what actually turns up in mail is parsed: hex in three, six or eight
|
||||
* digits, `rgb()`/`rgba()`, and the handful of names senders still write out.
|
||||
@@ -263,7 +263,7 @@ export function relativeLuminance(color: string): number | null {
|
||||
if (m[4] !== undefined) a = m[4].endsWith("%") ? Number(m[4].slice(0, -1)) / 100 : Number(m[4]);
|
||||
}
|
||||
if ([r, g, b, a].some((n) => !Number.isFinite(n))) return null;
|
||||
// A fully transparent colour paints nothing, whatever its channels say.
|
||||
// A fully transparent color paints nothing, whatever its channels say.
|
||||
if (a === 0) return null;
|
||||
const lin = (c: number) => { const x = c / 255; return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4; };
|
||||
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
|
||||
@@ -272,7 +272,7 @@ export function relativeLuminance(color: string): number | null {
|
||||
/**
|
||||
* Above this, a background is a sheet the message is laid on rather than a
|
||||
* thing drawn on top of it. White wrappers sit at 1.0; the blue of a call to
|
||||
* action lands near 0.09, mid-grey near 0.22.
|
||||
* action lands near 0.09, mid-gray near 0.22.
|
||||
*/
|
||||
export const LIGHT_SURFACE_LUMINANCE = 0.5;
|
||||
|
||||
@@ -288,26 +288,26 @@ function declaredLuminance(el: HTMLElement): number | null {
|
||||
*
|
||||
* The reader has asked for their palette on mail that brings its own, which
|
||||
* cannot be done perfectly — this is the same bargain a dark-reader extension
|
||||
* makes. What it can do is tell the two kinds of colour apart: a **sheet** the
|
||||
* design sits on, which is what reads as a bright card and is neutralised, and
|
||||
* makes. What it can do is tell the two kinds of color apart: a **sheet** the
|
||||
* design sits on, which is what reads as a bright card and is neutralized, and
|
||||
* a **painted surface** — a button, a banner — which is kept whole so its
|
||||
* label stays legible on it.
|
||||
*
|
||||
* Two attributes come out of this. `data-ihm-keep` is a painted surface, which
|
||||
* keeps its own colours. `data-ihm-in-keep` is an element sitting on one with
|
||||
* no background of its own, whose colour is left alone so a white label on a
|
||||
* blue button stays readable. One rule in EMAIL_BASE_CSS neutralises
|
||||
* keeps its own colors. `data-ihm-in-keep` is an element sitting on one with
|
||||
* no background of its own, whose color is left alone so a white label on a
|
||||
* blue button stays readable. One rule in EMAIL_BASE_CSS neutralizes
|
||||
* everything else.
|
||||
*
|
||||
* The distinction that matters is that being *inside* a painted surface is not
|
||||
* inherited past a sheet. A light table nested in a dark 600px card is still a
|
||||
* sheet and is still neutralised — that is issue #310, where a dark campaign
|
||||
* sheet and is still neutralized — that is issue #310, where a dark campaign
|
||||
* rendered with beige cards inside it because the exemption used to be
|
||||
* `[data-ihm-keep] *` in CSS and could not see the difference. Paint resumes
|
||||
* below it: a dark button inside that nested table is kept as usual.
|
||||
*
|
||||
* Nothing the sender wrote is removed, so turning the switch off puts the
|
||||
* message back exactly as it was — and a colour that arrived from a `<style>`
|
||||
* message back exactly as it was — and a color that arrived from a `<style>`
|
||||
* block rather than an attribute is covered too, which is most of them in
|
||||
* modern templates.
|
||||
*/
|
||||
@@ -336,11 +336,11 @@ export function markKeptSurfaces(root: ParentNode): number {
|
||||
kept++;
|
||||
childrenOnPaint = true;
|
||||
} else if (lum !== null) {
|
||||
// A sheet, wherever it sits. Left unmarked so it neutralises, and it
|
||||
// A sheet, wherever it sits. Left unmarked so it neutralizes, and it
|
||||
// ends the protection rather than passing it on.
|
||||
childrenOnPaint = false;
|
||||
} else if (onPaint) {
|
||||
// No background of its own, sitting on paint: leave its colour alone.
|
||||
// No background of its own, sitting on paint: leave its color alone.
|
||||
el.setAttribute("data-ihm-in-keep", "");
|
||||
}
|
||||
|
||||
|
||||
+16
-16
@@ -5,9 +5,9 @@ import { DEFAULT_UI_LANGUAGE, resolveUiLanguage } from "@/lib/languages";
|
||||
* Translation, in about as little machinery as the job takes.
|
||||
*
|
||||
* The English text is the key. `t("Archive")` looks "Archive" up in whatever
|
||||
* catalogue is loaded and returns the English if it is not there, which buys
|
||||
* catalog is loaded and returns the English if it is not there, which buys
|
||||
* three things worth more than tidy symbolic keys: there is no English
|
||||
* catalogue to keep in step with the code, a missing translation degrades to
|
||||
* catalog to keep in step with the code, a missing translation degrades to
|
||||
* readable English rather than to `mail.list.archive`, and extracting a string
|
||||
* is wrapping it rather than inventing a name for it. Names are where
|
||||
* extraction stalls -- 55 components is a lot of small naming arguments.
|
||||
@@ -71,7 +71,7 @@ export function t(source: string, vars?: Vars): string {
|
||||
*
|
||||
* So a context can be given, and the lookup becomes context + source while the
|
||||
* fallback stays the plain English. A translator sees the context and knows
|
||||
* which sense to render; a catalogue that has not got round to it still
|
||||
* which sense to render; a catalog that has not got round to it still
|
||||
* renders the English word, which was right in English all along.
|
||||
*
|
||||
* The separator is a control character rather than a punctuation mark, which
|
||||
@@ -91,7 +91,7 @@ export function tc(context: string, source: string, vars?: Vars): string {
|
||||
* Two forms is an English assumption and does not survive the second phase of
|
||||
* this: Russian and Ukrainian use three, and picking between them is not
|
||||
* `n === 1`. `Intl.PluralRules` knows the rule for every language the browser
|
||||
* knows, so the catalogue supplies the forms and the runtime picks.
|
||||
* knows, so the catalog supplies the forms and the runtime picks.
|
||||
*
|
||||
* The English `other` form is the key, so a call site reads as the sentence it
|
||||
* produces and needs no invented name.
|
||||
@@ -143,7 +143,7 @@ export function tNode(source: string, parts: Record<string, ReactNode>, vars?: V
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Subscribe to catalogue changes without React. Used by the tests. */
|
||||
/** Subscribe to catalog changes without React. Used by the tests. */
|
||||
export function subscribeForTest(fn: () => void): () => void {
|
||||
listeners.add(fn);
|
||||
return () => void listeners.delete(fn);
|
||||
@@ -155,27 +155,27 @@ export function currentLanguage(): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a catalogue in force.
|
||||
* Put a catalog in force.
|
||||
*
|
||||
* Exported for tests and for the loader; nothing else should call it, because
|
||||
* the tag and the catalogue have to move together or `plural` selects with one
|
||||
* the tag and the catalog have to move together or `plural` selects with one
|
||||
* language's rules against another's forms.
|
||||
*/
|
||||
export function setCatalog(tag: string, catalog: Catalog): void {
|
||||
/*
|
||||
* Publishing only when something actually changed is not an optimisation
|
||||
* Publishing only when something actually changed is not an optimization
|
||||
* here, it is the thing that stops an infinite loop.
|
||||
*
|
||||
* The root keys its tree on the language version, so a publish remounts
|
||||
* everything. Remounting re-runs the effect that fetches the account's
|
||||
* settings file, which calls `hydrate`, which calls `applyLang`, which lands
|
||||
* back here -- with the identical tag and the identical catalogue. Publishing
|
||||
* back here -- with the identical tag and the identical catalog. Publishing
|
||||
* that non-change bumped the version again and went round for ever: the
|
||||
* message list refetched on every pass, which is what it looked like from
|
||||
* the outside.
|
||||
*
|
||||
* Reference equality is enough. `EMPTY` is a module constant and a
|
||||
* dynamically imported catalogue is cached, so the same language really does
|
||||
* dynamically imported catalog is cached, so the same language really does
|
||||
* hand back the same object.
|
||||
*/
|
||||
if (currentTag === tag && current === catalog) return;
|
||||
@@ -188,16 +188,16 @@ export function setCatalog(tag: string, catalog: Catalog): void {
|
||||
* Load and apply a language.
|
||||
*
|
||||
* English is the built-in: it is the source text, so there is nothing to fetch
|
||||
* and no chance of a missing catalogue leaving the app blank. Everything else
|
||||
* and no chance of a missing catalog leaving the app blank. Everything else
|
||||
* is a dynamic import, so a reader who never leaves English never downloads a
|
||||
* catalogue -- which matters, because the main bundle is already large enough
|
||||
* catalog -- which matters, because the main bundle is already large enough
|
||||
* to warn about.
|
||||
*/
|
||||
/**
|
||||
* The catalogue load that is in flight, so the first paint can wait for it.
|
||||
* The catalog load that is in flight, so the first paint can wait for it.
|
||||
*
|
||||
* Without this, a cold load paints before the catalogue lands. Components
|
||||
* recover -- the tree is rebuilt when the catalogue arrives -- but a string
|
||||
* Without this, a cold load paints before the catalog lands. Components
|
||||
* recover -- the tree is rebuilt when the catalog arrives -- but a string
|
||||
* computed in an effect does not: a toast fired in that window is emitted in
|
||||
* English and stays English, in an interface that is otherwise German.
|
||||
* Reported as a stale-folder toast that ignored the language setting.
|
||||
@@ -224,7 +224,7 @@ async function loadLanguageNow(tag: string): Promise<void> {
|
||||
const mod = (await import(`../locales/${resolved}.ts`)) as { catalog: Catalog };
|
||||
setCatalog(resolved, mod.catalog);
|
||||
} catch {
|
||||
// A catalogue that will not load leaves English in force rather than a
|
||||
// A catalog that will not load leaves English in force rather than a
|
||||
// half-rendered page. `resolveUiLanguage` should already have prevented
|
||||
// this; it being reachable at all is why it is caught.
|
||||
setCatalog(DEFAULT_UI_LANGUAGE, EMPTY);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* is the reverse, which is why `uiLanguage` and `locale` are separate settings
|
||||
* rather than one.
|
||||
*
|
||||
* Adding a language means adding its catalogue and then adding it here, in
|
||||
* Adding a language means adding its catalog and then adding it here, in
|
||||
* that order. RTL languages — Arabic, Hebrew, Persian — need bidi and layout
|
||||
* work well beyond strings, so they are not simply a matter of another entry.
|
||||
*/
|
||||
@@ -26,8 +26,8 @@ export interface UiLanguage {
|
||||
/**
|
||||
* Machine-translated and not yet checked by somebody who speaks it.
|
||||
*
|
||||
* Stays true until a native speaker has actually read the catalogue and said
|
||||
* so. It is not a measure of how complete the file is -- a catalogue can be
|
||||
* Stays true until a native speaker has actually read the catalog and said
|
||||
* so. It is not a measure of how complete the file is -- a catalog can be
|
||||
* word-for-word finished and still read like a machine wrote it, which is
|
||||
* the thing this flag is about. Removing it is a deliberate act by a person,
|
||||
* not something a coverage number earns.
|
||||
@@ -56,8 +56,8 @@ export const DEFAULT_UI_LANGUAGE = "en";
|
||||
/**
|
||||
* The language to actually render in.
|
||||
*
|
||||
* A stored preference is only honoured if its strings are still shipped: a
|
||||
* catalogue can be withdrawn, and an account carrying `de` from another
|
||||
* A stored preference is only honored if its strings are still shipped: a
|
||||
* catalog can be withdrawn, and an account carrying `de` from another
|
||||
* machine must not leave this one claiming to be German while showing English.
|
||||
*/
|
||||
export function resolveUiLanguage(stored: string | undefined | null): string {
|
||||
|
||||
+4
-4
@@ -109,7 +109,7 @@ export function parseLdif(text: string): LdifRecord[] {
|
||||
/**
|
||||
* An identity for an entry, derived from its distinguished name.
|
||||
*
|
||||
* Mozilla's schema has no UID, so a re-import had nothing to be recognised by
|
||||
* Mozilla's schema has no UID, so a re-import had nothing to be recognized by
|
||||
* and duplicated everything (#223). The `dn` is what the file actually carries,
|
||||
* and it does not need to be a durable identity to answer the only question
|
||||
* being asked of it: have I imported this exact entry before? A migration is
|
||||
@@ -123,7 +123,7 @@ export function parseLdif(text: string): LdifRecord[] {
|
||||
* *same* address book, are one contact afterwards. Matching is per book, so
|
||||
* filing two directories in two books keeps them apart.
|
||||
*
|
||||
* Normalised for case and for the spacing exporters differ in, which costs
|
||||
* Normalized for case and for the spacing exporters differ in, which costs
|
||||
* nothing when a file is compared against itself and helps when it is compared
|
||||
* against a differently-produced export of the same directory.
|
||||
*
|
||||
@@ -131,10 +131,10 @@ export function parseLdif(text: string): LdifRecord[] {
|
||||
* and duplicates on re-import, as everything did before.
|
||||
*/
|
||||
export function uidFromDn(dn: string): string | null {
|
||||
const normalised = dn
|
||||
const normalized = dn
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, " ")
|
||||
.replace(/\s*([,=])\s*/g, "$1");
|
||||
return normalised ? `urn:x-ihasmail:ldif:${encodeURIComponent(normalised)}` : null;
|
||||
return normalized ? `urn:x-ihasmail:ldif:${encodeURIComponent(normalized)}` : null;
|
||||
}
|
||||
|
||||
@@ -48,8 +48,8 @@ const ROLE_NAMES: Record<string, () => string> = {
|
||||
/** The folder's name as the reader should see it. */
|
||||
export function mailboxDisplayName(mailbox: { name: string; role?: string | null } | null | undefined): string {
|
||||
if (!mailbox) return "";
|
||||
const localised = mailbox.role ? ROLE_NAMES[mailbox.role] : undefined;
|
||||
return localised ? localised() : mailbox.name;
|
||||
const localized = mailbox.role ? ROLE_NAMES[mailbox.role] : undefined;
|
||||
return localized ? localized() : mailbox.name;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +61,7 @@ export function mailboxDisplayName(mailbox: { name: string; role?: string | null
|
||||
* they were only looking at. Renaming a role folder is refused anyway, but
|
||||
* relying on that would be relying on a rule enforced somewhere else.
|
||||
*/
|
||||
export function isLocalisedName(mailbox: { role?: string | null } | null | undefined): boolean {
|
||||
export function isLocalizedName(mailbox: { role?: string | null } | null | undefined): boolean {
|
||||
return Boolean(mailbox?.role && mailbox.role in ROLE_NAMES);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { marked } from "marked";
|
||||
* something is DOMPurify, which the app already carries for mail.
|
||||
*
|
||||
* Rendered inline rather than in a shadow root the way mail bodies are: this
|
||||
* output is ours, sanitised and styled by `.md-body`, where an email arrives
|
||||
* output is ours, sanitized and styled by `.md-body`, where an email arrives
|
||||
* with a design of its own that has to be quarantined from the app's.
|
||||
*/
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ export function cardFromLdif(rec: LdifRecord): Partial<ContactCard> | null {
|
||||
/*
|
||||
* The four custom fields have nowhere of their own to go: JSContact has no
|
||||
* equivalent, and the schema does not say what they hold -- they are whatever
|
||||
* their owner decided. Appending them to the note keeps them, labelled the
|
||||
* their owner decided. Appending them to the note keeps them, labeled the
|
||||
* way Thunderbird labels them, which is worth more than the tidiness of
|
||||
* dropping something somebody chose to write down.
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* The theme used to be one enum — `system | light | dark | ihasmail` — where
|
||||
* "ihasmail" carried a whole palette and implied dark. That works for exactly
|
||||
* one palette. With several, the two questions come apart: **which palette**
|
||||
* (the colours) and **which mode** (light or dark), and they are chosen
|
||||
* (the colors) and **which mode** (light or dark), and they are chosen
|
||||
* separately.
|
||||
*
|
||||
* Every palette here is taken from the project that publishes it, all MIT, and
|
||||
@@ -32,7 +32,7 @@ export interface PaletteMeta {
|
||||
* the rest -- and are rendered translate="no" so a page translator leaves
|
||||
* them alone. "Classic" is not a name, it is an adjective describing the
|
||||
* theme, and a German reader should see "Klassisch". Reported by a native
|
||||
* speaker reviewing the German catalogue (#247).
|
||||
* speaker reviewing the German catalog (#247).
|
||||
*/
|
||||
translatable?: boolean;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export const PALETTES: PaletteMeta[] = [
|
||||
{ id: "ayu", name: "Ayu", credit: "Ayu by Konstantin Pschera (MIT)" },
|
||||
{ id: "kanagawa", name: "Kanagawa", credit: "Kanagawa by rebelot (MIT) — dark is Wave, light is Lotus" },
|
||||
{ id: "everforest", name: "Everforest", credit: "Everforest by sainnhe (MIT)" },
|
||||
// Named for the design system rather than for GitHub: the colours are MIT,
|
||||
// Named for the design system rather than for GitHub: the colors are MIT,
|
||||
// the name and the logo are trademarks, and nothing here is endorsed.
|
||||
{ id: "primer", name: "Primer", credit: "GitHub's Primer primitives (MIT); not affiliated with or endorsed by GitHub" },
|
||||
];
|
||||
@@ -67,7 +67,7 @@ export function paletteMeta(id: PaletteId | string | null | undefined): PaletteM
|
||||
* against the OS. That was not true while `ihasmail` was dark-only: the mode
|
||||
* then had to be overridden by the palette, and the toggle had to remember
|
||||
* which palette it had set aside on the way to light. Giving that palette a
|
||||
* light half removed the override, the memory and the greyed-out control in
|
||||
* light half removed the override, the memory and the grayed-out control in
|
||||
* one go.
|
||||
*/
|
||||
export function effectiveMode(mode: Mode, prefersDark: boolean): ResolvedMode {
|
||||
|
||||
@@ -50,7 +50,7 @@ export function splitLabel(label: string): { categoryKey: string; action: string
|
||||
|
||||
const loaded = new Map<string, Promise<PermissionCatalog | null>>();
|
||||
|
||||
/** The catalogue for a language, or null for English and for a language without a file. */
|
||||
/** The catalog for a language, or null for English and for a language without a file. */
|
||||
export function loadPermissionCatalog(tag: string = currentLanguage()): Promise<PermissionCatalog | null> {
|
||||
if (tag === DEFAULT_UI_LANGUAGE) return Promise.resolve(null);
|
||||
let pending = loaded.get(tag);
|
||||
|
||||
@@ -7,8 +7,8 @@ import { plural, t } from "@/lib/i18n";
|
||||
*
|
||||
* This was a table of English strings carrying `label: "Monday"` and
|
||||
* `short: "M"`, rendered straight into the picker. The long names could have
|
||||
* become catalogue entries; the short ones could not, because "T" is both
|
||||
* Tuesday and Thursday and "S" is both Saturday and Sunday, and a catalogue
|
||||
* become catalog entries; the short ones could not, because "T" is both
|
||||
* Tuesday and Thursday and "S" is both Saturday and Sunday, and a catalog
|
||||
* cannot hold two translations under one key. Intl knows all of them.
|
||||
*/
|
||||
export const WEEKDAY_KEYS: Array<JSCalendarNDay["day"]> = ["mo", "tu", "we", "th", "fr", "sa", "su"];
|
||||
@@ -61,7 +61,7 @@ export function ruleFromPreset(preset: RecurrencePreset, start: Date): JSCalenda
|
||||
*
|
||||
* Built as whole sentences with placeholders rather than by concatenation.
|
||||
* The old version appended fragments -- `base += " on " + names` -- which is
|
||||
* untranslatable however complete the catalogue is: German puts the weekday
|
||||
* untranslatable however complete the catalog is: German puts the weekday
|
||||
* list somewhere else in the clause, and a translator handed " on " alone
|
||||
* cannot move it. Every branch below is one key a translator can rewrite in
|
||||
* full, including the word order.
|
||||
@@ -144,7 +144,7 @@ export function describeRule(rule: JSCalendarRecurrenceRule | undefined): string
|
||||
* "first", "second", "last" -- words, not "1st".
|
||||
*
|
||||
* The suffix table this replaced ("st", "nd", "rd", "th") is English spelling
|
||||
* rules in code: German writes "1.", Japanese "第1", and no catalogue can
|
||||
* rules in code: German writes "1.", Japanese "第1", and no catalog can
|
||||
* reach a suffix chosen by arithmetic. JSCalendar's nthOfPeriod is 1-5 or -1
|
||||
* in practice, so five words and "last" cover it; anything else falls back to
|
||||
* the bare number, which is wrong in no language.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Settings that follow the account rather than the browser.
|
||||
*
|
||||
* Everything used to live in localStorage, which meant no preference travelled
|
||||
* Everything used to live in localStorage, which meant no preference traveled
|
||||
* between devices — most painfully the default identity, where the fallback is
|
||||
* whichever address sorts first, so a forgotten setting sends mail from an
|
||||
* address the recipient may not know (issue #54).
|
||||
@@ -152,7 +152,7 @@ export async function flushSettingsPush(): Promise<void> {
|
||||
if (!pending || !armed) return;
|
||||
const body = pending;
|
||||
pending = null;
|
||||
// Serialise: two overlapping writes could land in either order.
|
||||
// Serialize: two overlapping writes could land in either order.
|
||||
inFlight = (inFlight ?? Promise.resolve()).then(() => writeSettings(body)).catch(() => undefined);
|
||||
await inFlight;
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export async function collectShare(): Promise<SharedContent | null> {
|
||||
* The bytes, rather than the Blob holding them.
|
||||
*
|
||||
* `new File([blob], …)` is correct and works in a browser, but a Blob
|
||||
* only counts as a part where the File constructor recognises it as one
|
||||
* only counts as a part where the File constructor recognizes it as one
|
||||
* -- and where it does not, it is stringified instead, producing a file
|
||||
* containing the thirteen characters "[object Blob]" and no error
|
||||
* anywhere. That is exactly what CI caught on Node 22 while it passed
|
||||
|
||||
@@ -183,7 +183,7 @@ export function rulesToSieve(rules: SieveRule[]): string {
|
||||
*
|
||||
* Saving replaces the whole script with a fresh serialization of the rules read
|
||||
* out of it, so whatever was not read is deleted. `sieveToRules` cannot raise
|
||||
* the alarm by itself: it skips what it does not recognise, so a script cut off
|
||||
* the alarm by itself: it skips what it does not recognize, so a script cut off
|
||||
* partway through parses cleanly into a shorter list and looks exactly like one
|
||||
* that genuinely has fewer rules. That is the shape of the loss in #76 -- a
|
||||
* truncated download, a plausible parse, and a save that wrote the short
|
||||
@@ -326,10 +326,10 @@ export function reorderRules(rules: SieveRule[], fromId: string, toId: string, b
|
||||
*
|
||||
* Rebuilt as whole sentences with placeholders. The old version concatenated
|
||||
* fragments -- a header name, an operator, a quoted value, joined by " and "
|
||||
* -- which no catalogue could fix: German puts the verb last, Japanese does
|
||||
* -- which no catalog could fix: German puts the verb last, Japanese does
|
||||
* not separate list items with a word at all, and a translator handed " and "
|
||||
* on its own cannot move anything. Reported by a native speaker reviewing the
|
||||
* German catalogue (#247).
|
||||
* German catalog (#247).
|
||||
*
|
||||
* Intl.ListFormat does the joining, so "A, B and C" becomes "A, B und C" and,
|
||||
* for an anyof rule, the disjunction the language actually uses.
|
||||
|
||||
@@ -75,7 +75,7 @@ describe("what the signature is allowed to mean", () => {
|
||||
expect(shouldRemember(report)).toBe(true);
|
||||
});
|
||||
|
||||
it("the same certificate again is recognised", async () => {
|
||||
it("the same certificate again is recognized", async () => {
|
||||
const crypto = await verifyMessage(fixture("signed-rsa.eml"));
|
||||
if (crypto.kind !== "intact") throw new Error("fixture should verify");
|
||||
const known: KnownSigner = { fingerprint: crypto.cert.fingerprint, name: "Ada Lovelace", firstSeen: "2026-09-01T00:00:00Z" };
|
||||
@@ -129,7 +129,7 @@ describe("matching a certificate to an address", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("canonicalisation", () => {
|
||||
describe("canonicalization", () => {
|
||||
it("turns a lone LF into CRLF and leaves an existing CRLF alone", () => {
|
||||
const mixed = new TextEncoder().encode("a\nb\r\nc\n");
|
||||
expect(new TextDecoder().decode(toCanonicalCrlf(mixed))).toBe("a\r\nb\r\nc\r\n");
|
||||
@@ -141,7 +141,7 @@ describe("canonicalisation", () => {
|
||||
});
|
||||
|
||||
/*
|
||||
* The reason canonicalisation is applied at all: a store that hands back a
|
||||
* The reason canonicalization is applied at all: a store that hands back a
|
||||
* message with bare LFs would otherwise fail every signature it holds, and
|
||||
* the message would look identical on screen while doing it.
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* This works on bytes, not on a string, and that is the whole point. A
|
||||
* signature is over an octet sequence: decode it to text, re-encode it, or let
|
||||
* anything normalise a line ending on the way past, and the digest changes
|
||||
* anything normalize a line ending on the way past, and the digest changes
|
||||
* while the message still looks identical on screen. Every part here keeps a
|
||||
* subarray of the original buffer rather than a rebuilt copy.
|
||||
*
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* without a certificate authority anywhere in the picture.
|
||||
*
|
||||
* So nothing here ever renders the bare word "verified". The caller is given
|
||||
* the crypto result and the trust judgement separately, and has to say both.
|
||||
* the crypto result and the trust judgment separately, and has to say both.
|
||||
*/
|
||||
import { parseSignedData, type SignerInfo } from "./cms";
|
||||
import { decodeTransfer, findPart, parseMime, toCanonicalCrlf, type MimePart } from "./mime";
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
export interface SpamRule {
|
||||
/** The rule's own name, as the filter wrote it. */
|
||||
name: string;
|
||||
/** What it contributed. Negative moves the message towards clean. */
|
||||
/** What it contributed. Negative moves the message toward clean. */
|
||||
score: number;
|
||||
/** Rspamd's bracketed note, where there is one. */
|
||||
detail?: string;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* address book on it.
|
||||
*
|
||||
* Reads are gated as well as writes. A machine that was trusted once still has
|
||||
* the residue, and honouring it would let a previous session's data surface in
|
||||
* the residue, and honoring it would let a previous session's data surface in
|
||||
* a later untrusted one.
|
||||
*/
|
||||
const PREFIX = "ihasmail:";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* What the service worker cannot work out for itself.
|
||||
*
|
||||
* The worker can act on mail — see the note on `jmap()` in sw.js — but it
|
||||
* cannot read a catalogue or a store. It is plain JavaScript copied into the
|
||||
* cannot read a catalog or a store. It is plain JavaScript copied into the
|
||||
* build, outside the bundle, with no i18n and no idea which mailbox is the
|
||||
* archive. Both of those are things a tab knows and can simply write down.
|
||||
*
|
||||
|
||||
@@ -35,7 +35,7 @@ export interface SwipeDescriptor {
|
||||
action: Exclude<SwipeAction, "none">;
|
||||
label: string;
|
||||
icon: SwipeIcon;
|
||||
/** Which colour the strip behind the row takes. */
|
||||
/** Which color the strip behind the row takes. */
|
||||
tone: "danger" | "warn" | "accent" | "neutral";
|
||||
/**
|
||||
* Whether firing it takes the row out of the list. Those slide the rest of
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* mistake to undo.
|
||||
* - **A name that is not a placeholder is left alone too.** Templates are
|
||||
* written by hand and `{{` is not reserved anywhere else, but a body that
|
||||
* silently ate an unrecognised token would be worse than one that shows it.
|
||||
* silently ate an unrecognized token would be worse than one that shows it.
|
||||
*
|
||||
* Dates and times go through `datetime.ts` rather than `toLocaleDateString`,
|
||||
* so a template follows the same date order and clock the rest of the app was
|
||||
|
||||
@@ -41,7 +41,7 @@ export type Axis = "x" | "y" | null;
|
||||
/**
|
||||
* Which way a drag has committed, once it has moved far enough to tell.
|
||||
*
|
||||
* Deliberately biased towards the vertical. Scrolling is what a finger on a
|
||||
* Deliberately biased toward the vertical. Scrolling is what a finger on a
|
||||
* message list is doing almost every time, and a scroll misread as a swipe
|
||||
* grabs the list out from under the reader, while a swipe misread as a scroll
|
||||
* costs them a second attempt. So `x` has to win clearly -- a drag that is
|
||||
@@ -67,7 +67,7 @@ export function swipeThreshold(width: number): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* How far the row actually moves for a finger that has travelled `dx`.
|
||||
* How far the row actually moves for a finger that has traveled `dx`.
|
||||
*
|
||||
* One-to-one until the action would fire, and increasingly reluctant after
|
||||
* that. The resistance is the only thing that tells a thumb, without the
|
||||
@@ -113,7 +113,7 @@ export function pullDistance(dy: number): number {
|
||||
* without this the only feedback arrives after the row has already gone.
|
||||
*
|
||||
* iOS supports none of this and never has, so this is silently nothing there
|
||||
* rather than something to apologise for. Wrapped because a vibration inside
|
||||
* rather than something to apologize for. Wrapped because a vibration inside
|
||||
* a cross-origin iframe throws rather than returning false.
|
||||
*/
|
||||
export function haptic(pattern: number | number[] = 8): void {
|
||||
@@ -197,7 +197,7 @@ export function useTouchRow({ enabled, onLongPress, canSwipe, onSwipeMove, onSwi
|
||||
if (onLongPress) {
|
||||
timer.current = window.setTimeout(() => {
|
||||
timer.current = null;
|
||||
// Still here, still not moving: nothing has cancelled us.
|
||||
// Still here, still not moving: nothing has canceled us.
|
||||
if (!start.current || axis.current) return;
|
||||
swallowClick.current = true;
|
||||
onLongPress(start.current.target);
|
||||
@@ -251,7 +251,7 @@ export function useTouchRow({ enabled, onLongPress, canSwipe, onSwipeMove, onSwi
|
||||
/*
|
||||
* Crossing back the other way mid-gesture. The direction is re-read
|
||||
* rather than held from the lock, so a reader who overshoots, thinks
|
||||
* better of it and drags back past centre gets the other action offered
|
||||
* better of it and drags back past center gets the other action offered
|
||||
* instead of the row refusing to move.
|
||||
*/
|
||||
if (d !== dir.current) {
|
||||
@@ -322,7 +322,7 @@ export function useTouchRow({ enabled, onLongPress, canSwipe, onSwipeMove, onSwi
|
||||
*
|
||||
* Native listeners rather than React props because the move handler has to be
|
||||
* able to call `preventDefault`, and React attaches its own passively. Bound
|
||||
* to the scroll container itself so that everything inside it -- a virtualised
|
||||
* to the scroll container itself so that everything inside it -- a virtualized
|
||||
* list included -- comes down with the pull without knowing about it.
|
||||
*/
|
||||
export function usePullToRefresh(
|
||||
@@ -542,7 +542,7 @@ export function swipeNavDirection(dx: number, width: number): -1 | 0 | 1 {
|
||||
* moment it can be decided cleanly.
|
||||
* - **It does not start on the toolbar.** Buttons live there.
|
||||
*
|
||||
* The axis lock is the shared one, so it keeps the same bias towards the
|
||||
* The axis lock is the shared one, so it keeps the same bias toward the
|
||||
* vertical: the day grid scrolls through the hours, and a scroll misread as a
|
||||
* swipe throws the reader into another day.
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,7 @@ import { plural, setCatalog } from "@/lib/i18n";
|
||||
import { catalog as ru } from "@/locales/ru";
|
||||
|
||||
/**
|
||||
* Russian is the first shipped catalogue that needs `few` and `many`, so this
|
||||
* Russian is the first shipped catalog that needs `few` and `many`, so this
|
||||
* checks the real entries rather than a fixture. English would have rendered
|
||||
* "5 письмо" for all of these, which is the kind of wrong that makes a
|
||||
* translation read as machine output however good the vocabulary is.
|
||||
@@ -26,7 +26,7 @@ describe("Russian plurals", () => {
|
||||
});
|
||||
|
||||
it("carries every form for each counted string it ships", () => {
|
||||
// A catalogue missing `few` silently falls back to `other`, which is
|
||||
// A catalog missing `few` silently falls back to `other`, which is
|
||||
// grammatical often enough to go unnoticed and wrong the rest of the time.
|
||||
for (const [key, forms] of Object.entries(ru.plurals)) {
|
||||
for (const cat of ["one", "few", "many", "other"] as const) {
|
||||
|
||||
+21
-22
@@ -124,7 +124,7 @@ export const catalog: Catalog = {
|
||||
"This account signs in through an external directory, so its password can't be set here.": "Dieses Konto meldet sich über ein externes Verzeichnis an, daher kann sein Passwort hier nicht festgelegt werden.",
|
||||
"The server's license allows no more accounts.": "Die Lizenz des Servers erlaubt keine weiteren Konten.",
|
||||
"That domain name is already in use on this server, as a domain or another domain's other name.": "Dieser Domainname wird auf diesem Server bereits verwendet – als Domain oder als weiterer Name einer anderen Domain.",
|
||||
"Your organisation has reached the number of domains it is allowed.": "Ihre Organisation hat die Anzahl der erlaubten Domains erreicht.",
|
||||
"Your organization has reached the number of domains it is allowed.": "Ihre Organisation hat die Anzahl der erlaubten Domains erreicht.",
|
||||
"That is more than the mail server accepts in one change.": "Das ist mehr, als der Mailserver in einer Änderung annimmt.",
|
||||
"The mail server rejected one of the values. Check what you entered and try again.": "Der Mailserver hat einen der Werte abgelehnt. Prüfen Sie Ihre Eingaben und versuchen Sie es erneut.",
|
||||
"The mail server refused the change ({code}).": "Der Mailserver hat die Änderung abgelehnt ({code}).",
|
||||
@@ -179,7 +179,7 @@ export const catalog: Catalog = {
|
||||
"Search groups": "Gruppen durchsuchen",
|
||||
"No groups match": "Keine passenden Gruppen",
|
||||
"No groups yet": "Noch keine Gruppen",
|
||||
"Your organisation has reached the number of groups it is allowed.": "Ihre Organisation hat die erlaubte Anzahl an Gruppen erreicht.",
|
||||
"Your organization has reached the number of groups it is allowed.": "Ihre Organisation hat die erlaubte Anzahl an Gruppen erreicht.",
|
||||
"This group no longer exists. Someone may have deleted it.": "Diese Gruppe existiert nicht mehr. Jemand hat sie möglicherweise gelöscht.",
|
||||
"The server did not say whether the group was created.": "Der Server hat nicht mitgeteilt, ob die Gruppe angelegt wurde.",
|
||||
"Mailing lists": "Mailinglisten",
|
||||
@@ -204,7 +204,7 @@ export const catalog: Catalog = {
|
||||
"Search mailing lists": "Mailinglisten durchsuchen",
|
||||
"No mailing lists match": "Keine passenden Mailinglisten",
|
||||
"No mailing lists yet": "Noch keine Mailinglisten",
|
||||
"Your organisation has reached the number of mailing lists it is allowed.": "Ihre Organisation hat die erlaubte Anzahl an Mailinglisten erreicht.",
|
||||
"Your organization has reached the number of mailing lists it is allowed.": "Ihre Organisation hat die erlaubte Anzahl an Mailinglisten erreicht.",
|
||||
"This mailing list no longer exists. Someone may have deleted it.": "Diese Mailingliste existiert nicht mehr. Jemand hat sie möglicherweise gelöscht.",
|
||||
"The server did not say whether the list was created.": "Der Server hat nicht mitgeteilt, ob die Liste angelegt wurde.",
|
||||
"Roles": "Rollen",
|
||||
@@ -255,13 +255,13 @@ export const catalog: Catalog = {
|
||||
"Open {name}": "{name} öffnen",
|
||||
"Default for {kinds}": "Standard bei {kinds}",
|
||||
"You can't give a role permissions your own role doesn't have.": "Sie können einer Rolle keine Berechtigungen geben, die Ihre eigene Rolle nicht hat.",
|
||||
"Your organisation has reached the number of roles it is allowed.": "Ihre Organisation hat die erlaubte Anzahl an Rollen erreicht.",
|
||||
"Your organization has reached the number of roles it is allowed.": "Ihre Organisation hat die erlaubte Anzahl an Rollen erreicht.",
|
||||
"This role no longer exists. Someone may have deleted it.": "Diese Rolle existiert nicht mehr. Jemand hat sie möglicherweise gelöscht.",
|
||||
"the default roles": "den Standardrollen",
|
||||
"The server did not say whether the role was created.": "Der Server hat nicht mitgeteilt, ob die Rolle angelegt wurde.",
|
||||
"No tenant": "Kein Mandant",
|
||||
"You can't move your own account into a tenant.": "Sie können Ihr eigenes Konto nicht in einen Mandanten verschieben.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.": "Ein Konto kann in dem Mandanten sein, in dem seine Domain ist. In einem Mandanten ist es durch dessen Rolle begrenzt und zählt zu dessen Limits, und Administrator bedeutet Administrator dieses Mandanten.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts toward its limits, and Administrator means administrator of that tenant.": "Ein Konto kann in dem Mandanten sein, in dem seine Domain ist. In einem Mandanten ist es durch dessen Rolle begrenzt und zählt zu dessen Limits, und Administrator bedeutet Administrator dieses Mandanten.",
|
||||
"Tenants": "Mandanten",
|
||||
"Storage in GB": "Speicher in GB",
|
||||
"Default tenant roles": "Standardrollen für Mandanten",
|
||||
@@ -289,7 +289,7 @@ export const catalog: Catalog = {
|
||||
"Delete tenant…": "Mandant löschen…",
|
||||
"Still holds {things}. Move them out first.": "Enthält noch {things}. Verschieben Sie diese zuerst.",
|
||||
"Delete tenant": "Mandant löschen",
|
||||
"Separate organisations on one server, each with its own people, domains and limits.": "Getrennte Organisationen auf einem Server, jede mit eigenen Personen, Domains und Limits.",
|
||||
"Separate organizations on one server, each with its own people, domains and limits.": "Getrennte Organisationen auf einem Server, jede mit eigenen Personen, Domains und Limits.",
|
||||
"Tenants are a Stalwart Enterprise feature.": "Mandanten sind eine Funktion von Stalwart Enterprise.",
|
||||
"Search tenants": "Mandanten durchsuchen",
|
||||
"No tenants match": "Keine passenden Mandanten",
|
||||
@@ -355,7 +355,7 @@ export const catalog: Catalog = {
|
||||
"The mail server refused this. Your role may not allow it.": "Der Mailserver hat dies abgelehnt. Ihre Rolle erlaubt es möglicherweise nicht.",
|
||||
"That address is already in use on this server, as an account, a list or an alias.": "Diese Adresse wird auf diesem Server bereits verwendet – als Konto, Liste oder Alias.",
|
||||
"One of the chosen domain, role or group can't be used for this account.": "Die gewählte Domain, Rolle oder Gruppe kann für dieses Konto nicht verwendet werden.",
|
||||
"Your organisation has reached the number of accounts it is allowed.": "Ihre Organisation hat die Anzahl der erlaubten Konten erreicht.",
|
||||
"Your organization has reached the number of accounts it is allowed.": "Ihre Organisation hat die Anzahl der erlaubten Konten erreicht.",
|
||||
"Something still depends on this, so the server kept it.": "Etwas hängt noch davon ab, daher hat der Server es behalten.",
|
||||
"This account no longer exists. Someone may have deleted it.": "Dieses Konto existiert nicht mehr. Möglicherweise hat es jemand gelöscht.",
|
||||
"The password was not accepted: {reason}": "Das Passwort wurde nicht akzeptiert: {reason}",
|
||||
@@ -432,7 +432,7 @@ export const catalog: Catalog = {
|
||||
"Turn off": "Deaktivieren",
|
||||
"Clear": "Leeren",
|
||||
"Clear selection": "Auswahl aufheben",
|
||||
"Clear custom colour": "Eigene Farbe entfernen",
|
||||
"Clear custom color": "Eigene Farbe entfernen",
|
||||
"Select": "Auswählen",
|
||||
"Select all": "Alle auswählen",
|
||||
"Unsubscribe": "Abbestellen",
|
||||
@@ -625,7 +625,7 @@ export const catalog: Catalog = {
|
||||
"Maybe": "Vielleicht",
|
||||
"Confirmed": "Zugesagt",
|
||||
"Tentative": "Vorläufig",
|
||||
"Cancelled": "Abgesagt",
|
||||
"Canceled": "Abgesagt",
|
||||
"organizer": "Organisator",
|
||||
"Organizer: {name}": "Organisator: {name}",
|
||||
"Free": "Frei",
|
||||
@@ -640,7 +640,7 @@ export const catalog: Catalog = {
|
||||
"Working hours": "Arbeitszeiten",
|
||||
"Working hours start": "Arbeitszeit beginnt",
|
||||
"Working hours end": "Arbeitszeit endet",
|
||||
"Colour categories": "Farbkategorien",
|
||||
"Color categories": "Farbkategorien",
|
||||
"Category": "Kategorie",
|
||||
"No category": "Keine Kategorie",
|
||||
"New category": "Neue Kategorie",
|
||||
@@ -648,7 +648,7 @@ export const catalog: Catalog = {
|
||||
"Manage categories…": "Kategorien verwalten…",
|
||||
"Use category color": "Kategoriefarbe verwenden",
|
||||
"Use calendar color": "Kalenderfarbe verwenden",
|
||||
"Use the default colour": "Standardfarbe verwenden",
|
||||
"Use the default color": "Standardfarbe verwenden",
|
||||
"+{n} more": "+{n} weitere",
|
||||
|
||||
"Contacts": "Kontakte",
|
||||
@@ -803,7 +803,6 @@ export const catalog: Catalog = {
|
||||
"Theme": "Design",
|
||||
"Accent color": "Akzentfarbe",
|
||||
"Color": "Farbe",
|
||||
"Colour": "Farbe",
|
||||
"Text color": "Textfarbe",
|
||||
"Density & text": "Dichte & Text",
|
||||
"Display density": "Anzeigedichte",
|
||||
@@ -821,7 +820,7 @@ export const catalog: Catalog = {
|
||||
"Collapse sidebar to icons": "Seitenleiste auf Symbole verkleinern",
|
||||
"Apply the theme to messages too": "Design auch auf Nachrichten anwenden",
|
||||
"Apply it even to mail that styles itself": "Auch auf Mails anwenden, die sich selbst gestalten",
|
||||
"Most marketing and receipt mail sets a colour somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colours: backgrounds they laid the message on are dropped, while buttons and coloured banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Fast jede Werbe- oder Beleg-Mail setzt irgendwo eine Farbe, deshalb lässt die Einstellung darüber nahezu alle davon auf einer weißen Karte. Mit dieser Option wird das Design über die Farben des Absenders gelegt: Hintergründe, auf denen die Nachricht liegt, entfallen, während Schaltflächen und farbige Banner erhalten bleiben, damit ihr Text lesbar bleibt. Manche Mail übersteht das nicht unbeschadet – deshalb ist es eine eigene Einstellung.",
|
||||
"Most marketing and receipt mail sets a color somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colors: backgrounds they laid the message on are dropped, while buttons and colored banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Fast jede Werbe- oder Beleg-Mail setzt irgendwo eine Farbe, deshalb lässt die Einstellung darüber nahezu alle davon auf einer weißen Karte. Mit dieser Option wird das Design über die Farben des Absenders gelegt: Hintergründe, auf denen die Nachricht liegt, entfallen, während Schaltflächen und farbige Banner erhalten bleiben, damit ihr Text lesbar bleibt. Manche Mail übersteht das nicht unbeschadet – deshalb ist es eine eigene Einstellung.",
|
||||
"Swiping": "Wischgesten",
|
||||
"Swipe left": "Nach links wischen",
|
||||
"Swipe right": "Nach rechts wischen",
|
||||
@@ -1067,7 +1066,7 @@ export const catalog: Catalog = {
|
||||
"Each identity is a sender address with its own name, Reply-To and signature. The default identity is preselected when you compose; set a Reply-To when replies should go somewhere other than the From address.": "Jede Identität ist eine Absenderadresse mit eigenem Namen, eigener Antwortadresse und eigener Signatur. Die Standardidentität ist beim Verfassen vorausgewählt; legen Sie eine Antwortadresse fest, wenn Antworten woanders hingehen sollen als an die Absenderadresse.",
|
||||
"This signature is larger than the server's {limit}-byte limit. ihasmail will keep the full version in your Files and store a short text fallback on the server — other mail clients will see the plain-text version.": "Diese Signatur überschreitet das Limit des Servers von {limit} Byte. ihasmail behält die vollständige Fassung in Ihren Dateien und speichert eine kurze Textfassung auf dem Server — andere E-Mail-Programme sehen die Nur-Text-Fassung.",
|
||||
"Outlook-style categories you can assign to events from the right-click menu or the event editor. The category name is stored on the event, so it syncs to other clients.": "Kategorien im Outlook-Stil, die Sie Terminen über das Rechtsklick-Menü oder den Termin-Editor zuweisen können. Der Kategoriename wird im Termin gespeichert und daher mit anderen Clients synchronisiert.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colours of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "Nur-Text-Nachrichten folgen dem Design bereits. Ist dies aktiviert, gilt das auch für HTML-Nachrichten ohne eigene Farben, statt sie auf einer weißen Fläche darzustellen. Nachrichten mit eigener Gestaltung bleiben genau so, wie der Absender sie entworfen hat.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colors of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "Nur-Text-Nachrichten folgen dem Design bereits. Ist dies aktiviert, gilt das auch für HTML-Nachrichten ohne eigene Farben, statt sie auf einer weißen Fläche darzustellen. Nachrichten mit eigener Gestaltung bleiben genau so, wie der Absender sie entworfen hat.",
|
||||
"This is separate from {setting} in General, which decides how dates, times and numbers are written. You can read an English interface with German dates, or the other way round.": "Das ist unabhängig von {setting} unter „Allgemein“, wo festgelegt wird, wie Datum, Uhrzeit und Zahlen geschrieben werden. Sie können eine englische Oberfläche mit deutschen Datumsangaben lesen — oder umgekehrt.",
|
||||
"On a touchscreen, drag a message sideways to act on it. Each direction can do one thing, or nothing. These follow your account, so a phone and a tablet agree; a mouse ignores them and keeps dragging messages into folders instead.": "Auf einem Touchscreen ziehen Sie eine Nachricht zur Seite, um sie zu bearbeiten. Jede Richtung kann eine Aktion ausführen — oder keine. Die Einstellung folgt Ihrem Konto, sodass Telefon und Tablet übereinstimmen; mit der Maus wird sie ignoriert und Nachrichten werden weiterhin in Ordner gezogen.",
|
||||
"This screen has no touchscreen, so nothing here changes what it does. Your phone or tablet will pick these up.": "Dieser Bildschirm hat keinen Touchscreen, hier ändert sich also nichts. Ihr Telefon oder Tablet übernimmt diese Einstellungen.",
|
||||
@@ -1217,12 +1216,12 @@ export const catalog: Catalog = {
|
||||
"Applying filter to existing messages…": "Filter wird auf vorhandene Nachrichten angewendet…",
|
||||
"Attachments are still uploading": "Anhänge werden noch hochgeladen",
|
||||
"Calendar saved": "Kalender gespeichert",
|
||||
"Categorised as {name}": "Kategorie „{name}“ zugewiesen",
|
||||
"Categorized as {name}": "Kategorie „{name}“ zugewiesen",
|
||||
"Category cleared": "Kategorie entfernt",
|
||||
"Change this event?": "Diesen Termin ändern?",
|
||||
"Choose a calendar": "Wählen Sie einen Kalender",
|
||||
"Choose an address book": "Wählen Sie ein Adressbuch",
|
||||
"Colour updated": "Farbe geändert",
|
||||
"Color updated": "Farbe geändert",
|
||||
"Contact created": "Kontakt erstellt",
|
||||
"Contact deleted": "Kontakt gelöscht",
|
||||
"Contact saved": "Kontakt gespeichert",
|
||||
@@ -1246,7 +1245,7 @@ export const catalog: Catalog = {
|
||||
"Could not update: {error}": "Aktualisierung fehlgeschlagen: {error}",
|
||||
"Create": "Erstellen",
|
||||
"Create an address book first": "Legen Sie zuerst ein Adressbuch an",
|
||||
"Custom colour removed": "Eigene Farbe entfernt",
|
||||
"Custom color removed": "Eigene Farbe entfernt",
|
||||
"Deactivate": "Deaktivieren",
|
||||
"Delete failed: {error}": "Löschen fehlgeschlagen: {error}",
|
||||
"Delete forever": "Endgültig löschen",
|
||||
@@ -1333,7 +1332,7 @@ export const catalog: Catalog = {
|
||||
"Script saved": "Skript gespeichert",
|
||||
"Send (Ctrl+Enter)": "Senden (Strg+Enter)",
|
||||
"Send anyway": "Trotzdem senden",
|
||||
"Send cancelled — the message is back in Drafts": "Senden abgebrochen – die Nachricht liegt wieder in den Entwürfen",
|
||||
"Send canceled — the message is back in Drafts": "Senden abgebrochen – die Nachricht liegt wieder in den Entwürfen",
|
||||
"Send failed: {error}": "Senden fehlgeschlagen: {error}",
|
||||
"Send invites": "Einladungen senden",
|
||||
"Send scheduled for {when}": "Senden geplant für {when}",
|
||||
@@ -1482,7 +1481,7 @@ export const catalog: Catalog = {
|
||||
"Counts people rather than headers, so one address in To and nine in Cc is a message to ten. Catches a reply-all onto a long thread.": "Zählt Personen statt Kopfzeilen: eine Adresse in An und neun in Cc ergeben eine Nachricht an zehn. Erfasst ein Allen-Antworten auf einen langen Thread.",
|
||||
"Date received": "Empfangsdatum",
|
||||
"Date sent": "Sendedatum",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colours are derived, and every one is checked for contrast. The accent colour below still applies over any of them.": "Paletten, die nach einem anderen Projekt benannt sind, stammen von diesem Projekt und werden unter dessen eigener Lizenz verwendet; die Abstufungen zwischen den veröffentlichten Farben sind abgeleitet, und jede davon wird auf Kontrast geprüft. Die Akzentfarbe unten gilt weiterhin über jeder von ihnen.",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colors are derived, and every one is checked for contrast. The accent color below still applies over any of them.": "Paletten, die nach einem anderen Projekt benannt sind, stammen von diesem Projekt und werden unter dessen eigener Lizenz verwendet; die Abstufungen zwischen den veröffentlichten Farben sind abgeleitet, und jede davon wird auf Kontrast geprüft. Die Akzentfarbe unten gilt weiterhin über jeder von ihnen.",
|
||||
"Earlier": "Früher",
|
||||
"Every folder": "Jeder Ordner",
|
||||
"Everyone addressed will receive this.": "Alle Adressierten erhalten dies.",
|
||||
@@ -1491,7 +1490,7 @@ export const catalog: Catalog = {
|
||||
"Forward as attachment": "Als Anhang weiterleiten",
|
||||
"From the birthdays on your contacts. Nothing is stored.": "Aus den Geburtstagen Ihrer Kontakte. Es wird nichts gespeichert.",
|
||||
"Import iCAL file…": "iCAL-Datei importieren…",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colours and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Label sind IMAP-Keywords, die auf Ihren Nachrichten gespeichert werden, sodass jeder andere Client sie sieht. Namen, Farben und Verschachtelung gehören ihasmail selbst und folgen Ihrem Konto. Die Verschachtelung dient nur der Anzeige — sie schreibt im Postfach nichts um.",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colors and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Label sind IMAP-Keywords, die auf Ihren Nachrichten gespeichert werden, sodass jeder andere Client sie sieht. Namen, Farben und Verschachtelung gehören ihasmail selbst und folgen Ihrem Konto. Die Verschachtelung dient nur der Anzeige — sie schreibt im Postfach nichts um.",
|
||||
"Largest first": "Größte zuerst",
|
||||
"Later": "Später",
|
||||
"Light or dark": "Hell oder dunkel",
|
||||
@@ -1532,7 +1531,7 @@ export const catalog: Catalog = {
|
||||
"Save your changes?": "Änderungen speichern?",
|
||||
"Saved": "Gespeichert",
|
||||
"Select all {n} in {folder}": "Alle {n} in {folder} auswählen",
|
||||
"Send outside your organisation?": "Nach außerhalb Ihrer Organisation senden?",
|
||||
"Send outside your organization?": "Nach außerhalb Ihrer Organisation senden?",
|
||||
"Send to {count} people?": "An {count} Personen senden?",
|
||||
"Show birthdays from your contacts": "Geburtstage aus Ihren Kontakten anzeigen",
|
||||
"Show in the sidebar": "In der Seitenleiste anzeigen",
|
||||
@@ -1574,7 +1573,7 @@ export const catalog: Catalog = {
|
||||
"A to Z": "A bis Z",
|
||||
"It reads {shown} but goes to {actual}.": "Angezeigt wird {shown}, geöffnet wird aber {actual}.",
|
||||
"The full address is {href}.": "Die vollständige Adresse lautet {href}.",
|
||||
"This message came from {domain}, which is outside your organisation.": "Diese Nachricht kam von {domain} und damit von außerhalb Ihrer Organisation.",
|
||||
"This message came from {domain}, which is outside your organization.": "Diese Nachricht kam von {domain} und damit von außerhalb Ihrer Organisation.",
|
||||
"Unsaved changes": "Nicht gespeicherte Änderungen",
|
||||
"View as": "Anzeigen als",
|
||||
"Warnings": "Warnungen",
|
||||
|
||||
+21
-22
@@ -116,7 +116,7 @@ export const catalog: Catalog = {
|
||||
"This account signs in through an external directory, so its password can't be set here.": "Esta cuenta inicia sesión mediante un directorio externo, así que su contraseña no se puede establecer aquí.",
|
||||
"The server's license allows no more accounts.": "La licencia del servidor no permite más cuentas.",
|
||||
"That domain name is already in use on this server, as a domain or another domain's other name.": "Ese nombre de dominio ya está en uso en este servidor, como dominio o como otro nombre de otro dominio.",
|
||||
"Your organisation has reached the number of domains it is allowed.": "Su organización ha alcanzado el número de dominios permitido.",
|
||||
"Your organization has reached the number of domains it is allowed.": "Su organización ha alcanzado el número de dominios permitido.",
|
||||
"That is more than the mail server accepts in one change.": "Eso supera lo que el servidor de correo acepta en un solo cambio.",
|
||||
"The mail server rejected one of the values. Check what you entered and try again.": "El servidor de correo ha rechazado uno de los valores. Revise lo que ha escrito e inténtelo de nuevo.",
|
||||
"The mail server refused the change ({code}).": "El servidor de correo ha rechazado el cambio ({code}).",
|
||||
@@ -171,7 +171,7 @@ export const catalog: Catalog = {
|
||||
"Search groups": "Buscar grupos",
|
||||
"No groups match": "Ningún grupo coincide",
|
||||
"No groups yet": "Aún no hay grupos",
|
||||
"Your organisation has reached the number of groups it is allowed.": "Su organización ha alcanzado el número de grupos permitido.",
|
||||
"Your organization has reached the number of groups it is allowed.": "Su organización ha alcanzado el número de grupos permitido.",
|
||||
"This group no longer exists. Someone may have deleted it.": "Este grupo ya no existe. Puede que alguien lo haya eliminado.",
|
||||
"The server did not say whether the group was created.": "El servidor no indicó si el grupo se creó.",
|
||||
"Mailing lists": "Listas de correo",
|
||||
@@ -196,7 +196,7 @@ export const catalog: Catalog = {
|
||||
"Search mailing lists": "Buscar listas de correo",
|
||||
"No mailing lists match": "Ninguna lista de correo coincide",
|
||||
"No mailing lists yet": "Aún no hay listas de correo",
|
||||
"Your organisation has reached the number of mailing lists it is allowed.": "Su organización ha alcanzado el número de listas de correo permitido.",
|
||||
"Your organization has reached the number of mailing lists it is allowed.": "Su organización ha alcanzado el número de listas de correo permitido.",
|
||||
"This mailing list no longer exists. Someone may have deleted it.": "Esta lista de correo ya no existe. Puede que alguien la haya eliminado.",
|
||||
"The server did not say whether the list was created.": "El servidor no indicó si la lista se creó.",
|
||||
"Roles": "Roles",
|
||||
@@ -247,13 +247,13 @@ export const catalog: Catalog = {
|
||||
"Open {name}": "Abrir {name}",
|
||||
"Default for {kinds}": "Predeterminado para {kinds}",
|
||||
"You can't give a role permissions your own role doesn't have.": "No puede dar a un rol permisos que su propio rol no tiene.",
|
||||
"Your organisation has reached the number of roles it is allowed.": "Su organización ha alcanzado el número de roles permitido.",
|
||||
"Your organization has reached the number of roles it is allowed.": "Su organización ha alcanzado el número de roles permitido.",
|
||||
"This role no longer exists. Someone may have deleted it.": "Este rol ya no existe. Puede que alguien lo haya eliminado.",
|
||||
"the default roles": "los roles predeterminados",
|
||||
"The server did not say whether the role was created.": "El servidor no indicó si el rol se creó.",
|
||||
"No tenant": "Sin inquilino",
|
||||
"You can't move your own account into a tenant.": "No puede mover su propia cuenta a un inquilino.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.": "Una cuenta puede estar en el inquilino en el que está su dominio. En un inquilino está limitada por el rol del inquilino y cuenta para sus límites, y Administrador significa administrador de ese inquilino.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts toward its limits, and Administrator means administrator of that tenant.": "Una cuenta puede estar en el inquilino en el que está su dominio. En un inquilino está limitada por el rol del inquilino y cuenta para sus límites, y Administrador significa administrador de ese inquilino.",
|
||||
"Tenants": "Inquilinos",
|
||||
"Storage in GB": "Almacenamiento en GB",
|
||||
"Default tenant roles": "Roles de inquilino predeterminados",
|
||||
@@ -281,7 +281,7 @@ export const catalog: Catalog = {
|
||||
"Delete tenant…": "Eliminar inquilino…",
|
||||
"Still holds {things}. Move them out first.": "Aún tiene {things}. Muévalos primero.",
|
||||
"Delete tenant": "Eliminar inquilino",
|
||||
"Separate organisations on one server, each with its own people, domains and limits.": "Organizaciones separadas en un mismo servidor, cada una con sus propias personas, dominios y límites.",
|
||||
"Separate organizations on one server, each with its own people, domains and limits.": "Organizaciones separadas en un mismo servidor, cada una con sus propias personas, dominios y límites.",
|
||||
"Tenants are a Stalwart Enterprise feature.": "Los inquilinos son una función de Stalwart Enterprise.",
|
||||
"Search tenants": "Buscar inquilinos",
|
||||
"No tenants match": "Ningún inquilino coincide",
|
||||
@@ -347,7 +347,7 @@ export const catalog: Catalog = {
|
||||
"The mail server refused this. Your role may not allow it.": "El servidor de correo lo ha rechazado. Es posible que su rol no lo permita.",
|
||||
"That address is already in use on this server, as an account, a list or an alias.": "Esa dirección ya está en uso en este servidor, como cuenta, lista o alias.",
|
||||
"One of the chosen domain, role or group can't be used for this account.": "El dominio, el rol o el grupo elegido no se puede usar para esta cuenta.",
|
||||
"Your organisation has reached the number of accounts it is allowed.": "Su organización ha alcanzado el número de cuentas permitido.",
|
||||
"Your organization has reached the number of accounts it is allowed.": "Su organización ha alcanzado el número de cuentas permitido.",
|
||||
"Something still depends on this, so the server kept it.": "Algo todavía depende de esto, así que el servidor lo ha conservado.",
|
||||
"This account no longer exists. Someone may have deleted it.": "Esta cuenta ya no existe. Puede que alguien la haya eliminado.",
|
||||
"The password was not accepted: {reason}": "La contraseña no se ha aceptado: {reason}",
|
||||
@@ -424,7 +424,7 @@ export const catalog: Catalog = {
|
||||
"Turn off": "Desactivar",
|
||||
"Clear": "Vaciar",
|
||||
"Clear selection": "Anular la selección",
|
||||
"Clear custom colour": "Quitar el color personalizado",
|
||||
"Clear custom color": "Quitar el color personalizado",
|
||||
"Select": "Seleccionar",
|
||||
"Select all": "Seleccionar todo",
|
||||
"Unsubscribe": "Darse de baja",
|
||||
@@ -617,7 +617,7 @@ export const catalog: Catalog = {
|
||||
"Maybe": "Quizá",
|
||||
"Confirmed": "Confirmado",
|
||||
"Tentative": "Provisional",
|
||||
"Cancelled": "Cancelado",
|
||||
"Canceled": "Cancelado",
|
||||
"organizer": "organizador",
|
||||
"Organizer: {name}": "Organizador: {name}",
|
||||
"Free": "Libre",
|
||||
@@ -632,7 +632,7 @@ export const catalog: Catalog = {
|
||||
"Working hours": "Horario laboral",
|
||||
"Working hours start": "El horario laboral empieza",
|
||||
"Working hours end": "El horario laboral termina",
|
||||
"Colour categories": "Categorías de color",
|
||||
"Color categories": "Categorías de color",
|
||||
"Category": "Categoría",
|
||||
"No category": "Sin categoría",
|
||||
"New category": "Categoría nueva",
|
||||
@@ -640,7 +640,7 @@ export const catalog: Catalog = {
|
||||
"Manage categories…": "Gestionar las categorías…",
|
||||
"Use category color": "Usar el color de la categoría",
|
||||
"Use calendar color": "Usar el color del calendario",
|
||||
"Use the default colour": "Usar el color predeterminado",
|
||||
"Use the default color": "Usar el color predeterminado",
|
||||
"+{n} more": "+{n} más",
|
||||
|
||||
"Contacts": "Contactos",
|
||||
@@ -799,7 +799,6 @@ export const catalog: Catalog = {
|
||||
"Theme": "Tema",
|
||||
"Accent color": "Color de acento",
|
||||
"Color": "Color",
|
||||
"Colour": "Color",
|
||||
"Text color": "Color del texto",
|
||||
"Density & text": "Densidad y texto",
|
||||
"Display density": "Densidad de visualización",
|
||||
@@ -817,7 +816,7 @@ export const catalog: Catalog = {
|
||||
"Collapse sidebar to icons": "Reducir la barra lateral a iconos",
|
||||
"Apply the theme to messages too": "Aplicar el tema también a los mensajes",
|
||||
"Apply it even to mail that styles itself": "Aplicarlo incluso al correo que se da estilo propio",
|
||||
"Most marketing and receipt mail sets a colour somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colours: backgrounds they laid the message on are dropped, while buttons and coloured banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Casi todo el correo publicitario y de recibos define algún color, así que el ajuste anterior deja casi todo sobre una tarjeta blanca. Con esto activado, el tema se impone sobre los colores del remitente: se descartan los fondos sobre los que apoyó el mensaje, mientras que los botones y los banners de color se conservan para que su texto siga siendo legible. Algunos mensajes no sobrevivirán intactos, y por eso es un ajuste aparte.",
|
||||
"Most marketing and receipt mail sets a color somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colors: backgrounds they laid the message on are dropped, while buttons and colored banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Casi todo el correo publicitario y de recibos define algún color, así que el ajuste anterior deja casi todo sobre una tarjeta blanca. Con esto activado, el tema se impone sobre los colores del remitente: se descartan los fondos sobre los que apoyó el mensaje, mientras que los botones y los banners de color se conservan para que su texto siga siendo legible. Algunos mensajes no sobrevivirán intactos, y por eso es un ajuste aparte.",
|
||||
"Swiping": "Deslizamiento",
|
||||
"Swipe left": "Deslizar a la izquierda",
|
||||
"Swipe right": "Deslizar a la derecha",
|
||||
@@ -1129,7 +1128,7 @@ export const catalog: Catalog = {
|
||||
"Each identity is a sender address with its own name, Reply-To and signature. The default identity is preselected when you compose; set a Reply-To when replies should go somewhere other than the From address.": "Cada identidad es una dirección de envío con su propio nombre, dirección de respuesta y firma. La identidad predeterminada se preselecciona al redactar; defina una dirección de respuesta cuando las respuestas deban llegar a un sitio distinto del remitente.",
|
||||
"This signature is larger than the server's {limit}-byte limit. ihasmail will keep the full version in your Files and store a short text fallback on the server — other mail clients will see the plain-text version.": "Esta firma supera el límite de {limit} bytes del servidor. ihasmail conservará la versión completa en sus Archivos y guardará una versión corta de texto en el servidor: los demás clientes verán la versión en texto sin formato.",
|
||||
"Outlook-style categories you can assign to events from the right-click menu or the event editor. The category name is stored on the event, so it syncs to other clients.": "Categorías al estilo de Outlook que puede asignar a los eventos desde el menú contextual o el editor de eventos. El nombre de la categoría se guarda en el evento, así que se sincroniza con otros clientes.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colours of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "El correo en texto sin formato ya sigue el tema. Con esta opción, el correo HTML sin colores propios también lo hace, en lugar de mostrarse sobre un fondo blanco. Los mensajes con estilo propio se dejan exactamente como los diseñó el remitente.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colors of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "El correo en texto sin formato ya sigue el tema. Con esta opción, el correo HTML sin colores propios también lo hace, en lugar de mostrarse sobre un fondo blanco. Los mensajes con estilo propio se dejan exactamente como los diseñó el remitente.",
|
||||
"This is separate from {setting} in General, which decides how dates, times and numbers are written. You can read an English interface with German dates, or the other way round.": "Esto es independiente de {setting} en General, que determina cómo se escriben las fechas, horas y números. Puede leer una interfaz en inglés con fechas en español, o al revés.",
|
||||
"On a touchscreen, drag a message sideways to act on it. Each direction can do one thing, or nothing. These follow your account, so a phone and a tablet agree; a mouse ignores them and keeps dragging messages into folders instead.": "En una pantalla táctil, deslice un mensaje hacia un lado para actuar sobre él. Cada dirección puede hacer una cosa, o ninguna. Estos ajustes siguen a su cuenta, así que el teléfono y la tableta coinciden; con el ratón se ignoran y se sigue arrastrando mensajes a las carpetas.",
|
||||
"This screen has no touchscreen, so nothing here changes what it does. Your phone or tablet will pick these up.": "Esta pantalla no es táctil, así que nada de esto cambia su comportamiento. Su teléfono o tableta tomará estos ajustes.",
|
||||
@@ -1190,12 +1189,12 @@ export const catalog: Catalog = {
|
||||
"Applying filter to existing messages…": "Aplicando el filtro a los mensajes existentes…",
|
||||
"Attachments are still uploading": "Los archivos adjuntos aún se están subiendo",
|
||||
"Calendar saved": "Calendario guardado",
|
||||
"Categorised as {name}": "Categoría «{name}» asignada",
|
||||
"Categorized as {name}": "Categoría «{name}» asignada",
|
||||
"Category cleared": "Categoría quitada",
|
||||
"Change this event?": "¿Cambiar este evento?",
|
||||
"Choose a calendar": "Elija un calendario",
|
||||
"Choose an address book": "Elija una libreta de direcciones",
|
||||
"Colour updated": "Color actualizado",
|
||||
"Color updated": "Color actualizado",
|
||||
"Contact created": "Contacto creado",
|
||||
"Contact deleted": "Contacto eliminado",
|
||||
"Contact saved": "Contacto guardado",
|
||||
@@ -1219,7 +1218,7 @@ export const catalog: Catalog = {
|
||||
"Could not update: {error}": "No se pudo actualizar: {error}",
|
||||
"Create": "Crear",
|
||||
"Create an address book first": "Cree primero una libreta de direcciones",
|
||||
"Custom colour removed": "Color personalizado quitado",
|
||||
"Custom color removed": "Color personalizado quitado",
|
||||
"Deactivate": "Desactivar",
|
||||
"Delete failed: {error}": "No se pudo eliminar: {error}",
|
||||
"Delete forever": "Eliminar definitivamente",
|
||||
@@ -1306,7 +1305,7 @@ export const catalog: Catalog = {
|
||||
"Script saved": "Script guardado",
|
||||
"Send (Ctrl+Enter)": "Enviar (Ctrl+Intro)",
|
||||
"Send anyway": "Enviar de todos modos",
|
||||
"Send cancelled — the message is back in Drafts": "Envío cancelado: el mensaje ha vuelto a Borradores",
|
||||
"Send canceled — the message is back in Drafts": "Envío cancelado: el mensaje ha vuelto a Borradores",
|
||||
"Send failed: {error}": "No se pudo enviar: {error}",
|
||||
"Send invites": "Enviar invitaciones",
|
||||
"Send scheduled for {when}": "Envío programado para {when}",
|
||||
@@ -1438,7 +1437,7 @@ export const catalog: Catalog = {
|
||||
"Date received": "Fecha de recepción",
|
||||
"Date sent": "Fecha de envío",
|
||||
"Day view": "Vista de día",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colours are derived, and every one is checked for contrast. The accent colour below still applies over any of them.": "Las paletas que llevan el nombre de otro proyecto son obra de ese proyecto y se usan bajo su propia licencia; los tonos intermedios entre sus colores publicados son derivados, y cada uno se comprueba para el contraste. El color de acento de abajo sigue aplicándose sobre cualquiera de ellas.",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colors are derived, and every one is checked for contrast. The accent color below still applies over any of them.": "Las paletas que llevan el nombre de otro proyecto son obra de ese proyecto y se usan bajo su propia licencia; los tonos intermedios entre sus colores publicados son derivados, y cada uno se comprueba para el contraste. El color de acento de abajo sigue aplicándose sobre cualquiera de ellas.",
|
||||
"Earlier": "Antes",
|
||||
"Every folder": "Todas las carpetas",
|
||||
"Everyone addressed will receive this.": "Todos los destinatarios lo recibirán.",
|
||||
@@ -1455,7 +1454,7 @@ export const catalog: Catalog = {
|
||||
"Go to Settings": "Ir a Configuración",
|
||||
"Go to Starred": "Ir a Destacados",
|
||||
"Import iCAL file…": "Importar archivo iCAL…",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colours and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Las etiquetas son palabras clave IMAP guardadas en sus mensajes, así que cualquier otro cliente las ve. Los nombres, los colores y el anidamiento son propios de ihasmail y acompañan a su cuenta. El anidamiento es solo de presentación: no reescribe nada en el buzón.",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colors and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Las etiquetas son palabras clave IMAP guardadas en sus mensajes, así que cualquier otro cliente las ve. Los nombres, los colores y el anidamiento son propios de ihasmail y acompañan a su cuenta. El anidamiento es solo de presentación: no reescribe nada en el buzón.",
|
||||
"Largest first": "Los más grandes primero",
|
||||
"Later": "Después",
|
||||
"Light or dark": "Claro u oscuro",
|
||||
@@ -1503,7 +1502,7 @@ export const catalog: Catalog = {
|
||||
"Saved": "Guardado",
|
||||
"Select all {n} in {folder}": "Seleccionar los {n} de {folder}",
|
||||
"Send message": "Enviar el mensaje",
|
||||
"Send outside your organisation?": "¿Enviar fuera de su organización?",
|
||||
"Send outside your organization?": "¿Enviar fuera de su organización?",
|
||||
"Send to {count} people?": "¿Enviar a {count} personas?",
|
||||
"Show birthdays from your contacts": "Mostrar los cumpleaños de sus contactos",
|
||||
"Show in the sidebar": "Mostrar en la barra lateral",
|
||||
@@ -1546,7 +1545,7 @@ export const catalog: Catalog = {
|
||||
"A to Z": "De la A a la Z",
|
||||
"It reads {shown} but goes to {actual}.": "Dice {shown}, pero lleva a {actual}.",
|
||||
"The full address is {href}.": "La dirección completa es {href}.",
|
||||
"This message came from {domain}, which is outside your organisation.": "Este mensaje procede de {domain}, que está fuera de su organización.",
|
||||
"This message came from {domain}, which is outside your organization.": "Este mensaje procede de {domain}, que está fuera de su organización.",
|
||||
"Unsaved changes": "Cambios sin guardar",
|
||||
"View as": "Ver como",
|
||||
"Warnings": "Avisos",
|
||||
|
||||
+21
-22
@@ -121,7 +121,7 @@ export const catalog: Catalog = {
|
||||
"This account signs in through an external directory, so its password can't be set here.": "Ce compte se connecte via un annuaire externe, son mot de passe ne peut donc pas être défini ici.",
|
||||
"The server's license allows no more accounts.": "La license du serveur ne permet pas de comptes supplémentaires.",
|
||||
"That domain name is already in use on this server, as a domain or another domain's other name.": "Ce nom de domaine est déjà utilisé sur ce serveur, comme domaine ou comme autre nom d’un autre domaine.",
|
||||
"Your organisation has reached the number of domains it is allowed.": "Votre organisation a atteint le nombre de domaines autorisé.",
|
||||
"Your organization has reached the number of domains it is allowed.": "Votre organisation a atteint le nombre de domaines autorisé.",
|
||||
"That is more than the mail server accepts in one change.": "C’est plus que ce que le serveur de messagerie accepte en une seule modification.",
|
||||
"The mail server rejected one of the values. Check what you entered and try again.": "Le serveur de messagerie a refusé l’une des valeurs. Vérifiez votre saisie et réessayez.",
|
||||
"The mail server refused the change ({code}).": "Le serveur de messagerie a refusé la modification ({code}).",
|
||||
@@ -176,7 +176,7 @@ export const catalog: Catalog = {
|
||||
"Search groups": "Rechercher des groupes",
|
||||
"No groups match": "Aucun groupe ne correspond",
|
||||
"No groups yet": "Aucun groupe pour l’instant",
|
||||
"Your organisation has reached the number of groups it is allowed.": "Votre organisation a atteint le nombre de groupes autorisé.",
|
||||
"Your organization has reached the number of groups it is allowed.": "Votre organisation a atteint le nombre de groupes autorisé.",
|
||||
"This group no longer exists. Someone may have deleted it.": "Ce groupe n’existe plus. Quelqu’un l’a peut-être supprimé.",
|
||||
"The server did not say whether the group was created.": "Le serveur n’a pas indiqué si le groupe a été créé.",
|
||||
"Mailing lists": "Listes de diffusion",
|
||||
@@ -201,7 +201,7 @@ export const catalog: Catalog = {
|
||||
"Search mailing lists": "Rechercher des listes de diffusion",
|
||||
"No mailing lists match": "Aucune liste de diffusion ne correspond",
|
||||
"No mailing lists yet": "Aucune liste de diffusion pour l’instant",
|
||||
"Your organisation has reached the number of mailing lists it is allowed.": "Votre organisation a atteint le nombre de listes de diffusion autorisé.",
|
||||
"Your organization has reached the number of mailing lists it is allowed.": "Votre organisation a atteint le nombre de listes de diffusion autorisé.",
|
||||
"This mailing list no longer exists. Someone may have deleted it.": "Cette liste de diffusion n’existe plus. Quelqu’un l’a peut-être supprimée.",
|
||||
"The server did not say whether the list was created.": "Le serveur n’a pas indiqué si la liste a été créée.",
|
||||
"Roles": "Rôles",
|
||||
@@ -252,13 +252,13 @@ export const catalog: Catalog = {
|
||||
"Open {name}": "Ouvrir {name}",
|
||||
"Default for {kinds}": "Par défaut pour les {kinds}",
|
||||
"You can't give a role permissions your own role doesn't have.": "Vous ne pouvez pas donner à un rôle des autorisations que votre propre rôle n’a pas.",
|
||||
"Your organisation has reached the number of roles it is allowed.": "Votre organisation a atteint le nombre de rôles autorisé.",
|
||||
"Your organization has reached the number of roles it is allowed.": "Votre organisation a atteint le nombre de rôles autorisé.",
|
||||
"This role no longer exists. Someone may have deleted it.": "Ce rôle n’existe plus. Quelqu’un l’a peut-être supprimé.",
|
||||
"the default roles": "les rôles par défaut",
|
||||
"The server did not say whether the role was created.": "Le serveur n’a pas indiqué si le rôle a été créé.",
|
||||
"No tenant": "Aucun locataire",
|
||||
"You can't move your own account into a tenant.": "Vous ne pouvez pas déplacer votre propre compte dans un locataire.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.": "Un compte peut être dans le locataire où se trouve son domaine. Dans un locataire, il est limité par le rôle du locataire et compte dans ses limites, et Administrateur signifie administrateur de ce locataire.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts toward its limits, and Administrator means administrator of that tenant.": "Un compte peut être dans le locataire où se trouve son domaine. Dans un locataire, il est limité par le rôle du locataire et compte dans ses limites, et Administrateur signifie administrateur de ce locataire.",
|
||||
"Tenants": "Locataires",
|
||||
"Storage in GB": "Stockage en Go",
|
||||
"Default tenant roles": "Rôles de locataire par défaut",
|
||||
@@ -286,7 +286,7 @@ export const catalog: Catalog = {
|
||||
"Delete tenant…": "Supprimer le locataire…",
|
||||
"Still holds {things}. Move them out first.": "Contient encore {things}. Déplacez-les d’abord.",
|
||||
"Delete tenant": "Supprimer le locataire",
|
||||
"Separate organisations on one server, each with its own people, domains and limits.": "Des organisations distinctes sur un même serveur, chacune avec ses personnes, ses domaines et ses limites.",
|
||||
"Separate organizations on one server, each with its own people, domains and limits.": "Des organisations distinctes sur un même serveur, chacune avec ses personnes, ses domaines et ses limites.",
|
||||
"Tenants are a Stalwart Enterprise feature.": "Les locataires sont une fonctionnalité de Stalwart Enterprise.",
|
||||
"Search tenants": "Rechercher des locataires",
|
||||
"No tenants match": "Aucun locataire ne correspond",
|
||||
@@ -352,7 +352,7 @@ export const catalog: Catalog = {
|
||||
"The mail server refused this. Your role may not allow it.": "Le serveur de messagerie a refusé. Votre rôle ne le permet peut-être pas.",
|
||||
"That address is already in use on this server, as an account, a list or an alias.": "Cette adresse est déjà utilisée sur ce serveur, par un compte, une liste ou un alias.",
|
||||
"One of the chosen domain, role or group can't be used for this account.": "Le domaine, le rôle ou le groupe choisi ne peut pas être utilisé pour ce compte.",
|
||||
"Your organisation has reached the number of accounts it is allowed.": "Votre organisation a atteint le nombre de comptes autorisé.",
|
||||
"Your organization has reached the number of accounts it is allowed.": "Votre organisation a atteint le nombre de comptes autorisé.",
|
||||
"Something still depends on this, so the server kept it.": "Un autre élément en dépend encore, le serveur l’a donc conservé.",
|
||||
"This account no longer exists. Someone may have deleted it.": "Ce compte n’existe plus. Quelqu’un l’a peut-être supprimé.",
|
||||
"The password was not accepted: {reason}": "Le mot de passe a été refusé : {reason}",
|
||||
@@ -429,7 +429,7 @@ export const catalog: Catalog = {
|
||||
"Turn off": "Désactiver",
|
||||
"Clear": "Vider",
|
||||
"Clear selection": "Annuler la sélection",
|
||||
"Clear custom colour": "Retirer la couleur personnalisée",
|
||||
"Clear custom color": "Retirer la couleur personnalisée",
|
||||
"Select": "Sélectionner",
|
||||
"Select all": "Tout sélectionner",
|
||||
"Unsubscribe": "Se désabonner",
|
||||
@@ -622,7 +622,7 @@ export const catalog: Catalog = {
|
||||
"Maybe": "Peut-être",
|
||||
"Confirmed": "Confirmé",
|
||||
"Tentative": "Provisoire",
|
||||
"Cancelled": "Annulé",
|
||||
"Canceled": "Annulé",
|
||||
"organizer": "organisateur",
|
||||
"Organizer: {name}": "Organisateur : {name}",
|
||||
"Free": "Disponible",
|
||||
@@ -637,7 +637,7 @@ export const catalog: Catalog = {
|
||||
"Working hours": "Heures de travail",
|
||||
"Working hours start": "Début des heures de travail",
|
||||
"Working hours end": "Fin des heures de travail",
|
||||
"Colour categories": "Catégories de couleur",
|
||||
"Color categories": "Catégories de couleur",
|
||||
"Category": "Catégorie",
|
||||
"No category": "Aucune catégorie",
|
||||
"New category": "Nouvelle catégorie",
|
||||
@@ -645,7 +645,7 @@ export const catalog: Catalog = {
|
||||
"Manage categories…": "Gérer les catégories…",
|
||||
"Use category color": "Utiliser la couleur de la catégorie",
|
||||
"Use calendar color": "Utiliser la couleur de l'agenda",
|
||||
"Use the default colour": "Utiliser la couleur par défaut",
|
||||
"Use the default color": "Utiliser la couleur par défaut",
|
||||
"+{n} more": "+{n} autres",
|
||||
|
||||
"Contacts": "Contacts",
|
||||
@@ -805,7 +805,6 @@ export const catalog: Catalog = {
|
||||
"Theme": "Thème",
|
||||
"Accent color": "Couleur d'accentuation",
|
||||
"Color": "Couleur",
|
||||
"Colour": "Couleur",
|
||||
"Text color": "Couleur du texte",
|
||||
"Density & text": "Densité et texte",
|
||||
"Display density": "Densité d'affichage",
|
||||
@@ -823,7 +822,7 @@ export const catalog: Catalog = {
|
||||
"Collapse sidebar to icons": "Réduire la barre latérale en icônes",
|
||||
"Apply the theme to messages too": "Appliquer le thème aux messages",
|
||||
"Apply it even to mail that styles itself": "L'appliquer même aux messages qui se mettent en forme",
|
||||
"Most marketing and receipt mail sets a colour somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colours: backgrounds they laid the message on are dropped, while buttons and coloured banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Presque tous les courriers publicitaires et les reçus définissent une couleur quelque part, si bien que le réglage ci-dessus en laisse la quasi-totalité sur une carte blanche. Avec cette option, le thème est imposé par-dessus les couleurs de l'expéditeur : les fonds sur lesquels le message repose sont supprimés, tandis que les boutons et les bandeaux colorés sont conservés pour que leur texte reste lisible. Certains messages n'y survivront pas intacts, d'où un réglage distinct.",
|
||||
"Most marketing and receipt mail sets a color somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colors: backgrounds they laid the message on are dropped, while buttons and colored banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Presque tous les courriers publicitaires et les reçus définissent une couleur quelque part, si bien que le réglage ci-dessus en laisse la quasi-totalité sur une carte blanche. Avec cette option, le thème est imposé par-dessus les couleurs de l'expéditeur : les fonds sur lesquels le message repose sont supprimés, tandis que les boutons et les bandeaux colorés sont conservés pour que leur texte reste lisible. Certains messages n'y survivront pas intacts, d'où un réglage distinct.",
|
||||
"Swiping": "Balayage",
|
||||
"Swipe left": "Balayer vers la gauche",
|
||||
"Swipe right": "Balayer vers la droite",
|
||||
@@ -1134,7 +1133,7 @@ export const catalog: Catalog = {
|
||||
"Each identity is a sender address with its own name, Reply-To and signature. The default identity is preselected when you compose; set a Reply-To when replies should go somewhere other than the From address.": "Chaque identité est une adresse d'expédition avec son propre nom, sa propre adresse de réponse et sa propre signature. L'identité par défaut est présélectionnée à la rédaction ; définissez une adresse de réponse lorsque les réponses doivent arriver ailleurs qu'à l'adresse d'expédition.",
|
||||
"This signature is larger than the server's {limit}-byte limit. ihasmail will keep the full version in your Files and store a short text fallback on the server — other mail clients will see the plain-text version.": "Cette signature dépasse la limite de {limit} octets du serveur. ihasmail conservera la version complète dans vos Fichiers et enregistrera une version texte courte sur le serveur — les autres clients verront la version en texte brut.",
|
||||
"Outlook-style categories you can assign to events from the right-click menu or the event editor. The category name is stored on the event, so it syncs to other clients.": "Des catégories façon Outlook, attribuables aux événements depuis le menu contextuel ou l'éditeur d'événement. Le nom de la catégorie est enregistré dans l'événement et se synchronise donc avec les autres clients.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colours of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "Les messages en texte brut suivent déjà le thème. Avec cette option, les messages HTML sans couleurs propres le suivent aussi, au lieu de s'afficher sur un fond blanc. Les messages qui définissent leur propre style restent exactement tels que l'expéditeur les a conçus.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colors of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "Les messages en texte brut suivent déjà le thème. Avec cette option, les messages HTML sans couleurs propres le suivent aussi, au lieu de s'afficher sur un fond blanc. Les messages qui définissent leur propre style restent exactement tels que l'expéditeur les a conçus.",
|
||||
"This is separate from {setting} in General, which decides how dates, times and numbers are written. You can read an English interface with German dates, or the other way round.": "Ceci est indépendant de {setting} dans Général, qui détermine l'écriture des dates, heures et nombres. Vous pouvez lire une interface anglaise avec des dates françaises, ou l'inverse.",
|
||||
"On a touchscreen, drag a message sideways to act on it. Each direction can do one thing, or nothing. These follow your account, so a phone and a tablet agree; a mouse ignores them and keeps dragging messages into folders instead.": "Sur un écran tactile, faites glisser un message sur le côté pour agir dessus. Chaque direction peut faire une chose, ou rien. Ces réglages suivent votre compte : téléphone et tablette sont donc d'accord. À la souris, ils sont ignorés et le glisser-déposer vers les dossiers continue de fonctionner.",
|
||||
"This screen has no touchscreen, so nothing here changes what it does. Your phone or tablet will pick these up.": "Cet écran n'est pas tactile : rien ici ne change son comportement. Votre téléphone ou votre tablette reprendra ces réglages.",
|
||||
@@ -1195,12 +1194,12 @@ export const catalog: Catalog = {
|
||||
"Applying filter to existing messages…": "Application du filtre aux messages existants…",
|
||||
"Attachments are still uploading": "Les pièces jointes sont encore en cours d’envoi",
|
||||
"Calendar saved": "Agenda enregistré",
|
||||
"Categorised as {name}": "Catégorie « {name} » attribuée",
|
||||
"Categorized as {name}": "Catégorie « {name} » attribuée",
|
||||
"Category cleared": "Catégorie retirée",
|
||||
"Change this event?": "Modifier cet événement ?",
|
||||
"Choose a calendar": "Choisissez un agenda",
|
||||
"Choose an address book": "Choisissez un carnet d’adresses",
|
||||
"Colour updated": "Couleur modifiée",
|
||||
"Color updated": "Couleur modifiée",
|
||||
"Contact created": "Contact créé",
|
||||
"Contact deleted": "Contact supprimé",
|
||||
"Contact saved": "Contact enregistré",
|
||||
@@ -1224,7 +1223,7 @@ export const catalog: Catalog = {
|
||||
"Could not update: {error}": "Impossible de mettre à jour : {error}",
|
||||
"Create": "Créer",
|
||||
"Create an address book first": "Créez d’abord un carnet d’adresses",
|
||||
"Custom colour removed": "Couleur personnalisée retirée",
|
||||
"Custom color removed": "Couleur personnalisée retirée",
|
||||
"Deactivate": "Désactiver",
|
||||
"Delete failed: {error}": "Échec de la suppression : {error}",
|
||||
"Delete forever": "Supprimer définitivement",
|
||||
@@ -1311,7 +1310,7 @@ export const catalog: Catalog = {
|
||||
"Script saved": "Script enregistré",
|
||||
"Send (Ctrl+Enter)": "Envoyer (Ctrl+Entrée)",
|
||||
"Send anyway": "Envoyer quand même",
|
||||
"Send cancelled — the message is back in Drafts": "Envoi annulé : le message est de retour dans les brouillons",
|
||||
"Send canceled — the message is back in Drafts": "Envoi annulé : le message est de retour dans les brouillons",
|
||||
"Send failed: {error}": "Échec de l’envoi : {error}",
|
||||
"Send invites": "Envoyer les invitations",
|
||||
"Send scheduled for {when}": "Envoi programmé pour {when}",
|
||||
@@ -1443,7 +1442,7 @@ export const catalog: Catalog = {
|
||||
"Date received": "Date de réception",
|
||||
"Date sent": "Date d'envoi",
|
||||
"Day view": "Vue jour",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colours are derived, and every one is checked for contrast. The accent colour below still applies over any of them.": "Les palettes portant le nom d'un autre projet sont l'œuvre de ce projet et sont utilisées sous sa propre license ; les nuances intermédiaires entre leurs couleurs publiées sont dérivées, et chacune est vérifiée pour le contraste. La couleur d'accentuation ci-dessous s'applique toujours par-dessus n'importe laquelle d'entre elles.",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colors are derived, and every one is checked for contrast. The accent color below still applies over any of them.": "Les palettes portant le nom d'un autre projet sont l'œuvre de ce projet et sont utilisées sous sa propre license ; les nuances intermédiaires entre leurs couleurs publiées sont dérivées, et chacune est vérifiée pour le contraste. La couleur d'accentuation ci-dessous s'applique toujours par-dessus n'importe laquelle d'entre elles.",
|
||||
"Earlier": "Plus tôt",
|
||||
"Every folder": "Tous les dossiers",
|
||||
"Everyone addressed will receive this.": "Tous les destinataires le recevront.",
|
||||
@@ -1460,7 +1459,7 @@ export const catalog: Catalog = {
|
||||
"Go to Settings": "Aller aux Paramètres",
|
||||
"Go to Starred": "Aller aux messages suivis",
|
||||
"Import iCAL file…": "Importer un fichier iCAL…",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colours and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Les libellés sont des mots-clés IMAP stockés sur vos messages, donc tous les autres clients les voient. Les noms, les couleurs et l'imbrication appartiennent à ihasmail et suivent votre compte. L'imbrication est purement visuelle : elle ne réécrit rien dans la boîte aux lettres.",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colors and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Les libellés sont des mots-clés IMAP stockés sur vos messages, donc tous les autres clients les voient. Les noms, les couleurs et l'imbrication appartiennent à ihasmail et suivent votre compte. L'imbrication est purement visuelle : elle ne réécrit rien dans la boîte aux lettres.",
|
||||
"Largest first": "Les plus volumineux d'abord",
|
||||
"Later": "Plus tard",
|
||||
"Light or dark": "Clair ou sombre",
|
||||
@@ -1508,7 +1507,7 @@ export const catalog: Catalog = {
|
||||
"Saved": "Enregistré",
|
||||
"Select all {n} in {folder}": "Sélectionner les {n} de {folder}",
|
||||
"Send message": "Envoyer le message",
|
||||
"Send outside your organisation?": "Envoyer en dehors de votre organisation ?",
|
||||
"Send outside your organization?": "Envoyer en dehors de votre organisation ?",
|
||||
"Send to {count} people?": "Envoyer à {count} personnes ?",
|
||||
"Show birthdays from your contacts": "Afficher les anniversaires de vos contacts",
|
||||
"Show in the sidebar": "Afficher dans la barre latérale",
|
||||
@@ -1551,7 +1550,7 @@ export const catalog: Catalog = {
|
||||
"A to Z": "De A à Z",
|
||||
"It reads {shown} but goes to {actual}.": "Il affiche {shown} mais mène à {actual}.",
|
||||
"The full address is {href}.": "L'adresse complète est {href}.",
|
||||
"This message came from {domain}, which is outside your organisation.": "Ce message provient de {domain}, qui est extérieur à votre organisation.",
|
||||
"This message came from {domain}, which is outside your organization.": "Ce message provient de {domain}, qui est extérieur à votre organisation.",
|
||||
"Unsaved changes": "Modifications non enregistrées",
|
||||
"View as": "Afficher comme",
|
||||
"Warnings": "Avertissements",
|
||||
|
||||
+21
-22
@@ -115,7 +115,7 @@ export const catalog: Catalog = {
|
||||
"This account signs in through an external directory, so its password can't be set here.": "このアカウントは外部ディレクトリでサインインするため、ここではパスワードを設定できません。",
|
||||
"The server's license allows no more accounts.": "サーバーのライセンスでは、これ以上アカウントを追加できません。",
|
||||
"That domain name is already in use on this server, as a domain or another domain's other name.": "このドメイン名は、ドメインまたは別のドメインの別名としてこのサーバーで既に使われています。",
|
||||
"Your organisation has reached the number of domains it is allowed.": "組織で許可されているドメイン数の上限に達しました。",
|
||||
"Your organization has reached the number of domains it is allowed.": "組織で許可されているドメイン数の上限に達しました。",
|
||||
"That is more than the mail server accepts in one change.": "メールサーバーが一度の変更で受け付けられる量を超えています。",
|
||||
"The mail server rejected one of the values. Check what you entered and try again.": "メールサーバーが値のひとつを拒否しました。入力内容を確認して、もう一度お試しください。",
|
||||
"The mail server refused the change ({code}).": "メールサーバーが変更を拒否しました({code})。",
|
||||
@@ -170,7 +170,7 @@ export const catalog: Catalog = {
|
||||
"Search groups": "グループを検索",
|
||||
"No groups match": "一致するグループはありません",
|
||||
"No groups yet": "まだグループがありません",
|
||||
"Your organisation has reached the number of groups it is allowed.": "組織で許可されているグループ数の上限に達しました。",
|
||||
"Your organization has reached the number of groups it is allowed.": "組織で許可されているグループ数の上限に達しました。",
|
||||
"This group no longer exists. Someone may have deleted it.": "このグループはもう存在しません。誰かが削除した可能性があります。",
|
||||
"The server did not say whether the group was created.": "グループが作成されたかどうか、サーバーから返答がありませんでした。",
|
||||
"Mailing lists": "メーリングリスト",
|
||||
@@ -195,7 +195,7 @@ export const catalog: Catalog = {
|
||||
"Search mailing lists": "メーリングリストを検索",
|
||||
"No mailing lists match": "一致するメーリングリストはありません",
|
||||
"No mailing lists yet": "まだメーリングリストがありません",
|
||||
"Your organisation has reached the number of mailing lists it is allowed.": "組織で許可されているメーリングリスト数の上限に達しました。",
|
||||
"Your organization has reached the number of mailing lists it is allowed.": "組織で許可されているメーリングリスト数の上限に達しました。",
|
||||
"This mailing list no longer exists. Someone may have deleted it.": "このメーリングリストはもう存在しません。誰かが削除した可能性があります。",
|
||||
"The server did not say whether the list was created.": "リストが作成されたかどうか、サーバーから返答がありませんでした。",
|
||||
"Roles": "ロール",
|
||||
@@ -246,13 +246,13 @@ export const catalog: Catalog = {
|
||||
"Open {name}": "{name} を開く",
|
||||
"Default for {kinds}": "既定の付与先: {kinds}",
|
||||
"You can't give a role permissions your own role doesn't have.": "あなたのロールにない権限をロールに与えることはできません。",
|
||||
"Your organisation has reached the number of roles it is allowed.": "組織で許可されているロール数の上限に達しました。",
|
||||
"Your organization has reached the number of roles it is allowed.": "組織で許可されているロール数の上限に達しました。",
|
||||
"This role no longer exists. Someone may have deleted it.": "このロールはもう存在しません。誰かが削除した可能性があります。",
|
||||
"the default roles": "既定のロール設定",
|
||||
"The server did not say whether the role was created.": "ロールが作成されたかどうか、サーバーから返答がありませんでした。",
|
||||
"No tenant": "テナントなし",
|
||||
"You can't move your own account into a tenant.": "自分のアカウントをテナントに移すことはできません。",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.": "アカウントは、そのドメインが属するテナントにのみ入れられます。テナント内のアカウントは、テナントのロールによって制限され、テナントの上限に数えられます。また、管理者はそのテナントの管理者を意味します。",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts toward its limits, and Administrator means administrator of that tenant.": "アカウントは、そのドメインが属するテナントにのみ入れられます。テナント内のアカウントは、テナントのロールによって制限され、テナントの上限に数えられます。また、管理者はそのテナントの管理者を意味します。",
|
||||
"Tenants": "テナント",
|
||||
"Storage in GB": "ストレージ (GB)",
|
||||
"Default tenant roles": "テナントの既定ロール",
|
||||
@@ -280,7 +280,7 @@ export const catalog: Catalog = {
|
||||
"Delete tenant…": "テナントを削除…",
|
||||
"Still holds {things}. Move them out first.": "まだ {things} が含まれています。先に移動してください。",
|
||||
"Delete tenant": "テナントを削除",
|
||||
"Separate organisations on one server, each with its own people, domains and limits.": "1 台のサーバー上の別々の組織で、それぞれに利用者、ドメイン、上限があります。",
|
||||
"Separate organizations on one server, each with its own people, domains and limits.": "1 台のサーバー上の別々の組織で、それぞれに利用者、ドメイン、上限があります。",
|
||||
"Tenants are a Stalwart Enterprise feature.": "テナントは Stalwart Enterprise の機能です。",
|
||||
"Search tenants": "テナントを検索",
|
||||
"No tenants match": "一致するテナントはありません",
|
||||
@@ -346,7 +346,7 @@ export const catalog: Catalog = {
|
||||
"The mail server refused this. Your role may not allow it.": "メールサーバーに拒否されました。ロールで許可されていない可能性があります。",
|
||||
"That address is already in use on this server, as an account, a list or an alias.": "このアドレスは、アカウント、リスト、またはエイリアスとして、このサーバーですでに使われています。",
|
||||
"One of the chosen domain, role or group can't be used for this account.": "選択したドメイン、ロール、またはグループはこのアカウントには使えません。",
|
||||
"Your organisation has reached the number of accounts it is allowed.": "組織で許可されているアカウント数の上限に達しました。",
|
||||
"Your organization has reached the number of accounts it is allowed.": "組織で許可されているアカウント数の上限に達しました。",
|
||||
"Something still depends on this, so the server kept it.": "まだこれに依存しているものがあるため、サーバーは削除しませんでした。",
|
||||
"This account no longer exists. Someone may have deleted it.": "このアカウントはもう存在しません。誰かが削除した可能性があります。",
|
||||
"The password was not accepted: {reason}": "パスワードは受け付けられませんでした: {reason}",
|
||||
@@ -423,7 +423,7 @@ export const catalog: Catalog = {
|
||||
"Turn off": "オフにする",
|
||||
"Clear": "クリア",
|
||||
"Clear selection": "選択を解除",
|
||||
"Clear custom colour": "カスタム色をクリア",
|
||||
"Clear custom color": "カスタム色をクリア",
|
||||
"Select": "選択",
|
||||
"Select all": "すべて選択",
|
||||
"Unsubscribe": "配信を停止",
|
||||
@@ -616,7 +616,7 @@ export const catalog: Catalog = {
|
||||
"Maybe": "未定",
|
||||
"Confirmed": "確定",
|
||||
"Tentative": "仮",
|
||||
"Cancelled": "キャンセル済み",
|
||||
"Canceled": "キャンセル済み",
|
||||
"organizer": "主催者",
|
||||
"Organizer: {name}": "主催者: {name}",
|
||||
"Free": "空き",
|
||||
@@ -631,7 +631,7 @@ export const catalog: Catalog = {
|
||||
"Working hours": "勤務時間",
|
||||
"Working hours start": "勤務時間の開始",
|
||||
"Working hours end": "勤務時間の終了",
|
||||
"Colour categories": "色分類",
|
||||
"Color categories": "色分類",
|
||||
"Category": "分類",
|
||||
"No category": "分類なし",
|
||||
"New category": "新しい分類",
|
||||
@@ -639,7 +639,7 @@ export const catalog: Catalog = {
|
||||
"Manage categories…": "分類を管理…",
|
||||
"Use category color": "分類の色を使う",
|
||||
"Use calendar color": "カレンダーの色を使う",
|
||||
"Use the default colour": "既定の色を使う",
|
||||
"Use the default color": "既定の色を使う",
|
||||
"+{n} more": "他 {n} 件",
|
||||
|
||||
"Contacts": "連絡先",
|
||||
@@ -799,7 +799,6 @@ export const catalog: Catalog = {
|
||||
"Theme": "テーマ",
|
||||
"Accent color": "アクセントカラー",
|
||||
"Color": "色",
|
||||
"Colour": "色",
|
||||
"Text color": "文字色",
|
||||
"Density & text": "密度と文字",
|
||||
"Display density": "表示密度",
|
||||
@@ -817,7 +816,7 @@ export const catalog: Catalog = {
|
||||
"Collapse sidebar to icons": "サイドバーをアイコンだけにする",
|
||||
"Apply the theme to messages too": "メールにもテーマを適用する",
|
||||
"Apply it even to mail that styles itself": "自分で配色を持つメールにも適用する",
|
||||
"Most marketing and receipt mail sets a colour somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colours: backgrounds they laid the message on are dropped, while buttons and coloured banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "宣伝メールや領収メールはほとんどがどこかで色を指定しているため、上の設定では大半が白いカードのままになります。これを有効にすると、送信者の配色の上からテーマを適用します。メッセージが載っている背景は取り除き、ボタンや色付きのバナーは文字が読めるようにそのまま残します。一部のメールは元の見た目を保てないため、別の設定として分けています。",
|
||||
"Most marketing and receipt mail sets a color somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colors: backgrounds they laid the message on are dropped, while buttons and colored banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "宣伝メールや領収メールはほとんどがどこかで色を指定しているため、上の設定では大半が白いカードのままになります。これを有効にすると、送信者の配色の上からテーマを適用します。メッセージが載っている背景は取り除き、ボタンや色付きのバナーは文字が読めるようにそのまま残します。一部のメールは元の見た目を保てないため、別の設定として分けています。",
|
||||
"Swiping": "スワイプ操作",
|
||||
"Swipe left": "左へスワイプ",
|
||||
"Swipe right": "右へスワイプ",
|
||||
@@ -1083,7 +1082,7 @@ export const catalog: Catalog = {
|
||||
"Each identity is a sender address with its own name, Reply-To and signature. The default identity is preselected when you compose; set a Reply-To when replies should go somewhere other than the From address.": "差出人とは、それぞれ名前・返信先・署名を持つ送信用アドレスのことです。作成時には既定の差出人があらかじめ選ばれます。返信を差出人アドレス以外へ届けたい場合は、返信先を設定してください。",
|
||||
"This signature is larger than the server's {limit}-byte limit. ihasmail will keep the full version in your Files and store a short text fallback on the server — other mail clients will see the plain-text version.": "この署名はサーバーの上限 {limit} バイトを超えています。ihasmail は完全版を「ファイル」に保存し、サーバーには短いテキスト版を置きます。他のメールクライアントにはテキスト版が表示されます。",
|
||||
"Outlook-style categories you can assign to events from the right-click menu or the event editor. The category name is stored on the event, so it syncs to other clients.": "Outlook 形式の分類です。右クリックメニューや予定の編集画面から予定に割り当てられます。分類名は予定に保存されるため、他のクライアントにも同期されます。",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colours of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "プレーンテキストのメールは、もともとテーマに従います。これをオンにすると、独自の配色を持たない HTML メールもテーマに従い、白いカードの上に置かれなくなります。自分でスタイルを指定しているメールは、差出人が作ったとおりに表示されます。",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colors of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "プレーンテキストのメールは、もともとテーマに従います。これをオンにすると、独自の配色を持たない HTML メールもテーマに従い、白いカードの上に置かれなくなります。自分でスタイルを指定しているメールは、差出人が作ったとおりに表示されます。",
|
||||
"On a touchscreen, drag a message sideways to act on it. Each direction can do one thing, or nothing. These follow your account, so a phone and a tablet agree; a mouse ignores them and keeps dragging messages into folders instead.": "タッチ画面では、メールを横にドラッグすると操作できます。各方向に 1 つの操作を割り当てるか、何も割り当てないかを選べます。この設定はアカウントに従うため、スマートフォンとタブレットで揃います。マウスはこの設定を無視し、これまでどおりメールをフォルダーへドラッグします。",
|
||||
"This screen has no touchscreen, so nothing here changes what it does. Your phone or tablet will pick these up.": "この画面にはタッチ機能がないため、ここでの設定は動作に影響しません。スマートフォンやタブレットに反映されます。",
|
||||
"Holding a message selects it, and holding a folder opens its menu. Pull the top of the message list down to check for new mail.": "メールを長押しすると選択、フォルダーを長押しするとメニューが開きます。メール一覧の上端を下に引くと、新着メールを確認できます。",
|
||||
@@ -1198,12 +1197,12 @@ export const catalog: Catalog = {
|
||||
"Applying filter to existing messages…": "既存のメールにフィルターを適用しています…",
|
||||
"Attachments are still uploading": "添付ファイルをアップロード中です",
|
||||
"Calendar saved": "カレンダーを保存しました",
|
||||
"Categorised as {name}": "分類「{name}」を設定しました",
|
||||
"Categorized as {name}": "分類「{name}」を設定しました",
|
||||
"Category cleared": "分類を解除しました",
|
||||
"Change this event?": "この予定を変更しますか?",
|
||||
"Choose a calendar": "カレンダーを選んでください",
|
||||
"Choose an address book": "アドレス帳を選んでください",
|
||||
"Colour updated": "色を変更しました",
|
||||
"Color updated": "色を変更しました",
|
||||
"Contact created": "連絡先を作成しました",
|
||||
"Contact deleted": "連絡先を削除しました",
|
||||
"Contact saved": "連絡先を保存しました",
|
||||
@@ -1227,7 +1226,7 @@ export const catalog: Catalog = {
|
||||
"Could not update: {error}": "更新できませんでした: {error}",
|
||||
"Create": "作成",
|
||||
"Create an address book first": "先にアドレス帳を作成してください",
|
||||
"Custom colour removed": "カスタム色を解除しました",
|
||||
"Custom color removed": "カスタム色を解除しました",
|
||||
"Deactivate": "無効にする",
|
||||
"Delete failed: {error}": "削除できませんでした: {error}",
|
||||
"Delete forever": "完全に削除",
|
||||
@@ -1314,7 +1313,7 @@ export const catalog: Catalog = {
|
||||
"Script saved": "スクリプトを保存しました",
|
||||
"Send (Ctrl+Enter)": "送信 (Ctrl+Enter)",
|
||||
"Send anyway": "このまま送信",
|
||||
"Send cancelled — the message is back in Drafts": "送信を取り消しました。メールは下書きに戻っています",
|
||||
"Send canceled — the message is back in Drafts": "送信を取り消しました。メールは下書きに戻っています",
|
||||
"Send failed: {error}": "送信できませんでした: {error}",
|
||||
"Send invites": "招待を送信",
|
||||
"Send scheduled for {when}": "{when} に送信を予約しました",
|
||||
@@ -1446,7 +1445,7 @@ export const catalog: Catalog = {
|
||||
"Date received": "受信日時",
|
||||
"Date sent": "送信日時",
|
||||
"Day view": "日表示",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colours are derived, and every one is checked for contrast. The accent colour below still applies over any of them.": "他のプロジェクトの名前が付いたパレットは、そのプロジェクトの成果物であり、そのプロジェクト自身のライセンスのもとで使用しています。公開されている色の中間の階調は派生させたもので、いずれもコントラストを検証しています。下のアクセントカラーは、どのパレットの上にも適用されます。",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colors are derived, and every one is checked for contrast. The accent color below still applies over any of them.": "他のプロジェクトの名前が付いたパレットは、そのプロジェクトの成果物であり、そのプロジェクト自身のライセンスのもとで使用しています。公開されている色の中間の階調は派生させたもので、いずれもコントラストを検証しています。下のアクセントカラーは、どのパレットの上にも適用されます。",
|
||||
"Earlier": "これより前",
|
||||
"Every folder": "すべてのフォルダー",
|
||||
"Everyone addressed will receive this.": "宛先の全員がこれを受け取ります。",
|
||||
@@ -1463,7 +1462,7 @@ export const catalog: Catalog = {
|
||||
"Go to Settings": "設定へ移動",
|
||||
"Go to Starred": "スター付きへ移動",
|
||||
"Import iCAL file…": "iCAL ファイルをインポート…",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colours and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "ラベルはメールに保存される IMAP キーワードなので、ほかのクライアントからも見えます。名前、色、入れ子は ihasmail 独自のもので、アカウントに従います。入れ子は表示上のものにすぎず、メールボックスの中身は書き換えません。",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colors and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "ラベルはメールに保存される IMAP キーワードなので、ほかのクライアントからも見えます。名前、色、入れ子は ihasmail 独自のもので、アカウントに従います。入れ子は表示上のものにすぎず、メールボックスの中身は書き換えません。",
|
||||
"Largest first": "サイズの大きい順",
|
||||
"Later": "これより後",
|
||||
"Light or dark": "ライトまたはダーク",
|
||||
@@ -1511,7 +1510,7 @@ export const catalog: Catalog = {
|
||||
"Saved": "保存しました",
|
||||
"Select all {n} in {folder}": "{folder} 内の {n} 件すべてを選択",
|
||||
"Send message": "メールを送信",
|
||||
"Send outside your organisation?": "組織の外に送信しますか?",
|
||||
"Send outside your organization?": "組織の外に送信しますか?",
|
||||
"Send to {count} people?": "{count} 人に送信しますか?",
|
||||
"Show birthdays from your contacts": "連絡先の誕生日を表示する",
|
||||
"Show in the sidebar": "サイドバーに表示する",
|
||||
@@ -1554,7 +1553,7 @@ export const catalog: Catalog = {
|
||||
"A to Z": "A→Z の順",
|
||||
"It reads {shown} but goes to {actual}.": "表示は {shown} ですが、実際のリンク先は {actual} です。",
|
||||
"The full address is {href}.": "完全なアドレスは {href} です。",
|
||||
"This message came from {domain}, which is outside your organisation.": "このメッセージは組織外の {domain} から届いています。",
|
||||
"This message came from {domain}, which is outside your organization.": "このメッセージは組織外の {domain} から届いています。",
|
||||
"Unsaved changes": "保存されていない変更",
|
||||
"View as": "表示形式",
|
||||
"Warnings": "警告",
|
||||
|
||||
+21
-22
@@ -112,7 +112,7 @@ export const catalog: Catalog = {
|
||||
"This account signs in through an external directory, so its password can't be set here.": "Dit account logt in via een externe adreslijst, dus het wachtwoord kan hier niet worden ingesteld.",
|
||||
"The server's license allows no more accounts.": "De licentie van de server staat geen extra accounts toe.",
|
||||
"That domain name is already in use on this server, as a domain or another domain's other name.": "Die domeinnaam is op deze server al in gebruik, als domein of als andere naam van een ander domein.",
|
||||
"Your organisation has reached the number of domains it is allowed.": "Uw organisatie heeft het toegestane aantal domeinen bereikt.",
|
||||
"Your organization has reached the number of domains it is allowed.": "Uw organisatie heeft het toegestane aantal domeinen bereikt.",
|
||||
"That is more than the mail server accepts in one change.": "Dat is meer dan de mailserver in één wijziging accepteert.",
|
||||
"The mail server rejected one of the values. Check what you entered and try again.": "De mailserver heeft een van de waarden geweigerd. Controleer wat u hebt ingevuld en probeer het opnieuw.",
|
||||
"The mail server refused the change ({code}).": "De mailserver heeft de wijziging geweigerd ({code}).",
|
||||
@@ -167,7 +167,7 @@ export const catalog: Catalog = {
|
||||
"Search groups": "Groepen zoeken",
|
||||
"No groups match": "Geen groepen gevonden",
|
||||
"No groups yet": "Nog geen groepen",
|
||||
"Your organisation has reached the number of groups it is allowed.": "Uw organisatie heeft het toegestane aantal groepen bereikt.",
|
||||
"Your organization has reached the number of groups it is allowed.": "Uw organisatie heeft het toegestane aantal groepen bereikt.",
|
||||
"This group no longer exists. Someone may have deleted it.": "Deze groep bestaat niet meer. Iemand heeft hem mogelijk verwijderd.",
|
||||
"The server did not say whether the group was created.": "De server heeft niet gemeld of de groep is aangemaakt.",
|
||||
"Mailing lists": "Mailinglijsten",
|
||||
@@ -192,7 +192,7 @@ export const catalog: Catalog = {
|
||||
"Search mailing lists": "Mailinglijsten zoeken",
|
||||
"No mailing lists match": "Geen mailinglijsten gevonden",
|
||||
"No mailing lists yet": "Nog geen mailinglijsten",
|
||||
"Your organisation has reached the number of mailing lists it is allowed.": "Uw organisatie heeft het toegestane aantal mailinglijsten bereikt.",
|
||||
"Your organization has reached the number of mailing lists it is allowed.": "Uw organisatie heeft het toegestane aantal mailinglijsten bereikt.",
|
||||
"This mailing list no longer exists. Someone may have deleted it.": "Deze mailinglijst bestaat niet meer. Iemand heeft hem mogelijk verwijderd.",
|
||||
"The server did not say whether the list was created.": "De server heeft niet gemeld of de lijst is aangemaakt.",
|
||||
"Roles": "Rollen",
|
||||
@@ -243,13 +243,13 @@ export const catalog: Catalog = {
|
||||
"Open {name}": "{name} openen",
|
||||
"Default for {kinds}": "Standaard voor {kinds}",
|
||||
"You can't give a role permissions your own role doesn't have.": "U kunt een rol geen rechten geven die uw eigen rol niet heeft.",
|
||||
"Your organisation has reached the number of roles it is allowed.": "Uw organisatie heeft het toegestane aantal rollen bereikt.",
|
||||
"Your organization has reached the number of roles it is allowed.": "Uw organisatie heeft het toegestane aantal rollen bereikt.",
|
||||
"This role no longer exists. Someone may have deleted it.": "Deze rol bestaat niet meer. Iemand heeft hem mogelijk verwijderd.",
|
||||
"the default roles": "de standaardrollen",
|
||||
"The server did not say whether the role was created.": "De server heeft niet gemeld of de rol is aangemaakt.",
|
||||
"No tenant": "Geen tenant",
|
||||
"You can't move your own account into a tenant.": "U kunt uw eigen account niet naar een tenant verplaatsen.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.": "Een account kan in de tenant zitten waarin zijn domein zit. In een tenant wordt het beperkt door de rol van de tenant en telt het mee voor de limieten, en Beheerder betekent beheerder van die tenant.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts toward its limits, and Administrator means administrator of that tenant.": "Een account kan in de tenant zitten waarin zijn domein zit. In een tenant wordt het beperkt door de rol van de tenant en telt het mee voor de limieten, en Beheerder betekent beheerder van die tenant.",
|
||||
"Tenants": "Tenants",
|
||||
"Storage in GB": "Opslag in GB",
|
||||
"Default tenant roles": "Standaardrollen voor tenants",
|
||||
@@ -277,7 +277,7 @@ export const catalog: Catalog = {
|
||||
"Delete tenant…": "Tenant verwijderen…",
|
||||
"Still holds {things}. Move them out first.": "Bevat nog {things}. Verplaats die eerst.",
|
||||
"Delete tenant": "Tenant verwijderen",
|
||||
"Separate organisations on one server, each with its own people, domains and limits.": "Afzonderlijke organisaties op één server, elk met eigen mensen, domeinen en limieten.",
|
||||
"Separate organizations on one server, each with its own people, domains and limits.": "Afzonderlijke organisaties op één server, elk met eigen mensen, domeinen en limieten.",
|
||||
"Tenants are a Stalwart Enterprise feature.": "Tenants zijn een functie van Stalwart Enterprise.",
|
||||
"Search tenants": "Tenants zoeken",
|
||||
"No tenants match": "Geen tenants gevonden",
|
||||
@@ -343,7 +343,7 @@ export const catalog: Catalog = {
|
||||
"The mail server refused this. Your role may not allow it.": "De mailserver heeft dit geweigerd. Uw rol staat het mogelijk niet toe.",
|
||||
"That address is already in use on this server, as an account, a list or an alias.": "Dat adres is op deze server al in gebruik, als account, lijst of alias.",
|
||||
"One of the chosen domain, role or group can't be used for this account.": "Het gekozen domein, de rol of de groep kan niet voor dit account worden gebruikt.",
|
||||
"Your organisation has reached the number of accounts it is allowed.": "Uw organisatie heeft het toegestane aantal accounts bereikt.",
|
||||
"Your organization has reached the number of accounts it is allowed.": "Uw organisatie heeft het toegestane aantal accounts bereikt.",
|
||||
"Something still depends on this, so the server kept it.": "Er hangt nog iets van af, dus de server heeft het behouden.",
|
||||
"This account no longer exists. Someone may have deleted it.": "Dit account bestaat niet meer. Mogelijk heeft iemand het verwijderd.",
|
||||
"The password was not accepted: {reason}": "Het wachtwoord is niet geaccepteerd: {reason}",
|
||||
@@ -420,7 +420,7 @@ export const catalog: Catalog = {
|
||||
"Turn off": "Uitschakelen",
|
||||
"Clear": "Legen",
|
||||
"Clear selection": "Selectie opheffen",
|
||||
"Clear custom colour": "Eigen kleur wissen",
|
||||
"Clear custom color": "Eigen kleur wissen",
|
||||
"Select": "Selecteren",
|
||||
"Select all": "Alles selecteren",
|
||||
"Unsubscribe": "Afmelden",
|
||||
@@ -613,7 +613,7 @@ export const catalog: Catalog = {
|
||||
"Maybe": "Misschien",
|
||||
"Confirmed": "Bevestigd",
|
||||
"Tentative": "Onder voorbehoud",
|
||||
"Cancelled": "Geannuleerd",
|
||||
"Canceled": "Geannuleerd",
|
||||
"organizer": "organisator",
|
||||
"Organizer: {name}": "Organisator: {name}",
|
||||
"Free": "Vrij",
|
||||
@@ -628,7 +628,7 @@ export const catalog: Catalog = {
|
||||
"Working hours": "Werktijden",
|
||||
"Working hours start": "Werktijd begint",
|
||||
"Working hours end": "Werktijd eindigt",
|
||||
"Colour categories": "Kleurcategorieën",
|
||||
"Color categories": "Kleurcategorieën",
|
||||
"Category": "Categorie",
|
||||
"No category": "Geen categorie",
|
||||
"New category": "Nieuwe categorie",
|
||||
@@ -636,7 +636,7 @@ export const catalog: Catalog = {
|
||||
"Manage categories…": "Categorieën beheren…",
|
||||
"Use category color": "Kleur van de categorie gebruiken",
|
||||
"Use calendar color": "Kleur van de agenda gebruiken",
|
||||
"Use the default colour": "Standaardkleur gebruiken",
|
||||
"Use the default color": "Standaardkleur gebruiken",
|
||||
"+{n} more": "+{n} meer",
|
||||
|
||||
"Contacts": "Contacten",
|
||||
@@ -796,7 +796,6 @@ export const catalog: Catalog = {
|
||||
"Theme": "Thema",
|
||||
"Accent color": "Accentkleur",
|
||||
"Color": "Kleur",
|
||||
"Colour": "Kleur",
|
||||
"Text color": "Tekstkleur",
|
||||
"Density & text": "Dichtheid en tekst",
|
||||
"Display density": "Weergavedichtheid",
|
||||
@@ -814,7 +813,7 @@ export const catalog: Catalog = {
|
||||
"Collapse sidebar to icons": "Zijbalk inklappen tot pictogrammen",
|
||||
"Apply the theme to messages too": "Thema ook op berichten toepassen",
|
||||
"Apply it even to mail that styles itself": "Pas dit ook toe op e-mail met eigen vormgeving",
|
||||
"Most marketing and receipt mail sets a colour somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colours: backgrounds they laid the message on are dropped, while buttons and coloured banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Bijna alle reclame- en bonmail zet ergens een kleur, waardoor de instelling hierboven vrijwel alles op een witte kaart laat staan. Met deze optie wordt het thema over de kleuren van de afzender heen gelegd: achtergronden waarop het bericht is geplaatst vervallen, terwijl knoppen en gekleurde banners blijven staan zodat hun tekst leesbaar blijft. Sommige berichten overleven dat niet ongeschonden, en daarom is dit een aparte instelling.",
|
||||
"Most marketing and receipt mail sets a color somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colors: backgrounds they laid the message on are dropped, while buttons and colored banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Bijna alle reclame- en bonmail zet ergens een kleur, waardoor de instelling hierboven vrijwel alles op een witte kaart laat staan. Met deze optie wordt het thema over de kleuren van de afzender heen gelegd: achtergronden waarop het bericht is geplaatst vervallen, terwijl knoppen en gekleurde banners blijven staan zodat hun tekst leesbaar blijft. Sommige berichten overleven dat niet ongeschonden, en daarom is dit een aparte instelling.",
|
||||
"Swiping": "Vegen",
|
||||
"Swipe left": "Naar links vegen",
|
||||
"Swipe right": "Naar rechts vegen",
|
||||
@@ -1125,7 +1124,7 @@ export const catalog: Catalog = {
|
||||
"Each identity is a sender address with its own name, Reply-To and signature. The default identity is preselected when you compose; set a Reply-To when replies should go somewhere other than the From address.": "Elke identiteit is een afzenderadres met een eigen naam, antwoordadres en handtekening. De standaardidentiteit is voorgeselecteerd bij het opstellen; stel een antwoordadres in wanneer antwoorden ergens anders heen moeten dan naar het afzenderadres.",
|
||||
"This signature is larger than the server's {limit}-byte limit. ihasmail will keep the full version in your Files and store a short text fallback on the server — other mail clients will see the plain-text version.": "Deze handtekening is groter dan de limiet van {limit} bytes van de server. ihasmail bewaart de volledige versie in uw Bestanden en zet een korte tekstversie op de server — andere e-mailprogramma's zien de platte-tekstversie.",
|
||||
"Outlook-style categories you can assign to events from the right-click menu or the event editor. The category name is stored on the event, so it syncs to other clients.": "Categorieën in Outlook-stijl die u via het rechtsklikmenu of de afsprakeneditor aan afspraken kunt toewijzen. De categorienaam wordt in de afspraak opgeslagen en synchroniseert dus met andere clients.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colours of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "Platte-tekstberichten volgen het thema al. Met deze optie doen HTML-berichten zonder eigen kleuren dat ook, in plaats van op een witte achtergrond te staan. Berichten met een eigen vormgeving blijven precies zoals de afzender ze heeft ontworpen.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colors of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "Platte-tekstberichten volgen het thema al. Met deze optie doen HTML-berichten zonder eigen kleuren dat ook, in plaats van op een witte achtergrond te staan. Berichten met een eigen vormgeving blijven precies zoals de afzender ze heeft ontworpen.",
|
||||
"This is separate from {setting} in General, which decides how dates, times and numbers are written. You can read an English interface with German dates, or the other way round.": "Dit staat los van {setting} onder Algemeen, waar wordt bepaald hoe datums, tijden en getallen worden geschreven. U kunt een Engelse interface met Nederlandse datums lezen, of andersom.",
|
||||
"On a touchscreen, drag a message sideways to act on it. Each direction can do one thing, or nothing. These follow your account, so a phone and a tablet agree; a mouse ignores them and keeps dragging messages into folders instead.": "Sleep op een aanraakscherm een bericht opzij om er iets mee te doen. Elke richting kan één ding doen, of niets. Deze instelling volgt uw account, zodat telefoon en tablet overeenkomen; met een muis wordt ze genegeerd en blijft slepen naar mappen werken.",
|
||||
"This screen has no touchscreen, so nothing here changes what it does. Your phone or tablet will pick these up.": "Dit scherm heeft geen aanraakscherm, dus hier verandert niets. Uw telefoon of tablet neemt deze instellingen over.",
|
||||
@@ -1186,12 +1185,12 @@ export const catalog: Catalog = {
|
||||
"Applying filter to existing messages…": "Filter wordt toegepast op bestaande berichten…",
|
||||
"Attachments are still uploading": "Bijlagen worden nog geüpload",
|
||||
"Calendar saved": "Agenda opgeslagen",
|
||||
"Categorised as {name}": "Categorie ‘{name}’ toegekend",
|
||||
"Categorized as {name}": "Categorie ‘{name}’ toegekend",
|
||||
"Category cleared": "Categorie verwijderd",
|
||||
"Change this event?": "Deze afspraak wijzigen?",
|
||||
"Choose a calendar": "Kies een agenda",
|
||||
"Choose an address book": "Kies een adresboek",
|
||||
"Colour updated": "Kleur gewijzigd",
|
||||
"Color updated": "Kleur gewijzigd",
|
||||
"Contact created": "Contact aangemaakt",
|
||||
"Contact deleted": "Contact verwijderd",
|
||||
"Contact saved": "Contact opgeslagen",
|
||||
@@ -1215,7 +1214,7 @@ export const catalog: Catalog = {
|
||||
"Could not update: {error}": "Bijwerken mislukt: {error}",
|
||||
"Create": "Aanmaken",
|
||||
"Create an address book first": "Maak eerst een adresboek aan",
|
||||
"Custom colour removed": "Eigen kleur verwijderd",
|
||||
"Custom color removed": "Eigen kleur verwijderd",
|
||||
"Deactivate": "Deactiveren",
|
||||
"Delete failed: {error}": "Verwijderen mislukt: {error}",
|
||||
"Delete forever": "Definitief verwijderen",
|
||||
@@ -1302,7 +1301,7 @@ export const catalog: Catalog = {
|
||||
"Script saved": "Script opgeslagen",
|
||||
"Send (Ctrl+Enter)": "Verzenden (Ctrl+Enter)",
|
||||
"Send anyway": "Toch verzenden",
|
||||
"Send cancelled — the message is back in Drafts": "Verzenden geannuleerd — het bericht staat weer bij Concepten",
|
||||
"Send canceled — the message is back in Drafts": "Verzenden geannuleerd — het bericht staat weer bij Concepten",
|
||||
"Send failed: {error}": "Verzenden mislukt: {error}",
|
||||
"Send invites": "Uitnodigingen verzenden",
|
||||
"Send scheduled for {when}": "Verzenden gepland voor {when}",
|
||||
@@ -1434,7 +1433,7 @@ export const catalog: Catalog = {
|
||||
"Date received": "Ontvangstdatum",
|
||||
"Date sent": "Verzenddatum",
|
||||
"Day view": "Dagweergave",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colours are derived, and every one is checked for contrast. The accent colour below still applies over any of them.": "Paletten die naar een ander project zijn genoemd, zijn het werk van dat project en worden gebruikt onder de eigen licentie daarvan; de tinten tussen de gepubliceerde kleuren zijn afgeleid en elk daarvan wordt op contrast gecontroleerd. De accentkleur hieronder geldt nog steeds over elk ervan.",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colors are derived, and every one is checked for contrast. The accent color below still applies over any of them.": "Paletten die naar een ander project zijn genoemd, zijn het werk van dat project en worden gebruikt onder de eigen licentie daarvan; de tinten tussen de gepubliceerde kleuren zijn afgeleid en elk daarvan wordt op contrast gecontroleerd. De accentkleur hieronder geldt nog steeds over elk ervan.",
|
||||
"Earlier": "Eerder",
|
||||
"Every folder": "Elke map",
|
||||
"Everyone addressed will receive this.": "Iedereen die is geadresseerd ontvangt dit.",
|
||||
@@ -1451,7 +1450,7 @@ export const catalog: Catalog = {
|
||||
"Go to Settings": "Ga naar Instellingen",
|
||||
"Go to Starred": "Ga naar Met ster",
|
||||
"Import iCAL file…": "iCAL-bestand importeren…",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colours and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Labels zijn IMAP-trefwoorden die op uw berichten worden opgeslagen, dus elke andere client ziet ze. Namen, kleuren en nesting zijn van ihasmail zelf en volgen uw account. Nesting is alleen weergave: er wordt niets in het postvak herschreven.",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colors and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Labels zijn IMAP-trefwoorden die op uw berichten worden opgeslagen, dus elke andere client ziet ze. Namen, kleuren en nesting zijn van ihasmail zelf en volgen uw account. Nesting is alleen weergave: er wordt niets in het postvak herschreven.",
|
||||
"Largest first": "Grootste eerst",
|
||||
"Later": "Later",
|
||||
"Light or dark": "Licht of donker",
|
||||
@@ -1499,7 +1498,7 @@ export const catalog: Catalog = {
|
||||
"Saved": "Opgeslagen",
|
||||
"Select all {n} in {folder}": "Alle {n} in {folder} selecteren",
|
||||
"Send message": "Bericht verzenden",
|
||||
"Send outside your organisation?": "Buiten uw organisatie verzenden?",
|
||||
"Send outside your organization?": "Buiten uw organisatie verzenden?",
|
||||
"Send to {count} people?": "Naar {count} personen verzenden?",
|
||||
"Show birthdays from your contacts": "Verjaardagen van uw contacten tonen",
|
||||
"Show in the sidebar": "In de zijbalk tonen",
|
||||
@@ -1542,7 +1541,7 @@ export const catalog: Catalog = {
|
||||
"A to Z": "A tot Z",
|
||||
"It reads {shown} but goes to {actual}.": "Er staat {shown}, maar de link gaat naar {actual}.",
|
||||
"The full address is {href}.": "Het volledige adres is {href}.",
|
||||
"This message came from {domain}, which is outside your organisation.": "Dit bericht komt van {domain}, buiten uw organisatie.",
|
||||
"This message came from {domain}, which is outside your organization.": "Dit bericht komt van {domain}, buiten uw organisatie.",
|
||||
"Unsaved changes": "Niet-opgeslagen wijzigingen",
|
||||
"View as": "Weergeven als",
|
||||
"Warnings": "Waarschuwingen",
|
||||
|
||||
+21
-22
@@ -119,7 +119,7 @@ export const catalog: Catalog = {
|
||||
"This account signs in through an external directory, so its password can't be set here.": "Esta conta entra por meio de um diretório externo, então a senha dela não pode ser definida aqui.",
|
||||
"The server's license allows no more accounts.": "A licença do servidor não permite mais contas.",
|
||||
"That domain name is already in use on this server, as a domain or another domain's other name.": "Esse nome de domínio já está em uso neste servidor, como domínio ou como outro nome de outro domínio.",
|
||||
"Your organisation has reached the number of domains it is allowed.": "Sua organização atingiu o número de domínios permitido.",
|
||||
"Your organization has reached the number of domains it is allowed.": "Sua organização atingiu o número de domínios permitido.",
|
||||
"That is more than the mail server accepts in one change.": "Isso é mais do que o servidor de e-mail aceita em uma única alteração.",
|
||||
"The mail server rejected one of the values. Check what you entered and try again.": "O servidor de e-mail recusou um dos valores. Confira o que você digitou e tente novamente.",
|
||||
"The mail server refused the change ({code}).": "O servidor de e-mail recusou a alteração ({code}).",
|
||||
@@ -174,7 +174,7 @@ export const catalog: Catalog = {
|
||||
"Search groups": "Pesquisar grupos",
|
||||
"No groups match": "Nenhum grupo corresponde",
|
||||
"No groups yet": "Nenhum grupo ainda",
|
||||
"Your organisation has reached the number of groups it is allowed.": "Sua organização atingiu o número de grupos permitido.",
|
||||
"Your organization has reached the number of groups it is allowed.": "Sua organização atingiu o número de grupos permitido.",
|
||||
"This group no longer exists. Someone may have deleted it.": "Este grupo não existe mais. Alguém pode tê-lo excluído.",
|
||||
"The server did not say whether the group was created.": "O servidor não informou se o grupo foi criado.",
|
||||
"Mailing lists": "Listas de e-mail",
|
||||
@@ -199,7 +199,7 @@ export const catalog: Catalog = {
|
||||
"Search mailing lists": "Pesquisar listas de e-mail",
|
||||
"No mailing lists match": "Nenhuma lista de e-mail corresponde",
|
||||
"No mailing lists yet": "Nenhuma lista de e-mail ainda",
|
||||
"Your organisation has reached the number of mailing lists it is allowed.": "Sua organização atingiu o número de listas de e-mail permitido.",
|
||||
"Your organization has reached the number of mailing lists it is allowed.": "Sua organização atingiu o número de listas de e-mail permitido.",
|
||||
"This mailing list no longer exists. Someone may have deleted it.": "Esta lista de e-mail não existe mais. Alguém pode tê-la excluído.",
|
||||
"The server did not say whether the list was created.": "O servidor não informou se a lista foi criada.",
|
||||
"Roles": "Funções",
|
||||
@@ -250,13 +250,13 @@ export const catalog: Catalog = {
|
||||
"Open {name}": "Abrir {name}",
|
||||
"Default for {kinds}": "Padrão para {kinds}",
|
||||
"You can't give a role permissions your own role doesn't have.": "Você não pode dar a uma função permissões que a sua própria função não tem.",
|
||||
"Your organisation has reached the number of roles it is allowed.": "Sua organização atingiu o número de funções permitido.",
|
||||
"Your organization has reached the number of roles it is allowed.": "Sua organização atingiu o número de funções permitido.",
|
||||
"This role no longer exists. Someone may have deleted it.": "Esta função não existe mais. Alguém pode tê-la excluído.",
|
||||
"the default roles": "as funções padrão",
|
||||
"The server did not say whether the role was created.": "O servidor não informou se a função foi criada.",
|
||||
"No tenant": "Nenhum locatário",
|
||||
"You can't move your own account into a tenant.": "Você não pode mover sua própria conta para um locatário.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.": "Uma conta pode estar no locatário em que está o seu domínio. Num locatário, ela é limitada pela função do locatário e conta para os limites dele, e Administrador significa administrador desse locatário.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts toward its limits, and Administrator means administrator of that tenant.": "Uma conta pode estar no locatário em que está o seu domínio. Num locatário, ela é limitada pela função do locatário e conta para os limites dele, e Administrador significa administrador desse locatário.",
|
||||
"Tenants": "Locatários",
|
||||
"Storage in GB": "Armazenamento em GB",
|
||||
"Default tenant roles": "Funções padrão de locatário",
|
||||
@@ -284,7 +284,7 @@ export const catalog: Catalog = {
|
||||
"Delete tenant…": "Excluir locatário…",
|
||||
"Still holds {things}. Move them out first.": "Ainda tem {things}. Mova-os primeiro.",
|
||||
"Delete tenant": "Excluir locatário",
|
||||
"Separate organisations on one server, each with its own people, domains and limits.": "Organizações separadas em um mesmo servidor, cada uma com suas próprias pessoas, domínios e limites.",
|
||||
"Separate organizations on one server, each with its own people, domains and limits.": "Organizações separadas em um mesmo servidor, cada uma com suas próprias pessoas, domínios e limites.",
|
||||
"Tenants are a Stalwart Enterprise feature.": "Locatários são um recurso do Stalwart Enterprise.",
|
||||
"Search tenants": "Pesquisar locatários",
|
||||
"No tenants match": "Nenhum locatário corresponde",
|
||||
@@ -350,7 +350,7 @@ export const catalog: Catalog = {
|
||||
"The mail server refused this. Your role may not allow it.": "O servidor de e-mail recusou. Talvez sua função não permita.",
|
||||
"That address is already in use on this server, as an account, a list or an alias.": "Esse endereço já está em uso neste servidor, como conta, lista ou alias.",
|
||||
"One of the chosen domain, role or group can't be used for this account.": "O domínio, a função ou o grupo escolhido não pode ser usado nesta conta.",
|
||||
"Your organisation has reached the number of accounts it is allowed.": "Sua organização atingiu o número de contas permitido.",
|
||||
"Your organization has reached the number of accounts it is allowed.": "Sua organização atingiu o número de contas permitido.",
|
||||
"Something still depends on this, so the server kept it.": "Algo ainda depende disto, então o servidor o manteve.",
|
||||
"This account no longer exists. Someone may have deleted it.": "Esta conta não existe mais. Talvez alguém a tenha excluído.",
|
||||
"The password was not accepted: {reason}": "A senha não foi aceita: {reason}",
|
||||
@@ -427,7 +427,7 @@ export const catalog: Catalog = {
|
||||
"Turn off": "Desativar",
|
||||
"Clear": "Esvaziar",
|
||||
"Clear selection": "Limpar a seleção",
|
||||
"Clear custom colour": "Remover a cor personalizada",
|
||||
"Clear custom color": "Remover a cor personalizada",
|
||||
"Select": "Selecionar",
|
||||
"Select all": "Selecionar tudo",
|
||||
"Unsubscribe": "Cancelar a inscrição",
|
||||
@@ -620,7 +620,7 @@ export const catalog: Catalog = {
|
||||
"Maybe": "Talvez",
|
||||
"Confirmed": "Confirmado",
|
||||
"Tentative": "Provisório",
|
||||
"Cancelled": "Cancelado",
|
||||
"Canceled": "Cancelado",
|
||||
"organizer": "organizador",
|
||||
"Organizer: {name}": "Organizador: {name}",
|
||||
"Free": "Livre",
|
||||
@@ -635,7 +635,7 @@ export const catalog: Catalog = {
|
||||
"Working hours": "Horário de trabalho",
|
||||
"Working hours start": "O horário de trabalho começa",
|
||||
"Working hours end": "O horário de trabalho termina",
|
||||
"Colour categories": "Categorias de cor",
|
||||
"Color categories": "Categorias de cor",
|
||||
"Category": "Categoria",
|
||||
"No category": "Sem categoria",
|
||||
"New category": "Nova categoria",
|
||||
@@ -643,7 +643,7 @@ export const catalog: Catalog = {
|
||||
"Manage categories…": "Gerenciar as categorias…",
|
||||
"Use category color": "Usar a cor da categoria",
|
||||
"Use calendar color": "Usar a cor da agenda",
|
||||
"Use the default colour": "Usar a cor padrão",
|
||||
"Use the default color": "Usar a cor padrão",
|
||||
"+{n} more": "+{n} outros",
|
||||
|
||||
"Contacts": "Contatos",
|
||||
@@ -802,7 +802,6 @@ export const catalog: Catalog = {
|
||||
"Theme": "Tema",
|
||||
"Accent color": "Cor de destaque",
|
||||
"Color": "Cor",
|
||||
"Colour": "Cor",
|
||||
"Text color": "Cor do texto",
|
||||
"Density & text": "Densidade e texto",
|
||||
"Display density": "Densidade de exibição",
|
||||
@@ -820,7 +819,7 @@ export const catalog: Catalog = {
|
||||
"Collapse sidebar to icons": "Recolher a barra lateral em ícones",
|
||||
"Apply the theme to messages too": "Aplicar o tema também às mensagens",
|
||||
"Apply it even to mail that styles itself": "Aplicar mesmo em mensagens com estilo próprio",
|
||||
"Most marketing and receipt mail sets a colour somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colours: backgrounds they laid the message on are dropped, while buttons and coloured banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Quase toda mensagem de marketing ou de recibo define alguma cor, então a opção acima deixa quase todas em um cartão branco. Com isto ativado, o tema é imposto sobre as cores do remetente: os fundos sobre os quais a mensagem foi montada são descartados, enquanto botões e faixas coloridas são preservados para que o texto continue legível. Algumas mensagens não sobrevivem intactas, e por isso esta é uma opção separada.",
|
||||
"Most marketing and receipt mail sets a color somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colors: backgrounds they laid the message on are dropped, while buttons and colored banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Quase toda mensagem de marketing ou de recibo define alguma cor, então a opção acima deixa quase todas em um cartão branco. Com isto ativado, o tema é imposto sobre as cores do remetente: os fundos sobre os quais a mensagem foi montada são descartados, enquanto botões e faixas coloridas são preservados para que o texto continue legível. Algumas mensagens não sobrevivem intactas, e por isso esta é uma opção separada.",
|
||||
"Swiping": "Gestos de deslizar",
|
||||
"Swipe left": "Deslizar para a esquerda",
|
||||
"Swipe right": "Deslizar para a direita",
|
||||
@@ -1132,7 +1131,7 @@ export const catalog: Catalog = {
|
||||
"Each identity is a sender address with its own name, Reply-To and signature. The default identity is preselected when you compose; set a Reply-To when replies should go somewhere other than the From address.": "Cada identidade é um endereço de envio com nome, endereço de resposta e assinatura próprios. A identidade padrão vem pré-selecionada ao escrever; defina um endereço de resposta quando as respostas devam ir para outro lugar que não o remetente.",
|
||||
"This signature is larger than the server's {limit}-byte limit. ihasmail will keep the full version in your Files and store a short text fallback on the server — other mail clients will see the plain-text version.": "Esta assinatura passa do limite de {limit} bytes do servidor. O ihasmail guardará a versão completa nos seus Arquivos e uma versão curta em texto no servidor — os outros clientes verão a versão em texto simples.",
|
||||
"Outlook-style categories you can assign to events from the right-click menu or the event editor. The category name is stored on the event, so it syncs to other clients.": "Categorias no estilo do Outlook que você pode atribuir aos eventos pelo menu do botão direito ou pelo editor de eventos. O nome da categoria fica guardado no evento, então sincroniza com outros clientes.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colours of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "As mensagens em texto simples já seguem o tema. Com esta opção, as mensagens HTML sem cores próprias também seguem, em vez de aparecerem sobre um fundo branco. As mensagens com estilo próprio ficam exatamente como o remetente as desenhou.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colors of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "As mensagens em texto simples já seguem o tema. Com esta opção, as mensagens HTML sem cores próprias também seguem, em vez de aparecerem sobre um fundo branco. As mensagens com estilo próprio ficam exatamente como o remetente as desenhou.",
|
||||
"This is separate from {setting} in General, which decides how dates, times and numbers are written. You can read an English interface with German dates, or the other way round.": "Isto é independente de {setting} em Geral, que define como datas, horas e números são escritos. Você pode ler uma interface em inglês com datas em português, ou o contrário.",
|
||||
"On a touchscreen, drag a message sideways to act on it. Each direction can do one thing, or nothing. These follow your account, so a phone and a tablet agree; a mouse ignores them and keeps dragging messages into folders instead.": "Em uma tela sensível ao toque, arraste uma mensagem para o lado para agir sobre ela. Cada direção pode fazer uma coisa, ou nada. Estas opções seguem sua conta, então celular e tablet ficam iguais; com o mouse elas são ignoradas e o arrastar para pastas continua valendo.",
|
||||
"This screen has no touchscreen, so nothing here changes what it does. Your phone or tablet will pick these up.": "Esta tela não é sensível ao toque, então nada aqui muda o comportamento dela. Seu celular ou tablet vai adotar estas opções.",
|
||||
@@ -1193,12 +1192,12 @@ export const catalog: Catalog = {
|
||||
"Applying filter to existing messages…": "Aplicando o filtro às mensagens existentes…",
|
||||
"Attachments are still uploading": "Os anexos ainda estão sendo enviados",
|
||||
"Calendar saved": "Agenda salva",
|
||||
"Categorised as {name}": "Categoria “{name}” atribuída",
|
||||
"Categorized as {name}": "Categoria “{name}” atribuída",
|
||||
"Category cleared": "Categoria removida",
|
||||
"Change this event?": "Alterar este evento?",
|
||||
"Choose a calendar": "Escolha uma agenda",
|
||||
"Choose an address book": "Escolha um catálogo de endereços",
|
||||
"Colour updated": "Cor atualizada",
|
||||
"Color updated": "Cor atualizada",
|
||||
"Contact created": "Contato criado",
|
||||
"Contact deleted": "Contato excluído",
|
||||
"Contact saved": "Contato salvo",
|
||||
@@ -1222,7 +1221,7 @@ export const catalog: Catalog = {
|
||||
"Could not update: {error}": "Não foi possível atualizar: {error}",
|
||||
"Create": "Criar",
|
||||
"Create an address book first": "Crie um catálogo de endereços primeiro",
|
||||
"Custom colour removed": "Cor personalizada removida",
|
||||
"Custom color removed": "Cor personalizada removida",
|
||||
"Deactivate": "Desativar",
|
||||
"Delete failed: {error}": "Falha ao excluir: {error}",
|
||||
"Delete forever": "Excluir definitivamente",
|
||||
@@ -1309,7 +1308,7 @@ export const catalog: Catalog = {
|
||||
"Script saved": "Script salvo",
|
||||
"Send (Ctrl+Enter)": "Enviar (Ctrl+Enter)",
|
||||
"Send anyway": "Enviar mesmo assim",
|
||||
"Send cancelled — the message is back in Drafts": "Envio cancelado — a mensagem voltou para Rascunhos",
|
||||
"Send canceled — the message is back in Drafts": "Envio cancelado — a mensagem voltou para Rascunhos",
|
||||
"Send failed: {error}": "Falha ao enviar: {error}",
|
||||
"Send invites": "Enviar convites",
|
||||
"Send scheduled for {when}": "Envio agendado para {when}",
|
||||
@@ -1441,7 +1440,7 @@ export const catalog: Catalog = {
|
||||
"Date received": "Data de recebimento",
|
||||
"Date sent": "Data de envio",
|
||||
"Day view": "Visualização de dia",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colours are derived, and every one is checked for contrast. The accent colour below still applies over any of them.": "As paletas que levam o nome de outro projeto são obra desse projeto e são usadas sob a licença dele; os tons entre as cores publicadas são derivados, e cada um é verificado quanto ao contraste. A cor de destaque abaixo continua se aplicando sobre qualquer uma delas.",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colors are derived, and every one is checked for contrast. The accent color below still applies over any of them.": "As paletas que levam o nome de outro projeto são obra desse projeto e são usadas sob a licença dele; os tons entre as cores publicadas são derivados, e cada um é verificado quanto ao contraste. A cor de destaque abaixo continua se aplicando sobre qualquer uma delas.",
|
||||
"Earlier": "Antes",
|
||||
"Every folder": "Todas as pastas",
|
||||
"Everyone addressed will receive this.": "Todos os destinatários receberão isto.",
|
||||
@@ -1458,7 +1457,7 @@ export const catalog: Catalog = {
|
||||
"Go to Settings": "Ir para Configurações",
|
||||
"Go to Starred": "Ir para Favoritos",
|
||||
"Import iCAL file…": "Importar arquivo iCAL…",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colours and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Os marcadores são palavras-chave IMAP armazenadas nas suas mensagens, então todos os outros clientes os veem. Os nomes, as cores e o aninhamento são do próprio ihasmail e acompanham a sua conta. O aninhamento é apenas de exibição: não reescreve nada na caixa postal.",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colors and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Os marcadores são palavras-chave IMAP armazenadas nas suas mensagens, então todos os outros clientes os veem. Os nomes, as cores e o aninhamento são do próprio ihasmail e acompanham a sua conta. O aninhamento é apenas de exibição: não reescreve nada na caixa postal.",
|
||||
"Largest first": "Maiores primeiro",
|
||||
"Later": "Depois",
|
||||
"Light or dark": "Claro ou escuro",
|
||||
@@ -1506,7 +1505,7 @@ export const catalog: Catalog = {
|
||||
"Saved": "Salvo",
|
||||
"Select all {n} in {folder}": "Selecionar todas as {n} em {folder}",
|
||||
"Send message": "Enviar mensagem",
|
||||
"Send outside your organisation?": "Enviar para fora da sua organização?",
|
||||
"Send outside your organization?": "Enviar para fora da sua organização?",
|
||||
"Send to {count} people?": "Enviar para {count} pessoas?",
|
||||
"Show birthdays from your contacts": "Exibir aniversários dos seus contatos",
|
||||
"Show in the sidebar": "Exibir na barra lateral",
|
||||
@@ -1549,7 +1548,7 @@ export const catalog: Catalog = {
|
||||
"A to Z": "De A a Z",
|
||||
"It reads {shown} but goes to {actual}.": "Aparece como {shown}, mas leva para {actual}.",
|
||||
"The full address is {href}.": "O endereço completo é {href}.",
|
||||
"This message came from {domain}, which is outside your organisation.": "Esta mensagem veio de {domain}, que está fora da sua organização.",
|
||||
"This message came from {domain}, which is outside your organization.": "Esta mensagem veio de {domain}, que está fora da sua organização.",
|
||||
"Unsaved changes": "Alterações não salvas",
|
||||
"View as": "Exibir como",
|
||||
"Warnings": "Avisos",
|
||||
|
||||
+21
-22
@@ -118,7 +118,7 @@ export const catalog: Catalog = {
|
||||
"This account signs in through an external directory, so its password can't be set here.": "Эта учётная запись входит через внешний каталог, поэтому её пароль нельзя задать здесь.",
|
||||
"The server's license allows no more accounts.": "Лицензия сервера не допускает новых учётных записей.",
|
||||
"That domain name is already in use on this server, as a domain or another domain's other name.": "Это имя домена уже используется на сервере — как домен или как другое имя другого домена.",
|
||||
"Your organisation has reached the number of domains it is allowed.": "Ваша организация достигла допустимого числа доменов.",
|
||||
"Your organization has reached the number of domains it is allowed.": "Ваша организация достигла допустимого числа доменов.",
|
||||
"That is more than the mail server accepts in one change.": "Это больше, чем почтовый сервер принимает за одно изменение.",
|
||||
"The mail server rejected one of the values. Check what you entered and try again.": "Почтовый сервер отклонил одно из значений. Проверьте введённые данные и попробуйте снова.",
|
||||
"The mail server refused the change ({code}).": "Почтовый сервер отклонил изменение ({code}).",
|
||||
@@ -173,7 +173,7 @@ export const catalog: Catalog = {
|
||||
"Search groups": "Поиск групп",
|
||||
"No groups match": "Нет подходящих групп",
|
||||
"No groups yet": "Групп пока нет",
|
||||
"Your organisation has reached the number of groups it is allowed.": "Ваша организация достигла допустимого числа групп.",
|
||||
"Your organization has reached the number of groups it is allowed.": "Ваша организация достигла допустимого числа групп.",
|
||||
"This group no longer exists. Someone may have deleted it.": "Этой группы больше нет. Возможно, её кто-то удалил.",
|
||||
"The server did not say whether the group was created.": "Сервер не сообщил, создана ли группа.",
|
||||
"Mailing lists": "Списки рассылки",
|
||||
@@ -198,7 +198,7 @@ export const catalog: Catalog = {
|
||||
"Search mailing lists": "Поиск списков рассылки",
|
||||
"No mailing lists match": "Нет подходящих списков рассылки",
|
||||
"No mailing lists yet": "Списков рассылки пока нет",
|
||||
"Your organisation has reached the number of mailing lists it is allowed.": "Ваша организация достигла допустимого числа списков рассылки.",
|
||||
"Your organization has reached the number of mailing lists it is allowed.": "Ваша организация достигла допустимого числа списков рассылки.",
|
||||
"This mailing list no longer exists. Someone may have deleted it.": "Этого списка рассылки больше нет. Возможно, его кто-то удалил.",
|
||||
"The server did not say whether the list was created.": "Сервер не сообщил, создан ли список.",
|
||||
"Roles": "Роли",
|
||||
@@ -249,13 +249,13 @@ export const catalog: Catalog = {
|
||||
"Open {name}": "Открыть {name}",
|
||||
"Default for {kinds}": "По умолчанию: {kinds}",
|
||||
"You can't give a role permissions your own role doesn't have.": "Нельзя дать роли разрешения, которых нет у вашей собственной роли.",
|
||||
"Your organisation has reached the number of roles it is allowed.": "Ваша организация достигла допустимого числа ролей.",
|
||||
"Your organization has reached the number of roles it is allowed.": "Ваша организация достигла допустимого числа ролей.",
|
||||
"This role no longer exists. Someone may have deleted it.": "Этой роли больше нет. Возможно, её кто-то удалил.",
|
||||
"the default roles": "настройки ролей по умолчанию",
|
||||
"The server did not say whether the role was created.": "Сервер не сообщил, создана ли роль.",
|
||||
"No tenant": "Без арендатора",
|
||||
"You can't move your own account into a tenant.": "Нельзя переместить собственную учётную запись в арендатора.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.": "Учётная запись может быть в том арендаторе, в котором её домен. В арендаторе она ограничена его ролью и учитывается в его лимитах, а «Администратор» означает администратора этого арендатора.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts toward its limits, and Administrator means administrator of that tenant.": "Учётная запись может быть в том арендаторе, в котором её домен. В арендаторе она ограничена его ролью и учитывается в его лимитах, а «Администратор» означает администратора этого арендатора.",
|
||||
"Tenants": "Арендаторы",
|
||||
"Storage in GB": "Хранилище, ГБ",
|
||||
"Default tenant roles": "Роли арендатора по умолчанию",
|
||||
@@ -283,7 +283,7 @@ export const catalog: Catalog = {
|
||||
"Delete tenant…": "Удалить арендатора…",
|
||||
"Still holds {things}. Move them out first.": "Ещё содержит: {things}. Сначала перенесите их.",
|
||||
"Delete tenant": "Удалить арендатора",
|
||||
"Separate organisations on one server, each with its own people, domains and limits.": "Отдельные организации на одном сервере, у каждой свои люди, домены и лимиты.",
|
||||
"Separate organizations on one server, each with its own people, domains and limits.": "Отдельные организации на одном сервере, у каждой свои люди, домены и лимиты.",
|
||||
"Tenants are a Stalwart Enterprise feature.": "Арендаторы — функция Stalwart Enterprise.",
|
||||
"Search tenants": "Поиск арендаторов",
|
||||
"No tenants match": "Нет подходящих арендаторов",
|
||||
@@ -349,7 +349,7 @@ export const catalog: Catalog = {
|
||||
"The mail server refused this. Your role may not allow it.": "Почтовый сервер отклонил это действие. Возможно, ваша роль его не допускает.",
|
||||
"That address is already in use on this server, as an account, a list or an alias.": "Этот адрес уже используется на сервере — учётной записью, списком или псевдонимом.",
|
||||
"One of the chosen domain, role or group can't be used for this account.": "Выбранный домен, роль или группу нельзя использовать для этой учётной записи.",
|
||||
"Your organisation has reached the number of accounts it is allowed.": "Ваша организация достигла допустимого числа учётных записей.",
|
||||
"Your organization has reached the number of accounts it is allowed.": "Ваша организация достигла допустимого числа учётных записей.",
|
||||
"Something still depends on this, so the server kept it.": "От этого ещё что-то зависит, поэтому сервер это сохранил.",
|
||||
"This account no longer exists. Someone may have deleted it.": "Этой учётной записи больше нет. Возможно, её кто-то удалил.",
|
||||
"The password was not accepted: {reason}": "Пароль не принят: {reason}",
|
||||
@@ -426,7 +426,7 @@ export const catalog: Catalog = {
|
||||
"Turn off": "Отключить",
|
||||
"Clear": "Очистить",
|
||||
"Clear selection": "Снять выделение",
|
||||
"Clear custom colour": "Убрать свой цвет",
|
||||
"Clear custom color": "Убрать свой цвет",
|
||||
"Select": "Выбрать",
|
||||
"Select all": "Выбрать всё",
|
||||
"Unsubscribe": "Отписаться",
|
||||
@@ -619,7 +619,7 @@ export const catalog: Catalog = {
|
||||
"Maybe": "Возможно",
|
||||
"Confirmed": "Подтверждено",
|
||||
"Tentative": "Под вопросом",
|
||||
"Cancelled": "Отменено",
|
||||
"Canceled": "Отменено",
|
||||
"organizer": "организатор",
|
||||
"Organizer: {name}": "Организатор: {name}",
|
||||
"Free": "Свободен",
|
||||
@@ -634,7 +634,7 @@ export const catalog: Catalog = {
|
||||
"Working hours": "Рабочее время",
|
||||
"Working hours start": "Начало рабочего времени",
|
||||
"Working hours end": "Конец рабочего времени",
|
||||
"Colour categories": "Цветовые категории",
|
||||
"Color categories": "Цветовые категории",
|
||||
"Category": "Категория",
|
||||
"No category": "Без категории",
|
||||
"New category": "Новая категория",
|
||||
@@ -642,7 +642,7 @@ export const catalog: Catalog = {
|
||||
"Manage categories…": "Управление категориями…",
|
||||
"Use category color": "Цвет категории",
|
||||
"Use calendar color": "Цвет календаря",
|
||||
"Use the default colour": "Цвет по умолчанию",
|
||||
"Use the default color": "Цвет по умолчанию",
|
||||
"+{n} more": "+{n}",
|
||||
|
||||
"Contacts": "Контакты",
|
||||
@@ -802,7 +802,6 @@ export const catalog: Catalog = {
|
||||
"Theme": "Тема",
|
||||
"Accent color": "Акцентный цвет",
|
||||
"Color": "Цвет",
|
||||
"Colour": "Цвет",
|
||||
"Text color": "Цвет текста",
|
||||
"Density & text": "Плотность и текст",
|
||||
"Display density": "Плотность отображения",
|
||||
@@ -820,7 +819,7 @@ export const catalog: Catalog = {
|
||||
"Collapse sidebar to icons": "Свернуть боковую панель до значков",
|
||||
"Apply the theme to messages too": "Применять тему и к письмам",
|
||||
"Apply it even to mail that styles itself": "Применять даже к письмам с собственным оформлением",
|
||||
"Most marketing and receipt mail sets a colour somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colours: backgrounds they laid the message on are dropped, while buttons and coloured banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Почти в каждом рекламном письме и чеке где-нибудь задан цвет, поэтому настройка выше оставляет почти все такие письма на белой карточке. С этой настройкой тема накладывается поверх цветов отправителя: фон, на котором свёрстано письмо, убирается, а кнопки и цветные плашки сохраняются, чтобы текст на них оставался читаемым. Некоторые письма это не переживут без потерь — поэтому настройка отдельная.",
|
||||
"Most marketing and receipt mail sets a color somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colors: backgrounds they laid the message on are dropped, while buttons and colored banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Почти в каждом рекламном письме и чеке где-нибудь задан цвет, поэтому настройка выше оставляет почти все такие письма на белой карточке. С этой настройкой тема накладывается поверх цветов отправителя: фон, на котором свёрстано письмо, убирается, а кнопки и цветные плашки сохраняются, чтобы текст на них оставался читаемым. Некоторые письма это не переживут без потерь — поэтому настройка отдельная.",
|
||||
"Swiping": "Жесты смахивания",
|
||||
"Swipe left": "Смахнуть влево",
|
||||
"Swipe right": "Смахнуть вправо",
|
||||
@@ -1131,7 +1130,7 @@ export const catalog: Catalog = {
|
||||
"Each identity is a sender address with its own name, Reply-To and signature. The default identity is preselected when you compose; set a Reply-To when replies should go somewhere other than the From address.": "Каждый профиль — это адрес отправителя со своим именем, обратным адресом и подписью. Основной профиль подставляется при написании письма; укажите обратный адрес, если ответы должны приходить не на адрес отправителя.",
|
||||
"This signature is larger than the server's {limit}-byte limit. ihasmail will keep the full version in your Files and store a short text fallback on the server — other mail clients will see the plain-text version.": "Эта подпись больше серверного предела в {limit} байт. ihasmail сохранит полную версию в ваших Файлах, а на сервере оставит короткий текстовый вариант — другие почтовые клиенты увидят именно его.",
|
||||
"Outlook-style categories you can assign to events from the right-click menu or the event editor. The category name is stored on the event, so it syncs to other clients.": "Категории в стиле Outlook, которые можно присваивать событиям через контекстное меню или редактор события. Название категории хранится в самом событии и синхронизируется с другими клиентами.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colours of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "Письма в обычном тексте уже следуют теме. С этой настройкой ей следуют и HTML-письма без собственных цветов, а не показываются на белом фоне. Письма с собственным оформлением остаются ровно такими, какими их задумал отправитель.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colors of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "Письма в обычном тексте уже следуют теме. С этой настройкой ей следуют и HTML-письма без собственных цветов, а не показываются на белом фоне. Письма с собственным оформлением остаются ровно такими, какими их задумал отправитель.",
|
||||
"This is separate from {setting} in General, which decides how dates, times and numbers are written. You can read an English interface with German dates, or the other way round.": "Это не то же самое, что {setting} в разделе «Общие», где задаётся, как пишутся даты, время и числа. Можно читать английский интерфейс с русскими датами — или наоборот.",
|
||||
"On a touchscreen, drag a message sideways to act on it. Each direction can do one thing, or nothing. These follow your account, so a phone and a tablet agree; a mouse ignores them and keeps dragging messages into folders instead.": "На сенсорном экране смахните письмо в сторону, чтобы выполнить над ним действие. Каждое направление может делать что-то одно — или ничего. Настройка привязана к учётной записи, поэтому телефон и планшет ведут себя одинаково; мышь её игнорирует, и письма по-прежнему перетаскиваются в папки.",
|
||||
"This screen has no touchscreen, so nothing here changes what it does. Your phone or tablet will pick these up.": "У этого экрана нет сенсорного ввода, поэтому здесь ничего не изменится. Настройку подхватят телефон или планшет.",
|
||||
@@ -1192,12 +1191,12 @@ export const catalog: Catalog = {
|
||||
"Applying filter to existing messages…": "Фильтр применяется к существующим письмам…",
|
||||
"Attachments are still uploading": "Вложения ещё загружаются",
|
||||
"Calendar saved": "Календарь сохранён",
|
||||
"Categorised as {name}": "Назначена категория «{name}»",
|
||||
"Categorized as {name}": "Назначена категория «{name}»",
|
||||
"Category cleared": "Категория снята",
|
||||
"Change this event?": "Изменить это событие?",
|
||||
"Choose a calendar": "Выберите календарь",
|
||||
"Choose an address book": "Выберите адресную книгу",
|
||||
"Colour updated": "Цвет изменён",
|
||||
"Color updated": "Цвет изменён",
|
||||
"Contact created": "Контакт создан",
|
||||
"Contact deleted": "Контакт удалён",
|
||||
"Contact saved": "Контакт сохранён",
|
||||
@@ -1221,7 +1220,7 @@ export const catalog: Catalog = {
|
||||
"Could not update: {error}": "Не удалось обновить: {error}",
|
||||
"Create": "Создать",
|
||||
"Create an address book first": "Сначала создайте адресную книгу",
|
||||
"Custom colour removed": "Свой цвет снят",
|
||||
"Custom color removed": "Свой цвет снят",
|
||||
"Deactivate": "Выключить",
|
||||
"Delete failed: {error}": "Не удалось удалить: {error}",
|
||||
"Delete forever": "Удалить безвозвратно",
|
||||
@@ -1308,7 +1307,7 @@ export const catalog: Catalog = {
|
||||
"Script saved": "Сценарий сохранён",
|
||||
"Send (Ctrl+Enter)": "Отправить (Ctrl+Enter)",
|
||||
"Send anyway": "Всё равно отправить",
|
||||
"Send cancelled — the message is back in Drafts": "Отправка отменена — письмо вернулось в черновики",
|
||||
"Send canceled — the message is back in Drafts": "Отправка отменена — письмо вернулось в черновики",
|
||||
"Send failed: {error}": "Не удалось отправить: {error}",
|
||||
"Send invites": "Отправить приглашения",
|
||||
"Send scheduled for {when}": "Отправка запланирована на {when}",
|
||||
@@ -1440,7 +1439,7 @@ export const catalog: Catalog = {
|
||||
"Date received": "Дата получения",
|
||||
"Date sent": "Дата отправки",
|
||||
"Day view": "День",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colours are derived, and every one is checked for contrast. The accent colour below still applies over any of them.": "Палитры, названные в честь другого проекта, созданы этим проектом и используются по его собственной лицензии; оттенки между опубликованными цветами выводятся расчётом, и каждый из них проверяется на контраст. Акцентный цвет ниже по-прежнему применяется поверх любой из них.",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colors are derived, and every one is checked for contrast. The accent color below still applies over any of them.": "Палитры, названные в честь другого проекта, созданы этим проектом и используются по его собственной лицензии; оттенки между опубликованными цветами выводятся расчётом, и каждый из них проверяется на контраст. Акцентный цвет ниже по-прежнему применяется поверх любой из них.",
|
||||
"Earlier": "Раньше",
|
||||
"Every folder": "Все папки",
|
||||
"Everyone addressed will receive this.": "Это получат все указанные адресаты.",
|
||||
@@ -1457,7 +1456,7 @@ export const catalog: Catalog = {
|
||||
"Go to Settings": "Перейти к настройкам",
|
||||
"Go to Starred": "Перейти к отмеченным",
|
||||
"Import iCAL file…": "Импортировать файл iCAL…",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colours and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Ярлыки — это ключевые слова IMAP, которые хранятся на письмах, поэтому их видит любой другой клиент. Названия, цвета и вложенность принадлежат самому ihasmail и следуют за вашей учётной записью. Вложенность влияет только на отображение и ничего не переписывает в почтовом ящике.",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colors and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Ярлыки — это ключевые слова IMAP, которые хранятся на письмах, поэтому их видит любой другой клиент. Названия, цвета и вложенность принадлежат самому ihasmail и следуют за вашей учётной записью. Вложенность влияет только на отображение и ничего не переписывает в почтовом ящике.",
|
||||
"Largest first": "Сначала большие",
|
||||
"Later": "Позже",
|
||||
"Light or dark": "Светлая или тёмная",
|
||||
@@ -1505,7 +1504,7 @@ export const catalog: Catalog = {
|
||||
"Saved": "Сохранено",
|
||||
"Select all {n} in {folder}": "Выбрать все {n} в папке {folder}",
|
||||
"Send message": "Отправить письмо",
|
||||
"Send outside your organisation?": "Отправить за пределы организации?",
|
||||
"Send outside your organization?": "Отправить за пределы организации?",
|
||||
"Send to {count} people?": "Отправить {count} получателям?",
|
||||
"Show birthdays from your contacts": "Показывать дни рождения из контактов",
|
||||
"Show in the sidebar": "Показывать на боковой панели",
|
||||
@@ -1548,7 +1547,7 @@ export const catalog: Catalog = {
|
||||
"A to Z": "От А до Я",
|
||||
"It reads {shown} but goes to {actual}.": "Показано {shown}, но ссылка ведёт на {actual}.",
|
||||
"The full address is {href}.": "Полный адрес: {href}.",
|
||||
"This message came from {domain}, which is outside your organisation.": "Это письмо пришло с {domain} — за пределами вашей организации.",
|
||||
"This message came from {domain}, which is outside your organization.": "Это письмо пришло с {domain} — за пределами вашей организации.",
|
||||
"Unsaved changes": "Несохранённые изменения",
|
||||
"View as": "Показывать как",
|
||||
"Warnings": "Предупреждения",
|
||||
|
||||
+21
-22
@@ -112,7 +112,7 @@ export const catalog: Catalog = {
|
||||
"This account signs in through an external directory, so its password can't be set here.": "Цей обліковий запис входить через зовнішній каталог, тому його пароль не можна задати тут.",
|
||||
"The server's license allows no more accounts.": "Ліцензія сервера не дозволяє нових облікових записів.",
|
||||
"That domain name is already in use on this server, as a domain or another domain's other name.": "Це ім'я домену вже використовується на сервері — як домен або як інше ім'я іншого домену.",
|
||||
"Your organisation has reached the number of domains it is allowed.": "Ваша організація досягла дозволеної кількості доменів.",
|
||||
"Your organization has reached the number of domains it is allowed.": "Ваша організація досягла дозволеної кількості доменів.",
|
||||
"That is more than the mail server accepts in one change.": "Це більше, ніж поштовий сервер приймає за одну зміну.",
|
||||
"The mail server rejected one of the values. Check what you entered and try again.": "Поштовий сервер відхилив одне зі значень. Перевірте введені дані й спробуйте ще раз.",
|
||||
"The mail server refused the change ({code}).": "Поштовий сервер відхилив зміну ({code}).",
|
||||
@@ -167,7 +167,7 @@ export const catalog: Catalog = {
|
||||
"Search groups": "Пошук груп",
|
||||
"No groups match": "Немає відповідних груп",
|
||||
"No groups yet": "Груп поки немає",
|
||||
"Your organisation has reached the number of groups it is allowed.": "Ваша організація досягла дозволеної кількості груп.",
|
||||
"Your organization has reached the number of groups it is allowed.": "Ваша організація досягла дозволеної кількості груп.",
|
||||
"This group no longer exists. Someone may have deleted it.": "Цієї групи більше немає. Можливо, її хтось видалив.",
|
||||
"The server did not say whether the group was created.": "Сервер не повідомив, чи створено групу.",
|
||||
"Mailing lists": "Списки розсилки",
|
||||
@@ -192,7 +192,7 @@ export const catalog: Catalog = {
|
||||
"Search mailing lists": "Пошук списків розсилки",
|
||||
"No mailing lists match": "Немає відповідних списків розсилки",
|
||||
"No mailing lists yet": "Списків розсилки поки немає",
|
||||
"Your organisation has reached the number of mailing lists it is allowed.": "Ваша організація досягла дозволеної кількості списків розсилки.",
|
||||
"Your organization has reached the number of mailing lists it is allowed.": "Ваша організація досягла дозволеної кількості списків розсилки.",
|
||||
"This mailing list no longer exists. Someone may have deleted it.": "Цього списку розсилки більше немає. Можливо, його хтось видалив.",
|
||||
"The server did not say whether the list was created.": "Сервер не повідомив, чи створено список.",
|
||||
"Roles": "Ролі",
|
||||
@@ -243,13 +243,13 @@ export const catalog: Catalog = {
|
||||
"Open {name}": "Відкрити {name}",
|
||||
"Default for {kinds}": "За замовчуванням: {kinds}",
|
||||
"You can't give a role permissions your own role doesn't have.": "Не можна надати ролі дозволи, яких немає у вашої власної ролі.",
|
||||
"Your organisation has reached the number of roles it is allowed.": "Ваша організація досягла дозволеної кількості ролей.",
|
||||
"Your organization has reached the number of roles it is allowed.": "Ваша організація досягла дозволеної кількості ролей.",
|
||||
"This role no longer exists. Someone may have deleted it.": "Цієї ролі більше немає. Можливо, її хтось видалив.",
|
||||
"the default roles": "налаштування ролей за замовчуванням",
|
||||
"The server did not say whether the role was created.": "Сервер не повідомив, чи створено роль.",
|
||||
"No tenant": "Без орендаря",
|
||||
"You can't move your own account into a tenant.": "Не можна перемістити власний обліковий запис до орендаря.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.": "Обліковий запис може бути в тому орендарі, у якому його домен. В орендарі він обмежений роллю орендаря й зараховується до його лімітів, а «Адміністратор» означає адміністратора цього орендаря.",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts toward its limits, and Administrator means administrator of that tenant.": "Обліковий запис може бути в тому орендарі, у якому його домен. В орендарі він обмежений роллю орендаря й зараховується до його лімітів, а «Адміністратор» означає адміністратора цього орендаря.",
|
||||
"Tenants": "Орендарі",
|
||||
"Storage in GB": "Сховище, ГБ",
|
||||
"Default tenant roles": "Ролі орендаря за замовчуванням",
|
||||
@@ -277,7 +277,7 @@ export const catalog: Catalog = {
|
||||
"Delete tenant…": "Видалити орендаря…",
|
||||
"Still holds {things}. Move them out first.": "Ще містить: {things}. Спершу перенесіть їх.",
|
||||
"Delete tenant": "Видалити орендаря",
|
||||
"Separate organisations on one server, each with its own people, domains and limits.": "Окремі організації на одному сервері, кожна зі своїми людьми, доменами й лімітами.",
|
||||
"Separate organizations on one server, each with its own people, domains and limits.": "Окремі організації на одному сервері, кожна зі своїми людьми, доменами й лімітами.",
|
||||
"Tenants are a Stalwart Enterprise feature.": "Орендарі — функція Stalwart Enterprise.",
|
||||
"Search tenants": "Пошук орендарів",
|
||||
"No tenants match": "Немає відповідних орендарів",
|
||||
@@ -343,7 +343,7 @@ export const catalog: Catalog = {
|
||||
"The mail server refused this. Your role may not allow it.": "Поштовий сервер відхилив цю дію. Можливо, ваша роль її не дозволяє.",
|
||||
"That address is already in use on this server, as an account, a list or an alias.": "Ця адреса вже використовується на сервері — обліковим записом, списком або псевдонімом.",
|
||||
"One of the chosen domain, role or group can't be used for this account.": "Вибраний домен, роль або групу не можна використати для цього облікового запису.",
|
||||
"Your organisation has reached the number of accounts it is allowed.": "Ваша організація досягла дозволеної кількості облікових записів.",
|
||||
"Your organization has reached the number of accounts it is allowed.": "Ваша організація досягла дозволеної кількості облікових записів.",
|
||||
"Something still depends on this, so the server kept it.": "Від цього ще щось залежить, тому сервер це зберіг.",
|
||||
"This account no longer exists. Someone may have deleted it.": "Цього облікового запису більше немає. Можливо, його хтось видалив.",
|
||||
"The password was not accepted: {reason}": "Пароль не прийнято: {reason}",
|
||||
@@ -420,7 +420,7 @@ export const catalog: Catalog = {
|
||||
"Turn off": "Вимкнути",
|
||||
"Clear": "Очистити",
|
||||
"Clear selection": "Зняти позначення",
|
||||
"Clear custom colour": "Прибрати власний колір",
|
||||
"Clear custom color": "Прибрати власний колір",
|
||||
"Select": "Вибрати",
|
||||
"Select all": "Вибрати все",
|
||||
"Unsubscribe": "Відписатися",
|
||||
@@ -613,7 +613,7 @@ export const catalog: Catalog = {
|
||||
"Maybe": "Можливо",
|
||||
"Confirmed": "Підтверджено",
|
||||
"Tentative": "Під питанням",
|
||||
"Cancelled": "Скасовано",
|
||||
"Canceled": "Скасовано",
|
||||
"organizer": "організатор",
|
||||
"Organizer: {name}": "Організатор: {name}",
|
||||
"Free": "Вільний",
|
||||
@@ -628,7 +628,7 @@ export const catalog: Catalog = {
|
||||
"Working hours": "Робочий час",
|
||||
"Working hours start": "Початок робочого часу",
|
||||
"Working hours end": "Кінець робочого часу",
|
||||
"Colour categories": "Кольорові категорії",
|
||||
"Color categories": "Кольорові категорії",
|
||||
"Category": "Категорія",
|
||||
"No category": "Без категорії",
|
||||
"New category": "Нова категорія",
|
||||
@@ -636,7 +636,7 @@ export const catalog: Catalog = {
|
||||
"Manage categories…": "Керування категоріями…",
|
||||
"Use category color": "Колір категорії",
|
||||
"Use calendar color": "Колір календаря",
|
||||
"Use the default colour": "Колір за замовчуванням",
|
||||
"Use the default color": "Колір за замовчуванням",
|
||||
"+{n} more": "+{n}",
|
||||
|
||||
"Contacts": "Контакти",
|
||||
@@ -796,7 +796,6 @@ export const catalog: Catalog = {
|
||||
"Theme": "Тема",
|
||||
"Accent color": "Акцентний колір",
|
||||
"Color": "Колір",
|
||||
"Colour": "Колір",
|
||||
"Text color": "Колір тексту",
|
||||
"Density & text": "Щільність і текст",
|
||||
"Display density": "Щільність відображення",
|
||||
@@ -814,7 +813,7 @@ export const catalog: Catalog = {
|
||||
"Collapse sidebar to icons": "Згорнути бічну панель до значків",
|
||||
"Apply the theme to messages too": "Застосовувати тему й до листів",
|
||||
"Apply it even to mail that styles itself": "Застосовувати навіть до листів із власним оформленням",
|
||||
"Most marketing and receipt mail sets a colour somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colours: backgrounds they laid the message on are dropped, while buttons and coloured banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Майже в кожному рекламному листі та чеку десь задано колір, тому налаштування вище залишає майже всі такі листи на білій картці. Із цим налаштуванням тема накладається поверх кольорів відправника: тло, на якому зверстано лист, прибирається, а кнопки та кольорові плашки зберігаються, щоб текст на них залишався читабельним. Деякі листи цього не переживуть без втрат — тому це окреме налаштування.",
|
||||
"Most marketing and receipt mail sets a color somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colors: backgrounds they laid the message on are dropped, while buttons and colored banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "Майже в кожному рекламному листі та чеку десь задано колір, тому налаштування вище залишає майже всі такі листи на білій картці. Із цим налаштуванням тема накладається поверх кольорів відправника: тло, на якому зверстано лист, прибирається, а кнопки та кольорові плашки зберігаються, щоб текст на них залишався читабельним. Деякі листи цього не переживуть без втрат — тому це окреме налаштування.",
|
||||
"Swiping": "Жести проведення",
|
||||
"Swipe left": "Провести ліворуч",
|
||||
"Swipe right": "Провести праворуч",
|
||||
@@ -1125,7 +1124,7 @@ export const catalog: Catalog = {
|
||||
"Each identity is a sender address with its own name, Reply-To and signature. The default identity is preselected when you compose; set a Reply-To when replies should go somewhere other than the From address.": "Кожен профіль — це адреса відправника зі своїм іменем, зворотною адресою та підписом. Основний профіль підставляється під час написання листа; вкажіть зворотну адресу, якщо відповіді мають надходити не на адресу відправника.",
|
||||
"This signature is larger than the server's {limit}-byte limit. ihasmail will keep the full version in your Files and store a short text fallback on the server — other mail clients will see the plain-text version.": "Цей підпис більший за серверне обмеження в {limit} байт. ihasmail збереже повну версію у ваших Файлах, а на сервері залишить короткий текстовий варіант — інші поштові клієнти побачать саме його.",
|
||||
"Outlook-style categories you can assign to events from the right-click menu or the event editor. The category name is stored on the event, so it syncs to other clients.": "Категорії у стилі Outlook, які можна призначати подіям через контекстне меню або редактор події. Назва категорії зберігається в самій події й синхронізується з іншими клієнтами.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colours of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "Листи у звичайному тексті вже відповідають темі. З цим налаштуванням їй відповідають і HTML-листи без власних кольорів, замість того щоб показуватися на білому тлі. Листи з власним оформленням залишаються саме такими, якими їх задумав відправник.",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colors of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "Листи у звичайному тексті вже відповідають темі. З цим налаштуванням їй відповідають і HTML-листи без власних кольорів, замість того щоб показуватися на білому тлі. Листи з власним оформленням залишаються саме такими, якими їх задумав відправник.",
|
||||
"This is separate from {setting} in General, which decides how dates, times and numbers are written. You can read an English interface with German dates, or the other way round.": "Це не те саме, що {setting} у розділі «Загальні», де визначається, як записуються дати, час і числа. Можна читати англійський інтерфейс з українськими датами — або навпаки.",
|
||||
"On a touchscreen, drag a message sideways to act on it. Each direction can do one thing, or nothing. These follow your account, so a phone and a tablet agree; a mouse ignores them and keeps dragging messages into folders instead.": "На сенсорному екрані проведіть по листу вбік, щоб виконати над ним дію. Кожен напрямок може робити щось одне — або нічого. Налаштування прив'язане до облікового запису, тож телефон і планшет поводяться однаково; миша його ігнорує, і листи, як і раніше, перетягуються до тек.",
|
||||
"This screen has no touchscreen, so nothing here changes what it does. Your phone or tablet will pick these up.": "Цей екран не сенсорний, тому тут нічого не зміниться. Налаштування підхоплять телефон або планшет.",
|
||||
@@ -1186,12 +1185,12 @@ export const catalog: Catalog = {
|
||||
"Applying filter to existing messages…": "Фільтр застосовується до наявних листів…",
|
||||
"Attachments are still uploading": "Вкладення ще завантажуються",
|
||||
"Calendar saved": "Календар збережено",
|
||||
"Categorised as {name}": "Призначено категорію «{name}»",
|
||||
"Categorized as {name}": "Призначено категорію «{name}»",
|
||||
"Category cleared": "Категорію знято",
|
||||
"Change this event?": "Змінити цю подію?",
|
||||
"Choose a calendar": "Виберіть календар",
|
||||
"Choose an address book": "Виберіть адресну книгу",
|
||||
"Colour updated": "Колір змінено",
|
||||
"Color updated": "Колір змінено",
|
||||
"Contact created": "Контакт створено",
|
||||
"Contact deleted": "Контакт видалено",
|
||||
"Contact saved": "Контакт збережено",
|
||||
@@ -1215,7 +1214,7 @@ export const catalog: Catalog = {
|
||||
"Could not update: {error}": "Не вдалося оновити: {error}",
|
||||
"Create": "Створити",
|
||||
"Create an address book first": "Спершу створіть адресну книгу",
|
||||
"Custom colour removed": "Власний колір знято",
|
||||
"Custom color removed": "Власний колір знято",
|
||||
"Deactivate": "Вимкнути",
|
||||
"Delete failed: {error}": "Не вдалося видалити: {error}",
|
||||
"Delete forever": "Видалити назавжди",
|
||||
@@ -1302,7 +1301,7 @@ export const catalog: Catalog = {
|
||||
"Script saved": "Сценарій збережено",
|
||||
"Send (Ctrl+Enter)": "Надіслати (Ctrl+Enter)",
|
||||
"Send anyway": "Усе одно надіслати",
|
||||
"Send cancelled — the message is back in Drafts": "Надсилання скасовано — лист повернувся до чернеток",
|
||||
"Send canceled — the message is back in Drafts": "Надсилання скасовано — лист повернувся до чернеток",
|
||||
"Send failed: {error}": "Не вдалося надіслати: {error}",
|
||||
"Send invites": "Надіслати запрошення",
|
||||
"Send scheduled for {when}": "Надсилання заплановано на {when}",
|
||||
@@ -1434,7 +1433,7 @@ export const catalog: Catalog = {
|
||||
"Date received": "Дата отримання",
|
||||
"Date sent": "Дата надсилання",
|
||||
"Day view": "День",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colours are derived, and every one is checked for contrast. The accent colour below still applies over any of them.": "Палітри, названі на честь іншого проєкту, є роботою цього проєкту й використовуються за його власною ліцензією; відтінки між опублікованими кольорами обчислюються, і кожен із них перевіряється на контраст. Акцентний колір нижче й надалі застосовується поверх будь-якої з них.",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colors are derived, and every one is checked for contrast. The accent color below still applies over any of them.": "Палітри, названі на честь іншого проєкту, є роботою цього проєкту й використовуються за його власною ліцензією; відтінки між опублікованими кольорами обчислюються, і кожен із них перевіряється на контраст. Акцентний колір нижче й надалі застосовується поверх будь-якої з них.",
|
||||
"Earlier": "Раніше",
|
||||
"Every folder": "Усі теки",
|
||||
"Everyone addressed will receive this.": "Це отримають усі зазначені адресати.",
|
||||
@@ -1451,7 +1450,7 @@ export const catalog: Catalog = {
|
||||
"Go to Settings": "Перейти до налаштувань",
|
||||
"Go to Starred": "Перейти до позначених",
|
||||
"Import iCAL file…": "Імпортувати файл iCAL…",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colours and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Мітки — це ключові слова IMAP, які зберігаються на листах, тому їх бачить будь-який інший клієнт. Назви, кольори та вкладеність належать самому ihasmail і йдуть за вашим обліковим записом. Вкладеність впливає лише на відображення й нічого не переписує в поштовій скриньці.",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colors and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "Мітки — це ключові слова IMAP, які зберігаються на листах, тому їх бачить будь-який інший клієнт. Назви, кольори та вкладеність належать самому ihasmail і йдуть за вашим обліковим записом. Вкладеність впливає лише на відображення й нічого не переписує в поштовій скриньці.",
|
||||
"Largest first": "Спочатку великі",
|
||||
"Later": "Пізніше",
|
||||
"Light or dark": "Світла або темна",
|
||||
@@ -1499,7 +1498,7 @@ export const catalog: Catalog = {
|
||||
"Saved": "Збережено",
|
||||
"Select all {n} in {folder}": "Вибрати всі {n} у теці {folder}",
|
||||
"Send message": "Надіслати лист",
|
||||
"Send outside your organisation?": "Надіслати за межі організації?",
|
||||
"Send outside your organization?": "Надіслати за межі організації?",
|
||||
"Send to {count} people?": "Надіслати {count} одержувачам?",
|
||||
"Show birthdays from your contacts": "Показувати дні народження з контактів",
|
||||
"Show in the sidebar": "Показувати на бічній панелі",
|
||||
@@ -1542,7 +1541,7 @@ export const catalog: Catalog = {
|
||||
"A to Z": "Від А до Я",
|
||||
"It reads {shown} but goes to {actual}.": "Показано {shown}, але посилання веде на {actual}.",
|
||||
"The full address is {href}.": "Повна адреса: {href}.",
|
||||
"This message came from {domain}, which is outside your organisation.": "Цей лист надійшов з {domain} — за межами вашої організації.",
|
||||
"This message came from {domain}, which is outside your organization.": "Цей лист надійшов з {domain} — за межами вашої організації.",
|
||||
"Unsaved changes": "Незбережені зміни",
|
||||
"View as": "Показувати як",
|
||||
"Warnings": "Попередження",
|
||||
|
||||
+21
-22
@@ -114,7 +114,7 @@ export const catalog: Catalog = {
|
||||
"This account signs in through an external directory, so its password can't be set here.": "该账户通过外部目录登录,因此无法在此设置其密码。",
|
||||
"The server's license allows no more accounts.": "服务器许可证不允许再添加账户。",
|
||||
"That domain name is already in use on this server, as a domain or another domain's other name.": "该域名已在此服务器上被使用,可能是一个域名,也可能是另一个域名的其他名称。",
|
||||
"Your organisation has reached the number of domains it is allowed.": "您的组织已达到允许的域名数量上限。",
|
||||
"Your organization has reached the number of domains it is allowed.": "您的组织已达到允许的域名数量上限。",
|
||||
"That is more than the mail server accepts in one change.": "这超出了邮件服务器单次更改可接受的范围。",
|
||||
"The mail server rejected one of the values. Check what you entered and try again.": "邮件服务器拒绝了其中一个值。请检查您输入的内容后重试。",
|
||||
"The mail server refused the change ({code}).": "邮件服务器拒绝了此更改({code})。",
|
||||
@@ -169,7 +169,7 @@ export const catalog: Catalog = {
|
||||
"Search groups": "搜索群组",
|
||||
"No groups match": "没有匹配的群组",
|
||||
"No groups yet": "还没有群组",
|
||||
"Your organisation has reached the number of groups it is allowed.": "您的组织已达到允许的群组数量。",
|
||||
"Your organization has reached the number of groups it is allowed.": "您的组织已达到允许的群组数量。",
|
||||
"This group no longer exists. Someone may have deleted it.": "此群组已不存在。可能已被他人删除。",
|
||||
"The server did not say whether the group was created.": "服务器未说明群组是否已创建。",
|
||||
"Mailing lists": "邮件列表",
|
||||
@@ -194,7 +194,7 @@ export const catalog: Catalog = {
|
||||
"Search mailing lists": "搜索邮件列表",
|
||||
"No mailing lists match": "没有匹配的邮件列表",
|
||||
"No mailing lists yet": "还没有邮件列表",
|
||||
"Your organisation has reached the number of mailing lists it is allowed.": "您的组织已达到允许的邮件列表数量。",
|
||||
"Your organization has reached the number of mailing lists it is allowed.": "您的组织已达到允许的邮件列表数量。",
|
||||
"This mailing list no longer exists. Someone may have deleted it.": "此邮件列表已不存在。可能已被他人删除。",
|
||||
"The server did not say whether the list was created.": "服务器未说明列表是否已创建。",
|
||||
"Roles": "角色",
|
||||
@@ -245,13 +245,13 @@ export const catalog: Catalog = {
|
||||
"Open {name}": "打开 {name}",
|
||||
"Default for {kinds}": "默认授予:{kinds}",
|
||||
"You can't give a role permissions your own role doesn't have.": "您不能给角色授予您自己的角色所没有的权限。",
|
||||
"Your organisation has reached the number of roles it is allowed.": "您的组织已达到允许的角色数量。",
|
||||
"Your organization has reached the number of roles it is allowed.": "您的组织已达到允许的角色数量。",
|
||||
"This role no longer exists. Someone may have deleted it.": "此角色已不存在。可能已被他人删除。",
|
||||
"the default roles": "默认角色设置",
|
||||
"The server did not say whether the role was created.": "服务器未说明角色是否已创建。",
|
||||
"No tenant": "无租户",
|
||||
"You can't move your own account into a tenant.": "您不能将自己的账户移入租户。",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts towards its limits, and Administrator means administrator of that tenant.": "账户只能位于其域名所属的租户中。在租户中,账户受租户角色限制,并计入租户的限额;管理员指该租户的管理员。",
|
||||
"An account can be in the tenant its domain is in. In a tenant it is limited by the tenant's role and counts toward its limits, and Administrator means administrator of that tenant.": "账户只能位于其域名所属的租户中。在租户中,账户受租户角色限制,并计入租户的限额;管理员指该租户的管理员。",
|
||||
"Tenants": "租户",
|
||||
"Storage in GB": "存储 (GB)",
|
||||
"Default tenant roles": "默认租户角色",
|
||||
@@ -279,7 +279,7 @@ export const catalog: Catalog = {
|
||||
"Delete tenant…": "删除租户…",
|
||||
"Still holds {things}. Move them out first.": "仍包含 {things}。请先将其移出。",
|
||||
"Delete tenant": "删除租户",
|
||||
"Separate organisations on one server, each with its own people, domains and limits.": "同一服务器上相互独立的组织,各有自己的成员、域名和限额。",
|
||||
"Separate organizations on one server, each with its own people, domains and limits.": "同一服务器上相互独立的组织,各有自己的成员、域名和限额。",
|
||||
"Tenants are a Stalwart Enterprise feature.": "租户是 Stalwart Enterprise 的功能。",
|
||||
"Search tenants": "搜索租户",
|
||||
"No tenants match": "没有匹配的租户",
|
||||
@@ -345,7 +345,7 @@ export const catalog: Catalog = {
|
||||
"The mail server refused this. Your role may not allow it.": "邮件服务器拒绝了此操作。您的角色可能不允许。",
|
||||
"That address is already in use on this server, as an account, a list or an alias.": "该地址已在此服务器上被账户、列表或别名使用。",
|
||||
"One of the chosen domain, role or group can't be used for this account.": "所选的域名、角色或群组无法用于此账户。",
|
||||
"Your organisation has reached the number of accounts it is allowed.": "您的组织已达到允许的账户数量上限。",
|
||||
"Your organization has reached the number of accounts it is allowed.": "您的组织已达到允许的账户数量上限。",
|
||||
"Something still depends on this, so the server kept it.": "仍有其他内容依赖于它,因此服务器保留了它。",
|
||||
"This account no longer exists. Someone may have deleted it.": "该账户已不存在,可能已被他人删除。",
|
||||
"The password was not accepted: {reason}": "密码未被接受:{reason}",
|
||||
@@ -422,7 +422,7 @@ export const catalog: Catalog = {
|
||||
"Turn off": "关闭",
|
||||
"Clear": "清空",
|
||||
"Clear selection": "取消选择",
|
||||
"Clear custom colour": "清除自定义颜色",
|
||||
"Clear custom color": "清除自定义颜色",
|
||||
"Select": "选择",
|
||||
"Select all": "全选",
|
||||
"Unsubscribe": "退订",
|
||||
@@ -615,7 +615,7 @@ export const catalog: Catalog = {
|
||||
"Maybe": "待定",
|
||||
"Confirmed": "已确认",
|
||||
"Tentative": "待定",
|
||||
"Cancelled": "已取消",
|
||||
"Canceled": "已取消",
|
||||
"organizer": "组织者",
|
||||
"Organizer: {name}": "组织者:{name}",
|
||||
"Free": "空闲",
|
||||
@@ -630,7 +630,7 @@ export const catalog: Catalog = {
|
||||
"Working hours": "工作时间",
|
||||
"Working hours start": "工作时间开始",
|
||||
"Working hours end": "工作时间结束",
|
||||
"Colour categories": "颜色分类",
|
||||
"Color categories": "颜色分类",
|
||||
"Category": "分类",
|
||||
"No category": "无分类",
|
||||
"New category": "新建分类",
|
||||
@@ -638,7 +638,7 @@ export const catalog: Catalog = {
|
||||
"Manage categories…": "管理分类…",
|
||||
"Use category color": "使用分类颜色",
|
||||
"Use calendar color": "使用日历颜色",
|
||||
"Use the default colour": "使用默认颜色",
|
||||
"Use the default color": "使用默认颜色",
|
||||
"+{n} more": "还有 {n} 项",
|
||||
|
||||
"Contacts": "联系人",
|
||||
@@ -798,7 +798,6 @@ export const catalog: Catalog = {
|
||||
"Theme": "主题",
|
||||
"Accent color": "强调色",
|
||||
"Color": "颜色",
|
||||
"Colour": "颜色",
|
||||
"Text color": "文字颜色",
|
||||
"Density & text": "密度与文字",
|
||||
"Display density": "显示密度",
|
||||
@@ -816,7 +815,7 @@ export const catalog: Catalog = {
|
||||
"Collapse sidebar to icons": "将侧边栏收起为图标",
|
||||
"Apply the theme to messages too": "邮件也应用主题",
|
||||
"Apply it even to mail that styles itself": "即使邮件自带配色也套用",
|
||||
"Most marketing and receipt mail sets a colour somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colours: backgrounds they laid the message on are dropped, while buttons and coloured banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "几乎所有营销邮件和收据邮件都会在某处设置颜色,因此上面的设置会让它们几乎全部停留在白色卡片上。启用此项后,主题会覆盖发件人的配色:邮件所依托的背景会被去掉,而按钮和彩色横幅会保留下来,使其文字仍然清晰可读。有些邮件无法完好呈现,因此这是一项单独的设置。",
|
||||
"Most marketing and receipt mail sets a color somewhere, so the setting above leaves nearly all of it on a white card. With this on, the theme is forced over the sender's own colors: backgrounds they laid the message on are dropped, while buttons and colored banners are kept so their text stays readable. Some mail will not survive it intact, which is why it is separate.": "几乎所有营销邮件和收据邮件都会在某处设置颜色,因此上面的设置会让它们几乎全部停留在白色卡片上。启用此项后,主题会覆盖发件人的配色:邮件所依托的背景会被去掉,而按钮和彩色横幅会保留下来,使其文字仍然清晰可读。有些邮件无法完好呈现,因此这是一项单独的设置。",
|
||||
"Swiping": "滑动手势",
|
||||
"Swipe left": "向左滑动",
|
||||
"Swipe right": "向右滑动",
|
||||
@@ -1082,7 +1081,7 @@ export const catalog: Catalog = {
|
||||
"Each identity is a sender address with its own name, Reply-To and signature. The default identity is preselected when you compose; set a Reply-To when replies should go somewhere other than the From address.": "每个发件身份都是一个发件地址,拥有各自的名称、回复地址和签名。写邮件时会预先选中默认身份;若希望回复发往发件人地址以外的地方,请设置回复地址。",
|
||||
"This signature is larger than the server's {limit}-byte limit. ihasmail will keep the full version in your Files and store a short text fallback on the server — other mail clients will see the plain-text version.": "此签名超出了服务器 {limit} 字节的限制。ihasmail 会把完整版本保存在您的「文件」中,并在服务器上存放一段简短的文本备用版——其他邮件客户端看到的将是纯文本版本。",
|
||||
"Outlook-style categories you can assign to events from the right-click menu or the event editor. The category name is stored on the event, so it syncs to other clients.": "Outlook 风格的分类,可通过右键菜单或日程编辑器指定给日程。分类名称保存在日程上,因此会同步到其他客户端。",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colours of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "纯文本邮件本就会跟随主题。开启后,未自带配色的 HTML 邮件也会跟随主题,而不再显示在白色卡片上。自带样式的邮件则完全保持发件人设计的样子。",
|
||||
"Plain-text mail already follows the theme. With this on, HTML mail that brings no colors of its own does as well, instead of sitting on a white card. Messages that style themselves are left exactly as the sender designed them.": "纯文本邮件本就会跟随主题。开启后,未自带配色的 HTML 邮件也会跟随主题,而不再显示在白色卡片上。自带样式的邮件则完全保持发件人设计的样子。",
|
||||
"On a touchscreen, drag a message sideways to act on it. Each direction can do one thing, or nothing. These follow your account, so a phone and a tablet agree; a mouse ignores them and keeps dragging messages into folders instead.": "在触摸屏上,横向拖动邮件即可对其操作。每个方向可以执行一项操作,也可以什么都不做。这些设置跟随您的账户,因此手机和平板保持一致;鼠标不受影响,仍然是把邮件拖入文件夹。",
|
||||
"This screen has no touchscreen, so nothing here changes what it does. Your phone or tablet will pick these up.": "此屏幕没有触摸屏,因此这里的设置不会改变它的行为。您的手机或平板会应用这些设置。",
|
||||
"Holding a message selects it, and holding a folder opens its menu. Pull the top of the message list down to check for new mail.": "长按邮件可选中它,长按文件夹可打开其菜单。下拉邮件列表顶部即可检查新邮件。",
|
||||
@@ -1197,12 +1196,12 @@ export const catalog: Catalog = {
|
||||
"Applying filter to existing messages…": "正在对现有邮件应用过滤器…",
|
||||
"Attachments are still uploading": "附件仍在上传中",
|
||||
"Calendar saved": "日历已保存",
|
||||
"Categorised as {name}": "已归入分类「{name}」",
|
||||
"Categorized as {name}": "已归入分类「{name}」",
|
||||
"Category cleared": "已清除分类",
|
||||
"Change this event?": "要修改此日程吗?",
|
||||
"Choose a calendar": "请选择日历",
|
||||
"Choose an address book": "请选择通讯录",
|
||||
"Colour updated": "颜色已更新",
|
||||
"Color updated": "颜色已更新",
|
||||
"Contact created": "联系人已创建",
|
||||
"Contact deleted": "联系人已删除",
|
||||
"Contact saved": "联系人已保存",
|
||||
@@ -1226,7 +1225,7 @@ export const catalog: Catalog = {
|
||||
"Could not update: {error}": "无法更新:{error}",
|
||||
"Create": "创建",
|
||||
"Create an address book first": "请先创建通讯录",
|
||||
"Custom colour removed": "已清除自定义颜色",
|
||||
"Custom color removed": "已清除自定义颜色",
|
||||
"Deactivate": "停用",
|
||||
"Delete failed: {error}": "删除失败:{error}",
|
||||
"Delete forever": "永久删除",
|
||||
@@ -1313,7 +1312,7 @@ export const catalog: Catalog = {
|
||||
"Script saved": "脚本已保存",
|
||||
"Send (Ctrl+Enter)": "发送 (Ctrl+Enter)",
|
||||
"Send anyway": "仍然发送",
|
||||
"Send cancelled — the message is back in Drafts": "已取消发送——邮件已回到草稿箱",
|
||||
"Send canceled — the message is back in Drafts": "已取消发送——邮件已回到草稿箱",
|
||||
"Send failed: {error}": "发送失败:{error}",
|
||||
"Send invites": "发送邀请",
|
||||
"Send scheduled for {when}": "已定时于 {when} 发送",
|
||||
@@ -1445,7 +1444,7 @@ export const catalog: Catalog = {
|
||||
"Date received": "接收日期",
|
||||
"Date sent": "发送日期",
|
||||
"Day view": "日视图",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colours are derived, and every one is checked for contrast. The accent colour below still applies over any of them.": "以其他项目命名的配色方案是该项目的作品,按其自身的许可证使用;已发布颜色之间的过渡色是推导得出的,并且每一种都经过对比度检查。下方的强调色仍会应用于其中任何一种配色方案。",
|
||||
"Palettes named after another project are that project's work, used under its own license; the shades between their published colors are derived, and every one is checked for contrast. The accent color below still applies over any of them.": "以其他项目命名的配色方案是该项目的作品,按其自身的许可证使用;已发布颜色之间的过渡色是推导得出的,并且每一种都经过对比度检查。下方的强调色仍会应用于其中任何一种配色方案。",
|
||||
"Earlier": "更早",
|
||||
"Every folder": "所有文件夹",
|
||||
"Everyone addressed will receive this.": "所有收件人都会收到此邮件。",
|
||||
@@ -1462,7 +1461,7 @@ export const catalog: Catalog = {
|
||||
"Go to Settings": "转到设置",
|
||||
"Go to Starred": "转到已标星",
|
||||
"Import iCAL file…": "导入 iCAL 文件…",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colours and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "标签是保存在邮件上的 IMAP 关键字,因此其他客户端也能看到。名称、颜色和层级是 ihasmail 自有的,随您的账户一同保存。层级仅影响显示,不会改写邮箱中的任何内容。",
|
||||
"Labels are IMAP keywords stored on your messages, so every other client sees them. Names, colors and nesting are ihasmail’s own and follow your account. Nesting is display only — it rewrites nothing in the mailbox.": "标签是保存在邮件上的 IMAP 关键字,因此其他客户端也能看到。名称、颜色和层级是 ihasmail 自有的,随您的账户一同保存。层级仅影响显示,不会改写邮箱中的任何内容。",
|
||||
"Largest first": "从大到小",
|
||||
"Later": "更晚",
|
||||
"Light or dark": "浅色或深色",
|
||||
@@ -1510,7 +1509,7 @@ export const catalog: Catalog = {
|
||||
"Saved": "已保存",
|
||||
"Select all {n} in {folder}": "选中 {folder} 中的全部 {n} 项",
|
||||
"Send message": "发送邮件",
|
||||
"Send outside your organisation?": "发送到组织外部吗?",
|
||||
"Send outside your organization?": "发送到组织外部吗?",
|
||||
"Send to {count} people?": "发送给 {count} 个人吗?",
|
||||
"Show birthdays from your contacts": "显示联系人的生日",
|
||||
"Show in the sidebar": "在侧边栏中显示",
|
||||
@@ -1553,7 +1552,7 @@ export const catalog: Catalog = {
|
||||
"A to Z": "A 到 Z",
|
||||
"It reads {shown} but goes to {actual}.": "显示的是 {shown},实际打开的是 {actual}。",
|
||||
"The full address is {href}.": "完整地址为 {href}。",
|
||||
"This message came from {domain}, which is outside your organisation.": "此邮件来自 {domain},属于贵组织之外。",
|
||||
"This message came from {domain}, which is outside your organization.": "此邮件来自 {domain},属于贵组织之外。",
|
||||
"Unsaved changes": "未保存的更改",
|
||||
"View as": "查看方式",
|
||||
"Warnings": "警告",
|
||||
|
||||
@@ -243,7 +243,7 @@ describe("updateEvent, per occurrence", () => {
|
||||
});
|
||||
|
||||
describe("isThisAndFutureRefusal", () => {
|
||||
it("recognises the refusal worth offering the series for", () => {
|
||||
it("recognizes the refusal worth offering the series for", () => {
|
||||
expect(isThisAndFutureRefusal(new CalendarSetError({
|
||||
type: "invalidProperties",
|
||||
description: "Occurrences of a this-and-future change cannot be modified individually.",
|
||||
|
||||
@@ -264,7 +264,7 @@ describe("importing a file bigger than the server will take at once", () => {
|
||||
* Re-importing the same file.
|
||||
*
|
||||
* The import kept the file's own UID from the day it was written, which is the
|
||||
* whole of what is needed to recognise an event that is already here -- and
|
||||
* whole of what is needed to recognize an event that is already here -- and
|
||||
* nothing looked. Importing an export twice left second copies of everything,
|
||||
* which the reporter's colleague hit during testing (#173, decided there:
|
||||
* "duplicate checks on UIDs if UID present in event"). Issue #222 made that a
|
||||
|
||||
@@ -35,7 +35,7 @@ describe("applyLang", () => {
|
||||
expect(document.documentElement.lang).toBe("en");
|
||||
});
|
||||
|
||||
it("serves every language whose catalogue is shipped", () => {
|
||||
it("serves every language whose catalog is shipped", () => {
|
||||
for (const l of UI_LANGUAGES) {
|
||||
applyLang({ ...DEFAULT_SETTINGS, uiLanguage: l.tag });
|
||||
expect(document.documentElement.lang).toBe(l.tag);
|
||||
|
||||
@@ -92,7 +92,7 @@ describe("importing an LDIF address book", () => {
|
||||
const uids = Object.values(sets[0]!.create!).map((c) => c.uid as string);
|
||||
expect(new Set(uids).size).toBe(2);
|
||||
// Namespaced, so it is never mistaken for a UID a vCard author meant, and
|
||||
// stable, so importing the same file again recognises these.
|
||||
// stable, so importing the same file again recognizes these.
|
||||
expect(uids.every((u) => u.startsWith("urn:x-ihasmail:ldif:"))).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ describe("telling somebody what an LDIF re-import duplicated", () => {
|
||||
expect(r.alike).toBe(0);
|
||||
});
|
||||
|
||||
it("recognises a match on a second address", async () => {
|
||||
it("recognizes a match on a second address", async () => {
|
||||
server([card("c1", "Jane Doe", "[email protected]", "[email protected]")]);
|
||||
const r = await useContacts.getState().importLdif(entry("Jane Doe", "[email protected]"), "book1");
|
||||
expect(r.alike).toBe(1);
|
||||
|
||||
@@ -19,13 +19,13 @@ describe("isRecurring", () => {
|
||||
// and a base that is a different id. Neither makes it a series.
|
||||
expect(isRecurring(ev({ id: "eaaaaai", baseEventId: "i" }))).toBe(false);
|
||||
});
|
||||
it("recognises a series by its rule, under either name", () => {
|
||||
it("recognizes a series by its rule, under either name", () => {
|
||||
expect(isRecurring(ev({ recurrenceRules: [{ "@type": "RecurrenceRule", frequency: "weekly" }] }))).toBe(true);
|
||||
expect(isRecurring(ev({ excludedRecurrenceRules: [{ "@type": "RecurrenceRule", frequency: "monthly" }] }))).toBe(true);
|
||||
// Stalwart 0.16 keeps a single rule under the singular name.
|
||||
expect(isRecurring(ev({ recurrenceRule: { "@type": "RecurrenceRule", frequency: "weekly", count: 3 } }))).toBe(true);
|
||||
});
|
||||
it("recognises an occurrence, which arrives with no rule of its own", () => {
|
||||
it("recognizes an occurrence, which arrives with no rule of its own", () => {
|
||||
// A live 0.16.19 expands a weekly series into instances like this: an id
|
||||
// per occurrence, a recurrenceId, and no rule attached.
|
||||
expect(isRecurring(ev({ id: "iaaaaas", recurrenceId: "2030-03-11T10:00:00" }))).toBe(true);
|
||||
|
||||
@@ -73,7 +73,7 @@ describe("replying to a message somebody sent me", () => {
|
||||
expect(addrs(d.cc)).toEqual([BOB.email]);
|
||||
});
|
||||
|
||||
it("honours the sender's Reply-To, which is what it is for", async () => {
|
||||
it("honors the sender's Reply-To, which is what it is for", async () => {
|
||||
const d = await draftFor({ ...HERS, replyTo: [{ name: null, email: "[email protected]" }] } as Email, "reply");
|
||||
expect(addrs(d.to)).toEqual(["[email protected]"]);
|
||||
});
|
||||
@@ -105,7 +105,7 @@ describe("replying to a message I sent", () => {
|
||||
expect(addrs(d.cc)).toEqual([BOB.email]);
|
||||
});
|
||||
|
||||
it("recognises my address however the identity stored it", async () => {
|
||||
it("recognizes my address however the identity stored it", async () => {
|
||||
// A hand-typed identity address can carry whitespace, and comparing
|
||||
// strings rather than addresses made that enough to break the reply.
|
||||
const padded = [{ id: "i1", name: "John", email: " [email protected] " }] as unknown as Identity[];
|
||||
|
||||
@@ -111,7 +111,7 @@ describe("reconcile", () => {
|
||||
expect(useScheduled.getState().pending).toEqual({});
|
||||
});
|
||||
|
||||
it("returns a message cancelled elsewhere to Drafts, as a draft again", async () => {
|
||||
it("returns a message canceled elsewhere to Drafts, as a draft again", async () => {
|
||||
const s = server(["e1"], [{ id: "s1", emailId: "e1", sendAt: FUTURE, undoStatus: "canceled" }]);
|
||||
await useScheduled.getState().reconcile();
|
||||
expect(moved(s.updates[0]!.e1!)).toEqual({ into: DRAFTS, outOf: SCHED });
|
||||
@@ -156,7 +156,7 @@ describe("reconcile", () => {
|
||||
});
|
||||
|
||||
it("keeps a message whose live hold was moved earlier than the one it replaced", async () => {
|
||||
// Rescheduling to a sooner time leaves the cancelled submission holding the
|
||||
// Rescheduling to a sooner time leaves the canceled submission holding the
|
||||
// later sendAt. Going by timestamp alone would file a message back to
|
||||
// Drafts while the queue still has it.
|
||||
const s = server(
|
||||
|
||||
@@ -333,11 +333,11 @@ function forImport(event: Partial<CalendarEvent>): Partial<CalendarEvent> {
|
||||
}
|
||||
|
||||
/**
|
||||
* The events a calendar already holds, for recognising a re-import.
|
||||
* The events a calendar already holds, for recognizing a re-import.
|
||||
*
|
||||
* A UID is what makes an event the same event across calendars, and the import
|
||||
* already keeps the file's own wherever there is one -- so the thing needed to
|
||||
* recognise a re-import was there all along and nothing looked at it. Asked for
|
||||
* recognize a re-import was there all along and nothing looked at it. Asked for
|
||||
* once per import rather than once per event: `CalendarEvent/query` does take a
|
||||
* `uid` filter, but a file of two thousand events would be two thousand
|
||||
* queries.
|
||||
@@ -618,7 +618,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
|
||||
for (const b of birthdaysInRange(Object.values(useContacts.getState().cards), start, end)) {
|
||||
birthdays.push({
|
||||
key: b.id,
|
||||
event: synthesiseBirthdayEvent(b),
|
||||
event: synthesizeBirthdayEvent(b),
|
||||
start: b.date,
|
||||
end: new Date(b.date.getTime() + DAY_MS),
|
||||
allDay: true,
|
||||
@@ -639,7 +639,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
|
||||
if (e.end <= start || e.start >= end) continue;
|
||||
birthdays.push({
|
||||
key: `${calId}:${e.uid}:${e.start.getTime()}`,
|
||||
event: synthesiseSubscriptionEvent(sub.id, e),
|
||||
event: synthesizeSubscriptionEvent(sub.id, e),
|
||||
start: e.start,
|
||||
end: e.end,
|
||||
allDay: e.allDay,
|
||||
@@ -821,7 +821,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
|
||||
*
|
||||
* The query deliberately omits `expandRecurrences`, so what comes back is the
|
||||
* stored event and `id` is a real id. Callers rely on that — `InviteCard`
|
||||
* removes a cancelled event by handing this straight to `destroyEvent` — so
|
||||
* removes a canceled event by handing this straight to `destroyEvent` — so
|
||||
* it is a property of this method, not an accident of the default.
|
||||
*/
|
||||
async findByUid(uid) {
|
||||
@@ -881,7 +881,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
|
||||
* the disagreement. Weighed on #279 and kept: an import is not the place to
|
||||
* start sending mail on somebody's behalf, and the alternative is a file
|
||||
* dropped into a calendar mailing a room full of people who never asked for
|
||||
* it. Whoever is organising can send the update from the event itself.
|
||||
* it. Whoever is organizing can send the update from the event itself.
|
||||
*/
|
||||
async importIcs(text, calendarId) {
|
||||
const accountId = get().accountId!;
|
||||
@@ -984,7 +984,7 @@ export const useCalendar = create<CalendarState>((set, get) => ({
|
||||
* flattened the rule would import somewhere else as an unmaintainable pile.
|
||||
*
|
||||
* Written in the browser, unlike the import, which hands the parsing to the
|
||||
* server. There is no `CalendarEvent/serialise` to hand this to -- the JMAP
|
||||
* server. There is no `CalendarEvent/serialize` to hand this to -- the JMAP
|
||||
* calendar drafts define parsing and nothing the other way -- so it is done
|
||||
* here from the objects the server already returns.
|
||||
*/
|
||||
@@ -1053,7 +1053,7 @@ function birthdayCalendar(): Calendar {
|
||||
}
|
||||
|
||||
/** A CalendarEvent shaped enough for the views, and for nothing else. */
|
||||
function synthesiseBirthdayEvent(b: Birthday): CalendarEvent {
|
||||
function synthesizeBirthdayEvent(b: Birthday): CalendarEvent {
|
||||
const local = `${b.date.getFullYear()}-${String(b.date.getMonth() + 1).padStart(2, "0")}-${String(b.date.getDate()).padStart(2, "0")}T00:00:00`;
|
||||
return {
|
||||
id: b.id,
|
||||
@@ -1088,7 +1088,7 @@ function subscriptionCalendar(sub: { id: string; name: string; color: string }):
|
||||
} as unknown as Calendar;
|
||||
}
|
||||
|
||||
function synthesiseSubscriptionEvent(subId: string, e: IcsEvent): CalendarEvent {
|
||||
function synthesizeSubscriptionEvent(subId: string, e: IcsEvent): CalendarEvent {
|
||||
const local = `${e.start.getFullYear()}-${String(e.start.getMonth() + 1).padStart(2, "0")}-${String(e.start.getDate()).padStart(2, "0")}T${String(e.start.getHours()).padStart(2, "0")}:${String(e.start.getMinutes()).padStart(2, "0")}:00`;
|
||||
return {
|
||||
id: `${subscriptionCalendarId(subId)}:${e.uid}`,
|
||||
|
||||
@@ -623,7 +623,7 @@ export const useCompose = create<ComposeState>((set, get) => ({
|
||||
});
|
||||
}
|
||||
};
|
||||
// A scheduled send is already delayed, and cancelling it is a server-side
|
||||
// A scheduled send is already delayed, and canceling it is a server-side
|
||||
// operation from the Scheduled folder -- holding it locally first would
|
||||
// only add a second, different kind of undo.
|
||||
if (delay <= 0 || scheduling) {
|
||||
|
||||
@@ -83,7 +83,7 @@ async function scanBook(accountId: Id, addressBookId: Id): Promise<{ byUid: Map<
|
||||
* confusion rather than duplication, and being told costs nothing.
|
||||
*
|
||||
* One key per address, so a person whose second address matches is still
|
||||
* recognised.
|
||||
* recognized.
|
||||
*/
|
||||
function likenessKeys(c: Partial<ContactCard>): string[] {
|
||||
const name = contactDisplayName(c as ContactCard).trim().toLowerCase();
|
||||
@@ -225,7 +225,7 @@ interface ContactsState {
|
||||
/**
|
||||
* Import an address book in LDIF, read against Mozilla's schema.
|
||||
*
|
||||
* Mozilla's schema has no UID, so a re-import is recognised by the entry's
|
||||
* Mozilla's schema has no UID, so a re-import is recognized by the entry's
|
||||
* `dn` instead -- the same update-rather-than-duplicate rule the vCard import
|
||||
* follows, on the only identity the file carries. `alike` is what is left
|
||||
* over: entries that were created and still look like somebody already here,
|
||||
|
||||
@@ -1331,7 +1331,7 @@ async function notifyNewMail(created: Id[], get: () => MailState) {
|
||||
onClick: () => {
|
||||
window.location.hash = "";
|
||||
// The one navigation that does not go through wouter -- it is
|
||||
// synthesising a popstate so the router picks the address up -- so
|
||||
// synthesizing a popstate so the router picks the address up -- so
|
||||
// it is also the one that has to add the mount prefix itself.
|
||||
window.history.pushState({}, "", withBase(`/mail/${inbox}/${e.threadId}`));
|
||||
window.dispatchEvent(new PopStateEvent("popstate"));
|
||||
|
||||
@@ -177,7 +177,7 @@ export const useScheduled = create<ScheduledState>((set, get) => ({
|
||||
* Nothing moves a message out of Scheduled when its hold expires -- the
|
||||
* server sends it and updates the submission, but the message stays where we
|
||||
* filed it. So on the way into the folder, settle up: what went out belongs
|
||||
* in Sent, what was cancelled elsewhere belongs back in Drafts.
|
||||
* in Sent, what was canceled elsewhere belongs back in Drafts.
|
||||
*/
|
||||
async reconcile() {
|
||||
const mail = useMail.getState();
|
||||
@@ -215,7 +215,7 @@ export const useScheduled = create<ScheduledState>((set, get) => ({
|
||||
pending[emailId] = s;
|
||||
continue;
|
||||
}
|
||||
// Cancelled goes back to Drafts; sent (or a submission the server no
|
||||
// Canceled goes back to Drafts; sent (or a submission the server no
|
||||
// longer knows about) goes to Sent, which is where it actually is.
|
||||
const toDrafts = s?.undoStatus === "canceled";
|
||||
const dest = toDrafts ? draftsId : sentId;
|
||||
|
||||
@@ -26,7 +26,7 @@ interface SessionState {
|
||||
logout(): Promise<void>;
|
||||
refresh(): Promise<void>;
|
||||
setAccount(id: Id): void;
|
||||
/** The account to read and write for a capability, honouring the account switcher. */
|
||||
/** The account to read and write for a capability, honoring the account switcher. */
|
||||
accountFor(cap: string): Id | null;
|
||||
/** The user's own account for a capability, whatever they are looking at. */
|
||||
ownAccountFor(cap: string): Id | null;
|
||||
|
||||
+10
-10
@@ -13,7 +13,7 @@ import { loadLanguage } from "@/lib/i18n";
|
||||
/**
|
||||
* "ihasmail" is a dark theme carrying the palette from ihasmail.org. It is a
|
||||
* theme rather than an accent because it changes the backgrounds, borders and
|
||||
* text as well as the highlight colour — an accent could not.
|
||||
* text as well as the highlight color — an accent could not.
|
||||
*/
|
||||
export type Theme = "system" | "light" | "dark" | "ihasmail";
|
||||
export type Density = "comfortable" | "cozy" | "compact";
|
||||
@@ -83,7 +83,7 @@ export interface Settings {
|
||||
* which is the half that stops an older device showing a theme nobody chose.
|
||||
*/
|
||||
theme: Theme;
|
||||
/** The colours. */
|
||||
/** The colors. */
|
||||
palette: PaletteId;
|
||||
/** Light, dark, or whatever the system says. */
|
||||
mode: Mode;
|
||||
@@ -131,7 +131,7 @@ export interface Settings {
|
||||
/** Let messages follow the app's light/dark theme instead of always sitting on white. */
|
||||
themeMessageBody: boolean;
|
||||
/**
|
||||
* Extend that to mail which brings colours of its own.
|
||||
* Extend that to mail which brings colors of its own.
|
||||
*
|
||||
* Only meaningful with `themeMessageBody` on. Off by default because it
|
||||
* cannot be done perfectly: see `markKeptSurfaces` in lib/html.ts for the
|
||||
@@ -225,8 +225,8 @@ export interface Settings {
|
||||
templates: Template[];
|
||||
labels: Label[];
|
||||
/**
|
||||
* Folder colours, by mailbox id. Local to this browser, like every other
|
||||
* colour here: JMAP has nowhere on a Mailbox to keep one.
|
||||
* Folder colors, by mailbox id. Local to this browser, like every other
|
||||
* color here: JMAP has nowhere on a Mailbox to keep one.
|
||||
*/
|
||||
folderColors: Record<string, string>;
|
||||
sidebarCollapsed: boolean;
|
||||
@@ -265,7 +265,7 @@ export interface Settings {
|
||||
* sidebar with their own CSS keeps what they had until they choose otherwise.
|
||||
*/
|
||||
sidebarWidth: number | null;
|
||||
/** Outlook-style colour categories for calendar events. */
|
||||
/** Outlook-style color categories for calendar events. */
|
||||
eventCategories: Array<{ name: string; color: string }>;
|
||||
/** Default sending identity per account (JMAP has no such flag). */
|
||||
defaultIdentityByAccount: Record<string, string>;
|
||||
@@ -645,7 +645,7 @@ export function applyLang(s: Settings = useSettings.getState().settings): void {
|
||||
const tag = resolveUiLanguage(s.uiLanguage);
|
||||
document.documentElement.lang = tag;
|
||||
/*
|
||||
* The catalogue is fetched, so it lands a beat after the attribute. That
|
||||
* The catalog is fetched, so it lands a beat after the attribute. That
|
||||
* order is deliberate: `lang` is what stops Chrome offering to translate,
|
||||
* and it should not wait on a network request to say something it already
|
||||
* knows. English needs no fetch at all and resolves immediately.
|
||||
@@ -663,7 +663,7 @@ export function applyTheme(s: Settings = useSettings.getState().settings): void
|
||||
/*
|
||||
* Two attributes, because they answer two questions. `data-theme` is the
|
||||
* mode, and every dark-only rule in the stylesheet keys off it without
|
||||
* knowing any palette exists; `data-palette` layers the colours on top. The
|
||||
* knowing any palette exists; `data-palette` layers the colors on top. The
|
||||
* accent variants out-specify both, which is what lets an accent still apply
|
||||
* over any palette.
|
||||
*/
|
||||
@@ -678,7 +678,7 @@ export function applyTheme(s: Settings = useSettings.getState().settings): void
|
||||
}
|
||||
|
||||
/**
|
||||
* The browser chrome colour, read from the palette's own background so it does
|
||||
* The browser chrome color, read from the palette's own background so it does
|
||||
* not have to be listed twice and cannot drift from it.
|
||||
*/
|
||||
function paletteThemeColor(palette: PaletteId, mode: "light" | "dark"): string {
|
||||
@@ -724,7 +724,7 @@ export function useEffectiveTheme(): "light" | "dark" {
|
||||
export const settings = () => useSettings.getState().settings;
|
||||
|
||||
/**
|
||||
* Primitive that changes whenever a date/time preference does, so memoised
|
||||
* Primitive that changes whenever a date/time preference does, so memoized
|
||||
* components that render dates re-render when the format is switched.
|
||||
*/
|
||||
export const dateTimeKey = (s: Settings): string => `${s.locale}|${s.dateFormat}|${s.timeFormat}`;
|
||||
|
||||
@@ -108,7 +108,7 @@ export const useSieve = create<SieveState>((set, get) => ({
|
||||
|
||||
async saveRules(rules) {
|
||||
const existing = get().scripts.find((s) => s.name === IHASMAIL_SCRIPT) ?? null;
|
||||
// The last line of defence. Writing rules replaces the whole script, so
|
||||
// The last line of defense. Writing rules replaces the whole script, so
|
||||
// doing it from a baseline we never managed to read deletes whatever was
|
||||
// there. Refusing is recoverable; overwriting is not.
|
||||
if (existing) {
|
||||
|
||||
+20
-20
@@ -151,15 +151,15 @@
|
||||
|
||||
*
|
||||
|
||||
* Every colour is from the palette's own project (all MIT); the published
|
||||
* Every color is from the palette's own project (all MIT); the published
|
||||
|
||||
* values are recorded in .palette-sources/palettes-upstream.md. The tiers
|
||||
|
||||
* between them are derived, and every text colour is checked against the
|
||||
* between them are derived, and every text color is checked against the
|
||||
|
||||
* surface it sits on: 4.5:1 for prose, 3:1 for borders and marks. Several
|
||||
|
||||
* of these palettes do not meet that as published -- Dracula's comment grey
|
||||
* of these palettes do not meet that as published -- Dracula's comment gray
|
||||
|
||||
* is about 3.0:1 on its own background -- so those tiers are lifted, which
|
||||
|
||||
@@ -1098,7 +1098,7 @@ img { max-width: 100%; }
|
||||
.menu-item:hover, .menu-item.active { background: var(--bg-hover); }
|
||||
.menu-item:disabled { opacity: .5; cursor: default; }
|
||||
/* A menu entry that is a link still looks like a menu entry. The global rule
|
||||
for `a` would otherwise colour and underline the one item that leaves the
|
||||
for `a` would otherwise color and underline the one item that leaves the
|
||||
app, which reads as a mistake rather than a distinction. */
|
||||
a.menu-item { text-decoration: none; color: var(--fg); cursor: pointer; }
|
||||
a.menu-item:hover { color: var(--fg); }
|
||||
@@ -1261,7 +1261,7 @@ a.menu-item:hover { color: var(--fg); }
|
||||
.topbar-actions { display: flex; align-items: center; gap: 4px; }
|
||||
/* Live-updates indicator. Three states, and a raised bead rather than a flat
|
||||
speck: at 8px flat it was invisible against either theme. The gloss is a
|
||||
highlight over a solid colour rather than a colour-mix, so it needs no
|
||||
highlight over a solid color rather than a color-mix, so it needs no
|
||||
per-theme variant -- the same bead reads on light and dark alike. */
|
||||
.push-status { display: inline-flex; align-items: center; padding: 0 4px; }
|
||||
.push-dot {
|
||||
@@ -1289,7 +1289,7 @@ a.menu-item:hover { color: var(--fg); }
|
||||
.app-body.resizing, .app-body.resizing > .sidebar-splitter { transition: none; }
|
||||
/* The sidebar edge (#345). Laid over the seam rather than given a grid column:
|
||||
the sidebar and the content meet at the content's own border, and a 6px
|
||||
bar of border colour between them would draw a line the design never had.
|
||||
bar of border color between them would draw a line the design never had.
|
||||
Out of the flow, it shows only on hover and focus, like the list splitter. */
|
||||
.app-body > .sidebar-splitter { position: absolute; top: 0; bottom: 0; left: calc(var(--sidebar-w) - 3px); background: transparent; transition: left .2s var(--ease); }
|
||||
.app-body > .sidebar-splitter:hover, .app-body > .sidebar-splitter:focus-visible { background: var(--accent); }
|
||||
@@ -1310,9 +1310,9 @@ a.menu-item:hover { color: var(--fg); }
|
||||
.nav-item.active.unread .nav-label, .nav-item.active.unread .nav-count { color: inherit; }
|
||||
.nav-item.drop-target { background: var(--accent-soft); outline: 2px dashed var(--accent); outline-offset: -2px; }
|
||||
.nav-item.folder-row.dragging { opacity: .45; }
|
||||
/* A folder colour tints its icon; the label keeps the sidebar's contrast. */
|
||||
/* A folder color tints its icon; the label keeps the sidebar's contrast. */
|
||||
.folder-row .folder-icon { display: inline-flex; align-items: center; }
|
||||
/* .nav-item svg sets colour on the svg itself, so inheriting from the span is
|
||||
/* .nav-item svg sets color on the svg itself, so inheriting from the span is
|
||||
not enough -- the icon has to be targeted directly to win the cascade.
|
||||
The fill overrides lucide's own fill="none": a CSS rule beats a presentation
|
||||
attribute, and a solid folder reads at a glance where an outline does not. */
|
||||
@@ -1535,7 +1535,7 @@ a.menu-item:hover { color: var(--fg); }
|
||||
.vcard-card { margin: 0 16px 12px; padding: 12px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--bg-sunken); display: flex; align-items: center; gap: 12px; }
|
||||
.unsubscribe-row { margin: 0 16px 8px; font-size: .88em; color: var(--fg-muted); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.reply-box { margin: 8px 16px 24px; }
|
||||
/* Wraps, because it does not fit. Three labelled buttons and an overflow need
|
||||
/* Wraps, because it does not fit. Three labeled buttons and an overflow need
|
||||
about 390px of it, which a 430px phone has and a 360px one does not -- and it
|
||||
was already over the line on the smaller ones before the overflow was added. */
|
||||
.reply-box .reply-prompt { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius); color: var(--fg-muted); }
|
||||
@@ -1578,7 +1578,7 @@ a.menu-item:hover { color: var(--fg); }
|
||||
|
||||
/*
|
||||
* A native <select>'s dropdown is painted by the browser from the element's own
|
||||
* colours, not the page's. `.from-select` is deliberately transparent so it
|
||||
* colors, not the page's. `.from-select` is deliberately transparent so it
|
||||
* sits flush in the composer's From line -- which left its popup with no
|
||||
* background of its own, so the browser drew a light one while the text kept
|
||||
* the app's light foreground: light on light, unreadable in any dark theme.
|
||||
@@ -1878,7 +1878,7 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); }
|
||||
.ev-chip.timed:hover { background: var(--bg-hover) !important; }
|
||||
.ev-chip.declined { text-decoration: line-through; opacity: .6; }
|
||||
.ev-chip.tentative { border-style: dashed; opacity: .85; }
|
||||
.ev-chip.cancelled { text-decoration: line-through; opacity: .5; }
|
||||
.ev-chip.canceled { text-decoration: line-through; opacity: .5; }
|
||||
.week-view { flex: 1; display: flex; flex-direction: column; min-height: 0; }
|
||||
.week-head { display: grid; grid-template-columns: 56px repeat(var(--cols, 7), 1fr); border-bottom: 1px solid var(--border); flex: 0 0 auto; }
|
||||
.week-head .wh-day { padding: 8px 4px 4px; text-align: center; border-left: 1px solid var(--border); cursor: pointer; }
|
||||
@@ -2045,7 +2045,7 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); }
|
||||
.ptr-dial.armed { color: var(--accent); border-color: var(--accent); }
|
||||
|
||||
/*
|
||||
* A tap that leaves a grey rectangle behind reads as a rendering glitch rather
|
||||
* A tap that leaves a gray rectangle behind reads as a rendering glitch rather
|
||||
* than as feedback, and every surface here has a :active or a ripple of its own.
|
||||
*/
|
||||
@media (hover: none) {
|
||||
@@ -2156,13 +2156,13 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); }
|
||||
* icons at 36px (the search filter at 30), the folder twisty at 18, the row ⋮
|
||||
* at 24. Growing the boxes would reflow a top bar that has no room to give, so
|
||||
* each control keeps the size it draws at and gains a transparent hit area
|
||||
* centred on it. Rows grow for real, because a 44px hit area inside a 36px row
|
||||
* centered on it. Rows grow for real, because a 44px hit area inside a 36px row
|
||||
* would reach into the rows above and below and steal their taps.
|
||||
*/
|
||||
@media (pointer: coarse) {
|
||||
.icon-btn, .nav-twisty { position: relative; }
|
||||
/* `.drill-into` already draws at 44px and is excluded: expanding a control
|
||||
that is big enough only lets it reach into its neighbour. */
|
||||
that is big enough only lets it reach into its neighbor. */
|
||||
.icon-btn:not(.drill-into)::after, .nav-twisty[role="button"]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
@@ -2199,11 +2199,11 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); }
|
||||
*
|
||||
* Paper has no theme. Whatever palette is on screen, the tokens are pinned to
|
||||
* a light, unpainted set here -- backgrounds white rather than the light
|
||||
* theme's greys, since a printer would otherwise lay ink over the whole sheet.
|
||||
* theme's grays, since a printer would otherwise lay ink over the whole sheet.
|
||||
* Doing it on the tokens rather than per rule is what reaches the message
|
||||
* body: it renders in a shadow root this stylesheet cannot select into, and
|
||||
* follows the app theme through inherited custom properties (see
|
||||
* EMAIL_BASE_CSS in lib/html.ts). Mail that brings colours of its own is left
|
||||
* EMAIL_BASE_CSS in lib/html.ts). Mail that brings colors of its own is left
|
||||
* alone -- it is not themed in the first place.
|
||||
*
|
||||
* `!important` rather than a longer selector: the dark palettes sit on
|
||||
@@ -2250,7 +2250,7 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); }
|
||||
.app, .app-body, .main, .mail-layout, .mail-reading-pane, .thread-view, .thread-scroll { display: block !important; height: auto !important; overflow: visible !important; grid-template-columns: 1fr !important; border: 0 !important; }
|
||||
/*
|
||||
* `break-inside: avoid` on the whole card is what put the first message on
|
||||
* page two: a message longer than a sheet cannot honour it, and Chrome
|
||||
* page two: a message longer than a sheet cannot honor it, and Chrome
|
||||
* answers by moving the card to a fresh page and breaking it there anyway --
|
||||
* leaving page one holding nothing but the subject. Only the header is
|
||||
* indivisible now, and it is kept with the body that follows it.
|
||||
@@ -2385,11 +2385,11 @@ button.dp-open:disabled { cursor: default; opacity: .5; }
|
||||
/* The senders whose remote images load without asking, in Privacy & safety. */
|
||||
.trusted-senders { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
|
||||
|
||||
/* The banner naming a sender from outside the organisation. */
|
||||
/* The banner naming a sender from outside the organization. */
|
||||
.remote-banner.external-banner { background: var(--warn-soft); border-color: var(--warn); }
|
||||
/*
|
||||
* The signature banner's colour is the claim it is making, so the quiet tones
|
||||
* are deliberate. "Signed by somebody new" is grey, because an unknown
|
||||
* The signature banner's color is the claim it is making, so the quiet tones
|
||||
* are deliberate. "Signed by somebody new" is gray, because an unknown
|
||||
* certificate that verifies against itself has established nothing worth a
|
||||
* green tick; green is kept for the one case that earned it, a signer matching
|
||||
* what was pinned the first time. Red is for a signer that changed.
|
||||
|
||||
@@ -33,7 +33,7 @@ export function isDomMutationError(err: unknown): boolean {
|
||||
// NotFoundError is what removeChild/insertBefore throw when the node they
|
||||
// were given is not where React last saw it. The name is checked first
|
||||
// because it is the reliable half -- the message is browser-specific and
|
||||
// localised, so matching on it alone would work in English Chrome and
|
||||
// localized, so matching on it alone would work in English Chrome and
|
||||
// nowhere else, which for a translation bug would be a poor joke.
|
||||
if (err.name === "NotFoundError" || err.name === "HierarchyRequestError") return true;
|
||||
return /removeChild|insertBefore|replaceChild|not a child of this node/i.test(err.message);
|
||||
@@ -83,7 +83,7 @@ export class TranslateBoundary extends Component<Props, State> {
|
||||
/*
|
||||
* console.info, not console.error. A reader translating the page is not a
|
||||
* fault, and logging it as one would put an entry in every error reporter
|
||||
* that reads the console, for behaviour that is expected and recovered
|
||||
* that reads the console, for behavior that is expected and recovered
|
||||
* from. The marker is here to be counted, not alarmed at.
|
||||
*/
|
||||
console.info(
|
||||
|
||||
@@ -12,7 +12,7 @@ import { TranslateBoundary, isDomMutationError } from "../TranslateBoundary";
|
||||
* (facebook/react#11538). The boundary's job is to put the subtree back
|
||||
* instead, and to leave everything that is not that alone.
|
||||
*/
|
||||
describe("recognising the translator's damage", () => {
|
||||
describe("recognizing the translator's damage", () => {
|
||||
it("knows the DOM errors Chrome's rewriting produces", () => {
|
||||
const notFound = new Error("Failed to execute 'removeChild' on 'Node'");
|
||||
notFound.name = "NotFoundError";
|
||||
@@ -21,11 +21,11 @@ describe("recognising the translator's damage", () => {
|
||||
});
|
||||
|
||||
it("matches on the error name as well as the message", () => {
|
||||
// The message is browser-specific and localised. Matching only on English
|
||||
// The message is browser-specific and localized. Matching only on English
|
||||
// text would be a translation bug that only works in English.
|
||||
const localised = new Error("Знайдений вузол не є дочірнім");
|
||||
localised.name = "NotFoundError";
|
||||
expect(isDomMutationError(localised)).toBe(true);
|
||||
const localized = new Error("Знайдений вузол не є дочірнім");
|
||||
localized.name = "NotFoundError";
|
||||
expect(isDomMutationError(localized)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not claim an ordinary bug", () => {
|
||||
@@ -84,7 +84,7 @@ describe("the boundary", () => {
|
||||
it("logs the recovery as information, not as an error", () => {
|
||||
// A reader translating the page is expected and recovered from. Logging it
|
||||
// as an error would file a bug report in every console-reading reporter,
|
||||
// every time, for behaviour that worked.
|
||||
// every time, for behavior that worked.
|
||||
const fails = { left: 2 };
|
||||
const info = vi.spyOn(console, "info").mockImplementation(() => {});
|
||||
const error = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
|
||||
@@ -168,7 +168,7 @@ function toIsoDateTime(d: Date): string {
|
||||
return `${toLocalDateOnly(d)}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
}
|
||||
|
||||
/** Shared text-box behaviour: type freely, commit on blur or Enter, revert what won't parse. */
|
||||
/** Shared text-box behavior: type freely, commit on blur or Enter, revert what won't parse. */
|
||||
function useTextField(value: string, display: (v: string) => string, commit: (text: string) => boolean) {
|
||||
const [text, setText] = useState(() => display(value));
|
||||
const [editing, setEditing] = useState(false);
|
||||
|
||||
@@ -97,7 +97,7 @@ export interface DialogChoice {
|
||||
* The safe answer, given the weight a dialog's confirm button has.
|
||||
*
|
||||
* A list of choices has no default until one is said to be, and the
|
||||
* destructive one must not become it by being the only thing with a colour --
|
||||
* destructive one must not become it by being the only thing with a color --
|
||||
* which is what "Discard changes" was, on a guard whose whole purpose is to
|
||||
* stop you losing work ([#175]).
|
||||
*
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user