Repository navigation
feat(plugin): support localized plugin READMEs with a dashboard switcher - #10613
mjy1113451 wants to merge 1 commit into
Conversation
Plugins can declare readmes in metadata.yaml. The dashboard resolves the requested locale (exact, then base language, then default), validates that declared paths stay inside the plugin directory, and shows a language selector when more than one README exists.
3d43641 to
6e02a77
Compare
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="dashboard/src/views/extension/PluginDetailPage.vue" line_range="761" />
<code_context>
+ readmeLocale.value || locale.value,
+ );
+ // Ignore slow responses that belong to an older locale selection.
+ if (requestId !== readmeRequestId) return;
if (res.data.status !== "ok") {
</code_context>
<issue_to_address>
**Stale plugin README replaces current selection**
When plugin A's detail request is still pending when the user switches to plugin B, and A's request completes after B's detail request, a delayed `fetchPluginDetail` for plugin A can finish after the user selects plugin B, then its watcher continuation calls `fetchReadme` with A's detail and assigns that read to the newest `readmeRequestId`; the guard accepts it, so A's README appears on B's page.
Cancel or identity-check each detail request and its continuation before assigning `pluginDetail` or starting a README fetch.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and the README endpoint now serves files selected through plugin metadata, so a defect in the path-confinement checks could let a caller with plugin scope read Markdown files outside the plugin directory. Reverting would stop future reads, but any data already returned cannot be undisclosed.
Blocking findings: dashboard/src/views/extension/PluginDetailPage.vue:761
| readmeLocale.value || locale.value, | ||
| ); | ||
| // Ignore slow responses that belong to an older locale selection. | ||
| if (requestId !== readmeRequestId) return; |
There was a problem hiding this comment.
🟡 Medium · Stale plugin README replaces current selection
When plugin A's detail request is still pending when the user switches to plugin B, and A's request completes after B's detail request, a delayed fetchPluginDetail for plugin A can finish after the user selects plugin B, then its watcher continuation calls fetchReadme with A's detail and assigns that read to the newest readmeRequestId; the guard accepts it, so A's README appears on B's page.
Cancel or identity-check each detail request and its continuation before assigning pluginDetail or starting a README fetch.
Prompt for AI agents
In `dashboard/src/views/extension/PluginDetailPage.vue` at line 761:
**Stale plugin README replaces current selection**
When plugin A's detail request is still pending when the user switches to plugin B, and A's request completes after B's detail request, a delayed `fetchPluginDetail` for plugin A can finish after the user selects plugin B, then its watcher continuation calls `fetchReadme` with A's detail and assigns that read to the newest `readmeRequestId`; the guard accepts it, so A's README appears on B's page.
Cancel or identity-check each detail request and its continuation before assigning `pluginDetail` or starting a README fetch.
Motivation
Closes #10593.
Following the analysis in the issue, the two halves of the request are handled differently:
.astrbot-plugin/i18n/<locale>.jsonandAstrBotConfig. No second language switch was added there, so there is still exactly one source of truth for config strings.get_plugin_readme()always readREADME.md). This PR adds the declaration, the server-side resolution and the dashboard switcher.What changed
Plugin contract
metadata.yamlmay declare README variants:StarMetadata.readmesstores the normalized mapping; invalid entries are dropped at load time so a typo cannot break plugin loading.Resolution
PluginService.get_plugin_readme(plugin_name, locale)resolves in this order:zh-CN),zh-TW→zh),default(orREADME.mdwhen nodefaultis declared).Every declared path must be a relative Markdown file inside the plugin directory:
.., absolute paths, non-Markdown files and symlinks that resolve outside the plugin root are rejected, and a missing variant falls back todefault. Plugins withoutreadmeskeep the exact legacy behavior.The response now also returns
resolved_localeandavailable_locales(additive,contentunchanged).API and dashboard
GET /api/v1/plugins/readme?plugin_id=...&locale=...(the route the dashboard uses) andGET /api/v1/plugins/{plugin_id}/readme?locale=...; the legacy/api/plugin/readmealso forwardslocale. OpenAPI spec updated and the generated client regenerated.PluginDetailPage.vueshows a language select only when the plugin declares more than one README. The default option follows the WebUI locale; picking a variant overrides it for the current detail page. Locale changes are part of the refresh dependencies and responses are guarded by a request id so a slow older response cannot overwrite a newer one.Verification
ruff format --check/ruff checkclean on all touched Python files.python -m py_compileon touched files.pnpm run i18n:checkandpnpm run build(vue-tsc + vite) pass.tests/test_plugin_readme_locale.pycovers: legacy single README, exact locale, language fallback (zh-TW→zh), unknown locale → default, missing variant → default,../escape, absolute path, non-Markdown file, symlink escape, and locale normalization.Notes
readme_urlas before; per-locale marketplace URLs are a separate data-contract change on the marketplace side and are intentionally not included here.Summary by Sourcery
Support locale-aware plugin README delivery and dashboard selection while preserving compatibility with existing plugins.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests: