fix(ui): localize channel and session views

This commit is contained in:
Vincent Koc
2026-07-12 13:45:11 +08:00
committed by Vincent Koc
parent 27da133957
commit 77207c9784
12 changed files with 48 additions and 35 deletions
+6 -4
View File
@@ -93,11 +93,13 @@ function renderChannelConfigForm(props: ChannelConfigFormProps) {
const analysis = analyzeConfigSchema(props.schema);
const normalized = analysis.schema;
if (!normalized) {
return html` <div class="callout danger">Schema unavailable. Use Raw.</div> `;
return html` <div class="callout danger">${t("channels.config.schemaUnavailable")}</div> `;
}
const node = resolveSchemaNode(normalized, ["channels", props.channelId]);
if (!node) {
return html` <div class="callout danger">Channel config schema unavailable.</div> `;
return html`
<div class="callout danger">${t("channels.config.channelSchemaUnavailable")}</div>
`;
}
const configValue = props.configValue ?? {};
const value = resolveChannelValue(configValue, props.channelId);
@@ -124,7 +126,7 @@ export function renderChannelConfigSection(params: { channelId: string; props: C
return html`
<div style="margin-top: 16px;">
${props.configSchemaLoading
? html` <div class="muted">Loading config schema…</div> `
? html` <div class="muted">${t("channels.config.loadingSchema")}</div> `
: renderChannelConfigForm({
channelId,
configValue: props.configForm,
@@ -139,7 +141,7 @@ export function renderChannelConfigSection(params: { channelId: string; props: C
?disabled=${disabled || !props.configFormDirty}
@click=${() => props.onConfigSave()}
>
${props.configSaving ? "Saving" : "Save"}
${props.configSaving ? t("common.saving") : t("common.save")}
</button>
<button class="btn" ?disabled=${disabled} @click=${() => props.onConfigReload()}>
${t("common.reload")}
+2 -2
View File
@@ -20,8 +20,8 @@ export function renderDiscordCard(params: {
const configured = resolveChannelConfigured("discord", props);
return renderSingleAccountChannelCard({
title: "Discord",
subtitle: "Bot status and channel configuration.",
title: t("channels.discord.title"),
subtitle: t("channels.discord.subtitle"),
accountCountLabel,
statusRows: [
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
+2 -2
View File
@@ -20,8 +20,8 @@ export function renderGoogleChatCard(params: {
const configured = resolveChannelConfigured("googlechat", props);
return renderSingleAccountChannelCard({
title: "Google Chat",
subtitle: "Chat API webhook status and channel configuration.",
title: t("channels.googleChat.title"),
subtitle: t("channels.googleChat.subtitle"),
accountCountLabel,
statusRows: [
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
+2 -2
View File
@@ -20,8 +20,8 @@ export function renderIMessageCard(params: {
const configured = resolveChannelConfigured("imessage", props);
return renderSingleAccountChannelCard({
title: "iMessage",
subtitle: "macOS bridge status and channel configuration.",
title: t("channels.imessage.title"),
subtitle: t("channels.imessage.subtitle"),
accountCountLabel,
statusRows: [
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
@@ -202,12 +202,12 @@ export function renderNostrProfileForm(params: {
: nothing}
${renderPicturePreview()}
${renderField("name", t("channels.nostr.username"), {
placeholder: "satoshi",
placeholder: t("channels.nostr.placeholders.username"),
maxLength: 256,
help: t("channels.nostr.usernameHelp"),
})}
${renderField("displayName", t("channels.nostr.displayName"), {
placeholder: "Satoshi Nakamoto",
placeholder: t("channels.nostr.placeholders.displayName"),
maxLength: 256,
help: t("channels.nostr.displayNameHelp"),
})}
@@ -219,7 +219,7 @@ export function renderNostrProfileForm(params: {
})}
${renderField("picture", t("channels.nostr.avatarUrl"), {
type: "url",
placeholder: "https://example.com/avatar.jpg",
placeholder: t("channels.nostr.placeholders.avatarUrl"),
help: t("channels.nostr.avatarHelp"),
})}
${state.showAdvanced
@@ -233,20 +233,20 @@ export function renderNostrProfileForm(params: {
${renderField("banner", t("channels.nostr.bannerUrl"), {
type: "url",
placeholder: "https://example.com/banner.jpg",
placeholder: t("channels.nostr.placeholders.bannerUrl"),
help: t("channels.nostr.bannerHelp"),
})}
${renderField("website", t("channels.nostr.website"), {
type: "url",
placeholder: "https://example.com",
placeholder: t("channels.nostr.placeholders.website"),
help: t("channels.nostr.websiteHelp"),
})}
${renderField("nip05", t("channels.nostr.nip05Identifier"), {
placeholder: "you@example.com",
placeholder: t("channels.nostr.placeholders.nip05"),
help: t("channels.nostr.nip05Help"),
})}
${renderField("lud16", t("channels.nostr.lightningAddress"), {
placeholder: "you@getalby.com",
placeholder: t("channels.nostr.placeholders.lightningAddress"),
help: t("channels.nostr.lightningHelp"),
})}
</div>
+2 -2
View File
@@ -194,8 +194,8 @@ export function renderNostrCard(params: {
return html`
<div class="card">
<div class="card-title">Nostr</div>
<div class="card-sub">Decentralized DMs via Nostr relays (NIP-04).</div>
<div class="card-title">${t("channels.nostr.title")}</div>
<div class="card-sub">${t("channels.nostr.subtitle")}</div>
${accountCountLabel}
${hasMultipleAccounts
? html`
+2 -2
View File
@@ -20,8 +20,8 @@ export function renderSignalCard(params: {
const configured = resolveChannelConfigured("signal", props);
return renderSingleAccountChannelCard({
title: "Signal",
subtitle: "signal-cli status and channel configuration.",
title: t("channels.signal.title"),
subtitle: t("channels.signal.subtitle"),
accountCountLabel,
statusRows: [
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
+2 -2
View File
@@ -20,8 +20,8 @@ export function renderSlackCard(params: {
const configured = resolveChannelConfigured("slack", props);
return renderSingleAccountChannelCard({
title: "Slack",
subtitle: "Socket mode status and channel configuration.",
title: t("channels.slack.title"),
subtitle: t("channels.slack.subtitle"),
accountCountLabel,
statusRows: [
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
+4 -4
View File
@@ -59,8 +59,8 @@ export function renderTelegramCard(params: {
if (hasMultipleAccounts) {
return html`
<div class="card">
<div class="card-title">Telegram</div>
<div class="card-sub">Bot status and channel configuration.</div>
<div class="card-title">${t("channels.telegram.title")}</div>
<div class="card-sub">${t("channels.telegram.subtitle")}</div>
${accountCountLabel}
<div class="account-card-list">
@@ -86,8 +86,8 @@ export function renderTelegramCard(params: {
}
return renderSingleAccountChannelCard({
title: "Telegram",
subtitle: "Bot status and channel configuration.",
title: t("channels.telegram.title"),
subtitle: t("channels.telegram.subtitle"),
accountCountLabel,
statusRows: [
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
+1 -1
View File
@@ -89,7 +89,7 @@ export function renderChannels(props: ChannelsProps) {
${showingStaleSnapshot
? html`
<div class="callout info" style="margin-top: 12px;">
Refreshing channel status in the background; showing the last successful snapshot.
${t("channels.refreshingStaleSnapshot")}
</div>
`
: nothing}
+2 -2
View File
@@ -22,8 +22,8 @@ export function renderWhatsAppCard(params: {
const hasQr = props.whatsappQrDataUrl != null;
return renderSingleAccountChannelCard({
title: "WhatsApp",
subtitle: "Link WhatsApp Web and monitor connection health.",
title: t("channels.whatsapp.title"),
subtitle: t("channels.whatsapp.subtitle"),
accountCountLabel,
statusRows: [
{ label: t("common.configured"), value: formatNullableBoolean(configured) },
+16 -5
View File
@@ -941,7 +941,10 @@ export function renderSessions(props: SessionsProps) {
: nothing}
<div class="data-table-wrapper">
<div class="sessions-toolbar sessions-filter-bar" aria-label="Session filters">
<div
class="sessions-toolbar sessions-filter-bar"
aria-label=${t("sessionsView.filterControls")}
>
<div class="data-table-search sessions-toolbar__search">
${icons.search}
<input
@@ -1176,8 +1179,11 @@ export function renderSessions(props: SessionsProps) {
? html`
<div class="data-table-pagination">
<div class="data-table-pagination__info">
${page * props.pageSize + 1}-${Math.min((page + 1) * props.pageSize, totalRows)}
of ${totalRows} row${totalRows === 1 ? "" : "s"}
${t("sessionsView.pagination", {
start: String(page * props.pageSize + 1),
end: String(Math.min((page + 1) * props.pageSize, totalRows)),
total: String(totalRows),
})}
</div>
<div class="data-table-pagination__controls">
<select
@@ -1186,10 +1192,15 @@ export function renderSessions(props: SessionsProps) {
@change=${(e: Event) =>
props.onPageSizeChange(Number((e.target as HTMLSelectElement).value))}
>
${PAGE_SIZES.map((s) => html`<option value=${s}>${s} per page</option>`)}
${PAGE_SIZES.map(
(s) =>
html`<option value=${s}>
${t("sessionsView.rowsPerPage", { count: String(s) })}
</option>`,
)}
</select>
<button ?disabled=${page <= 0} @click=${() => props.onPageChange(page - 1)}>
Previous
${t("common.previous")}
</button>
<button
?disabled=${page >= totalPages - 1}