feat(notify): add notif to internal users when granted

- add coalesced protection to avoid mail bombing if an invited goes many grant in a short period
    - add resentd method in share menu item (work for both internal and external users)
    - user can disable email notification via his properties
    - add env variable from admin to disable notifications
This commit is contained in:
Edouard Vanbelle
2026-06-05 09:46:51 +02:00
parent b8e8fcd7dc
commit 8cc21f17c5
45 changed files with 1906 additions and 81 deletions
+81
View File
@@ -9,6 +9,7 @@
* 'sharedWith' — lane = user | 'links:public' | 'links:password'; row identity = resource
*/
import { getCsrfHeaders } from '../core/csrf.js';
import { formatExpiryChip } from '../core/formatters.js';
import { i18n } from '../core/i18n.js';
import { fileSharing } from '../features/sharing/fileSharing.js';
@@ -432,6 +433,24 @@ class MySharesList {
const initialExpiry = grant.expires_at ? String(grant.expires_at).slice(0, 10) : null;
if (grant.subject_type === 'user' || grant.subject_type === 'group') {
// PR N2 — "Resend invitation email" / "Notify by email" /
// "Notify group members". First item in the menu; only
// present for user and group subjects (token shares have
// no email channel; the server returns 409 anyway).
const notifyLabel =
grant.subject_type === 'group'
? i18n.t('myshares.notifyGroupMembers', 'Notify group members')
: grant.is_external
? i18n.t('myshares.resendInvitation', 'Resend invitation email')
: i18n.t('myshares.notifyByEmail', 'Notify by email');
menu.appendChild(
this._menuItem('fas fa-paper-plane', notifyLabel, false, async () => {
menu.remove();
await this._notifyRecipient(grant);
})
);
menu.appendChild(this._menuSeparator());
for (const role of /** @type {('admin'|'editor'|'viewer')[]} */ (['admin', 'editor', 'viewer'])) {
const isCurrent = grant.role === role;
const mi = this._menuItem(isCurrent ? 'fas fa-check' : '', roleLabel(role), false, async () => {
@@ -554,6 +573,68 @@ class MySharesList {
return row;
}
/**
* PR N2 — manual share-notification resend. Calls
* `POST /api/grants/{grant_id}/notify` and surfaces the aggregated
* outcome to the granter. The endpoint returns:
* - 204 No Content — all recipients sent
* - 200 + NotifyOutcomeSetDto — mixed outcomes (coalesced /
* not-applicable / partial sent)
* - 429 Too Many Requests — per-recipient rate limit hit on every
* recipient
* - 404 Not Found — caller is not the granter, or grant
* doesn't exist (anti-enumeration; the audit log carries the
* truth)
* - 409 Conflict — token subject (UI shouldn't reach this)
*
* @param {OutgoingResourceGrant} grant
*/
async _notifyRecipient(grant) {
try {
const resp = await fetch(`/api/grants/${encodeURIComponent(grant.grant_id)}/notify`, {
method: 'POST',
credentials: 'same-origin',
headers: { ...getCsrfHeaders() }
});
if (resp.status === 204) {
// All sent — silent success.
console.log('[myshares] notify: all recipients sent', grant.grant_id);
return;
}
if (resp.status === 429) {
// eslint-disable-next-line no-alert -- minimal v1 surface
alert(i18n.t('myshares.notifyRateLimited', 'Too many notifications for this recipient — try again later.'));
return;
}
if (resp.ok) {
/** @type {{ total_recipients: number, outcomes: Array<{kind: string, detail?: string, reason?: string}> }} */
const body = await resp.json();
console.log('[myshares] notify outcomes:', body);
const sent = body.outcomes.filter((o) => o.kind === 'sent').length;
const coalesced = body.outcomes.filter((o) => o.kind === 'coalesced').length;
const notApplicable = body.outcomes.filter((o) => o.kind === 'not_applicable').length;
/** @type {string[]} */
const lines = [];
if (sent > 0) lines.push(`${sent} recipient(s) notified by email.`);
if (coalesced > 0) lines.push(`${coalesced} recipient(s) already notified recently — they'll see the share at next login.`);
if (notApplicable > 0) lines.push(`${notApplicable} recipient(s) skipped (opted out, no email, or operator-disabled).`);
if (lines.length > 0) {
// eslint-disable-next-line no-alert -- minimal v1 surface
alert(lines.join('\n'));
}
return;
}
// 404 / 409 / unexpected
console.error('[myshares] notify failed:', resp.status);
// eslint-disable-next-line no-alert -- minimal v1 surface
alert(i18n.t('myshares.notifyFailed', 'Could not send notification.'));
} catch (err) {
console.error('[myshares] notify error:', err);
// eslint-disable-next-line no-alert -- minimal v1 surface
alert(i18n.t('myshares.notifyFailed', 'Could not send notification.'));
}
}
/**
* Non-closing password row embedded in the link context menu.
* Saves immediately on confirm (blur / Enter).
+73 -1
View File
@@ -1004,6 +1004,13 @@ const shareModal = {
const item = this._item;
const itemType = this._itemType;
// Accumulate notification outcomes across all create-grant calls
// in this apply round so the post-apply summary aggregates ("3
// recipients notified, 1 already notified recently") rather than
// showing one toast per granted member.
/** @type {Array<{kind: string, detail?: string, last_sent_at?: string, retry_after_secs?: number, reason?: string}>} */
const notifyOutcomes = [];
try {
// ── Grants ─────────────────────────────────────────────────────────
for (const m of this._localMembers) {
@@ -1028,12 +1035,17 @@ const shareModal = {
// user_id in the grant DTO. Until `fetchOutgoingGrants`
// refreshes below, the row keeps the pending vignette.
const subject = m._invitedEmail ? { type: 'email', email: m._invitedEmail } : { type: m.grant.subject.type, id: m.grant.subject.id };
await grants.createGrant({
const result = await grants.createGrant({
subject,
resource: { type: itemType, id: item.id },
role: m.role,
expires_at: expiresIso
});
// PR N1: collect per-recipient notification outcomes
// so we can show one aggregated summary after the loop.
if (result?.notification?.outcomes) {
notifyOutcomes.push(...result.notification.outcomes);
}
}
}
@@ -1076,6 +1088,16 @@ const shareModal = {
Modal.close(true);
this._onApplied?.();
// PR N1: surface share-notification outcomes. The granter
// needs to know whether the recipient actually got an email
// (or was silently coalesced / rate-limited / opted out).
// Without a project-wide toast component the cheapest
// honest signal is a console log + a one-shot alert() for
// the non-success states. A proper toast surface lands in
// a small follow-up; the backend data is correct, the UI
// is just brief.
_surfaceNotifySummary(notifyOutcomes);
} catch (err) {
console.error('shareModal._applyAll error:', err);
if (Modal.confirmBtn) Modal.confirmBtn.disabled = false;
@@ -1083,4 +1105,54 @@ const shareModal = {
}
};
/**
* Show a one-shot aggregated summary of share-notification outcomes
* after a batch of create-grant calls. v1 surface is minimal — logs
* everything to the console for traceability and pops a single alert()
* only when at least one recipient was coalesced, rate-limited, or
* landed on the not-applicable arm (i.e. the granter SHOULD know the
* email didn't go). The all-Sent happy path stays silent because the
* modal-close already implies success.
*
* A proper toast component is deferred; this function is the seam to
* upgrade later — replace the alert() body, keep the call site.
*
* @param {Array<{kind: string, detail?: string, last_sent_at?: string, retry_after_secs?: number, reason?: string}>} outcomes
*/
function _surfaceNotifySummary(outcomes) {
if (!outcomes || outcomes.length === 0) return;
// Always log — useful in dev tools regardless of the alert path.
console.log('[share] notification outcomes:', outcomes);
const sent = outcomes.filter((o) => o.kind === 'sent').length;
const coalesced = outcomes.filter((o) => o.kind === 'coalesced').length;
const rateLimited = outcomes.filter((o) => o.kind === 'rate_limited').length;
const notApplicable = outcomes.filter((o) => o.kind === 'not_applicable');
// Happy path — all sent. Stay silent; the closed modal is the toast.
if (coalesced === 0 && rateLimited === 0 && notApplicable.length === 0) return;
/** @type {string[]} */
const lines = [];
if (sent > 0) {
lines.push(`${sent} recipient(s) notified by email.`);
}
if (coalesced > 0) {
lines.push(`${coalesced} recipient(s) already notified recently — they'll see the share at next login.`);
}
if (rateLimited > 0) {
lines.push(`${rateLimited} recipient(s) hit the notification rate limit — try again later.`);
}
if (notApplicable.length > 0) {
const reasons = notApplicable
.map((o) => o.reason)
.filter((r, i, arr) => r && arr.indexOf(r) === i)
.join(', ');
lines.push(`${notApplicable.length} recipient(s) skipped (${reasons || 'unknown'}).`);
}
// eslint-disable-next-line no-alert -- minimal v1 surface; toast component lands as follow-up
alert(lines.join('\n'));
}
export { shareModal };
+4
View File
@@ -338,6 +338,10 @@ const OxiIcons = {
576,
'm 360.55,24 v 72 h 64 c 79.5,0 144,64.5 144,144 0,93.4 -82.8,134.8 -100.6,142.6 -2.2,1 -4.6,1.4 -7.1,1.4 h -2.5 c -9.8,0 -17.8,-8 -17.8,-17.8 0,-8.3 5.9,-15.5 12.8,-20.3 8.9,-6.2 19.2,-18.2 19.2,-40.5 0,-45 -36.5,-81.5 -81.5,-81.5 h -30.5 v 72 c 0,9.7 -5.8,18.5 -14.8,22.2 -9,3.7 -19.3,1.7 -26.2,-5.2 l -136,-136 c -9.4,-9.4 -9.4,-24.6 0,-33.9 l 136,-136 c 6.9,-6.9 17.2,-8.9 26.2,-5.2 9,3.7 14.8,12.5 14.8,22.2 z M 112.5,96 c -44.2,0 -80,35.8 -80,80 v 256 c 0,44.2 35.8,80 80,80 h 256 c 44.2,0 80,-35.8 80,-80 v -32 c 0,-17.7 -14.3,-32 -32,-32 -17.7,0 -32,14.3 -32,32 v 32 c 0,8.8 -7.2,16 -16,16 h -256 c -8.8,0 -16,-7.2 -16,-16 V 176 c 0,-8.8 7.2,-16 16,-16 h 16 c 17.7,0 32,-14.3 32,-32 0,-17.7 -14.3,-32 -32,-32 z'
],
'paper-plane': [
576,
'M290.5 287.7L491.4 86.9 359 456.3 290.5 287.7zM457.4 53L256.6 253.8 88 185.3 457.4 53zM38.1 216.8l205.8 83.6 83.6 205.8c5.3 13.1 18.1 21.7 32.3 21.7 14.7 0 27.8-9.2 32.8-23.1L570.6 8c3.5-9.8 1-20.6-6.3-28s-18.2-9.8-28-6.3L39.4 151.7c-13.9 5-23.1 18.1-23.1 32.8 0 14.2 8.6 27 21.7 32.3z'
],
pause: [
384,
'M48 32C21.5 32 0 53.5 0 80L0 432c0 26.5 21.5 48 48 48l64 0c26.5 0 48-21.5 48-48l0-352c0-26.5-21.5-48-48-48L48 32zm224 0c-26.5 0-48 21.5-48 48l0 352c0 26.5 21.5 48 48 48l64 0c26.5 0 48-21.5 48-48l0-352c0-26.5-21.5-48-48-48l-64 0z'
+2
View File
@@ -166,6 +166,7 @@
* @property {string} [family_name] Last/family name; set at OIDC JIT or via PATCH /api/auth/me/profile (PR 24)
* @property {string} [email_verified_at] ISO 8601 timestamp of the first proof-of-email-control (PR 23). Omitted when unverified.
* @property {string} [preferred_locale] User-chosen locale code (e.g. `"fr"`, `"zh-TW"`); omitted when unset. Round-trips via PATCH /api/auth/me/profile.
* @property {boolean} notify_on_share Whether the user wants share-notification emails ("Alice shared X with you"). Default TRUE. Toggled via the profile checkbox; round-trips via PATCH /api/auth/me/profile.
*/
/**
@@ -365,6 +366,7 @@
* @property {string} granted_at - ISO-8601
* @property {string|null} [expires_at] - ISO-8601 or absent.
* @property {boolean} has_password - True when a token subject has a password set.
* @property {boolean} [is_external] - True when a user subject is a magic-link-only external user (PR N2). Drives the My Shares menu label ("Resend invitation email" vs "Notify by email"). Always false for token and group subjects.
*/
/**
+19 -1
View File
@@ -166,8 +166,26 @@ const grants = {
* Create a new grant.
* Body mirrors `CreateGrantDto`: `{ subject, resource, role }` OR `{ subject, resource, permissions }`.
*
* Response shape (PR N1 — `CreateGrantResponseDto`):
*
* ```json
* {
* "grants": [ {Grant}, … ],
* "notification": {
* "total_recipients": 1,
* "outcomes": [{ "kind": "sent", "detail": "plain_notification" }]
* }
* }
* ```
*
* `notification.outcomes` is empty for token subjects; size 1 for
* user subjects; size N for group subjects (one entry per resolved
* member). Callers that just need the grant rows can `.grants`;
* callers that want to surface "did Carol get my email?" UX read
* `.notification.outcomes[]`.
*
* @param {Object} dto - CreateGrantDto shape
* @returns {Promise<Grant[]>}
* @returns {Promise<{ grants: Grant[], notification: { total_recipients: number, outcomes: Array<{kind: string, detail?: string, last_sent_at?: string, retry_after_secs?: number, reason?: string}> } }>}
*/
async createGrant(dto) {
const response = await fetch('/api/grants', {
+18 -1
View File
@@ -645,6 +645,14 @@ function _renderProfileEdit(user) {
}
givenInput.value = user.given_name || '';
familyInput.value = user.family_name || '';
const notifyInput = /** @type {HTMLInputElement | null} */ (document.getElementById('profile-edit-notify-on-share'));
if (notifyInput) {
// notify_on_share is a boolean on the server; default TRUE for
// pre-existing rows via the column default, so the checkbox is
// ticked unless the user has explicitly opted out.
notifyInput.checked = user.notify_on_share !== false;
}
}
/**
@@ -665,7 +673,7 @@ async function submitProfile(e) {
const givenInput = /** @type {HTMLInputElement} */ (document.getElementById('profile-edit-given-name'));
const familyInput = /** @type {HTMLInputElement} */ (document.getElementById('profile-edit-family-name'));
/** @type {{ username?: string, given_name?: string, family_name?: string }} */
/** @type {{ username?: string, given_name?: string, family_name?: string, notify_on_share?: boolean }} */
const body = {};
if (!usernameInput.disabled && usernameInput.value.trim()) {
body.username = usernameInput.value.trim();
@@ -675,6 +683,15 @@ async function submitProfile(e) {
const family = familyInput.value.trim();
if (family) body.family_name = family;
// Always send the share-notification preference. The backend
// compares against the current value and skips the write if
// unchanged, so this is idempotent — sending it on every save
// simplifies the frontend rather than tracking a dirty bit.
const notifyInput = /** @type {HTMLInputElement | null} */ (document.getElementById('profile-edit-notify-on-share'));
if (notifyInput) {
body.notify_on_share = notifyInput.checked;
}
if (Object.keys(body).length === 0) {
statusEl.innerHTML = `<div class="alert alert-info"><i class="fas fa-info-circle"></i> ${escapeHtml(i18n.t('profile.profile_no_changes'))}</div>`;
return false;
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "شارك {{inviter}} معك {{kind}} على OxiCloud",
"body": "شارك {{inviter}} معك {{kind}} على OxiCloud.\n\nافتحه بالنقر على الرابط أدناه:\n{{link}}\n\nيعمل الرابط مرة واحدة وتنتهي صلاحيته خلال {{ttl_hours}} ساعة.\nإذا لم تكن تتوقع هذه الدعوة، يمكنك تجاهل هذه الرسالة.\n\n— OxiCloud"
"body": "شارك {{inviter_full}} معك {{kind}} على OxiCloud.\n\nافتحه بالنقر على الرابط أدناه:\n{{link}}\n\nيعمل الرابط مرة واحدة وتنتهي صلاحيته خلال {{ttl_hours}} ساعة.\nإذا لم تكن تتوقع هذه الدعوة، يمكنك تجاهل هذه الرسالة.\n\n— OxiCloud"
},
"login": {
"subject": "تسجيل الدخول إلى OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "مجلد",
"english_fallback_divider": "--- النسخة الإنجليزية أدناه ---"
}
},
"notification": {
"share": {
"subject": "شارك {{inviter}} معك {{kind}} على OxiCloud",
"body": "شارك {{inviter_full}} معك {{kind}} على OxiCloud.\n\nافتح OxiCloud لعرض مشاركتك الجديدة:\n{{login_link}}\n\nقد تكون لديك مشاركات جديدة أخرى من {{inviter}} — سجّل الدخول لرؤية جميع العناصر المشاركة معك.\n\n— OxiCloud\n\nأنت تتلقى هذه الرسالة لأن لديك حسابًا في OxiCloud وتفضيل إشعارات المشاركة مُفعّل. يمكنك تعطيله من ملفك الشخصي (راسلني عندما يشاركني شخص ما)."
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "اسم المستخدم محدد ولا يمكن تغييره (عملاء DAV/NextCloud يعتمدون عليه).",
"given_name": "الاسم الأول",
"family_name": "اسم العائلة",
"notify_on_share": "أرسل لي بريدًا إلكترونيًا عندما يشاركني شخص ما",
"notify_on_share_hint": "عند إلغاء التحديد، ستظل المشاركات تظهر في حسابك — لن تتلقى فقط بريدًا إلكترونيًا بشأنها.",
"save_profile": "حفظ التغييرات",
"profile_saved": "تم تحديث الملف الشخصي",
"profile_no_changes": "لا توجد تغييرات لحفظها.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} hat einen {{kind}} mit Ihnen auf OxiCloud geteilt",
"body": "{{inviter}} hat einen {{kind}} mit Ihnen auf OxiCloud geteilt.\n\nÖffnen Sie ihn, indem Sie auf den folgenden Link klicken:\n{{link}}\n\nDer Link kann nur einmal verwendet werden und läuft in {{ttl_hours}} Stunden ab.\nFalls Sie diese Einladung nicht erwartet haben, können Sie diese Nachricht ignorieren.\n\n— OxiCloud"
"body": "{{inviter_full}} hat einen {{kind}} mit Ihnen auf OxiCloud geteilt.\n\nÖffnen Sie ihn, indem Sie auf den folgenden Link klicken:\n{{link}}\n\nDer Link kann nur einmal verwendet werden und läuft in {{ttl_hours}} Stunden ab.\nFalls Sie diese Einladung nicht erwartet haben, können Sie diese Nachricht ignorieren.\n\n— OxiCloud"
},
"login": {
"subject": "Anmeldung bei OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "Ordner",
"english_fallback_divider": "--- Englische Version unten ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} hat einen {{kind}} mit Ihnen auf OxiCloud geteilt",
"body": "{{inviter_full}} hat einen {{kind}} mit Ihnen auf OxiCloud geteilt.\n\nÖffnen Sie OxiCloud, um Ihre neue Freigabe zu sehen:\n{{login_link}}\n\nMöglicherweise gibt es weitere neue Freigaben von {{inviter}} — melden Sie sich an, um alle Ihre freigegebenen Elemente zu sehen.\n\n— OxiCloud\n\nSie erhalten diese Nachricht, weil Sie ein OxiCloud-Konto haben und die Benachrichtigung über Freigaben aktiviert ist. Sie können sie in Ihrem Profil deaktivieren (Per E-Mail benachrichtigen, wenn jemand mit mir teilt)."
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "Benutzername ist gesetzt und kann nicht geändert werden (DAV/NextCloud-Clients hängen davon ab).",
"given_name": "Vorname",
"family_name": "Nachname",
"notify_on_share": "Mich per E-Mail benachrichtigen, wenn jemand mit mir teilt",
"notify_on_share_hint": "Wenn deaktiviert, werden Freigaben weiterhin in deinem Konto angezeigt — du erhältst nur keine E-Mail dazu.",
"save_profile": "Änderungen speichern",
"profile_saved": "Profil aktualisiert",
"profile_no_changes": "Keine Änderungen zu speichern.",
+18
View File
@@ -30,12 +30,28 @@
"kind_folder": "folder",
"english_fallback_divider": "--- English version below ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} shared a {{kind}} with you on OxiCloud",
"body": "{{inviter}} shared a {{kind}} with you on OxiCloud.\n\nOpen OxiCloud to see your new share:\n{{login_link}}\n\nYou may have additional new shares from {{inviter}} — sign in to see all your shared items.\n\n— OxiCloud\n\nYou're receiving this message because you have an OxiCloud account and your share-notification preference is on. You can turn it off in your profile (Email me when someone shares with me)."
}
}
},
"app": {
"title": "OxiCloud",
"description": "Minimalist cloud storage system"
},
"myshares": {
"resendInvitation": "Resend invitation email",
"notifyByEmail": "Notify by email",
"notifyGroupMembers": "Notify group members",
"notifyRateLimited": "Too many notifications for this recipient — try again later.",
"notifyFailed": "Could not send notification.",
"removeAccess": "Remove access",
"copyLink": "Copy link",
"deleteLink": "Delete link"
},
"nav": {
"files": "Files",
"shared": "My shares",
@@ -759,6 +775,8 @@
"username_already_claimed": "Username is set and can't be changed (DAV/NextCloud clients depend on it).",
"given_name": "First name",
"family_name": "Last name",
"notify_on_share": "Email me when someone shares with me",
"notify_on_share_hint": "When unchecked, shares still appear in your account — you just won't get an email about them.",
"save_profile": "Save changes",
"profile_saved": "Profile updated",
"profile_no_changes": "No changes to save.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} ha compartido un {{kind}} contigo en OxiCloud",
"body": "{{inviter}} ha compartido un {{kind}} contigo en OxiCloud.\n\nÁbrelo haciendo clic en el enlace de abajo:\n{{link}}\n\nEl enlace es de un solo uso y expira en {{ttl_hours}} horas.\nSi no esperabas esta invitación, puedes ignorar este mensaje.\n\n— OxiCloud"
"body": "{{inviter_full}} ha compartido un {{kind}} contigo en OxiCloud.\n\nÁbrelo haciendo clic en el enlace de abajo:\n{{link}}\n\nEl enlace es de un solo uso y expira en {{ttl_hours}} horas.\nSi no esperabas esta invitación, puedes ignorar este mensaje.\n\n— OxiCloud"
},
"login": {
"subject": "Inicia sesión en OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "carpeta",
"english_fallback_divider": "--- Versión en inglés a continuación ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} ha compartido un {{kind}} contigo en OxiCloud",
"body": "{{inviter_full}} ha compartido un {{kind}} contigo en OxiCloud.\n\nAbre OxiCloud para ver tu nuevo recurso compartido:\n{{login_link}}\n\nPuede que tengas más recursos compartidos nuevos de {{inviter}} — inicia sesión para ver todos tus elementos compartidos.\n\n— OxiCloud\n\nRecibes este mensaje porque tienes una cuenta de OxiCloud y la preferencia de notificación de recursos compartidos está activada. Puedes desactivarla en tu perfil (Enviarme un correo cuando alguien comparta conmigo)."
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "Nombre de usuario fijado y no modificable (los clientes DAV/NextCloud dependen de él).",
"given_name": "Nombre",
"family_name": "Apellidos",
"notify_on_share": "Enviarme un correo cuando alguien comparta conmigo",
"notify_on_share_hint": "Cuando esté desmarcado, los recursos compartidos seguirán apareciendo en tu cuenta — simplemente no recibirás un correo sobre ellos.",
"save_profile": "Guardar cambios",
"profile_saved": "Perfil actualizado",
"profile_no_changes": "Sin cambios que guardar.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} یک {{kind}} را با شما در OxiCloud به اشتراک گذاشت",
"body": "{{inviter}} یک {{kind}} را با شما در OxiCloud به اشتراک گذاشت.\n\nبا کلیک روی پیوند زیر آن را باز کنید:\n{{link}}\n\nپیوند یک‌بار مصرف است و در {{ttl_hours}} ساعت منقضی می‌شود.\nاگر منتظر این دعوت نبودید، می‌توانید این پیام را نادیده بگیرید.\n\n— OxiCloud"
"body": "{{inviter_full}} یک {{kind}} را با شما در OxiCloud به اشتراک گذاشت.\n\nبا کلیک روی پیوند زیر آن را باز کنید:\n{{link}}\n\nپیوند یک‌بار مصرف است و در {{ttl_hours}} ساعت منقضی می‌شود.\nاگر منتظر این دعوت نبودید، می‌توانید این پیام را نادیده بگیرید.\n\n— OxiCloud"
},
"login": {
"subject": "ورود به OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "پوشه",
"english_fallback_divider": "--- نسخهٔ انگلیسی در پایین ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} یک {{kind}} را با شما در OxiCloud به اشتراک گذاشت",
"body": "{{inviter_full}} یک {{kind}} را با شما در OxiCloud به اشتراک گذاشت.\n\nبرای دیدن اشتراک‌گذاری جدید خود، OxiCloud را باز کنید:\n{{login_link}}\n\nممکن است اشتراک‌گذاری‌های جدید دیگری از {{inviter}} داشته باشید — وارد شوید تا همه موارد به اشتراک گذاشته‌شده با خود را ببینید.\n\n— OxiCloud\n\nشما این پیام را دریافت می‌کنید زیرا حساب OxiCloud دارید و گزینه اعلان اشتراک‌گذاری شما روشن است. می‌توانید آن را در پروفایل خود خاموش کنید (وقتی کسی با من چیزی به اشتراک می‌گذارد، به من ایمیل بزن)."
}
}
},
"app": {
@@ -725,6 +731,8 @@
"username_already_claimed": "نام کاربری تنظیم شده و قابل تغییر نیست (کلاینت‌های DAV/NextCloud به آن وابسته‌اند).",
"given_name": "نام",
"family_name": "نام خانوادگی",
"notify_on_share": "وقتی کسی با من چیزی به اشتراک می‌گذارد، به من ایمیل بزن",
"notify_on_share_hint": "وقتی تیک‌خورده نباشد، اشتراک‌گذاری‌ها همچنان در حساب شما نمایش داده می‌شوند — فقط ایمیلی درباره آنها دریافت نخواهید کرد.",
"save_profile": "ذخیره تغییرات",
"profile_saved": "نمایه به‌روز شد",
"profile_no_changes": "تغییری برای ذخیره وجود ندارد.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} a partagé un {{kind}} avec vous sur OxiCloud",
"body": "{{inviter}} a partagé un {{kind}} avec vous sur OxiCloud.\n\nOuvrez-le en cliquant sur le lien ci-dessous :\n{{link}}\n\nLe lien est à usage unique et expire dans {{ttl_hours}} heures.\nSi vous n'attendiez pas cette invitation, vous pouvez ignorer ce message.\n\n— OxiCloud"
"body": "{{inviter_full}} a partagé un {{kind}} avec vous sur OxiCloud.\n\nOuvrez-le en cliquant sur le lien ci-dessous :\n{{link}}\n\nLe lien est à usage unique et expire dans {{ttl_hours}} heures.\nSi vous n'attendiez pas cette invitation, vous pouvez ignorer ce message.\n\n— OxiCloud"
},
"login": {
"subject": "Connexion à OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "dossier",
"english_fallback_divider": "--- Version anglaise ci-dessous ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} a partagé un {{kind}} avec vous sur OxiCloud",
"body": "{{inviter_full}} a partagé un {{kind}} avec vous sur OxiCloud.\n\nOuvrez OxiCloud pour voir votre nouveau partage :\n{{login_link}}\n\nVous avez peut-être d'autres nouveaux partages de {{inviter}} — connectez-vous pour voir tous vos éléments partagés.\n\n— OxiCloud\n\nVous recevez ce message parce que vous avez un compte OxiCloud et que la préférence de notification de partage est activée. Vous pouvez la désactiver dans votre profil (M'avertir par e-mail quand quelqu'un partage avec moi)."
}
}
},
"app": {
@@ -759,6 +765,8 @@
"username_already_claimed": "Nom d'utilisateur fixé et non modifiable (les clients DAV/NextCloud en dépendent).",
"given_name": "Prénom",
"family_name": "Nom",
"notify_on_share": "M'avertir par e-mail quand quelqu'un partage avec moi",
"notify_on_share_hint": "Lorsque décoché, les partages apparaissent toujours dans votre compte — vous ne recevrez simplement pas d'e-mail à leur sujet.",
"save_profile": "Enregistrer",
"profile_saved": "Profil mis à jour",
"profile_no_changes": "Aucun changement à enregistrer.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} ने OxiCloud पर आपके साथ एक {{kind}} साझा किया",
"body": "{{inviter}} ने OxiCloud पर आपके साथ एक {{kind}} साझा किया है।\n\nइसे नीचे दिए गए लिंक पर क्लिक करके खोलें:\n{{link}}\n\nलिंक केवल एक बार काम करता है और {{ttl_hours}} घंटों में समाप्त हो जाता है।\nयदि आप इस आमंत्रण की अपेक्षा नहीं कर रहे थे, तो आप इस संदेश को अनदेखा कर सकते हैं।\n\n— OxiCloud"
"body": "{{inviter_full}} ने OxiCloud पर आपके साथ एक {{kind}} साझा किया है।\n\nइसे नीचे दिए गए लिंक पर क्लिक करके खोलें:\n{{link}}\n\nलिंक केवल एक बार काम करता है और {{ttl_hours}} घंटों में समाप्त हो जाता है।\nयदि आप इस आमंत्रण की अपेक्षा नहीं कर रहे थे, तो आप इस संदेश को अनदेखा कर सकते हैं।\n\n— OxiCloud"
},
"login": {
"subject": "OxiCloud में साइन इन करें",
@@ -30,6 +30,12 @@
"kind_folder": "फ़ोल्डर",
"english_fallback_divider": "--- अंग्रेज़ी संस्करण नीचे ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} ने OxiCloud पर आपके साथ एक {{kind}} साझा किया",
"body": "{{inviter_full}} ने OxiCloud पर आपके साथ एक {{kind}} साझा किया है।\n\nअपना नया साझाकरण देखने के लिए OxiCloud खोलें:\n{{login_link}}\n\nहो सकता है आपके पास {{inviter}} से और भी नए साझाकरण हों — साइन इन करें और अपने सभी साझा किए गए आइटम देखें।\n\n— OxiCloud\n\nआपको यह संदेश इसलिए मिल रहा है क्योंकि आपका OxiCloud खाता है और साझाकरण-सूचना प्राथमिकता चालू है। आप इसे अपनी प्रोफ़ाइल में बंद कर सकते हैं (जब कोई मेरे साथ साझा करे तो मुझे ईमेल भेजें)।"
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "उपयोगकर्ता नाम सेट है और बदला नहीं जा सकता (DAV/NextCloud क्लाइंट इस पर निर्भर करते हैं)।",
"given_name": "प्रथम नाम",
"family_name": "अंतिम नाम",
"notify_on_share": "जब कोई मेरे साथ साझा करे तो मुझे ईमेल भेजें",
"notify_on_share_hint": "जब अनचेक किया जाए, तो साझाकरण आपके खाते में दिखाई देते रहेंगे — आपको बस उनके बारे में ईमेल नहीं मिलेगा।",
"save_profile": "परिवर्तन सहेजें",
"profile_saved": "प्रोफ़ाइल अद्यतन की गई",
"profile_no_changes": "सहेजने के लिए कोई परिवर्तन नहीं।",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} ha condiviso un {{kind}} con te su OxiCloud",
"body": "{{inviter}} ha condiviso un {{kind}} con te su OxiCloud.\n\nAprilo facendo clic sul link sottostante:\n{{link}}\n\nIl link è monouso e scade tra {{ttl_hours}} ore.\nSe non ti aspettavi questo invito, puoi ignorare questo messaggio.\n\n— OxiCloud"
"body": "{{inviter_full}} ha condiviso un {{kind}} con te su OxiCloud.\n\nAprilo facendo clic sul link sottostante:\n{{link}}\n\nIl link è monouso e scade tra {{ttl_hours}} ore.\nSe non ti aspettavi questo invito, puoi ignorare questo messaggio.\n\n— OxiCloud"
},
"login": {
"subject": "Accedi a OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "cartella",
"english_fallback_divider": "--- Versione inglese qui sotto ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} ha condiviso un {{kind}} con te su OxiCloud",
"body": "{{inviter_full}} ha condiviso un {{kind}} con te su OxiCloud.\n\nApri OxiCloud per vedere la tua nuova condivisione:\n{{login_link}}\n\nPotresti avere altre nuove condivisioni da {{inviter}} — accedi per vedere tutti gli elementi condivisi con te.\n\n— OxiCloud\n\nRicevi questo messaggio perché hai un account OxiCloud e la preferenza di notifica delle condivisioni è attiva. Puoi disattivarla dal tuo profilo (Avvisami via email quando qualcuno condivide con me)."
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "Nome utente impostato e non modificabile (i client DAV/NextCloud dipendono da esso).",
"given_name": "Nome",
"family_name": "Cognome",
"notify_on_share": "Avvisami via email quando qualcuno condivide con me",
"notify_on_share_hint": "Se deselezionato, le condivisioni continueranno ad apparire nel tuo account — semplicemente non riceverai un'email a riguardo.",
"save_profile": "Salva modifiche",
"profile_saved": "Profilo aggiornato",
"profile_no_changes": "Nessuna modifica da salvare.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} さんが OxiCloud で {{kind}} をあなたと共有しました",
"body": "{{inviter}} さんが OxiCloud で {{kind}} をあなたと共有しました。\n\n以下のリンクをクリックして開いてください:\n{{link}}\n\nリンクは一度のみ有効で、{{ttl_hours}} 時間で期限切れになります。\nこの招待に心当たりがない場合は、このメッセージを無視していただいて結構です。\n\n— OxiCloud"
"body": "{{inviter_full}} さんが OxiCloud で {{kind}} をあなたと共有しました。\n\n以下のリンクをクリックして開いてください:\n{{link}}\n\nリンクは一度のみ有効で、{{ttl_hours}} 時間で期限切れになります。\nこの招待に心当たりがない場合は、このメッセージを無視していただいて結構です。\n\n— OxiCloud"
},
"login": {
"subject": "OxiCloud にサインイン",
@@ -30,6 +30,12 @@
"kind_folder": "フォルダー",
"english_fallback_divider": "--- 以下は英語版 ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} さんが OxiCloud で {{kind}} をあなたと共有しました",
"body": "{{inviter_full}} さんが OxiCloud で {{kind}} をあなたと共有しました。\n\n新しい共有を確認するには OxiCloud を開いてください:\n{{login_link}}\n\n{{inviter}} さんから他にも新しい共有があるかもしれません — サインインしてあなたと共有されたすべての項目を確認してください。\n\n— OxiCloud\n\nOxiCloud のアカウントをお持ちで、共有通知の設定が有効になっているため、このメッセージが届いています。プロフィールでオフにできます(誰かが共有したときにメールで通知する)。"
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "ユーザー名は設定済みで変更できません(DAV/NextCloudクライアントが依存します)。",
"given_name": "名",
"family_name": "姓",
"notify_on_share": "誰かが共有したときにメールで通知する",
"notify_on_share_hint": "チェックを外しても、共有はアカウントに表示されますが、メールでの通知は届きません。",
"save_profile": "変更を保存",
"profile_saved": "プロフィールを更新しました",
"profile_no_changes": "保存する変更はありません。",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}}님이 OxiCloud에서 {{kind}}을(를) 공유했습니다",
"body": "{{inviter}}님이 OxiCloud에서 {{kind}}을(를) 공유했습니다.\n\n아래 링크를 클릭하여 여세요:\n{{link}}\n\n링크는 한 번만 사용 가능하며 {{ttl_hours}}시간 후에 만료됩니다.\n이 초대를 예상하지 못했다면 이 메시지를 무시하셔도 됩니다.\n\n— OxiCloud"
"body": "{{inviter_full}}님이 OxiCloud에서 {{kind}}을(를) 공유했습니다.\n\n아래 링크를 클릭하여 여세요:\n{{link}}\n\n링크는 한 번만 사용 가능하며 {{ttl_hours}}시간 후에 만료됩니다.\n이 초대를 예상하지 못했다면 이 메시지를 무시하셔도 됩니다.\n\n— OxiCloud"
},
"login": {
"subject": "OxiCloud 로그인",
@@ -30,6 +30,12 @@
"kind_folder": "폴더",
"english_fallback_divider": "--- 영어 버전은 아래 ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}}님이 OxiCloud에서 {{kind}}을(를) 공유했습니다",
"body": "{{inviter_full}}님이 OxiCloud에서 {{kind}}을(를) 공유했습니다.\n\n새 공유 항목을 확인하려면 OxiCloud를 여세요:\n{{login_link}}\n\n{{inviter}}님이 추가로 공유한 항목이 있을 수 있습니다 — 로그인하여 공유받은 모든 항목을 확인하세요.\n\n— OxiCloud\n\nOxiCloud 계정이 있고 공유 알림 기본 설정이 켜져 있어 이 메시지를 받았습니다. 프로필에서 끌 수 있습니다(다른 사람이 나에게 공유할 때 이메일로 알림 받기)."
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "사용자 이름이 설정되어 있어 변경할 수 없습니다(DAV/NextCloud 클라이언트가 이에 의존합니다).",
"given_name": "이름",
"family_name": "성",
"notify_on_share": "다른 사람이 나에게 공유할 때 이메일로 알림 받기",
"notify_on_share_hint": "선택을 해제해도 공유 항목은 계정에 계속 표시되지만, 이메일 알림은 받지 않습니다.",
"save_profile": "변경 사항 저장",
"profile_saved": "프로필이 업데이트되었습니다",
"profile_no_changes": "저장할 변경 사항이 없습니다.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} heeft een {{kind}} met je gedeeld op OxiCloud",
"body": "{{inviter}} heeft een {{kind}} met je gedeeld op OxiCloud.\n\nOpen het door op de onderstaande link te klikken:\n{{link}}\n\nDe link werkt eenmalig en verloopt over {{ttl_hours}} uur.\nAls je deze uitnodiging niet verwacht, kun je dit bericht negeren.\n\n— OxiCloud"
"body": "{{inviter_full}} heeft een {{kind}} met je gedeeld op OxiCloud.\n\nOpen het door op de onderstaande link te klikken:\n{{link}}\n\nDe link werkt eenmalig en verloopt over {{ttl_hours}} uur.\nAls je deze uitnodiging niet verwacht, kun je dit bericht negeren.\n\n— OxiCloud"
},
"login": {
"subject": "Aanmelden bij OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "map",
"english_fallback_divider": "--- Engelse versie hieronder ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} heeft een {{kind}} met je gedeeld op OxiCloud",
"body": "{{inviter_full}} heeft een {{kind}} met je gedeeld op OxiCloud.\n\nOpen OxiCloud om je nieuwe gedeelde item te bekijken:\n{{login_link}}\n\nMisschien heb je nog meer nieuwe gedeelde items van {{inviter}} — meld je aan om al je gedeelde items te zien.\n\n— OxiCloud\n\nJe ontvangt dit bericht omdat je een OxiCloud-account hebt en je voorkeur voor deelmeldingen aanstaat. Je kunt het uitzetten in je profiel (Stuur me een e-mail wanneer iemand iets met mij deelt)."
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "Gebruikersnaam ingesteld en niet wijzigbaar (DAV/NextCloud-clients zijn ervan afhankelijk).",
"given_name": "Voornaam",
"family_name": "Achternaam",
"notify_on_share": "Stuur me een e-mail wanneer iemand iets met mij deelt",
"notify_on_share_hint": "Wanneer uitgevinkt, verschijnen gedeelde items nog steeds in je account — je krijgt er alleen geen e-mail over.",
"save_profile": "Wijzigingen opslaan",
"profile_saved": "Profiel bijgewerkt",
"profile_no_changes": "Geen wijzigingen om op te slaan.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} udostępnił Ci {{kind}} w OxiCloud",
"body": "{{inviter}} udostępnił Ci {{kind}} w OxiCloud.\n\nOtwórz, klikając poniższy link:\n{{link}}\n\nLink działa raz i wygasa za {{ttl_hours}} godzin.\nJeśli nie spodziewałeś się tego zaproszenia, możesz zignorować tę wiadomość.\n\n— OxiCloud"
"body": "{{inviter_full}} udostępnił Ci {{kind}} w OxiCloud.\n\nOtwórz, klikając poniższy link:\n{{link}}\n\nLink działa raz i wygasa za {{ttl_hours}} godzin.\nJeśli nie spodziewałeś się tego zaproszenia, możesz zignorować tę wiadomość.\n\n— OxiCloud"
},
"login": {
"subject": "Zaloguj się do OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "folder",
"english_fallback_divider": "--- Wersja angielska poniżej ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} udostępnił Ci {{kind}} w OxiCloud",
"body": "{{inviter_full}} udostępnił Ci {{kind}} w OxiCloud.\n\nOtwórz OxiCloud, aby zobaczyć nowe udostępnienie:\n{{login_link}}\n\nMożesz mieć dodatkowe nowe udostępnienia od {{inviter}} — zaloguj się, aby zobaczyć wszystkie udostępnione Ci elementy.\n\n— OxiCloud\n\nOtrzymujesz tę wiadomość, ponieważ masz konto OxiCloud i preferencja powiadomień o udostępnieniach jest włączona. Możesz ją wyłączyć w swoim profilu (Wyślij mi e-mail, gdy ktoś coś mi udostępni)."
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "Nazwa użytkownika jest ustawiona i nie może być zmieniona (klienty DAV/NextCloud są od niej zależne).",
"given_name": "Imię",
"family_name": "Nazwisko",
"notify_on_share": "Wyślij mi e-mail, gdy ktoś coś mi udostępni",
"notify_on_share_hint": "Gdy odznaczone, udostępnienia nadal pojawiają się na Twoim koncie — po prostu nie otrzymasz o nich e-maila.",
"save_profile": "Zapisz zmiany",
"profile_saved": "Profil zaktualizowany",
"profile_no_changes": "Brak zmian do zapisania.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} partilhou um {{kind}} consigo no OxiCloud",
"body": "{{inviter}} partilhou um {{kind}} consigo no OxiCloud.\n\nAbra-o clicando no link abaixo:\n{{link}}\n\nO link é de uso único e expira em {{ttl_hours}} horas.\nSe não esperava este convite, pode ignorar esta mensagem.\n\n— OxiCloud"
"body": "{{inviter_full}} partilhou um {{kind}} consigo no OxiCloud.\n\nAbra-o clicando no link abaixo:\n{{link}}\n\nO link é de uso único e expira em {{ttl_hours}} horas.\nSe não esperava este convite, pode ignorar esta mensagem.\n\n— OxiCloud"
},
"login": {
"subject": "Iniciar sessão no OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "pasta",
"english_fallback_divider": "--- Versão em inglês abaixo ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} partilhou um {{kind}} consigo no OxiCloud",
"body": "{{inviter_full}} partilhou um {{kind}} consigo no OxiCloud.\n\nAbra o OxiCloud para ver a sua nova partilha:\n{{login_link}}\n\nPode ter mais partilhas novas de {{inviter}} — inicie sessão para ver todos os itens partilhados consigo.\n\n— OxiCloud\n\nRecebeu esta mensagem porque tem uma conta OxiCloud e a preferência de notificação de partilhas está ativada. Pode desativá-la no seu perfil (Avisar-me por e-mail quando alguém compartilhar comigo)."
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "Nome de usuário definido e não pode ser alterado (clientes DAV/NextCloud dependem dele).",
"given_name": "Nome",
"family_name": "Sobrenome",
"notify_on_share": "Avisar-me por e-mail quando alguém compartilhar comigo",
"notify_on_share_hint": "Quando desmarcado, os compartilhamentos continuarão aparecendo na sua conta — você apenas não receberá um e-mail sobre eles.",
"save_profile": "Salvar alterações",
"profile_saved": "Perfil atualizado",
"profile_no_changes": "Sem alterações para salvar.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} поделился(ась) с вами {{kind}} в OxiCloud",
"body": "{{inviter}} поделился(ась) с вами {{kind}} в OxiCloud.\n\nОткройте, нажав на ссылку ниже:\n{{link}}\n\nСсылка работает один раз и истекает через {{ttl_hours}} часов.\nЕсли вы не ожидали этого приглашения, можете спокойно проигнорировать это сообщение.\n\n— OxiCloud"
"body": "{{inviter_full}} поделился(ась) с вами {{kind}} в OxiCloud.\n\nОткройте, нажав на ссылку ниже:\n{{link}}\n\nСсылка работает один раз и истекает через {{ttl_hours}} часов.\nЕсли вы не ожидали этого приглашения, можете спокойно проигнорировать это сообщение.\n\n— OxiCloud"
},
"login": {
"subject": "Вход в OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "папку",
"english_fallback_divider": "--- Английская версия ниже ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} поделился(ась) с вами {{kind}} в OxiCloud",
"body": "{{inviter_full}} поделился(ась) с вами {{kind}} в OxiCloud.\n\nОткройте OxiCloud, чтобы увидеть новый общий ресурс:\n{{login_link}}\n\nВозможно, у вас есть и другие новые общие ресурсы от {{inviter}} — войдите, чтобы увидеть все элементы, которыми с вами поделились.\n\n— OxiCloud\n\nВы получаете это сообщение, потому что у вас есть учётная запись OxiCloud и предпочтение уведомлений об общих ресурсах включено. Вы можете отключить его в своём профиле (Уведомлять меня по электронной почте, когда кто-то делится со мной)."
}
}
},
"app": {
@@ -742,6 +748,8 @@
"username_already_claimed": "Имя пользователя установлено и не может быть изменено (клиенты DAV/NextCloud зависят от него).",
"given_name": "Имя",
"family_name": "Фамилия",
"notify_on_share": "Уведомлять меня по электронной почте, когда кто-то делится со мной",
"notify_on_share_hint": "Если флажок снят, общие ресурсы по-прежнему будут отображаться в вашей учётной записи — вы просто не будете получать о них письма.",
"save_profile": "Сохранить изменения",
"profile_saved": "Профиль обновлён",
"profile_no_changes": "Нет изменений для сохранения.",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} 在 OxiCloud 上與您分享了一個{{kind}}",
"body": "{{inviter}} 在 OxiCloud 上與您分享了一個{{kind}}。\n\n點擊下方連結開啟:\n{{link}}\n\n該連結僅可使用一次,並將在 {{ttl_hours}} 小時後過期。\n如果您未預期收到此邀請,可以忽略此訊息。\n\n— OxiCloud"
"body": "{{inviter_full}} 在 OxiCloud 上與您分享了一個{{kind}}。\n\n點擊下方連結開啟:\n{{link}}\n\n該連結僅可使用一次,並將在 {{ttl_hours}} 小時後過期。\n如果您未預期收到此邀請,可以忽略此訊息。\n\n— OxiCloud"
},
"login": {
"subject": "登入 OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "資料夾",
"english_fallback_divider": "--- 以下為英文版本 ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} 在 OxiCloud 上與您分享了一個{{kind}}",
"body": "{{inviter_full}} 在 OxiCloud 上與您分享了一個{{kind}}。\n\n開啟 OxiCloud 檢視您的新分享:\n{{login_link}}\n\n您可能還有來自 {{inviter}} 的其他新分享 — 登入以檢視所有與您分享的項目。\n\n— OxiCloud\n\n您收到此訊息是因為您擁有 OxiCloud 帳戶且分享通知偏好已開啟。您可以在個人資料中關閉它(當有人與我分享時透過電子郵件通知我)。"
}
}
},
"app": {
@@ -725,6 +731,8 @@
"username_already_claimed": "使用者名稱已設定,不可更改(DAV/NextCloud 用戶端依賴它)。",
"given_name": "名",
"family_name": "姓",
"notify_on_share": "當有人與我分享時透過電子郵件通知我",
"notify_on_share_hint": "取消勾選後,分享項目仍會顯示在您的帳戶中 — 只是不會收到相關郵件通知。",
"save_profile": "儲存變更",
"profile_saved": "個人資料已更新",
"profile_no_changes": "沒有變更可儲存。",
+9 -1
View File
@@ -20,7 +20,7 @@
"email": {
"invitation": {
"subject": "{{inviter}} 在 OxiCloud 上与您共享了一个{{kind}}",
"body": "{{inviter}} 在 OxiCloud 上与您共享了一个{{kind}}。\n\n点击下方链接打开:\n{{link}}\n\n该链接仅可使用一次,并将在 {{ttl_hours}} 小时后过期。\n如果您未预期收到此邀请,可以忽略此消息。\n\n— OxiCloud"
"body": "{{inviter_full}} 在 OxiCloud 上与您共享了一个{{kind}}。\n\n点击下方链接打开:\n{{link}}\n\n该链接仅可使用一次,并将在 {{ttl_hours}} 小时后过期。\n如果您未预期收到此邀请,可以忽略此消息。\n\n— OxiCloud"
},
"login": {
"subject": "登录 OxiCloud",
@@ -30,6 +30,12 @@
"kind_folder": "文件夹",
"english_fallback_divider": "--- 以下为英文版本 ---"
}
},
"notification": {
"share": {
"subject": "{{inviter}} 在 OxiCloud 上与您共享了一个{{kind}}",
"body": "{{inviter_full}} 在 OxiCloud 上与您共享了一个{{kind}}。\n\n打开 OxiCloud 查看您的新共享:\n{{login_link}}\n\n您可能还有来自 {{inviter}} 的其他新共享 — 登录以查看所有共享给您的项目。\n\n— OxiCloud\n\n您收到此消息是因为您拥有 OxiCloud 账户且共享通知偏好已开启。您可以在个人资料中关闭它(当有人与我共享时通过电子邮件通知我)。"
}
}
},
"app": {
@@ -725,6 +731,8 @@
"username_already_claimed": "用户名已设置,不可更改(DAV/NextCloud 客户端依赖它)。",
"given_name": "名",
"family_name": "姓",
"notify_on_share": "当有人与我共享时通过电子邮件通知我",
"notify_on_share_hint": "取消勾选后,共享项目仍会显示在您的账户中 — 只是不会收到相关邮件通知。",
"save_profile": "保存更改",
"profile_saved": "个人资料已更新",
"profile_no_changes": "无更改可保存。",
+7
View File
@@ -139,6 +139,13 @@
<label for="profile-edit-family-name" data-i18n="profile.family_name">Last name</label>
<input type="text" id="profile-edit-family-name" maxlength="128" autocomplete="family-name">
</div>
<div class="form-group form-group--checkbox">
<label for="profile-edit-notify-on-share" class="checkbox-label">
<input type="checkbox" id="profile-edit-notify-on-share">
<span data-i18n="profile.notify_on_share">Email me when someone shares with me</span>
</label>
<small data-i18n="profile.notify_on_share_hint">When unchecked, shares still appear in your account — you just won't get an email about them.</small>
</div>
<button type="submit" class="btn btn-primary" id="profile-edit-submit">
<i class="fas fa-save"></i> <span data-i18n="profile.save_profile">Save changes</span>
</button>