From a0c9cb0039e95beee5daf99db33c086d6d800e6a Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 26 Feb 2026 14:34:24 +0300 Subject: [PATCH] Add Chinese locale, fix audio device duplicates, remove display lock restriction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Simplified Chinese (中文) locale with all 906 translation keys - Fix duplicate audio devices: WASAPI two-pass enumerate avoids double-listing loopback endpoints; cross-engine dedup by (name, is_loopback) prefers higher-priority engine - Remove redundant display lock check from capture template, picture source, and postprocessing template test endpoints — screen capture is read-only and concurrent access is safe Co-Authored-By: Claude Opus 4.6 --- README.md | 4 +- .../api/routes/picture_sources.py | 19 - .../api/routes/postprocessing.py | 19 - .../wled_controller/api/routes/templates.py | 24 - .../core/audio/audio_capture.py | 16 +- .../core/audio/wasapi_engine.py | 62 +- .../wled_controller/static/js/core/i18n.js | 3 +- .../wled_controller/static/locales/zh.json | 915 ++++++++++++++++++ .../src/wled_controller/templates/index.html | 1 + 9 files changed, 977 insertions(+), 86 deletions(-) create mode 100644 server/src/wled_controller/static/locales/zh.json diff --git a/README.md b/README.md index a102965..ec9cb03 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ A Home Assistant integration exposes devices as entities for smart home automati - Visual calibration editor with overlay preview - Live LED strip preview via WebSocket - Real-time FPS, latency, and uptime charts -- Localized in English and Russian +- Localized in English, Russian, and Chinese ### Home Assistant Integration @@ -113,7 +113,7 @@ wled-screen-controller/ │ │ │ ├── js/core/ # API client, state, i18n, modals, events │ │ │ ├── js/features/ # Feature modules (devices, streams, targets, etc.) │ │ │ ├── css/ # Stylesheets -│ │ │ └── locales/ # en.json, ru.json +│ │ │ └── locales/ # en.json, ru.json, zh.json │ │ └── utils/ # Logging, monitor detection │ ├── config/ # default_config.yaml │ ├── tests/ # pytest suite diff --git a/server/src/wled_controller/api/routes/picture_sources.py b/server/src/wled_controller/api/routes/picture_sources.py index 9dbab7d..c2daa16 100644 --- a/server/src/wled_controller/api/routes/picture_sources.py +++ b/server/src/wled_controller/api/routes/picture_sources.py @@ -12,11 +12,9 @@ from fastapi.responses import Response from wled_controller.api.auth import AuthRequired from wled_controller.api.dependencies import ( - get_device_store, get_picture_source_store, get_picture_target_store, get_pp_template_store, - get_processor_manager, get_template_store, ) from wled_controller.api.schemas.common import ( @@ -35,8 +33,6 @@ from wled_controller.api.schemas.picture_sources import ( ) from wled_controller.core.capture_engines import EngineRegistry from wled_controller.core.filters import FilterRegistry, ImagePool -from wled_controller.core.processing.processor_manager import ProcessorManager -from wled_controller.storage import DeviceStore from wled_controller.storage.picture_target_store import PictureTargetStore from wled_controller.storage.template_store import TemplateStore from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore @@ -288,8 +284,6 @@ async def test_picture_source( _auth: AuthRequired, store: PictureSourceStore = Depends(get_picture_source_store), template_store: TemplateStore = Depends(get_template_store), - processor_manager: ProcessorManager = Depends(get_processor_manager), - device_store: DeviceStore = Depends(get_device_store), pp_store: PostprocessingTemplateStore = Depends(get_pp_template_store), ): """Test a picture source by resolving its chain and running a capture test. @@ -349,19 +343,6 @@ async def test_picture_source( detail=f"Engine '{capture_template.engine_type}' is not available on this system", ) - locked_device_id = processor_manager.get_display_lock_info(display_index) - if locked_device_id: - try: - device = device_store.get_device(locked_device_id) - device_name = device.name - except Exception: - device_name = locked_device_id - raise HTTPException( - status_code=409, - detail=f"Display {display_index} is currently being captured by device '{device_name}'. " - f"Please stop the device processing before testing.", - ) - stream = EngineRegistry.create_stream( capture_template.engine_type, display_index, capture_template.engine_config ) diff --git a/server/src/wled_controller/api/routes/postprocessing.py b/server/src/wled_controller/api/routes/postprocessing.py index 3c9cde5..f189e51 100644 --- a/server/src/wled_controller/api/routes/postprocessing.py +++ b/server/src/wled_controller/api/routes/postprocessing.py @@ -11,10 +11,8 @@ from fastapi import APIRouter, HTTPException, Depends from wled_controller.api.auth import AuthRequired from wled_controller.api.dependencies import ( - get_device_store, get_picture_source_store, get_pp_template_store, - get_processor_manager, get_template_store, ) from wled_controller.api.schemas.common import ( @@ -32,8 +30,6 @@ from wled_controller.api.schemas.postprocessing import ( ) from wled_controller.core.capture_engines import EngineRegistry from wled_controller.core.filters import FilterRegistry, FilterInstance, ImagePool -from wled_controller.core.processing.processor_manager import ProcessorManager -from wled_controller.storage import DeviceStore from wled_controller.storage.template_store import TemplateStore from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore from wled_controller.storage.picture_source_store import PictureSourceStore @@ -168,8 +164,6 @@ async def test_pp_template( pp_store: PostprocessingTemplateStore = Depends(get_pp_template_store), stream_store: PictureSourceStore = Depends(get_picture_source_store), template_store: TemplateStore = Depends(get_template_store), - processor_manager: ProcessorManager = Depends(get_processor_manager), - device_store: DeviceStore = Depends(get_device_store), ): """Test a postprocessing template by capturing from a source stream and applying filters.""" stream = None @@ -227,19 +221,6 @@ async def test_pp_template( detail=f"Engine '{capture_template.engine_type}' is not available on this system", ) - locked_device_id = processor_manager.get_display_lock_info(display_index) - if locked_device_id: - try: - device = device_store.get_device(locked_device_id) - device_name = device.name - except Exception: - device_name = locked_device_id - raise HTTPException( - status_code=409, - detail=f"Display {display_index} is currently being captured by device '{device_name}'. " - f"Please stop the device processing before testing.", - ) - stream = EngineRegistry.create_stream( capture_template.engine_type, display_index, capture_template.engine_config ) diff --git a/server/src/wled_controller/api/routes/templates.py b/server/src/wled_controller/api/routes/templates.py index 791717f..ae3c0b8 100644 --- a/server/src/wled_controller/api/routes/templates.py +++ b/server/src/wled_controller/api/routes/templates.py @@ -10,10 +10,8 @@ from fastapi import APIRouter, HTTPException, Depends from wled_controller.api.auth import AuthRequired from wled_controller.api.dependencies import ( - get_device_store, get_picture_source_store, get_pp_template_store, - get_processor_manager, get_template_store, ) from wled_controller.api.schemas.common import ( @@ -37,8 +35,6 @@ from wled_controller.api.schemas.filters import ( ) from wled_controller.core.capture_engines import EngineRegistry from wled_controller.core.filters import FilterRegistry -from wled_controller.core.processing.processor_manager import ProcessorManager -from wled_controller.storage import DeviceStore from wled_controller.storage.template_store import TemplateStore from wled_controller.storage.picture_source_store import PictureSourceStore from wled_controller.storage.picture_source import ScreenCapturePictureSource @@ -246,8 +242,6 @@ async def list_engines(_auth: AuthRequired): def test_template( test_request: TemplateTestRequest, _auth: AuthRequired, - processor_manager: ProcessorManager = Depends(get_processor_manager), - device_store: DeviceStore = Depends(get_device_store), ): """Test a capture template configuration. @@ -267,24 +261,6 @@ def test_template( detail=f"Engine '{test_request.engine_type}' is not available on this system" ) - # Check if display is already being captured - locked_device_id = processor_manager.get_display_lock_info(test_request.display_index) - if locked_device_id: - # Get device info for better error message - try: - device = device_store.get_device(locked_device_id) - device_name = device.name - except Exception: - device_name = locked_device_id - - raise HTTPException( - status_code=409, - detail=( - f"Display {test_request.display_index} is currently being captured by device " - f"'{device_name}'. Please stop the device processing before testing this template." - ) - ) - # Create and initialize capture stream stream = EngineRegistry.create_stream( test_request.engine_type, test_request.display_index, test_request.engine_config diff --git a/server/src/wled_controller/core/audio/audio_capture.py b/server/src/wled_controller/core/audio/audio_capture.py index 39197ec..92d347e 100644 --- a/server/src/wled_controller/core/audio/audio_capture.py +++ b/server/src/wled_controller/core/audio/audio_capture.py @@ -253,13 +253,27 @@ class AudioCaptureManager: """List available audio devices from all registered engines. Returns list of dicts with device info, each tagged with engine_type. + Deduplicates by (name, is_loopback), keeping the entry from the + highest-priority engine. """ + # Collect from all engines, sorted by descending priority + engines = [ + (engine_class.ENGINE_PRIORITY, engine_type, engine_class) + for engine_type, engine_class in AudioEngineRegistry.get_all_engines().items() + ] + engines.sort(key=lambda x: x[0], reverse=True) + + seen: set = set() result = [] - for engine_type, engine_class in AudioEngineRegistry.get_all_engines().items(): + for _priority, engine_type, engine_class in engines: try: if not engine_class.is_available(): continue for dev in engine_class.enumerate_devices(): + key = (dev.name, dev.is_loopback) + if key in seen: + continue + seen.add(key) result.append({ "index": dev.index, "name": dev.name, diff --git a/server/src/wled_controller/core/audio/wasapi_engine.py b/server/src/wled_controller/core/audio/wasapi_engine.py index c5ab61c..cf1f780 100644 --- a/server/src/wled_controller/core/audio/wasapi_engine.py +++ b/server/src/wled_controller/core/audio/wasapi_engine.py @@ -163,34 +163,56 @@ class WasapiEngine(AudioCaptureEngine): wasapi_idx = wasapi_info["index"] result = [] + loopback_names: set = set() device_count = pa.get_device_count() + + # First pass: collect input devices. PyAudioWPatch creates + # dedicated loopback input endpoints for output devices; these + # show up as input devices whose name already contains + # "[Loopback]". We mark them as loopback and remember the name + # so the second pass won't duplicate them. for i in range(device_count): dev = pa.get_device_info_by_index(i) if dev["hostApi"] != wasapi_idx: continue + if dev["maxInputChannels"] <= 0: + continue - is_input = dev["maxInputChannels"] > 0 - is_output = dev["maxOutputChannels"] > 0 + name = dev["name"] + is_loopback = "[Loopback]" in name + if is_loopback: + loopback_names.add(name) - if is_input: - result.append(AudioDeviceInfo( - index=i, - name=dev["name"], - is_input=True, - is_loopback=False, - channels=dev["maxInputChannels"], - default_samplerate=dev["defaultSampleRate"], - )) + result.append(AudioDeviceInfo( + index=i, + name=name, + is_input=True, + is_loopback=is_loopback, + channels=dev["maxInputChannels"], + default_samplerate=dev["defaultSampleRate"], + )) - if is_output: - result.append(AudioDeviceInfo( - index=i, - name=f"{dev['name']} [Loopback]", - is_input=False, - is_loopback=True, - channels=dev["maxOutputChannels"], - default_samplerate=dev["defaultSampleRate"], - )) + # Second pass: add loopback entries for output devices that + # don't already have a dedicated loopback input endpoint. + for i in range(device_count): + dev = pa.get_device_info_by_index(i) + if dev["hostApi"] != wasapi_idx: + continue + if dev["maxOutputChannels"] <= 0: + continue + + loopback_name = f"{dev['name']} [Loopback]" + if loopback_name in loopback_names: + continue # already covered by a dedicated loopback endpoint + + result.append(AudioDeviceInfo( + index=i, + name=loopback_name, + is_input=False, + is_loopback=True, + channels=dev["maxOutputChannels"], + default_samplerate=dev["defaultSampleRate"], + )) return result diff --git a/server/src/wled_controller/static/js/core/i18n.js b/server/src/wled_controller/static/js/core/i18n.js index 88454c9..d8bca17 100644 --- a/server/src/wled_controller/static/js/core/i18n.js +++ b/server/src/wled_controller/static/js/core/i18n.js @@ -8,7 +8,8 @@ let _initialized = false; const supportedLocales = { 'en': 'English', - 'ru': 'Русский' + 'ru': 'Русский', + 'zh': '中文' }; const fallbackTranslations = { diff --git a/server/src/wled_controller/static/locales/zh.json b/server/src/wled_controller/static/locales/zh.json new file mode 100644 index 0000000..86de631 --- /dev/null +++ b/server/src/wled_controller/static/locales/zh.json @@ -0,0 +1,915 @@ +{ + "app.title": "LED Grab", + "app.version": "版本:", + "app.api_docs": "API 文档", + "theme.toggle": "切换主题", + "locale.change": "切换语言", + "auth.login": "登录", + "auth.logout": "退出", + "auth.authenticated": "● 已认证", + "auth.title": "登录 LED Grab", + "auth.message": "请输入 API 密钥以进行身份验证并访问 LED Grab。", + "auth.label": "API 密钥:", + "auth.placeholder": "输入您的 API 密钥...", + "auth.hint": "API 密钥将安全存储在浏览器的本地存储中。", + "auth.button.cancel": "取消", + "auth.button.login": "登录", + "auth.error.required": "请输入 API 密钥", + "auth.success": "登录成功!", + "auth.logout.confirm": "确定要退出登录吗?", + "auth.logout.success": "已成功退出", + "auth.please_login": "请先登录", + "auth.session_expired": "会话已过期或 API 密钥无效,请重新登录。", + "displays.title": "可用显示器", + "displays.layout": "\uD83D\uDDA5\uFE0F 显示器", + "displays.information": "显示器信息", + "displays.legend.primary": "主显示器", + "displays.legend.secondary": "副显示器", + "displays.badge.primary": "主", + "displays.badge.secondary": "副", + "displays.resolution": "分辨率:", + "displays.refresh_rate": "刷新率:", + "displays.position": "位置:", + "displays.index": "显示器序号:", + "displays.loading": "正在加载显示器...", + "displays.none": "没有可用的显示器", + "displays.failed": "加载显示器失败", + "displays.picker.title": "选择显示器", + "displays.picker.select": "选择显示器...", + "displays.picker.click_to_select": "点击选择此显示器", + "displays.picker.adb_connect": "连接 ADB 设备", + "displays.picker.adb_connect.placeholder": "IP 地址(例如 192.168.2.201)", + "displays.picker.adb_connect.button": "连接", + "displays.picker.adb_connect.success": "设备已连接", + "displays.picker.adb_connect.error": "连接设备失败", + "displays.picker.adb_disconnect": "断开连接", + "displays.picker.no_android": "未找到 Android 设备。请通过 USB 连接或在上方输入 IP 地址。", + "templates.title": "\uD83D\uDCC4 引擎模板", + "templates.description": "采集模板定义屏幕的采集方式。每个模板使用特定的采集引擎(MSS、DXcam、WGC)及自定义设置。将模板分配给设备以获得最佳性能。", + "templates.loading": "正在加载模板...", + "templates.empty": "尚未配置采集模板", + "templates.add": "添加引擎模板", + "templates.edit": "编辑引擎模板", + "templates.name": "模板名称:", + "templates.name.placeholder": "我的自定义模板", + "templates.description.label": "描述(可选):", + "templates.description.placeholder": "描述此模板...", + "templates.engine": "采集引擎:", + "templates.engine.hint": "选择要使用的屏幕采集技术", + "templates.engine.select": "选择引擎...", + "templates.engine.unavailable": "不可用", + "templates.engine.unavailable.hint": "此引擎在您的系统上不可用", + "templates.config": "配置", + "templates.config.show": "显示配置", + "templates.config.none": "无额外配置", + "templates.config.default": "默认", + "templates.created": "模板创建成功", + "templates.updated": "模板更新成功", + "templates.deleted": "模板删除成功", + "templates.delete.confirm": "确定要删除此模板吗?", + "templates.error.load": "加载模板失败", + "templates.error.engines": "加载引擎失败", + "templates.error.required": "请填写所有必填项", + "templates.error.delete": "删除模板失败", + "templates.test.title": "测试采集", + "templates.test.description": "保存前测试此模板,查看采集预览和性能指标。", + "templates.test.display": "显示器:", + "templates.test.display.select": "选择显示器...", + "templates.test.duration": "采集时长(秒):", + "templates.test.border_width": "边框宽度(像素):", + "templates.test.run": "\uD83E\uDDEA 运行", + "templates.test.running": "正在运行测试...", + "templates.test.results.preview": "全幅采集预览", + "templates.test.results.borders": "边框提取", + "templates.test.results.top": "上", + "templates.test.results.right": "右", + "templates.test.results.bottom": "下", + "templates.test.results.left": "左", + "templates.test.results.performance": "性能", + "templates.test.results.capture_time": "采集", + "templates.test.results.extraction_time": "提取", + "templates.test.results.total_time": "总计", + "templates.test.results.max_fps": "最大 FPS", + "templates.test.results.duration": "时长", + "templates.test.results.frame_count": "帧数", + "templates.test.results.actual_fps": "实际 FPS", + "templates.test.results.avg_capture_time": "平均采集", + "templates.test.error.no_engine": "请选择采集引擎", + "templates.test.error.no_display": "请选择显示器", + "templates.test.error.failed": "测试失败", + "devices.title": "\uD83D\uDCA1 设备", + "devices.add": "添加新设备", + "devices.loading": "正在加载设备...", + "devices.none": "尚未配置设备", + "devices.failed": "加载设备失败", + "devices.wled_config": "WLED 配置:", + "devices.wled_note": "使用以下方式配置您的 WLED 设备(效果、分段、颜色顺序、功率限制等):", + "devices.wled_link": "官方 WLED 应用", + "devices.wled_note_or": "或内置的", + "devices.wled_webui_link": "WLED Web UI", + "devices.wled_note_webui": "(在浏览器中打开设备 IP 地址)。", + "devices.wled_note2": "此控制器发送像素颜色数据并控制每个设备的亮度。", + "device.scan": "自动发现", + "device.scan.empty": "未找到设备", + "device.scan.error": "网络扫描失败", + "device.scan.already_added": "已添加", + "device.scan.selected": "设备已选择", + "device.type": "设备类型:", + "device.type.hint": "选择 LED 控制器的类型", + "device.serial_port": "串口:", + "device.serial_port.hint": "选择 Adalight 设备的 COM 端口", + "device.serial_port.none": "未找到串口", + "device.led_count_manual.hint": "灯带上的 LED 数量(必须与 Arduino 程序匹配)", + "device.baud_rate": "波特率:", + "device.baud_rate.hint": "串口通信速率。越高 FPS 越高,但需要与 Arduino 程序匹配。", + "device.led_type": "LED 类型:", + "device.led_type.hint": "RGB(3通道)或 RGBW(4通道,带独立白色)", + "device.send_latency": "发送延迟(毫秒):", + "device.send_latency.hint": "每帧模拟网络/串口延迟(毫秒)", + "device.url.hint": "设备的 IP 地址或主机名(例如 http://192.168.1.100)", + "device.name": "设备名称:", + "device.name.placeholder": "客厅电视", + "device.url": "地址:", + "device.url.placeholder": "http://192.168.1.100", + "device.led_count": "LED 数量:", + "device.led_count.hint": "设备中配置的 LED 数量", + "device.led_count.hint.auto": "从设备自动检测", + "device.button.add": "添加设备", + "device.button.start": "启动", + "device.button.stop": "停止", + "device.button.settings": "常规设置", + "device.button.capture_settings": "采集设置", + "device.button.calibrate": "校准", + "device.button.remove": "移除", + "device.button.webui": "打开设备 Web UI", + "device.button.power_off": "关闭", + "device.power.off_success": "设备已关闭", + "device.status.connected": "已连接", + "device.status.disconnected": "已断开", + "device.status.error": "错误", + "device.status.processing": "处理中", + "device.status.idle": "空闲", + "device.fps": "FPS:", + "device.display": "显示器:", + "device.remove.confirm": "确定要移除此设备吗?", + "device.added": "设备添加成功", + "device.removed": "设备移除成功", + "device.started": "处理已启动", + "device.stopped": "处理已停止", + "device.metrics.actual_fps": "实际 FPS", + "device.metrics.current_fps": "当前 FPS", + "device.metrics.target_fps": "目标 FPS", + "device.metrics.potential_fps": "潜在 FPS", + "device.metrics.frames": "帧数", + "device.metrics.frames_skipped": "已跳过", + "device.metrics.keepalive": "心跳", + "device.metrics.errors": "错误", + "device.metrics.uptime": "运行时长", + "device.metrics.timing": "管线时序:", + "device.metrics.device_fps": "设备刷新率", + "device.health.online": "在线", + "device.health.offline": "离线", + "device.health.checking": "检测中...", + "device.tutorial.start": "开始教程", + "device.tip.metadata": "设备信息(LED 数量、类型、颜色通道)从设备自动检测", + "device.tip.brightness": "滑动调节设备亮度", + "device.tip.start": "启动或停止屏幕采集处理", + "device.tip.settings": "配置设备常规设置(名称、地址、健康检查)", + "device.tip.capture_settings": "配置采集设置(显示器、采集模板)", + "device.tip.calibrate": "校准 LED 位置、方向和覆盖范围", + "device.tip.webui": "打开设备内置的 Web 界面进行高级配置", + "device.tip.add": "点击此处添加新的 LED 设备", + "settings.title": "设备设置", + "settings.general.title": "常规设置", + "settings.capture.title": "采集设置", + "settings.capture.saved": "采集设置已更新", + "settings.capture.failed": "保存采集设置失败", + "settings.brightness": "亮度:", + "settings.brightness.hint": "此设备的全局亮度(0-100%)", + "settings.url.hint": "设备的 IP 地址或主机名", + "settings.display_index": "显示器:", + "settings.display_index.hint": "为此设备采集哪个屏幕", + "settings.fps": "目标 FPS:", + "settings.fps.hint": "目标帧率(10-90)", + "settings.capture_template": "引擎模板:", + "settings.capture_template.hint": "此设备的屏幕采集引擎和配置", + "settings.button.cancel": "取消", + "settings.health_interval": "健康检查间隔(秒):", + "settings.health_interval.hint": "检查设备状态的频率(5-600秒)", + "settings.auto_shutdown": "自动恢复:", + "settings.auto_shutdown.hint": "当目标停止或服务器关闭时恢复设备到空闲状态", + "settings.button.save": "保存更改", + "settings.saved": "设置保存成功", + "settings.failed": "保存设置失败", + "calibration.title": "LED 校准", + "calibration.tip.led_count": "输入每条边的 LED 数量", + "calibration.tip.start_corner": "点击角落设置起始位置", + "calibration.tip.direction": "切换灯带方向(顺时针/逆时针)", + "calibration.tip.offset": "设置 LED 偏移 — 从 LED 0 到起始角落的距离", + "calibration.tip.span": "拖动绿色条调整覆盖范围", + "calibration.tip.test": "点击边缘切换测试 LED", + "calibration.tip.overlay": "切换屏幕叠加层以查看显示器上的 LED 位置和编号", + "calibration.tip.toggle_inputs": "点击 LED 总数切换边缘输入", + "calibration.tip.border_width": "从屏幕边缘采样多少像素来确定 LED 颜色", + "calibration.tip.skip_leds_start": "跳过灯带起始端的 LED — 被跳过的 LED 保持关闭", + "calibration.tip.skip_leds_end": "跳过灯带末尾端的 LED — 被跳过的 LED 保持关闭", + "calibration.tutorial.start": "开始教程", + "calibration.overlay_toggle": "叠加层", + "calibration.start_position": "起始位置:", + "calibration.position.bottom_left": "左下", + "calibration.position.bottom_right": "右下", + "calibration.position.top_left": "左上", + "calibration.position.top_right": "右上", + "calibration.direction": "方向:", + "calibration.direction.clockwise": "顺时针", + "calibration.direction.counterclockwise": "逆时针", + "calibration.leds.top": "顶部 LED:", + "calibration.leds.right": "右侧 LED:", + "calibration.leds.bottom": "底部 LED:", + "calibration.leds.left": "左侧 LED:", + "calibration.offset": "LED 偏移:", + "calibration.offset.hint": "从物理 LED 0 到起始角落的距离(沿灯带方向)", + "calibration.skip_start": "跳过 LED(起始):", + "calibration.skip_start.hint": "灯带起始端关闭的 LED 数量(0 = 无)", + "calibration.skip_end": "跳过 LED(末尾):", + "calibration.skip_end.hint": "灯带末尾端关闭的 LED 数量(0 = 无)", + "calibration.border_width": "边框(像素):", + "calibration.border_width.hint": "从屏幕边缘采样多少像素来确定 LED 颜色(1-100)", + "calibration.button.cancel": "取消", + "calibration.button.save": "保存", + "calibration.saved": "校准保存成功", + "calibration.failed": "保存校准失败", + "server.healthy": "服务器在线", + "server.offline": "服务器离线", + "error.unauthorized": "未授权 - 请先登录", + "error.network": "网络错误", + "error.unknown": "发生错误", + "modal.discard_changes": "有未保存的更改。是否放弃?", + "confirm.title": "确认操作", + "confirm.yes": "是", + "confirm.no": "否", + "common.loading": "加载中...", + "common.delete": "删除", + "common.edit": "编辑", + "common.clone": "克隆", + "section.filter.placeholder": "筛选...", + "section.filter.reset": "清除筛选", + "section.expand_all": "全部展开", + "section.collapse_all": "全部折叠", + "streams.title": "\uD83D\uDCFA 源", + "streams.description": "源定义采集管线。原始源使用采集模板从显示器采集。处理源对另一个源应用后处理。将源分配给设备。", + "streams.group.raw": "屏幕采集", + "streams.group.processed": "已处理", + "streams.group.audio": "音频", + "streams.section.streams": "\uD83D\uDCFA 源", + "streams.add": "添加源", + "streams.add.raw": "添加屏幕采集", + "streams.add.processed": "添加处理源", + "streams.edit": "编辑源", + "streams.edit.raw": "编辑屏幕采集", + "streams.edit.processed": "编辑处理源", + "streams.name": "源名称:", + "streams.name.placeholder": "我的源", + "streams.type": "类型:", + "streams.type.raw": "屏幕采集", + "streams.type.processed": "已处理", + "streams.display": "显示器:", + "streams.display.hint": "采集哪个屏幕", + "streams.capture_template": "引擎模板:", + "streams.capture_template.hint": "定义屏幕采集方式的引擎模板", + "streams.target_fps": "目标 FPS:", + "streams.target_fps.hint": "采集的目标帧率(1-90)", + "streams.source": "源:", + "streams.source.hint": "要应用处理滤镜的源", + "streams.pp_template": "滤镜模板:", + "streams.pp_template.hint": "要应用到源的滤镜模板", + "streams.description_label": "描述(可选):", + "streams.description_placeholder": "描述此源...", + "streams.created": "源创建成功", + "streams.updated": "源更新成功", + "streams.deleted": "源删除成功", + "streams.delete.confirm": "确定要删除此源吗?", + "streams.error.load": "加载源失败", + "streams.error.required": "请填写所有必填项", + "streams.error.delete": "删除源失败", + "streams.test.title": "测试源", + "streams.test.run": "🧪 运行", + "streams.test.running": "正在测试源...", + "streams.test.duration": "采集时长(秒):", + "streams.test.error.failed": "源测试失败", + "postprocessing.title": "\uD83D\uDCC4 滤镜模板", + "postprocessing.description": "处理模板定义图像滤镜和色彩校正。将它们分配给处理图片源以实现跨设备的一致后处理。", + "postprocessing.add": "添加滤镜模板", + "postprocessing.edit": "编辑滤镜模板", + "postprocessing.name": "模板名称:", + "postprocessing.name.placeholder": "我的滤镜模板", + "filters.select_type": "选择滤镜类型...", + "filters.add": "添加滤镜", + "filters.remove": "移除", + "filters.move_up": "上移", + "filters.move_down": "下移", + "filters.empty": "尚未添加滤镜。使用下方选择器添加滤镜。", + "filters.brightness": "亮度", + "filters.saturation": "饱和度", + "filters.gamma": "伽马", + "filters.downscaler": "缩小", + "filters.pixelate": "像素化", + "filters.auto_crop": "自动裁剪", + "filters.flip": "翻转", + "filters.color_correction": "色彩校正", + "filters.filter_template": "滤镜模板", + "postprocessing.description_label": "描述(可选):", + "postprocessing.description_placeholder": "描述此模板...", + "postprocessing.created": "模板创建成功", + "postprocessing.updated": "模板更新成功", + "postprocessing.deleted": "模板删除成功", + "postprocessing.delete.confirm": "确定要删除此滤镜模板吗?", + "postprocessing.error.load": "加载处理模板失败", + "postprocessing.error.required": "请填写所有必填项", + "postprocessing.error.delete": "删除处理模板失败", + "postprocessing.config.show": "显示设置", + "postprocessing.test.title": "测试滤镜模板", + "postprocessing.test.source_stream": "源:", + "postprocessing.test.running": "正在测试处理模板...", + "postprocessing.test.error.no_stream": "请选择一个源", + "postprocessing.test.error.failed": "处理模板测试失败", + "device.button.stream_selector": "源设置", + "device.stream_settings.title": "📺 源设置", + "device.stream_selector.label": "源:", + "device.stream_selector.hint": "选择一个源来定义此设备采集和处理的内容", + "device.stream_selector.none": "-- 未分配源 --", + "device.stream_selector.saved": "源设置已更新", + "device.stream_settings.border_width": "边框宽度(像素):", + "device.stream_settings.border_width_hint": "从屏幕边缘采样多少像素来确定 LED 颜色(1-100)", + "device.stream_settings.interpolation": "插值模式:", + "device.stream_settings.interpolation.average": "平均", + "device.stream_settings.interpolation.median": "中位数", + "device.stream_settings.interpolation.dominant": "主色", + "device.stream_settings.interpolation_hint": "如何从采样像素计算 LED 颜色", + "device.stream_settings.smoothing": "平滑:", + "device.stream_settings.smoothing_hint": "帧间时间混合(0=无,1=完全)。减少闪烁。", + "device.tip.stream_selector": "为此设备配置图片源和 LED 投影设置", + "streams.group.static_image": "静态图片", + "streams.add.static_image": "添加静态图片源", + "streams.edit.static_image": "编辑静态图片源", + "streams.type.static_image": "静态图片", + "streams.image_source": "图片源:", + "streams.image_source.placeholder": "https://example.com/image.jpg 或 C:\\path\\to\\image.png", + "streams.image_source.hint": "输入图片的 URL(http/https)或本地文件路径", + "streams.validate_image.validating": "正在验证...", + "streams.validate_image.valid": "图片可访问", + "streams.validate_image.invalid": "图片不可访问", + "targets.title": "⚡ 目标", + "targets.description": "目标将色带源桥接到输出设备。每个目标引用一个设备和一个色带源。", + "targets.subtab.wled": "LED", + "targets.subtab.led": "LED", + "targets.section.devices": "💡 设备", + "targets.section.color_strips": "🎞️ 色带源", + "targets.section.targets": "⚡ 目标", + "targets.add": "添加目标", + "targets.edit": "编辑目标", + "targets.loading": "正在加载目标...", + "targets.none": "尚未配置目标", + "targets.failed": "加载目标失败", + "targets.name": "目标名称:", + "targets.name.placeholder": "我的目标", + "targets.device": "设备:", + "targets.device.hint": "选择要发送数据的 LED 设备", + "targets.device.none": "-- 选择设备 --", + "targets.color_strip_source": "色带源:", + "targets.color_strip_source.hint": "选择为此目标提供 LED 颜色的色带源", + "targets.no_css": "无源", + "targets.source": "源:", + "targets.source.hint": "要采集和处理的图片源", + "targets.source.none": "-- 未分配源 --", + "targets.fps": "目标 FPS:", + "targets.fps.hint": "采集和 LED 更新的目标帧率(1-90)", + "targets.fps.rec": "硬件最大 ≈ {fps} fps({leds} 个 LED)", + "targets.border_width": "边框宽度(像素):", + "targets.border_width.hint": "从屏幕边缘采样多少像素来确定 LED 颜色(1-100)", + "targets.interpolation": "插值模式:", + "targets.interpolation.hint": "如何从采样像素计算 LED 颜色", + "targets.interpolation.average": "平均", + "targets.interpolation.median": "中位数", + "targets.interpolation.dominant": "主色", + "targets.smoothing": "平滑:", + "targets.smoothing.hint": "帧间时间混合(0=无,1=完全)。减少闪烁。", + "targets.keepalive_interval": "心跳间隔:", + "targets.keepalive_interval.hint": "源静态时重新发送最后一帧的频率,保持设备在活动模式(0.5-5.0秒)", + "targets.created": "目标创建成功", + "targets.updated": "目标更新成功", + "targets.deleted": "目标删除成功", + "targets.delete.confirm": "确定要删除此目标吗?", + "targets.error.load": "加载目标失败", + "targets.error.required": "请填写所有必填项", + "targets.error.name_required": "请输入目标名称", + "targets.error.delete": "删除目标失败", + "targets.button.start": "启动", + "targets.button.stop": "停止", + "targets.status.processing": "处理中", + "targets.status.idle": "空闲", + "targets.status.error": "错误", + "targets.metrics.actual_fps": "实际 FPS", + "targets.metrics.target_fps": "目标 FPS", + "targets.metrics.frames": "帧数", + "targets.metrics.errors": "错误", + "targets.subtab.key_colors": "关键颜色", + "targets.section.key_colors": "🎨 关键颜色目标", + "kc.add": "添加关键颜色目标", + "kc.edit": "编辑关键颜色目标", + "kc.name": "目标名称:", + "kc.name.placeholder": "我的关键颜色目标", + "kc.source": "图片源:", + "kc.source.hint": "从哪个图片源提取颜色", + "kc.source.none": "-- 未分配源 --", + "kc.fps": "提取 FPS:", + "kc.fps.hint": "每秒提取颜色的次数(1-60)", + "kc.interpolation": "颜色模式:", + "kc.interpolation.hint": "如何从每个矩形中的像素计算关键颜色", + "kc.interpolation.average": "平均", + "kc.interpolation.median": "中位数", + "kc.interpolation.dominant": "主色", + "kc.smoothing": "平滑:", + "kc.smoothing.hint": "提取间的时间混合(0=无,1=完全)", + "kc.pattern_template": "图案模板:", + "kc.pattern_template.hint": "选择用于颜色提取的矩形图案", + "kc.pattern_template.none": "-- 选择图案模板 --", + "kc.brightness_vs": "亮度源:", + "kc.brightness_vs.hint": "可选的值源,每帧动态控制亮度(与手动亮度滑块相乘)", + "kc.brightness_vs.none": "无(仅手动亮度)", + "kc.created": "关键颜色目标创建成功", + "kc.updated": "关键颜色目标更新成功", + "kc.deleted": "关键颜色目标删除成功", + "kc.delete.confirm": "确定要删除此关键颜色目标吗?", + "kc.error.no_pattern": "请选择图案模板", + "kc.error.required": "请填写所有必填项", + "kc.colors.none": "尚未提取颜色", + "kc.test": "测试", + "kc.test.error": "测试失败", + "targets.section.pattern_templates": "📄 图案模板", + "pattern.add": "📄 添加图案模板", + "pattern.edit": "📄 编辑图案模板", + "pattern.name": "模板名称:", + "pattern.name.placeholder": "我的图案模板", + "pattern.description_label": "描述(可选):", + "pattern.description_placeholder": "描述此图案...", + "pattern.rectangles": "矩形", + "pattern.rect.name": "名称", + "pattern.rect.x": "X", + "pattern.rect.y": "Y", + "pattern.rect.width": "宽", + "pattern.rect.height": "高", + "pattern.rect.add": "添加矩形", + "pattern.rect.remove": "移除", + "pattern.rect.empty": "尚未定义矩形。请至少添加一个矩形。", + "pattern.created": "图案模板创建成功", + "pattern.updated": "图案模板更新成功", + "pattern.deleted": "图案模板删除成功", + "pattern.delete.confirm": "确定要删除此图案模板吗?", + "pattern.delete.referenced": "无法删除:此模板正被目标引用", + "pattern.error.required": "请填写所有必填项", + "pattern.visual_editor": "可视编辑器", + "pattern.capture_bg": "采集背景", + "pattern.source_for_bg": "背景源:", + "pattern.source_for_bg.none": "-- 选择源 --", + "pattern.delete_selected": "删除选中", + "pattern.name.hint": "此矩形布局的描述性名称", + "pattern.description.hint": "关于此图案使用位置或方式的可选说明", + "pattern.visual_editor.hint": "点击 + 按钮添加矩形。拖动边缘调整大小,拖动内部移动位置。", + "pattern.rectangles.hint": "用精确坐标(0.0 到 1.0)微调矩形位置和大小", + "overlay.button.show": "显示叠加层可视化", + "overlay.button.hide": "隐藏叠加层可视化", + "overlay.started": "叠加层可视化已启动", + "overlay.stopped": "叠加层可视化已停止", + "overlay.error.start": "启动叠加层失败", + "overlay.error.stop": "停止叠加层失败", + "dashboard.title": "📊 仪表盘", + "dashboard.section.targets": "目标", + "dashboard.section.running": "运行中", + "dashboard.section.stopped": "已停止", + "dashboard.no_targets": "尚未配置目标", + "dashboard.uptime": "运行时长", + "dashboard.fps": "FPS", + "dashboard.errors": "错误", + "dashboard.device": "设备", + "dashboard.stop_all": "全部停止", + "dashboard.failed": "加载仪表盘失败", + "dashboard.section.profiles": "配置文件", + "dashboard.targets": "目标", + "dashboard.section.performance": "系统性能", + "dashboard.perf.cpu": "CPU", + "dashboard.perf.ram": "内存", + "dashboard.perf.gpu": "GPU", + "dashboard.perf.unavailable": "不可用", + "dashboard.poll_interval": "刷新间隔", + + "profiles.title": "\uD83D\uDCCB 配置文件", + "profiles.empty": "尚未配置配置文件。创建一个以自动化目标激活。", + "profiles.add": "\uD83D\uDCCB 添加配置文件", + "profiles.edit": "编辑配置文件", + "profiles.delete.confirm": "删除配置文件 \"{name}\"?", + "profiles.name": "名称:", + "profiles.name.hint": "此配置文件的描述性名称", + "profiles.enabled": "启用:", + "profiles.enabled.hint": "禁用的配置文件即使满足条件也不会激活", + "profiles.condition_logic": "条件逻辑:", + "profiles.condition_logic.hint": "多个条件的组合方式:任一(或)或 全部(与)", + "profiles.condition_logic.or": "任一条件(或)", + "profiles.condition_logic.and": "全部条件(与)", + "profiles.conditions": "条件:", + "profiles.conditions.hint": "决定此配置文件何时激活的规则", + "profiles.conditions.add": "添加条件", + "profiles.conditions.empty": "无条件 — 启用后配置文件始终处于活动状态", + "profiles.condition.always": "始终", + "profiles.condition.always.hint": "配置文件启用后立即激活并保持活动。用于服务器启动时自动启动目标。", + "profiles.condition.application": "应用程序", + "profiles.condition.application.apps": "应用程序:", + "profiles.condition.application.apps.hint": "进程名,每行一个(例如 firefox.exe)", + "profiles.condition.application.browse": "浏览", + "profiles.condition.application.search": "筛选进程...", + "profiles.condition.application.no_processes": "未找到进程", + "profiles.condition.application.match_type": "匹配类型:", + "profiles.condition.application.match_type.hint": "如何检测应用程序", + "profiles.condition.application.match_type.running": "运行中", + "profiles.condition.application.match_type.topmost": "最前(前台)", + "profiles.condition.application.match_type.topmost_fullscreen": "最前 + 全屏", + "profiles.condition.application.match_type.fullscreen": "全屏", + "profiles.targets": "目标:", + "profiles.targets.hint": "配置文件激活时要启动的目标", + "profiles.targets.empty": "没有可用的目标", + "profiles.status.active": "活动", + "profiles.status.inactive": "非活动", + "profiles.status.disabled": "已禁用", + "profiles.action.disable": "禁用", + "profiles.last_activated": "上次激活", + "profiles.logic.and": " 与 ", + "profiles.logic.or": " 或 ", + "profiles.logic.all": "全部", + "profiles.logic.any": "任一", + "profiles.updated": "配置文件已更新", + "profiles.created": "配置文件已创建", + "profiles.deleted": "配置文件已删除", + "profiles.error.name_required": "名称为必填项", + "profiles.toggle_all.start": "启动所有目标", + "profiles.toggle_all.stop": "停止所有目标", + "autostart.title": "自动启动目标", + "autostart.toggle.enabled": "自动启动已启用", + "autostart.toggle.disabled": "自动启动已禁用", + "autostart.goto_target": "跳转到目标", + "time.hours_minutes": "{h}时 {m}分", + "time.minutes_seconds": "{m}分 {s}秒", + "time.seconds": "{s}秒", + "dashboard.type.led": "LED", + "dashboard.type.kc": "关键颜色", + "aria.close": "关闭", + "aria.save": "保存", + "aria.cancel": "取消", + "aria.previous": "上一个", + "aria.next": "下一个", + "aria.hint": "显示提示", + + "color_strip.add": "🎞️ 添加色带源", + "color_strip.edit": "🎞️ 编辑色带源", + "color_strip.name": "名称:", + "color_strip.name.placeholder": "墙壁灯带", + "color_strip.picture_source": "图片源:", + "color_strip.picture_source.hint": "用作 LED 颜色计算输入的屏幕采集源", + "color_strip.fps": "目标 FPS:", + "color_strip.fps.hint": "LED 颜色更新的目标帧率(10-90)", + "color_strip.interpolation": "颜色模式:", + "color_strip.interpolation.hint": "如何从采样的边框像素计算 LED 颜色", + "color_strip.interpolation.average": "平均", + "color_strip.interpolation.median": "中位数", + "color_strip.interpolation.dominant": "主色", + "color_strip.smoothing": "平滑:", + "color_strip.smoothing.hint": "帧间时间混合(0=无,1=完全)。减少闪烁。", + "color_strip.frame_interpolation": "帧插值:", + "color_strip.frame_interpolation.hint": "在连续采集帧之间混合,以在采集速率较低时仍以完整目标 FPS 输出。减少慢速环境过渡时的可见阶梯效应。", + "color_strip.color_corrections": "色彩校正", + "color_strip.brightness": "亮度:", + "color_strip.brightness.hint": "输出亮度倍数(0=关闭,1=不变,2=加倍)。在颜色提取后应用。", + "color_strip.saturation": "饱和度:", + "color_strip.saturation.hint": "颜色饱和度(0=灰度,1=不变,2=双倍饱和度)", + "color_strip.gamma": "伽马:", + "color_strip.gamma.hint": "伽马校正(1=无,<1=更亮的中间调,>1=更暗的中间调)", + "color_strip.test_device": "测试设备:", + "color_strip.test_device.hint": "选择一个设备,在点击边缘切换时发送测试像素", + "color_strip.leds": "LED 数量", + "color_strip.led_count": "LED 数量:", + "color_strip.led_count.hint": "物理灯带上的 LED 总数。屏幕源:0 = 从校准自动获取(未映射到边缘的额外 LED 将为黑色)。静态颜色:设置为与设备 LED 数量匹配。", + "color_strip.created": "色带源已创建", + "color_strip.updated": "色带源已更新", + "color_strip.deleted": "色带源已删除", + "color_strip.delete.confirm": "确定要删除此色带源吗?", + "color_strip.delete.referenced": "无法删除:此源正在被目标使用", + "color_strip.error.name_required": "请输入名称", + "color_strip.type": "类型:", + "color_strip.type.hint": "图片源从屏幕采集推导 LED 颜色。静态颜色用单一颜色填充所有 LED。渐变在所有 LED 上分布颜色渐变。颜色循环平滑循环用户定义的颜色列表。组合将多个源作为混合图层叠加。音频响应从实时音频输入驱动 LED。API 输入通过 REST 或 WebSocket 从外部客户端接收原始 LED 颜色。", + "color_strip.type.picture": "图片源", + "color_strip.type.static": "静态颜色", + "color_strip.type.gradient": "渐变", + "color_strip.type.color_cycle": "颜色循环", + "color_strip.static_color": "颜色:", + "color_strip.static_color.hint": "将发送到灯带上所有 LED 的纯色。", + "color_strip.gradient.preview": "渐变:", + "color_strip.gradient.preview.hint": "可视预览。点击下方标记轨道添加色标。拖动标记重新定位。", + "color_strip.gradient.stops": "色标:", + "color_strip.gradient.stops.hint": "每个色标在相对位置定义一种颜色(0.0 = 起始,1.0 = 结束)。↔ 按钮添加右侧颜色以在该色标处创建硬边。", + "color_strip.gradient.stops_count": "个色标", + "color_strip.gradient.add_stop": "+ 添加色标", + "color_strip.gradient.position": "位置(0.0-1.0)", + "color_strip.gradient.bidir.hint": "在此色标右侧添加第二种颜色以在渐变中创建硬边。", + "color_strip.gradient.min_stops": "渐变至少需要 2 个色标", + "color_strip.gradient.preset": "预设:", + "color_strip.gradient.preset.hint": "加载预定义的渐变调色板。选择预设将替换当前色标。", + "color_strip.gradient.preset.custom": "— 自定义 —", + "color_strip.gradient.preset.rainbow": "彩虹", + "color_strip.gradient.preset.sunset": "日落", + "color_strip.gradient.preset.ocean": "海洋", + "color_strip.gradient.preset.forest": "森林", + "color_strip.gradient.preset.fire": "火焰", + "color_strip.gradient.preset.lava": "熔岩", + "color_strip.gradient.preset.aurora": "极光", + "color_strip.gradient.preset.ice": "冰", + "color_strip.gradient.preset.warm": "暖色", + "color_strip.gradient.preset.cool": "冷色", + "color_strip.gradient.preset.neon": "霓虹", + "color_strip.gradient.preset.pastel": "柔和", + "color_strip.animation": "动画", + "color_strip.animation.type": "效果:", + "color_strip.animation.type.hint": "要应用的动画效果。", + "color_strip.animation.type.none": "无(无动画效果)", + "color_strip.animation.type.breathing": "呼吸", + "color_strip.animation.type.breathing.desc": "平滑的亮度渐入渐出", + "color_strip.animation.type.color_cycle": "颜色循环", + "color_strip.animation.type.gradient_shift": "渐变移动", + "color_strip.animation.type.gradient_shift.desc": "渐变沿灯带滑动", + "color_strip.animation.type.wave": "波浪", + "color_strip.animation.type.wave.desc": "沿灯带移动的正弦亮度波", + "color_strip.animation.type.strobe": "频闪", + "color_strip.animation.type.strobe.desc": "快速开/关闪烁", + "color_strip.animation.type.sparkle": "闪烁", + "color_strip.animation.type.sparkle.desc": "随机 LED 短暂闪亮", + "color_strip.animation.type.pulse": "脉冲", + "color_strip.animation.type.pulse.desc": "快速衰减的尖锐亮度脉冲", + "color_strip.animation.type.candle": "烛光", + "color_strip.animation.type.candle.desc": "温暖的类似蜡烛的闪烁光芒", + "color_strip.animation.type.rainbow_fade": "彩虹渐变", + "color_strip.animation.type.rainbow_fade.desc": "循环整个色相光谱", + "color_strip.animation.speed": "速度:", + "color_strip.animation.speed.hint": "动画速度倍数。1.0 ≈ 呼吸效果每秒一个循环;更高值循环更快。", + "color_strip.color_cycle.colors": "颜色:", + "color_strip.color_cycle.colors.hint": "平滑循环的颜色列表。至少需要 2 种。默认为全彩虹光谱。", + "color_strip.color_cycle.add_color": "+ 添加颜色", + "color_strip.color_cycle.speed": "速度:", + "color_strip.color_cycle.speed.hint": "循环速度倍数。1.0 ≈ 每 20 秒一个完整循环;更高值循环更快。", + "color_strip.color_cycle.min_colors": "颜色循环至少需要 2 种颜色", + "color_strip.type.effect": "效果", + "color_strip.type.effect.hint": "实时生成的程序化 LED 效果(火焰、流星、等离子、噪声、极光)。", + "color_strip.type.composite": "组合", + "color_strip.type.composite.hint": "将多个色带源作为图层叠加,支持混合模式和不透明度。", + "color_strip.type.mapped": "映射", + "color_strip.type.mapped.hint": "将不同色带源分配到不同 LED 范围(区域)。与组合的图层混合不同,映射将源并排放置。", + "color_strip.type.audio": "音频响应", + "color_strip.type.audio.hint": "LED 颜色由实时音频输入驱动 — 系统音频或麦克风。", + "color_strip.type.api_input": "API 输入", + "color_strip.type.api_input.hint": "通过 REST POST 或 WebSocket 从外部客户端接收原始 LED 颜色数组。用于与自定义软件、家庭自动化或任何能发送 HTTP 请求的系统集成。", + "color_strip.api_input.fallback_color": "备用颜色:", + "color_strip.api_input.fallback_color.hint": "超时未收到数据时显示的颜色。启动时和连接丢失后 LED 将显示此颜色。", + "color_strip.api_input.timeout": "超时(秒):", + "color_strip.api_input.timeout.hint": "等待新颜色数据多长时间后恢复为备用颜色。设为 0 表示永不超时。", + "color_strip.api_input.endpoints": "推送端点:", + "color_strip.api_input.endpoints.hint": "使用这些 URL 从外部应用程序推送 LED 颜色数据。REST 接受 JSON,WebSocket 接受 JSON 和原始二进制帧。", + "color_strip.api_input.save_first": "请先保存源以查看推送端点 URL。", + "color_strip.composite.layers": "图层:", + "color_strip.composite.layers.hint": "叠加多个色带源。第一个图层在底部,最后一个在顶部。每个图层可以有自己的混合模式和不透明度。", + "color_strip.composite.add_layer": "+ 添加图层", + "color_strip.composite.source": "源", + "color_strip.composite.blend_mode": "混合", + "color_strip.composite.blend_mode.normal": "正常", + "color_strip.composite.blend_mode.add": "叠加", + "color_strip.composite.blend_mode.multiply": "正片叠底", + "color_strip.composite.blend_mode.screen": "滤色", + "color_strip.composite.opacity": "不透明度", + "color_strip.composite.enabled": "启用", + "color_strip.composite.error.min_layers": "至少需要 1 个图层", + "color_strip.composite.error.no_source": "每个图层必须选择一个源", + "color_strip.composite.layers_count": "个图层", + "color_strip.mapped.zones": "区域:", + "color_strip.mapped.zones.hint": "每个区域将色带源映射到特定 LED 范围。区域并排放置 — 区域之间的间隙保持黑色。", + "color_strip.mapped.add_zone": "+ 添加区域", + "color_strip.mapped.zone_source": "源", + "color_strip.mapped.zone_start": "起始 LED", + "color_strip.mapped.zone_end": "结束 LED", + "color_strip.mapped.zone_reverse": "反转", + "color_strip.mapped.zones_count": "个区域", + "color_strip.mapped.error.no_source": "每个区域必须选择一个源", + "color_strip.audio.visualization": "可视化:", + "color_strip.audio.visualization.hint": "音频数据如何渲染到 LED。", + "color_strip.audio.viz.spectrum": "频谱分析", + "color_strip.audio.viz.beat_pulse": "节拍脉冲", + "color_strip.audio.viz.vu_meter": "VU 表", + "color_strip.audio.source": "音频源:", + "color_strip.audio.source.hint": "此可视化的音频源。可以是多声道(设备)或单声道(单通道)源。在源标签页中创建和管理音频源。", + "color_strip.audio.sensitivity": "灵敏度:", + "color_strip.audio.sensitivity.hint": "音频电平的增益倍数。更高值使 LED 对较安静的声音也有反应。", + "color_strip.audio.smoothing": "平滑:", + "color_strip.audio.smoothing.hint": "帧间时间平滑。更高值产生更平滑但反应较慢的视觉效果。", + "color_strip.audio.palette": "调色板:", + "color_strip.audio.palette.hint": "用于频谱条或节拍脉冲着色的调色板。", + "color_strip.audio.color": "基础颜色:", + "color_strip.audio.color.hint": "VU 表条的低电平颜色。", + "color_strip.audio.color_peak": "峰值颜色:", + "color_strip.audio.color_peak.hint": "VU 表条顶部的高电平颜色。", + "color_strip.audio.mirror": "镜像:", + "color_strip.audio.mirror.hint": "从中心向外镜像频谱:低音在中间,高音在两端。", + "color_strip.effect.type": "效果类型:", + "color_strip.effect.type.hint": "选择程序化算法。", + "color_strip.effect.fire": "火焰", + "color_strip.effect.fire.desc": "模拟带热量扩散的上升火焰的元胞自动机", + "color_strip.effect.meteor": "流星", + "color_strip.effect.meteor.desc": "明亮头部沿灯带移动,带指数衰减的尾迹", + "color_strip.effect.plasma": "等离子", + "color_strip.effect.plasma.desc": "映射到调色板的重叠正弦波 — 经典演示场景效果", + "color_strip.effect.noise": "噪声", + "color_strip.effect.noise.desc": "滚动的分形值噪声映射到调色板", + "color_strip.effect.aurora": "极光", + "color_strip.effect.aurora.desc": "漂移和混合的分层噪声带 — 北极光风格", + "color_strip.effect.speed": "速度:", + "color_strip.effect.speed.hint": "效果动画的速度倍数(0.1 = 非常慢,10.0 = 非常快)。", + "color_strip.effect.palette": "调色板:", + "color_strip.effect.palette.hint": "用于将效果值映射到 RGB 颜色的调色板。", + "color_strip.effect.color": "流星颜色:", + "color_strip.effect.color.hint": "流星效果的头部颜色。", + "color_strip.effect.intensity": "强度:", + "color_strip.effect.intensity.hint": "效果强度 — 控制火花率(火焰)、尾迹衰减(流星)或亮度范围(极光)。", + "color_strip.effect.scale": "比例:", + "color_strip.effect.scale.hint": "空间比例 — 波频率(等离子)、缩放级别(噪声)或带宽(极光)。", + "color_strip.effect.mirror": "镜像:", + "color_strip.effect.mirror.hint": "反弹模式 — 流星在灯带末端反转方向而不是循环。", + "color_strip.palette.fire": "火焰", + "color_strip.palette.ocean": "海洋", + "color_strip.palette.lava": "熔岩", + "color_strip.palette.forest": "森林", + "color_strip.palette.rainbow": "彩虹", + "color_strip.palette.aurora": "极光", + "color_strip.palette.sunset": "日落", + "color_strip.palette.ice": "冰", + + "audio_source.title": "音频源", + "audio_source.group.multichannel": "多声道", + "audio_source.group.mono": "单声道", + "audio_source.add": "添加音频源", + "audio_source.add.multichannel": "添加多声道源", + "audio_source.add.mono": "添加单声道源", + "audio_source.edit": "编辑音频源", + "audio_source.edit.multichannel": "编辑多声道源", + "audio_source.edit.mono": "编辑单声道源", + "audio_source.name": "名称:", + "audio_source.name.placeholder": "系统音频", + "audio_source.name.hint": "此音频源的描述性名称", + "audio_source.type": "类型:", + "audio_source.type.hint": "多声道从物理音频设备采集所有通道。单声道从多声道源提取单个通道。", + "audio_source.type.multichannel": "多声道", + "audio_source.type.mono": "单声道", + "audio_source.device": "音频设备:", + "audio_source.device.hint": "音频输入源。回环设备采集系统音频输出;输入设备采集麦克风或线路输入。", + "audio_source.parent": "父源:", + "audio_source.parent.hint": "要从中提取通道的多声道源", + "audio_source.channel": "通道:", + "audio_source.channel.hint": "从多声道源提取哪个音频通道", + "audio_source.channel.mono": "单声道(左+右混合)", + "audio_source.channel.left": "左", + "audio_source.channel.right": "右", + "audio_source.description": "描述(可选):", + "audio_source.description.placeholder": "描述此音频源...", + "audio_source.description.hint": "关于此音频源的可选说明", + "audio_source.created": "音频源已创建", + "audio_source.updated": "音频源已更新", + "audio_source.deleted": "音频源已删除", + "audio_source.delete.confirm": "确定要删除此音频源吗?", + "audio_source.error.name_required": "请输入名称", + "audio_source.audio_template": "音频模板:", + "audio_source.audio_template.hint": "定义此设备使用哪个引擎和设置的音频采集模板", + "audio_source.test": "测试", + "audio_source.test.title": "测试音频源", + "audio_source.test.rms": "RMS", + "audio_source.test.peak": "峰值", + "audio_source.test.beat": "节拍", + "audio_source.test.connecting": "连接中...", + "audio_source.test.error": "音频测试失败", + + "audio_template.test": "测试", + "audio_template.test.title": "测试音频模板", + "audio_template.test.device": "音频设备:", + "audio_template.test.device.hint": "选择测试期间要采集的音频设备", + "audio_template.test.run": "🧪 运行", + + "audio_template.title": "🎵 音频模板", + "audio_template.add": "添加音频模板", + "audio_template.edit": "编辑音频模板", + "audio_template.name": "模板名称:", + "audio_template.name.placeholder": "我的音频模板", + "audio_template.description.label": "描述(可选):", + "audio_template.description.placeholder": "描述此模板...", + "audio_template.engine": "音频引擎:", + "audio_template.engine.hint": "选择要使用的音频采集后端。WASAPI 仅限 Windows 并支持回环。Sounddevice 支持跨平台。", + "audio_template.engine.unavailable": "不可用", + "audio_template.engine.unavailable.hint": "此引擎在您的系统上不可用", + "audio_template.config": "配置", + "audio_template.config.show": "显示配置", + "audio_template.created": "音频模板已创建", + "audio_template.updated": "音频模板已更新", + "audio_template.deleted": "音频模板已删除", + "audio_template.delete.confirm": "确定要删除此音频模板吗?", + "audio_template.error.load": "加载音频模板失败", + "audio_template.error.engines": "加载音频引擎失败", + "audio_template.error.required": "请填写所有必填项", + "audio_template.error.delete": "删除音频模板失败", + + "streams.group.value": "值源", + "value_source.group.title": "🔢 值源", + "value_source.add": "添加值源", + "value_source.edit": "编辑值源", + "value_source.name": "名称:", + "value_source.name.placeholder": "亮度脉冲", + "value_source.name.hint": "此值源的描述性名称", + "value_source.type": "类型:", + "value_source.type.hint": "静态输出固定值。动画循环波形。音频响应声音输入。自适应类型根据时间或场景内容自动调节亮度。", + "value_source.type.static": "静态", + "value_source.type.animated": "动画", + "value_source.type.audio": "音频", + "value_source.type.adaptive_time": "自适应(时间)", + "value_source.type.adaptive_scene": "自适应(场景)", + "value_source.value": "值:", + "value_source.value.hint": "固定输出值(0.0 = 关闭,1.0 = 最大亮度)", + "value_source.waveform": "波形:", + "value_source.waveform.hint": "亮度动画循环的形状", + "value_source.waveform.sine": "正弦", + "value_source.waveform.triangle": "三角", + "value_source.waveform.square": "方波", + "value_source.waveform.sawtooth": "锯齿", + "value_source.speed": "速度(周期/分):", + "value_source.speed.hint": "每分钟周期数 — 波形重复的速度(1 = 非常慢,120 = 非常快)", + "value_source.min_value": "最小值:", + "value_source.min_value.hint": "波形周期的最小输出", + "value_source.max_value": "最大值:", + "value_source.max_value.hint": "波形周期的最大输出", + "value_source.audio_source": "音频源:", + "value_source.audio_source.hint": "要读取音频电平的音频源(多声道或单声道)", + "value_source.mode": "模式:", + "value_source.mode.hint": "RMS 测量平均音量。峰值跟踪最响的时刻。节拍在节奏上触发。", + "value_source.mode.rms": "RMS(音量)", + "value_source.mode.peak": "峰值", + "value_source.mode.beat": "节拍", + "value_source.sensitivity": "灵敏度:", + "value_source.sensitivity.hint": "音频信号的增益倍数(越高反应越灵敏)", + "value_source.smoothing": "平滑:", + "value_source.smoothing.hint": "时间平滑(0 = 即时响应,1 = 非常平滑/缓慢)", + "value_source.audio_min_value": "最小值:", + "value_source.audio_min_value.hint": "音频静默时的输出(例如 0.3 = 30% 亮度下限)", + "value_source.audio_max_value": "最大值:", + "value_source.audio_max_value.hint": "最大音频电平时的输出", + "value_source.schedule": "计划:", + "value_source.schedule.hint": "定义至少 2 个时间点。亮度在各点之间线性插值,午夜循环。", + "value_source.schedule.add": "+ 添加时间点", + "value_source.schedule.points": "个时间点", + "value_source.picture_source": "图片源:", + "value_source.picture_source.hint": "将分析其帧以获取平均亮度的图片源。", + "value_source.scene_behavior": "行为:", + "value_source.scene_behavior.hint": "互补:暗场景 = 高亮度(适合环境背光)。匹配:亮场景 = 高亮度。", + "value_source.scene_behavior.complement": "互补(暗 → 亮)", + "value_source.scene_behavior.match": "匹配(亮 → 亮)", + "value_source.adaptive_min_value": "最小值:", + "value_source.adaptive_min_value.hint": "最小输出亮度", + "value_source.adaptive_max_value": "最大值:", + "value_source.adaptive_max_value.hint": "最大输出亮度", + "value_source.error.schedule_min": "计划至少需要 2 个时间点", + "value_source.description": "描述(可选):", + "value_source.description.placeholder": "描述此值源...", + "value_source.description.hint": "关于此值源的可选说明", + "value_source.created": "值源已创建", + "value_source.updated": "值源已更新", + "value_source.deleted": "值源已删除", + "value_source.delete.confirm": "确定要删除此值源吗?", + "value_source.error.name_required": "请输入名称", + "targets.brightness_vs": "亮度源:", + "targets.brightness_vs.hint": "可选的值源,每帧动态控制亮度(覆盖设备亮度)", + "targets.brightness_vs.none": "无(设备亮度)", + + "search.open": "搜索 (Ctrl+K)", + "search.placeholder": "搜索实体... (Ctrl+K)", + "search.loading": "加载中...", + "search.no_results": "未找到结果", + "search.group.devices": "设备", + "search.group.targets": "LED 目标", + "search.group.kc_targets": "关键颜色目标", + "search.group.css": "色带源", + "search.group.profiles": "配置文件", + "search.group.streams": "图片流", + "search.group.capture_templates": "采集模板", + "search.group.pp_templates": "后处理模板", + "search.group.pattern_templates": "图案模板", + "search.group.audio": "音频源", + "search.group.value": "值源" +} diff --git a/server/src/wled_controller/templates/index.html b/server/src/wled_controller/templates/index.html index 4e9da3e..3f987f3 100644 --- a/server/src/wled_controller/templates/index.html +++ b/server/src/wled_controller/templates/index.html @@ -37,6 +37,7 @@