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