Add zh/en i18n and follow DSH locale.bind for UI.

Wire API/delivery/tool strings through lib/i18n.js, and make the sidebar use ctx.locale.bind plus locale/change so English host preference no longer stays frozen on Chinese.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-09 22:10:07 +08:00
parent 51deaf037b
commit b35bfde557
12 changed files with 373 additions and 37 deletions

View file

@ -9,6 +9,7 @@ import { homedir } from 'node:os'
import { basename, join } from 'node:path'
import { claimOccurrence, executeClaimedRun, extractAssistantText, interruptActiveRuns, publicJob, settleRun, TITLE_PREFIX } from './fire.js'
import { deliverRunToIm, mergeDeliveryMention, mirrorRunToSession, normalizeOrigin } from './delivery.js'
import { apiError, resolveLocale } from './i18n.js'
import { workspaceVisibleIds } from './isolation.js'
import { decideDispatch, nextFire, validateSchedule } from './scheduler.js'
import {
@ -148,8 +149,9 @@ async function resolveBrowserIdentity(request, getUdsAuth) {
* @returns {Promise<object|null>} identity or null after writing error response
*/
async function requireIdentity(request, write, getUdsAuth) {
const locale = resolveLocale(request)
if (!isTrustedApiRequest(request)) {
write(403, { ok: false, error: 'forbidden' })
write(403, apiError('forbidden', locale))
return null
}
const uds = typeof getUdsAuth === 'function' ? getUdsAuth() : null
@ -159,10 +161,10 @@ async function requireIdentity(request, write, getUdsAuth) {
}
const identity = await resolveBrowserIdentity(request, getUdsAuth)
if (!identity?.empNo) {
write(401, { ok: false, error: 'login_required', message: '登录后才能使用定时任务' })
write(401, apiError('login_required', locale))
return null
}
return { ...identity, mode: 'multi' }
return { ...identity, mode: 'multi', lang: identity.lang || locale }
}
function sanitizeJobCwd(cwd, identity, getUdsAuth, { forOwnerEmpNo } = {}) {
@ -740,7 +742,7 @@ export function createHostService(options = {}) {
const runId = decodeURIComponent(openMatch[1])
const state = await snapshot()
const run = (state.runs || []).find((row) => row.id === runId)
if (!run?.sessionId) return write(404, { ok: false, error: 'run not found' })
if (!run?.sessionId) return write(404, apiError('run_not_found', resolveLocale(req, identity)))
assertCanAccessJob(getJob(state, run.jobId), identity)
let unarchived = false
if (typeof sessionPort?.revealSession === 'function') {
@ -755,7 +757,7 @@ export function createHostService(options = {}) {
const identity = await requireIdentity(req, write, getUdsAuth)
if (!identity) return
const sessionId = decodeURIComponent(adoptMatch[1])
if (!sessionId) return write(400, { ok: false, error: 'sessionId required' })
if (!sessionId) return write(400, apiError('session_id_required', resolveLocale(req, identity)))
if (typeof sessionPort?.adoptSession !== 'function') {
return write(200, { ok: false, attached: false, sessionId })
}
@ -810,16 +812,19 @@ export function createHostService(options = {}) {
return
}
write(404, { ok: false, error: 'not found' })
write(404, apiError('not_found', resolveLocale(req)))
} catch (error) {
const locale = resolveLocale(req)
const code = error && error.code
if (code === 'NOT_FOUND') return write(404, { ok: false, error: error.message })
write(401, { ok: false, error: 'login_required', message: '登录后才能使用定时任务' })
if (code === 'NOT_FOUND') return write(404, { ...apiError('not_found', locale), error: error.message || 'not_found' })
if (code === 'INVALID_CRON' || code === 'INVALID_AT' || code === 'INVALID_SCHEDULE' || code === 'INVALID_JOB' || code === 'INVALID_TIMEZONE' || code === 'INVALID_CWD') {
return write(400, { ok: false, error: error.message, code })
return write(400, { ok: false, error: error.message, code, message: error.message })
}
if (code === 'PAYLOAD_TOO_LARGE') return write(413, { ok: false, error: 'payload too large' })
write(500, { ok: false, error: error instanceof Error ? error.message : 'internal error' })
if (code === 'PAYLOAD_TOO_LARGE') return write(413, apiError('payload_too_large', locale))
write(500, {
...apiError('internal', locale),
error: error instanceof Error ? error.message : 'internal',
})
}
}