A tenant's administrator can turn legacy mail apps off for the organization

INBUXA's tenant switch (legacy-protocols LP-9 to LP-18) in the
administration. Each tenant's sheet gains "Legacy mail apps": whether IMAP,
POP3, ManageSieve and sending from mail apps are on or off on the tenant's
domains, and the switch.

Nobody turns it off by accident. "Turn off legacy protocols…" first shows
who would notice -- every account in the tenant that signed in with a
legacy mail app in the last 30 days, with the protocols and when (LP-15) --
and the statement of what it means, "for everyone in {tenant}" (LP-16),
then asks for the phrase "turn off legacy mail", matched exactly (LP-17).
Turning it back on is one click; the server refuses while it has legacy
protocols off for everyone, and its words are shown. A tenant's switch
closes no port, so the statement names none.

It needs the domain permissions the server checks for the switch; with
read-only access the state shows and the buttons don't. On a server that
isn't INBUXA, or is older, the section isn't there.

The dashboard says so while legacy mail is off for the signed-in
administrator's organization (LP-18), from the same session flag as
Settings › Security's line.

Every new string in all nine languages, the register each catalog uses, and
the count in each language's plural forms.
This commit is contained in:
2026-09-21 13:54:24 -07:00
parent 4cac9088dd
commit 8abf3a96aa
16 changed files with 597 additions and 1 deletions
@@ -0,0 +1,30 @@
import { describe, expect, it } from "vitest";
import { CONFIRM_PHRASE, impactEntries, parseTenantLegacy, phraseMatches } from "../adminLegacyProtocols";
describe("a tenant's legacy mail protocols switch, as the server sends it", () => {
it("reads the switch, and tells an older server from nobody", () => {
expect(parseTenantLegacy({ legacyProtocols: "disabled", recentLegacyUse: [] })).toEqual({ off: true, recent: [] });
expect(parseTenantLegacy({ legacyProtocols: "enabled" })).toEqual({ off: false, recent: null });
});
it("puts each account on the panel once, with every protocol and its latest use", () => {
const { recent } = parseTenantLegacy({
recentLegacyUse: [
{ accountId: "a", name: "[email protected]", protocol: "submission", lastUsedAt: 100 },
{ accountId: "a", name: "[email protected]", protocol: "imap", lastUsedAt: 300 },
{ accountId: "b", name: "[email protected]", protocol: "pop3", lastUsedAt: 200 },
{ accountId: "c", name: "broken" },
],
});
expect(impactEntries(recent!)).toEqual([
{ name: "[email protected]", protocols: ["IMAP", "SMTP"], lastUsedAt: 300 },
{ name: "[email protected]", protocols: ["POP3"], lastUsedAt: 200 },
]);
});
it("takes only the exact phrase", () => {
expect(phraseMatches(CONFIRM_PHRASE)).toBe(true);
expect(phraseMatches(` ${CONFIRM_PHRASE}`)).toBe(false);
expect(phraseMatches(CONFIRM_PHRASE.toUpperCase())).toBe(false);
});
});
+104
View File
@@ -0,0 +1,104 @@
import { client, INBUXA_CAP } from "@/jmap/client";
/**
* One tenant's legacy mail protocols switch: INBUXA's
* `inbuxa:TenantProtocolPolicy` (legacy-protocols LP-9 to LP-18).
*
* Turning it off refuses sign-in over IMAP, POP3, ManageSieve and SMTP
* submission on the tenant's domains, so only this webmail and other JMAP
* apps work there. It closes no port -- other tenants share them. Turning it
* back on is refused by the server while the server has legacy protocols off
* for everyone.
*
* Only INBUXA serves it. On any other server the method is unknown, which
* `fetchTenantLegacy` reports as `null` so the sheet shows nothing.
*/
const OBJECT = "inbuxa:TenantProtocolPolicy";
/** The phrase that turns legacy protocols off (LP-17). Turning them back on needs none. */
export const CONFIRM_PHRASE = "turn off legacy mail";
/** Whether the typed confirmation matches: exactly, no trimming, no case folding. */
export function phraseMatches(typed: string): boolean {
return typed === CONFIRM_PHRASE;
}
/** One account's last sign-in over one legacy protocol, as the server reports it (LP-15). */
export interface RecentUse {
accountId: string;
name: string;
protocol: string;
/** Milliseconds since the epoch. */
lastUsedAt: number;
}
export interface TenantLegacy {
off: boolean;
/** Null from a server too old to say who uses legacy apps -- not the same as nobody. */
recent: RecentUse[] | null;
}
function parseRecent(raw: unknown): RecentUse[] | null {
if (!Array.isArray(raw)) return null;
return raw.flatMap((entry) => {
if (!entry || typeof entry !== "object") return [];
const r = entry as Record<string, unknown>;
if (typeof r.name !== "string" || typeof r.protocol !== "string" || typeof r.lastUsedAt !== "number") return [];
return [{ accountId: typeof r.accountId === "string" ? r.accountId : "", name: r.name, protocol: r.protocol, lastUsedAt: r.lastUsedAt }];
});
}
export function parseTenantLegacy(raw: Record<string, unknown>): TenantLegacy {
return { off: raw.legacyProtocols === "disabled", recent: parseRecent(raw.recentLegacyUse) };
}
/** The tenant's switch, or null where the server has none (not INBUXA, or too old). */
export async function fetchTenantLegacy(tenantId: string): Promise<TenantLegacy | null> {
try {
const res = await client.call<{ list?: Record<string, unknown>[] }>(`${OBJECT}/get`, { ids: [tenantId] }, [INBUXA_CAP]);
return res.list?.[0] ? parseTenantLegacy(res.list[0]) : null;
} catch {
return null;
}
}
/** Turns the tenant's switch. A refusal (LP-9) comes back as the server's words. */
export async function setTenantLegacy(tenantId: string, off: boolean): Promise<void> {
const res = await client.call<{ notUpdated?: Record<string, { type: string; description?: string }> | null }>(
`${OBJECT}/set`,
{ update: { [tenantId]: { legacyProtocols: off ? "disabled" : "enabled" } } },
[INBUXA_CAP],
);
const failed = res.notUpdated?.[tenantId];
if (failed) throw new Error(failed.description ?? failed.type);
}
const PROTOCOL_ORDER = ["imap", "pop3", "manageSieve", "submission"];
const PROTOCOL_LABELS: Record<string, string> = { imap: "IMAP", pop3: "POP3", manageSieve: "ManageSieve", submission: "SMTP" };
/** One account on the impact panel: every protocol it used, and when it last used any. */
export interface ImpactEntry {
name: string;
protocols: string[];
lastUsedAt: number;
}
/** The impact panel's lines (LP-15): one per account, most recent first. */
export function impactEntries(recent: RecentUse[]): ImpactEntry[] {
const byAccount = new Map<string, { name: string; protocols: Set<string>; lastUsedAt: number }>();
for (const use of recent) {
const key = use.accountId || use.name;
const entry = byAccount.get(key) ?? { name: use.name, protocols: new Set<string>(), lastUsedAt: 0 };
entry.protocols.add(use.protocol);
entry.lastUsedAt = Math.max(entry.lastUsedAt, use.lastUsedAt);
byAccount.set(key, entry);
}
return [...byAccount.values()]
.map((e) => ({
name: e.name,
protocols: [...e.protocols].sort((a, b) => PROTOCOL_ORDER.indexOf(a) - PROTOCOL_ORDER.indexOf(b)).map((p) => PROTOCOL_LABELS[p] ?? p),
lastUsedAt: e.lastUsedAt,
}))
.sort((a, b) => b.lastUsedAt - a.lastUsedAt || a.name.localeCompare(b.name));
}
+21
View File
@@ -1721,8 +1721,29 @@ export const catalog: Catalog = {
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} weist Sie darauf hin, wenn eine spätere Nachricht von dieser Adresse von jemand anderem signiert ist.",
"itself, or an issuer it does not name": "sich selbst, oder einem nicht genannten Aussteller",
"no address": "keine Adresse",
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"Legacy mail apps": "Mail-Apps über ältere Protokolle",
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Aus für {tenant}. Nur {app} und JMAP-Apps können sich bei den Domains dieser Organisation anmelden.",
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "An für {tenant}. Mail-Apps können auf den Domains dieser Organisation IMAP, POP3 und ManageSieve nutzen.",
"Turn legacy protocols back on": "Ältere Mailprotokolle wieder einschalten",
"Turn off legacy protocols…": "Ältere Mailprotokolle ausschalten…",
"No account used a legacy mail app in the last 30 days.": "In den letzten 30 Tagen hat kein Konto eine Mail-App mit älteren Protokollen verwendet.",
"Their mail apps will stop working the moment you turn this on:": "Deren Mail-Apps funktionieren nicht mehr, sobald Sie dies einschalten:",
"Only {app} and JMAP apps will work.": "Nur {app} und JMAP-Apps funktionieren dann noch.",
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Ältere Mailprotokolle (IMAP, POP3, ManageSieve und das Senden aus Mail-Apps) werden für alle in {tenant} ausgeschaltet.",
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Mail-Apps auf Smartphones und Computern können keine Mails mehr empfangen und senden. Das betrifft Mail auf iPhone und iPad, die Gmail- und Outlook-Apps, Outlook, Thunderbird und Apple Mail. Dort erscheinen Anmeldefehler.",
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Filter, die über eine Mail-App verwaltet werden (ManageSieve), funktionieren nicht mehr. In {app} eingerichtete Filter funktionieren weiterhin.",
"Incoming mail is not affected. Calendars and contacts are not affected.": "Eingehende Mails sind nicht betroffen. Kalender und Kontakte sind nicht betroffen.",
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Alle behalten vollen Zugriff über {app}, das sich auf Smartphones und Computern als App installieren lässt.",
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "Das Senden aus Mail-Apps (SMTP-Submission) funktioniert nicht mehr, die Ports bleiben aber offen: Mail-Apps erfahren, dass sie sich nicht anmelden können. Eingehende Mails (SMTP) und {app} (JMAP) sind nicht betroffen und lassen sich hier nicht ausschalten.",
"You can turn legacy protocols back on at any time.": "Sie können ältere Mailprotokolle jederzeit wieder einschalten.",
"To confirm, type {phrase}": "Zur Bestätigung {phrase} eingeben",
"Turn off legacy protocols": "Ältere Mailprotokolle ausschalten",
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Ältere Mailprotokolle sind für Ihre Organisation ausgeschaltet. Nur {app} und JMAP-Apps können sich anmelden.",
},
plurals: {
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} Konto hat in den letzten 30 Tagen eine Mail-App mit älteren Protokollen verwendet.", other: "{n} Konten haben in den letzten 30 Tagen eine Mail-App mit älteren Protokollen verwendet."},
// ── Administration: domains ────────────────────────────────────
"{n} accounts use this domain. Move or delete them first.": { one: "{n} Konto verwendet diese Domain. Verschieben oder löschen Sie es zuerst.", other: "{n} Konten verwenden diese Domain. Verschieben oder löschen Sie sie zuerst." },
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Der Server nimmt keine E-Mails mehr für diese Domain an, und ihr {n} DKIM-Schlüssel wird gelöscht. Dies kann nicht rückgängig gemacht werden.", other: "Der Server nimmt keine E-Mails mehr für diese Domain an, und ihre {n} DKIM-Schlüssel werden gelöscht. Dies kann nicht rückgängig gemacht werden." },
+21
View File
@@ -1694,8 +1694,29 @@ export const catalog: Catalog = {
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} le avisará si un mensaje posterior de esta dirección lo firma otra persona.",
"itself, or an issuer it does not name": "sí mismo, o un emisor que no nombra",
"no address": "ninguna dirección",
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"Legacy mail apps": "Aplicaciones de correo con protocolos heredados",
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Desactivados para {tenant}. Solo {app} y las aplicaciones JMAP pueden iniciar sesión en sus dominios.",
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Activados para {tenant}. Las aplicaciones de correo pueden usar IMAP, POP3 y ManageSieve en sus dominios.",
"Turn legacy protocols back on": "Volver a activar los protocolos de correo heredados",
"Turn off legacy protocols…": "Desactivar los protocolos de correo heredados…",
"No account used a legacy mail app in the last 30 days.": "Ninguna cuenta ha usado una aplicación de correo con protocolos heredados en los últimos 30 días.",
"Their mail apps will stop working the moment you turn this on:": "Sus aplicaciones de correo dejarán de funcionar en cuanto active esto:",
"Only {app} and JMAP apps will work.": "Solo funcionarán {app} y las aplicaciones JMAP.",
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Los protocolos de correo heredados (IMAP, POP3, ManageSieve y el envío desde aplicaciones de correo) se desactivarán para todos en {tenant}.",
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Las aplicaciones de correo del teléfono y del ordenador dejarán de recibir y enviar correo. Eso incluye Mail de iPhone y iPad, las aplicaciones de Gmail y Outlook, Outlook, Thunderbird y Apple Mail. Se verán errores de inicio de sesión en ellas.",
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Los filtros gestionados desde una aplicación de correo (ManageSieve) dejarán de funcionar. Los filtros configurados en {app} siguen funcionando.",
"Incoming mail is not affected. Calendars and contacts are not affected.": "El correo entrante no se ve afectado. Los calendarios y los contactos no se ven afectados.",
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Todos conservan el acceso completo a través de {app}, que se puede instalar como aplicación en teléfonos y ordenadores.",
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "El envío desde aplicaciones de correo (envío SMTP) dejará de funcionar, pero sus puertos siguen abiertos: se indicará a las aplicaciones de correo que no pueden iniciar sesión. El correo entrante (SMTP) y {app} (JMAP) no se ven afectados y no se pueden desactivar aquí.",
"You can turn legacy protocols back on at any time.": "Puede volver a activar los protocolos de correo heredados en cualquier momento.",
"To confirm, type {phrase}": "Para confirmar, escriba {phrase}",
"Turn off legacy protocols": "Desactivar los protocolos de correo heredados",
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Los protocolos de correo heredados están desactivados para su organización. Solo {app} y las aplicaciones JMAP pueden iniciar sesión.",
},
plurals: {
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} cuenta ha usado una aplicación de correo con protocolos heredados en los últimos 30 días.", other: "{n} cuentas han usado una aplicación de correo con protocolos heredados en los últimos 30 días."},
// ── Administration: domains ────────────────────────────────────
"{n} accounts use this domain. Move or delete them first.": { one: "{n} cuenta usa este dominio. Muévala o elimínela primero.", other: "{n} cuentas usan este dominio. Muévalas o elimínelas primero." },
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "El servidor deja de aceptar correo para este dominio y se elimina su {n} clave DKIM. No se puede deshacer.", other: "El servidor deja de aceptar correo para este dominio y se eliminan sus {n} claves DKIM. No se puede deshacer." },
+21
View File
@@ -1699,8 +1699,29 @@ export const catalog: Catalog = {
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} vous préviendra si un message ultérieur de cette adresse est signé par quelqu'un d'autre.",
"itself, or an issuer it does not name": "lui-même, ou un émetteur qu'il ne nomme pas",
"no address": "aucune adresse",
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"Legacy mail apps": "Applications de messagerie à protocoles historiques",
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Désactivés pour {tenant}. Seuls {app} et les applications JMAP peuvent se connecter à ses domaines.",
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Activés pour {tenant}. Les applications de messagerie peuvent utiliser IMAP, POP3 et ManageSieve sur ses domaines.",
"Turn legacy protocols back on": "Réactiver les protocoles de messagerie historiques",
"Turn off legacy protocols…": "Désactiver les protocoles de messagerie historiques…",
"No account used a legacy mail app in the last 30 days.": "Aucun compte n'a utilisé d'application de messagerie à protocoles historiques ces 30 derniers jours.",
"Their mail apps will stop working the moment you turn this on:": "Leurs applications de messagerie cesseront de fonctionner dès que vous activerez ceci :",
"Only {app} and JMAP apps will work.": "Seuls {app} et les applications JMAP fonctionneront.",
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Les protocoles de messagerie historiques (IMAP, POP3, ManageSieve et l'envoi depuis les applications de messagerie) seront désactivés pour tout le monde dans {tenant}.",
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Les applications de messagerie sur téléphone et ordinateur ne pourront plus recevoir ni envoyer de courrier : Mail sur iPhone et iPad, les applications Gmail et Outlook, Outlook, Thunderbird et Apple Mail. Des erreurs de connexion s'y afficheront.",
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Les filtres gérés depuis une application de messagerie (ManageSieve) cesseront de fonctionner. Les filtres configurés dans {app} continuent de fonctionner.",
"Incoming mail is not affected. Calendars and contacts are not affected.": "Le courrier entrant n'est pas concerné. Les agendas et les contacts ne sont pas concernés.",
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Chacun garde un accès complet via {app}, qui peut s'installer comme application sur téléphone et ordinateur.",
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "L'envoi depuis les applications de messagerie (soumission SMTP) cessera de fonctionner, mais ses ports restent ouverts : les applications de messagerie seront informées qu'elles ne peuvent pas se connecter. Le courrier entrant (SMTP) et {app} (JMAP) ne sont pas concernés et ne peuvent pas être désactivés ici.",
"You can turn legacy protocols back on at any time.": "Vous pouvez réactiver les protocoles de messagerie historiques à tout moment.",
"To confirm, type {phrase}": "Pour confirmer, saisissez {phrase}",
"Turn off legacy protocols": "Désactiver les protocoles de messagerie historiques",
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Les protocoles de messagerie historiques sont désactivés pour votre organisation. Seuls {app} et les applications JMAP peuvent se connecter.",
},
plurals: {
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} compte a utilisé une application de messagerie à protocoles historiques ces 30 derniers jours.", other: "{n} comptes ont utilisé une application de messagerie à protocoles historiques ces 30 derniers jours."},
// ── Administration: domains ────────────────────────────────────
"{n} accounts use this domain. Move or delete them first.": { one: "{n} compte utilise ce domaine. Déplacez-le ou supprimez-le dabord.", other: "{n} comptes utilisent ce domaine. Déplacez-les ou supprimez-les dabord." },
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Le serveur naccepte plus de messages pour ce domaine, et sa {n} clé DKIM est supprimée. Cest irréversible.", other: "Le serveur naccepte plus de messages pour ce domaine, et ses {n} clés DKIM sont supprimées. Cest irréversible." },
+21
View File
@@ -1702,8 +1702,29 @@ export const catalog: Catalog = {
"{app} will tell you if a later message from this address is signed by anybody else.": "このアドレスからの以降のメールが別の人の署名だった場合、{app} がお知らせします。",
"itself, or an issuer it does not name": "自分自身、または名前のない発行者",
"no address": "アドレスなし",
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"Legacy mail apps": "従来のプロトコルを使うメールアプリ",
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "{tenant} ではオフです。そのドメインにサインインできるのは {app} と JMAP アプリのみです。",
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "{tenant} ではオンです。そのドメインではメールアプリが IMAP、POP3、ManageSieve を利用できます。",
"Turn legacy protocols back on": "従来のメールプロトコルを再びオンにする",
"Turn off legacy protocols…": "従来のメールプロトコルをオフにする…",
"No account used a legacy mail app in the last 30 days.": "過去 30 日間に従来のプロトコルでメールアプリを使用したアカウントはありません。",
"Their mail apps will stop working the moment you turn this on:": "これをオンにした時点で、これらのメールアプリは動作しなくなります:",
"Only {app} and JMAP apps will work.": "{app} と JMAP アプリのみが動作します。",
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "{tenant} のすべてのユーザーに対して、従来のメールプロトコル(IMAP、POP3、ManageSieve、メールアプリからの送信)がオフになります。",
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "スマートフォンやパソコンのメールアプリでメールの送受信ができなくなります。iPhone と iPad のメール、Gmail と Outlook のアプリ、Outlook、Thunderbird、Apple Mail が対象です。これらのアプリにはサインインエラーが表示されます。",
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "メールアプリから管理しているフィルタ(ManageSieve)は動作しなくなります。{app} で設定したフィルタは引き続き動作します。",
"Incoming mail is not affected. Calendars and contacts are not affected.": "受信メールには影響しません。カレンダーと連絡先にも影響しません。",
"People keep full access through {app}, which can be installed as an app on phones and computers.": "{app} からは引き続きすべて利用できます。{app} はスマートフォンやパソコンにアプリとしてインストールできます。",
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "メールアプリからの送信(SMTP submission)は動作しなくなりますが、ポートは開いたままです。メールアプリにはサインインできないことが通知されます。受信メール(SMTP)と {app}(JMAP)には影響せず、ここでオフにすることはできません。",
"You can turn legacy protocols back on at any time.": "従来のメールプロトコルはいつでも再びオンにできます。",
"To confirm, type {phrase}": "確認のため {phrase} と入力してください",
"Turn off legacy protocols": "従来のメールプロトコルをオフにする",
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "組織では従来のメールプロトコルがオフになっています。サインインできるのは {app} と JMAP アプリのみです。",
},
plurals: {
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"{n} accounts used a legacy mail app in the last 30 days.": {other: "過去 30 日間に {n} 件のアカウントが従来のプロトコルでメールアプリを使用しました。"},
// ── Administration: domains ────────────────────────────────────
"{n} accounts use this domain. Move or delete them first.": { other: "{n} 件のアカウントがこのドメインを使用しています。先に移動または削除してください。" },
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { other: "サーバーはこのドメイン宛てのメールを受け付けなくなり、{n} 個の DKIM 鍵も削除されます。元に戻すことはできません。" },
+21
View File
@@ -1691,8 +1691,29 @@ export const catalog: Catalog = {
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} laat het weten als een later bericht van dit adres door iemand anders is ondertekend.",
"itself, or an issuer it does not name": "zichzelf, of een uitgever die niet wordt genoemd",
"no address": "geen adres",
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"Legacy mail apps": "Mail-apps met verouderde protocollen",
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Uit voor {tenant}. Alleen {app} en JMAP-apps kunnen inloggen op de domeinen ervan.",
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Aan voor {tenant}. Mail-apps kunnen IMAP, POP3 en ManageSieve gebruiken op de domeinen ervan.",
"Turn legacy protocols back on": "Verouderde mailprotocollen weer inschakelen",
"Turn off legacy protocols…": "Verouderde mailprotocollen uitschakelen…",
"No account used a legacy mail app in the last 30 days.": "Geen enkel account heeft de afgelopen 30 dagen een mail-app met verouderde protocollen gebruikt.",
"Their mail apps will stop working the moment you turn this on:": "Hun mail-apps werken niet meer zodra u dit inschakelt:",
"Only {app} and JMAP apps will work.": "Alleen {app} en JMAP-apps werken dan nog.",
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Verouderde mailprotocollen (IMAP, POP3, ManageSieve en verzenden vanuit mail-apps) worden uitgeschakeld voor iedereen in {tenant}.",
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Mail-apps op telefoon en computer kunnen geen mail meer ontvangen en verzenden. Het gaat om Mail op iPhone en iPad, de Gmail- en Outlook-apps, Outlook, Thunderbird en Apple Mail. Gebruikers zien daarin inlogfouten.",
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Filters die vanuit een mail-app worden beheerd (ManageSieve) werken niet meer. Filters die in {app} zijn ingesteld blijven werken.",
"Incoming mail is not affected. Calendars and contacts are not affected.": "Inkomende mail wordt niet beïnvloed. Agenda's en contacten worden niet beïnvloed.",
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Iedereen houdt volledige toegang via {app}, dat als app op telefoons en computers kan worden geïnstalleerd.",
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "Verzenden vanuit mail-apps (SMTP-submission) werkt niet meer, maar de poorten blijven open: mail-apps krijgen te horen dat ze niet kunnen inloggen. Inkomende mail (SMTP) en {app} (JMAP) worden niet beïnvloed en kunnen hier niet worden uitgeschakeld.",
"You can turn legacy protocols back on at any time.": "U kunt verouderde mailprotocollen op elk moment weer inschakelen.",
"To confirm, type {phrase}": "Typ ter bevestiging {phrase}",
"Turn off legacy protocols": "Verouderde mailprotocollen uitschakelen",
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Verouderde mailprotocollen zijn uitgeschakeld voor uw organisatie. Alleen {app} en JMAP-apps kunnen inloggen.",
},
plurals: {
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} account heeft de afgelopen 30 dagen een mail-app met verouderde protocollen gebruikt.", other: "{n} accounts hebben de afgelopen 30 dagen een mail-app met verouderde protocollen gebruikt."},
// ── Administration: domains ────────────────────────────────────
"{n} accounts use this domain. Move or delete them first.": { one: "{n} account gebruikt dit domein. Verplaats of verwijder deze eerst.", other: "{n} accounts gebruiken dit domein. Verplaats of verwijder ze eerst." },
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "De server accepteert geen e-mails meer voor dit domein en de {n} DKIM-sleutel wordt verwijderd. Dit kan niet ongedaan worden gemaakt.", other: "De server accepteert geen e-mails meer voor dit domein en de {n} DKIM-sleutels worden verwijderd. Dit kan niet ongedaan worden gemaakt." },
+21
View File
@@ -1697,8 +1697,29 @@ export const catalog: Catalog = {
"{app} will tell you if a later message from this address is signed by anybody else.": "O {app} avisará você se uma mensagem posterior deste endereço for assinada por outra pessoa.",
"itself, or an issuer it does not name": "ele mesmo, ou um emissor que ele não nomeia",
"no address": "nenhum endereço",
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"Legacy mail apps": "Aplicativos de e-mail com protocolos legados",
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Desativados para {tenant}. Só {app} e aplicativos JMAP podem entrar nos domínios dessa organização.",
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Ativados para {tenant}. Aplicativos de e-mail podem usar IMAP, POP3 e ManageSieve nos domínios dessa organização.",
"Turn legacy protocols back on": "Reativar os protocolos de e-mail legados",
"Turn off legacy protocols…": "Desativar os protocolos de e-mail legados…",
"No account used a legacy mail app in the last 30 days.": "Nenhuma conta usou um aplicativo de e-mail com protocolos legados nos últimos 30 dias.",
"Their mail apps will stop working the moment you turn this on:": "Os aplicativos de e-mail dessas contas vão parar de funcionar assim que você ativar isto:",
"Only {app} and JMAP apps will work.": "Só {app} e aplicativos JMAP vão funcionar.",
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Os protocolos de e-mail legados (IMAP, POP3, ManageSieve e o envio por aplicativos de e-mail) serão desativados para todos em {tenant}.",
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Aplicativos de e-mail no celular e no computador vão parar de receber e enviar e-mail. Isso inclui o Mail do iPhone e do iPad, os aplicativos Gmail e Outlook, o Outlook, o Thunderbird e o Apple Mail. As pessoas verão erros de login neles.",
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Filtros gerenciados por um aplicativo de e-mail (ManageSieve) vão parar de funcionar. Filtros configurados no {app} continuam funcionando.",
"Incoming mail is not affected. Calendars and contacts are not affected.": "O e-mail recebido não é afetado. Agendas e contatos não são afetados.",
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Todos mantêm acesso completo pelo {app}, que pode ser instalado como aplicativo em celulares e computadores.",
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "O envio por aplicativos de e-mail (SMTP submission) vai parar de funcionar, mas as portas continuam abertas: os aplicativos de e-mail serão informados de que não podem entrar. O e-mail recebido (SMTP) e o {app} (JMAP) não são afetados e não podem ser desativados aqui.",
"You can turn legacy protocols back on at any time.": "Você pode reativar os protocolos de e-mail legados a qualquer momento.",
"To confirm, type {phrase}": "Para confirmar, digite {phrase}",
"Turn off legacy protocols": "Desativar os protocolos de e-mail legados",
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Os protocolos de e-mail legados estão desativados para sua organização. Só {app} e aplicativos JMAP podem entrar.",
},
plurals: {
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} conta usou um aplicativo de e-mail com protocolos legados nos últimos 30 dias.", other: "{n} contas usaram um aplicativo de e-mail com protocolos legados nos últimos 30 dias."},
// ── Administration: domains ────────────────────────────────────
"{n} accounts use this domain. Move or delete them first.": { one: "{n} conta usa este domínio. Mova-a ou exclua-a primeiro.", other: "{n} contas usam este domínio. Mova-as ou exclua-as primeiro." },
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "O servidor deixa de aceitar e-mails para este domínio, e a {n} chave DKIM dele é excluída. Não é possível desfazer.", other: "O servidor deixa de aceitar e-mails para este domínio, e as {n} chaves DKIM dele são excluídas. Não é possível desfazer." },
+21
View File
@@ -1696,8 +1696,29 @@ export const catalog: Catalog = {
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} сообщит, если следующее письмо с этого адреса подпишет кто-то другой.",
"itself, or an issuer it does not name": "самим собой или неназванным издателем",
"no address": "нет адреса",
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"Legacy mail apps": "Почтовые приложения на устаревших протоколах",
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Отключены для {tenant}. Входить на домены этой организации могут только {app} и приложения JMAP.",
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Включены для {tenant}. Почтовые приложения могут использовать IMAP, POP3 и ManageSieve на доменах этой организации.",
"Turn legacy protocols back on": "Снова включить устаревшие почтовые протоколы",
"Turn off legacy protocols…": "Отключить устаревшие почтовые протоколы…",
"No account used a legacy mail app in the last 30 days.": "За последние 30 дней ни одна учётная запись не использовала почтовое приложение на устаревших протоколах.",
"Their mail apps will stop working the moment you turn this on:": "Их почтовые приложения перестанут работать сразу после того, как вы это включите:",
"Only {app} and JMAP apps will work.": "Работать будут только {app} и приложения JMAP.",
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Устаревшие почтовые протоколы (IMAP, POP3, ManageSieve и отправка из почтовых приложений) будут отключены для всех в {tenant}.",
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Почтовые приложения на телефонах и компьютерах перестанут получать и отправлять почту. Это Почта на iPhone и iPad, приложения Gmail и Outlook, Outlook, Thunderbird и Apple Mail. В них появятся ошибки входа.",
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Фильтры, управляемые из почтового приложения (ManageSieve), перестанут работать. Фильтры, настроенные в {app}, продолжат работать.",
"Incoming mail is not affected. Calendars and contacts are not affected.": "Входящая почта не затрагивается. Календари и контакты не затрагиваются.",
"People keep full access through {app}, which can be installed as an app on phones and computers.": "У всех остаётся полный доступ через {app}, который можно установить как приложение на телефоны и компьютеры.",
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "Отправка из почтовых приложений (SMTP submission) перестанет работать, но её порты останутся открытыми: почтовым приложениям будет сообщено, что войти нельзя. Входящая почта (SMTP) и {app} (JMAP) не затрагиваются, и отключить их здесь нельзя.",
"You can turn legacy protocols back on at any time.": "Вы можете снова включить устаревшие почтовые протоколы в любой момент.",
"To confirm, type {phrase}": "Для подтверждения введите {phrase}",
"Turn off legacy protocols": "Отключить устаревшие почтовые протоколы",
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Устаревшие почтовые протоколы отключены для вашей организации. Входить могут только {app} и приложения JMAP.",
},
plurals: {
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} учётная запись использовала почтовое приложение на устаревших протоколах за последние 30 дней.", few: "{n} учётные записи использовали почтовое приложение на устаревших протоколах за последние 30 дней.", many: "{n} учётных записей использовали почтовое приложение на устаревших протоколах за последние 30 дней.", other: "{n} учётной записи использовали почтовое приложение на устаревших протоколах за последние 30 дней."},
// ── Administration: domains ────────────────────────────────────
"{n} accounts use this domain. Move or delete them first.": { one: "Этот домен использует {n} учётная запись. Сначала перенесите или удалите её.", few: "Этот домен используют {n} учётные записи. Сначала перенесите или удалите их.", many: "Этот домен используют {n} учётных записей. Сначала перенесите или удалите их.", other: "Этот домен используют {n} учётной записи. Сначала перенесите или удалите их." },
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Сервер перестанет принимать почту для этого домена, и его {n} ключ DKIM будет удалён. Отменить это нельзя.", few: "Сервер перестанет принимать почту для этого домена, и его {n} ключа DKIM будут удалены. Отменить это нельзя.", many: "Сервер перестанет принимать почту для этого домена, и его {n} ключей DKIM будут удалены. Отменить это нельзя.", other: "Сервер перестанет принимать почту для этого домена, и его {n} ключа DKIM будут удалены. Отменить это нельзя." },
+21
View File
@@ -1690,8 +1690,29 @@ export const catalog: Catalog = {
"{app} will tell you if a later message from this address is signed by anybody else.": "{app} повідомить, якщо наступний лист із цієї адреси підпише хтось інший.",
"itself, or an issuer it does not name": "самим собою або неназваним видавцем",
"no address": "немає адреси",
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"Legacy mail apps": "Поштові програми на застарілих протоколах",
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "Вимкнено для {tenant}. Входити на домени цієї організації можуть лише {app} і програми JMAP.",
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "Увімкнено для {tenant}. Поштові програми можуть використовувати IMAP, POP3 і ManageSieve на доменах цієї організації.",
"Turn legacy protocols back on": "Знову увімкнути застарілі поштові протоколи",
"Turn off legacy protocols…": "Вимкнути застарілі поштові протоколи…",
"No account used a legacy mail app in the last 30 days.": "За останні 30 днів жоден обліковий запис не використовував поштову програму на застарілих протоколах.",
"Their mail apps will stop working the moment you turn this on:": "Їхні поштові програми перестануть працювати, щойно ви це ввімкнете:",
"Only {app} and JMAP apps will work.": "Працюватимуть лише {app} і програми JMAP.",
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "Застарілі поштові протоколи (IMAP, POP3, ManageSieve і надсилання з поштових програм) буде вимкнено для всіх у {tenant}.",
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "Поштові програми на телефонах і комп'ютерах перестануть отримувати й надсилати пошту. Це Пошта на iPhone та iPad, програми Gmail і Outlook, Outlook, Thunderbird і Apple Mail. У них з'являтимуться помилки входу.",
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "Фільтри, якими керують із поштової програми (ManageSieve), перестануть працювати. Фільтри, налаштовані в {app}, працюватимуть і далі.",
"Incoming mail is not affected. Calendars and contacts are not affected.": "Вхідну пошту це не зачіпає. Календарі й контакти це не зачіпає.",
"People keep full access through {app}, which can be installed as an app on phones and computers.": "Усі зберігають повний доступ через {app}, який можна встановити як програму на телефони й комп'ютери.",
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "Надсилання з поштових програм (SMTP submission) перестане працювати, але його порти залишаться відкритими: поштовим програмам повідомлять, що увійти не можна. Вхідну пошту (SMTP) і {app} (JMAP) це не зачіпає, і вимкнути їх тут не можна.",
"You can turn legacy protocols back on at any time.": "Ви можете знову ввімкнути застарілі поштові протоколи будь-коли.",
"To confirm, type {phrase}": "Для підтвердження введіть {phrase}",
"Turn off legacy protocols": "Вимкнути застарілі поштові протоколи",
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "Застарілі поштові протоколи вимкнено для вашої організації. Входити можуть лише {app} і програми JMAP.",
},
plurals: {
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"{n} accounts used a legacy mail app in the last 30 days.": {one: "{n} обліковий запис використовував поштову програму на застарілих протоколах за останні 30 днів.", few: "{n} облікові записи використовували поштову програму на застарілих протоколах за останні 30 днів.", many: "{n} облікових записів використовували поштову програму на застарілих протоколах за останні 30 днів.", other: "{n} облікового запису використовували поштову програму на застарілих протоколах за останні 30 днів."},
// ── Administration: domains ────────────────────────────────────
"{n} accounts use this domain. Move or delete them first.": { one: "Цей домен використовує {n} обліковий запис. Спершу перенесіть або видаліть його.", few: "Цей домен використовують {n} облікові записи. Спершу перенесіть або видаліть їх.", many: "Цей домен використовують {n} облікових записів. Спершу перенесіть або видаліть їх.", other: "Цей домен використовують {n} облікового запису. Спершу перенесіть або видаліть їх." },
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { one: "Сервер перестане приймати пошту для цього домену, і його {n} ключ DKIM буде видалено. Скасувати це неможливо.", few: "Сервер перестане приймати пошту для цього домену, і його {n} ключі DKIM буде видалено. Скасувати це неможливо.", many: "Сервер перестане приймати пошту для цього домену, і його {n} ключів DKIM буде видалено. Скасувати це неможливо.", other: "Сервер перестане приймати пошту для цього домену, і його {n} ключа DKIM буде видалено. Скасувати це неможливо." },
+21
View File
@@ -1701,8 +1701,29 @@ export const catalog: Catalog = {
"{app} will tell you if a later message from this address is signed by anybody else.": "如果此地址之后的邮件由他人签名,{app} 会提醒您。",
"itself, or an issuer it does not name": "其自身,或一个未具名的颁发者",
"no address": "无地址",
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"Legacy mail apps": "使用传统协议的邮件应用",
"Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.": "{tenant} 已关闭。只有 {app} 和 JMAP 应用可以登录其域名。",
"On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.": "{tenant} 已开启。邮件应用可以在其域名上使用 IMAP、POP3 和 ManageSieve。",
"Turn legacy protocols back on": "重新开启传统邮件协议",
"Turn off legacy protocols…": "关闭传统邮件协议…",
"No account used a legacy mail app in the last 30 days.": "过去 30 天内没有账户使用过传统协议的邮件应用。",
"Their mail apps will stop working the moment you turn this on:": "您开启此项后,这些账户的邮件应用将立即停止工作:",
"Only {app} and JMAP apps will work.": "只有 {app} 和 JMAP 应用可以使用。",
"Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.": "{tenant} 中所有人的传统邮件协议(IMAP、POP3、ManageSieve 以及从邮件应用发信)都将被关闭。",
"Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.": "手机和电脑上的邮件应用将无法收发邮件,包括 iPhone 和 iPad 上的邮件、Gmail 和 Outlook 应用、Outlook、Thunderbird 以及 Apple Mail。用户会在这些应用中看到登录错误。",
"Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.": "通过邮件应用管理的过滤器(ManageSieve)将停止工作。在 {app} 中设置的过滤器会继续工作。",
"Incoming mail is not affected. Calendars and contacts are not affected.": "收件不受影响。日历和联系人不受影响。",
"People keep full access through {app}, which can be installed as an app on phones and computers.": "所有人仍可通过 {app} 完整访问,{app} 可以作为应用安装在手机和电脑上。",
"Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.": "从邮件应用发信(SMTP submission)将停止工作,但其端口保持开放:邮件应用会被告知无法登录。收件(SMTP)和 {app}(JMAP)不受影响,也无法在此关闭。",
"You can turn legacy protocols back on at any time.": "您可以随时重新开启传统邮件协议。",
"To confirm, type {phrase}": "请输入 {phrase} 以确认",
"Turn off legacy protocols": "关闭传统邮件协议",
"Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.": "您的组织已关闭传统邮件协议。只有 {app} 和 JMAP 应用可以登录。",
},
plurals: {
// ── Administration: legacy mail protocols (INBUXA) ──────────────
"{n} accounts used a legacy mail app in the last 30 days.": {other: "过去 30 天内有 {n} 个账户使用过传统协议的邮件应用。"},
// ── Administration: domains ────────────────────────────────────
"{n} accounts use this domain. Move or delete them first.": { other: "有 {n} 个账户正在使用此域名。请先移动或删除它们。" },
"The server stops accepting mail for this domain, and its {n} DKIM keys are deleted. This can't be undone.": { other: "服务器将不再接收此域名的邮件,其 {n} 个 DKIM 密钥也会被删除。此操作无法撤销。" },
+6
View File
@@ -1726,6 +1726,12 @@ select optgroup { background-color: var(--bg-elev); color: var(--fg); }
.admin-notice { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border-radius: var(--radius-sm); background: var(--bg-sunken); color: var(--fg-muted); font-size: .92em; margin: 8px 0; }
.admin-notice svg { flex: none; margin-top: 2px; }
.admin-notice.warn { background: var(--warn-soft); color: var(--fg); }
/* INBUXA: a tenant's legacy mail protocols switch (legacy-protocols LP-15 to LP-17) */
.admin-legacy-confirm { display: grid; gap: 12px; margin-top: 12px; }
.admin-legacy-box { padding: 10px 12px; border-radius: var(--radius-sm); background: var(--bg-sunken); font-size: .92em; }
.admin-legacy-box.warn { background: var(--warn-soft); color: var(--fg); }
.admin-legacy-box p { margin: 0 0 6px; }
.admin-legacy-box ul { margin: 6px 0; padding-left: 18px; }
.admin-notice.warn svg { color: var(--warn); }
.admin-notice.error { background: var(--danger-soft); color: var(--fg); }
.admin-sheet { position: absolute; top: 0; right: 0; bottom: 0; width: min(460px, 100%); z-index: 20; display: flex; flex-direction: column; background: var(--bg-elev); border-left: 1px solid var(--border); box-shadow: var(--shadow-3); animation: admin-sheet-in .18s var(--ease); }
+12 -1
View File
@@ -1,6 +1,8 @@
import { useEffect, useState, type ReactNode } from "react";
import { Link } from "wouter";
import { ArrowDownToLine, ArrowUpFromLine, ExternalLink, Globe, Hourglass, LayoutDashboard, MemoryStick, RefreshCw, Users } from "lucide-react";
import { ArrowDownToLine, ArrowUpFromLine, ExternalLink, Globe, Hourglass, LayoutDashboard, MemoryStick, RefreshCw, ShieldCheck, Users } from "lucide-react";
import { legacyProtocolsOff } from "@/jmap/client";
import { useAppName } from "@/lib/brand";
import { adminSections, dashboardCards, type DashboardCard } from "@/lib/admin/adminAccess";
import { balancedColumns, countObjects, DASHBOARD_WINDOW_MS, isRefused, loadMetrics, summarizeMetrics, type MessageStats } from "@/lib/admin/adminDashboard";
import { formatDayMonthTime, resolvedLocale } from "@/lib/datetime";
@@ -35,6 +37,8 @@ async function settle<T>(work: Promise<T>): Promise<Loaded<T>> {
export function AdminDashboard() {
const perms = usePermissions();
const adminUrl = useSession((s) => s.session?.ihasmail?.server?.adminUrl ?? null);
const legacyOff = useSession((s) => legacyProtocolsOff(s.session, s.accountId));
const app = useAppName();
const cards = dashboardCards(perms);
const sections = adminSections(perms);
const [reload, setReload] = useState(0);
@@ -102,6 +106,13 @@ export function AdminDashboard() {
<RefreshCw size={18} />
</button>
</div>
{legacyOff && (
// INBUXA legacy-protocols LP-18: while it's off for the organization
<p className="admin-notice">
<ShieldCheck size={16} />
<span>{t("Legacy mail protocols are off for your organization. Only {app} and JMAP apps can sign in.", { app })}</span>
</p>
)}
{shown.length ? (
<div className="admin-cards-wrap">
<div className="admin-cards">
@@ -0,0 +1,152 @@
import { useEffect, useState } from "react";
import { useAppName } from "@/lib/brand";
import { can } from "@/lib/admin/adminAccess";
import {
CONFIRM_PHRASE,
fetchTenantLegacy,
impactEntries,
phraseMatches,
setTenantLegacy,
type TenantLegacy,
} from "@/lib/admin/adminLegacyProtocols";
import { formatRelative } from "@/lib/format";
import { plural, t, tNode } from "@/lib/i18n";
import { toast } from "@/ui/toast";
import { usePermissions } from "./usePermissions";
/**
* A tenant's legacy mail protocols switch, in its sheet (INBUXA
* legacy-protocols LP-9 to LP-17, at tenant scope).
*
* Nobody should turn it on by accident or without understanding it: turning
* it off shows who would notice (LP-15) and what it means (LP-16) before the
* typed phrase is asked for (LP-17). Turning it back on is one click -- undoing
* a restriction must never be the hard part. A tenant's switch closes no port,
* so the statement names none.
*
* Only INBUXA has it; on any other server the section isn't there.
*/
export function TenantLegacyProtocols({ tenantId, tenantName }: { tenantId: string; tenantName: string }) {
const perms = usePermissions();
const app = useAppName();
const canChange = can(perms, "Domain", "Update");
const [state, setState] = useState<TenantLegacy | null>(null);
const [confirming, setConfirming] = useState(false);
const [typed, setTyped] = useState("");
const [busy, setBusy] = useState(false);
const [revision, setRevision] = useState(0);
useEffect(() => {
let canceled = false;
void fetchTenantLegacy(tenantId).then((s) => !canceled && setState(s));
return () => {
canceled = true;
};
}, [tenantId, revision]);
if (!state) return null;
const turn = async (off: boolean) => {
setBusy(true);
try {
await setTenantLegacy(tenantId, off);
setConfirming(false);
setTyped("");
setRevision((r) => r + 1);
} catch (err) {
toast.error((err as Error).message);
} finally {
setBusy(false);
}
};
const entries = state.recent ? impactEntries(state.recent) : null;
return (
<>
<h3>{t("Legacy mail apps")}</h3>
<p>
{state.off
? t("Off for {tenant}. Only {app} and JMAP apps can sign in to its domains.", { tenant: tenantName, app })
: t("On for {tenant}. Mail apps can use IMAP, POP3 and ManageSieve on its domains.", { tenant: tenantName })}
</p>
{canChange && state.off && (
<button className="btn" disabled={busy} onClick={() => void turn(false)}>
{t("Turn legacy protocols back on")}
</button>
)}
{canChange && !state.off && !confirming && (
<button className="btn btn-danger" onClick={() => setConfirming(true)}>
{t("Turn off legacy protocols…")}
</button>
)}
{!state.off && confirming && (
<div className="admin-legacy-confirm">
{entries &&
(entries.length === 0 ? (
<p className="hint">{t("No account used a legacy mail app in the last 30 days.")}</p>
) : (
<div className="admin-legacy-box">
<p>
<b>
{plural(entries.length, {
one: "{n} account used a legacy mail app in the last 30 days.",
other: "{n} accounts used a legacy mail app in the last 30 days.",
}, { n: entries.length })}
</b>{" "}
{t("Their mail apps will stop working the moment you turn this on:")}
</p>
<ul>
{entries.map((e) => (
<li key={e.name}>
<span className="notranslate" translate="no">{e.name}</span>: {e.protocols.join(", ")},{" "}
{formatRelative(new Date(e.lastUsedAt).toISOString())}
</li>
))}
</ul>
</div>
))}
<div className="admin-legacy-box warn">
<p><b>{t("Only {app} and JMAP apps will work.", { app })}</b></p>
<p>
{t("Legacy mail protocols (IMAP, POP3, ManageSieve and sending from mail apps) will be turned off for everyone in {tenant}.", { tenant: tenantName })}
</p>
<ul>
<li>{t("Phone and desktop mail apps will stop receiving and sending mail. That's iPhone and iPad Mail, the Gmail and Outlook apps, Outlook, Thunderbird and Apple Mail. People will see sign-in errors in them.")}</li>
<li>{t("Filters managed from a mail app (ManageSieve) will stop working. Filters set in {app} keep working.", { app })}</li>
<li>{t("Incoming mail is not affected. Calendars and contacts are not affected.")}</li>
<li>{t("People keep full access through {app}, which can be installed as an app on phones and computers.", { app })}</li>
</ul>
<p>{t("Sending from mail apps (SMTP submission) will stop working, but its ports stay open: mail apps will be told they cannot sign in. Incoming mail (SMTP) and {app} (JMAP) are not affected and cannot be turned off here.", { app })}</p>
<p>{t("You can turn legacy protocols back on at any time.")}</p>
</div>
<div className="field">
<label htmlFor="admin-legacy-confirm">
{tNode("To confirm, type {phrase}", { phrase: <code>{CONFIRM_PHRASE}</code> })}
</label>
<input
id="admin-legacy-confirm"
className="input"
autoComplete="off"
spellCheck={false}
value={typed}
onChange={(e) => setTyped(e.target.value)}
/>
</div>
<div className="row gap-8">
<button className="btn btn-danger" disabled={busy || !phraseMatches(typed)} onClick={() => void turn(true)}>
{t("Turn off legacy protocols")}
</button>
<button className="btn" disabled={busy} onClick={() => { setConfirming(false); setTyped(""); }}>
{t("Cancel")}
</button>
</div>
</div>
)}
</>
);
}
+3
View File
@@ -27,6 +27,7 @@ import { Dialog } from "@/ui/dialog";
import { Spinner } from "@/ui/misc";
import { toast } from "@/ui/toast";
import { usePermissions } from "./usePermissions";
import { TenantLegacyProtocols } from "./TenantLegacyProtocols";
const GIB = 1024 ** 3;
@@ -203,6 +204,8 @@ export function TenantSheet({ tenant, roles, onClose, onChanged, onCreated, onDe
<h3>{t("Domains")}</h3>
<TenantDomains tenant={tenant} canChange={can(perms, "Domain", "Update")} onChanged={() => { setRevision((n) => n + 1); onChanged(); }} />
<TenantLegacyProtocols tenantId={tenant.id} tenantName={tenant.name} />
</>
)}
@@ -0,0 +1,101 @@
import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useSession } from "@/store/session";
import type { JmapSession } from "@/jmap/types";
import type { TenantLegacy } from "@/lib/admin/adminLegacyProtocols";
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const api = vi.hoisted(() => ({
state: null as TenantLegacy | null,
set: vi.fn(async () => {}),
}));
vi.mock("@/lib/admin/adminLegacyProtocols", async (original) => ({
...(await original<typeof import("@/lib/admin/adminLegacyProtocols")>()),
fetchTenantLegacy: vi.fn(async () => api.state),
setTenantLegacy: api.set,
}));
const { TenantLegacyProtocols } = await import("../TenantLegacyProtocols");
const signIn = (permissions: string[]) =>
useSession.setState({ session: { capabilities: {}, accounts: {}, primaryAccounts: {}, username: "[email protected]", ihasmail: { permissions } } as unknown as JmapSession });
const button = (host: HTMLElement, label: string) => [...host.querySelectorAll("button")].find((b) => b.textContent?.trim() === label);
const type = async (el: HTMLInputElement, value: string) => {
await act(async () => {
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(el, value);
el.dispatchEvent(new Event("input", { bubbles: true }));
});
};
/**
* INBUXA legacy-protocols at tenant scope: nobody turns it off by accident
* (the statement and a typed phrase first), and turning it back on is one
* click.
*/
describe("a tenant's legacy mail protocols switch", () => {
let host: HTMLDivElement;
let root: Root;
const render = async () => {
await act(async () => {
root.render(<TenantLegacyProtocols tenantId="t1" tenantName="Acme Corp" />);
});
await act(async () => {});
};
beforeEach(() => {
host = document.createElement("div");
document.body.appendChild(host);
root = createRoot(host);
api.set.mockClear();
});
afterEach(async () => {
await act(async () => root.unmount());
host.remove();
});
it("isn't there on a server without the switch", async () => {
api.state = null;
signIn(["sysDomainGet", "sysDomainUpdate"]);
await render();
expect(host.textContent).toBe("");
});
it("shows who would notice and what it means, then asks for the exact phrase", async () => {
api.state = { off: false, recent: [{ accountId: "a", name: "[email protected]", protocol: "imap", lastUsedAt: Date.now() - 2 * 86400_000 }] };
signIn(["sysDomainGet", "sysDomainUpdate"]);
await render();
await act(async () => button(host, "Turn off legacy protocols…")!.click());
expect(host.textContent).toContain("1 account used a legacy mail app in the last 30 days.");
expect(host.textContent).toContain("[email protected]: IMAP");
expect(host.textContent).toContain("turned off for everyone in Acme Corp");
// A tenant's switch closes no port, so the statement names none.
expect(host.textContent).not.toContain("firewall");
const confirm = button(host, "Turn off legacy protocols")!;
const box = host.querySelector<HTMLInputElement>("#admin-legacy-confirm")!;
await type(box, "Turn off legacy mail");
expect(confirm.disabled).toBe(true);
await type(box, "turn off legacy mail");
expect(confirm.disabled).toBe(false);
await act(async () => confirm.click());
expect(api.set).toHaveBeenCalledWith("t1", true);
});
it("turns it back on with one click", async () => {
api.state = { off: true, recent: [] };
signIn(["sysDomainGet", "sysDomainUpdate"]);
await render();
await act(async () => button(host, "Turn legacy protocols back on")!.click());
expect(api.set).toHaveBeenCalledWith("t1", false);
});
it("shows the state but no switch to someone who can't change domains", async () => {
api.state = { off: true, recent: null };
signIn(["sysDomainGet"]);
await render();
expect(host.textContent).toContain("Off for Acme Corp");
expect(host.querySelector("button")).toBeNull();
});
});