Sync bot chat language with DSH system locale.

Localize WhatsApp access-grant approvals via t() and push Host language from the UI on load and locale change.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-09-14 22:38:16 +08:00
parent 6107692418
commit 45f430fb28
10 changed files with 181 additions and 33 deletions

View file

@ -1,7 +1,8 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { createUpdateRpcHandler, installUpdateRpc } from '../plugin-src/host/update-rpc.mjs';
import { createUpdateRpcHandler, installUpdateRpc, HOST_LANGUAGE_SET_ENDPOINT } from '../plugin-src/host/update-rpc.mjs';
import { createImHostPlugin } from '../plugin-src/host/index.mjs';
import { getImHostLanguage, setImHostLanguage } from '../src/channels/shared/i18n.mjs';
test('update RPC rejects arbitrary commands, paths, profiles, sources and invalid request identifiers', async () => {
const calls = [];
@ -13,22 +14,53 @@ test('update RPC rejects arbitrary commands, paths, profiles, sources and invali
['update.install', { checkId: 'id', requestId: 'request', command: 'anything' }],
['update.install', { checkId: 'id', requestId: '; rm -rf /' }],
['update.install', { checkId: 'id', requestId: 'request', version: '9.0.0' }],
[HOST_LANGUAGE_SET_ENDPOINT, {}],
[HOST_LANGUAGE_SET_ENDPOINT, { language: 'en', extra: true }],
[HOST_LANGUAGE_SET_ENDPOINT, { language: '' }],
]) {
assert.equal((await handle(endpoint, payload)).error.code, 'bad-request');
}
assert.deepEqual(calls, []);
});
test('update RPC keeps local authority even when other management uses trusted-host', () => {
const calls = [];
test('host.language.set follows DSH locale ids and returns the normalized host language', async () => {
const handle = createUpdateRpcHandler({});
setImHostLanguage('zh');
try {
assert.deepEqual(await handle(HOST_LANGUAGE_SET_ENDPOINT, { language: 'en' }), {
ok: true, value: { language: 'en' },
});
assert.equal(getImHostLanguage(), 'en');
assert.deepEqual(await handle(HOST_LANGUAGE_SET_ENDPOINT, { language: 'zh-CN' }), {
ok: true, value: { language: 'zh' },
});
assert.equal(getImHostLanguage(), 'zh');
} finally {
setImHostLanguage('zh');
}
});
test('update RPC mounts on /dsh-im through webServer', () => {
const routes = [];
const service = { close: async () => {} };
const ctx = {
connection: { rpc: { handle: (...args) => calls.push(args) } },
effect: () => {},
connection: {
requestRejection: () => undefined,
},
webServer: {
register: (route) => {
routes.push(route);
return () => {};
},
},
effect: (factory) => {
const dispose = factory();
return typeof dispose === 'function' ? dispose : () => {};
},
};
installUpdateRpc(ctx, { service, runtime: {} });
assert.equal(calls[0][0], '/dsh-im');
assert.deepEqual(calls[0][2], { authority: 'loopback' });
assert.equal(routes[0]?.path, '/dsh-im');
assert.equal(routes[0]?.kind, 'prefix');
});
test('update RPC returns only safe codes for unanticipated runtime errors', async () => {