mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
fix(ui): preserve locale bootstrap and trusted-proxy overview behavior
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
import { en } from "../locales/en.ts";
|
||||
import type { Locale, TranslationMap } from "./types.ts";
|
||||
import { en } from "../locales/en.ts";
|
||||
|
||||
type Subscriber = (locale: Locale) => void;
|
||||
|
||||
export const SUPPORTED_LOCALES: ReadonlyArray<Locale> = ["en", "zh-CN", "zh-TW", "pt-BR"];
|
||||
|
||||
export function isSupportedLocale(value: string | null | undefined): value is Locale {
|
||||
return value !== null && value !== undefined && SUPPORTED_LOCALES.includes(value as Locale);
|
||||
}
|
||||
|
||||
class I18nManager {
|
||||
private locale: Locale = "en";
|
||||
private translations: Record<Locale, TranslationMap> = { en } as Record<Locale, TranslationMap>;
|
||||
@@ -13,8 +19,8 @@ class I18nManager {
|
||||
}
|
||||
|
||||
private loadLocale() {
|
||||
const saved = localStorage.getItem("openclaw.i18n.locale") as Locale;
|
||||
if (saved && ["en", "zh-CN", "zh-TW", "pt-BR"].includes(saved)) {
|
||||
const saved = localStorage.getItem("openclaw.i18n.locale");
|
||||
if (isSupportedLocale(saved)) {
|
||||
this.locale = saved;
|
||||
} else {
|
||||
const navLang = navigator.language;
|
||||
|
||||
@@ -61,6 +61,7 @@ export const en: TranslationMap = {
|
||||
sessionKey: "Default Session Key",
|
||||
language: "Language",
|
||||
connectHint: "Click Connect to apply connection changes.",
|
||||
trustedProxy: "Authenticated via trusted proxy.",
|
||||
},
|
||||
snapshot: {
|
||||
title: "Snapshot",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const pt_BR: TranslationMap = {
|
||||
sessionKey: "Chave de Sessão Padrão",
|
||||
language: "Idioma",
|
||||
connectHint: "Clique em Conectar para aplicar as alterações de conexão.",
|
||||
trustedProxy: "Autenticado por proxy confiável.",
|
||||
},
|
||||
snapshot: {
|
||||
title: "Snapshot",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const zh_CN: TranslationMap = {
|
||||
sessionKey: "默认会话密钥",
|
||||
language: "语言",
|
||||
connectHint: "点击连接以应用连接更改。",
|
||||
trustedProxy: "通过受信任代理认证。",
|
||||
},
|
||||
snapshot: {
|
||||
title: "快照",
|
||||
|
||||
@@ -61,6 +61,7 @@ export const zh_TW: TranslationMap = {
|
||||
sessionKey: "默認會話密鑰",
|
||||
language: "語言",
|
||||
connectHint: "點擊連接以應用連接更改。",
|
||||
trustedProxy: "通過受信任代理身份驗證。",
|
||||
},
|
||||
snapshot: {
|
||||
title: "快照",
|
||||
|
||||
@@ -1,6 +1,35 @@
|
||||
import { LitElement } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { i18n, I18nController, type Locale } from "../i18n/index.ts";
|
||||
import type { EventLogEntry } from "./app-events.ts";
|
||||
import type { AppViewState } from "./app-view-state.ts";
|
||||
import type { DevicePairingList } from "./controllers/devices.ts";
|
||||
import type { ExecApprovalRequest } from "./controllers/exec-approval.ts";
|
||||
import type { ExecApprovalsFile, ExecApprovalsSnapshot } from "./controllers/exec-approvals.ts";
|
||||
import type { SkillMessage } from "./controllers/skills.ts";
|
||||
import type { GatewayBrowserClient, GatewayHelloOk } from "./gateway.ts";
|
||||
import type { Tab } from "./navigation.ts";
|
||||
import type { ResolvedTheme, ThemeMode } from "./theme.ts";
|
||||
import type {
|
||||
AgentsListResult,
|
||||
AgentsFilesListResult,
|
||||
AgentIdentityResult,
|
||||
ConfigSnapshot,
|
||||
ConfigUiHints,
|
||||
CronJob,
|
||||
CronRunLogEntry,
|
||||
CronStatus,
|
||||
HealthSnapshot,
|
||||
LogEntry,
|
||||
LogLevel,
|
||||
PresenceEntry,
|
||||
ChannelsStatusSnapshot,
|
||||
SessionsListResult,
|
||||
SkillStatusReport,
|
||||
StatusSummary,
|
||||
NostrProfile,
|
||||
} from "./types.ts";
|
||||
import type { NostrProfileFormState } from "./views/channels.nostr-profile-form.ts";
|
||||
import { i18n, I18nController, isSupportedLocale } from "../i18n/index.ts";
|
||||
import {
|
||||
handleChannelConfigReload as handleChannelConfigReloadInternal,
|
||||
handleChannelConfigSave as handleChannelConfigSaveInternal,
|
||||
@@ -20,7 +49,6 @@ import {
|
||||
removeQueuedMessage as removeQueuedMessageInternal,
|
||||
} from "./app-chat.ts";
|
||||
import { DEFAULT_CRON_FORM, DEFAULT_LOG_LEVEL_FILTERS } from "./app-defaults.ts";
|
||||
import type { EventLogEntry } from "./app-events.ts";
|
||||
import { connectGateway as connectGatewayInternal } from "./app-gateway.ts";
|
||||
import {
|
||||
handleConnected,
|
||||
@@ -49,38 +77,10 @@ import {
|
||||
type ToolStreamEntry,
|
||||
type CompactionStatus,
|
||||
} from "./app-tool-stream.ts";
|
||||
import type { AppViewState } from "./app-view-state.ts";
|
||||
import { normalizeAssistantIdentity } from "./assistant-identity.ts";
|
||||
import { loadAssistantIdentity as loadAssistantIdentityInternal } from "./controllers/assistant-identity.ts";
|
||||
import type { DevicePairingList } from "./controllers/devices.ts";
|
||||
import type { ExecApprovalRequest } from "./controllers/exec-approval.ts";
|
||||
import type { ExecApprovalsFile, ExecApprovalsSnapshot } from "./controllers/exec-approvals.ts";
|
||||
import type { SkillMessage } from "./controllers/skills.ts";
|
||||
import type { GatewayBrowserClient, GatewayHelloOk } from "./gateway.ts";
|
||||
import type { Tab } from "./navigation.ts";
|
||||
import { loadSettings, type UiSettings } from "./storage.ts";
|
||||
import type { ResolvedTheme, ThemeMode } from "./theme.ts";
|
||||
import type {
|
||||
AgentsListResult,
|
||||
AgentsFilesListResult,
|
||||
AgentIdentityResult,
|
||||
ConfigSnapshot,
|
||||
ConfigUiHints,
|
||||
CronJob,
|
||||
CronRunLogEntry,
|
||||
CronStatus,
|
||||
HealthSnapshot,
|
||||
LogEntry,
|
||||
LogLevel,
|
||||
PresenceEntry,
|
||||
ChannelsStatusSnapshot,
|
||||
SessionsListResult,
|
||||
SkillStatusReport,
|
||||
StatusSummary,
|
||||
NostrProfile,
|
||||
} from "./types.ts";
|
||||
import { type ChatAttachment, type ChatQueueItem, type CronFormState } from "./ui-types.ts";
|
||||
import type { NostrProfileFormState } from "./views/channels.nostr-profile-form.ts";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -109,11 +109,8 @@ export class OpenClawApp extends LitElement {
|
||||
@state() settings: UiSettings = loadSettings();
|
||||
constructor() {
|
||||
super();
|
||||
if (this.settings.locale) {
|
||||
const supportedLocales: Locale[] = ["en", "zh-CN", "zh-TW", "pt-BR"];
|
||||
if (supportedLocales.includes(this.settings.locale as Locale)) {
|
||||
void i18n.setLocale(this.settings.locale as Locale);
|
||||
}
|
||||
if (isSupportedLocale(this.settings.locale)) {
|
||||
void i18n.setLocale(this.settings.locale);
|
||||
}
|
||||
}
|
||||
@state() password = "";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const KEY = "openclaw.control.settings.v1";
|
||||
|
||||
import type { ThemeMode } from "./theme.ts";
|
||||
import { isSupportedLocale } from "../i18n/index.ts";
|
||||
|
||||
export type UiSettings = {
|
||||
gatewayUrl: string;
|
||||
@@ -33,7 +34,6 @@ export function loadSettings(): UiSettings {
|
||||
splitRatio: 0.6,
|
||||
navCollapsed: false,
|
||||
navGroupsCollapsed: {},
|
||||
locale: "en",
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -79,7 +79,7 @@ export function loadSettings(): UiSettings {
|
||||
typeof parsed.navGroupsCollapsed === "object" && parsed.navGroupsCollapsed !== null
|
||||
? parsed.navGroupsCollapsed
|
||||
: defaults.navGroupsCollapsed,
|
||||
locale: typeof parsed.locale === "string" ? parsed.locale : defaults.locale,
|
||||
locale: isSupportedLocale(parsed.locale) ? parsed.locale : undefined,
|
||||
};
|
||||
} catch {
|
||||
return defaults;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { html } from "lit";
|
||||
import type { GatewayHelloOk } from "../gateway.ts";
|
||||
import type { UiSettings } from "../storage.ts";
|
||||
import { t, i18n, type Locale } from "../../i18n/index.ts";
|
||||
import { formatRelativeTimestamp, formatDurationHuman } from "../format.ts";
|
||||
import type { GatewayHelloOk } from "../gateway.ts";
|
||||
import { formatNextRun } from "../presenter.ts";
|
||||
import type { UiSettings } from "../storage.ts";
|
||||
|
||||
export type OverviewProps = {
|
||||
connected: boolean;
|
||||
@@ -25,12 +25,18 @@ export type OverviewProps = {
|
||||
|
||||
export function renderOverview(props: OverviewProps) {
|
||||
const snapshot = props.hello?.snapshot as
|
||||
| { uptimeMs?: number; policy?: { tickIntervalMs?: number } }
|
||||
| {
|
||||
uptimeMs?: number;
|
||||
policy?: { tickIntervalMs?: number };
|
||||
authMode?: "none" | "token" | "password" | "trusted-proxy";
|
||||
}
|
||||
| undefined;
|
||||
const uptime = snapshot?.uptimeMs ? formatDurationHuman(snapshot.uptimeMs) : t("common.na");
|
||||
const tick = snapshot?.policy?.tickIntervalMs
|
||||
? `${snapshot.policy.tickIntervalMs}ms`
|
||||
: t("common.na");
|
||||
const authMode = snapshot?.authMode;
|
||||
const isTrustedProxy = authMode === "trusted-proxy";
|
||||
|
||||
const authHint = (() => {
|
||||
if (props.connected || !props.lastError) {
|
||||
@@ -141,29 +147,35 @@ export function renderOverview(props: OverviewProps) {
|
||||
placeholder="ws://100.x.y.z:18789"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>${t("overview.access.token")}</span>
|
||||
<input
|
||||
.value=${props.settings.token}
|
||||
@input=${(e: Event) => {
|
||||
const v = (e.target as HTMLInputElement).value;
|
||||
props.onSettingsChange({ ...props.settings, token: v });
|
||||
}}
|
||||
placeholder="OPENCLAW_GATEWAY_TOKEN"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>${t("overview.access.password")}</span>
|
||||
<input
|
||||
type="password"
|
||||
.value=${props.password}
|
||||
@input=${(e: Event) => {
|
||||
const v = (e.target as HTMLInputElement).value;
|
||||
props.onPasswordChange(v);
|
||||
}}
|
||||
placeholder="system or shared password"
|
||||
/>
|
||||
</label>
|
||||
${
|
||||
isTrustedProxy
|
||||
? ""
|
||||
: html`
|
||||
<label class="field">
|
||||
<span>${t("overview.access.token")}</span>
|
||||
<input
|
||||
.value=${props.settings.token}
|
||||
@input=${(e: Event) => {
|
||||
const v = (e.target as HTMLInputElement).value;
|
||||
props.onSettingsChange({ ...props.settings, token: v });
|
||||
}}
|
||||
placeholder="OPENCLAW_GATEWAY_TOKEN"
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>${t("overview.access.password")}</span>
|
||||
<input
|
||||
type="password"
|
||||
.value=${props.password}
|
||||
@input=${(e: Event) => {
|
||||
const v = (e.target as HTMLInputElement).value;
|
||||
props.onPasswordChange(v);
|
||||
}}
|
||||
placeholder="system or shared password"
|
||||
/>
|
||||
</label>
|
||||
`
|
||||
}
|
||||
<label class="field">
|
||||
<span>${t("overview.access.sessionKey")}</span>
|
||||
<input
|
||||
@@ -194,7 +206,9 @@ export function renderOverview(props: OverviewProps) {
|
||||
<div class="row" style="margin-top: 14px;">
|
||||
<button class="btn" @click=${() => props.onConnect()}>${t("common.connect")}</button>
|
||||
<button class="btn" @click=${() => props.onRefresh()}>${t("common.refresh")}</button>
|
||||
<span class="muted">${t("overview.access.connectHint")}</span>
|
||||
<span class="muted">${
|
||||
isTrustedProxy ? t("overview.access.trustedProxy") : t("overview.access.connectHint")
|
||||
}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user