fix: explain a closed directory and refused free/busy in the right words

With directory queries off on Stalwart 1.0, the free/busy list under the
attendees disappeared without a word. The share dialog explained every
`forbidden` from Principal/query with the 1.0 text: "only your groups
and people who have shared with you are listed", and a hint at
Stalwart's sharing settings. On 0.16 that refusal only means the role
lacks the permission, Principal/get then lists everyone, and the setting
has no effect over JMAP.

- When the server refuses free/busy for the user (`mayGetAvailability`
  false on the user's own principal, or a lookup refused with
  `forbidden`), one muted line says "Free/busy isn't available on this
  server". The block stays empty only when the server offers no
  free/busy at all.
- The share dialog keeps the 1.0 wording for Stalwart 1.0's own refusal
  ("The administrator has disabled directory queries.") and otherwise
  says "Your server doesn't let you browse its user directory.", still
  listing whoever Principal/get returned.
This commit is contained in:
Linus Rath
2026-09-25 20:07:46 +02:00
parent 6bd55c9501
commit f7deb92d16
34 changed files with 194 additions and 42 deletions
@@ -148,20 +148,36 @@ describe('ParticipantAvailability', () => {
vi.restoreAllMocks();
});
it('stays hidden on Stalwart 1.0 with directory queries off, without looking anyone up', async () => {
it('says free/busy is not available on Stalwart 1.0 with directory queries off, without looking anyone up', async () => {
const { client, requests } = makeClient({ ownPrincipal: OWN_PRINCIPAL_1_0_DIRECTORY_OFF });
renderFor(client);
// Nothing shows while the server is asked, either.
// Nothing shows while the server is asked.
expect(screen.queryByTestId('participant-availability')).toBeNull();
expect(screen.queryByTestId('participant-availability-unavailable')).toBeNull();
await waitFor(() => expect(requests).toHaveLength(1));
// The i18n test mock renders the translation key.
await waitFor(() => expect(screen.getByText('unavailable_on_server')).toBeInTheDocument());
await settle();
expect(screen.queryByTestId('participant-availability')).toBeNull();
expect(screen.getAllByTestId('participant-availability-unavailable')).toHaveLength(1);
expect(methodsOf(requests)).toEqual(['Principal/get']);
expect(requests[0][0]).toEqual(['Principal/get', { accountId: 'me', ids: ['me'], properties: ['id', 'capabilities'] }, '0']);
});
it('renders nothing when the server offers no free/busy at all', async () => {
const { client, requests } = makeClient({ ownPrincipal: OWN_PRINCIPAL_0_16 });
// No urn:ietf:params:jmap:principals:availability in the session.
const capabilities = (client as unknown as { capabilities: Record<string, unknown> }).capabilities;
delete capabilities['urn:ietf:params:jmap:principals:availability'];
const { container } = renderFor(client);
await settle();
expect(container).toBeEmptyDOMElement();
expect(requests).toHaveLength(0);
});
it('checks attendees on Stalwart 0.16, whose principal capabilities are empty', async () => {
const { client } = makeClient({
ownPrincipal: OWN_PRINCIPAL_0_16,
@@ -177,28 +193,30 @@ describe('ParticipantAvailability', () => {
expect(screen.getByText('unknown')).toBeInTheDocument();
});
it('hides the block once getAvailability is refused with forbidden, and stays hidden', async () => {
it('says free/busy is not available once getAvailability is refused with forbidden, and asks no more', async () => {
const { client, requests } = makeClient({
ownPrincipal: OWN_PRINCIPAL_0_16,
availability: () => DIRECTORY_QUERIES_OFF,
});
const first = renderFor(client);
await waitFor(() => expect(methodsOf(requests)).toContain('Principal/getAvailability'), { timeout: 2000 });
await waitFor(() => expect(screen.queryByTestId('participant-availability')).toBeNull());
await waitFor(() => expect(screen.getByText('unavailable_on_server')).toBeInTheDocument());
expect(screen.queryByTestId('participant-availability')).toBeNull();
first.unmount();
const calls = requests.length;
renderFor(client);
await settle();
expect(screen.getByText('unavailable_on_server')).toBeInTheDocument();
expect(screen.queryByTestId('participant-availability')).toBeNull();
expect(requests).toHaveLength(calls);
});
it('stays hidden when the role may not read principals', async () => {
it('says free/busy is not available when the role may not read principals (0.16 and 1.0)', async () => {
const { client, requests } = makeClient({ ownPrincipal: NOT_AUTHORIZED });
renderFor(client);
await waitFor(() => expect(requests).toHaveLength(1));
await waitFor(() => expect(screen.getByText('unavailable_on_server')).toBeInTheDocument());
await settle();
expect(screen.queryByTestId('participant-availability')).toBeNull();
@@ -93,8 +93,9 @@ async function lookUpStatuses(
/**
* Free/busy of the invited attendees for the event window
* (Principal/getAvailability). Only attendees that are principals on this
* server can be checked; external addresses show as unknown. Nothing is shown
* when the server won't answer free/busy for this user.
* server can be checked; external addresses show as unknown. When the server
* won't answer free/busy for this user, one line says so; nothing is shown
* when it doesn't offer free/busy at all.
*/
export function ParticipantAvailability({ attendees, start, end }: ParticipantAvailabilityProps) {
const t = useTranslations("calendar.participants.availability");
@@ -153,7 +154,18 @@ export function ParticipantAvailability({ attendees, start, end }: ParticipantAv
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [client, wanted, allowed, emailsKey, startMs, endMs]);
if (!wanted || allowed !== true) return null;
if (!wanted) return null;
if (allowed === false) {
// Stalwart 1.0 with directory queries off (its default), or a role without
// the principal permissions: the lookups are refused, so explain the gap
// instead of leaving the attendees without a word.
return (
<p className="mt-1.5 text-xs text-muted-foreground" role="status" data-testid="participant-availability-unavailable">
{t("unavailable_on_server")}
</p>
);
}
if (allowed !== true) return null;
return (
<ul className="mt-1.5 space-y-0.5" aria-label={t("title")} data-testid="participant-availability">
@@ -21,6 +21,13 @@ const DIRECTORY_OFF: MethodResponse[] = [
['error', { type: 'invalidResultReference', description: 'Result reference to 0#Principal/query not found.' }, '1'],
];
// A role without the query permission: 0.16's dispatcher refuses before the
// handler runs (api/auth.rs), whatever the directory setting says.
const NOT_AUTHORIZED: MethodResponse[] = [
['error', { type: 'forbidden', description: 'You are not authorized to perform this action' }, '0'],
['error', { type: 'invalidResultReference', description: 'Result reference to 0#Principal/query not found.' }, '1'],
];
const READ: CalendarRights = {
mayReadFreeBusy: true, mayReadItems: true, mayWriteAll: false, mayWriteOwn: false,
mayUpdatePrivate: false, mayRSVP: false, mayShare: false, mayDelete: false,
@@ -91,6 +98,7 @@ describe('ShareCollectionDialog directory', () => {
fireEvent.click(screen.getByText('add_person'));
await waitFor(() => expect(screen.getByText('directory_restricted')).toBeInTheDocument());
expect(screen.queryByText('directory_not_browsable')).toBeNull();
expect(screen.getByText('Sales')).toBeInTheDocument();
expect(screen.queryByText('me@example.com')).toBeNull();
@@ -98,6 +106,21 @@ describe('ShareCollectionDialog directory', () => {
await waitFor(() => expect(onShare).toHaveBeenCalledWith('jane', READ));
});
it('says only that the directory cannot be browsed when a 0.16 role may not query it, and lists whoever comes back', async () => {
// Principal/get without ids returns everyone on 0.16, so the 1.0 wording
// ("only your groups and people who have shared with you") and its hint
// about Stalwart's sharing settings would both be wrong.
renderDialog(makeClient((calls) => (calls[0][0] === 'Principal/query'
? NOT_AUTHORIZED
: [['Principal/get', { list: [ME, ANNA, SALES], notFound: [] }, '0']])));
fireEvent.click(screen.getByText('add_person'));
await waitFor(() => expect(screen.getByText('directory_not_browsable')).toBeInTheDocument());
expect(screen.queryByText('directory_restricted')).toBeNull();
expect(screen.getByText('anna@example.com')).toBeInTheDocument();
expect(screen.getByText('Sales')).toBeInTheDocument();
});
it('shows the notice instead of "no users found" when a restricted list offers no one', async () => {
renderDialog(restrictedDirectory([ME]));
fireEvent.click(screen.getByText('add_person'));
@@ -154,6 +154,7 @@ export function ShareCollectionDialog({
const modalRef = useRef<HTMLDivElement>(null);
const [allPrincipals, setAllPrincipals] = useState<Principal[]>([]);
const [directoryStatus, setDirectoryStatus] = useState<PrincipalDirectoryStatus>("ok");
const [directoryQueriesOff, setDirectoryQueriesOff] = useState(false);
const [loadingPrincipals, setLoadingPrincipals] = useState(true);
const [search, setSearch] = useState("");
const [savingId, setSavingId] = useState<string | null>(null);
@@ -163,10 +164,11 @@ export function ShareCollectionDialog({
useEffect(() => {
let cancelled = false;
setLoadingPrincipals(true);
void loadPrincipalDirectory(client).then(({ principals, status }) => {
void loadPrincipalDirectory(client).then(({ principals, status, directoryQueriesOff }) => {
if (cancelled) return;
setAllPrincipals(principals);
setDirectoryStatus(status);
setDirectoryQueriesOff(!!directoryQueriesOff);
setLoadingPrincipals(false);
});
return () => { cancelled = true; };
@@ -374,10 +376,15 @@ export function ShareCollectionDialog({
autoFocus
/>
{/* The list below is partial or empty for a reason the user
can't see otherwise; say so instead of "no users found". */}
can't see otherwise; say so instead of "no users found". Only
Stalwart 1.0's own refusal says who is still listed and what
an administrator can do: a 0.16 role without the permission
gets another refusal, and a list of everyone after it. */}
{!loadingPrincipals && directoryStatus !== "ok" && (
<p className="text-xs text-muted-foreground" role="status">
{directoryStatus === "restricted" ? t("directory_restricted") : t("directory_failed")}
{directoryStatus === "error"
? t("directory_failed")
: directoryQueriesOff ? t("directory_restricted") : t("directory_not_browsable")}
</p>
)}
<div className="max-h-48 overflow-y-auto -mx-1">
@@ -107,6 +107,7 @@ describe('listPrincipals', () => {
const listing = await client.listPrincipals();
expect(listing.status).toBe('restricted');
expect(listing.directoryQueriesOff).toBe(true);
expect(listing.principals.map((p) => p.id)).toEqual(['me', 'grp', 'jane']);
expect(listing.principals.every((p) => p.accountId === 'me')).toBe(true);
expect(requests).toHaveLength(2);
@@ -125,6 +126,25 @@ describe('listPrincipals', () => {
expect(await client.listPrincipals()).toEqual({ principals: [], status: 'restricted' });
});
it('does not blame the directory setting when a 0.16 role may get principals but not query them', async () => {
// 0.16's Principal/get without ids returns every principal (get.rs), and
// its directory setting has no effect over JMAP.
const client = makeClient();
answer(client, (calls) => (calls[0][0] === 'Principal/query'
? NOT_AUTHORIZED
: [['Principal/get', { accountId: 'me', state: 'n', list: [
{ id: 'me', type: 'individual', name: 'me@example.com', description: null, email: 'me@example.com' },
{ id: 'a', type: 'individual', name: 'anna@example.com', description: null, email: 'anna@example.com' },
{ id: 'b', type: 'individual', name: 'bob@example.com', description: null, email: 'bob@example.com' },
], notFound: [] }, '0']]));
const listing = await client.listPrincipals();
expect(listing.status).toBe('restricted');
expect(listing.directoryQueriesOff).toBeUndefined();
expect(listing.principals.map((p) => p.id)).toEqual(['me', 'a', 'b']);
});
it('keeps the pages already fetched when a later page fails', async () => {
const client = makeClient();
vi.spyOn(console, 'error').mockImplementation(() => {});
+14 -2
View File
@@ -435,6 +435,13 @@ const MAILBOX_PROPERTIES = [
// SieveScript properties (RFC 9661 §2), explicit for the same reason.
const SIEVE_SCRIPT_PROPERTIES = ["id", "name", "blobId", "isActive"] as const;
// How Stalwart 1.0 words its refusal of Principal/query while the
// administrator has directory queries off (jmap/src/principal/query.rs). A
// role without the permission gets `forbidden` too, as "You are not
// authorized to perform this action", so only the wording tells the two
// apart; it picks the explanation shown, never what Bulwark does.
const DIRECTORY_QUERIES_OFF = "The administrator has disabled directory queries.";
/**
* Detect whether a calendar object returned by the server is actually a
* task (VTODO) rather than an event (VEVENT). CalDAV clients like
@@ -6088,7 +6095,8 @@ export class JMAPClient implements IJMAPClient {
* does the same for a role without the query permission. The chained get
* then fails with `invalidResultReference`, so only the query's own error
* says why. The listing is then `restricted` to what Principal/get without
* ids still returns.
* ids still returns: on 1.0 the principals the user knows, on 0.16
* everyone, unless the role may not read principals either.
*/
async listPrincipals(targetAccountId?: string): Promise<PrincipalListing> {
if (!this.supportsPrincipals()) return { principals: [], status: "ok" };
@@ -6113,7 +6121,11 @@ export class JMAPClient implements IJMAPClient {
if (!queryResp || !getResp) {
const error = responses.find((r) => r[0] === "error")?.[1] as JmapErrorInfo | undefined;
if (error?.type === "forbidden") {
return { principals: await this.getKnownPrincipals(accountId), status: "restricted" };
return {
principals: await this.getKnownPrincipals(accountId),
status: "restricted",
...(error.description === DIRECTORY_QUERIES_OFF ? { directoryQueriesOff: true } : {}),
};
}
console.error("Failed to fetch principals:", error);
return { principals: all, status: "error" };
+6
View File
@@ -548,6 +548,12 @@ export type PrincipalDirectoryStatus = 'ok' | 'restricted' | 'error';
export interface PrincipalListing {
principals: Principal[];
status: PrincipalDirectoryStatus;
/**
* With `restricted`: the administrator turned directory queries off, as
* Stalwart 1.0 says in its refusal, rather than the user's role lacking
* the permission. 0.16 has no such refusal over JMAP.
*/
directoryQueriesOff?: boolean;
}
/**
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "لا توجد أوقات انشغال مُشارَكة",
"unknown": "ليس مستخدمًا على هذا الخادم",
"error": "تعذّر التحقق",
"checking": "جارٍ التحقق…"
"checking": "جارٍ التحقق…",
"unavailable_on_server": "معلومات التوفر/الانشغال غير متاحة على هذا الخادم"
},
"title": "المشاركون",
"add": "إضافة مشارك",
@@ -3267,6 +3268,7 @@
"no_principals": "لم يتم العثور على مستخدمين أو مجموعات أخرى.",
"no_match": "لا توجد نتائج مطابقة.",
"directory_restricted": "لا يسمح خادمك بتصفح دليل المستخدمين، لذا لا تظهر إلا مجموعاتك والأشخاص الذين شاركوا شيئًا معك. يمكن للمسؤول تفعيل استعلامات الدليل من إعدادات المشاركة في Stalwart.",
"directory_not_browsable": "لا يسمح لك خادمك بتصفح دليل المستخدمين.",
"directory_failed": "تعذّر تحميل قائمة المستخدمين والمجموعات.",
"remove": "إزالة الوصول",
"group": "مجموعة",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "No hi ha hores ocupades compartides",
"unknown": "No és un usuari d'aquest servidor",
"error": "No s'ha pogut comprovar",
"checking": "Comprovant…"
"checking": "Comprovant…",
"unavailable_on_server": "La informació de lliure/ocupat no està disponible en aquest servidor"
},
"title": "Participants",
"add": "Afegeix un participant",
@@ -3267,6 +3268,7 @@
"no_principals": "No s'ha trobat cap altre usuari o grup.",
"no_match": "Cap coincidència.",
"directory_restricted": "El vostre servidor no permet explorar el directori d'usuaris, així que només es mostren els vostres grups i les persones que han compartit alguna cosa amb vós. Un administrador pot activar les consultes al directori a la configuració de compartició de Stalwart.",
"directory_not_browsable": "El vostre servidor no us permet explorar el directori d'usuaris.",
"directory_failed": "No s'ha pogut carregar la llista d'usuaris i grups.",
"remove": "Elimina l'accés",
"group": "Grup",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Žádné sdílené obsazené časy",
"unknown": "Není uživatelem tohoto serveru",
"error": "Nepodařilo se ověřit",
"checking": "Ověřuje se…"
"checking": "Ověřuje se…",
"unavailable_on_server": "Informace o volném/obsazeném čase nejsou na tomto serveru dostupné"
},
"title": "Účastníci",
"add": "Přidat účastníka",
@@ -3586,6 +3587,7 @@
"no_principals": "Nenalezeni žádní další uživatelé ani skupiny.",
"no_match": "Žádné výsledky.",
"directory_restricted": "Váš server neumožňuje procházet adresář uživatelů, proto se zobrazují jen vaše skupiny a lidé, kteří s vámi něco sdíleli. Správce může dotazy na adresář zapnout v nastavení sdílení serveru Stalwart.",
"directory_not_browsable": "Váš server vám neumožňuje procházet adresář uživatelů.",
"directory_failed": "Seznam uživatelů a skupin se nepodařilo načíst.",
"remove": "Odebrat přístup",
"group": "Skupina",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Ingen delte optagede tider",
"unknown": "Ikke en bruger på denne server",
"error": "Kunne ikke kontrollere",
"checking": "Kontrollerer…"
"checking": "Kontrollerer…",
"unavailable_on_server": "Ledig/optaget-oplysninger er ikke tilgængelige på denne server"
},
"title": "Deltagere",
"add": "Tilføj deltager",
@@ -3267,6 +3268,7 @@
"no_principals": "Ingen andre brugere eller grupper fundet.",
"no_match": "Ingen match.",
"directory_restricted": "Din server tillader ikke, at brugerkataloget gennemses, så kun dine grupper og personer, der har delt noget med dig, vises. En administrator kan slå katalogforespørgsler til i Stalwarts delingsindstillinger.",
"directory_not_browsable": "Din server lader dig ikke gennemse brugerkataloget.",
"directory_failed": "Kunne ikke indlæse listen over brugere og grupper.",
"remove": "Fjern adgang",
"group": "Gruppe",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Keine belegten Zeiten freigegeben",
"unknown": "Kein Benutzer auf diesem Server",
"error": "Konnte nicht geprüft werden",
"checking": "Wird geprüft…"
"checking": "Wird geprüft…",
"unavailable_on_server": "Frei/Belegt-Informationen sind auf diesem Server nicht verfügbar"
},
"title": "Teilnehmer",
"add": "Teilnehmer hinzufügen",
@@ -3586,6 +3587,7 @@
"no_principals": "Keine weiteren Benutzer oder Gruppen gefunden.",
"no_match": "Keine Treffer.",
"directory_restricted": "Ihr Server erlaubt kein Durchsuchen seines Benutzerverzeichnisses, daher werden nur Ihre Gruppen und Personen aufgeführt, die etwas mit Ihnen geteilt haben. Ein Administrator kann Verzeichnisabfragen in den Freigabeeinstellungen von Stalwart aktivieren.",
"directory_not_browsable": "Ihr Server erlaubt Ihnen nicht, sein Benutzerverzeichnis zu durchsuchen.",
"directory_failed": "Die Liste der Benutzer und Gruppen konnte nicht geladen werden.",
"remove": "Zugriff entfernen",
"group": "Gruppe",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "No busy times shared",
"unknown": "Not a user on this server",
"error": "Couldn't check",
"checking": "Checking…"
"checking": "Checking…",
"unavailable_on_server": "Free/busy isn't available on this server"
},
"title": "Participants",
"add": "Add participant",
@@ -3267,6 +3268,7 @@
"no_principals": "No other users or groups found.",
"no_match": "No matches.",
"directory_restricted": "Your server doesn't allow browsing its user directory, so only your groups and people who have shared with you are listed. An administrator can turn on directory queries in Stalwart's sharing settings.",
"directory_not_browsable": "Your server doesn't let you browse its user directory.",
"directory_failed": "Couldn't load the list of users and groups.",
"remove": "Remove access",
"group": "Group",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "No hay horas ocupadas compartidas",
"unknown": "No es un usuario de este servidor",
"error": "No se pudo comprobar",
"checking": "Comprobando…"
"checking": "Comprobando…",
"unavailable_on_server": "La información de libre/ocupado no está disponible en este servidor"
},
"title": "Participantes",
"add": "Agregar participante",
@@ -3586,6 +3587,7 @@
"no_principals": "No se han encontrado otros usuarios ni grupos.",
"no_match": "Sin resultados.",
"directory_restricted": "Tu servidor no permite explorar su directorio de usuarios, así que solo se muestran tus grupos y las personas que han compartido algo contigo. Un administrador puede activar las consultas al directorio en los ajustes de uso compartido de Stalwart.",
"directory_not_browsable": "Tu servidor no te permite explorar su directorio de usuarios.",
"directory_failed": "No se pudo cargar la lista de usuarios y grupos.",
"remove": "Quitar acceso",
"group": "Grupo",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "هیچ زمان مشغولی به اشتراک گذاشته نشده است",
"unknown": "کاربر این سرور نیست",
"error": "بررسی ممکن نشد",
"checking": "در حال بررسی…"
"checking": "در حال بررسی…",
"unavailable_on_server": "اطلاعات آزاد/مشغول در این سرور در دسترس نیست"
},
"title": "شرکت‌کنندگان",
"add": "افزودن شرکت‌کننده",
@@ -3267,6 +3268,7 @@
"no_principals": "کاربر یا گروه دیگری یافت نشد.",
"no_match": "موردی مطابقت ندارد.",
"directory_restricted": "سرور شما اجازهٔ مرور فهرست کاربرانش را نمی‌دهد، بنابراین فقط گروه‌های شما و افرادی که چیزی را با شما به اشتراک گذاشته‌اند نمایش داده می‌شوند. مدیر می‌تواند پرس‌وجوهای فهرست را در تنظیمات اشتراک‌گذاری Stalwart فعال کند.",
"directory_not_browsable": "سرور شما به شما اجازهٔ مرور فهرست کاربرانش را نمی‌دهد.",
"directory_failed": "بارگذاری فهرست کاربران و گروه‌ها ممکن نشد.",
"remove": "حذف دسترسی",
"group": "گروه",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Aucune plage occupée partagée",
"unknown": "Pas un utilisateur de ce serveur",
"error": "Vérification impossible",
"checking": "Vérification…"
"checking": "Vérification…",
"unavailable_on_server": "Les informations libre/occupé ne sont pas disponibles sur ce serveur"
},
"title": "Participants",
"add": "Ajouter un participant",
@@ -3586,6 +3587,7 @@
"no_principals": "Aucun autre utilisateur ou groupe trouvé.",
"no_match": "Aucun résultat.",
"directory_restricted": "Votre serveur ne permet pas de parcourir son annuaire d'utilisateurs, seuls vos groupes et les personnes ayant partagé quelque chose avec vous sont donc listés. Un administrateur peut activer les requêtes d'annuaire dans les paramètres de partage de Stalwart.",
"directory_not_browsable": "Votre serveur ne vous permet pas de parcourir son annuaire d'utilisateurs.",
"directory_failed": "Impossible de charger la liste des utilisateurs et des groupes.",
"remove": "Révoquer l'accès",
"group": "Groupe",
+3 -1
View File
@@ -2788,7 +2788,8 @@
"no_busy_times": "לא שותפו זמנים תפוסים",
"unknown": "לא משתמש בשרת זה",
"error": "לא ניתן היה לבדוק",
"checking": "בודק…"
"checking": "בודק…",
"unavailable_on_server": "מידע פנוי/תפוס אינו זמין בשרת זה"
},
"title": "משתתפים",
"add": "הוסף משתתף",
@@ -3584,6 +3585,7 @@
"no_principals": "לא נמצאו משתמשים או קבוצות אחרים.",
"no_match": "אין התאמות.",
"directory_restricted": "השרת שלך אינו מאפשר לעיין בספריית המשתמשים שלו, ולכן מוצגות רק הקבוצות שלך ואנשים ששיתפו איתך משהו. מנהל מערכת יכול להפעיל שאילתות ספרייה בהגדרות השיתוף של Stalwart.",
"directory_not_browsable": "השרת שלך אינו מאפשר לך לעיין בספריית המשתמשים שלו.",
"directory_failed": "לא ניתן היה לטעון את רשימת המשתמשים והקבוצות.",
"remove": "הסר גישה",
"group": "קבוצה",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Nincsenek megosztott foglalt időszakok",
"unknown": "Nem felhasználó ezen a kiszolgálón",
"error": "Nem sikerült ellenőrizni",
"checking": "Ellenőrzés…"
"checking": "Ellenőrzés…",
"unavailable_on_server": "A szabad/foglalt információ nem érhető el ezen a szerveren"
},
"title": "Résztvevők",
"add": "Résztvevő hozzáadása",
@@ -3267,6 +3268,7 @@
"no_principals": "Nincs más felhasználó vagy csoport.",
"no_match": "Nincs találat.",
"directory_restricted": "A szervered nem engedi a felhasználói címtár böngészését, ezért csak a csoportjaid és azok jelennek meg, akik megosztottak veled valamit. Egy rendszergazda a Stalwart megosztási beállításaiban engedélyezheti a címtár-lekérdezéseket.",
"directory_not_browsable": "A szervered nem engedi, hogy böngészd a felhasználói címtárát.",
"directory_failed": "Nem sikerült betölteni a felhasználók és csoportok listáját.",
"remove": "Hozzáférés eltávolítása",
"group": "Csoport",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Nessun orario occupato condiviso",
"unknown": "Non è un utente di questo server",
"error": "Impossibile verificare",
"checking": "Verifica in corso…"
"checking": "Verifica in corso…",
"unavailable_on_server": "Le informazioni libero/occupato non sono disponibili su questo server"
},
"title": "Partecipanti",
"add": "Aggiungi partecipante",
@@ -3586,6 +3587,7 @@
"no_principals": "Nessun altro utente o gruppo trovato.",
"no_match": "Nessun risultato.",
"directory_restricted": "Il tuo server non consente di sfogliare la directory degli utenti, quindi vengono elencati solo i tuoi gruppi e le persone che hanno condiviso qualcosa con te. Un amministratore può attivare le query sulla directory nelle impostazioni di condivisione di Stalwart.",
"directory_not_browsable": "Il tuo server non ti consente di sfogliare la directory degli utenti.",
"directory_failed": "Impossibile caricare l'elenco di utenti e gruppi.",
"remove": "Rimuovi accesso",
"group": "Gruppo",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "共有された予定あり時間はありません",
"unknown": "このサーバーのユーザーではありません",
"error": "確認できませんでした",
"checking": "確認中…"
"checking": "確認中…",
"unavailable_on_server": "このサーバーでは空き時間情報を利用できません"
},
"title": "参加者",
"add": "参加者を追加",
@@ -3586,6 +3587,7 @@
"no_principals": "他のユーザーまたはグループは見つかりません。",
"no_match": "一致する項目がありません。",
"directory_restricted": "お使いのサーバーではユーザーディレクトリの参照が許可されていないため、所属グループと、あなたと共有しているユーザーのみが表示されます。管理者は Stalwart の共有設定でディレクトリクエリを有効にできます。",
"directory_not_browsable": "お使いのサーバーでは、ユーザーディレクトリを参照できません。",
"directory_failed": "ユーザーとグループの一覧を読み込めませんでした。",
"remove": "アクセス権を削除",
"group": "グループ",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "공유된 바쁜 시간 없음",
"unknown": "이 서버의 사용자가 아님",
"error": "확인할 수 없음",
"checking": "확인 중…"
"checking": "확인 중…",
"unavailable_on_server": "이 서버에서는 한가함/바쁨 정보를 사용할 수 없습니다"
},
"title": "참석자",
"add": "참석자 추가",
@@ -3586,6 +3587,7 @@
"no_principals": "다른 사용자나 그룹을 찾을 수 없습니다.",
"no_match": "일치하는 항목이 없습니다.",
"directory_restricted": "서버에서 사용자 디렉터리 탐색을 허용하지 않아 내 그룹과 나와 공유한 사람만 표시됩니다. 관리자는 Stalwart의 공유 설정에서 디렉터리 쿼리를 켤 수 있습니다.",
"directory_not_browsable": "서버에서 사용자 디렉터리를 탐색할 수 없습니다.",
"directory_failed": "사용자 및 그룹 목록을 불러오지 못했습니다.",
"remove": "액세스 권한 제거",
"group": "그룹",
+3 -1
View File
@@ -2863,7 +2863,8 @@
"no_busy_times": "Nav kopīgotu aizņemto laiku",
"unknown": "Nav šī servera lietotājs",
"error": "Neizdevās pārbaudīt",
"checking": "Pārbauda…"
"checking": "Pārbauda…",
"unavailable_on_server": "Informācija par brīvo/aizņemto laiku šajā serverī nav pieejama"
},
"title": "Dalībnieki",
"add": "Pievienot dalībnieku",
@@ -3586,6 +3587,7 @@
"no_principals": "Citi lietotāji vai grupas nav atrastas.",
"no_match": "Nav atbilstību.",
"directory_restricted": "Jūsu serveris neļauj pārlūkot tā lietotāju direktoriju, tāpēc tiek parādītas tikai jūsu grupas un personas, kas ar jums kaut ko kopīgojušas. Administrators var ieslēgt direktorija vaicājumus Stalwart kopīgošanas iestatījumos.",
"directory_not_browsable": "Jūsu serveris neļauj jums pārlūkot tā lietotāju direktoriju.",
"directory_failed": "Neizdevās ielādēt lietotāju un grupu sarakstu.",
"remove": "Noņemt piekļuvi",
"group": "Grupa",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Хуваалцсан завгүй цаг байхгүй",
"unknown": "Энэ серверийн хэрэглэгч биш",
"error": "Шалгаж чадсангүй",
"checking": "Шалгаж байна…"
"checking": "Шалгаж байна…",
"unavailable_on_server": "Энэ сервер дээр сул/завгүй мэдээлэл ашиглах боломжгүй"
},
"title": "Оролцогчид",
"add": "Оролцогч нэмэх",
@@ -3267,6 +3268,7 @@
"no_principals": "Өөр хэрэглэгч болон групп олдсонгүй.",
"no_match": "Тохирох зүйл алга.",
"directory_restricted": "Таны сервер хэрэглэгчийн лавлахаа үзэхийг зөвшөөрдөггүй тул зөвхөн таны бүлгүүд болон тантай ямар нэг зүйл хуваалцсан хүмүүс харагдана. Админ Stalwart-ын хуваалцах тохиргоонд лавлахын хайлтыг идэвхжүүлж болно.",
"directory_not_browsable": "Таны сервер танд хэрэглэгчийн лавлахаа үзэхийг зөвшөөрдөггүй.",
"directory_failed": "Хэрэглэгч болон бүлгийн жагсаалтыг ачаалж чадсангүй.",
"remove": "Хандалтыг арилгах",
"group": "Бүлэг",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Ingen delte opptatt-tider",
"unknown": "Ikke en bruker på denne serveren",
"error": "Kunne ikke sjekke",
"checking": "Sjekker…"
"checking": "Sjekker…",
"unavailable_on_server": "Ledig/opptatt-informasjon er ikke tilgjengelig på denne serveren"
},
"title": "Deltakere",
"add": "Legg til deltaker",
@@ -3267,6 +3268,7 @@
"no_principals": "Ingen andre brukere eller grupper funnet.",
"no_match": "Ingen treff.",
"directory_restricted": "Serveren din tillater ikke å bla gjennom brukerkatalogen, så bare gruppene dine og personer som har delt noe med deg, vises. En administrator kan slå på katalogspørringer i delingsinnstillingene i Stalwart.",
"directory_not_browsable": "Serveren din lar deg ikke bla gjennom brukerkatalogen.",
"directory_failed": "Kunne ikke laste inn listen over brukere og grupper.",
"remove": "Fjern tilgang",
"group": "Gruppe",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Geen bezette tijden gedeeld",
"unknown": "Geen gebruiker op deze server",
"error": "Kon niet controleren",
"checking": "Controleren…"
"checking": "Controleren…",
"unavailable_on_server": "Vrij/bezet-informatie is niet beschikbaar op deze server"
},
"title": "Deelnemers",
"add": "Deelnemer toevoegen",
@@ -3586,6 +3587,7 @@
"no_principals": "Geen andere gebruikers of groepen gevonden.",
"no_match": "Geen overeenkomsten.",
"directory_restricted": "Uw server staat het doorbladeren van de gebruikersdirectory niet toe, daarom worden alleen uw groepen en personen die iets met u hebben gedeeld weergegeven. Een beheerder kan directoryquery's inschakelen in de instellingen voor delen van Stalwart.",
"directory_not_browsable": "Uw server staat u niet toe de gebruikersdirectory te doorbladeren.",
"directory_failed": "Kan de lijst met gebruikers en groepen niet laden.",
"remove": "Toegang intrekken",
"group": "Groep",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Brak udostępnionych zajętych terminów",
"unknown": "Nie jest użytkownikiem tego serwera",
"error": "Nie udało się sprawdzić",
"checking": "Sprawdzanie…"
"checking": "Sprawdzanie…",
"unavailable_on_server": "Informacje wolny/zajęty nie są dostępne na tym serwerze"
},
"title": "Uczestnicy",
"add": "Dodaj uczestnika",
@@ -3586,6 +3587,7 @@
"no_principals": "Nie znaleziono innych użytkowników ani grup.",
"no_match": "Brak wyników.",
"directory_restricted": "Twój serwer nie pozwala przeglądać katalogu użytkowników, dlatego widoczne są tylko Twoje grupy i osoby, które coś Ci udostępniły. Administrator może włączyć zapytania do katalogu w ustawieniach udostępniania Stalwart.",
"directory_not_browsable": "Twój serwer nie pozwala Ci przeglądać katalogu użytkowników.",
"directory_failed": "Nie udało się wczytać listy użytkowników i grup.",
"remove": "Usuń dostęp",
"group": "Grupa",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Nenhum horário ocupado compartilhado",
"unknown": "Não é um utilizador deste servidor",
"error": "Não foi possível verificar",
"checking": "A verificar…"
"checking": "A verificar…",
"unavailable_on_server": "As informações de livre/ocupado não estão disponíveis neste servidor"
},
"title": "Participantes",
"add": "Adicionar participante",
@@ -3586,6 +3587,7 @@
"no_principals": "Nenhum outro usuário ou grupo encontrado.",
"no_match": "Sem resultados.",
"directory_restricted": "Seu servidor não permite navegar no diretório de usuários, então apenas seus grupos e as pessoas que compartilharam algo com você são listados. Um administrador pode ativar as consultas ao diretório nas configurações de compartilhamento do Stalwart.",
"directory_not_browsable": "Seu servidor não permite que você navegue no diretório de usuários.",
"directory_failed": "Não foi possível carregar a lista de usuários e grupos.",
"remove": "Remover acesso",
"group": "Grupo",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Niciun interval ocupat partajat",
"unknown": "Nu este un utilizator al acestui server",
"error": "Nu s-a putut verifica",
"checking": "Se verifică…"
"checking": "Se verifică…",
"unavailable_on_server": "Informațiile liber/ocupat nu sunt disponibile pe acest server"
},
"title": "Participanți",
"add": "Adăugați un participant",
@@ -3267,6 +3268,7 @@
"no_principals": "Nu s-au găsit alți utilizatori sau grupuri.",
"no_match": "Nu s-au găsit rezultate.",
"directory_restricted": "Serverul dvs. nu permite răsfoirea directorului de utilizatori, așa că sunt afișate doar grupurile dvs. și persoanele care au partajat ceva cu dvs. Un administrator poate activa interogările directorului în setările de partajare ale Stalwart.",
"directory_not_browsable": "Serverul dvs. nu vă permite să răsfoiți directorul de utilizatori.",
"directory_failed": "Lista de utilizatori și grupuri nu a putut fi încărcată.",
"remove": "Eliminați accesul",
"group": "Grup",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Нет общих данных о занятости",
"unknown": "Не пользователь этого сервера",
"error": "Не удалось проверить",
"checking": "Проверка…"
"checking": "Проверка…",
"unavailable_on_server": "Сведения о занятости недоступны на этом сервере"
},
"title": "Участники",
"add": "Добавить участника",
@@ -3586,6 +3587,7 @@
"no_principals": "Других пользователей или групп не найдено.",
"no_match": "Нет совпадений.",
"directory_restricted": "Ваш сервер не разрешает просматривать каталог пользователей, поэтому показаны только ваши группы и люди, которые чем-то поделились с вами. Администратор может включить запросы к каталогу в настройках общего доступа Stalwart.",
"directory_not_browsable": "Ваш сервер не позволяет вам просматривать каталог пользователей.",
"directory_failed": "Не удалось загрузить список пользователей и групп.",
"remove": "Отозвать доступ",
"group": "Группа",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Žiadne zdieľané obsadené časy",
"unknown": "Nie je používateľom tohto servera",
"error": "Nepodarilo sa overiť",
"checking": "Overuje sa…"
"checking": "Overuje sa…",
"unavailable_on_server": "Informácie o voľnom/obsadenom čase nie sú na tomto serveri dostupné"
},
"title": "Účastníci",
"add": "Pridať účastníka",
@@ -3267,6 +3268,7 @@
"no_principals": "Neboli nájdení žiadni ďalší používatelia ani skupiny.",
"no_match": "Žiadne výsledky.",
"directory_restricted": "Váš server nepovoľuje prehliadať adresár používateľov, preto sa zobrazujú iba vaše skupiny a ľudia, ktorí s vami niečo zdieľali. Správca môže zapnúť dopyty na adresár v nastaveniach zdieľania servera Stalwart.",
"directory_not_browsable": "Váš server vám nepovoľuje prehliadať adresár používateľov.",
"directory_failed": "Zoznam používateľov a skupín sa nepodarilo načítať.",
"remove": "Odstrániť prístup",
"group": "Skupina",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Paylaşılan meşgul zaman yok",
"unknown": "Bu sunucuda kullanıcı değil",
"error": "Kontrol edilemedi",
"checking": "Kontrol ediliyor…"
"checking": "Kontrol ediliyor…",
"unavailable_on_server": "Müsait/meşgul bilgisi bu sunucuda kullanılamıyor"
},
"title": "Katılımcılar",
"add": "Katılımcı ekle",
@@ -3267,6 +3268,7 @@
"no_principals": "Başka kullanıcı veya grup bulunamadı.",
"no_match": "Eşleşme yok.",
"directory_restricted": "Sunucunuz kullanıcı dizinine göz atılmasına izin vermiyor, bu nedenle yalnızca gruplarınız ve sizinle bir şey paylaşmış kişiler listeleniyor. Bir yönetici, Stalwart'ın paylaşım ayarlarından dizin sorgularını açabilir.",
"directory_not_browsable": "Sunucunuz kullanıcı dizinine göz atmanıza izin vermiyor.",
"directory_failed": "Kullanıcı ve grup listesi yüklenemedi.",
"remove": "Erişimi kaldır",
"group": "Grup",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "Немає спільних даних про зайнятість",
"unknown": "Не користувач цього сервера",
"error": "Не вдалося перевірити",
"checking": "Перевірка…"
"checking": "Перевірка…",
"unavailable_on_server": "Відомості про зайнятість недоступні на цьому сервері"
},
"title": "Учасники",
"add": "Додати учасника",
@@ -3586,6 +3587,7 @@
"no_principals": "Інших користувачів або груп не знайдено.",
"no_match": "Збігів немає.",
"directory_restricted": "Ваш сервер не дозволяє переглядати каталог користувачів, тому показано лише ваші групи та людей, які чимось поділилися з вами. Адміністратор може ввімкнути запити до каталогу в налаштуваннях спільного доступу Stalwart.",
"directory_not_browsable": "Ваш сервер не дозволяє вам переглядати каталог користувачів.",
"directory_failed": "Не вдалося завантажити список користувачів і груп.",
"remove": "Видалити доступ",
"group": "Група",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "未分享忙碌時段",
"unknown": "不是此伺服器上的使用者",
"error": "無法檢查",
"checking": "檢查中…"
"checking": "檢查中…",
"unavailable_on_server": "此伺服器不提供空閒/忙碌資訊"
},
"title": "參與者",
"add": "新增參與者",
@@ -3267,6 +3268,7 @@
"no_principals": "找不到其他使用者或群組。",
"no_match": "找不到相符項目。",
"directory_restricted": "您的伺服器不允許瀏覽使用者目錄,因此只會列出您所屬的群組,以及曾與您分享內容的使用者。管理員可以在 Stalwart 的分享設定中啟用目錄查詢。",
"directory_not_browsable": "您的伺服器不允許您瀏覽使用者目錄。",
"directory_failed": "無法載入使用者和群組清單。",
"remove": "移除存取權",
"group": "群組",
+3 -1
View File
@@ -2864,7 +2864,8 @@
"no_busy_times": "未共享忙碌时间",
"unknown": "不是此服务器上的用户",
"error": "无法检查",
"checking": "正在检查…"
"checking": "正在检查…",
"unavailable_on_server": "此服务器不提供空闲/忙碌信息"
},
"title": "参加者",
"add": "添加参与者",
@@ -3586,6 +3587,7 @@
"no_principals": "未找到其他用户或群组。",
"no_match": "无匹配项。",
"directory_restricted": "您的服务器不允许浏览用户目录,因此仅列出您所在的群组以及与您共享过内容的用户。管理员可以在 Stalwart 的共享设置中启用目录查询。",
"directory_not_browsable": "您的服务器不允许您浏览用户目录。",
"directory_failed": "无法加载用户和群组列表。",
"remove": "取消访问",
"group": "群组",