feat(frontend): analytics panel and online statuses speak russian and english with real plurals

This commit is contained in:
hh committed 2026-10-19 05:37:44 +00:00
1 parent 543e6eaa50
commit cff493f5dd
9 files changed
+286 -190

No files matched your search

@@ -5,6 +5,7 @@
import Spinner from "#lib/components/ui/Spinner.svelte";
import { formatLongDay, formatTime } from "#lib/format/datetime.js";
import { formatSpan } from "#lib/format/presence.js";
import { t } from "#lib/i18n/locale.svelte.js";
interface Props {
days: number;
@@ -16,18 +17,6 @@
const { days, first, pageSize, peerId, tz }: Props = $props();
const TEXT = {
failed: "Не удалось загрузить сессии.",
more: "Показать ещё",
none: "За этот период в сети не был(а).",
now: "в сети",
retry: "Повторить",
since: (time: string) => `с ${time}`,
span: (from: string, to: string) => (from === to ? from : `${from}–${to}`),
today: "Сегодня",
yesterday: "Вчера",
};
const MS_IN_DAY = 86_400_000;
let extra = $state<PresenceSession[]>([]);
@@ -54,18 +43,22 @@
function dayLabel(date: Date): string {
const today = new Date();
if (date.toDateString() === today.toDateString()) {
return TEXT.today;
return t("presence.today");
}
if (
date.toDateString() ===
new Date(today.getTime() - MS_IN_DAY).toDateString()
) {
return TEXT.yesterday;
return t("presence.yesterday");
}
const text = formatLongDay(date);
return text.charAt(0).toUpperCase() + text.slice(1);
}
function span(from: string, to: string): string {
return from === to ? from : `${from}–${to}`;
}
async function loadMore() {
if (loading) {
return;
@@ -92,7 +85,7 @@
</script>
{#if sessions.length === 0}
<p class="state">{TEXT.none}</p>
<p class="state">{t("presence.noSessions")}</p>
{:else}
{#each groups as group (group.label)}
<h4 class="day">{group.label}</h4>
@@ -101,14 +94,13 @@
<li>
<span>
{session.ongoing
? TEXT.since(formatTime(session.started))
: TEXT.span(
formatTime(session.started),
formatTime(session.ended)
)}
? t("presence.since", { time: formatTime(session.started) })
: span(formatTime(session.started), formatTime(session.ended))}
</span>
<span class={["length", { online: session.ongoing }]}>
{session.ongoing ? TEXT.now : formatSpan(session.seconds)}
{session.ongoing
? t("presence.online")
: formatSpan(session.seconds)}
</span>
</li>
{/each}
@@ -118,11 +110,13 @@
<div class="state"><Spinner /></div>
{:else if failed}
<div class="state">
<p>{TEXT.failed}</p>
<Button onclick={loadMore} smaller variant="text">{TEXT.retry}</Button>
<p>{t("presence.sessionsFailed")}</p>
<Button onclick={loadMore} smaller variant="text">{t("ui.retry")}</Button>
</div>
{:else if !exhausted}
<button class="more" onclick={loadMore} type="button">{TEXT.more}</button>
<button class="more" onclick={loadMore} type="button">
{t("ui.showMore")}
</button>
{/if}
{/if}