Say every Administration refusal in the reader's language

Stalwart explains a refused change in English, and several of its words
reached the page as they were: "Invalid domain name" for a reserved TLD,
"Invalid email address" for a catch-all, a grant refusal, and ihasmail's own
proxy messages. Every registry error type now has its own message, and a
value one of the registry's string validators refused is recognised by the
validator's wording and explained again. A domain clash or a missing domain
is worded for a domain rather than an account.

The one exception is kept on purpose: a password policy's reason follows a
translated sentence, because the rule is the server's and dropping it would
leave no way to find out why.

The mock now refuses a reserved TLD and a catch-all without a domain the way
the live server did. KNOWN-ISSUES records the fix, and that the last two live
cases -- an administrator-set password and the outranking guard -- held.

15 new strings in all nine catalogues, 3 retired; strings falling back to
English stay at 16.
This commit is contained in:
2026-09-13 17:18:15 -07:00
parent 822314e8b7
commit 15b1838e21
16 changed files with 270 additions and 50 deletions
@@ -55,3 +55,45 @@ describe("the account query", () => {
call.mockRestore();
});
});
/**
* Stalwart explains a refusal in English, and none of it should reach an
* interface in another language as it is. Each case below is a refusal a
* 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", () => {
// 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/);
expect(domain).not.toContain("Invalid domain name");
expect(describeDirectoryError(new DirectoryError("invalidPatch", "Invalid email address", ["catchAllAddress"]), "domain")).toMatch(/full address/);
expect(describeDirectoryError(new DirectoryError("invalidProperties", "Invalid email local part", ["name"]))).toMatch(/before the @/);
});
it("never echoes a description it does not know", () => {
const text = describeDirectoryError(new DirectoryError("invalidPatch", "Something only the server would say", ["whatever"]));
expect(text).not.toContain("Something only the server would say");
expect(describeDirectoryError(new DirectoryError("forbidden", "You are not allowed to do that thing"))).not.toContain("not allowed to do that thing");
expect(describeDirectoryError(new DirectoryError("someNewType", "Brand new English"))).not.toContain("Brand new English");
});
it("tells a grant refusal and a directory-backed account apart from a plain no", () => {
expect(describeDirectoryError(new DirectoryError("forbidden", "You are not authorized to grant permissions: sysDomainDestroy."))).toMatch(/permissions your own role/);
expect(describeDirectoryError(new DirectoryError("forbidden", "Cannot set credentials for accounts in an external directory."))).toMatch(/external directory/);
});
it("words a clash and a missing object for what it was about", () => {
expect(describeDirectoryError(new DirectoryError("primaryKeyViolation", undefined, ["name"]), "domain")).toMatch(/domain name is already in use/);
expect(describeDirectoryError(new DirectoryError("primaryKeyViolation", undefined))).toMatch(/address is already in use/);
expect(describeDirectoryError(new DirectoryError("notFound", undefined), "domain")).toMatch(/domain no longer exists/);
});
it("explains ihasmail's own refusals by their code, not their English message", () => {
const own = { status: 403, code: "administration_needs_own_device", message: "Administration is only available when signed in on a device marked as your own (x:Account/query)." };
expect(describeDirectoryError(own)).toMatch(/marked as your own/);
expect(describeDirectoryError(own)).not.toContain("x:Account/query");
expect(describeDirectoryError({ status: 403, code: "administration_disabled", message: "…" })).toMatch(/turned off/);
expect(describeDirectoryError({ method: "x:Account/query", type: "unsupportedFilter", message: "x:Account/query: unsupportedFilter - type" })).toBe("The mail server could not carry out the request (unsupportedFilter).");
});
});
+63 -15
View File
@@ -215,35 +215,83 @@ export function quotasWithDisk(quotas: Record<string, number> | undefined, bytes
}
/**
* Say what went wrong in terms of the person's own action.
*
* Stalwart's descriptions are often exact and occasionally all there is -- a
* password policy says what it wants, in English -- so a description is kept
* where it carries something the type does not.
* The server's own wording for a value one of its validators refused, and
* what to say instead. These come from the registry's string validators
* (`crates/registry/src/types/string.rs`), which is the whole list: anything
* else Stalwart says about a value is picked up by the fallback below.
*/
export function describeDirectoryError(err: unknown): string {
const VALIDATOR_MESSAGES: Record<string, () => string> = {
"Invalid domain name": () => t("That isn't a valid domain name. Use a name such as example.com, on a real top-level domain."),
"Invalid email address": () => t("That isn't a valid email address. Use a full address, such as [email protected]."),
"Invalid email local part": () => t("That isn't a valid address. Use letters, numbers, dots, hyphens or underscores before the @."),
"Invalid hostname or IP address": () => t("That isn't a valid host name or IP address."),
"String cannot be empty": () => t("A required value was left empty."),
};
/** What kind of thing a refusal was about, where the wording has to differ. */
export type DirectoryObject = "account" | "domain";
/**
* Say what went wrong in terms of the person's own action, in their language.
*
* 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
* validator's wording and said again here.
*
* One exception, on purpose. A password policy is the server's to set -- a
* length, a strength -- and there is no way to know its rule in advance to
* translate it, so its reason is kept after a translated sentence. Dropping it
* would leave "not accepted" with no way to find out why.
*/
export function describeDirectoryError(err: unknown, object: DirectoryObject = "account"): string {
if (!(err instanceof DirectoryError)) {
const e = err as { type?: string; message?: string };
const e = err as { type?: string; code?: string; status?: number };
// ihasmail's own proxy, refusing for this session or this installation.
if (e?.code === "administration_needs_own_device") return t("Only on a device you've marked as your own. Sign in again with “This is my own device” ticked.");
if (e?.code === "administration_disabled") return t("Administration is turned off on this installation.");
if (e?.code === "network_error" || e?.status === 0) return t("Network error. Please check your connection.");
if (e?.code === "rate_limited" || e?.status === 429) return t("Too many attempts. Please wait a few minutes and try again.");
// A method-level JMAP error: the whole call was refused.
if (e?.type === "forbidden") return t("The mail server refused this. Your role may not allow it.");
return e?.message ?? String(err);
if (e?.type) return t("The mail server could not carry out the request ({code}).", { code: e.type });
return t("The mail server could not carry out the request ({code}).", { code: e?.code ?? "error" });
}
const description = err.description ?? "";
switch (err.type) {
case "forbidden":
return err.description ? t("The mail server refused this: {reason}", { reason: err.description }) : t("The mail server refused this. Your role may not allow it.");
if (/not authorized to grant/i.test(description)) return t("You can't give an account permissions your own role doesn't have.");
if (/external directory/i.test(description)) return t("This account signs in through an external directory, so its password can't be set here.");
if (/licen[cs]ed account limit/i.test(description)) return t("The server's licence allows no more accounts.");
return t("The mail server refused this. Your role may not allow it.");
case "primaryKeyViolation":
return t("That address is already in use on this server, as an account, a list or an alias.");
return object === "domain"
? t("That domain name is already in use on this server, as a domain or another domain's other name.")
: t("That address is already in use on this server, as an account, a list or an alias.");
case "invalidForeignKey":
return t("One of the chosen domain, role or group can't be used for this account.");
case "overQuota":
return t("Your organisation has reached the number of accounts it is allowed.");
return object === "domain" ? t("Your organisation has reached the number of domains it is allowed.") : t("Your organisation 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":
return t("This account no longer exists. Someone may have deleted it.");
return object === "domain" ? t("This domain no longer exists. Someone may have removed it.") : t("This account no longer exists. Someone may have deleted it.");
case "rateLimit":
return t("Too many attempts. Please wait a few minutes and try again.");
case "tooLarge":
return t("That is more than the mail server accepts in one change.");
case "invalidPatch":
case "invalidProperties":
if (err.properties.includes("secret")) return err.description ? t("The password was not accepted: {reason}", { reason: err.description }) : t("The password was not accepted.");
return err.description ? t("The mail server rejected a value: {reason}", { reason: err.description }) : t("The mail server rejected a value.");
case "validationFailed": {
if (err.properties.includes("secret")) {
return description ? t("The password was not accepted: {reason}", { reason: description }) : t("The password was not accepted.");
}
const known = VALIDATOR_MESSAGES[description];
if (known) return known();
return t("The mail server rejected one of the values. Check what you entered and try again.");
}
default:
return err.description ?? err.type;
return t("The mail server refused the change ({code}).", { code: err.type });
}
}
+16 -3
View File
@@ -112,6 +112,22 @@ export const catalog: Catalog = {
"Disabled": "Deaktiviert",
"also {names}": "auch {names}",
"The server did not say whether the domain was created.": "Der Server hat nicht mitgeteilt, ob die Domain angelegt wurde.",
// ── Administration: refusals ───────────────────────────────────
"That isn't a valid domain name. Use a name such as example.com, on a real top-level domain.": "Das ist kein gültiger Domainname. Verwenden Sie einen Namen wie example.com mit einer echten Top-Level-Domain.",
"That isn't a valid email address. Use a full address, such as [email protected].": "Das ist keine gültige E-Mail-Adresse. Verwenden Sie eine vollständige Adresse wie [email protected].",
"That isn't a valid address. Use letters, numbers, dots, hyphens or underscores before the @.": "Das ist keine gültige Adresse. Verwenden Sie vor dem @ Buchstaben, Ziffern, Punkte, Bindestriche oder Unterstriche.",
"That isn't a valid host name or IP address.": "Das ist kein gültiger Hostname und keine gültige IP-Adresse.",
"A required value was left empty.": "Ein erforderlicher Wert wurde leer gelassen.",
"Administration is turned off on this installation.": "Die Verwaltung ist in dieser Installation deaktiviert.",
"The mail server could not carry out the request ({code}).": "Der Mailserver konnte die Anfrage nicht ausführen ({code}).",
"You can't give an account permissions your own role doesn't have.": "Sie können einem Konto keine Berechtigungen geben, die Ihre eigene Rolle nicht hat.",
"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 licence 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.",
"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}).",
// ── Administration: accounts ───────────────────────────────────
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Nur auf einem Gerät, das Sie als Ihr eigenes markiert haben. Melden Sie sich erneut an und setzen Sie das Häkchen bei „Das ist mein eigenes Gerät“.",
"Change your own password in {settings}.": "Ihr eigenes Passwort ändern Sie unter {settings}.",
@@ -172,7 +188,6 @@ export const catalog: Catalog = {
"Deleted {address}": "{address} gelöscht",
"The server did not say whether the account was created.": "Der Server hat nicht mitgeteilt, ob das Konto angelegt wurde.",
"The mail server refused this. Your role may not allow it.": "Der Mailserver hat dies abgelehnt. Ihre Rolle erlaubt es möglicherweise nicht.",
"The mail server refused this: {reason}": "Der Mailserver hat dies abgelehnt: {reason}",
"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.",
@@ -180,8 +195,6 @@ export const catalog: Catalog = {
"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}",
"The password was not accepted.": "Das Passwort wurde nicht akzeptiert.",
"The mail server rejected a value: {reason}": "Der Mailserver hat einen Wert abgelehnt: {reason}",
"The mail server rejected a value.": "Der Mailserver hat einen Wert abgelehnt.",
"Go to folder…": "Zu Ordner springen…",
"Set for everyone here. You cannot change this.": "Für alle hier festgelegt. Sie können dies nicht ändern.",
"Export iCAL file": "iCAL-Datei exportieren",
+16 -3
View File
@@ -104,6 +104,22 @@ export const catalog: Catalog = {
"Disabled": "Desactivado",
"also {names}": "también {names}",
"The server did not say whether the domain was created.": "El servidor no indicó si el dominio se creó.",
// ── Administration: refusals ───────────────────────────────────
"That isn't a valid domain name. Use a name such as example.com, on a real top-level domain.": "Ese no es un nombre de dominio válido. Use un nombre como example.com, con un dominio de nivel superior real.",
"That isn't a valid email address. Use a full address, such as [email protected].": "Esa no es una dirección de correo válida. Use una dirección completa, como [email protected].",
"That isn't a valid address. Use letters, numbers, dots, hyphens or underscores before the @.": "Esa no es una dirección válida. Use letras, números, puntos, guiones o guiones bajos antes de la @.",
"That isn't a valid host name or IP address.": "Ese no es un nombre de host ni una dirección IP válidos.",
"A required value was left empty.": "Se dejó vacío un valor obligatorio.",
"Administration is turned off on this installation.": "La administración está desactivada en esta instalación.",
"The mail server could not carry out the request ({code}).": "El servidor de correo no pudo completar la solicitud ({code}).",
"You can't give an account permissions your own role doesn't have.": "No puede dar a una cuenta permisos que su propio rol no tiene.",
"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 licence 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.",
"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}).",
// ── Administration: accounts ───────────────────────────────────
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Solo en un dispositivo que haya marcado como suyo. Vuelva a iniciar sesión con «Este es mi propio dispositivo» marcado.",
"Change your own password in {settings}.": "Cambie su propia contraseña en {settings}.",
@@ -164,7 +180,6 @@ export const catalog: Catalog = {
"Deleted {address}": "{address} eliminada",
"The server did not say whether the account was created.": "El servidor no indicó si la cuenta se creó.",
"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.",
"The mail server refused this: {reason}": "El servidor de correo lo ha rechazado: {reason}",
"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.",
@@ -172,8 +187,6 @@ export const catalog: Catalog = {
"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}",
"The password was not accepted.": "La contraseña no se ha aceptado.",
"The mail server rejected a value: {reason}": "El servidor de correo ha rechazado un valor: {reason}",
"The mail server rejected a value.": "El servidor de correo ha rechazado un valor.",
"Go to folder…": "Ir a la carpeta…",
"Set for everyone here. You cannot change this.": "Definido para todos aquí. No puedes cambiarlo.",
"Export iCAL file": "Exportar archivo iCAL",
+16 -3
View File
@@ -109,6 +109,22 @@ export const catalog: Catalog = {
"Disabled": "Désactivé",
"also {names}": "aussi {names}",
"The server did not say whether the domain was created.": "Le serveur na pas indiqué si le domaine a été créé.",
// ── Administration: refusals ───────────────────────────────────
"That isn't a valid domain name. Use a name such as example.com, on a real top-level domain.": "Ce nest pas un nom de domaine valide. Utilisez un nom comme example.com, avec un vrai domaine de premier niveau.",
"That isn't a valid email address. Use a full address, such as [email protected].": "Ce nest pas une adresse e-mail valide. Utilisez une adresse complète, comme [email protected].",
"That isn't a valid address. Use letters, numbers, dots, hyphens or underscores before the @.": "Ce nest pas une adresse valide. Utilisez des lettres, des chiffres, des points, des tirets ou des traits de soulignement avant le @.",
"That isn't a valid host name or IP address.": "Ce nest ni un nom dhôte ni une adresse IP valide.",
"A required value was left empty.": "Une valeur obligatoire a été laissée vide.",
"Administration is turned off on this installation.": "Ladministration est désactivée sur cette installation.",
"The mail server could not carry out the request ({code}).": "Le serveur de messagerie na pas pu traiter la demande ({code}).",
"You can't give an account permissions your own role doesn't have.": "Vous ne pouvez pas donner à un compte des autorisations que votre propre rôle na pas.",
"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 licence allows no more accounts.": "La licence 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 dun autre domaine.",
"Your organisation 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.": "Cest 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é lune 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}).",
// ── Administration: accounts ───────────────────────────────────
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Uniquement sur un appareil que vous avez indiqué comme le vôtre. Reconnectez-vous en cochant « Cet appareil est le mien ».",
"Change your own password in {settings}.": "Modifiez votre propre mot de passe dans {settings}.",
@@ -169,7 +185,6 @@ export const catalog: Catalog = {
"Deleted {address}": "{address} supprimé",
"The server did not say whether the account was created.": "Le serveur na pas indiqué si le compte a été créé.",
"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.",
"The mail server refused this: {reason}": "Le serveur de messagerie a refusé : {reason}",
"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é.",
@@ -177,8 +192,6 @@ export const catalog: Catalog = {
"This account no longer exists. Someone may have deleted it.": "Ce compte nexiste plus. Quelquun la peut-être supprimé.",
"The password was not accepted: {reason}": "Le mot de passe a été refusé : {reason}",
"The password was not accepted.": "Le mot de passe a été refusé.",
"The mail server rejected a value: {reason}": "Le serveur de messagerie a refusé une valeur : {reason}",
"The mail server rejected a value.": "Le serveur de messagerie a refusé une valeur.",
"Go to folder…": "Aller au dossier…",
"Set for everyone here. You cannot change this.": "Défini pour tout le monde ici. Vous ne pouvez pas le modifier.",
"Export iCAL file": "Exporter un fichier iCAL",
+16 -3
View File
@@ -103,6 +103,22 @@ export const catalog: Catalog = {
"Disabled": "無効",
"also {names}": "別名: {names}",
"The server did not say whether the domain was created.": "ドメインが作成されたかどうか、サーバーから応答がありませんでした。",
// ── Administration: refusals ───────────────────────────────────
"That isn't a valid domain name. Use a name such as example.com, on a real top-level domain.": "有効なドメイン名ではありません。example.com のように、実在するトップレベルドメインの名前を使ってください。",
"That isn't a valid email address. Use a full address, such as [email protected].": "有効なメールアドレスではありません。[email protected] のような完全なアドレスを使ってください。",
"That isn't a valid address. Use letters, numbers, dots, hyphens or underscores before the @.": "有効なアドレスではありません。@ の前には英数字、ドット、ハイフン、アンダースコアを使ってください。",
"That isn't a valid host name or IP address.": "有効なホスト名または IP アドレスではありません。",
"A required value was left empty.": "必須の値が空欄です。",
"Administration is turned off on this installation.": "このインストールでは管理機能が無効になっています。",
"The mail server could not carry out the request ({code}).": "メールサーバーはリクエストを実行できませんでした({code})。",
"You can't give an account permissions your own role doesn't have.": "自分のロールにない権限をアカウントに付与することはできません。",
"This account signs in through an external directory, so its password can't be set here.": "このアカウントは外部ディレクトリでサインインするため、ここではパスワードを設定できません。",
"The server's licence 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.": "組織で許可されているドメイン数の上限に達しました。",
"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})。",
// ── Administration: accounts ───────────────────────────────────
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "自分のデバイスとして指定した端末でのみ使えます。「これは自分のデバイスです」にチェックを入れて、もう一度サインインしてください。",
"Change your own password in {settings}.": "ご自身のパスワードは{settings}で変更してください。",
@@ -163,7 +179,6 @@ export const catalog: Catalog = {
"Deleted {address}": "{address} を削除しました",
"The server did not say whether the account was created.": "アカウントが作成されたかどうか、サーバーから応答がありませんでした。",
"The mail server refused this. Your role may not allow it.": "メールサーバーに拒否されました。ロールで許可されていない可能性があります。",
"The mail server refused this: {reason}": "メールサーバーに拒否されました: {reason}",
"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.": "組織で許可されているアカウント数の上限に達しました。",
@@ -171,8 +186,6 @@ export const catalog: Catalog = {
"This account no longer exists. Someone may have deleted it.": "このアカウントはもう存在しません。誰かが削除した可能性があります。",
"The password was not accepted: {reason}": "パスワードは受け付けられませんでした: {reason}",
"The password was not accepted.": "パスワードは受け付けられませんでした。",
"The mail server rejected a value: {reason}": "メールサーバーが値を拒否しました: {reason}",
"The mail server rejected a value.": "メールサーバーが値を拒否しました。",
"Go to folder…": "フォルダーへ移動…",
"Set for everyone here. You cannot change this.": "この環境全体で設定されています。変更できません。",
"Export iCAL file": "iCAL ファイルをエクスポート",
+16 -3
View File
@@ -100,6 +100,22 @@ export const catalog: Catalog = {
"Disabled": "Uitgeschakeld",
"also {names}": "ook {names}",
"The server did not say whether the domain was created.": "De server heeft niet gemeld of het domein is aangemaakt.",
// ── Administration: refusals ───────────────────────────────────
"That isn't a valid domain name. Use a name such as example.com, on a real top-level domain.": "Dat is geen geldige domeinnaam. Gebruik een naam zoals example.com, met een echt topleveldomein.",
"That isn't a valid email address. Use a full address, such as [email protected].": "Dat is geen geldig e-mailadres. Gebruik een volledig adres, zoals [email protected].",
"That isn't a valid address. Use letters, numbers, dots, hyphens or underscores before the @.": "Dat is geen geldig adres. Gebruik letters, cijfers, punten, koppeltekens of underscores vóór de @.",
"That isn't a valid host name or IP address.": "Dat is geen geldige hostnaam of IP-adres.",
"A required value was left empty.": "Een verplichte waarde is leeg gelaten.",
"Administration is turned off on this installation.": "Beheer is uitgeschakeld in deze installatie.",
"The mail server could not carry out the request ({code}).": "De mailserver kon het verzoek niet uitvoeren ({code}).",
"You can't give an account permissions your own role doesn't have.": "U kunt een account geen rechten geven die uw eigen rol niet heeft.",
"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 licence 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.",
"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}).",
// ── Administration: accounts ───────────────────────────────────
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Alleen op een apparaat dat u als uw eigen apparaat hebt aangemerkt. Log opnieuw in met Dit is mijn eigen apparaat aangevinkt.",
"Change your own password in {settings}.": "Wijzig uw eigen wachtwoord bij {settings}.",
@@ -160,7 +176,6 @@ export const catalog: Catalog = {
"Deleted {address}": "{address} verwijderd",
"The server did not say whether the account was created.": "De server heeft niet gemeld of het account is aangemaakt.",
"The mail server refused this. Your role may not allow it.": "De mailserver heeft dit geweigerd. Uw rol staat het mogelijk niet toe.",
"The mail server refused this: {reason}": "De mailserver heeft dit geweigerd: {reason}",
"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.",
@@ -168,8 +183,6 @@ export const catalog: Catalog = {
"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}",
"The password was not accepted.": "Het wachtwoord is niet geaccepteerd.",
"The mail server rejected a value: {reason}": "De mailserver heeft een waarde geweigerd: {reason}",
"The mail server rejected a value.": "De mailserver heeft een waarde geweigerd.",
"Go to folder…": "Ga naar map…",
"Set for everyone here. You cannot change this.": "Hier voor iedereen ingesteld. U kunt dit niet wijzigen.",
"Export iCAL file": "iCAL-bestand exporteren",
+16 -3
View File
@@ -107,6 +107,22 @@ export const catalog: Catalog = {
"Disabled": "Desativado",
"also {names}": "também {names}",
"The server did not say whether the domain was created.": "O servidor não informou se o domínio foi criado.",
// ── Administration: refusals ───────────────────────────────────
"That isn't a valid domain name. Use a name such as example.com, on a real top-level domain.": "Esse não é um nome de domínio válido. Use um nome como example.com, com um domínio de nível superior real.",
"That isn't a valid email address. Use a full address, such as [email protected].": "Esse não é um endereço de e-mail válido. Use um endereço completo, como [email protected].",
"That isn't a valid address. Use letters, numbers, dots, hyphens or underscores before the @.": "Esse não é um endereço válido. Use letras, números, pontos, hifens ou sublinhados antes do @.",
"That isn't a valid host name or IP address.": "Esse não é um nome de host ou endereço IP válido.",
"A required value was left empty.": "Um valor obrigatório foi deixado em branco.",
"Administration is turned off on this installation.": "A administração está desativada nesta instalação.",
"The mail server could not carry out the request ({code}).": "O servidor de e-mail não conseguiu executar a solicitação ({code}).",
"You can't give an account permissions your own role doesn't have.": "Você não pode dar a uma conta permissões que sua própria função não tem.",
"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 licence 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.",
"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}).",
// ── Administration: accounts ───────────────────────────────────
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Só em um dispositivo que você marcou como seu. Entre novamente com “Este dispositivo é meu” marcado.",
"Change your own password in {settings}.": "Altere sua própria senha em {settings}.",
@@ -167,7 +183,6 @@ export const catalog: Catalog = {
"Deleted {address}": "{address} excluída",
"The server did not say whether the account was created.": "O servidor não informou se a conta foi criada.",
"The mail server refused this. Your role may not allow it.": "O servidor de e-mail recusou. Talvez sua função não permita.",
"The mail server refused this: {reason}": "O servidor de e-mail recusou: {reason}",
"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.",
@@ -175,8 +190,6 @@ export const catalog: Catalog = {
"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}",
"The password was not accepted.": "A senha não foi aceita.",
"The mail server rejected a value: {reason}": "O servidor de e-mail recusou um valor: {reason}",
"The mail server rejected a value.": "O servidor de e-mail recusou um valor.",
"Go to folder…": "Ir para a pasta…",
"Set for everyone here. You cannot change this.": "Definido para todos aqui. Você não pode alterar isto.",
"Export iCAL file": "Exportar arquivo iCAL",
+16 -3
View File
@@ -106,6 +106,22 @@ export const catalog: Catalog = {
"Disabled": "Отключён",
"also {names}": "также {names}",
"The server did not say whether the domain was created.": "Сервер не сообщил, создан ли домен.",
// ── Administration: refusals ───────────────────────────────────
"That isn't a valid domain name. Use a name such as example.com, on a real top-level domain.": "Это недопустимое имя домена. Укажите имя вроде example.com с настоящим доменом верхнего уровня.",
"That isn't a valid email address. Use a full address, such as [email protected].": "Это недопустимый адрес электронной почты. Укажите полный адрес, например [email protected].",
"That isn't a valid address. Use letters, numbers, dots, hyphens or underscores before the @.": "Это недопустимый адрес. Перед @ используйте буквы, цифры, точки, дефисы или подчёркивания.",
"That isn't a valid host name or IP address.": "Это недопустимое имя хоста или IP-адрес.",
"A required value was left empty.": "Обязательное значение не заполнено.",
"Administration is turned off on this installation.": "Администрирование отключено в этой установке.",
"The mail server could not carry out the request ({code}).": "Почтовый сервер не смог выполнить запрос ({code}).",
"You can't give an account permissions your own role doesn't have.": "Нельзя дать учётной записи разрешения, которых нет у вашей роли.",
"This account signs in through an external directory, so its password can't be set here.": "Эта учётная запись входит через внешний каталог, поэтому её пароль нельзя задать здесь.",
"The server's licence 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.": "Ваша организация достигла допустимого числа доменов.",
"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}).",
// ── Administration: accounts ───────────────────────────────────
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Только на устройстве, отмеченном как ваше. Войдите снова, отметив «Это моё личное устройство».",
"Change your own password in {settings}.": "Свой пароль можно изменить в разделе {settings}.",
@@ -166,7 +182,6 @@ export const catalog: Catalog = {
"Deleted {address}": "Учётная запись {address} удалена",
"The server did not say whether the account was created.": "Сервер не сообщил, создана ли учётная запись.",
"The mail server refused this. Your role may not allow it.": "Почтовый сервер отклонил это действие. Возможно, ваша роль его не допускает.",
"The mail server refused this: {reason}": "Почтовый сервер отклонил это действие: {reason}",
"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.": "Ваша организация достигла допустимого числа учётных записей.",
@@ -174,8 +189,6 @@ export const catalog: Catalog = {
"This account no longer exists. Someone may have deleted it.": "Этой учётной записи больше нет. Возможно, её кто-то удалил.",
"The password was not accepted: {reason}": "Пароль не принят: {reason}",
"The password was not accepted.": "Пароль не принят.",
"The mail server rejected a value: {reason}": "Почтовый сервер отклонил значение: {reason}",
"The mail server rejected a value.": "Почтовый сервер отклонил значение.",
"Go to folder…": "Перейти к папке…",
"Set for everyone here. You cannot change this.": "Задано для всех здесь. Изменить нельзя.",
"Export iCAL file": "Экспортировать файл iCAL",
+16 -3
View File
@@ -100,6 +100,22 @@ export const catalog: Catalog = {
"Disabled": "Вимкнено",
"also {names}": "також {names}",
"The server did not say whether the domain was created.": "Сервер не повідомив, чи створено домен.",
// ── Administration: refusals ───────────────────────────────────
"That isn't a valid domain name. Use a name such as example.com, on a real top-level domain.": "Це недійсне ім'я домену. Вкажіть ім'я на кшталт example.com зі справжнім доменом верхнього рівня.",
"That isn't a valid email address. Use a full address, such as [email protected].": "Це недійсна адреса електронної пошти. Вкажіть повну адресу, наприклад [email protected].",
"That isn't a valid address. Use letters, numbers, dots, hyphens or underscores before the @.": "Це недійсна адреса. Перед @ використовуйте літери, цифри, крапки, дефіси або підкреслення.",
"That isn't a valid host name or IP address.": "Це недійсне ім'я хоста чи IP-адреса.",
"A required value was left empty.": "Обов'язкове значення не заповнено.",
"Administration is turned off on this installation.": "Адміністрування вимкнено в цьому встановленні.",
"The mail server could not carry out the request ({code}).": "Поштовий сервер не зміг виконати запит ({code}).",
"You can't give an account permissions your own role doesn't have.": "Не можна надати обліковому запису дозволи, яких немає у вашої ролі.",
"This account signs in through an external directory, so its password can't be set here.": "Цей обліковий запис входить через зовнішній каталог, тому його пароль не можна задати тут.",
"The server's licence 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.": "Ваша організація досягла дозволеної кількості доменів.",
"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}).",
// ── Administration: accounts ───────────────────────────────────
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "Лише на пристрої, позначеному як ваш. Увійдіть знову, позначивши «Це мій власний пристрій».",
"Change your own password in {settings}.": "Власний пароль можна змінити в розділі {settings}.",
@@ -160,7 +176,6 @@ export const catalog: Catalog = {
"Deleted {address}": "Обліковий запис {address} видалено",
"The server did not say whether the account was created.": "Сервер не повідомив, чи створено обліковий запис.",
"The mail server refused this. Your role may not allow it.": "Поштовий сервер відхилив цю дію. Можливо, ваша роль її не дозволяє.",
"The mail server refused this: {reason}": "Поштовий сервер відхилив цю дію: {reason}",
"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.": "Ваша організація досягла дозволеної кількості облікових записів.",
@@ -168,8 +183,6 @@ export const catalog: Catalog = {
"This account no longer exists. Someone may have deleted it.": "Цього облікового запису більше немає. Можливо, його хтось видалив.",
"The password was not accepted: {reason}": "Пароль не прийнято: {reason}",
"The password was not accepted.": "Пароль не прийнято.",
"The mail server rejected a value: {reason}": "Поштовий сервер відхилив значення: {reason}",
"The mail server rejected a value.": "Поштовий сервер відхилив значення.",
"Go to folder…": "Перейти до теки…",
"Set for everyone here. You cannot change this.": "Задано для всіх тут. Змінити не можна.",
"Export iCAL file": "Експортувати файл iCAL",
+16 -3
View File
@@ -102,6 +102,22 @@ export const catalog: Catalog = {
"Disabled": "已停用",
"also {names}": "别名:{names}",
"The server did not say whether the domain was created.": "服务器没有说明域名是否已创建。",
// ── Administration: refusals ───────────────────────────────────
"That isn't a valid domain name. Use a name such as example.com, on a real top-level domain.": "这不是有效的域名。请使用 example.com 这样、带有真实顶级域名的名称。",
"That isn't a valid email address. Use a full address, such as [email protected].": "这不是有效的电子邮件地址。请使用完整地址,例如 [email protected]。",
"That isn't a valid address. Use letters, numbers, dots, hyphens or underscores before the @.": "这不是有效的地址。@ 前请使用字母、数字、点、连字符或下划线。",
"That isn't a valid host name or IP address.": "这不是有效的主机名或 IP 地址。",
"A required value was left empty.": "有必填值未填写。",
"Administration is turned off on this installation.": "此安装已关闭管理功能。",
"The mail server could not carry out the request ({code}).": "邮件服务器无法执行该请求({code})。",
"You can't give an account permissions your own role doesn't have.": "您不能授予账户您自己的角色所没有的权限。",
"This account signs in through an external directory, so its password can't be set here.": "该账户通过外部目录登录,因此无法在此设置其密码。",
"The server's licence 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.": "您的组织已达到允许的域名数量上限。",
"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})。",
// ── Administration: accounts ───────────────────────────────────
"Only on a device you've marked as your own. Sign in again with \u201cThis is my own device\u201d ticked.": "仅限在您标记为自己设备的设备上使用。请勾选「这是我自己的设备」后重新登录。",
"Change your own password in {settings}.": "请在{settings}中更改您自己的密码。",
@@ -162,7 +178,6 @@ export const catalog: Catalog = {
"Deleted {address}": "已删除 {address}",
"The server did not say whether the account was created.": "服务器没有说明账户是否已创建。",
"The mail server refused this. Your role may not allow it.": "邮件服务器拒绝了此操作。您的角色可能不允许。",
"The mail server refused this: {reason}": "邮件服务器拒绝了此操作:{reason}",
"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.": "您的组织已达到允许的账户数量上限。",
@@ -170,8 +185,6 @@ export const catalog: Catalog = {
"This account no longer exists. Someone may have deleted it.": "该账户已不存在,可能已被他人删除。",
"The password was not accepted: {reason}": "密码未被接受:{reason}",
"The password was not accepted.": "密码未被接受。",
"The mail server rejected a value: {reason}": "邮件服务器拒绝了一个值:{reason}",
"The mail server rejected a value.": "邮件服务器拒绝了一个值。",
"Go to folder…": "转到文件夹…",
"Set for everyone here. You cannot change this.": "已为此处所有人设定,您无法更改。",
"Export iCAL file": "导出 iCAL 文件",
+3 -3
View File
@@ -98,7 +98,7 @@ export function DomainSheet({ id, accountCount, onClose, onChanged, onCreated, o
void namesOf("DnsServer", [serverId]).then((n) => { if (!cancelled) setProvider(n.get(serverId) ?? null); }, () => {});
}
} catch (err) {
if (!cancelled) setLoadError(describeDirectoryError(err));
if (!cancelled) setLoadError(describeDirectoryError(err, "domain"));
}
})();
return () => {
@@ -148,7 +148,7 @@ export function DomainSheet({ id, accountCount, onClose, onChanged, onCreated, o
setRevision((n) => n + 1);
onChanged();
} catch (err) {
setError(describeDirectoryError(err));
setError(describeDirectoryError(err, "domain"));
} finally {
setBusy(false);
}
@@ -389,7 +389,7 @@ function RemoveDomain({ domain, accountCount, keys, canRemoveKeys, onDeleted }:
setError(
err instanceof DomainError && err.type === "objectIsLinked" && err.linked.length
? t("The server kept the domain: it is still used by {things}.", { things: describeLinked(err.linked) })
: describeDirectoryError(err),
: describeDirectoryError(err, "domain"),
);
} finally {
setBusy(false);
+1 -1
View File
@@ -47,7 +47,7 @@ export function DomainsAdmin({ selectedId }: { selectedId?: string }) {
} catch (err) {
if (!cancelled) {
setPage({ domains: [], total: 0 });
setError(describeDirectoryError(err));
setError(describeDirectoryError(err, "domain"));
}
}
})();