From d33d8d3391f2d86c40f9024f2400bc415466f610 Mon Sep 17 00:00:00 2001 From: lincube Date: Tue, 10 Mar 2026 00:40:26 +0800 Subject: [PATCH] 0.5.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 示例插件 --- LanAirApp/README.md | 1 + .../LanMountainDesktop.SamplePlugin.csproj | 1 + .../Localization/en-US.json | 79 +++++++++ .../Localization/zh-CN.json | 79 +++++++++ .../SamplePlugin.cs | 28 +-- .../SamplePluginRuntimeStatus.cs | 165 ++++++++++++------ .../SamplePluginSettingsView.cs | 91 +++++++--- .../SamplePluginStatusClockWidget.cs | 20 ++- .../plugins/PluginRuntimeService.cs | 9 +- 9 files changed, 376 insertions(+), 97 deletions(-) create mode 100644 LanAirApp/samples/LanMountainDesktop.SamplePlugin/Localization/en-US.json create mode 100644 LanAirApp/samples/LanMountainDesktop.SamplePlugin/Localization/zh-CN.json diff --git a/LanAirApp/README.md b/LanAirApp/README.md index 75e2c53..0af78fb 100644 --- a/LanAirApp/README.md +++ b/LanAirApp/README.md @@ -10,6 +10,7 @@ 目录结构: - `docs/`:插件开发文档、打包文档 +- `releases/`:已经打包完成、可直接分享与安装的 `.laapp` 插件包 - `samples/`:示例插件,其中 `LanMountainDesktop.SamplePlugin` 是示例开发插件 - `standards/`:插件标准文件与模板 - `tools/`:插件打包与构建工具 diff --git a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/LanMountainDesktop.SamplePlugin.csproj b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/LanMountainDesktop.SamplePlugin.csproj index 11a8e77..3856220 100644 --- a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/LanMountainDesktop.SamplePlugin.csproj +++ b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/LanMountainDesktop.SamplePlugin.csproj @@ -17,6 +17,7 @@ + diff --git a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/Localization/en-US.json b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/Localization/en-US.json new file mode 100644 index 0000000..438ea77 --- /dev/null +++ b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/Localization/en-US.json @@ -0,0 +1,79 @@ +{ + "settings.page_title": "Plugin Status", + "plugin.name": "LanMountain Sample Plugin", + "plugin.description": "Example plugin used to validate PluginSdk loading, services, communication, and localization.", + "widget.display_name": "Sample Plugin Status Clock", + "widget.category": "Plugins", + "settings.header.title": "Sample Plugin Capability Inspector", + "settings.section.info": "Plugin Info", + "settings.section.capabilities": "Accessible Capabilities", + "settings.section.status": "Live Runtime Status", + "settings.info.plugin_name": "Plugin Name", + "settings.info.plugin_id": "Plugin Id", + "settings.info.version": "Version", + "settings.info.author": "Author", + "settings.info.description": "Description", + "settings.info.plugin_directory": "Plugin Directory", + "settings.info.data_directory": "Data Directory", + "settings.info.host_application": "Host Application", + "settings.info.host_version": "Host Version", + "settings.info.sdk_api_version": "SDK API Version", + "settings.info.state_service_resolved": "State Service Resolved", + "settings.info.clock_service_resolved": "Clock Service Resolved", + "settings.info.message_bus_resolved": "Message Bus Resolved", + "settings.info.component_placed": "Component Placed", + "settings.info.placed_count": "Placed Count", + "settings.info.preview_count": "Preview Count", + "settings.info.placement_ids": "Placement Ids", + "settings.info.last_component_id": "Last Component Id", + "settings.info.last_cell_size": "Last Cell Size", + "settings.info.clock_service_time": "Clock Service Time", + "settings.status.updated_at": "Updated: {0}", + "status.frontend.title": "Frontend Status", + "status.component.title": "Component Status", + "status.backend.title": "Backend Status", + "status.service.title": "Clock Service", + "status.summary.pending": "Pending", + "status.summary.attached": "Attached", + "status.summary.healthy": "Healthy", + "status.summary.faulted": "Faulted", + "status.summary.placed": "Placed", + "status.summary.preview": "Preview", + "status.frontend.detail.pending": "Waiting for a plugin UI surface to connect.", + "status.frontend.detail.settings_connected": "Settings page is connected to plugin services and communication.", + "status.frontend.detail.widget_connected": "Widget surface is connected to plugin services and communication.", + "status.component.detail.pending": "No component instance has been created yet.", + "status.component.detail.none": "No component instance is active.", + "status.component.detail.preview": "Preview instances: {0}; no placed desktop instance is active yet.", + "status.component.detail.placed": "Placed count: {0}; preview count: {1}; placements: {2}", + "status.backend.detail.pending": "Plugin initialization is in progress.", + "status.backend.detail.log_written": "Initialization log written to: {0}", + "status.backend.detail.log_write_failed": "Initialization log write failed: {0}", + "status.service.detail.pending": "Clock service is not attached yet.", + "status.service.detail.attached": "Clock service was attached and is waiting for the first tick.", + "status.service.detail.running": "Clock service is running. Current service time: {0}", + "status.service.detail.write_failed": "Clock state write failed: {0}", + "capability.manifest.title": "IPluginContext.Manifest", + "capability.manifest.detail": "Readable. Current plugin id: {0}; version: {1}.", + "capability.directories.title": "IPluginContext.PluginDirectory / DataDirectory", + "capability.directories.detail": "Readable. Plugin directory: {0}; data directory: {1}.", + "capability.properties.title": "IPluginContext.Properties", + "capability.properties.detail": "Readable. Host properties currently exposed: {0}.", + "capability.get_service.title": "IPluginContext.GetService()", + "capability.get_service.detail": "Callable. State service resolved: {0}; clock service resolved: {1}; message bus resolved: {2}.", + "capability.register_service.title": "IPluginContext.RegisterService()", + "capability.register_service.detail": "Callable during plugin initialization. This sample plugin registers SamplePluginRuntimeStateService and SamplePluginClockService into the plugin service container.", + "capability.message_bus.title": "Plugin Communication Bus", + "capability.message_bus.detail": "This sample plugin uses IPluginMessageBus to push clock ticks and state change notifications into plugin UI surfaces.", + "capability.widget_context.title": "PluginDesktopComponentContext", + "capability.widget_context.detail": "Widgets can read ComponentId, PlacementId, CellSize, and call GetService() against the same plugin service container.", + "widget.subtitle.preview": "Preview surface | placed: {0}", + "widget.subtitle.placement": "Placement {0} | placed: {1}", + "common.dev": "dev", + "common.none": "(none)", + "common.unknown": "(unknown)", + "common.true": "true", + "common.false": "false", + "common.yes": "Yes", + "common.no": "No" +} diff --git a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/Localization/zh-CN.json b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/Localization/zh-CN.json new file mode 100644 index 0000000..82d3796 --- /dev/null +++ b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/Localization/zh-CN.json @@ -0,0 +1,79 @@ +{ + "settings.page_title": "插件状态", + "plugin.name": "阑山示例插件", + "plugin.description": "用于验证 PluginSdk 加载、服务、通信与本地化能力的示例插件。", + "widget.display_name": "示例插件状态时钟", + "widget.category": "插件", + "settings.header.title": "示例插件能力检查器", + "settings.section.info": "插件信息", + "settings.section.capabilities": "可访问能力", + "settings.section.status": "实时运行状态", + "settings.info.plugin_name": "插件名称", + "settings.info.plugin_id": "插件 Id", + "settings.info.version": "版本", + "settings.info.author": "作者", + "settings.info.description": "描述", + "settings.info.plugin_directory": "插件目录", + "settings.info.data_directory": "数据目录", + "settings.info.host_application": "宿主应用", + "settings.info.host_version": "宿主版本", + "settings.info.sdk_api_version": "SDK API 版本", + "settings.info.state_service_resolved": "状态服务已解析", + "settings.info.clock_service_resolved": "时钟服务已解析", + "settings.info.message_bus_resolved": "消息总线已解析", + "settings.info.component_placed": "组件是否已放置", + "settings.info.placed_count": "已放置数量", + "settings.info.preview_count": "预览数量", + "settings.info.placement_ids": "放置位置 Id", + "settings.info.last_component_id": "最近组件 Id", + "settings.info.last_cell_size": "最近单元尺寸", + "settings.info.clock_service_time": "时钟服务时间", + "settings.status.updated_at": "更新时间:{0}", + "status.frontend.title": "前端状态", + "status.component.title": "组件状态", + "status.backend.title": "后端状态", + "status.service.title": "时钟服务", + "status.summary.pending": "等待中", + "status.summary.attached": "已挂接", + "status.summary.healthy": "正常", + "status.summary.faulted": "异常", + "status.summary.placed": "已放置", + "status.summary.preview": "预览中", + "status.frontend.detail.pending": "等待插件界面接入。", + "status.frontend.detail.settings_connected": "设置页已接入插件服务与通信。", + "status.frontend.detail.widget_connected": "组件界面已接入插件服务与通信。", + "status.component.detail.pending": "当前还没有创建组件实例。", + "status.component.detail.none": "当前没有活动中的组件实例。", + "status.component.detail.preview": "当前预览实例数量:{0};尚未有已放置的桌面实例。", + "status.component.detail.placed": "已放置数量:{0};预览数量:{1};放置位置:{2}", + "status.backend.detail.pending": "插件初始化进行中。", + "status.backend.detail.log_written": "初始化日志已写入:{0}", + "status.backend.detail.log_write_failed": "初始化日志写入失败:{0}", + "status.service.detail.pending": "时钟服务尚未挂接。", + "status.service.detail.attached": "时钟服务已挂接,正在等待第一次心跳。", + "status.service.detail.running": "时钟服务运行中,当前服务时间:{0}", + "status.service.detail.write_failed": "时钟状态写入失败:{0}", + "capability.manifest.title": "IPluginContext.Manifest", + "capability.manifest.detail": "可读取。当前插件 id:{0};版本:{1}。", + "capability.directories.title": "IPluginContext.PluginDirectory / DataDirectory", + "capability.directories.detail": "可读取。插件目录:{0};数据目录:{1}。", + "capability.properties.title": "IPluginContext.Properties", + "capability.properties.detail": "可读取。宿主当前暴露的属性:{0}。", + "capability.get_service.title": "IPluginContext.GetService()", + "capability.get_service.detail": "可调用。状态服务已解析:{0};时钟服务已解析:{1};消息总线已解析:{2}。", + "capability.register_service.title": "IPluginContext.RegisterService()", + "capability.register_service.detail": "可在插件初始化阶段调用。这个示例插件会把 SamplePluginRuntimeStateService 和 SamplePluginClockService 注册进插件服务容器。", + "capability.message_bus.title": "插件通信总线", + "capability.message_bus.detail": "这个示例插件通过 IPluginMessageBus 向插件 UI 推送时钟心跳和状态变化通知。", + "capability.widget_context.title": "PluginDesktopComponentContext", + "capability.widget_context.detail": "组件可以读取 ComponentId、PlacementId、CellSize,并能在同一个插件服务容器上调用 GetService()。", + "widget.subtitle.preview": "预览界面 | 已放置:{0}", + "widget.subtitle.placement": "位置 {0} | 已放置:{1}", + "common.dev": "开发版", + "common.none": "(无)", + "common.unknown": "(未知)", + "common.true": "是", + "common.false": "否", + "common.yes": "是", + "common.no": "否" +} diff --git a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePlugin.cs b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePlugin.cs index d5f70fe..9f57b11 100644 --- a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePlugin.cs +++ b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePlugin.cs @@ -11,10 +11,11 @@ public sealed class SamplePlugin : PluginBase, IDisposable public override void Initialize(IPluginContext context) { Directory.CreateDirectory(context.DataDirectory); + var localizer = PluginLocalizer.Create(context); - var hostName = GetHostProperty(context, "HostApplicationName", "UnknownHost"); - var hostVersion = GetHostProperty(context, "HostVersion", "UnknownVersion"); - var sdkApiVersion = GetHostProperty(context, "PluginSdkApiVersion", "UnknownApiVersion"); + var hostName = GetHostProperty(context, PluginHostPropertyKeys.HostApplicationName, "UnknownHost"); + var hostVersion = GetHostProperty(context, PluginHostPropertyKeys.HostVersion, "UnknownVersion"); + var sdkApiVersion = GetHostProperty(context, PluginHostPropertyKeys.PluginSdkApiVersion, "UnknownApiVersion"); var messageBus = context.GetService() ?? throw new InvalidOperationException("Plugin message bus is not available."); @@ -25,10 +26,11 @@ public sealed class SamplePlugin : PluginBase, IDisposable hostName, hostVersion, sdkApiVersion, - messageBus); + messageBus, + localizer); context.RegisterService(_stateService); - _clockService = new SamplePluginClockService(context.DataDirectory, _stateService, messageBus); + _clockService = new SamplePluginClockService(context.DataDirectory, _stateService, messageBus, localizer); context.RegisterService(_clockService); _stateService.AttachClockService(_clockService); @@ -39,11 +41,17 @@ public sealed class SamplePlugin : PluginBase, IDisposable try { File.AppendAllText(logPath, initMessage + Environment.NewLine); - _stateService.MarkBackendReady($"Initialization log written to {logPath}."); + _stateService.MarkBackendReady(localizer.Format( + "status.backend.detail.log_written", + "初始化日志已写入:{0}", + logPath)); } catch (Exception ex) { - _stateService.MarkBackendFaulted($"Initialization log write failed: {ex.Message}"); + _stateService.MarkBackendFaulted(localizer.Format( + "status.backend.detail.log_write_failed", + "初始化日志写入失败:{0}", + ex.Message)); throw; } @@ -51,15 +59,15 @@ public sealed class SamplePlugin : PluginBase, IDisposable context.RegisterSettingsPage(new PluginSettingsPageRegistration( "status", - "Plugin Status", + localizer.GetString("settings.page_title", "插件状态"), () => new SamplePluginSettingsView(context))); context.RegisterDesktopComponent(new PluginDesktopComponentRegistration( "LanMountainDesktop.SamplePlugin.StatusClock", - "Sample Plugin Status Clock", + localizer.GetString("widget.display_name", "示例插件状态时钟"), widgetContext => new SamplePluginStatusClockWidget(widgetContext), iconKey: "PuzzlePiece", - category: "Plugins", + category: localizer.GetString("widget.category", "插件"), minWidthCells: 4, minHeightCells: 4, allowDesktopPlacement: true, diff --git a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginRuntimeStatus.cs b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginRuntimeStatus.cs index ea5a0e7..29a82d3 100644 --- a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginRuntimeStatus.cs +++ b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginRuntimeStatus.cs @@ -66,6 +66,7 @@ internal sealed class SamplePluginRuntimeStateService private readonly string _hostApplicationName; private readonly string _hostVersion; private readonly string _sdkApiVersion; + private readonly PluginLocalizer _localizer; private SamplePluginStatusEntry _frontend; private SamplePluginStatusEntry _component; @@ -82,7 +83,8 @@ internal sealed class SamplePluginRuntimeStateService string hostApplicationName, string hostVersion, string sdkApiVersion, - IPluginMessageBus messageBus) + IPluginMessageBus messageBus, + PluginLocalizer localizer) { _manifest = manifest; _pluginDirectory = pluginDirectory; @@ -91,34 +93,35 @@ internal sealed class SamplePluginRuntimeStateService _hostVersion = hostVersion; _sdkApiVersion = sdkApiVersion; _messageBus = messageBus; + _localizer = localizer; _frontend = CreateEntry( "frontend", - "Frontend", + T("status.frontend.title", "前端状态"), SamplePluginHealthState.Pending, - "Pending", - "Waiting for a plugin UI surface to connect."); + T("status.summary.pending", "等待中"), + T("status.frontend.detail.pending", "等待插件界面接入。")); _component = CreateEntry( "component", - "Component", + T("status.component.title", "组件状态"), SamplePluginHealthState.Pending, - "Pending", - "No component instance has been created yet."); + T("status.summary.pending", "等待中"), + T("status.component.detail.pending", "当前还没有创建组件实例。")); _backend = CreateEntry( "backend", - "Backend", + T("status.backend.title", "后端状态"), SamplePluginHealthState.Pending, - "Pending", - "Plugin initialization is in progress."); + T("status.summary.pending", "等待中"), + T("status.backend.detail.pending", "插件初始化进行中。")); _service = CreateEntry( "service", - "Clock Service", + T("status.service.title", "时钟服务"), SamplePluginHealthState.Pending, - "Pending", - "Clock service is not attached yet."); + T("status.summary.pending", "等待中"), + T("status.service.detail.pending", "时钟服务尚未挂接。")); } public void AttachClockService(SamplePluginClockService clockService) @@ -130,10 +133,10 @@ internal sealed class SamplePluginRuntimeStateService _serviceClockTime = clockService.CurrentTime; _service = CreateEntry( "service", - "Clock Service", + T("status.service.title", "时钟服务"), SamplePluginHealthState.Pending, - "Attached", - "Clock service was attached and is waiting for the first tick."); + T("status.summary.attached", "已挂接"), + T("status.service.detail.attached", "时钟服务已挂接,正在等待第一次心跳。")); } PublishStateChanged("Clock service attached"); @@ -145,9 +148,9 @@ internal sealed class SamplePluginRuntimeStateService { _frontend = CreateEntry( "frontend", - "Frontend", + T("status.frontend.title", "前端状态"), SamplePluginHealthState.Healthy, - "Healthy", + T("status.summary.healthy", "正常"), detail); } @@ -160,9 +163,9 @@ internal sealed class SamplePluginRuntimeStateService { _backend = CreateEntry( "backend", - "Backend", + T("status.backend.title", "后端状态"), SamplePluginHealthState.Healthy, - "Healthy", + T("status.summary.healthy", "正常"), detail); } @@ -175,9 +178,9 @@ internal sealed class SamplePluginRuntimeStateService { _backend = CreateEntry( "backend", - "Backend", + T("status.backend.title", "后端状态"), SamplePluginHealthState.Faulted, - "Faulted", + T("status.summary.faulted", "异常"), detail); } @@ -191,10 +194,13 @@ internal sealed class SamplePluginRuntimeStateService _serviceClockTime = currentTime; _service = CreateEntry( "service", - "Clock Service", + T("status.service.title", "时钟服务"), SamplePluginHealthState.Healthy, - "Healthy", - $"Clock service is running. Current service time: {currentTime.LocalDateTime:HH:mm:ss}"); + T("status.summary.healthy", "正常"), + Tf( + "status.service.detail.running", + "时钟服务运行中,当前服务时间:{0}", + currentTime.LocalDateTime.ToString("HH:mm:ss"))); } PublishStateChanged("Clock service tick"); @@ -206,9 +212,9 @@ internal sealed class SamplePluginRuntimeStateService { _service = CreateEntry( "service", - "Clock Service", + T("status.service.title", "时钟服务"), SamplePluginHealthState.Faulted, - "Faulted", + T("status.summary.faulted", "异常"), detail); } @@ -291,32 +297,54 @@ internal sealed class SamplePluginRuntimeStateService ArgumentNullException.ThrowIfNull(context); var propertyNames = context.Properties.Count == 0 - ? "(none)" + ? T("common.none", "(无)") : string.Join(", ", context.Properties.Keys.OrderBy(key => key, StringComparer.OrdinalIgnoreCase)); return [ new SamplePluginCapabilityItem( - "IPluginContext.Manifest", - $"Readable. Current plugin id: {context.Manifest.Id}; version: {context.Manifest.Version ?? "dev"}."), + T("capability.manifest.title", "IPluginContext.Manifest"), + Tf( + "capability.manifest.detail", + "可读取。当前插件 id:{0};版本:{1}。", + context.Manifest.Id, + context.Manifest.Version ?? T("common.dev", "开发版"))), new SamplePluginCapabilityItem( - "IPluginContext.PluginDirectory / DataDirectory", - $"Readable. Plugin directory: {context.PluginDirectory}; data directory: {context.DataDirectory}."), + T("capability.directories.title", "IPluginContext.PluginDirectory / DataDirectory"), + Tf( + "capability.directories.detail", + "可读取。插件目录:{0};数据目录:{1}。", + context.PluginDirectory, + context.DataDirectory)), new SamplePluginCapabilityItem( - "IPluginContext.Properties", - $"Readable. Host properties currently exposed: {propertyNames}."), + T("capability.properties.title", "IPluginContext.Properties"), + Tf( + "capability.properties.detail", + "可读取。宿主当前暴露的属性:{0}。", + propertyNames)), new SamplePluginCapabilityItem( - "IPluginContext.GetService()", - $"Callable. State service resolved: {hasStateService}; clock service resolved: {hasClockService}; message bus resolved: {hasMessageBus}."), + T("capability.get_service.title", "IPluginContext.GetService()"), + Tf( + "capability.get_service.detail", + "可调用。状态服务已解析:{0};时钟服务已解析:{1};消息总线已解析:{2}。", + FormatBoolean(hasStateService), + FormatBoolean(hasClockService), + FormatBoolean(hasMessageBus))), new SamplePluginCapabilityItem( - "IPluginContext.RegisterService()", - "Callable during plugin initialization. This plugin registers SamplePluginRuntimeStateService and SamplePluginClockService into the plugin service container."), + T("capability.register_service.title", "IPluginContext.RegisterService()"), + T( + "capability.register_service.detail", + "可在插件初始化阶段调用。这个示例插件会把 SamplePluginRuntimeStateService 和 SamplePluginClockService 注册进插件服务容器。")), new SamplePluginCapabilityItem( - "Plugin communication bus", - "This plugin uses IPluginMessageBus to push clock ticks and state change notifications into plugin UI surfaces."), + T("capability.message_bus.title", "插件通信总线"), + T( + "capability.message_bus.detail", + "这个示例插件通过 IPluginMessageBus 向插件 UI 推送时钟心跳和状态变化通知。")), new SamplePluginCapabilityItem( - "PluginDesktopComponentContext", - "Widgets can read ComponentId, PlacementId, CellSize, and call GetService() against the same plugin service container.") + T("capability.widget_context.title", "PluginDesktopComponentContext"), + T( + "capability.widget_context.detail", + "组件可以读取 ComponentId、PlacementId、CellSize,并能在同一个插件服务容器上调用 GetService()。")) ]; } @@ -335,10 +363,15 @@ internal sealed class SamplePluginRuntimeStateService { _component = CreateEntry( "component", - "Component", + T("status.component.title", "组件状态"), SamplePluginHealthState.Healthy, - "Placed", - $"Placed count: {placementIds.Length}; preview count: {previewCount}; placements: {string.Join(", ", placementIds)}"); + T("status.summary.placed", "已放置"), + Tf( + "status.component.detail.placed", + "已放置数量:{0};预览数量:{1};放置位置:{2}", + placementIds.Length, + previewCount, + string.Join(", ", placementIds))); return; } @@ -346,19 +379,22 @@ internal sealed class SamplePluginRuntimeStateService { _component = CreateEntry( "component", - "Component", + T("status.component.title", "组件状态"), SamplePluginHealthState.Healthy, - "Preview", - $"Preview instances: {previewCount}; no placed desktop instance is active yet."); + T("status.summary.preview", "预览中"), + Tf( + "status.component.detail.preview", + "当前预览实例数量:{0};尚未有已放置的桌面实例。", + previewCount)); return; } _component = CreateEntry( "component", - "Component", + T("status.component.title", "组件状态"), SamplePluginHealthState.Pending, - "Pending", - "No component instance is active."); + T("status.summary.pending", "等待中"), + T("status.component.detail.none", "当前没有活动中的组件实例。")); } private void PublishStateChanged(string reason) @@ -381,6 +417,23 @@ internal sealed class SamplePluginRuntimeStateService detail, DateTimeOffset.Now); } + + private string T(string key, string fallback) + { + return _localizer.GetString(key, fallback); + } + + private string Tf(string key, string fallback, params object[] args) + { + return _localizer.Format(key, fallback, args); + } + + private string FormatBoolean(bool value) + { + return value + ? T("common.true", "是") + : T("common.false", "否"); + } } internal sealed class SamplePluginClockService : IDisposable @@ -389,6 +442,7 @@ internal sealed class SamplePluginClockService : IDisposable private readonly string _clockStateFilePath; private readonly SamplePluginRuntimeStateService _stateService; private readonly IPluginMessageBus _messageBus; + private readonly PluginLocalizer _localizer; private readonly Timer _timer; private DateTimeOffset _currentTime = DateTimeOffset.Now; private int _disposed; @@ -396,11 +450,13 @@ internal sealed class SamplePluginClockService : IDisposable public SamplePluginClockService( string dataDirectory, SamplePluginRuntimeStateService stateService, - IPluginMessageBus messageBus) + IPluginMessageBus messageBus, + PluginLocalizer localizer) { _clockStateFilePath = Path.Combine(dataDirectory, "clock-service.txt"); _stateService = stateService; _messageBus = messageBus; + _localizer = localizer; _timer = new Timer(OnTimerTick); } @@ -459,7 +515,10 @@ internal sealed class SamplePluginClockService : IDisposable } catch (Exception ex) { - _stateService.MarkClockServiceFaulted($"Clock state write failed: {ex.Message}"); + _stateService.MarkClockServiceFaulted(_localizer.Format( + "status.service.detail.write_failed", + "时钟状态写入失败:{0}", + ex.Message)); } } } diff --git a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginSettingsView.cs b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginSettingsView.cs index 75859dd..c1450e4 100644 --- a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginSettingsView.cs +++ b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginSettingsView.cs @@ -10,6 +10,7 @@ namespace LanMountainDesktop.SamplePlugin; internal sealed class SamplePluginSettingsView : UserControl { private readonly IPluginContext _context; + private readonly PluginLocalizer _localizer; private readonly SamplePluginRuntimeStateService _stateService; private readonly SamplePluginClockService _clockService; private readonly IPluginMessageBus _messageBus; @@ -21,6 +22,7 @@ internal sealed class SamplePluginSettingsView : UserControl public SamplePluginSettingsView(IPluginContext context) { _context = context; + _localizer = PluginLocalizer.Create(context); _stateService = context.GetService() ?? throw new InvalidOperationException("SamplePluginRuntimeStateService is not available."); _clockService = context.GetService() @@ -28,7 +30,9 @@ internal sealed class SamplePluginSettingsView : UserControl _messageBus = context.GetService() ?? throw new InvalidOperationException("IPluginMessageBus is not available."); - _stateService.MarkFrontendReady("Settings page is connected to plugin services and communication."); + _stateService.MarkFrontendReady(T( + "status.frontend.detail.settings_connected", + "设置页已接入插件服务与通信。")); AttachedToVisualTree += OnAttachedToVisualTree; DetachedFromVisualTree += OnDetachedFromVisualTree; @@ -56,14 +60,14 @@ internal sealed class SamplePluginSettingsView : UserControl { new TextBlock { - Text = "Sample Plugin Capability Inspector", + Text = T("settings.header.title", "示例插件能力检查器"), FontSize = 22, FontWeight = FontWeight.SemiBold, Foreground = Brushes.White }, - CreateSection("Plugin Info", _pluginInfoPanel), - CreateSection("Accessible Capabilities", _capabilityPanel), - CreateSection("Live Runtime Status", _statusPanel) + CreateSection(T("settings.section.info", "插件信息"), _pluginInfoPanel), + CreateSection(T("settings.section.capabilities", "可访问能力"), _capabilityPanel), + CreateSection(T("settings.section.status", "实时运行状态"), _statusPanel) } } }; @@ -112,31 +116,45 @@ internal sealed class SamplePluginSettingsView : UserControl private void RefreshPluginInfo(SamplePluginRuntimeSnapshot snapshot) { _pluginInfoPanel.Children.Clear(); - _pluginInfoPanel.Children.Add(CreateInfoLine("Plugin Name", snapshot.Manifest.Name)); - _pluginInfoPanel.Children.Add(CreateInfoLine("Plugin Id", snapshot.Manifest.Id)); - _pluginInfoPanel.Children.Add(CreateInfoLine("Version", snapshot.Manifest.Version ?? "dev")); - _pluginInfoPanel.Children.Add(CreateInfoLine("Author", snapshot.Manifest.Author ?? "(none)")); - _pluginInfoPanel.Children.Add(CreateInfoLine("Description", snapshot.Manifest.Description ?? "(none)")); - _pluginInfoPanel.Children.Add(CreateInfoLine("Plugin Directory", snapshot.PluginDirectory)); - _pluginInfoPanel.Children.Add(CreateInfoLine("Data Directory", snapshot.DataDirectory)); - _pluginInfoPanel.Children.Add(CreateInfoLine("Host Application", snapshot.HostApplicationName)); - _pluginInfoPanel.Children.Add(CreateInfoLine("Host Version", snapshot.HostVersion)); - _pluginInfoPanel.Children.Add(CreateInfoLine("SDK API Version", snapshot.SdkApiVersion)); - _pluginInfoPanel.Children.Add(CreateInfoLine("State Service Resolved", (_context.GetService() is not null).ToString())); - _pluginInfoPanel.Children.Add(CreateInfoLine("Clock Service Resolved", (_context.GetService() is not null).ToString())); - _pluginInfoPanel.Children.Add(CreateInfoLine("Message Bus Resolved", (_context.GetService() is not null).ToString())); - _pluginInfoPanel.Children.Add(CreateInfoLine("Component Placed", snapshot.HasPlacedComponent ? "Yes" : "No")); - _pluginInfoPanel.Children.Add(CreateInfoLine("Placed Count", snapshot.PlacedCount.ToString())); - _pluginInfoPanel.Children.Add(CreateInfoLine("Preview Count", snapshot.PreviewCount.ToString())); _pluginInfoPanel.Children.Add(CreateInfoLine( - "Placement Ids", - snapshot.PlacementIds.Count == 0 ? "(none)" : string.Join(", ", snapshot.PlacementIds))); - _pluginInfoPanel.Children.Add(CreateInfoLine("Last Component Id", snapshot.LastComponentId ?? "(none)")); + T("settings.info.plugin_name", "插件名称"), + T("plugin.name", snapshot.Manifest.Name))); + _pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.plugin_id", "插件 Id"), snapshot.Manifest.Id)); + _pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.version", "版本"), snapshot.Manifest.Version ?? T("common.dev", "开发版"))); + _pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.author", "作者"), snapshot.Manifest.Author ?? T("common.none", "(无)"))); _pluginInfoPanel.Children.Add(CreateInfoLine( - "Last Cell Size", - snapshot.LastCellSize > 0 ? $"{snapshot.LastCellSize:F0}px" : "(unknown)")); + T("settings.info.description", "描述"), + T("plugin.description", snapshot.Manifest.Description ?? T("common.none", "(无)")))); + _pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.plugin_directory", "插件目录"), snapshot.PluginDirectory)); + _pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.data_directory", "数据目录"), snapshot.DataDirectory)); + _pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.host_application", "宿主应用"), snapshot.HostApplicationName)); + _pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.host_version", "宿主版本"), snapshot.HostVersion)); + _pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.sdk_api_version", "SDK API 版本"), snapshot.SdkApiVersion)); _pluginInfoPanel.Children.Add(CreateInfoLine( - "Clock Service Time", + T("settings.info.state_service_resolved", "状态服务已解析"), + FormatBoolean(_context.GetService() is not null))); + _pluginInfoPanel.Children.Add(CreateInfoLine( + T("settings.info.clock_service_resolved", "时钟服务已解析"), + FormatBoolean(_context.GetService() is not null))); + _pluginInfoPanel.Children.Add(CreateInfoLine( + T("settings.info.message_bus_resolved", "消息总线已解析"), + FormatBoolean(_context.GetService() is not null))); + _pluginInfoPanel.Children.Add(CreateInfoLine( + T("settings.info.component_placed", "组件是否已放置"), + snapshot.HasPlacedComponent ? T("common.yes", "是") : T("common.no", "否"))); + _pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.placed_count", "已放置数量"), snapshot.PlacedCount.ToString())); + _pluginInfoPanel.Children.Add(CreateInfoLine(T("settings.info.preview_count", "预览数量"), snapshot.PreviewCount.ToString())); + _pluginInfoPanel.Children.Add(CreateInfoLine( + T("settings.info.placement_ids", "放置位置 Id"), + snapshot.PlacementIds.Count == 0 ? T("common.none", "(无)") : string.Join(", ", snapshot.PlacementIds))); + _pluginInfoPanel.Children.Add(CreateInfoLine( + T("settings.info.last_component_id", "最近组件 Id"), + snapshot.LastComponentId ?? T("common.none", "(无)"))); + _pluginInfoPanel.Children.Add(CreateInfoLine( + T("settings.info.last_cell_size", "最近单元尺寸"), + snapshot.LastCellSize > 0 ? $"{snapshot.LastCellSize:F0}px" : T("common.unknown", "(未知)"))); + _pluginInfoPanel.Children.Add(CreateInfoLine( + T("settings.info.clock_service_time", "时钟服务时间"), _clockService.CurrentTime.LocalDateTime.ToString("HH:mm:ss"))); } @@ -183,7 +201,7 @@ internal sealed class SamplePluginSettingsView : UserControl }, new TextBlock { - Text = $"Updated: {entry.UpdatedAt.LocalDateTime:HH:mm:ss}", + Text = Tf("settings.status.updated_at", "更新时间:{0}", entry.UpdatedAt.LocalDateTime.ToString("HH:mm:ss")), Foreground = new SolidColorBrush(Color.Parse("#FF93C5FD")) } } @@ -336,4 +354,21 @@ internal sealed class SamplePluginSettingsView : UserControl Color.Parse("#FBBF24")) }; } + + private string T(string key, string fallback) + { + return _localizer.GetString(key, fallback); + } + + private string Tf(string key, string fallback, params object[] args) + { + return _localizer.Format(key, fallback, args); + } + + private string FormatBoolean(bool value) + { + return value + ? T("common.true", "是") + : T("common.false", "否"); + } } diff --git a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginStatusClockWidget.cs b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginStatusClockWidget.cs index 76c2b6d..5793fda 100644 --- a/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginStatusClockWidget.cs +++ b/LanAirApp/samples/LanMountainDesktop.SamplePlugin/SamplePluginStatusClockWidget.cs @@ -10,6 +10,7 @@ namespace LanMountainDesktop.SamplePlugin; internal sealed class SamplePluginStatusClockWidget : Border { private readonly PluginDesktopComponentContext _context; + private readonly PluginLocalizer _localizer; private readonly SamplePluginRuntimeStateService _stateService; private readonly SamplePluginClockService _clockService; private readonly IPluginMessageBus _messageBus; @@ -23,6 +24,7 @@ internal sealed class SamplePluginStatusClockWidget : Border public SamplePluginStatusClockWidget(PluginDesktopComponentContext context) { _context = context; + _localizer = PluginLocalizer.Create(context); _stateService = context.GetService() ?? throw new InvalidOperationException("SamplePluginRuntimeStateService is not available."); _clockService = context.GetService() @@ -111,7 +113,9 @@ internal sealed class SamplePluginStatusClockWidget : Border _context.CellSize); } - _stateService.MarkFrontendReady("Widget surface is connected to plugin services and communication."); + _stateService.MarkFrontendReady(T( + "status.frontend.detail.widget_connected", + "组件界面已接入插件服务与通信。")); SubscribeToPluginBus(); RefreshClock(_clockService.CurrentTime); @@ -170,8 +174,8 @@ internal sealed class SamplePluginStatusClockWidget : Border { var snapshot = _stateService.GetSnapshot(); _subtitleTextBlock.Text = string.IsNullOrWhiteSpace(_context.PlacementId) - ? $"Preview surface | placed: {snapshot.PlacedCount}" - : $"Placement {_context.PlacementId} | placed: {snapshot.PlacedCount}"; + ? Tf("widget.subtitle.preview", "预览界面 | 已放置:{0}", snapshot.PlacedCount) + : Tf("widget.subtitle.placement", "位置 {0} | 已放置:{1}", _context.PlacementId!, snapshot.PlacedCount); } private void RefreshStatusPanel() @@ -281,4 +285,14 @@ internal sealed class SamplePluginStatusClockWidget : Border Color.Parse("#FDBA74")) }; } + + private string T(string key, string fallback) + { + return _localizer.GetString(key, fallback); + } + + private string Tf(string key, string fallback, params object[] args) + { + return _localizer.Format(key, fallback, args); + } } diff --git a/LanMountainDesktop/plugins/PluginRuntimeService.cs b/LanMountainDesktop/plugins/PluginRuntimeService.cs index 5635e08..46547e3 100644 --- a/LanMountainDesktop/plugins/PluginRuntimeService.cs +++ b/LanMountainDesktop/plugins/PluginRuntimeService.cs @@ -48,11 +48,14 @@ public sealed class PluginRuntimeService : IDisposable UnloadInstalledPlugins(); var disabledPluginIds = GetDisabledPluginIds(); + var settingsSnapshot = _appSettingsService.Load(); + var hostLanguageCode = PluginLocalizer.NormalizeLanguageCode(settingsSnapshot.LanguageCode); var hostProperties = new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["HostApplicationName"] = "LanMountainDesktop", - ["HostVersion"] = typeof(App).Assembly.GetName().Version?.ToString(), - ["PluginSdkApiVersion"] = PluginSdkInfo.ApiVersion + [PluginHostPropertyKeys.HostApplicationName] = "LanMountainDesktop", + [PluginHostPropertyKeys.HostVersion] = typeof(App).Assembly.GetName().Version?.ToString(), + [PluginHostPropertyKeys.PluginSdkApiVersion] = PluginSdkInfo.ApiVersion, + [PluginHostPropertyKeys.HostLanguageCode] = hostLanguageCode }; var discoveryFailures = new List();