/** * Localized copy for everything this plugin puts in front of a person. * * The harness renders none of a third-party schema's descriptions — the * provider editor is hand-written React with its own bundled copy — so the * plugin's user-facing surface is exactly two things: the names of its entries * in the model picker, and the failure text a refused call shows. Those are * what this module translates. * * The browser client only lets a user *choose* `en` or `zh` * (`dsh-client-locale` ships those two), so `language: 'auto'` follows that * choice, and the other five locales are reachable by naming one explicitly in * this plugin's settings. * * Log lines stay English on purpose: they are diagnostics that end up in * issues and grep, not UI copy. */ /** Every locale this plugin ships copy for. */ export const LOCALES = ['en', 'ru', 'zh', 'de', 'fr', 'es', 'tr'] as const; /** One shipped locale. */ export type Locale = (typeof LOCALES)[number]; /** The locale used when nothing else resolves. */ export const FALLBACK_LOCALE: Locale = 'en'; /** Message keys this plugin translates. */ export type MessageKey = | 'entry.auto' | 'entry.auto-cost-saver' | 'entry.auto-balanced' | 'entry.auto-quality-first' | 'error.noProvider' | 'error.refused'; const MESSAGES: Record> = { en: { 'entry.auto': 'Auto — the router picks the model', 'entry.auto-cost-saver': 'Auto · cost saver', 'entry.auto-balanced': 'Auto · balanced', 'entry.auto-quality-first': 'Auto · quality first', 'error.noProvider': 'no registered provider serves any candidate model ({chain}); register a provider plugin (e.g. llm-deepseek) or correct the model ids in the routing table', 'error.refused': 'no registered provider could serve any candidate model ({chain}); refused by {refused}', }, ru: { 'entry.auto': 'Авто — модель выбирает роутер', 'entry.auto-cost-saver': 'Авто · экономия', 'entry.auto-balanced': 'Авто · баланс', 'entry.auto-quality-first': 'Авто · качество', 'error.noProvider': 'ни один зарегистрированный провайдер не обслуживает кандидатов ({chain}); подключите плагин-провайдер (например llm-deepseek) или исправьте id моделей в таблице маршрутизации', 'error.refused': 'ни один провайдер не смог обслужить кандидатов ({chain}); отказали: {refused}', }, zh: { 'entry.auto': '自动 — 由路由器选择模型', 'entry.auto-cost-saver': '自动 · 优先省钱', 'entry.auto-balanced': '自动 · 均衡', 'entry.auto-quality-first': '自动 · 优先质量', 'error.noProvider': '没有已注册的提供商能提供任何候选模型({chain});请安装提供商插件(例如 llm-deepseek),或修正路由表中的模型 id', 'error.refused': '没有提供商能够处理任何候选模型({chain});被拒绝:{refused}', }, de: { 'entry.auto': 'Auto – der Router wählt das Modell', 'entry.auto-cost-saver': 'Auto · sparsam', 'entry.auto-balanced': 'Auto · ausgewogen', 'entry.auto-quality-first': 'Auto · Qualität zuerst', 'error.noProvider': 'kein registrierter Anbieter bedient eines der Kandidatenmodelle ({chain}); installieren Sie ein Anbieter-Plugin (z. B. llm-deepseek) oder korrigieren Sie die Modell-IDs in der Routing-Tabelle', 'error.refused': 'kein registrierter Anbieter konnte eines der Kandidatenmodelle bedienen ({chain}); abgelehnt von {refused}', }, fr: { 'entry.auto': 'Auto — le routeur choisit le modèle', 'entry.auto-cost-saver': 'Auto · économique', 'entry.auto-balanced': 'Auto · équilibré', 'entry.auto-quality-first': 'Auto · qualité d’abord', 'error.noProvider': 'aucun fournisseur enregistré ne sert les modèles candidats ({chain}) ; installez un plugin fournisseur (par exemple llm-deepseek) ou corrigez les identifiants de modèles dans la table de routage', 'error.refused': 'aucun fournisseur enregistré n’a pu servir les modèles candidats ({chain}) ; refusé par {refused}', }, es: { 'entry.auto': 'Auto — el router elige el modelo', 'entry.auto-cost-saver': 'Auto · ahorro', 'entry.auto-balanced': 'Auto · equilibrado', 'entry.auto-quality-first': 'Auto · calidad primero', 'error.noProvider': 'ningún proveedor registrado sirve los modelos candidatos ({chain}); instale un plugin de proveedor (por ejemplo llm-deepseek) o corrija los id de modelo en la tabla de enrutamiento', 'error.refused': 'ningún proveedor registrado pudo servir los modelos candidatos ({chain}); rechazado por {refused}', }, tr: { 'entry.auto': 'Otomatik — modeli yönlendirici seçer', 'entry.auto-cost-saver': 'Otomatik · tasarruf', 'entry.auto-balanced': 'Otomatik · dengeli', 'entry.auto-quality-first': 'Otomatik · önce kalite', 'error.noProvider': 'kayıtlı hiçbir sağlayıcı aday modelleri sunmuyor ({chain}); bir sağlayıcı eklentisi kurun (örneğin llm-deepseek) veya yönlendirme tablosundaki model kimliklerini düzeltin', 'error.refused': 'kayıtlı hiçbir sağlayıcı aday modelleri sunamadı ({chain}); reddeden: {refused}', }, }; /** * Whether `value` names a locale this plugin ships copy for. * @param value - a candidate locale id, from settings or the harness. * @returns true when the value can be used as a {@link Locale}. */ export function isLocale(value: unknown): value is Locale { return typeof value === 'string' && (LOCALES as readonly string[]).includes(value); } /** * One translated message with its `{placeholders}` filled in. * * A locale never falls back per key: every locale carries every key, so a * partially translated release cannot leak English into the middle of a * sentence. Only an unknown locale falls back, and it falls back whole. * @param locale - the resolved locale. * @param key - the message to render. * @param params - values for the `{name}` placeholders in that message. * @returns the rendered message. */ export function translate( locale: Locale, key: MessageKey, params: Record = {}, ): string { const table = MESSAGES[locale] ?? MESSAGES[FALLBACK_LOCALE]; return table[key].replace(/\{(\w+)\}/g, (whole, name: string) => params[name] ?? whole); } /** Message keys for the catalog entries this plugin ships, by entry id. */ const ENTRY_LABELS: Record = { 'auto': 'entry.auto', 'auto-cost-saver': 'entry.auto-cost-saver', 'auto-balanced': 'entry.auto-balanced', 'auto-quality-first': 'entry.auto-quality-first', }; /** * The localized label for one of this plugin's own catalog entries. * A user-added entry has no shipped copy and keeps whatever name it configures. * @param locale - the resolved locale. * @param id - the catalog entry id. * @returns the label, or `undefined` for an entry this plugin does not ship. */ export function entryLabel(locale: Locale, id: string): string | undefined { const key = ENTRY_LABELS[id]; return key === undefined ? undefined : translate(locale, key); }