Add Chinese locale, fix audio device duplicates, remove display lock restriction

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-02-26 14:34:24 +03:00
parent 147ef3b4eb
commit a0c9cb0039
9 changed files with 977 additions and 86 deletions

View File

@@ -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

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -8,7 +8,8 @@ let _initialized = false;
const supportedLocales = {
'en': 'English',
'ru': 'Русский'
'ru': 'Русский',
'zh': '中文'
};
const fallbackTranslations = {

View File

@@ -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": "RGB3通道或 RGBW4通道带独立白色",
"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": "输入图片的 URLhttp/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 接受 JSONWebSocket 接受 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": "值源"
}

View File

@@ -37,6 +37,7 @@
<select id="locale-select" onchange="changeLocale()" data-i18n-title="locale.change" title="Change language" style="padding: 4px 8px; border: 1px solid var(--border-color); border-radius: 4px; background: var(--bg-color); color: var(--text-color); font-size: 0.8rem; cursor: pointer;">
<option value="en">English</option>
<option value="ru">Русский</option>
<option value="zh">中文</option>
</select>
<button id="login-btn" class="btn btn-primary" onclick="showLogin()" style="display: none; padding: 4px 12px; font-size: 0.8rem;">
🔑 <span data-i18n="auth.login">Login</span>