From cdaf6b86da82c9671495936788e4e05c9c473677 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Jan 2026 10:26:37 +0000 Subject: [PATCH] feat(ui): translate Nostr, Google Chat, and config views to Chinese - Add i18n to channels.nostr.ts and channels.nostr-profile-form.ts - Add i18n to channels.googlechat.ts - Add i18n to channels.config.ts (channel configuration section) - Add translation keys for profile form fields and config messages - Complete Chinese translations for all channel views https://claude.ai/code/session_01UK3kVX7BRyE1zEHVh3vrFY --- ui/src/i18n/locales/en-US.ts | 61 ++++++++++++++--- ui/src/i18n/locales/zh-TW.ts | 57 +++++++++++++--- ui/src/ui/views/channels.config.ts | 11 +-- ui/src/ui/views/channels.googlechat.ts | 31 ++++----- .../ui/views/channels.nostr-profile-form.ts | 67 ++++++++++--------- ui/src/ui/views/channels.nostr.ts | 51 +++++++------- 6 files changed, 180 insertions(+), 98 deletions(-) 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: {
Profile picture preview { 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: {
Profile picture { (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 })}
- +
`;