diff --git a/astrbot/builtin_stars/astrbot/.astrbot-plugin/i18n/en-US.json b/astrbot/builtin_stars/astrbot/.astrbot-plugin/i18n/en-US.json new file mode 100644 index 000000000..eb5202b42 --- /dev/null +++ b/astrbot/builtin_stars/astrbot/.astrbot-plugin/i18n/en-US.json @@ -0,0 +1,6 @@ +{ + "metadata": { + "display_name": "AstrBot", + "desc": "AstrBot's internal plugin, providing some basic capabilities." + } +} diff --git a/astrbot/builtin_stars/astrbot/.astrbot-plugin/i18n/zh-CN.json b/astrbot/builtin_stars/astrbot/.astrbot-plugin/i18n/zh-CN.json new file mode 100644 index 000000000..0438444a3 --- /dev/null +++ b/astrbot/builtin_stars/astrbot/.astrbot-plugin/i18n/zh-CN.json @@ -0,0 +1,6 @@ +{ + "metadata": { + "display_name": "AstrBot", + "desc": "AstrBot 的内部插件,提供一些基础能力。" + } +} diff --git a/astrbot/builtin_stars/builtin_commands/.astrbot-plugin/i18n/en-US.json b/astrbot/builtin_stars/builtin_commands/.astrbot-plugin/i18n/en-US.json new file mode 100644 index 000000000..f0afe53f0 --- /dev/null +++ b/astrbot/builtin_stars/builtin_commands/.astrbot-plugin/i18n/en-US.json @@ -0,0 +1,6 @@ +{ + "metadata": { + "display_name": "Built-in Commands", + "desc": "AstrBot's internal plugin, providing built-in commands such as /reset, /help, and /sid." + } +} diff --git a/astrbot/builtin_stars/builtin_commands/.astrbot-plugin/i18n/zh-CN.json b/astrbot/builtin_stars/builtin_commands/.astrbot-plugin/i18n/zh-CN.json new file mode 100644 index 000000000..3e2be6cce --- /dev/null +++ b/astrbot/builtin_stars/builtin_commands/.astrbot-plugin/i18n/zh-CN.json @@ -0,0 +1,6 @@ +{ + "metadata": { + "display_name": "内置指令", + "desc": "AstrBot 自带插件,提供 /reset、/help、/sid 等内置指令。" + } +} diff --git a/astrbot/core/star/star.py b/astrbot/core/star/star.py index 8cebbd772..0c6f2bc85 100644 --- a/astrbot/core/star/star.py +++ b/astrbot/core/star/star.py @@ -67,6 +67,9 @@ class StarMetadata: astrbot_version: str | None = None """插件要求的 AstrBot 版本范围(PEP 440 specifier,如 >=4.13.0,<4.17.0)""" + i18n: dict[str, dict] = field(default_factory=dict) + """插件自带的国际化文案,按 locale 分组。""" + def __str__(self) -> str: return f"Plugin {self.name} ({self.version}) by {self.author}: {self.desc}" diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index dcaed8eba..9420448a8 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -13,6 +13,7 @@ import tempfile import traceback from dataclasses import dataclass from enum import Enum, auto +from pathlib import Path from types import ModuleType import yaml @@ -513,10 +514,49 @@ class PluginManager: if isinstance(metadata.get("astrbot_version"), str) else None ), + i18n=PluginManager._load_plugin_i18n(plugin_path), ) return metadata + @staticmethod + def _load_plugin_i18n(plugin_path: str) -> dict[str, dict]: + plugin_root = Path(plugin_path) + i18n_dir = plugin_root / ".astrbot-plugin" / "i18n" + if not i18n_dir.is_dir(): + return {} + + translations: dict[str, dict] = {} + try: + for file_path in i18n_dir.iterdir(): + if file_path.suffix.lower() != ".json": + continue + locale = file_path.stem + if not locale or len(locale) > 32: + continue + if not file_path.is_file(): + continue + if file_path.stat().st_size > 1024 * 1024: + logger.warning("插件 i18n 文件超过 1MB,已跳过: %s", file_path) + continue + + try: + with file_path.open(encoding="utf-8") as f: + locale_data = json.load(f) + if isinstance(locale_data, dict): + translations[locale] = locale_data + else: + logger.warning( + "插件 i18n 文件内容不是 JSON object,已跳过: %s", + file_path, + ) + except Exception as exc: + logger.warning("加载插件 i18n 文件失败 %s: %s", file_path, exc) + except OSError as exc: + logger.warning("读取插件 i18n 目录失败 %s: %s", i18n_dir, exc) + + return translations + @staticmethod def _normalize_plugin_dir_name(plugin_name: str) -> str: return plugin_name.strip() @@ -942,6 +982,7 @@ class PluginManager: metadata.display_name = metadata_yaml.display_name metadata.support_platforms = metadata_yaml.support_platforms metadata.astrbot_version = metadata_yaml.astrbot_version + metadata.i18n = metadata_yaml.i18n except Exception as e: logger.warning( f"插件 {root_dir_name} 元数据载入失败: {e!s}。使用默认元数据。", diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index bcd7e075c..c82b8088d 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -1498,7 +1498,7 @@ class ConfigRoute(Route): } async def _get_plugin_config(self, plugin_name: str): - ret: dict = {"metadata": None, "config": None} + ret: dict = {"metadata": None, "config": None, "i18n": {}} for plugin_md in star_registry: if plugin_md.name == plugin_name: @@ -1514,6 +1514,7 @@ class ConfigRoute(Route): "items": plugin_md.config.schema, # 初始化时通过 __setattr__ 存入了 schema }, } + ret["i18n"] = plugin_md.i18n break return ret diff --git a/astrbot/dashboard/routes/plugin.py b/astrbot/dashboard/routes/plugin.py index 0d289f158..dc54c2db7 100644 --- a/astrbot/dashboard/routes/plugin.py +++ b/astrbot/dashboard/routes/plugin.py @@ -409,6 +409,7 @@ class PluginRoute(Route): "support_platforms": plugin.support_platforms, "astrbot_version": plugin.astrbot_version, "installed_at": self._get_plugin_installed_at(plugin), + "i18n": plugin.i18n, } # 检查是否为全空的幽灵插件 if not any( diff --git a/dashboard/src/components/extension/MarketPluginCard.vue b/dashboard/src/components/extension/MarketPluginCard.vue index 445f07b8c..fe5c1d45d 100644 --- a/dashboard/src/components/extension/MarketPluginCard.vue +++ b/dashboard/src/components/extension/MarketPluginCard.vue @@ -40,6 +40,7 @@ const handleInstall = (plugin) => { - diff --git a/dashboard/src/components/shared/PluginSetSelector.vue b/dashboard/src/components/shared/PluginSetSelector.vue index 78d1b53c3..2eeda6db7 100644 --- a/dashboard/src/components/shared/PluginSetSelector.vue +++ b/dashboard/src/components/shared/PluginSetSelector.vue @@ -66,9 +66,9 @@ > - {{ plugin.name }} + {{ pluginDisplayName(plugin) }} - {{ plugin.desc || tm('pluginSetSelector.noDescription') }} + {{ pluginDescription(plugin) || tm('pluginSetSelector.noDescription') }} {{ tm('pluginSetSelector.notActivated') }} @@ -105,6 +105,7 @@ import { ref, computed, watch } from 'vue' import axios from 'axios' import { useModuleI18n } from '@/i18n/composables' +import { usePluginI18n } from '@/utils/pluginI18n' const props = defineProps({ modelValue: { @@ -123,6 +124,7 @@ const props = defineProps({ const emit = defineEmits(['update:modelValue']) const { tm } = useModuleI18n('core.shared') +const { pluginName, pluginDesc } = usePluginI18n() const dialog = ref(false) const pluginList = ref([]) @@ -130,6 +132,9 @@ const loading = ref(false) const selectionMode = ref('custom') // 'all', 'none', 'custom' const selectedPlugins = ref([]) +const pluginDisplayName = (plugin) => pluginName(plugin) || plugin.name +const pluginDescription = (plugin) => pluginDesc(plugin) + // 判断是否为"所有插件"模式 const isAllPlugins = computed(() => { return props.modelValue && props.modelValue.length === 1 && props.modelValue[0] === '*' diff --git a/dashboard/src/components/shared/TemplateListEditor.vue b/dashboard/src/components/shared/TemplateListEditor.vue index 9cc49d9a9..9b4b4c183 100644 --- a/dashboard/src/components/shared/TemplateListEditor.vue +++ b/dashboard/src/components/shared/TemplateListEditor.vue @@ -19,8 +19,8 @@ :key="option.value" @click="addEntry(option.value)" > - {{ translateIfKey(option.label) }} - {{ translateIfKey(option.hint) }} + {{ option.label }} + {{ option.hint }} @@ -58,7 +58,7 @@
{{ templateLabel(entry.__template_key) }} - {{ translateIfKey(getTemplate(entry)?.hint || getTemplate(entry)?.description) }} + {{ templateText(entry.__template_key, 'hint', getTemplate(entry)?.hint || getTemplate(entry)?.description) }}
@@ -82,10 +82,10 @@ >
- {{ translateIfKey(itemMeta?.description) || itemKey }} + {{ templateItemText(entry.__template_key, itemKey, 'description', itemMeta?.description) || itemKey }} - {{ translateIfKey(itemMeta.hint) }} + {{ templateItemText(entry.__template_key, itemKey, 'hint', itemMeta.hint) }}
@@ -94,10 +94,10 @@ - {{ translateIfKey(childMeta?.description) || childKey }} + {{ templateItemText(entry.__template_key, `${itemKey}.${childKey}`, 'description', childMeta?.description) || childKey }} - {{ translateIfKey(childMeta?.hint) }} + {{ templateItemText(entry.__template_key, `${itemKey}.${childKey}`, 'hint', childMeta?.hint) }} @@ -105,6 +105,9 @@ @@ -122,11 +125,11 @@ - {{ translateIfKey(itemMeta?.description) }} ({{ itemKey }}) + {{ templateItemText(entry.__template_key, itemKey, 'description', itemMeta?.description) }} ({{ itemKey }}) {{ itemKey }} - {{ translateIfKey(itemMeta?.hint) }} + {{ templateItemText(entry.__template_key, itemKey, 'hint', itemMeta?.hint) }} @@ -134,6 +137,9 @@ @@ -153,7 +159,8 @@