fix(ui): localize control ui strings

This commit is contained in:
Vincent Koc
2026-04-06 02:51:39 +01:00
parent 38cb5aefc8
commit e8f0f91d29
51 changed files with 2023 additions and 191 deletions

View File

@@ -699,7 +699,9 @@ export function renderApp(state: AppViewState) {
?disabled=${dreamingLoading || state.dreamDiaryLoading}
@click=${refreshDreaming}
>
${dreamingRefreshLoading ? "Refreshing…" : "Refresh"}
${dreamingRefreshLoading
? t("dreaming.header.refreshing")
: t("dreaming.header.refresh")}
</button>
<button
class="dreams__phase-toggle ${dreamingOn
@@ -709,9 +711,9 @@ export function renderApp(state: AppViewState) {
@click=${() => applyDreamingEnabled(!dreamingOn)}
>
<span class="dreams__phase-toggle-dot"></span>
<span class="dreams__phase-toggle-label"
>${dreamingOn ? "Dreaming On" : "Dreaming Off"}</span
>
<span class="dreams__phase-toggle-label">
${dreamingOn ? t("dreaming.header.on") : t("dreaming.header.off")}
</span>
</button>
</div>
`
@@ -1859,7 +1861,7 @@ export function renderApp(state: AppViewState) {
assistantName: state.assistantName,
configPath: state.configSnapshot?.path ?? null,
rawAvailable: typeof state.configSnapshot?.raw === "string",
navRootLabel: "Appearance",
navRootLabel: t("tabs.appearance"),
includeSections: [...APPEARANCE_SECTION_KEYS],
includeVirtualSections: true,
})

View File

@@ -1,12 +1,13 @@
import { formatDurationHuman } from "../../../src/infra/format-time/format-duration.ts";
import { formatRelativeTimestamp } from "../../../src/infra/format-time/format-relative.ts";
import { stripAssistantInternalScaffolding } from "../../../src/shared/text/assistant-visible-text.js";
import { t } from "../i18n/index.ts";
export { formatRelativeTimestamp, formatDurationHuman };
export function formatMs(ms?: number | null): string {
if (!ms && ms !== 0) {
return "n/a";
return t("common.na");
}
return new Date(ms).toLocaleString();
}

View File

@@ -1,3 +1,4 @@
import { t } from "../i18n/index.ts";
import { formatRelativeTimestamp, formatDurationHuman, formatMs } from "./format.ts";
import type { CronJob, GatewaySessionRow, PresenceEntry } from "./types.ts";
@@ -11,12 +12,12 @@ export function formatPresenceSummary(entry: PresenceEntry): string {
export function formatPresenceAge(entry: PresenceEntry): string {
const ts = entry.ts ?? null;
return ts ? formatRelativeTimestamp(ts) : "n/a";
return ts ? formatRelativeTimestamp(ts) : t("common.na");
}
export function formatNextRun(ms?: number | null) {
if (!ms) {
return "n/a";
return t("common.na");
}
const weekday = new Date(ms).toLocaleDateString(undefined, { weekday: "short" });
return `${weekday}, ${formatMs(ms)} (${formatRelativeTimestamp(ms)})`;
@@ -24,7 +25,7 @@ export function formatNextRun(ms?: number | null) {
export function formatSessionTokens(row: GatewaySessionRow) {
if (row.totalTokens == null) {
return "n/a";
return t("common.na");
}
const total = row.totalTokens ?? 0;
const ctx = row.contextTokens ?? 0;
@@ -45,9 +46,9 @@ export function formatEventPayload(payload: unknown): string {
export function formatCronState(job: CronJob) {
const state = job.state ?? {};
const next = state.nextRunAtMs ? formatMs(state.nextRunAtMs) : "n/a";
const last = state.lastRunAtMs ? formatMs(state.lastRunAtMs) : "n/a";
const status = state.lastStatus ?? "n/a";
const next = state.nextRunAtMs ? formatMs(state.nextRunAtMs) : t("common.na");
const last = state.lastRunAtMs ? formatMs(state.lastRunAtMs) : t("common.na");
const status = state.lastStatus ?? t("common.na");
return `${status} · next ${next} · last ${last}`;
}

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import type {
AgentIdentityResult,
AgentsFilesListResult,
@@ -205,7 +206,7 @@ export function renderAgentOverview(params: {
?disabled=${configLoading}
@click=${onConfigReload}
>
Reload Config
${t("common.reloadConfig")}
</button>
<button
type="button"

View File

@@ -3,6 +3,7 @@ import DOMPurify from "dompurify";
import { html, nothing } from "lit";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { marked } from "marked";
import { t } from "../../i18n/index.ts";
import { formatRelativeTimestamp } from "../format.ts";
import { icons } from "../icons.ts";
import {
@@ -175,7 +176,7 @@ export function renderAgentChannels(params: {
<div class="card-sub">Gateway-wide channel status snapshot.</div>
</div>
<button class="btn btn--sm" ?disabled=${params.loading} @click=${params.onRefresh}>
${params.loading ? "Refreshing" : "Refresh"}
${params.loading ? t("common.refreshing") : t("common.refresh")}
</button>
</div>
<div class="muted" style="margin-top: 8px;">Last refresh: ${lastSuccessLabel}</div>
@@ -270,7 +271,7 @@ export function renderAgentCron(params: {
<div class="card-sub">Gateway cron status.</div>
</div>
<button class="btn btn--sm" ?disabled=${params.loading} @click=${params.onRefresh}>
${params.loading ? "Refreshing" : "Refresh"}
${params.loading ? t("common.refreshing") : t("common.refresh")}
</button>
</div>
<div class="stat-grid" style="margin-top: 16px;">
@@ -373,7 +374,7 @@ export function renderAgentFiles(params: {
?disabled=${params.agentFilesLoading}
@click=${() => params.onLoadFiles(params.agentId)}
>
${params.agentFilesLoading ? "Loading" : "Refresh"}
${params.agentFilesLoading ? t("common.loading") : t("common.refresh")}
</button>
</div>
${list

View File

@@ -1,5 +1,6 @@
import { html, nothing } from "lit";
import { normalizeToolName } from "../../../../src/agents/tool-policy-shared.js";
import { t } from "../../i18n/index.ts";
import type {
SkillStatusEntry,
SkillStatusReport,
@@ -188,7 +189,7 @@ export function renderAgentTools(params: {
?disabled=${params.configLoading}
@click=${params.onConfigReload}
>
Reload Config
${t("common.reloadConfig")}
</button>
<button
class="btn btn--sm primary"
@@ -468,10 +469,10 @@ export function renderAgentSkills(params: {
?disabled=${params.configLoading}
@click=${params.onConfigReload}
>
Reload Config
${t("common.reloadConfig")}
</button>
<button class="btn btn--sm" ?disabled=${params.loading} @click=${params.onRefresh}>
${params.loading ? "Loading" : "Refresh"}
${params.loading ? t("common.loading") : t("common.refresh")}
</button>
<button
class="btn btn--sm primary"

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import type {
AgentIdentityResult,
AgentsFilesListResult,
@@ -196,7 +197,7 @@ export function renderAgents(props: AgentsProps) {
?disabled=${props.loading}
@click=${props.onRefresh}
>
${props.loading ? "Loading" : "Refresh"}
${props.loading ? t("common.loading") : t("common.refresh")}
</button>
</div>
</div>

View File

@@ -1,4 +1,5 @@
import { html } from "lit";
import { t } from "../../i18n/index.ts";
import type { ConfigUiHints } from "../types.ts";
import { formatChannelExtraValue, resolveChannelConfigValue } from "./channel-config-extras.ts";
import type { ChannelsProps } from "./channels.types.ts";
@@ -135,7 +136,7 @@ export function renderChannelConfigSection(params: { channelId: string; props: C
${props.configSaving ? "Saving…" : "Save"}
</button>
<button class="btn" ?disabled=${disabled} @click=${() => props.onConfigReload()}>
Reload
${t("common.reload")}
</button>
</div>
</div>

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import { formatRelativeTimestamp } from "../format.ts";
import type { DiscordStatus } from "../types.ts";
import { renderChannelConfigSection } from "./channels.config.ts";
@@ -22,27 +23,27 @@ export function renderDiscordCard(params: {
subtitle: "Bot status and channel configuration.",
accountCountLabel,
statusRows: [
{ label: "Configured", value: formatNullableBoolean(configured) },
{ label: "Running", value: discord?.running ? "Yes" : "No" },
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
{ label: t("common.running"), value: discord?.running ? t("common.yes") : t("common.no") },
{
label: "Last start",
value: discord?.lastStartAt ? formatRelativeTimestamp(discord.lastStartAt) : "n/a",
label: t("common.lastStart"),
value: discord?.lastStartAt ? formatRelativeTimestamp(discord.lastStartAt) : t("common.na"),
},
{
label: "Last probe",
value: discord?.lastProbeAt ? formatRelativeTimestamp(discord.lastProbeAt) : "n/a",
label: t("common.lastProbe"),
value: discord?.lastProbeAt ? formatRelativeTimestamp(discord.lastProbeAt) : t("common.na"),
},
],
lastError: discord?.lastError,
secondaryCallout: discord?.probe
? html`<div class="callout" style="margin-top: 12px;">
Probe ${discord.probe.ok ? "ok" : "failed"} · ${discord.probe.status ?? ""}
${discord.probe.error ?? ""}
${discord.probe.ok ? t("common.probeOk") : t("common.probeFailed")} ·
${discord.probe.status ?? ""} ${discord.probe.error ?? ""}
</div>`
: nothing,
configSection: renderChannelConfigSection({ channelId: "discord", props }),
footer: html`<div class="row" style="margin-top: 12px;">
<button class="btn" @click=${() => props.onRefresh(true)}>Probe</button>
<button class="btn" @click=${() => props.onRefresh(true)}>${t("common.probe")}</button>
</div>`,
});
}

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import { formatRelativeTimestamp } from "../format.ts";
import type { GoogleChatStatus } from "../types.ts";
import { renderChannelConfigSection } from "./channels.config.ts";
@@ -22,37 +23,45 @@ export function renderGoogleChatCard(params: {
subtitle: "Chat API webhook status and channel configuration.",
accountCountLabel,
statusRows: [
{ label: "Configured", value: formatNullableBoolean(configured) },
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
{
label: "Running",
value: googleChat ? (googleChat.running ? "Yes" : "No") : "n/a",
label: t("common.running"),
value: googleChat
? googleChat.running
? t("common.yes")
: t("common.no")
: t("common.na"),
},
{ label: "Credential", value: googleChat?.credentialSource ?? "n/a" },
{ label: t("common.credential"), value: googleChat?.credentialSource ?? t("common.na") },
{
label: "Audience",
label: t("common.audience"),
value: googleChat?.audienceType
? `${googleChat.audienceType}${googleChat.audience ? ` · ${googleChat.audience}` : ""}`
: "n/a",
: t("common.na"),
},
{
label: "Last start",
value: googleChat?.lastStartAt ? formatRelativeTimestamp(googleChat.lastStartAt) : "n/a",
label: t("common.lastStart"),
value: googleChat?.lastStartAt
? formatRelativeTimestamp(googleChat.lastStartAt)
: t("common.na"),
},
{
label: "Last probe",
value: googleChat?.lastProbeAt ? formatRelativeTimestamp(googleChat.lastProbeAt) : "n/a",
label: t("common.lastProbe"),
value: googleChat?.lastProbeAt
? formatRelativeTimestamp(googleChat.lastProbeAt)
: t("common.na"),
},
],
lastError: googleChat?.lastError,
secondaryCallout: googleChat?.probe
? html`<div class="callout" style="margin-top: 12px;">
Probe ${googleChat.probe.ok ? "ok" : "failed"} · ${googleChat.probe.status ?? ""}
${googleChat.probe.error ?? ""}
${googleChat.probe.ok ? t("common.probeOk") : t("common.probeFailed")} ·
${googleChat.probe.status ?? ""} ${googleChat.probe.error ?? ""}
</div>`
: nothing,
configSection: renderChannelConfigSection({ channelId: "googlechat", props }),
footer: html`<div class="row" style="margin-top: 12px;">
<button class="btn" @click=${() => props.onRefresh(true)}>Probe</button>
<button class="btn" @click=${() => props.onRefresh(true)}>${t("common.probe")}</button>
</div>`,
});
}

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import { formatRelativeTimestamp } from "../format.ts";
import type { IMessageStatus } from "../types.ts";
import { renderChannelConfigSection } from "./channels.config.ts";
@@ -22,26 +23,31 @@ export function renderIMessageCard(params: {
subtitle: "macOS bridge status and channel configuration.",
accountCountLabel,
statusRows: [
{ label: "Configured", value: formatNullableBoolean(configured) },
{ label: "Running", value: imessage?.running ? "Yes" : "No" },
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
{ label: t("common.running"), value: imessage?.running ? t("common.yes") : t("common.no") },
{
label: "Last start",
value: imessage?.lastStartAt ? formatRelativeTimestamp(imessage.lastStartAt) : "n/a",
label: t("common.lastStart"),
value: imessage?.lastStartAt
? formatRelativeTimestamp(imessage.lastStartAt)
: t("common.na"),
},
{
label: "Last probe",
value: imessage?.lastProbeAt ? formatRelativeTimestamp(imessage.lastProbeAt) : "n/a",
label: t("common.lastProbe"),
value: imessage?.lastProbeAt
? formatRelativeTimestamp(imessage.lastProbeAt)
: t("common.na"),
},
],
lastError: imessage?.lastError,
secondaryCallout: imessage?.probe
? html`<div class="callout" style="margin-top: 12px;">
Probe ${imessage.probe.ok ? "ok" : "failed"} · ${imessage.probe.error ?? ""}
${imessage.probe.ok ? t("common.probeOk") : t("common.probeFailed")} ·
${imessage.probe.error ?? ""}
</div>`
: nothing,
configSection: renderChannelConfigSection({ channelId: "imessage", props }),
footer: html`<div class="row" style="margin-top: 12px;">
<button class="btn" @click=${() => props.onRefresh(true)}>Probe</button>
<button class="btn" @click=${() => props.onRefresh(true)}>${t("common.probe")}</button>
</div>`,
});
}

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import { formatRelativeTimestamp } from "../format.ts";
import type { ChannelAccountSnapshot, NostrStatus } from "../types.ts";
import { renderChannelConfigSection } from "./channels.config.ts";
@@ -209,16 +210,18 @@ export function renderNostrCard(params: {
<span>${summaryRunning ? "Yes" : "No"}</span>
</div>
<div>
<span class="label">Public Key</span>
<span class="label">${t("common.publicKey")}</span>
<span class="monospace" title="${summaryPublicKey ?? ""}"
>${truncatePubkey(summaryPublicKey)}</span
>
</div>
<div>
<span class="label">Last start</span>
<span
>${summaryLastStartAt ? formatRelativeTimestamp(summaryLastStartAt) : "n/a"}</span
>
<span class="label">${t("common.lastStart")}</span>
<span>
${summaryLastStartAt
? formatRelativeTimestamp(summaryLastStartAt)
: t("common.na")}
</span>
</div>
</div>
`}
@@ -228,7 +231,7 @@ export function renderNostrCard(params: {
${renderProfileSection()} ${renderChannelConfigSection({ channelId: "nostr", props })}
<div class="row" style="margin-top: 12px;">
<button class="btn" @click=${() => props.onRefresh(false)}>Refresh</button>
<button class="btn" @click=${() => props.onRefresh(false)}>${t("common.refresh")}</button>
</div>
</div>
`;

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import type { ChannelAccountSnapshot } from "../types.ts";
import type { ChannelKey, ChannelsProps } from "./channels.types.ts";
@@ -87,9 +88,9 @@ export function resolveChannelConfigured(key: ChannelKey, props: ChannelsProps):
export function formatNullableBoolean(value: boolean | null): string {
if (value == null) {
return "n/a";
return t("common.na");
}
return value ? "Yes" : "No";
return value ? t("common.yes") : t("common.no");
}
export function renderSingleAccountChannelCard(params: {

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import { formatRelativeTimestamp } from "../format.ts";
import type { SignalStatus } from "../types.ts";
import { renderChannelConfigSection } from "./channels.config.ts";
@@ -22,28 +23,28 @@ export function renderSignalCard(params: {
subtitle: "signal-cli status and channel configuration.",
accountCountLabel,
statusRows: [
{ label: "Configured", value: formatNullableBoolean(configured) },
{ label: "Running", value: signal?.running ? "Yes" : "No" },
{ label: "Base URL", value: signal?.baseUrl ?? "n/a" },
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
{ label: t("common.running"), value: signal?.running ? t("common.yes") : t("common.no") },
{ label: t("common.baseUrl"), value: signal?.baseUrl ?? t("common.na") },
{
label: "Last start",
value: signal?.lastStartAt ? formatRelativeTimestamp(signal.lastStartAt) : "n/a",
label: t("common.lastStart"),
value: signal?.lastStartAt ? formatRelativeTimestamp(signal.lastStartAt) : t("common.na"),
},
{
label: "Last probe",
value: signal?.lastProbeAt ? formatRelativeTimestamp(signal.lastProbeAt) : "n/a",
label: t("common.lastProbe"),
value: signal?.lastProbeAt ? formatRelativeTimestamp(signal.lastProbeAt) : t("common.na"),
},
],
lastError: signal?.lastError,
secondaryCallout: signal?.probe
? html`<div class="callout" style="margin-top: 12px;">
Probe ${signal.probe.ok ? "ok" : "failed"} · ${signal.probe.status ?? ""}
${signal.probe.error ?? ""}
${signal.probe.ok ? t("common.probeOk") : t("common.probeFailed")} ·
${signal.probe.status ?? ""} ${signal.probe.error ?? ""}
</div>`
: nothing,
configSection: renderChannelConfigSection({ channelId: "signal", props }),
footer: html`<div class="row" style="margin-top: 12px;">
<button class="btn" @click=${() => props.onRefresh(true)}>Probe</button>
<button class="btn" @click=${() => props.onRefresh(true)}>${t("common.probe")}</button>
</div>`,
});
}

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import { formatRelativeTimestamp } from "../format.ts";
import type { SlackStatus } from "../types.ts";
import { renderChannelConfigSection } from "./channels.config.ts";
@@ -22,27 +23,27 @@ export function renderSlackCard(params: {
subtitle: "Socket mode status and channel configuration.",
accountCountLabel,
statusRows: [
{ label: "Configured", value: formatNullableBoolean(configured) },
{ label: "Running", value: slack?.running ? "Yes" : "No" },
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
{ label: t("common.running"), value: slack?.running ? t("common.yes") : t("common.no") },
{
label: "Last start",
value: slack?.lastStartAt ? formatRelativeTimestamp(slack.lastStartAt) : "n/a",
label: t("common.lastStart"),
value: slack?.lastStartAt ? formatRelativeTimestamp(slack.lastStartAt) : t("common.na"),
},
{
label: "Last probe",
value: slack?.lastProbeAt ? formatRelativeTimestamp(slack.lastProbeAt) : "n/a",
label: t("common.lastProbe"),
value: slack?.lastProbeAt ? formatRelativeTimestamp(slack.lastProbeAt) : t("common.na"),
},
],
lastError: slack?.lastError,
secondaryCallout: slack?.probe
? html`<div class="callout" style="margin-top: 12px;">
Probe ${slack.probe.ok ? "ok" : "failed"} · ${slack.probe.status ?? ""}
${slack.probe.error ?? ""}
${slack.probe.ok ? t("common.probeOk") : t("common.probeFailed")} ·
${slack.probe.status ?? ""} ${slack.probe.error ?? ""}
</div>`
: nothing,
configSection: renderChannelConfigSection({ channelId: "slack", props }),
footer: html`<div class="row" style="margin-top: 12px;">
<button class="btn" @click=${() => props.onRefresh(true)}>Probe</button>
<button class="btn" @click=${() => props.onRefresh(true)}>${t("common.probe")}</button>
</div>`,
});
}

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import { formatRelativeTimestamp } from "../format.ts";
import type { ChannelAccountSnapshot, TelegramStatus } from "../types.ts";
import { renderChannelConfigSection } from "./channels.config.ts";
@@ -31,19 +32,19 @@ export function renderTelegramCard(params: {
</div>
<div class="status-list account-card-status">
<div>
<span class="label">Running</span>
<span>${account.running ? "Yes" : "No"}</span>
<span class="label">${t("common.running")}</span>
<span>${account.running ? t("common.yes") : t("common.no")}</span>
</div>
<div>
<span class="label">Configured</span>
<span>${account.configured ? "Yes" : "No"}</span>
<span class="label">${t("common.configured")}</span>
<span>${account.configured ? t("common.yes") : t("common.no")}</span>
</div>
<div>
<span class="label">Last inbound</span>
<span class="label">${t("common.lastInbound")}</span>
<span
>${account.lastInboundAt
? formatRelativeTimestamp(account.lastInboundAt)
: "n/a"}</span
: t("common.na")}</span
>
</div>
${account.lastError
@@ -70,14 +71,14 @@ export function renderTelegramCard(params: {
: nothing}
${telegram?.probe
? html`<div class="callout" style="margin-top: 12px;">
Probe ${telegram.probe.ok ? "ok" : "failed"} · ${telegram.probe.status ?? ""}
${telegram.probe.error ?? ""}
${telegram.probe.ok ? t("common.probeOk") : t("common.probeFailed")} ·
${telegram.probe.status ?? ""} ${telegram.probe.error ?? ""}
</div>`
: nothing}
${renderChannelConfigSection({ channelId: "telegram", props })}
<div class="row" style="margin-top: 12px;">
<button class="btn" @click=${() => props.onRefresh(true)}>Probe</button>
<button class="btn" @click=${() => props.onRefresh(true)}>${t("common.probe")}</button>
</div>
</div>
`;
@@ -88,28 +89,32 @@ export function renderTelegramCard(params: {
subtitle: "Bot status and channel configuration.",
accountCountLabel,
statusRows: [
{ label: "Configured", value: formatNullableBoolean(configured) },
{ label: "Running", value: telegram?.running ? "Yes" : "No" },
{ label: "Mode", value: telegram?.mode ?? "n/a" },
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
{ label: t("common.running"), value: telegram?.running ? t("common.yes") : t("common.no") },
{ label: t("common.mode"), value: telegram?.mode ?? t("common.na") },
{
label: "Last start",
value: telegram?.lastStartAt ? formatRelativeTimestamp(telegram.lastStartAt) : "n/a",
label: t("common.lastStart"),
value: telegram?.lastStartAt
? formatRelativeTimestamp(telegram.lastStartAt)
: t("common.na"),
},
{
label: "Last probe",
value: telegram?.lastProbeAt ? formatRelativeTimestamp(telegram.lastProbeAt) : "n/a",
label: t("common.lastProbe"),
value: telegram?.lastProbeAt
? formatRelativeTimestamp(telegram.lastProbeAt)
: t("common.na"),
},
],
lastError: telegram?.lastError,
secondaryCallout: telegram?.probe
? html`<div class="callout" style="margin-top: 12px;">
Probe ${telegram.probe.ok ? "ok" : "failed"} · ${telegram.probe.status ?? ""}
${telegram.probe.error ?? ""}
${telegram.probe.ok ? t("common.probeOk") : t("common.probeFailed")} ·
${telegram.probe.status ?? ""} ${telegram.probe.error ?? ""}
</div>`
: nothing,
configSection: renderChannelConfigSection({ channelId: "telegram", props }),
footer: html`<div class="row" style="margin-top: 12px;">
<button class="btn" @click=${() => props.onRefresh(true)}>Probe</button>
<button class="btn" @click=${() => props.onRefresh(true)}>${t("common.probe")}</button>
</div>`,
});
}

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import { formatRelativeTimestamp, formatDurationHuman } from "../format.ts";
import type { WhatsAppStatus } from "../types.ts";
import { renderChannelConfigSection } from "./channels.config.ts";
@@ -78,7 +79,7 @@ export function renderWhatsAppCard(params: {
>
Logout
</button>
<button class="btn" @click=${() => props.onRefresh(true)}>Refresh</button>
<button class="btn" @click=${() => props.onRefresh(true)}>${t("common.refresh")}</button>
</div>`,
});
}

View File

@@ -1,4 +1,5 @@
import { html, nothing, type TemplateResult } from "lit";
import { t } from "../../i18n/index.ts";
import { icons } from "../icons.ts";
import { BORDER_RADIUS_STOPS, type BorderRadiusStop } from "../storage.ts";
import type { ThemeTransitionContext } from "../theme-transition.ts";
@@ -421,7 +422,7 @@ const SECTION_CATEGORIES: SectionCategory[] = [
},
{
id: "appearance",
label: "Appearance",
label: t("tabs.appearance"),
sections: [
{ key: "__appearance__", label: "Theme" },
{ key: "ui", label: "UI" },
@@ -842,7 +843,7 @@ export function renderConfig(props: ConfigProps) {
`
: nothing}
<button class="btn btn--sm" ?disabled=${props.loading} @click=${props.onReload}>
${props.loading ? "Loading" : "Reload"}
${props.loading ? t("common.loading") : t("common.reload")}
</button>
<button class="btn btn--sm primary" ?disabled=${!canSave} @click=${props.onSave}>
${props.saving ? "Saving…" : "Save"}
@@ -896,7 +897,11 @@ export function renderConfig(props: ConfigProps) {
`
: nothing}
<div class="config-top-tabs__scroller" role="tablist" aria-label="Settings sections">
<div
class="config-top-tabs__scroller"
role="tablist"
aria-label="${t("common.settingsSections")}"
>
${topTabs.map(
(tab) => html`
<button

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import type { EventLogEntry } from "../app-events.ts";
import { formatEventPayload } from "../presenter.ts";
@@ -42,7 +43,7 @@ export function renderDebug(props: DebugProps) {
<div class="card-sub">Status, health, and heartbeat data.</div>
</div>
<button class="btn" ?disabled=${props.loading} @click=${props.onRefresh}>
${props.loading ? "Refreshing" : "Refresh"}
${props.loading ? t("common.refreshing") : t("common.refresh")}
</button>
</div>
<div class="stack" style="margin-top: 12px;">
@@ -95,7 +96,7 @@ export function renderDebug(props: DebugProps) {
</label>
</div>
<div class="row" style="margin-top: 12px;">
<button class="btn primary" @click=${props.onCall}>Call</button>
<button class="btn primary" @click=${props.onCall}>${t("common.call")}</button>
</div>
${props.callError
? html`<div class="callout danger" style="margin-top: 12px;">${props.callError}</div>`

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
// ── Diary entry parser ─────────────────────────────────────────────────
@@ -74,27 +75,27 @@ export type DreamingProps = {
onRequestUpdate?: () => void;
};
const DREAM_PHRASES = [
"consolidating memories\u2026",
"tidying the knowledge graph\u2026",
"replaying today's conversations\u2026",
"weaving short-term into long-term\u2026",
"defragmenting the mind palace\u2026",
"filing away loose thoughts\u2026",
"connecting distant dots\u2026",
"composting old context windows\u2026",
"alphabetizing the subconscious\u2026",
"promoting promising hunches\u2026",
"forgetting what doesn't matter\u2026",
"dreaming in embeddings\u2026",
"reorganizing the memory attic\u2026",
"softly indexing the day\u2026",
"nurturing fledgling insights\u2026",
"simmering half-formed ideas\u2026",
"whispering to the vector store\u2026",
];
const DREAM_PHRASE_KEYS = [
"dreaming.phrases.consolidatingMemories",
"dreaming.phrases.tidyingKnowledgeGraph",
"dreaming.phrases.replayingConversations",
"dreaming.phrases.weavingShortTerm",
"dreaming.phrases.defragmentingMindPalace",
"dreaming.phrases.filingLooseThoughts",
"dreaming.phrases.connectingDots",
"dreaming.phrases.compostingContext",
"dreaming.phrases.alphabetizingSubconscious",
"dreaming.phrases.promotingHunches",
"dreaming.phrases.forgettingNoise",
"dreaming.phrases.dreamingEmbeddings",
"dreaming.phrases.reorganizingAttic",
"dreaming.phrases.indexingDay",
"dreaming.phrases.nurturingInsights",
"dreaming.phrases.simmeringIdeas",
"dreaming.phrases.whisperingVectorStore",
] as const;
let _dreamIndex = Math.floor(Math.random() * DREAM_PHRASES.length);
let _dreamIndex = Math.floor(Math.random() * DREAM_PHRASE_KEYS.length);
let _dreamLastSwap = 0;
const DREAM_SWAP_MS = 6_000;
@@ -121,9 +122,9 @@ function currentDreamPhrase(): string {
const now = Date.now();
if (now - _dreamLastSwap > DREAM_SWAP_MS) {
_dreamLastSwap = now;
_dreamIndex = (_dreamIndex + 1) % DREAM_PHRASES.length;
_dreamIndex = (_dreamIndex + 1) % DREAM_PHRASE_KEYS.length;
}
return DREAM_PHRASES[_dreamIndex];
return t(DREAM_PHRASE_KEYS[_dreamIndex] ?? DREAM_PHRASE_KEYS[0]);
}
const STARS: {
@@ -198,7 +199,7 @@ export function renderDreaming(props: DreamingProps) {
props.onRequestUpdate?.();
}}
>
Scene
${t("dreaming.tabs.scene")}
</button>
<button
class="dreams__tab ${_subTab === "diary" ? "dreams__tab--active" : ""}"
@@ -207,7 +208,7 @@ export function renderDreaming(props: DreamingProps) {
props.onRequestUpdate?.();
}}
>
Diary
${t("dreaming.tabs.diary")}
</button>
</nav>
@@ -263,13 +264,15 @@ function renderScene(props: DreamingProps, idle: boolean, dreamText: string) {
<div class="dreams__status">
<span class="dreams__status-label"
>${props.active ? "Dreaming Active" : "Dreaming Idle"}</span
>${props.active ? t("dreaming.status.active") : t("dreaming.status.idle")}</span
>
<div class="dreams__status-detail">
<div class="dreams__status-dot"></div>
<span>
${props.promotedCount} promoted
${props.nextCycle ? html`· next sweep ${props.nextCycle}` : nothing}
${props.promotedCount} ${t("dreaming.status.promotedSuffix")}
${props.nextCycle
? html`· ${t("dreaming.status.nextSweepPrefix")} ${props.nextCycle}`
: nothing}
${props.timezone ? html`· ${props.timezone}` : nothing}
</span>
</div>
@@ -280,21 +283,21 @@ function renderScene(props: DreamingProps, idle: boolean, dreamText: string) {
<span class="dreams__stat-value" style="color: var(--text-strong);"
>${props.shortTermCount}</span
>
<span class="dreams__stat-label">Short-term</span>
<span class="dreams__stat-label">${t("dreaming.stats.shortTerm")}</span>
</div>
<div class="dreams__stat-divider"></div>
<div class="dreams__stat">
<span class="dreams__stat-value" style="color: var(--accent);"
>${props.totalSignalCount}</span
>
<span class="dreams__stat-label">Signals</span>
<span class="dreams__stat-label">${t("dreaming.stats.signals")}</span>
</div>
<div class="dreams__stat-divider"></div>
<div class="dreams__stat">
<span class="dreams__stat-value" style="color: var(--accent-2);"
>${props.phaseSignalCount}</span
>
<span class="dreams__stat-label">Phase Hits</span>
<span class="dreams__stat-label">${t("dreaming.stats.phaseHits")}</span>
</div>
</div>
@@ -337,10 +340,8 @@ function renderDiarySection(props: DreamingProps) {
/>
</svg>
</div>
<div class="dreams-diary__empty-text">No dreams yet</div>
<div class="dreams-diary__empty-hint">
Dreams will appear here after the first dreaming cycle runs.
</div>
<div class="dreams-diary__empty-text">${t("dreaming.diary.noDreamsYet")}</div>
<div class="dreams-diary__empty-hint">${t("dreaming.diary.noDreamsHint")}</div>
</div>
</section>
`;
@@ -353,10 +354,8 @@ function renderDiarySection(props: DreamingProps) {
return html`
<section class="dreams-diary">
<div class="dreams-diary__empty">
<div class="dreams-diary__empty-text">The diary is waiting</div>
<div class="dreams-diary__empty-hint">
Narrative entries will appear after the next dreaming cycle.
</div>
<div class="dreams-diary__empty-text">${t("dreaming.diary.waitingTitle")}</div>
<div class="dreams-diary__empty-hint">${t("dreaming.diary.waitingHint")}</div>
</div>
</section>
`;
@@ -373,7 +372,7 @@ function renderDiarySection(props: DreamingProps) {
return html`
<section class="dreams-diary">
<div class="dreams-diary__header">
<span class="dreams-diary__title">Dream Diary</span>
<span class="dreams-diary__title">${t("dreaming.diary.title")}</span>
<div class="dreams-diary__nav">
<button
class="dreams-diary__nav-btn"
@@ -382,7 +381,7 @@ function renderDiarySection(props: DreamingProps) {
setDiaryPage(page + 1);
props.onRequestUpdate?.();
}}
title="Older"
title=${t("dreaming.diary.older")}
>
</button>
@@ -394,7 +393,7 @@ function renderDiarySection(props: DreamingProps) {
setDiaryPage(page - 1);
props.onRequestUpdate?.();
}}
title="Newer"
title=${t("dreaming.diary.newer")}
>
</button>
@@ -407,7 +406,7 @@ function renderDiarySection(props: DreamingProps) {
props.onRefreshDiary();
}}
>
${props.dreamDiaryLoading ? "\u2026" : "Reload"}
${props.dreamDiaryLoading ? t("dreaming.diary.reloading") : t("dreaming.diary.reload")}
</button>
</div>

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import { icons } from "../icons.ts";
import { formatPresenceAge } from "../presenter.ts";
import type { PresenceEntry } from "../types.ts";
@@ -38,7 +39,7 @@ export function renderInstances(props: InstancesProps) {
${masked ? icons.eyeOff : icons.eye}
</button>
<button class="btn" ?disabled=${props.loading} @click=${props.onRefresh}>
${props.loading ? "Loading" : "Refresh"}
${props.loading ? t("common.loading") : t("common.refresh")}
</button>
</div>
</div>

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import type { LogEntry, LogLevel } from "../types.ts";
const LEVELS: LogLevel[] = ["trace", "debug", "info", "warn", "error", "fatal"];
@@ -62,7 +63,7 @@ export function renderLogs(props: LogsProps) {
</div>
<div class="row" style="gap: 8px;">
<button class="btn" ?disabled=${props.loading} @click=${props.onRefresh}>
${props.loading ? "Loading" : "Refresh"}
${props.loading ? t("common.loading") : t("common.refresh")}
</button>
<button
class="btn"

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import type {
ExecApprovalsAllowlistEntry,
ExecApprovalsFile,
@@ -215,7 +216,7 @@ export function renderExecApprovals(state: ExecApprovalsState) {
? html`<div class="row" style="margin-top: 12px; gap: 12px;">
<div class="muted">Load exec approvals to edit allowlists.</div>
<button class="btn" ?disabled=${state.loading || !targetReady} @click=${state.onLoad}>
${state.loading ? "Loading" : "Load approvals"}
${state.loading ? t("common.loading") : t("common.loadApprovals")}
</button>
</div>`
: html`

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import type {
DevicePairingList,
DeviceTokenSummary,
@@ -58,7 +59,7 @@ export function renderNodes(props: NodesProps) {
<div class="card-sub">Paired devices and live links.</div>
</div>
<button class="btn" ?disabled=${props.loading} @click=${props.onRefresh}>
${props.loading ? "Loading" : "Refresh"}
${props.loading ? t("common.loading") : t("common.refresh")}
</button>
</div>
<div class="list" style="margin-top: 16px;">
@@ -82,7 +83,7 @@ function renderDevices(props: NodesProps) {
<div class="card-sub">Pairing requests + role tokens.</div>
</div>
<button class="btn" ?disabled=${props.devicesLoading} @click=${props.onDevicesRefresh}>
${props.devicesLoading ? "Loading" : "Refresh"}
${props.devicesLoading ? t("common.loading") : t("common.refresh")}
</button>
</div>
${props.devicesError
@@ -276,7 +277,7 @@ function renderBindings(state: BindingState) {
? html`<div class="row" style="margin-top: 12px; gap: 12px;">
<div class="muted">Load config to edit bindings.</div>
<button class="btn" ?disabled=${state.configLoading} @click=${state.onLoadConfig}>
${state.configLoading ? "Loading" : "Load config"}
${state.configLoading ? t("common.loading") : t("common.loadConfig")}
</button>
</div>`
: html`

View File

@@ -1,4 +1,5 @@
import { html, nothing } from "lit";
import { t } from "../../i18n/index.ts";
import { formatRelativeTimestamp } from "../format.ts";
import { icons } from "../icons.ts";
import { pathForTab } from "../navigation.ts";
@@ -222,7 +223,7 @@ export function renderSessions(props: SessionsProps) {
</div>
</div>
<button class="btn" ?disabled=${props.loading} @click=${props.onRefresh}>
${props.loading ? "Loading" : "Refresh"}
${props.loading ? t("common.loading") : t("common.refresh")}
</button>
</div>
@@ -306,7 +307,9 @@ export function renderSessions(props: SessionsProps) {
? html`
<div class="data-table-bulk-bar">
<span>${props.selectedKeys.size} selected</span>
<button class="btn btn--sm" @click=${props.onDeselectAll}>Unselect</button>
<button class="btn btn--sm" @click=${props.onDeselectAll}>
${t("common.unselect")}
</button>
<button
class="btn btn--sm danger"
?disabled=${props.loading}

View File

@@ -1,5 +1,6 @@
import { html, nothing } from "lit";
import { ref } from "lit/directives/ref.js";
import { t } from "../../i18n/index.ts";
import type {
ClawHubSearchResult,
ClawHubSkillDetail,
@@ -137,7 +138,7 @@ export function renderSkills(props: SkillsProps) {
?disabled=${props.loading || !props.connected}
@click=${props.onRefresh}
>
${props.loading ? "Loading\u2026" : "Refresh"}
${props.loading ? t("common.loading") : t("common.refresh")}
</button>
</div>
@@ -320,7 +321,7 @@ function renderClawHubDetailDialog(props: SkillsProps) {
</div>
<div class="md-preview-dialog__body" style="display: grid; gap: 16px;">
${props.clawhubDetailLoading
? html`<div class="muted">Loading…</div>`
? html`<div class="muted">${t("common.loading")}</div>`
: props.clawhubDetailError
? html`<div class="callout danger">${props.clawhubDetailError}</div>`
: detail?.skill