refactor: remove LocaleSwitcher component and update locale handling

- Removed LocaleSwitcher from NavUser, SiteHeader, and WorkbenchHeader components.
- Updated tests to reflect the removal of LocaleSwitcher.
- Changed default locale from "en-US" to "zh-CN" in i18n configuration.
- Refactored locale resolution logic to read configured locale without relying on browser language detection.
- Updated AgentDesk SDK to support language configuration from public API.
- Cleaned up unused auth options fetching in the auth API.
This commit is contained in:
mlogclub
2026-06-26 14:46:01 +08:00
parent 12d188fc7e
commit f827a7471c
32 changed files with 215 additions and 268 deletions
+17 -9
View File
@@ -50,14 +50,21 @@ async function loadSdk(config) {
const sandbox = {
URL,
console,
fetch: async () => ({
json: async () => ({
success: true,
data: {
title: "\u5728\u7ebf\u5ba2\u670d",
themeColor: "#2563eb",
},
}),
fetch: async (url) => ({
json: async () =>
String(url).endsWith("/api/config")
? {
success: true,
data: {
language: "en-US",
},
}
: {
success: true,
data: {
themeColor: "#2563eb",
},
},
}),
document: {
body,
@@ -95,7 +102,7 @@ async function loadSdk(config) {
return sandbox
}
async function flushPromises(count = 5) {
async function flushPromises(count = 10) {
for (let i = 0; i < count; i += 1) {
await Promise.resolve()
}
@@ -133,6 +140,7 @@ test("launcher click creates chat iframe with a freshly resolved userToken", asy
)
assert.ok(launcher)
assert.equal(launcher.children.at(-1)?.textContent, "Support")
launcher.click()
await flushPromises()
+52 -24
View File
@@ -7,6 +7,7 @@ import type {
type NormalizedAgentDeskConfig = AgentDeskConfig & {
baseUrl: string
channelId: string
language: string
position: "left" | "right"
themeColor: string
width: string
@@ -38,22 +39,23 @@ type WidgetConfigResponse = {
>>
}
function getWidgetLocale() {
try {
const stored = window.localStorage?.getItem("cs_ai_agent_locale")
const language = stored || document.documentElement.lang || window.navigator?.language || ""
return language.toLowerCase().startsWith("zh") ? "zh-CN" : "en-US"
} catch {
return "en-US"
type PublicConfigResponse = {
success?: boolean
data?: {
language?: string
}
}
function getDefaultWidgetTitle() {
return getWidgetLocale() === "en-US" ? "Support" : "\u5728\u7ebf\u5ba2\u670d"
function normalizeWidgetLanguage(language: string | undefined) {
return String(language || "").toLowerCase().startsWith("en") ? "en-US" : "zh-CN"
}
function getLauncherText() {
return getWidgetLocale() === "en-US" ? "Support" : "\u5ba2\u670d"
function getDefaultWidgetTitle(config?: NormalizedAgentDeskConfig | null) {
return normalizeWidgetLanguage(config?.language) === "en-US" ? "Support" : "\u5728\u7ebf\u5ba2\u670d"
}
function getLauncherText(config?: NormalizedAgentDeskConfig | null) {
return normalizeWidgetLanguage(config?.language) === "en-US" ? "Support" : "\u5ba2\u670d"
}
type FrameMessage =
@@ -65,8 +67,9 @@ type FrameMessage =
(function () {
const DEFAULT_CONFIG: Pick<
NormalizedAgentDeskConfig,
"position" | "themeColor" | "width"
"language" | "position" | "themeColor" | "width"
> = {
language: "zh-CN",
position: "right",
themeColor: "#0f6cbd",
width: "380px",
@@ -103,6 +106,7 @@ type FrameMessage =
delete merged.apiBaseUrl
}
merged.channelId = String(merged.channelId || "")
merged.language = normalizeWidgetLanguage(String(merged.language || "zh-CN"))
if (merged.externalId) {
merged.externalId = String(merged.externalId)
}
@@ -209,6 +213,28 @@ type FrameMessage =
.catch(() => config)
}
function fetchPublicConfig(config: NormalizedAgentDeskConfig) {
const baseUrl = String(config.apiBaseUrl || config.baseUrl || "").replace(/\/$/, "")
if (!baseUrl || typeof fetch !== "function") {
return Promise.resolve(config)
}
return fetch(`${baseUrl}/api/config`, {
method: "GET",
cache: "no-store",
})
.then((response) => response.json() as Promise<PublicConfigResponse>)
.then((payload) => {
if (!payload || payload.success === false) {
return config
}
return normalizeConfig({
...config,
language: payload.data?.language || config.language,
})
})
.catch(() => config)
}
function clearFrameTimers() {
if (state.frameHideTimer) {
window.clearTimeout(state.frameHideTimer)
@@ -369,7 +395,7 @@ type FrameMessage =
state.frame = document.createElement("iframe")
state.frame.dataset.agentDeskWidget = "frame"
state.frame.title = state.config.title || getDefaultWidgetTitle()
state.frame.title = state.config.title || getDefaultWidgetTitle(state.config)
state.frame.src = state.frameUrl.toString()
applyFrameLayout()
state.frame.style.display = "block"
@@ -435,7 +461,7 @@ type FrameMessage =
const text = document.createElement("span")
button.type = "button"
button.dataset.agentDeskWidget = "launcher"
button.setAttribute("aria-label", config.title || getDefaultWidgetTitle())
button.setAttribute("aria-label", config.title || getDefaultWidgetTitle(config))
icon.setAttribute("viewBox", "0 0 24 24")
icon.setAttribute("fill", "none")
icon.setAttribute("stroke", "currentColor")
@@ -451,7 +477,7 @@ type FrameMessage =
path.setAttribute("d", pathData)
icon.appendChild(path)
})
text.textContent = getLauncherText()
text.textContent = getLauncherText(config)
text.style.display = "block"
button.style.position = "fixed"
button.style.bottom = "24px"
@@ -504,15 +530,17 @@ type FrameMessage =
}
state.configLoading = true
fetchWidgetConfig(state.config).then((nextConfig) => {
state.configLoading = false
state.config = normalizeConfig(nextConfig)
if (state.button?.parentNode) {
state.button.parentNode.removeChild(state.button)
state.button = null
}
createLauncher()
})
fetchPublicConfig(state.config)
.then((nextConfig) => fetchWidgetConfig(nextConfig))
.then((nextConfig) => {
state.configLoading = false
state.config = normalizeConfig(nextConfig)
if (state.button?.parentNode) {
state.button.parentNode.removeChild(state.button)
state.button = null
}
createLauncher()
})
}
function destroy() {
+1
View File
@@ -11,6 +11,7 @@ export type AgentDeskConfig = {
getUserToken?: () => string | Promise<string>
title?: string
subtitle?: string
language?: string
position?: "left" | "right"
themeColor?: string
width?: string