diff --git a/ui/src/i18n/locales/en-US.ts b/ui/src/i18n/locales/en-US.ts
index 88a75b138..99346320d 100644
--- a/ui/src/i18n/locales/en-US.ts
+++ b/ui/src/i18n/locales/en-US.ts
@@ -268,27 +268,62 @@ export const enUS = {
// Google Chat
googlechat: {
title: "Google Chat",
- desc: "Google Chat via service account.",
+ desc: "Service account status and channel configuration.",
+ credential: "Credential",
+ audience: "Audience",
+ lastStart: "Last start",
+ lastProbe: "Last probe",
+ probe: "Probe",
+ probeOk: "ok",
+ probeFailed: "failed",
},
// Nostr
nostr: {
title: "Nostr",
- desc: "Nostr protocol via NIP-04 DMs.",
+ desc: "Decentralized DMs via Nostr relays (NIP-04).",
+ publicKey: "Public Key",
+ lastStart: "Last start",
+ profile: "Profile",
editProfile: "Edit Profile",
+ profilePicture: "Profile picture",
+ displayName: "Display Name",
+ noProfile: "No profile set. Click \"Edit Profile\" to add your name, bio, and avatar.",
profileForm: {
- title: "Edit Nostr Profile",
- name: "Display Name",
- about: "About",
- picture: "Picture URL",
- nip05: "NIP-05 Identifier",
- lud16: "Lightning Address",
+ title: "Edit Profile",
+ account: "Account",
+ name: "Username",
+ nameHelp: "Short username (e.g., satoshi)",
+ namePlaceholder: "satoshi",
+ displayName: "Display Name",
+ displayNameHelp: "Your full display name",
+ displayNamePlaceholder: "Satoshi Nakamoto",
+ about: "Bio",
+ aboutHelp: "A brief bio or description",
+ aboutPlaceholder: "Tell people about yourself...",
+ picture: "Avatar URL",
+ pictureHelp: "HTTPS URL to your profile picture",
+ picturePlaceholder: "https://example.com/avatar.jpg",
+ picturePreview: "Profile picture preview",
+ advanced: "Advanced",
banner: "Banner URL",
+ bannerHelp: "HTTPS URL to a banner image",
+ bannerPlaceholder: "https://example.com/banner.jpg",
website: "Website",
- showAdvanced: "Show advanced fields",
- hideAdvanced: "Hide advanced fields",
- importFromRelays: "Import from relays",
+ websiteHelp: "Your personal website",
+ websitePlaceholder: "https://example.com",
+ nip05: "NIP-05 Identifier",
+ nip05Help: "Verifiable identifier (e.g., you@domain.com)",
+ nip05Placeholder: "you@example.com",
+ lud16: "Lightning Address",
+ lud16Help: "Lightning address for tips (LUD-16)",
+ lud16Placeholder: "you@getalby.com",
+ showAdvanced: "Show Advanced",
+ hideAdvanced: "Hide Advanced",
+ importFromRelays: "Import from Relays",
importing: "Importing…",
+ savePublish: "Save & Publish",
+ unsavedChanges: "You have unsaved changes",
},
},
@@ -298,6 +333,10 @@ export const enUS = {
saveChanges: "Save Changes",
reloadConfig: "Reload Config",
unsavedChanges: "Unsaved changes",
+ loadingSchema: "Loading config schema…",
+ schemaUnavailable: "Schema unavailable. Use Raw.",
+ channelSchemaUnavailable: "Channel config schema unavailable.",
+ reload: "Reload",
},
},
diff --git a/ui/src/i18n/locales/zh-TW.ts b/ui/src/i18n/locales/zh-TW.ts
index 5d331c5ac..88cde04d6 100644
--- a/ui/src/i18n/locales/zh-TW.ts
+++ b/ui/src/i18n/locales/zh-TW.ts
@@ -275,27 +275,62 @@ export const zhTW = {
// Google Chat
googlechat: {
title: "Google Chat",
- desc: "透過服務帳戶連接 Google Chat。",
+ desc: "服務帳戶狀態與頻道設定。",
+ credential: "憑證",
+ audience: "對象",
+ lastStart: "上次啟動",
+ lastProbe: "上次探測",
+ probe: "探測",
+ probeOk: "正常",
+ probeFailed: "失敗",
},
// Nostr
nostr: {
title: "Nostr",
- desc: "透過 NIP-04 私訊連接 Nostr 協定。",
+ desc: "透過 Nostr 中繼站進行去中心化私訊(NIP-04)。",
+ publicKey: "公鑰",
+ lastStart: "上次啟動",
+ profile: "個人檔案",
editProfile: "編輯個人檔案",
+ profilePicture: "個人頭像",
+ displayName: "顯示名稱",
+ noProfile: "尚未設定個人檔案。點擊「編輯個人檔案」來新增您的名稱、簡介和頭像。",
profileForm: {
- title: "編輯 Nostr 個人檔案",
- name: "顯示名稱",
- about: "關於",
+ title: "編輯個人檔案",
+ account: "帳號",
+ name: "使用者名稱",
+ nameHelp: "簡短的使用者名稱(如:satoshi)",
+ namePlaceholder: "satoshi",
+ displayName: "顯示名稱",
+ displayNameHelp: "您的完整顯示名稱",
+ displayNamePlaceholder: "Satoshi Nakamoto",
+ about: "個人簡介",
+ aboutHelp: "簡短的自我介紹",
+ aboutPlaceholder: "介紹一下您自己...",
picture: "頭像網址",
- nip05: "NIP-05 識別碼",
- lud16: "閃電網路地址",
+ pictureHelp: "您的頭像的 HTTPS 網址",
+ picturePlaceholder: "https://example.com/avatar.jpg",
+ picturePreview: "頭像預覽",
+ advanced: "進階設定",
banner: "橫幅圖片網址",
+ bannerHelp: "橫幅圖片的 HTTPS 網址",
+ bannerPlaceholder: "https://example.com/banner.jpg",
website: "網站",
- showAdvanced: "顯示進階欄位",
- hideAdvanced: "隱藏進階欄位",
+ websiteHelp: "您的個人網站",
+ websitePlaceholder: "https://example.com",
+ nip05: "NIP-05 識別碼",
+ nip05Help: "可驗證的識別碼(如:you@domain.com)",
+ nip05Placeholder: "you@example.com",
+ lud16: "閃電網路地址",
+ lud16Help: "用於接收打賞的閃電網路地址(LUD-16)",
+ lud16Placeholder: "you@getalby.com",
+ showAdvanced: "顯示進階設定",
+ hideAdvanced: "隱藏進階設定",
importFromRelays: "從中繼站匯入",
importing: "匯入中…",
+ savePublish: "儲存並發布",
+ unsavedChanges: "有未儲存的變更",
},
},
@@ -305,6 +340,10 @@ export const zhTW = {
saveChanges: "儲存變更",
reloadConfig: "重新載入組態",
unsavedChanges: "有未儲存的變更",
+ loadingSchema: "載入組態結構中…",
+ schemaUnavailable: "結構不可用。請使用原始模式。",
+ channelSchemaUnavailable: "頻道組態結構不可用。",
+ reload: "重新載入",
},
},
diff --git a/ui/src/ui/views/channels.config.ts b/ui/src/ui/views/channels.config.ts
index 3c4d2c7df..dcb413414 100644
--- a/ui/src/ui/views/channels.config.ts
+++ b/ui/src/ui/views/channels.config.ts
@@ -1,5 +1,6 @@
import { html } from "lit";
+import { t } from "../../i18n";
import type { ConfigUiHints } from "../types";
import type { ChannelsProps } from "./channels.types";
import {
@@ -71,11 +72,11 @@ export function renderChannelConfigForm(props: ChannelConfigFormProps) {
const analysis = analyzeConfigSchema(props.schema);
const normalized = analysis.schema;
if (!normalized) {
- return html`
Schema unavailable. Use Raw.
`;
+ return html`${t("channels.config.schemaUnavailable")}
`;
}
const node = resolveSchemaNode(normalized, ["channels", props.channelId]);
if (!node) {
- return html`Channel config schema unavailable.
`;
+ return html`${t("channels.config.channelSchemaUnavailable")}
`;
}
const configValue = props.configValue ?? {};
const value = resolveChannelValue(configValue, props.channelId);
@@ -104,7 +105,7 @@ export function renderChannelConfigSection(params: {
return html`
${props.configSchemaLoading
- ? html`
Loading config schema…
`
+ ? html`
${t("channels.config.loadingSchema")}
`
: renderChannelConfigForm({
channelId,
configValue: props.configForm,
@@ -119,14 +120,14 @@ export function renderChannelConfigSection(params: {
?disabled=${disabled || !props.configFormDirty}
@click=${() => props.onConfigSave()}
>
- ${props.configSaving ? "Saving…" : "Save"}
+ ${props.configSaving ? t("common.saving") : t("common.save")}
diff --git a/ui/src/ui/views/channels.googlechat.ts b/ui/src/ui/views/channels.googlechat.ts
index a014ac89e..b3ca46837 100644
--- a/ui/src/ui/views/channels.googlechat.ts
+++ b/ui/src/ui/views/channels.googlechat.ts
@@ -1,5 +1,6 @@
import { html, nothing } from "lit";
+import { t } from "../../i18n";
import { formatAgo } from "../format";
import type { GoogleChatStatus } from "../types";
import { renderChannelConfigSection } from "./channels.config";
@@ -15,37 +16,37 @@ export function renderGoogleChatCard(params: {
return html`
Google Chat
-
Chat API webhook status and channel configuration.
+
${t("channels.googlechat.desc")}
${accountCountLabel}
- Configured
- ${googleChat ? (googleChat.configured ? "Yes" : "No") : "n/a"}
+ ${t("channels.labels.configured")}
+ ${googleChat ? (googleChat.configured ? t("common.yes") : t("common.no")) : t("common.na")}
- Running
- ${googleChat ? (googleChat.running ? "Yes" : "No") : "n/a"}
+ ${t("channels.labels.running")}
+ ${googleChat ? (googleChat.running ? t("common.yes") : t("common.no")) : t("common.na")}
- Credential
- ${googleChat?.credentialSource ?? "n/a"}
+ ${t("channels.googlechat.credential")}
+ ${googleChat?.credentialSource ?? t("common.na")}
- Audience
+ ${t("channels.googlechat.audience")}
${googleChat?.audienceType
? `${googleChat.audienceType}${googleChat.audience ? ` · ${googleChat.audience}` : ""}`
- : "n/a"}
+ : t("common.na")}
- Last start
- ${googleChat?.lastStartAt ? formatAgo(googleChat.lastStartAt) : "n/a"}
+ ${t("channels.googlechat.lastStart")}
+ ${googleChat?.lastStartAt ? formatAgo(googleChat.lastStartAt) : t("common.na")}
- Last probe
- ${googleChat?.lastProbeAt ? formatAgo(googleChat.lastProbeAt) : "n/a"}
+ ${t("channels.googlechat.lastProbe")}
+ ${googleChat?.lastProbeAt ? formatAgo(googleChat.lastProbeAt) : t("common.na")}
@@ -57,7 +58,7 @@ export function renderGoogleChatCard(params: {
${googleChat?.probe
? html`
- Probe ${googleChat.probe.ok ? "ok" : "failed"} ·
+ ${t("channels.googlechat.probe")} ${googleChat.probe.ok ? t("channels.googlechat.probeOk") : t("channels.googlechat.probeFailed")} ·
${googleChat.probe.status ?? ""} ${googleChat.probe.error ?? ""}
`
: nothing}
@@ -66,7 +67,7 @@ export function renderGoogleChatCard(params: {
diff --git a/ui/src/ui/views/channels.nostr-profile-form.ts b/ui/src/ui/views/channels.nostr-profile-form.ts
index 8565d8ef9..7ace4ddd1 100644
--- a/ui/src/ui/views/channels.nostr-profile-form.ts
+++ b/ui/src/ui/views/channels.nostr-profile-form.ts
@@ -6,6 +6,7 @@
import { html, nothing, type TemplateResult } from "lit";
+import { t } from "../../i18n";
import type { NostrProfile as NostrProfileType } from "../types";
// ============================================================================
@@ -147,7 +148,7 @@ export function renderNostrProfileForm(params: {

{
const img = e.target as HTMLImageElement;
@@ -165,8 +166,8 @@ export function renderNostrProfileForm(params: {
return html`
-
Edit Profile
-
Account: ${accountId}
+
${t("channels.nostr.profileForm.title")}
+
${t("channels.nostr.profileForm.account")}: ${accountId}
${state.error
@@ -179,56 +180,56 @@ export function renderNostrProfileForm(params: {
${renderPicturePreview()}
- ${renderField("name", "Username", {
- placeholder: "satoshi",
+ ${renderField("name", t("channels.nostr.profileForm.name"), {
+ placeholder: t("channels.nostr.profileForm.namePlaceholder"),
maxLength: 256,
- help: "Short username (e.g., satoshi)",
+ help: t("channels.nostr.profileForm.nameHelp"),
})}
- ${renderField("displayName", "Display Name", {
- placeholder: "Satoshi Nakamoto",
+ ${renderField("displayName", t("channels.nostr.profileForm.displayName"), {
+ placeholder: t("channels.nostr.profileForm.displayNamePlaceholder"),
maxLength: 256,
- help: "Your full display name",
+ help: t("channels.nostr.profileForm.displayNameHelp"),
})}
- ${renderField("about", "Bio", {
+ ${renderField("about", t("channels.nostr.profileForm.about"), {
type: "textarea",
- placeholder: "Tell people about yourself...",
+ placeholder: t("channels.nostr.profileForm.aboutPlaceholder"),
maxLength: 2000,
- help: "A brief bio or description",
+ help: t("channels.nostr.profileForm.aboutHelp"),
})}
- ${renderField("picture", "Avatar URL", {
+ ${renderField("picture", t("channels.nostr.profileForm.picture"), {
type: "url",
- placeholder: "https://example.com/avatar.jpg",
- help: "HTTPS URL to your profile picture",
+ placeholder: t("channels.nostr.profileForm.picturePlaceholder"),
+ help: t("channels.nostr.profileForm.pictureHelp"),
})}
${state.showAdvanced
? html`
-
Advanced
+
${t("channels.nostr.profileForm.advanced")}
- ${renderField("banner", "Banner URL", {
+ ${renderField("banner", t("channels.nostr.profileForm.banner"), {
type: "url",
- placeholder: "https://example.com/banner.jpg",
- help: "HTTPS URL to a banner image",
+ placeholder: t("channels.nostr.profileForm.bannerPlaceholder"),
+ help: t("channels.nostr.profileForm.bannerHelp"),
})}
- ${renderField("website", "Website", {
+ ${renderField("website", t("channels.nostr.profileForm.website"), {
type: "url",
- placeholder: "https://example.com",
- help: "Your personal website",
+ placeholder: t("channels.nostr.profileForm.websitePlaceholder"),
+ help: t("channels.nostr.profileForm.websiteHelp"),
})}
- ${renderField("nip05", "NIP-05 Identifier", {
- placeholder: "you@example.com",
- help: "Verifiable identifier (e.g., you@domain.com)",
+ ${renderField("nip05", t("channels.nostr.profileForm.nip05"), {
+ placeholder: t("channels.nostr.profileForm.nip05Placeholder"),
+ help: t("channels.nostr.profileForm.nip05Help"),
})}
- ${renderField("lud16", "Lightning Address", {
- placeholder: "you@getalby.com",
- help: "Lightning address for tips (LUD-16)",
+ ${renderField("lud16", t("channels.nostr.profileForm.lud16"), {
+ placeholder: t("channels.nostr.profileForm.lud16Placeholder"),
+ help: t("channels.nostr.profileForm.lud16Help"),
})}
`
@@ -240,7 +241,7 @@ export function renderNostrProfileForm(params: {
@click=${callbacks.onSave}
?disabled=${state.saving || !isDirty}
>
- ${state.saving ? "Saving..." : "Save & Publish"}
+ ${state.saving ? t("common.saving") : t("channels.nostr.profileForm.savePublish")}
${isDirty
? html`
- You have unsaved changes
+ ${t("channels.nostr.profileForm.unsavedChanges")}
`
: nothing}
diff --git a/ui/src/ui/views/channels.nostr.ts b/ui/src/ui/views/channels.nostr.ts
index 05152d80b..4d8360849 100644
--- a/ui/src/ui/views/channels.nostr.ts
+++ b/ui/src/ui/views/channels.nostr.ts
@@ -1,5 +1,6 @@
import { html, nothing } from "lit";
+import { t } from "../../i18n";
import { formatAgo } from "../format";
import type { ChannelAccountSnapshot, NostrStatus } from "../types";
import type { ChannelsProps } from "./channels.types";
@@ -14,7 +15,7 @@ import {
* Truncate a pubkey for display (shows first and last 8 chars)
*/
function truncatePubkey(pubkey: string | null | undefined): string {
- if (!pubkey) return "n/a";
+ if (!pubkey) return t("common.na");
if (pubkey.length <= 20) return pubkey;
return `${pubkey.slice(0, 8)}...${pubkey.slice(-8)}`;
}
@@ -64,20 +65,20 @@ export function renderNostrCard(params: {
- Running
- ${account.running ? "Yes" : "No"}
+ ${t("channels.labels.running")}
+ ${account.running ? t("common.yes") : t("common.no")}
- Configured
- ${account.configured ? "Yes" : "No"}
+ ${t("channels.labels.configured")}
+ ${account.configured ? t("common.yes") : t("common.no")}
- Public Key
+ ${t("channels.nostr.publicKey")}
${truncatePubkey(publicKey)}
- Last inbound
- ${account.lastInboundAt ? formatAgo(account.lastInboundAt) : "n/a"}
+ ${t("channels.lastInbound")}
+ ${account.lastInboundAt ? formatAgo(account.lastInboundAt) : t("common.na")}
${account.lastError
? html`
@@ -117,7 +118,7 @@ export function renderNostrCard(params: {
return html`
-
Profile
+
${t("channels.nostr.profile")}
${summaryConfigured
? html`
`
: nothing}
@@ -138,7 +139,7 @@ export function renderNostrCard(params: {

{
(e.target as HTMLImageElement).style.display = "none";
@@ -147,19 +148,19 @@ export function renderNostrCard(params: {
`
: nothing}
- ${name ? html`
Name${name}
` : nothing}
+ ${name ? html`
${t("channels.nostr.profileForm.name")}${name}
` : nothing}
${displayName
- ? html`
Display Name${displayName}
`
+ ? html`
${t("channels.nostr.displayName")}${displayName}
`
: nothing}
${about
- ? html`
About${about}
`
+ ? html`
${t("channels.nostr.profileForm.about")}${about}
`
: nothing}
- ${nip05 ? html`
NIP-05${nip05}
` : nothing}
+ ${nip05 ? html`
${t("channels.nostr.profileForm.nip05")}${nip05}
` : nothing}
`
: html`
- No profile set. Click "Edit Profile" to add your name, bio, and avatar.
+ ${t("channels.nostr.noProfile")}
`}
@@ -169,7 +170,7 @@ export function renderNostrCard(params: {
return html`
Nostr
-
Decentralized DMs via Nostr relays (NIP-04).
+
${t("channels.nostr.desc")}
${accountCountLabel}
${hasMultipleAccounts
@@ -181,22 +182,22 @@ export function renderNostrCard(params: {
: html`
- Configured
- ${summaryConfigured ? "Yes" : "No"}
+ ${t("channels.labels.configured")}
+ ${summaryConfigured ? t("common.yes") : t("common.no")}
- Running
- ${summaryRunning ? "Yes" : "No"}
+ ${t("channels.labels.running")}
+ ${summaryRunning ? t("common.yes") : t("common.no")}
- Public Key
+ ${t("channels.nostr.publicKey")}
${truncatePubkey(summaryPublicKey)}
- Last start
- ${summaryLastStartAt ? formatAgo(summaryLastStartAt) : "n/a"}
+ ${t("channels.nostr.lastStart")}
+ ${summaryLastStartAt ? formatAgo(summaryLastStartAt) : t("common.na")}
`}
@@ -210,7 +211,7 @@ export function renderNostrCard(params: {
${renderChannelConfigSection({ channelId: "nostr", props })}
-
+
`;