feat(frontend): storage, trash, policy, stories, shares, jobs, bots, media, login and notes speak russian and english

This commit is contained in:
hh committed 2026-10-19 03:53:08 +00:00
1 parent e5a5edcaae
commit 4a5c92f0e9
51 files changed
+1504 -588

No files matched your search

@@ -16,7 +16,8 @@
import Spinner from "#lib/components/ui/Spinner.svelte";
import { formatFull } from "#lib/format/datetime.js";
import { formatBytes } from "#lib/format/media.js";
import { formatDownloads, formatExpiry, plural } from "#lib/format/shares.js";
import { formatDownloads, formatExpiry } from "#lib/format/shares.js";
import { t } from "#lib/i18n/locale.svelte.js";
import { accounts } from "#lib/stores/accounts.svelte.js";
import { shareUi } from "#lib/stores/shares.svelte.js";
import { toasts } from "#lib/stores/toasts.svelte.js";
@@ -25,13 +26,6 @@
type Filter = "active" | "all";
const stateLabels: Record<string, string> = {
active: "Открыт",
exhausted: "Лимит исчерпан",
expired: "Истёк",
revoked: "Отозван",
};
let items = $state<FileShare[]>([]);
let loading = $state(false);
let filter = $state<Filter>("active");
@@ -63,7 +57,7 @@
}
} catch {
if (current === token) {
toasts.error("Не удалось загрузить список ссылок");
toasts.error(t("shares.listFailed"));
}
} finally {
if (current === token) {
@@ -91,24 +85,24 @@
async function copy(share: FileShare) {
await navigator.clipboard.writeText(shareUrl(share));
toasts.success("Ссылка скопирована");
toasts.success(t("shares.copied"));
}
async function revoke(share: FileShare) {
try {
replace(await revokeShare(share.id));
toasts.success("Доступ отозван");
toasts.success(t("shares.revokedToast"));
} catch {
toasts.error("Не удалось отозвать доступ");
toasts.error(t("shares.revokeFailed"));
}
}
async function reissue(share: FileShare) {
try {
replace(await reissueShare(share.id));
toasts.success("Выдана новая ссылка");
toasts.success(t("shares.reissued"));
} catch {
toasts.error("Не удалось перевыпустить ссылку");
toasts.error(t("shares.reissueFailed"));
}
}
@@ -117,7 +111,7 @@
await deleteShare(share.id);
items = items.filter((item) => item.id !== share.id);
} catch {
toasts.error("Не удалось удалить запись");
toasts.error(t("shares.forgetFailed"));
}
}
@@ -142,7 +136,7 @@
type="button"
class:selected={filter === "active"}
>
Открытые{activeCount ? ` · ${activeCount}` : ""}
{t("shares.tab.active")}{activeCount ? ` · ${activeCount}` : ""}
</button>
<button
class="tab"
@@ -152,11 +146,11 @@
type="button"
class:selected={filter === "all"}
>
Все
{t("shares.tab.all")}
</button>
</div>
<button
aria-label="Обновить"
aria-label={t("ui.refresh")}
class="reload"
onclick={() => load()}
type="button"
@@ -169,8 +163,8 @@
<div class="center"><Spinner /></div>
{:else if visible.length === 0}
<EmptyState
description="Откройте доступ к файлу из меню медиа"
title="Ничего не открыто"
description={t("shares.empty.hint")}
title={t("shares.empty.title")}
/>
{:else}
<ul class="list">
@@ -198,14 +192,14 @@
<span class="text">
<span class="name">{share.file_name}</span>
<span class="sub">
{stateLabels[state]}
{t(`shares.state.${state}`)}
· {formatDownloads(share)}
{#if share.file_size}
· {formatBytes(share.file_size)}
{/if}
</span>
<span class="sub">
{share.title ?? "Без чата"}
{share.title ?? t("shares.noChat")}
· {formatExpiry(share)}
</span>
</span>
@@ -216,11 +210,11 @@
{/snippet}
{#snippet menu()}
<ContextMenuItem icon="copy" onselect={() => copy(share)}>
Копировать ссылку
{t("shares.copy")}
</ContextMenuItem>
{#if share.message_id !== null || share.peer_id !== null}
<ContextMenuItem icon="reply" onselect={() => jump(share)}>
Перейти к сообщению
{t("ui.jumpToMessage")}
</ContextMenuItem>
{/if}
{#if state === "active"}
@@ -228,14 +222,14 @@
icon="link-broken"
onselect={() => revoke(share)}
>
Отозвать
{t("shares.revoke")}
</ContextMenuItem>
{:else}
<ContextMenuItem icon="replace" onselect={() => reissue(share)}>
Выдать новую ссылку
{t("shares.reissue")}
</ContextMenuItem>
<ContextMenuItem icon="delete" onselect={() => forget(share)}>
Убрать из списка
{t("shares.forget")}
</ContextMenuItem>
{/if}
{/snippet}
@@ -246,13 +240,15 @@
<ShareLinkField url={shareUrl(share)} />
{#if share.last_download_at}
<p class="last">
Последнее скачивание: {formatFull(share.last_download_at)}
{t("shares.lastDownload", {
date: formatFull(share.last_download_at),
})}
</p>
{/if}
<div class="actions">
{#if share.message_id !== null || share.peer_id !== null}
<button class="pill" onclick={() => jump(share)} type="button">
<Icon name="reply" size="1rem" />К сообщению
<Icon name="reply" size="1rem" />{t("shares.toMessage")}
</button>
{/if}
{#if state === "active"}
@@ -261,7 +257,7 @@
onclick={() => revoke(share)}
type="button"
>
<Icon name="link-broken" size="1rem" />Отозвать
<Icon name="link-broken" size="1rem" />{t("shares.revoke")}
</button>
{:else}
<button
@@ -269,7 +265,7 @@
onclick={() => reissue(share)}
type="button"
>
<Icon name="replace" size="1rem" />Новая ссылка
<Icon name="replace" size="1rem" />{t("shares.newLink")}
</button>
{/if}
</div>
@@ -279,9 +275,7 @@
{/each}
</ul>
<p class="footnote">
{visible.length}
{plural(visible.length, "ссылка", "ссылки", "ссылок")}
· превью-краулеры не списывают скачивания
{t("shares.footnote", { count: visible.length })}
</p>
{/if}