feat(frontend): forwarded header, pinned bar, folder tabs, inline buttons, edit history and deleted accounts speak russian and english

This commit is contained in:
hh committed 2026-10-19 05:38:24 +00:00
1 parent 0cd34f3037
commit 25364f4523
8 files changed
+50 -18

No files matched your search

@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import ContextMenu from "#lib/components/ui/ContextMenu.svelte"; import ContextMenu from "#lib/components/ui/ContextMenu.svelte";
import ContextMenuItem from "#lib/components/ui/ContextMenuItem.svelte"; import ContextMenuItem from "#lib/components/ui/ContextMenuItem.svelte";
import { t } from "#lib/i18n/locale.svelte.js";
import { folders } from "#lib/stores/folders.svelte.js"; import { folders } from "#lib/stores/folders.svelte.js";
import { policyUi } from "#lib/stores/policy.svelte.js"; import { policyUi } from "#lib/stores/policy.svelte.js";
@@ -10,7 +11,7 @@
} }
const tabs = $derived<Tab[]>([ const tabs = $derived<Tab[]>([
{ id: null, title: "All Chats" }, { id: null, title: t("folders.all") },
...folders.list.map((folder) => ({ ...folders.list.map((folder) => ({
id: folder.folder_id, id: folder.folder_id,
title: folder.title, title: folder.title,
@@ -93,7 +94,7 @@
{/snippet} {/snippet}
{#snippet menu()} {#snippet menu()}
<ContextMenuItem icon="settings" onselect={() => editPolicy(tab)}> <ContextMenuItem icon="settings" onselect={() => editPolicy(tab)}>
Политика захвата {t("settings.policy")}
</ContextMenuItem> </ContextMenuItem>
{/snippet} {/snippet}
</ContextMenu> </ContextMenu>
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { ForwardView } from "#lib/api/types.js"; import type { ForwardView } from "#lib/api/types.js";
import { peerName } from "#lib/format/peer.js"; import { peerName } from "#lib/format/peer.js";
import { t } from "#lib/i18n/locale.svelte.js";
import { peers } from "#lib/stores/peers.svelte.js"; import { peers } from "#lib/stores/peers.svelte.js";
interface Props { interface Props {
@@ -14,20 +15,20 @@
); );
const name = $derived.by(() => { const name = $derived.by(() => {
if (forward.kind === "channel") { if (forward.kind === "channel") {
return forward.chat_title ?? "Channel"; return forward.chat_title ?? t("forward.channel");
} }
if (forward.kind === "hidden") { if (forward.kind === "hidden") {
return forward.from_name ?? "Hidden account"; return forward.from_name ?? t("forward.hidden");
} }
if (peer) { if (peer) {
return peerName(peer); return peerName(peer);
} }
return forward.from_name ?? "Unknown"; return forward.from_name ?? t("forward.unknown");
}); });
</script> </script>
<div class="ForwardHeader"> <div class="ForwardHeader">
<span class="label">Forwarded from</span> <span class="label">{t("forward.from")}</span>
<span class="name">{name}</span> <span class="name">{name}</span>
{#if forward.signature} {#if forward.signature}
<span class="signature">({forward.signature})</span> <span class="signature">({forward.signature})</span>
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { InlineButton } from "#lib/api/types.js"; import type { InlineButton } from "#lib/api/types.js";
import { safeHref } from "#lib/format/url.js"; import { safeHref } from "#lib/format/url.js";
import { t } from "#lib/i18n/locale.svelte.js";
interface Props { interface Props {
rows: InlineButton[][]; rows: InlineButton[][];
@@ -43,7 +44,7 @@
type="button" type="button"
> >
<span class="label" <span class="label"
>{copied === key ? "Скопировано" : button.text}</span >{copied === key ? t("inline.copied") : button.text}</span
> >
</button> </button>
{:else} {:else}
@@ -6,6 +6,7 @@
import Icon from "#lib/components/ui/Icon.svelte"; import Icon from "#lib/components/ui/Icon.svelte";
import Spinner from "#lib/components/ui/Spinner.svelte"; import Spinner from "#lib/components/ui/Spinner.svelte";
import { formatFull } from "#lib/format/datetime.js"; import { formatFull } from "#lib/format/datetime.js";
import { t } from "#lib/i18n/locale.svelte.js";
import { toasts } from "#lib/stores/toasts.svelte.js"; import { toasts } from "#lib/stores/toasts.svelte.js";
interface Props { interface Props {
@@ -33,7 +34,7 @@
versions = text; versions = text;
mediaVersions = media; mediaVersions = media;
}) })
.catch(() => toasts.error("Failed to load edit history")) .catch(() => toasts.error(t("versions.failed")))
.finally(() => { .finally(() => {
loading = false; loading = false;
}); });
@@ -46,8 +47,8 @@
<Dialog.Overlay class="dialog-overlay" /> <Dialog.Overlay class="dialog-overlay" />
<Dialog.Content class="dialog-content"> <Dialog.Content class="dialog-content">
<header class="dialog-head"> <header class="dialog-head">
<Dialog.Title class="dialog-title">Edit history</Dialog.Title> <Dialog.Title class="dialog-title">{t("versions.title")}</Dialog.Title>
<Dialog.Close aria-label="Close" class="dialog-close"> <Dialog.Close aria-label={t("common.close")} class="dialog-close">
<Icon name="close" size="1.25rem" /> <Icon name="close" size="1.25rem" />
</Dialog.Close> </Dialog.Close>
</header> </header>
@@ -55,11 +56,11 @@
{#if loading} {#if loading}
<div class="centered"><Spinner /></div> <div class="centered"><Spinner /></div>
{:else if versions.length === 0 && mediaVersions.length === 0} {:else if versions.length === 0 && mediaVersions.length === 0}
<p class="empty">No versions recorded.</p> <p class="empty">{t("versions.none")}</p>
{:else} {:else}
{#if mediaVersions.length > 0} {#if mediaVersions.length > 0}
<div class="media-versions"> <div class="media-versions">
<span class="media-label">Media versions</span> <span class="media-label">{t("versions.media")}</span>
<div class="media-strip"> <div class="media-strip">
{#each mediaVersions as media (media.id)} {#each mediaVersions as media (media.id)}
<MediaVersionThumb version={media} /> <MediaVersionThumb version={media} />
@@ -70,12 +71,14 @@
{#each versions as version, index (version.observed_at)} {#each versions as version, index (version.observed_at)}
<div class="version"> <div class="version">
<div class="version-meta"> <div class="version-meta">
<span class="version-label">Version {index + 1}</span> <span class="version-label"
>{t("versions.version", { number: index + 1 })}</span
>
<span class="version-date" <span class="version-date"
>{formatFull(version.observed_at)}</span >{formatFull(version.observed_at)}</span
> >
</div> </div>
<div class="version-text">{version.text ?? "(no text)"}</div> <div class="version-text">{version.text ?? t("feed.noText")}</div>
</div> </div>
{/each} {/each}
{/if} {/if}
+3 -2
View File
@@ -3,6 +3,7 @@
import type { PinnedView } from "#lib/api/types.js"; import type { PinnedView } from "#lib/api/types.js";
import Icon from "#lib/components/ui/Icon.svelte"; import Icon from "#lib/components/ui/Icon.svelte";
import { mediaKindLabel } from "#lib/format/media.js"; import { mediaKindLabel } from "#lib/format/media.js";
import { t } from "#lib/i18n/locale.svelte.js";
import { accounts } from "#lib/stores/accounts.svelte.js"; import { accounts } from "#lib/stores/accounts.svelte.js";
interface Props { interface Props {
@@ -19,7 +20,7 @@
? (pinned.text ?? ? (pinned.text ??
(pinned.media_kind (pinned.media_kind
? mediaKindLabel(pinned.media_kind) ? mediaKindLabel(pinned.media_kind)
: "Pinned message")) : t("pinned.message")))
: "" : ""
); );
@@ -56,7 +57,7 @@
> >
<Icon name="pin" size="1.125rem" /> <Icon name="pin" size="1.125rem" />
<span class="body"> <span class="body">
<span class="label">Pinned message</span> <span class="label">{t("pinned.message")}</span>
<span class="preview">{preview}</span> <span class="preview">{preview}</span>
</span> </span>
</button> </button>
+3 -2
View File
@@ -1,4 +1,5 @@
import type { Account, PeerView } from "#lib/api/types.js"; import type { Account, PeerView } from "#lib/api/types.js";
import { t } from "#lib/i18n/locale.svelte.js";
const PEER_COLOR_COUNT = 7; const PEER_COLOR_COUNT = 7;
@@ -11,7 +12,7 @@ export function peerName(peer: PeerView | null): string {
return ""; return "";
} }
if (peer.is_deleted_account) { if (peer.is_deleted_account) {
return "Удалённый аккаунт"; return t("chat.deletedAccount");
} }
const parts = [peer.first_name, peer.last_name].filter(Boolean); const parts = [peer.first_name, peer.last_name].filter(Boolean);
if (parts.length > 0) { if (parts.length > 0) {
@@ -20,7 +21,7 @@ export function peerName(peer: PeerView | null): string {
if (peer.username) { if (peer.username) {
return `@${peer.username}`; return `@${peer.username}`;
} }
return "Удалённый аккаунт"; return t("chat.deletedAccount");
} }
export function accountName(account: Account): string { export function accountName(account: Account): string {
+2
View File
@@ -10,6 +10,7 @@ import { login } from "#lib/i18n/messages/login.js";
import { media } from "#lib/i18n/messages/media.js"; import { media } from "#lib/i18n/messages/media.js";
import { menu } from "#lib/i18n/messages/menu.js"; import { menu } from "#lib/i18n/messages/menu.js";
import { notes } from "#lib/i18n/messages/notes.js"; import { notes } from "#lib/i18n/messages/notes.js";
import { parts } from "#lib/i18n/messages/parts.js";
import { policy } from "#lib/i18n/messages/policy.js"; import { policy } from "#lib/i18n/messages/policy.js";
import { presence } from "#lib/i18n/messages/presence.js"; import { presence } from "#lib/i18n/messages/presence.js";
import { profile } from "#lib/i18n/messages/profile.js"; import { profile } from "#lib/i18n/messages/profile.js";
@@ -39,6 +40,7 @@ export const messages = {
...media, ...media,
...menu, ...menu,
...notes, ...notes,
...parts,
...policy, ...policy,
...presence, ...presence,
...profile, ...profile,
+22
View File
@@ -0,0 +1,22 @@
import type { Messages } from "#lib/i18n/types.js";
export const parts = {
"folders.all": { en: "All Chats", ru: "Все чаты" },
"forward.channel": { en: "Channel", ru: "Канал" },
"forward.from": { en: "Forwarded from", ru: "Переслано от" },
"forward.hidden": { en: "Hidden account", ru: "Скрытый аккаунт" },
"forward.unknown": { en: "Unknown sender", ru: "Неизвестный отправитель" },
"inline.copied": { en: "Copied", ru: "Скопировано" },
"pinned.message": { en: "Pinned Message", ru: "Закреплённое сообщение" },
"versions.failed": {
en: "Couldn't load the edit history",
ru: "Не удалось загрузить историю изменений",
},
"versions.media": { en: "Media versions", ru: "Версии медиа" },
"versions.none": {
en: "No versions saved.",
ru: "Версии не сохранились.",
},
"versions.title": { en: "Edit history", ru: "История изменений" },
"versions.version": { en: "Version {number}", ru: "Версия {number}" },
} satisfies Messages;