@goodandready/dsh-russian-lang 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"dsh-image-gen":{"card.actionRemoveBg":"Удалить фон","card.actionReroll":"Перегенерировать","card.actionUpscale":"Увеличить разрешение","card.copyPrompt":"Скопировать промпт","card.failed":"Ошибка генерации","card.generating":"Генерация изображения…","card.image":"Результат Image Studio","card.origPrompt":"Исходный промпт","card.remix":"Ремикс","card.removeBg":"Удалить фон","card.reroll":"Перегенерировать","card.responsiveTabs":"Адаптивные экраны","card.seedPrefix":"Seed","card.sizePrefix":"Size","card.toolCall":"Вызов генерации изображения","card.unknownError":"An unknown error occurred during generation.","card.upscale":"Увеличить","card.vary":"Вариация","diagnostics.desc":"Быстрая проверка статуса подключения и задержки выбранного API сервиса генерации.","diagnostics.ready":"В норме","diagnostics.test":"Проверить подключение","diagnostics.testing":"Проверка...","diagnostics.title":"Диагностика подключения провайдера","f.apiKeyEnv":"Справка по API-ключу","f.apiKeyEnvHint":"Ссылка на учетные данные / переменная окружения, содержащая ключ API FAL. Префикс авторизации \"Key \" добавляется автоматически.","f.autoEnhancePrompt":"Auto Enhance Prompt","f.autoEnhancePromptHint":"Enrich prompts with photography, lighting, and composition quality tokens.","f.baseURL":"Базовый URL очереди","f.baseURLHint":"Базовый URL очереди FAL (queue.fal.run).","f.cacheByPrompt":"Cache by Prompt Only","f.cacheByPromptHint":"Reuse existing result if the prompt has already been generated.","f.cacheBySeed":"Cache by Seed + Prompt","f.cacheBySeedHint":"Reuse existing result if the exact same seed and prompt are requested.","f.cacheTtlDays":"Cache Retention (Legacy)","f.cacheTtlDaysHint":"Legacy name for retention period in days.","f.customBaseURL":"Пользовательский API: базовый URL","f.customBaseURLHint":"Корень API без завершающего слеша. Запрос отправляется на {base}/images/generations.","f.customKeyEnv":"Пользовательский API: ссылка на ключ","f.customKeyEnvHint":"Ссылка на учётные данные / переменная окружения с API-ключом. Пустое значение означает отсутствие заголовка авторизации для шлюзов, которым он не требуется.","f.customModel":"Пользовательский API: модель","f.customModelHint":"Идентификатор модели, отправленный в этот API, например, gpt-image-1.","f.customSize":"Пользовательский API: фиксированный размер","f.customSizeHint":"Оставьте пустым, и именованный размер будет переведён автоматически (square_hd → 1024x1024). Указывайте размер только для API, требующих конкретных размеров.","f.dailyBudgetUsd":"Дневной бюджет ($ USD)","f.dailyBudgetUsdHint":"Дневной бюджет ($ USD)","f.defaultFormat":"Формат по умолчанию","f.defaultFormatHint":"Используется, если вызов инструмента не содержит output_format.","f.defaultSize":"Размер изображения по умолчанию","f.defaultSizeHint":"Используется, когда вызов инструмента пропускает параметр image_size.","f.defaultStylePreset":"Default Style Preset","f.defaultStylePresetHint":"Default style preset applied to generations (e.g. cinematic, anime, photorealistic).","f.deliverAs":"Как изображение попадает в чат","f.deliverAsHint":"ссылка — результат представляет собой текст со ссылкой, и карта отображает изображение из неё; работает с любой моделью чата. изображение — результат содержит само изображение, которое текстовые модели чата не могут прочитать, поэтому для этого требуется dsh-vision-bridge или модель с поддержкой зрения.","f.diskCache":"Дисковый кэш по хэшу","f.diskCacheHint":"Дисковый кэш по хэшу","f.enableLlmEnhancer":"Включить","f.enableLlmEnhancerHint":"Включить","f.enabled":"Включить","f.enabledHint":"Включить","f.enhanceBelowChars":"Улучшение промптов","f.enhanceBelowCharsHint":"Улучшение промптов","f.enhanceModel":"Улучшение промптов","f.enhanceModelHint":"Улучшение промптов","f.enhancePrompt":"Улучшение промптов","f.enhancePromptHint":"Улучшение промптов","f.geminiKeyEnv":"Gemini Key Reference","f.geminiKeyEnvHint":"Credential reference holding Google Gemini API key.","f.geminiModel":"Gemini Model ID","f.geminiModelHint":"Google Gemini image generation model identifier.","f.historyLimit":"History Limit","f.historyLimitHint":"Maximum number of recent generation records kept in memory.","f.inherit":"По умолчанию / Наследовать","f.localA1111Url":"Local A1111 URL (Legacy)","f.localA1111UrlHint":"Direct Automatic1111 endpoint URL.","f.localBaseURL":"Local Server URL","f.localBaseURLHint":"Server address (e.g. http://127.0.0.1:8188 for ComfyUI, http://127.0.0.1:7860 for A1111).","f.localCfg":"CFG Scale","f.localCfgHint":"Classifier-free guidance scale.","f.localComfyUrl":"Local ComfyUI URL (Legacy)","f.localComfyUrlHint":"Direct ComfyUI endpoint URL.","f.localKind":"Local Engine Architecture","f.localKindHint":"Select between ComfyUI API or Automatic1111 WebUI backend.","f.localModel":"Local Model / Checkpoint","f.localModelHint":"Checkpoint name or ComfyUI workflow name.","f.localSteps":"Sampling Steps","f.localStepsHint":"Number of denoising sampling steps.","f.loopGuardLimit":"Лимит защитного цикла (Loop Guard)","f.loopGuardLimitHint":"Лимит защитного цикла (Loop Guard)","f.model":"Модель (FAL)","f.modelHint":"Идентификатор модели FAL, вызывается как {baseURL}/{model}. Используется, когда провайдер — fal.","f.needCodex":"Built-in Codex subscription","f.needCustom":"Requires OpenAI-compatible endpoint","f.needFal":"Requires FAL API key","f.needGemini":"Requires Google Gemini API key","f.needGrok":"Built-in Grok subscription","f.needLocal":"Local ComfyUI or A1111","f.needReplicate":"Requires Replicate API token","f.needSeedream":"Requires SeaDream API key","f.outputDir":"Выходной каталог","f.outputDirHint":"Относительно рабочего каталога сессии; абсолютный путь используется как есть.","f.pollIntervalMs":"Интервал опроса (мс)","f.pollIntervalMsHint":"Интервал опроса статуса во время выполнения задачи.","f.provider":"Провайдер","f.providerHint":"fal — очередь FAL, настроенная ниже. custom — любой API, совместимый с OpenAI, настраивается далее.","f.pruneDays":"Срок хранения файлов (дней)","f.pruneDaysHint":"Срок хранения файлов (дней)","f.qualityGate":"Качество изображения","f.qualityGateHint":"Качество изображения","f.replicateKeyEnv":"Replicate Key Reference","f.replicateKeyEnvHint":"Credential reference holding the Replicate API token.","f.replicateModel":"Replicate Model ID","f.replicateModelHint":"Target model identifier on Replicate.","f.seedreamBaseURL":"SeaDream Base URL","f.seedreamBaseURLHint":"SeaDream API endpoint URL (ByteDance/Volcengine Ark).","f.seedreamKeyEnv":"SeaDream Key Reference","f.seedreamKeyEnvHint":"Credential reference holding the SeaDream API key.","f.seedreamModel":"SeaDream Model ID","f.seedreamModelHint":"ByteDance SeaDream model identifier.","f.stylePreset":"Суффикс художественного стиля","f.stylePresetHint":"Суффикс художественного стиля","f.subscriptionQuality":"Subscription Quality","f.subscriptionQualityHint":"Target quality profile when using Codex or Grok subscriptions.","f.timeoutMs":"Таймаут (мс)","f.timeoutMsHint":"Общее время ожидания генерации: отправка + опрос + загрузка.","gallery.copy_link":"Копировать Markdown-ссылку","gallery.copy_prompt":"Копировать промпт","gallery.empty":"Галерея пуста. Сгенерируйте первое изображение в чате!","gallery.inspector":"Детали записи генерации","gallery.loading":"Загрузка галереи…","gallery.title":"Галерея Image Studio","p.apiKeyEnv":"Пример: FAL_API_KEY","p.baseURL":"Пример: https://queue.fal.run","p.cacheTtlDays":"0","p.customBaseURL":"Пример: https://api.openai.com/v1","p.customKeyEnv":"Пример: OPENAI_API_KEY","p.customModel":"Пример: gpt-image-1","p.customSize":"Пример: 1024x1024","p.dailyBudgetUsd":"0.00","p.defaultStylePreset":"none","p.enhanceBelowChars":"200","p.enhanceModel":"Leave empty for default chat model","p.geminiKeyEnv":"GEMINI_API_KEY","p.geminiModel":"gemini-2.0-flash-exp-image-generation","p.historyLimit":"50","p.localA1111Url":"http://127.0.0.1:7860","p.localBaseURL":"http://127.0.0.1:8188","p.localCfg":"7","p.localComfyUrl":"http://127.0.0.1:8188","p.localModel":"v1-5-pruned-emaonly.safetensors","p.localSteps":"20","p.loopGuardLimit":"3","p.model":"Пример: fal-ai/flux-2/klein/9b","p.outputDir":"Пример: сгенерированные/изображения","p.pollIntervalMs":"Пример: 2000","p.pruneDays":"0","p.replicateKeyEnv":"REPLICATE_API_TOKEN","p.replicateModel":"black-forest-labs/flux-schnell","p.seedreamBaseURL":"https://api.bytedanceapi.com/v1","p.seedreamKeyEnv":"SEEDREAM_API_KEY","p.seedreamModel":"seedream-4.0","p.timeoutMs":"Пример: 180000","remix.copied":"✓ Copied!","remix.copy_instruction":"Copy Instruction","remix.creative_btn":"Creative (0.65)","remix.creativity_label":"Creativity / Denoising Strength","remix.drawer_title":"Variations & Style Remix Workbench","remix.prompt_placeholder":"Describe stylistic adjustments, new background, or variations...","remix.subtle_btn":"Subtle (0.25)","settings.collapse":"Свернуть","settings.credentialHint":"API-ключи находятся в разделе Учетные данные в указанном наборе ссылок (Настройки → Учетные данные).","settings.description":"Генерация изображений с помощью ИИ, дорисовка, вариации, апскейл и векторизация.","settings.discard":"Сбросить изменения","settings.expand":"Развернуть","settings.invalidNumber":"Пожалуйста, введите корректное число","settings.navLabel":"Генерация изображений","settings.overridden":"переопределено","settings.readOnly":"Это развертывание доступно только для чтения: настройки нельзя изменить через графический интерфейс.","settings.reset":"Сбросить по умолчанию","settings.save":"Сохранить","settings.saveFailed":"Не удалось сохранить настройки. Пожалуйста, проверьте учетные данные и попробуйте снова.","settings.saving":"Сохранение…","settings.title":"Студия изображений","settings.unavailable":"Настройки плагина инициализируются. Они появятся автоматически через несколько секунд.","settings.unsaved":"Несохраненные изменения","stat.active_provider":"Активный провайдер","stat.budget_cache":"Бюджет и кэш","stat.format_size":"Стандартные параметры","stat.safety_status":"Безопасность и Loop Guard","tab.cache":"Кэш и хранилище","tab.enhancer":"Промпт и стили","tab.gallery":"Галерея","tab.general":"Общие","tab.provider":"Провайдер","tab.safety":"Безопасность и бюджет","updater.check":"Check updates","updater.checking":"Checking…","updater.current":"Current version","updater.failed":"Update check failed: ","updater.latest":"Latest version","updater.success":"Plugin updated successfully! Please restart DSH service.","updater.title":"Plugin Updates","updater.upToDate":"Up to date","updater.updateNow":"Update to v{version}","updater.updating":"Updating…"}}
1
+ {"dsh-image-gen":{"card.actionRemoveBg":"Удалить фон","card.actionReroll":"Перегенерировать","card.actionUpscale":"Увеличить разрешение","card.copyPrompt":"Скопировать промпт","card.failed":"Ошибка генерации","card.generating":"Генерация изображения…","card.image":"Результат Image Studio","card.origPrompt":"Исходный промпт","card.remix":"Ремикс","card.removeBg":"Удалить фон","card.reroll":"Перегенерировать","card.responsiveTabs":"Адаптивные экраны","card.seedPrefix":"Seed","card.sizePrefix":"Size","card.toolCall":"Вызов генерации изображения","card.unknownError":"An unknown error occurred during generation.","card.upscale":"Увеличить","card.vary":"Вариация","diagnostics.desc":"Быстрая проверка статуса подключения и задержки выбранного API сервиса генерации.","diagnostics.ready":"В норме","diagnostics.test":"Проверить подключение","diagnostics.testing":"Проверка...","diagnostics.title":"Диагностика подключения провайдера","f.apiKeyEnv":"Ссылка на ключ FAL","f.apiKeyEnvHint":"Имя записи в хранилище учётных данных, содержащей API-ключ FAL.","f.autoEnhancePrompt":"Auto Enhance Prompt","f.autoEnhancePromptHint":"Enrich prompts with photography, lighting, and composition quality tokens.","f.baseURL":"Базовый URL FAL","f.baseURLHint":"Эндпоинт сервиса очередей FAL.","f.cacheByPrompt":"Cache by Prompt Only","f.cacheByPromptHint":"Reuse existing result if the prompt has already been generated.","f.cacheBySeed":"Cache by Seed + Prompt","f.cacheBySeedHint":"Reuse existing result if the exact same seed and prompt are requested.","f.cacheTtlDays":"Cache Retention (Legacy)","f.cacheTtlDaysHint":"Legacy name for retention period in days.","f.customBaseURL":"Пользовательский базовый URL API","f.customBaseURLHint":"Корень OpenAI-совместимого эндпоинта изображений (например https://api.openai.com/v1).","f.customKeyEnv":"Ссылка на пользовательский ключ","f.customKeyEnvHint":"Имя записи в хранилище учётных данных, содержащей API-ключ для пользовательского эндпоинта.","f.customModel":"Пользовательский ID модели","f.customModelHint":"Имя модели, отправляемое на пользовательский эндпоинт изображений.","f.customSize":"Пользовательский размер изображения","f.customSizeHint":"Фиксированный размер, отправляемый на пользовательский эндпоинт (например 1024x1024).","f.dailyBudgetUsd":"Дневной бюджет ($ USD)","f.dailyBudgetUsdHint":"Дневной бюджет ($ USD)","f.defaultFormat":"Формат вывода","f.defaultFormatHint":"Целевой формат файла (PNG, JPEG или WEBP).","f.defaultSize":"Соотношение сторон по умолчанию","f.defaultSizeHint":"Соотношение сторон и разрешение, применяемые если image_size не указан.","f.defaultStylePreset":"Default Style Preset","f.defaultStylePresetHint":"Default style preset applied to generations (e.g. cinematic, anime, photorealistic).","f.deliverAs":"Режим доставки","f.deliverAsHint":"Как изображения передаются в диалог: \"link\" (безопасно для текстовых LLM) или \"image\" (мультимодальное зрение).","f.diskCache":"Дисковый кэш по хэшу","f.diskCacheHint":"Дисковый кэш по хэшу","f.enableLlmEnhancer":"Включить","f.enableLlmEnhancerHint":"Включить","f.enabled":"Включить","f.enabledHint":"Включить","f.enhanceBelowChars":"Улучшение промптов","f.enhanceBelowCharsHint":"Улучшение промптов","f.enhanceModel":"Улучшение промптов","f.enhanceModelHint":"Улучшение промптов","f.enhancePrompt":"Улучшение промптов","f.enhancePromptHint":"Улучшение промптов","f.geminiKeyEnv":"Gemini Key Reference","f.geminiKeyEnvHint":"Credential reference holding Google Gemini API key.","f.geminiModel":"Gemini Model ID","f.geminiModelHint":"Google Gemini image generation model identifier.","f.historyLimit":"History Limit","f.historyLimitHint":"Maximum number of recent generation records kept in memory.","f.inherit":"По умолчанию / Наследовать","f.localA1111Url":"Local A1111 URL (Legacy)","f.localA1111UrlHint":"Direct Automatic1111 endpoint URL.","f.localBaseURL":"Local Server URL","f.localBaseURLHint":"Server address (e.g. http://127.0.0.1:8188 for ComfyUI, http://127.0.0.1:7860 for A1111).","f.localCfg":"CFG Scale","f.localCfgHint":"Classifier-free guidance scale.","f.localComfyUrl":"Local ComfyUI URL (Legacy)","f.localComfyUrlHint":"Direct ComfyUI endpoint URL.","f.localKind":"Local Engine Architecture","f.localKindHint":"Select between ComfyUI API or Automatic1111 WebUI backend.","f.localModel":"Local Model / Checkpoint","f.localModelHint":"Checkpoint name or ComfyUI workflow name.","f.localSteps":"Sampling Steps","f.localStepsHint":"Number of denoising sampling steps.","f.loopGuardLimit":"Лимит защитного цикла (Loop Guard)","f.loopGuardLimitHint":"Лимит защитного цикла (Loop Guard)","f.model":"ID модели FAL","f.modelHint":"Целевая модель на эндпоинте FAL (например fal-ai/flux-2/klein/9b).","f.needCodex":"Built-in Codex subscription","f.needCustom":"Requires OpenAI-compatible endpoint","f.needFal":"Requires FAL API key","f.needGemini":"Requires Google Gemini API key","f.needGrok":"Built-in Grok subscription","f.needLocal":"Local ComfyUI or A1111","f.needReplicate":"Requires Replicate API token","f.needSeedream":"Requires SeaDream API key","f.outputDir":"Каталог сохранения","f.outputDirHint":"Путь к каталогу на диске, куда сохраняются сгенерированные изображения.","f.pollIntervalMs":"Интервал опроса (мс)","f.pollIntervalMsHint":"Частота проверки статуса задачи в асинхронной очереди.","f.provider":"Провайдер изображений","f.providerHint":"Бэкенд-сервис для генерации изображений.","f.pruneDays":"Срок хранения файлов (дней)","f.pruneDaysHint":"Срок хранения файлов (дней)","f.qualityGate":"Качество изображения","f.qualityGateHint":"Качество изображения","f.replicateKeyEnv":"Replicate Key Reference","f.replicateKeyEnvHint":"Credential reference holding the Replicate API token.","f.replicateModel":"Replicate Model ID","f.replicateModelHint":"Target model identifier on Replicate.","f.seedreamBaseURL":"SeaDream Base URL","f.seedreamBaseURLHint":"SeaDream API endpoint URL (ByteDance/Volcengine Ark).","f.seedreamKeyEnv":"SeaDream Key Reference","f.seedreamKeyEnvHint":"Credential reference holding the SeaDream API key.","f.seedreamModel":"SeaDream Model ID","f.seedreamModelHint":"ByteDance SeaDream model identifier.","f.stylePreset":"Суффикс художественного стиля","f.stylePresetHint":"Суффикс художественного стиля","f.subscriptionQuality":"Subscription Quality","f.subscriptionQualityHint":"Target quality profile when using Codex or Grok subscriptions.","f.timeoutMs":"Таймаут (мс)","f.timeoutMsHint":"Максимальный общий таймаут до прерывания запроса генерации.","gallery.copy_link":"Копировать Markdown-ссылку","gallery.copy_prompt":"Копировать промпт","gallery.empty":"Галерея пуста. Сгенерируйте первое изображение в чате!","gallery.inspector":"Детали записи генерации","gallery.loading":"Загрузка галереи…","gallery.title":"Галерея Image Studio","p.apiKeyEnv":"FAL_API_KEY","p.baseURL":"https://queue.fal.run","p.cacheTtlDays":"0","p.customBaseURL":"https://api.openai.com/v1","p.customKeyEnv":"OPENAI_API_KEY","p.customModel":"dall-e-3","p.customSize":"1024x1024","p.dailyBudgetUsd":"0.00","p.defaultStylePreset":"none","p.enhanceBelowChars":"200","p.enhanceModel":"Leave empty for default chat model","p.geminiKeyEnv":"GEMINI_API_KEY","p.geminiModel":"gemini-2.0-flash-exp-image-generation","p.historyLimit":"50","p.localA1111Url":"http://127.0.0.1:7860","p.localBaseURL":"http://127.0.0.1:8188","p.localCfg":"7","p.localComfyUrl":"http://127.0.0.1:8188","p.localModel":"v1-5-pruned-emaonly.safetensors","p.localSteps":"20","p.loopGuardLimit":"3","p.model":"fal-ai/flux-2/klein/9b","p.outputDir":"generated/images","p.pollIntervalMs":"2000","p.pruneDays":"0","p.replicateKeyEnv":"REPLICATE_API_TOKEN","p.replicateModel":"black-forest-labs/flux-schnell","p.seedreamBaseURL":"https://api.bytedanceapi.com/v1","p.seedreamKeyEnv":"SEEDREAM_API_KEY","p.seedreamModel":"seedream-4.0","p.timeoutMs":"180000","remix.copied":"✓ Copied!","remix.copy_instruction":"Copy Instruction","remix.creative_btn":"Creative (0.65)","remix.creativity_label":"Creativity / Denoising Strength","remix.drawer_title":"Variations & Style Remix Workbench","remix.prompt_placeholder":"Describe stylistic adjustments, new background, or variations...","remix.subtle_btn":"Subtle (0.25)","settings.collapse":"Свернуть","settings.credentialHint":"Ключи API хранятся в учётных данных DSH (~/.dsh/.credentials.yaml) и не попадают в файлы конфигурации.","settings.description":"Генерация изображений с помощью ИИ, дорисовка, вариации, апскейл и векторизация.","settings.discard":"Сбросить изменения","settings.expand":"Развернуть","settings.invalidNumber":"Пожалуйста, введите корректное число","settings.navLabel":"Студия изображений","settings.overridden":"переопределено","settings.readOnly":"Настройки доступны только для чтения в этом контексте.","settings.reset":"Сбросить по умолчанию","settings.save":"Сохранить настройки","settings.saveFailed":"Не удалось сохранить настройки. Пожалуйста, проверьте учетные данные и попробуйте снова.","settings.saving":"Сохранение…","settings.title":"Студия изображений","settings.unavailable":"Настройки плагина инициализируются. Они появятся автоматически через несколько секунд.","settings.unsaved":"Несохраненные изменения","stat.active_provider":"Активный провайдер","stat.budget_cache":"Бюджет и кэш","stat.format_size":"Стандартные параметры","stat.safety_status":"Безопасность и Loop Guard","tab.cache":"Кэш и хранилище","tab.enhancer":"Промпт и стили","tab.gallery":"Галерея","tab.general":"Общие","tab.provider":"Провайдер","tab.safety":"Безопасность и бюджет","updater.check":"Check updates","updater.checking":"Checking…","updater.current":"Current version","updater.failed":"Update check failed: ","updater.latest":"Latest version","updater.success":"Plugin updated successfully! Please restart DSH service.","updater.title":"Plugin Updates","updater.upToDate":"Up to date","updater.updateNow":"Update to v{version}","updater.updating":"Updating…"}}
@@ -1 +1 @@
1
- {"dsh-hypotheses":{"notificationHome":"Home мессенджера","notificationHomeHint":"Использует существующий Telegram home мессенджера; credentials остаются в gateway.","notificationHomePlaceholder":"Например: default","notifications":"Уведомления о завершении расследования","notificationsHint":"Отправлять один отчёт после явного завершения гипотезы.","save":"Сохранить","saved":"Сохранено","saving":"Сохранение…","settingsLoading":"Загрузка настроек уведомлений…","settingsUnavailable":"Настройки уведомлений недоступны."}}
1
+ {"@goodandready/dsh-hypotheses":{"active":"Активные","archived":"В архиве","clusters":"Кластеры","confidence":"Уверенность","confidenceHistory":"История уверенности","empty":"Гипотезы пока не зафиксированы.","evidence":"Факты","loading":"Загрузка аналитики…","noEvidence":"Факты не зафиксированы.","noHistory":"История пересчёта отсутствует.","notificationHome":"Home мессенджера","notificationHomeHint":"Использует существующий Telegram home мессенджера; credentials остаются в gateway.","notificationHomePlaceholder":"Например: default","notifications":"Уведомления о завершении расследования","notificationsHint":"Отправлять один отчёт после явного завершения гипотезы.","recentEvidence":"Последние факты","refresh":"Обновить","resolved":"Подтверждённые","save":"Сохранить","saved":"Сохранено","saving":"Сохранение…","settingsLoading":"Загрузка настроек уведомлений…","settingsUnavailable":"Настройки уведомлений недоступны.","stale":"Устаревшие","subtitle":"Анализ и проверка гипотез расследования (только чтение)","title":"Гипотезы","truncated":"Часть записей скрыта из-за ограничений отображения.","unavailable":"Аналитика в данный момент недоступна."},"dsh-hypotheses":{"active":"Активные","archived":"В архиве","clusters":"Кластеры","confidence":"Уверенность","confidenceHistory":"История уверенности","empty":"Гипотезы пока не зафиксированы.","evidence":"Факты","loading":"Загрузка аналитики…","noEvidence":"Факты не зафиксированы.","noHistory":"История пересчёта отсутствует.","notificationHome":"Home мессенджера","notificationHomeHint":"Использует существующий Telegram home мессенджера; credentials остаются в gateway.","notificationHomePlaceholder":"Например: default","notifications":"Уведомления о завершении расследования","notificationsHint":"Отправлять один отчёт после явного завершения гипотезы.","recentEvidence":"Последние факты","refresh":"Обновить","resolved":"Подтверждённые","save":"Сохранить","saved":"Сохранено","saving":"Сохранение…","settingsLoading":"Загрузка настроек уведомлений…","settingsUnavailable":"Настройки уведомлений недоступны.","stale":"Устаревшие","subtitle":"Анализ и проверка гипотез расследования (только чтение)","title":"Гипотезы","truncated":"Часть записей скрыта из-за ограничений отображения.","unavailable":"Аналитика в данный момент недоступна."}}
@@ -1 +1 @@
1
- {"dsh-server-monitor":{"addServer":"Добавить сервер","address":"Адрес","architecture":"Архитектура","auth":"Способ аутентификации","available":"Доступно","cancel":"Отмена","command":"Команда","confirmDelete":"Удалить этот профиль сервера?","containers":"Контейнеры","cpu":"Процессор","deleteServer":"Удалить профиль","disks":"Диски","editServer":"Редактировать сервер","error":"Ошибка","filesystem":"Файловая система","host":"Хост","hostname":"Имя узла","image":"Образ","interface":"Сетевой интерфейс","keepSecret":"Оставьте поле пустым, чтобы сохранить текущее значение.","kernel":"Ядро","key":"Закрытый ключ","keyHint":"Ключ хранится только в хранилище этого плагина.","keyPath":"Путь к ключу","load":"Средняя нагрузка","loading":"Загрузка…","memory":"Память","mount":"Точка монтирования","name":"Название","network":"Сеть","noServers":"Серверы не настроены.","noSnapshot":"Снимок состояния недоступен.","os":"Операционная система","passphrase":"Парольная фраза ключа","password":"Пароль","port":"Порт","ports":"Прослушиваемые порты","processes":"Наиболее ресурсоёмкие процессы","protocol":"Протокол","resources":"Ресурсы","rx":"Получено (RX)","saved":"Сохранено","saving":"Сохранение…","select":"Выберите сервер","servers":"Серверы","state":"Состояние","subtitle":"Состояние Linux-серверов — только для просмотра","swap":"Область подкачки","system":"Система","testing":"Проверка подключения…","title":"Монитор серверов","tx":"Передано (TX)","unavailable":"Недоступно","uptime":"Время работы","used":"Использовано","username":"Имя пользователя"}}
1
+ {"dsh-server-monitor":{"active":"Активен","add":"Добавить сервер","addServer":"Добавить сервер","address":"Адрес","architecture":"Архитектура","auth":"Способ аутентификации","available":"Доступно","cancel":"Отмена","checkUpdateFailed":"Сбой проверки","command":"Команда","confirmDelete":"Удалить этот профиль сервера?","connected":"В сети","containers":"Контейнеры","copied":"Скопировано!","copyInstallCmd":"Скопировать команду настройки сервера","cpu":"Процессор","delete":"Удалить","deleteServer":"Удалить профиль","disks":"Диски","editServer":"Редактировать сервер","error":"Ошибка","filesystem":"Файловая система","generateKey":"Сгенерировать SSH-ключ","generating":"Генерация…","guideDescription":"Мониторинг статуса Linux-серверов (только чтение)","host":"Хост","hostname":"Имя узла","image":"Образ","interface":"Сетевой интерфейс","keepSecret":"Оставьте поле пустым, чтобы сохранить текущее значение.","kernel":"Ядро","key":"Закрытый ключ","keyHint":"Ключ хранится только в хранилище этого плагина.","keyPath":"Путь к ключу","keygenSection":"Настройка SSH-ключа","load":"Средняя нагрузка","loading":"Загрузка…","memory":"Память","mount":"Точка монтирования","name":"Название","network":"Сеть","noServers":"Серверы не настроены.","noSnapshot":"Снимок состояния недоступен.","offline":"Не в сети","os":"Операционная система","partial":"Частично","passphrase":"Парольная фраза ключа","password":"Пароль","port":"Порт","ports":"Прослушиваемые порты","processes":"Наиболее ресурсоёмкие процессы","protocol":"Протокол","refresh":"Обновить","resources":"Ресурсы","rx":"Получено (RX)","save":"Сохранить сервер","saved":"Сохранено","saving":"Сохранение…","select":"Выберите сервер","servers":"Серверы","shareWorkspaceHint":"Сохранено в ~/.dsh/keys/id_ed25519_dsh (совместимо с Remote Workspace)","state":"Состояние","subtitle":"Состояние Linux-серверов — только для просмотра","swap":"Область подкачки","system":"Система","test":"Проверить подключение","testAfterInstall":"Команда выполнена — проверить подключение","testing":"Проверка подключения…","title":"Монитор серверов","tx":"Передано (TX)","unavailable":"Недоступно","upToDate":"Актуальная версия","updateAvailable":"Доступно обновление: v{v}","updateChecking":"Проверка обновлений…","updateFailed":"Сбой обновления: {msg}","updateNow":"Обновить сейчас","updateSuccess":"Обновлено до v{v}. Перезапустите сервис DSH для применения изменений.","updating":"Обновление…","uptime":"Время работы","used":"Использовано","username":"Имя пользователя","version":"Версия"}}
@@ -1 +1 @@
1
- {"agentrouter":{"apiKeyEnv":"Имя переменной окружения / ключа API","apiKeyHint":"Укажите ключ ar-… в учётных данных DSH или в переменной окружения процесса. Не вставляйте секрет в эту форму.","baseUrl":"Базовый URL","defaultModel":"Модель по умолчанию","enabled":"Включено","keyMissing":"ключ отсутствует","loading":"Загрузка настроек…","refresh":"Обновить","registerProvider":"Зарегистрировать в моделях","save":"Сохранить","saved":"Сохранено","smokeChat":"Тестовый чат","subtitle":"Маршрутизация агентов и интеграция моделей через локальный или удалённый прокси","title":"AgentRouter","unavailable":"Настройки недоступны (статус снимка: {status}). Пространство имён хоста отсутствует."},"dsh-agentrouter":{"apiKeyEnv":"Имя переменной окружения / ключа API","apiKeyHint":"Укажите ключ ar-… в учётных данных DSH или в переменной окружения процесса. Не вставляйте секрет в эту форму.","baseUrl":"Базовый URL","defaultModel":"Модель по умолчанию","enabled":"Включено","keyMissing":"ключ отсутствует","loading":"Загрузка настроек…","refresh":"Обновить","registerProvider":"Зарегистрировать в моделях","save":"Сохранить","saved":"Сохранено","smokeChat":"Тестовый чат","subtitle":"Маршрутизация агентов и интеграция моделей через локальный или удалённый прокси","title":"AgentRouter","unavailable":"Настройки недоступны (статус снимка: {status}). Пространство имён хоста отсутствует."}}
1
+ {"@goodandready-private/dsh-agentrouter":{"apiKeyEnv":"Имя переменной окружения / ключа API","apiKeyHint":"Укажите ключ ar-… в учётных данных DSH или в переменной окружения процесса. Не вставляйте секрет в эту форму.","baseUrl":"Базовый URL","defaultModel":"Модель по умолчанию","enabled":"Включено","keyMissing":"ключ отсутствует","loading":"Загрузка настроек…","refresh":"Обновить","registerProvider":"Зарегистрировать в моделях","save":"Сохранить","saved":"Сохранено","smokeChat":"Тестовый чат","subtitle":"Маршрутизация агентов и интеграция моделей через локальный или удалённый прокси","title":"AgentRouter","unavailable":"Настройки недоступны (статус снимка: {status}). Пространство имён хоста отсутствует."},"agentrouter":{"apiKeyEnv":"Имя переменной окружения / ключа API","apiKeyHint":"Укажите ключ ar-… в учётных данных DSH или в переменной окружения процесса. Не вставляйте секрет в эту форму.","baseUrl":"Базовый URL","defaultModel":"Модель по умолчанию","enabled":"Включено","keyMissing":"ключ отсутствует","loading":"Загрузка настроек…","refresh":"Обновить","registerProvider":"Зарегистрировать в моделях","save":"Сохранить","saved":"Сохранено","smokeChat":"Тестовый чат","subtitle":"Маршрутизация агентов и интеграция моделей через локальный или удалённый прокси","title":"AgentRouter","unavailable":"Настройки недоступны (статус снимка: {status}). Пространство имён хоста отсутствует."},"dsh-agentrouter":{"apiKeyEnv":"Имя переменной окружения / ключа API","apiKeyHint":"Укажите ключ ar-… в учётных данных DSH или в переменной окружения процесса. Не вставляйте секрет в эту форму.","baseUrl":"Базовый URL","defaultModel":"Модель по умолчанию","enabled":"Включено","keyMissing":"ключ отсутствует","loading":"Загрузка настроек…","refresh":"Обновить","registerProvider":"Зарегистрировать в моделях","save":"Сохранить","saved":"Сохранено","smokeChat":"Тестовый чат","subtitle":"Маршрутизация агентов и интеграция моделей через локальный или удалённый прокси","title":"AgentRouter","unavailable":"Настройки недоступны (статус снимка: {status}). Пространство имён хоста отсутствует."}}
@@ -0,0 +1 @@
1
+ {"dsh-github-ops":{"approvalMode":"Подтверждение записи","approvalModeHint":"\"auto\" разрешает согласованные неразрушающие операции записи без запроса; удаление релиза, тега, секрета, переменной, набора правил, защиты ветки или отмена запуска всегда запрашивают подтверждение. \"ask\" запрашивает каждое действие записи; \"off\" передаёт контроль хосту.","baseUrl":"Базовый URL API","baseUrlHint":"Измените для GitHub Enterprise; по умолчанию используется публичный API.","cardHint":"Токен берётся из учётных данных DSH; в настройках хранится только его имя.","defaultRepository":"Репозиторий по умолчанию","defaultRepositoryHint":"owner/repo, используемый если вызов инструмента опускает репозиторий. Оставьте пустым, чтобы всегда указывать явно.","invalidNumber":"Введите положительное число.","maxRetries":"Повторы при ошибке чтения","maxRetriesHint":"Повтор чтения (GET/HEAD) при сетевом сбое, таймауте или 5xx. Запись никогда не повторяется автоматически.","noChanges":"Нет изменений для сохранения.","reviewRules":"Переопределение правил ревью (JSON)","reviewRulesHint":"Необязательно. sensitivePaths, sensitiveSeverity, attentionPaths, migrationPaths, testsRequired, sourcePatterns, testPatterns, largeDiffLines. Пример: {\"largeDiffLines\": 400, \"sensitivePaths\": [\".env\", \"private/**\"]}","save":"Сохранить","saved":"Сохранено","savedPartial":"Сохранено, за исключением: ","saving":"Сохранение…","statusLoading":"Чтение настроек…","statusReadOnly":"Настройки доступны только для чтения в этой сессии.","statusUnavailable":"Сервис настроек недоступен в этой сборке: значения здесь редактировать нельзя.","sub":"Релизы, теги, защищённый шлюз API, публикация зеркал и настройки репозитория.","timeoutMs":"Таймаут запроса, мс","timeoutMsHint":"Предельное время выполнения одного запроса к GitHub до его прерывания.","title":"Операции с GitHub","tokenEnv":"Имя учётных данных","tokenEnvHint":"Имя записи в хранилище учётных данных DSH, содержащей токен GitHub. Сам токен здесь никогда не сохраняется.","tokenSource":"Источник доступа","tokenSourceHint":"\"auto\" проверяет учётные данные DSH, затем переменную окружения, затем сессию gh CLI. Зафиксируйте значение, чтобы использовать только один источник."}}
@@ -0,0 +1 @@
1
+ {"@goodandready/dsh-web-gateway":{"allowInternalUrls":"Разрешить внутренние URL для извлечения","allowInternalUrlsDesc":"Опасно: разрешает извлечение адресов, разрешающихся в loopback или приватные сети. Оставляйте выключенным без явной необходимости.","cacheTtlMs":"Время жизни кэша (мс)","cacheTtlMsDesc":"Срок хранения результатов в оперативной памяти. 0 отключает кэширование.","crawl4aiTokenEnv":"Имя учётных данных Crawl4AI","crawl4aiUrl":"Базовый URL Crawl4AI","crawl4aiUrlDesc":"Необязательный Crawl4AI для извлечения страниц. Пустое значение отключает его.","defaultLimit":"Лимит результатов по умолчанию","defaultLimitDesc":"Количество результатов по умолчанию для вызова web_gateway_search.","exaApiKeyEnv":"Имя учётных данных Exa","firecrawlApiKeyEnv":"Имя учётных данных Firecrawl","header_desc":"Резервный веб-поиск (Tavily → Firecrawl → Exa → SearXNG) и извлечение страниц при исчерпании лимитов встроенных web_search / web_fetch.","keysDesc":"Храните секретные значения в Настройки → Учётные данные под этими именами. Не вставляйте сами API-ключи сюда.","maxLimit":"Жёсткий лимит результатов","maxLimitDesc":"Максимальное количество результатов за один поисковый запрос.","save":"Сохранить","saved":"Настройки сохранены","saving":"Сохранение…","searxngUrl":"Базовый URL SearXNG","searxngUrlDesc":"Необязательный локальный SearXNG (например http://127.0.0.1:8080). Пустое значение отключает этот резерв.","settings.loading":"Загрузка настроек Web Gateway…","settings.retry":"Повторить","settings.unavailable":"Область настроек недоступна (пространство имён хоста не готово).","subtitle":"Отказоустойчивая цепочка поиска и извлечения веб-страниц","tavilyApiKeyEnv":"Имя учётных данных Tavily","timeoutMs":"Таймаут провайдера (мс)","timeoutMsDesc":"HTTP-таймаут на одного провайдера в миллисекундах.","title":"Веб-шлюз (Web Gateway)","updater_check":"Проверить обновления","updater_checking":"Проверка…","updater_current":"Текущая","updater_failed":"Сбой проверки обновлений","updater_latest":"Последняя","updater_success":"Обновлено. Перезапустите DSH для загрузки новой версии.","updater_title":"Версия плагина","updater_unavailable":"Автоматическое обновление недоступно для этого профиля.","updater_up_to_date":"Актуальная версия","updater_update_now":"Обновить до {version}","updater_updating":"Обновление…"},"dsh-web-gateway":{"allowInternalUrls":"Разрешить внутренние URL для извлечения","allowInternalUrlsDesc":"Опасно: разрешает извлечение адресов, разрешающихся в loopback или приватные сети. Оставляйте выключенным без явной необходимости.","cacheTtlMs":"Время жизни кэша (мс)","cacheTtlMsDesc":"Срок хранения результатов в оперативной памяти. 0 отключает кэширование.","crawl4aiTokenEnv":"Имя учётных данных Crawl4AI","crawl4aiUrl":"Базовый URL Crawl4AI","crawl4aiUrlDesc":"Необязательный Crawl4AI для извлечения страниц. Пустое значение отключает его.","defaultLimit":"Лимит результатов по умолчанию","defaultLimitDesc":"Количество результатов по умолчанию для вызова web_gateway_search.","exaApiKeyEnv":"Имя учётных данных Exa","firecrawlApiKeyEnv":"Имя учётных данных Firecrawl","header_desc":"Резервный веб-поиск (Tavily → Firecrawl → Exa → SearXNG) и извлечение страниц при исчерпании лимитов встроенных web_search / web_fetch.","keysDesc":"Храните секретные значения в Настройки → Учётные данные под этими именами. Не вставляйте сами API-ключи сюда.","maxLimit":"Жёсткий лимит результатов","maxLimitDesc":"Максимальное количество результатов за один поисковый запрос.","save":"Сохранить","saved":"Настройки сохранены","saving":"Сохранение…","searxngUrl":"Базовый URL SearXNG","searxngUrlDesc":"Необязательный локальный SearXNG (например http://127.0.0.1:8080). Пустое значение отключает этот резерв.","settings.loading":"Загрузка настроек Web Gateway…","settings.retry":"Повторить","settings.unavailable":"Область настроек недоступна (пространство имён хоста не готово).","subtitle":"Отказоустойчивая цепочка поиска и извлечения веб-страниц","tavilyApiKeyEnv":"Имя учётных данных Tavily","timeoutMs":"Таймаут провайдера (мс)","timeoutMsDesc":"HTTP-таймаут на одного провайдера в миллисекундах.","title":"Веб-шлюз (Web Gateway)","updater_check":"Проверить обновления","updater_checking":"Проверка…","updater_current":"Текущая","updater_failed":"Сбой проверки обновлений","updater_latest":"Последняя","updater_success":"Обновлено. Перезапустите DSH для загрузки новой версии.","updater_title":"Версия плагина","updater_unavailable":"Автоматическое обновление недоступно для этого профиля.","updater_up_to_date":"Актуальная версия","updater_update_now":"Обновить до {version}","updater_updating":"Обновление…"}}
@@ -0,0 +1 @@
1
+ {"@goodandready/dsh-approval-gate":{"approvalRequired":"Требуется подтверждение","askCredentialInArgv":"Раскрытые данные авторизации передаются как аргументы процесса; используйте безопасный хелпер или проверьте перед разрешением","askDynamicArguments":"Раскрытые аргументы чувствительной команды не могут быть проверены; проверьте перед разрешением","askDynamicCommand":"Раскрытое имя команды не может быть проверено; проверьте перед разрешением","askDynamicRedirect":"Раскрытая цель перенаправления не может быть проверена; проверьте перед разрешением","askInterpreterPayload":"Побочные эффекты интерпретатора не проанализированы полностью; проверьте перед разрешением","askScriptFile":"Содержимое файла скрипта недоступно для проверки; проверьте перед разрешением","askShellStdin":"Ввод shell не полностью доступен для проверки; проверьте перед разрешением","askUnknown":"Команда не может быть полностью проверена; проверьте перед разрешением","askUnknownFormat":"Формат вызова инструмента не может быть проверен; проверьте перед разрешением","askUnknownWriteTarget":"Целевой файл для записи не определён; проверьте перед разрешением","askUnparseable":"Синтаксис shell не может быть полностью проверен; проверьте перед разрешением","blockedPrefix":"Заблокировано правилом dsh-approval-gate","commandLabel":"Команда","detailDanglingEscape":"незавершённое экранирование","detailInvalidHeredoc":"недопустимый разделитель here-document","detailProcessSubstitution":"подстановка процесса требует подтверждения","detailUnclosedBacktick":"незакрытая подстановка в обратных кавычках","detailUnclosedCommandSubstitution":"незакрытая подстановка команды","detailUnclosedDoubleQuote":"незакрытая двойная кавычка","detailUnclosedParameterExpansion":"незакрытое раскрытие параметров","detailUnclosedProcessSubstitution":"незакрытая подстановка процесса","detailUnclosedSingleQuote":"незакрытая одинарная кавычка","detailUnsupported":"неподдерживаемый синтаксис shell","detailUnsupportedGrouping":"синтаксис группировки shell не поддерживается анализатором","detailUnterminatedHeredoc":"терминатор here-document не найден","detectedLabel":"Обнаружено","parseReasonLabel":"Причина разбора","ruleCurlPipe":"Передача загруженного содержимого в конвейер shell","ruleDestructiveSql":"Разрушающий SQL-запрос","ruleDeviceWrite":"Запись в устройство или защищённый файл","ruleEval":"Динамическое выполнение shell (eval)","ruleFindDelete":"find -delete","ruleGitClean":"Разрушающий git clean","ruleGitResetHard":"Разрушающий git reset --hard","ruleInterpreterPayload":"Небезопасный код интерпретатора","ruleMkfsDevice":"Форматирование файловой системы","ruleNestedShell":"Превышен лимит глубины вложенного shell","ruleProcessSignal":"Завершение процесса","ruleRecursiveRm":"Рекурсивное удаление файлов","ruleSecretWrite":"Запись в защищённый файл с секретами","ruleServiceControl":"Изменение состояния службы","ruleUnknown":"Сработало блокирующее правило безопасности"},"dsh-approval-gate":{"approvalRequired":"Требуется подтверждение","askCredentialInArgv":"Раскрытые данные авторизации передаются как аргументы процесса; используйте безопасный хелпер или проверьте перед разрешением","askDynamicArguments":"Раскрытые аргументы чувствительной команды не могут быть проверены; проверьте перед разрешением","askDynamicCommand":"Раскрытое имя команды не может быть проверено; проверьте перед разрешением","askDynamicRedirect":"Раскрытая цель перенаправления не может быть проверена; проверьте перед разрешением","askInterpreterPayload":"Побочные эффекты интерпретатора не проанализированы полностью; проверьте перед разрешением","askScriptFile":"Содержимое файла скрипта недоступно для проверки; проверьте перед разрешением","askShellStdin":"Ввод shell не полностью доступен для проверки; проверьте перед разрешением","askUnknown":"Команда не может быть полностью проверена; проверьте перед разрешением","askUnknownFormat":"Формат вызова инструмента не может быть проверен; проверьте перед разрешением","askUnknownWriteTarget":"Целевой файл для записи не определён; проверьте перед разрешением","askUnparseable":"Синтаксис shell не может быть полностью проверен; проверьте перед разрешением","blockedPrefix":"Заблокировано правилом dsh-approval-gate","commandLabel":"Команда","detailDanglingEscape":"незавершённое экранирование","detailInvalidHeredoc":"недопустимый разделитель here-document","detailProcessSubstitution":"подстановка процесса требует подтверждения","detailUnclosedBacktick":"незакрытая подстановка в обратных кавычках","detailUnclosedCommandSubstitution":"незакрытая подстановка команды","detailUnclosedDoubleQuote":"незакрытая двойная кавычка","detailUnclosedParameterExpansion":"незакрытое раскрытие параметров","detailUnclosedProcessSubstitution":"незакрытая подстановка процесса","detailUnclosedSingleQuote":"незакрытая одинарная кавычка","detailUnsupported":"неподдерживаемый синтаксис shell","detailUnsupportedGrouping":"синтаксис группировки shell не поддерживается анализатором","detailUnterminatedHeredoc":"терминатор here-document не найден","detectedLabel":"Обнаружено","parseReasonLabel":"Причина разбора","ruleCurlPipe":"Передача загруженного содержимого в конвейер shell","ruleDestructiveSql":"Разрушающий SQL-запрос","ruleDeviceWrite":"Запись в устройство или защищённый файл","ruleEval":"Динамическое выполнение shell (eval)","ruleFindDelete":"find -delete","ruleGitClean":"Разрушающий git clean","ruleGitResetHard":"Разрушающий git reset --hard","ruleInterpreterPayload":"Небезопасный код интерпретатора","ruleMkfsDevice":"Форматирование файловой системы","ruleNestedShell":"Превышен лимит глубины вложенного shell","ruleProcessSignal":"Завершение процесса","ruleRecursiveRm":"Рекурсивное удаление файлов","ruleSecretWrite":"Запись в защищённый файл с секретами","ruleServiceControl":"Изменение состояния службы","ruleUnknown":"Сработало блокирующее правило безопасности"}}
package/lib/locales.js CHANGED
@@ -7,6 +7,29 @@ const CORE_FILE = join(__dirname, "locales", "core.json")
7
7
  const PLUGINS_DIR = join(__dirname, "locales", "plugins")
