dsh-im-ops/src/channels/telegram/config-store.mjs
Zaakir dcd7cd64d8 feat(i18n): add English support for host-side chat messages
Wrap all user-facing chat text in src/channels with a host-side t()
translator (Chinese is the identity default, so untranslated text is
always sent verbatim in Chinese). English is selected via the host
plugin config `language: en` or the DSH_IM_LANGUAGE environment
variable, and the settings UI keeps following the DSH web locale.

- add src/channels/shared/i18n.mjs with setImHostLanguage/t() and a
  Chinese-key -> English dictionary split by area (shared + channels)
- complete the English dictionary for every t() key in src/channels,
  including the feishu channel (welcome/help, repair flow, interactive
  cards, watch flow) and the shared command/approval/stream text
- wrap previously untranslated chat-facing strings in feishu bridge,
  feishu runtime, editable-message-stream, harness-client,
  connection-test and image-prompt
- dedupe dictionary entries that existed in multiple channel files and
  canonicalize conflicting translations into the shared dictionaries
- extend the i18n unit test to decode string-literal escapes so keys
  containing \n are checked correctly
- document the language switch in README.md, README.en.md and
  CHANGELOG.md, and rebuild lib/index.js

Added English support for bot chat messages across all nine IM
channels and AI Office without changing any Chinese output.
2026-08-24 00:32:30 +05:00

86 lines
2.7 KiB
JavaScript

import { t } from '../shared/i18n.mjs';
import {
deriveTokenBotIdentity,
maskPlatformId,
TokenBotConfigStore,
} from '../shared/token-config-store.mjs';
const IDENTITY_OPTIONS = Object.freeze({
botPrefix: 'telegram',
tokenRefPrefix: 'DSH_TELEGRAM_BOT_TOKEN',
});
export const TELEGRAM_ACCESS_MODES = Object.freeze({
compatible: 'compatible',
privateAllowlist: 'private-allowlist',
});
const TELEGRAM_USER_ID = /^[1-9]\d{0,15}$/;
export function normalizeTelegramAllowedUsers(value) {
if (value === undefined) return Object.freeze([]);
if (!Array.isArray(value)) {
throw new TypeError('allowedUsers must be an array of numeric Telegram User IDs');
}
const normalized = value.map((entry) => {
const userId = typeof entry === 'number' && Number.isSafeInteger(entry)
? String(entry) : typeof entry === 'string' ? entry.trim() : '';
if (!TELEGRAM_USER_ID.test(userId)) {
throw new TypeError('allowedUsers contains an invalid Telegram User ID');
}
return userId;
});
return Object.freeze([...new Set(normalized)]);
}
export function normalizeTelegramAccessPolicy(value = {}) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new TypeError('Telegram access policy must be an object');
}
const accessMode = value.accessMode ?? TELEGRAM_ACCESS_MODES.compatible;
if (!Object.values(TELEGRAM_ACCESS_MODES).includes(accessMode)) {
throw new TypeError('Telegram accessMode must be compatible or private-allowlist');
}
return Object.freeze({
accessMode,
allowedUsers: normalizeTelegramAllowedUsers(value.allowedUsers),
});
}
function normalizeTelegramBotExtension(value) {
const hasAccessMode = Object.hasOwn(value, 'accessMode');
const hasAllowedUsers = Object.hasOwn(value, 'allowedUsers');
if (!hasAccessMode && !hasAllowedUsers) return {};
try {
const policy = normalizeTelegramAccessPolicy(value);
return {
...(hasAccessMode ? { accessMode: policy.accessMode } : {}),
...(hasAllowedUsers || hasAccessMode ? { allowedUsers: policy.allowedUsers } : {}),
};
} catch {
return null;
}
}
export function deriveTelegramBotIdentity(platformId) {
return deriveTokenBotIdentity(platformId, IDENTITY_OPTIONS);
}
export function maskTelegramBotId(platformId) {
return maskPlatformId(platformId, t('Telegram机器人'));
}
export class TelegramConfigStore extends TokenBotConfigStore {
constructor(path) {
super(path, {
channel: 'Telegram',
...IDENTITY_OPTIONS,
normalizeBotExtension: normalizeTelegramBotExtension,
});
}
async save(value) {
const previous = value?.platformId ? this.getByPlatformId(String(value.platformId)) : null;
return super.save({ ...previous, ...value });
}
}