8
8
  const ZH_RU_FILE = join(__dirname, "locales", "zh-ru.json")
9
9
 
10
+ let _pluginCache = null
11
+ let _pluginIndex = null
12
+
13
+ function ensurePluginCache() {
14
+ if (_pluginCache && _pluginIndex) return
15
+ _pluginCache = {}
16
+ _pluginIndex = new Map()
17
+ if (existsSync(PLUGINS_DIR)) {
18
+ for (const file of readdirSync(PLUGINS_DIR).sort()) {
19
+ if (!file.endsWith(".json")) continue
20
+ try {
21
+ const data = JSON.parse(readFileSync(join(PLUGINS_DIR, file), "utf8"))
22
+ for (const [ns, entries] of Object.entries(data)) {
23
+ _pluginCache[ns] = Object.assign(_pluginCache[ns] || {}, entries)
24
+ _pluginIndex.set(ns.toLowerCase(), ns)
25
+ const baseName = ns.replace(/^@[^/]+\//, "")
26
+ _pluginIndex.set(baseName.toLowerCase(), ns)
27
+ }
28
+ } catch (_) { /* bestEffort */ void _; }
29
+ }
30
+ }
31
+ }
32
+
10
33
  export function getCoreDictionaries() {
11
34
  if (existsSync(CORE_FILE)) {
12
35
  try {
@@ -17,19 +40,34 @@ export function getCoreDictionaries() {
17
40
  }
18
41
 
19
42
  export function getPluginDictionaries() {
20
- const merged = {}
21
- if (existsSync(PLUGINS_DIR)) {
22
- for (const file of readdirSync(PLUGINS_DIR).sort()) {
23
- if (!file.endsWith(".json")) continue
24
- try {
25
- const data = JSON.parse(readFileSync(join(PLUGINS_DIR, file), "utf8"))
26
- for (const [ns, entries] of Object.entries(data)) {
27
- merged[ns] = Object.assign(merged[ns] || {}, entries)
28
- }
29
- } catch (_) { /* bestEffort */ void _; }
43
+ ensurePluginCache()
44
+ return Object.assign(
45
+ {},
46
+ _pluginCache
47
+ )
48
+ }
49
+
50
+ export function getPluginDictionariesByNames(names) {
51
+ ensurePluginCache()
52
+ if (!names || (Array.isArray(names) && names.length === 0)) {
53
+ return Object.assign(
54
+ {},
55
+ _pluginCache
56
+ )
57
+ }
58
+ const nameList = Array.isArray(names) ? names : String(names).split(",")
59
+ const result = {}
60
+ for (const rawName of nameList) {
61
+ const trimmed = String(rawName).trim()
62
+ if (!trimmed) continue
63
+ const targetNs = _pluginIndex.get(trimmed.toLowerCase())
64
+ if (targetNs && _pluginCache[targetNs]) {
65
+ result[targetNs] = _pluginCache[targetNs]
66
+ } else if (_pluginCache[trimmed]) {
67
+ result[trimmed] = _pluginCache[trimmed]
30
68
  }
31
69
  }
32
- return merged
70
+ return result
33
71
  }
34
72
 
35
73
  export function getAllDictionaries() {
@@ -41,7 +79,7 @@ export function getAllDictionaries() {
41
79
  export function getZhRuMap() {
42
80
  if (existsSync(ZH_RU_FILE)) {
43
81
  try {
44
- return JSON.parse(readFileSync(ZH_RU_FILE, "utf8"))
82
+ return JSON.parse(readFileSync(ZG_RU_FILE, "utf8"))
45
83
  } catch (_) { /* bestEffort */ void _; }
46
84
  }
47
85
  return {}
package/lib/pure.js CHANGED
@@ -17,7 +17,50 @@
17
17
  // ---------------------------------------------------------------- форматы
18
18
 
19
19
  const numberFormat = new Intl.NumberFormat('ru-RU')
20
+ const compactNumberFormat = new Intl.NumberFormat('ru-RU', { notation: 'compact', compactDisplay: 'short' })
20
21
  const relativeTimeFormat = new Intl.RelativeTimeFormat('ru-RU', { numeric: 'auto' })
22
+
23
+ const datePresetFormats = {
24
+ short: new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' }),
25
+ shortDateTime: new Intl.DateTimeFormat('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }),
26
+ medium: new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }),
27
+ long: new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }),
28
+ time: new Intl.DateTimeFormat('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
29
+ }
30
+
31
+ export const formatDate = (val, style = 'short') => {
32
+ if (val === null || val === undefined || val === '') return ''
33
+ const d = val instanceof Date ? val : new Date(typeof val === 'number' && val < 1e12 ? val * 1000 : val)
34
+ if (isNaN(d.getTime())) return String(val)
35
+ if (typeof style === 'string' && datePresetFormats[style]) {
36
+ return datePresetFormats[style].format(d)
37
+ }
38
+ if (typeof style === 'object' && style !== null) {
39
+ try {
40
+ return new Intl.DateTimeFormat('ru-RU', style).format(d)
41
+ } catch (_) {
42
+ return datePresetFormats.short.format(d)
43
+ }
44
+ }
45
+ return datePresetFormats.short.format(d)
46
+ }
47
+
48
+ export const formatCompactNumber = (val) => {
49
+ if (val === null || val === undefined || val === '') return ''
50
+ const n = typeof val === 'number' ? val : Number(val)
51
+ return isNaN(n) ? String(val) : compactNumberFormat.format(n)
52
+ }
53
+
54
+ export const formatTokens = (count, compact = false) => {
55
+ if (count === null || count === undefined || count === '') return ''
56
+ const n = typeof count === 'number' ? count : Number(count)
57
+ if (isNaN(n)) return String(count)
58
+ const tokenWord = plural(n, ['токен', 'токена', 'токенов'])
59
+ if (compact && n >= 1000) {
60
+ return `${compactNumberFormat.format(n)} токенов`
61
+ }
62
+ return `${numberFormat.format(n)} ${tokenWord}`
63
+ }
21
64
  const currencyFormats = new Map()
22
65
 
23
66
  const getCurrencyFormat = (cur) => {
@@ -119,24 +162,64 @@ export const inflect = (phrase, cName) => {
119
162
 
120
163
  // ------------------------------------------------- подстановка и плюрализация
121
164
 
165
+ const pluralRules = new Intl.PluralRules('ru-RU')
166
+
167
+ /** Русская форма числительного: one | few | many | other. */
168
+ export const pluralForm = (n) => pluralRules.select(n)
169
+
170
+ /**
171
+ * Универсальная русская плюрализация 1, 2-4, 5+
172
+ * @param {number|string} count - Число элементов
173
+ * @param {string[]|string} forms - Массив [one, few, many] или строка первого варианта
174
+ * @param {boolean} [withCount=false] - Включать ли число в результат ("5 задач" vs "задач")
175
+ */
176
+ export function plural(count, forms, withCount = false) {
177
+ const n = typeof count === 'number' ? count : (Number(count) || 0)
178
+ let one = '', few = '', many = ''
179
+ if (Array.isArray(forms)) {
180
+ one = forms[0] || ''
181
+ few = forms[1] || one
182
+ many = forms[2] || few || one
183
+ } else if (typeof forms === 'string') {
184
+ one = forms
185
+ few = arguments[2] || one
186
+ many = arguments[3] || few || one
187
+ withCount = arguments[4] || false
188
+ } else {
189
+ return ''
190
+ }
191
+ const form = pluralRules.select(n)
192
+ let word = many
193
+ if (form === 'one') word = one
194
+ else if (form === 'few') word = few
195
+ else if (form === 'many') word = many
196
+
197
+ return withCount ? `${formatNumber(n)} ${word}` : word
198
+ }
199
+
122
200
  export const fill = (template, params) => {
123
201
  if (!params || typeof params !== 'object') return String(template)
124
- return String(template).replace(/\{(\w+)(?::(\w+))?\}/g, (match, name, spec) => {
202
+ return String(template).replace(/\{(\w+)(?::([^}]+))?\}/g, (match, name, spec) => {
125
203
  if (!(name in params)) return match
126
204
  const val = params[name]
205
+ if (!spec) return String(val)
127
206
  if (spec === 'number') return formatNumber(val)
207
+ if (spec === 'compact') return formatCompactNumber(val)
208
+ if (spec === 'tokens') return formatTokens(val)
209
+ if (spec === 'tokens:compact') return formatTokens(val, true)
128
210
  if (spec === 'reltime') return formatRelativeTime(val)
129
211
  if (spec === 'currency') return formatCurrency(val, params.currency || 'RUB')
212
+ if (spec === 'date') return formatDate(val)
213
+ if (spec.startsWith('date:')) return formatDate(val, spec.slice(5))
214
+ if (spec.startsWith('plural:')) {
215
+ const parts = spec.slice(7).split(',')
216
+ return plural(val, parts)
217
+ }
130
218
  if (INFLECT_CASES.has(spec)) return inflect(String(val), spec)
131
219
  return String(val)
132
220
  })
133
221
  }
134
222
 
135
- const pluralRules = new Intl.PluralRules('ru-RU')
136
-
137
- /** Русская форма числительного: one | few | many | other. */
138
- export const pluralForm = (n) => pluralRules.select(n)
139
-
140
223
  // ------------------------------------------------------ поисковая морфология
141
224
 
142
225
  export const stemRussian = (word) => {
@@ -197,7 +280,13 @@ export const ERROR_MAP = {
197
280
  500: { title: 'Внутренняя ошибка сервера', message: 'На стороне сервера произошел сбой', hint: 'Попробуйте повторить запрос позже или проверьте серверные логи.' },
198
281
  502: { title: 'Ошибочный шлюз (Bad Gateway)', message: 'Промежуточный прокси не получил корректный ответ', hint: 'Проверьте работу нижележащей службы или upstream-сервера.' },
199
282
  503: { title: 'Служба временно недоступна', message: 'Сервер перегружен или находится на обслуживании', hint: 'Попробуйте повторить операцию через некоторое время.' },
200
- 504: { title: 'Шлюз не отвечает (Gateway Timeout)', message: 'Превышено время ожидания ответа от upstream-сервера', hint: 'Попробуйте повторить запрос позже.' }
283
+ 504: { title: 'Шлюз не отвечает (Gateway Timeout)', message: 'Превышено время ожидания ответа от upstream-сервера', hint: 'Попробуйте повторить запрос позже.' },
284
+ FETCH_FAILED: { title: 'Сетевой запрос не удался', message: 'Не удалось связаться с сервером (Failed to fetch)', hint: 'Проверьте сетевое подключение и доступность адреса.' },
285
+ CORS_ERROR: { title: 'Ошибка CORS', message: 'Запрос заблокирован политикой безопасности браузера', hint: 'Проверьте заголовки разрешённых источников на сервере.' },
286
+ WS_CLOSED: { title: 'Разрыв WebSocket', message: 'Связь с сервером по WebSocket прервана', hint: 'Проверьте работу сервера и сетевой статус.' },
287
+ QUOTA_EXCEEDED: { title: 'Исчерпана квота API', message: 'Превышен лимит запросов или баланс провайдера', hint: 'Проверьте баланс API-ключа в кабинете провайдера.' },
288
+ CONTEXT_OVERFLOW: { title: 'Превышен контекст', message: 'Объём диалога превышает размер контекстного окна', hint: 'Очистите или сожмите историю сообщений сессии.' },
289
+ MODEL_NOT_FOUND: { title: 'Модель не найдена', message: 'Указанная модель отсутствует или недоступна', hint: 'Проверьте идентификатор модели в настройках.' }
201
290
  }
202
291
 
203
292
  const fromMap = (key, rawMsg) => ({
@@ -213,9 +302,25 @@ export const humanizeError = (err) => {
213
302
  if (lookupKey && ERROR_MAP[lookupKey]) return fromMap(lookupKey, rawMsg)
214
303
  if (/rate limit|too many requests/i.test(rawMsg)) return fromMap(429, rawMsg)
215
304
  if (/unauthorized|invalid token|invalid api key/i.test(rawMsg)) return fromMap(401, rawMsg)
305
+ if (/failed to fetch|network\s*error|load\s*failed|net::err_/i.test(rawMsg)) return fromMap('FETCH_FAILED', rawMsg)
306
+ if (/cors|cross-origin/i.test(rawMsg)) return fromMap('CORS_ERROR', rawMsg)
307
+ if (/websocket.*(?:closed|failed)/i.test(rawMsg)) return fromMap('WS_CLOSED', rawMsg)
308
+ if (/insufficient_quota|quota\s*exceeded/i.test(rawMsg)) return fromMap('QUOTA_EXCEEDED', rawMsg)
309
+ if (/context.*(?:exceeded|overflow|length)/i.test(rawMsg)) return fromMap('CONTEXT_OVERFLOW', rawMsg)
310
+ if (/model.*not.*found|model.*does not exist/i.test(rawMsg)) return fromMap('MODEL_NOT_FOUND', rawMsg)
216
311
  return { code: 'UNKNOWN', title: 'Ошибка операции', message: rawMsg, hint: 'Проверьте параметры операции и логи.', raw: rawMsg }
217
312
  }
218
313
 
314
+ export const formatErrorToast = (err) => {
315
+ const h = humanizeError(err)
316
+ if (!h) return null
317
+ return {
318
+ type: 'error',
319
+ title: h.title,
320
+ description: h.message + (h.hint ? ' ' + h.hint : '')
321
+ }
322
+ }
323
+
219
324
  // -------------------------------------------------------- отчёт об ошибке
220
325
 
221
326
  /** Версию передаёт вызывающий: в бандл её подставляет build.py из package.json,
@@ -486,20 +591,38 @@ export const SYSTEM_PROMPT_PRESETS = {
486
591
  technical_expert: {
487
592
  id: 'technical_expert',
488
593
  label: 'Технический эксперт',
489
- desc: 'Строгая русская инженерная терминология, чистый код и русские комментарии',
490
- text: 'Отвечай пользователю на русском языке. Используй точную русскую инженерную терминологию, пиши чистый, идиоматичный код с русскими комментариями и следуй лучшим практикам разработки. Если пользователь пишет на другом языке, отвечай на его языке.'
594
+ desc: 'Точная терминология, чистый код и русские комментарии',
595
+ text: 'Отвечай пользователю на русском языке. Используй точную инженерную терминологию, пиши чистый код и русские комментарии.'
491
596
  },
492
597
  tech_writer: {
493
598
  id: 'tech_writer',
494
599
  label: 'Технический писатель',
495
- desc: 'Структурированные тексты, Markdown, ГОСТ/RFC и выверенная типографика',
496
- text: 'Отвечай пользователю на русском языке. Оформляй документацию и пояснения в структурированном виде (Markdown, списки, таблицы), строго соблюдай правила русской типографики («кавычки-ёлочки», длинное тире, буква «ё») и требования к техническим текстам.'
600
+ desc: 'Структурированные тексты, Markdown и выверенная типографика',
601
+ text: 'Отвечай пользователю на русском языке. Оформляй документацию в Markdown, строго соблюдай правила русской типографики («ёлочки», тире, «ё»).'
497
602
  },
498
603
  concise: {
499
604
  id: 'concise',
500
605
  label: 'Лаконичный режим',
501
- desc: 'Краткие ёмкие ответы без лишней вводной воды и повторов',
502
- text: 'Отвечай пользователю на русском языке максимально кратко и ёмко. Без лишних вводных слов, повторов и пространных рассуждений. Сразу переходи к сути, коду или прямому решению задачи.'
606
+ desc: 'Краткие ёмкие ответы без вводных слов и воды',
607
+ text: 'Отвечай пользователю на русском языке максимально кратко и ёмко. Без вводных слов и воды, сразу код или решение.'
608
+ },
609
+ code_reviewer: {
610
+ id: 'code_reviewer',
611
+ label: 'Код-ревьюер',
612
+ desc: 'Поиск багов, безопасность и архитектурный аудит',
613
+ text: 'Анализируй код и отвечай на русском языке. Ищи уязвимости, граничные случаи и нарушения контрактов, предлагая исправления.'
614
+ },
615
+ architect: {
616
+ id: 'architect',
617
+ label: 'Системный архитектор',
618
+ desc: 'Контракты API/CLI, модульность и надёжность',
619
+ text: 'Отвечай на русском языке как архитектор. Проектируй модульные системы, чёткие контракты API/CLI и надёжные решения без лишней сложности.'
620
+ },
621
+ tutor: {
622
+ id: 'tutor',
623
+ label: 'Наставник (ментор)',
624
+ desc: 'Понятные объяснения сложных концепций и пошаговый разбор',
625
+ text: 'Отвечай на русском языке как наставник. Объясняй сложные темы простыми словами, наглядными аналогиями и пошаговыми примерами.'
503
626
  }
504
627
  }
505
628
 
@@ -548,3 +671,118 @@ export const exportSessionToMarkdown = (session, options = {}) => {
548
671
  md += `*Сгенерировано с помощью [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) и @goodandready/dsh-russian-lang*\n`
549
672
  return md
550
673
  }
674
+
675
+ // -------------------------------------------------- инспектор переводов (#298)
676
+
677
+ export const findTranslationKey = (text, dicts = {}, overrides = {}, zhRu = {}) => {
678
+ if (!text || typeof text !== 'string') return null
679
+ const trimmed = text.trim()
680
+ if (!trimmed) return null
681
+
682
+ if (overrides && typeof overrides === 'object') {
683
+ for (const [k, v] of Object.entries(overrides)) {
684
+ if (v === text || (typeof v === 'string' && v.trim() === trimmed)) {
685
+ return { key: k, value: v, source: 'override' }
686
+ }
687
+ }
688
+ }
689
+
690
+ if (dicts && typeof dicts === 'object') {
691
+ for (const [ns, map] of Object.entries(dicts)) {
692
+ if (!map || typeof map !== 'object') continue
693
+ for (const [k, v] of Object.entries(map)) {
694
+ if (v === text || (typeof v === 'string' && v.trim() === trimmed)) {
695
+ return { ns, key: k, value: v, source: 'dictionary' }
696
+ }
697
+ }
698
+ }
699
+ }
700
+
701
+ if (zhRu && typeof zhRu === 'object') {
702
+ for (const [zh, ru] of Object.entries(zhRu)) {
703
+ if (ru === text || (typeof ru === 'string' && ru.trim() === trimmed)) {
704
+ return { zh, key: zh, value: ru, source: 'dom_zh' }
705
+ }
706
+ }
707
+ }
708
+
709
+ return null
710
+ }
711
+
712
+ // ------------------------------------------ двуязычный поиск команд (#300)
713
+
714
+ export const bilingualCommandMatch = (query, command, options = {}) => {
715
+ if (!query || !command) return { matched: false, score: 0 }
716
+ const q = String(query).trim().toLowerCase()
717
+ if (!q) return { matched: false, score: 0 }
718
+
719
+ const cmdObj = typeof command === 'string' ? { title: command } : command
720
+ const candidates = [
721
+ cmdObj.title,
722
+ cmdObj.label,
723
+ cmdObj.name,
724
+ cmdObj.originalTitle,
725
+ cmdObj.enTitle,
726
+ cmdObj.id,
727
+ cmdObj.description,
728
+ cmdObj.category,
729
+ ...(Array.isArray(cmdObj.keywords) ? cmdObj.keywords : [])
730
+ ].filter((c) => typeof c === 'string' && c.trim())
731
+
732
+ if (!candidates.length) return { matched: false, score: 0 }
733
+
734
+ const queryVariants = [{ text: q, kind: 'direct' }]
735
+
736
+ const lat2cyr = translit(q, LAYOUT_LAT_TO_CYR)
737
+ if (lat2cyr !== q) queryVariants.push({ text: lat2cyr, kind: 'layout' })
738
+
739
+ const cyr2lat = translit(q, LAYOUT_CYR_TO_LAT)
740
+ if (cyr2lat !== q) queryVariants.push({ text: cyr2lat, kind: 'layout' })
741
+
742
+ const phonLat2Cyr = phoneticTranslit(q, 'lat2cyr')
743
+ if (phonLat2Cyr && phonLat2Cyr !== q && phonLat2Cyr !== lat2cyr) {
744
+ queryVariants.push({ text: phonLat2Cyr, kind: 'phonetic' })
745
+ }
746
+
747
+ const phonCyr2Lat = phoneticTranslit(q, 'cyr2lat')
748
+ if (phonCyr2Lat && phonCyr2Lat !== q && phonCyr2Lat !== cyr2lat) {
749
+ queryVariants.push({ text: phonCyr2Lat, kind: 'phonetic' })
750
+ }
751
+
752
+ let bestScore = 0
753
+ let bestVariant = null
754
+ let bestCandidate = null
755
+
756
+ for (const variant of queryVariants) {
757
+ for (const cand of candidates) {
758
+ const score = fuzzyMatchRu(variant.text, cand)
759
+ if (score > bestScore) {
760
+ bestScore = score
761
+ bestVariant = variant
762
+ bestCandidate = cand
763
+ }
764
+ }
765
+ }
766
+
767
+ const threshold = options.threshold ?? 50
768
+ return {
769
+ matched: bestScore >= threshold,
770
+ score: bestScore,
771
+ matchedText: bestCandidate,
772
+ variant: bestVariant ? bestVariant.text : q,
773
+ kind: bestVariant ? bestVariant.kind : 'none'
774
+ }
775
+ }
776
+
777
+ export const filterCommands = (query, commands = [], options = {}) => {
778
+ if (!Array.isArray(commands)) return []
779
+ if (!query || !String(query).trim()) return commands
780
+ const results = []
781
+ for (const cmd of commands) {
782
+ const res = bilingualCommandMatch(query, cmd, options)
783
+ if (res.matched) {
784
+ results.push({ item: cmd, ...res })
785
+ }
786
+ }
787
+ return results.sort((a, b) => b.score - a.score).map((r) => r.item)
788
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-russian-lang",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Russian localization for DeepSeek Harness (DSH v0.1.6-alpha.1+) web UI: adds Русский as native language option. 100.0% coverage across 11,295 keys (127 namespaces). Features full DSH v0.1.6-alpha.1 core support (terminal, archive, command palette, Auto review, trajectory, PTC/Creator presets) and complete GoodAndReady plugin catalog (subscriptions, tts, image-gen, voice, key-limits, notify, commandcode, plugin-console).",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -12,6 +12,7 @@
12
12
  },
13
13
  "files": [
14
14
  "lib",
15
+ "bin",
15
16
  "cordis.patch.yml",
16
17
  "README.md",
17
18
  "README.ru.md",
@@ -65,5 +66,8 @@
65
66
  "homepage": "https://github.com/GooDAnDReaDY/dsh-russian-lang#readme",
66
67
  "bugs": {
67
68
  "url": "https://github.com/GooDAnDReaDY/dsh-russian-lang/issues"
69
+ },
70
+ "bin": {
71
+ "dsh-i18n": "./bin/dsh-i18n.mjs"
68
72
  }
69
73
  }