feat: implement comprehensive i18n internationalization support

- Add custom i18n infrastructure with TypeScript support
- Support Chinese (zh-CN) and English (en-US) languages
- Implement language switching with localStorage persistence
- Update all components with translation keys:
  * System info components (board-info-panel, board-index)
  * Display management components (display-state-index, display-state-card)
  * LED strip configuration components (led-strip-configuration, led-count-control-panel)
  * White balance component with detailed usage instructions
  * LED test component with test pattern descriptions
- Add comprehensive translation coverage for:
  * Navigation menus and page titles
  * Common UI elements (buttons, status, actions)
  * Hardware information and connection status
  * Display configuration options
  * LED strip settings and controls
  * White balance adjustment instructions and tips
  * LED test modes and descriptions
  * Error messages and status indicators
- Features:
  * Dynamic language switching without app restart
  * Type-safe translation keys with full TypeScript support
  * Modular design for easy language extension
  * Responsive UI updates using SolidJS reactivity
This commit is contained in:
2025-07-08 16:55:12 +08:00
parent 4a3d7681d6
commit 2c6b777fa6
16 changed files with 893 additions and 115 deletions

View File

@@ -1,5 +1,6 @@
import { Component, ParentComponent } from 'solid-js';
import { DisplayState } from '../../models/display-state.model';
import { useLanguage } from '../../i18n/index';
type DisplayStateCardProps = {
state: DisplayState;
@@ -19,48 +20,49 @@ const Item: ParentComponent<ItemProps> = (props) => {
};
export const DisplayStateCard: Component<DisplayStateCardProps> = (props) => {
const { t } = useLanguage();
return (
<div class="card bg-base-200 shadow-lg hover:shadow-xl transition-shadow duration-200">
<div class="card-body p-4">
<div class="card-title text-base mb-3 flex items-center justify-between">
<span></span>
<div class="badge badge-primary badge-outline"></div>
<span>{t('displays.title')}</span>
<div class="badge badge-primary badge-outline">{t('common.realtime')}</div>
</div>
<div class="grid grid-cols-1 gap-3">
{/* 亮度信息 */}
<div class="bg-base-100 rounded-lg p-3">
<h4 class="text-sm font-semibold text-base-content mb-2"></h4>
<h4 class="text-sm font-semibold text-base-content mb-2">{t('displays.brightnessSettings')}</h4>
<div class="space-y-1">
<Item label="当前亮度">{props.state.brightness}</Item>
<Item label="最大亮度">{props.state.max_brightness}</Item>
<Item label="最小亮度">{props.state.min_brightness}</Item>
<Item label={t('displays.currentBrightness')}>{props.state.brightness}</Item>
<Item label={t('displays.maxBrightness')}>{props.state.max_brightness}</Item>
<Item label={t('displays.minBrightness')}>{props.state.min_brightness}</Item>
</div>
</div>
{/* 对比度信息 */}
<div class="bg-base-100 rounded-lg p-3">
<h4 class="text-sm font-semibold text-base-content mb-2"></h4>
<h4 class="text-sm font-semibold text-base-content mb-2">{t('displays.contrastSettings')}</h4>
<div class="space-y-1">
<Item label="当前对比度">{props.state.contrast}</Item>
<Item label="最大对比度">{props.state.max_contrast}</Item>
<Item label="最小对比度">{props.state.min_contrast}</Item>
<Item label={t('displays.currentContrast')}>{props.state.contrast}</Item>
<Item label={t('displays.maxContrast')}>{props.state.max_contrast}</Item>
<Item label={t('displays.minContrast')}>{props.state.min_contrast}</Item>
</div>
</div>
{/* 模式信息 */}
<div class="bg-base-100 rounded-lg p-3">
<h4 class="text-sm font-semibold text-base-content mb-2"></h4>
<h4 class="text-sm font-semibold text-base-content mb-2">{t('displays.modeSettings')}</h4>
<div class="space-y-1">
<Item label="当前模式">{props.state.mode}</Item>
<Item label="最大模式">{props.state.max_mode}</Item>
<Item label="最小模式">{props.state.min_mode}</Item>
<Item label={t('displays.currentMode')}>{props.state.mode}</Item>
<Item label={t('displays.maxMode')}>{props.state.max_mode}</Item>
<Item label={t('displays.minMode')}>{props.state.min_mode}</Item>
</div>
</div>
{/* 更新时间 */}
<div class="text-xs text-base-content/50 text-center pt-2 border-t border-base-300">
: {props.state.last_modified_at.toLocaleString()}
{t('displays.lastModified')}: {props.state.last_modified_at.toLocaleString()}
</div>
</div>
</div>

View File

@@ -4,11 +4,13 @@ import debug from 'debug';
import { invoke } from '@tauri-apps/api/core';
import { DisplayState, RawDisplayState } from '../../models/display-state.model';
import { DisplayStateCard } from './display-state-card';
import { useLanguage } from '../../i18n/index';
const logger = debug('app:components:displays:display-state-index');
export const DisplayStateIndex: Component = () => {
const [states, setStates] = createSignal<DisplayState[]>([]);
const { t } = useLanguage();
createEffect(() => {
const unlisten = listen<RawDisplayState[]>('displays_changed', (ev) => {
@@ -38,10 +40,10 @@ export const DisplayStateIndex: Component = () => {
return (
<div class="space-y-6">
<div class="flex items-center justify-between">
<h1 class="text-2xl font-bold text-base-content"></h1>
<h1 class="text-2xl font-bold text-base-content">{t('displays.title')}</h1>
<div class="stats shadow">
<div class="stat">
<div class="stat-title"></div>
<div class="stat-title">{t('displays.displayCount')}</div>
<div class="stat-value text-primary">{states().length}</div>
</div>
</div>
@@ -63,8 +65,8 @@ export const DisplayStateIndex: Component = () => {
{states().length === 0 && (
<div class="text-center py-12">
<div class="text-6xl mb-4">🖥</div>
<h3 class="text-lg font-semibold text-base-content mb-2"></h3>
<p class="text-base-content/70"></p>
<h3 class="text-lg font-semibold text-base-content mb-2">{t('displays.noDisplaysFound')}</h3>
<p class="text-base-content/70">{t('displays.checkConnection')}</p>
</div>
)}
</div>

View File

@@ -4,11 +4,13 @@ import { listen } from '@tauri-apps/api/event';
import debug from 'debug';
import { invoke } from '@tauri-apps/api/core';
import { BoardInfoPanel } from './board-info-panel';
import { useLanguage } from '../../i18n/index';
const logger = debug('app:components:info:board-index');
export const BoardIndex: Component = () => {
const [boards, setBoards] = createSignal<BoardInfo[]>([]);
const { t } = useLanguage();
createEffect(() => {
const unlisten = listen<BoardInfo[]>('boards_changed', (ev) => {
@@ -28,10 +30,10 @@ export const BoardIndex: Component = () => {
return (
<div class="space-y-6">
<div class="flex items-center justify-between">
<h1 class="text-2xl font-bold text-base-content"></h1>
<h1 class="text-2xl font-bold text-base-content">{t('info.boardInfo')}</h1>
<div class="stats shadow">
<div class="stat">
<div class="stat-title"></div>
<div class="stat-title">{t('info.deviceCount')}</div>
<div class="stat-value text-primary">{boards().length}</div>
</div>
</div>
@@ -53,8 +55,8 @@ export const BoardIndex: Component = () => {
{boards().length === 0 && (
<div class="text-center py-12">
<div class="text-6xl mb-4">🔍</div>
<h3 class="text-lg font-semibold text-base-content mb-2"></h3>
<p class="text-base-content/70"></p>
<h3 class="text-lg font-semibold text-base-content mb-2">{t('info.noDevicesFound')}</h3>
<p class="text-base-content/70">{t('info.checkConnection')}</p>
</div>
)}
</div>

View File

@@ -1,5 +1,6 @@
import { Component, ParentComponent, createMemo } from 'solid-js';
import { BoardInfo } from '../../models/board-info.model';
import { useLanguage } from '../../i18n/index';
type ItemProps = {
label: string;
@@ -15,6 +16,7 @@ const Item: ParentComponent<ItemProps> = (props) => {
};
export const BoardInfoPanel: Component<{ board: BoardInfo }> = (props) => {
const { t } = useLanguage();
const ttl = createMemo(() => {
if (props.board.connect_status !== 'Connected') {
return '--';
@@ -60,10 +62,10 @@ export const BoardInfoPanel: Component<{ board: BoardInfo }> = (props) => {
<div class={statusBadgeClass()}>{connectStatus()}</div>
</div>
<div class="space-y-2">
<Item label="主机名">{props.board.host}</Item>
<Item label="IP地址">{props.board.address}</Item>
<Item label="端口">{props.board.port}</Item>
<Item label="延迟">{ttl()}</Item>
<Item label={t('info.hostname')}>{props.board.host}</Item>
<Item label={t('info.ipAddress')}>{props.board.address}</Item>
<Item label={t('info.port')}>{props.board.port}</Item>
<Item label={t('info.latency')}>{ttl()}</Item>
</div>
</div>
</div>

View File

@@ -5,6 +5,7 @@ import { ledStripStore } from '../../stores/led-strip.store';
import { Borders } from '../../constants/border';
import { LedType } from '../../models/led-strip-config';
import { LedStripConfigurationContext } from '../../contexts/led-strip-configuration.context';
import { useLanguage } from '../../i18n/index';
type LedCountControlItemProps = {
displayId: number;
@@ -14,6 +15,7 @@ type LedCountControlItemProps = {
const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
const [stripConfiguration, { setHoveredStripPart }] = useContext(LedStripConfigurationContext);
const { t } = useLanguage();
const config = createMemo(() => {
return ledStripStore.strips.find(
@@ -116,7 +118,7 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
class="btn btn-xs btn-circle btn-outline flex-shrink-0 min-h-0 h-6 w-6"
onClick={handleDecrease}
disabled={!config() || (config()?.len || 0) <= 0}
title="减少LED数量"
title={t('ledConfig.decreaseLedCount')}
>
-
</button>
@@ -139,7 +141,7 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
class="btn btn-xs btn-circle btn-outline flex-shrink-0 min-h-0 h-6 w-6"
onClick={handleIncrease}
disabled={!config() || (config()?.len || 0) >= 1000}
title="增加LED数量"
title={t('ledConfig.increaseLedCount')}
>
+
</button>
@@ -150,7 +152,7 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
class="select select-xs w-full text-xs h-6 min-h-0"
value={config()?.led_type || LedType.RGB}
onChange={handleLedTypeChange}
title="LED类型"
title={t('ledConfig.ledType')}
>
<option value={LedType.RGB}>RGB</option>
<option value={LedType.RGBW}>RGBW</option>
@@ -167,20 +169,21 @@ type LedCountControlPanelProps = {
export const LedCountControlPanel: Component<LedCountControlPanelProps> = (props) => {
const [localProps, rootProps] = splitProps(props, ['display']);
const { t } = useLanguage();
const borders: { border: Borders; label: string }[] = [
{ border: 'Top', label: '上' },
{ border: 'Bottom', label: '下' },
{ border: 'Left', label: '左' },
{ border: 'Right', label: '右' },
{ border: 'Top', label: t('ledConfig.top') },
{ border: 'Bottom', label: t('ledConfig.bottom') },
{ border: 'Left', label: t('ledConfig.left') },
{ border: 'Right', label: t('ledConfig.right') },
];
return (
<div {...rootProps} class={'card bg-base-200 shadow-lg border border-base-300 ' + (rootProps.class || '')}>
<div class="card-body p-3">
<div class="card-title text-sm mb-2 flex items-center justify-between">
<span>LED数量控制</span>
<div class="badge badge-info badge-outline text-xs"> {localProps.display.id}</div>
<span>{t('ledConfig.ledCountControl')}</span>
<div class="badge badge-info badge-outline text-xs">{t('ledConfig.display')} {localProps.display.id}</div>
</div>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-2">
@@ -196,7 +199,7 @@ export const LedCountControlPanel: Component<LedCountControlPanelProps> = (props
</div>
<div class="text-xs text-base-content/50 mt-2 p-1.5 bg-base-300/50 rounded">
💡 +/- LED0-1000
💡 {t('ledConfig.controlTip')}
</div>
</div>
</div>

View File

@@ -13,9 +13,11 @@ import {
LedStripConfigurationContext,
LedStripConfigurationContextType,
} from '../../contexts/led-strip-configuration.context';
import { useLanguage } from '../../i18n/index';
export const LedStripConfiguration = () => {
const { t } = useLanguage();
createEffect(() => {
invoke<string>('list_display_info').then((displays) => {
const parsedDisplays = JSON.parse(displays);
@@ -106,10 +108,10 @@ export const LedStripConfiguration = () => {
return (
<div class="space-y-4">
<div class="flex items-center justify-between">
<h1 class="text-xl font-bold text-base-content"></h1>
<h1 class="text-xl font-bold text-base-content">{t('ledConfig.title')}</h1>
<div class="stats shadow">
<div class="stat py-2 px-4">
<div class="stat-title text-xs"></div>
<div class="stat-title text-xs">{t('displays.displayCount')}</div>
<div class="stat-value text-primary text-lg">{displayStore.displays.length}</div>
</div>
</div>
@@ -121,12 +123,12 @@ export const LedStripConfiguration = () => {
<div class="card bg-base-200 shadow-lg">
<div class="card-body p-3">
<div class="card-title text-sm mb-2">
<span></span>
<div class="badge badge-info badge-outline text-xs"></div>
<span>{t('ledConfig.stripSorting')}</span>
<div class="badge badge-info badge-outline text-xs">{t('ledConfig.realtimePreview')}</div>
</div>
<LedStripPartsSorter />
<div class="text-xs text-base-content/50 mt-2">
💡
💡 {t('ledConfig.sortingTip')}
</div>
</div>
</div>
@@ -135,8 +137,8 @@ export const LedStripConfiguration = () => {
<div class="card bg-base-200 shadow-lg">
<div class="card-body p-3">
<div class="card-title text-sm mb-2">
<span></span>
<div class="badge badge-secondary badge-outline text-xs"></div>
<span>{t('ledConfig.displayConfiguration')}</span>
<div class="badge badge-secondary badge-outline text-xs">{t('ledConfig.visualEditor')}</div>
</div>
<div class="mb-3">
<DisplayListContainer>
@@ -146,7 +148,7 @@ export const LedStripConfiguration = () => {
</DisplayListContainer>
</div>
<div class="text-xs text-base-content/50">
💡 使LED数量
💡 {t('ledConfig.displayTip')}
</div>
</div>
</div>
@@ -154,8 +156,8 @@ export const LedStripConfiguration = () => {
{/* LED Count Control Panels */}
<div class="flex-shrink-0">
<div class="flex items-center gap-2 mb-2">
<h2 class="text-base font-semibold text-base-content">LED数量控制</h2>
<div class="badge badge-info badge-outline text-xs"></div>
<h2 class="text-base font-semibold text-base-content">{t('ledConfig.ledCountControl')}</h2>
<div class="badge badge-info badge-outline text-xs">{t('ledConfig.realtimeAdjustment')}</div>
</div>
<div class="led-control-grid">
{displayStore.displays.map((display) => (

View File

@@ -1,6 +1,7 @@
import { createSignal, createEffect, For, Show, onCleanup } from 'solid-js';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import { useLanguage } from '../../i18n/index';
interface BoardInfo {
fullname: string;
@@ -24,6 +25,7 @@ interface TestEffectConfig {
}
export const LedStripTest = () => {
const { t } = useLanguage();
const [boards, setBoards] = createSignal<BoardInfo[]>([]);
const [selectedBoard, setSelectedBoard] = createSignal<BoardInfo | null>(null);
const [ledCount, setLedCount] = createSignal(60);
@@ -100,23 +102,23 @@ export const LedStripTest = () => {
// Test patterns
const testPatterns: TestPattern[] = [
{
name: '流光效果',
description: '彩虹色流光,用于测试灯带方向',
name: t('ledTest.flowingRainbow'),
description: t('ledTest.flowingRainbowDesc'),
effect_type: 'FlowingRainbow'
},
{
name: '十个一组计数',
description: '每十个LED一组不同颜色用于快速计算灯珠数量',
name: t('ledTest.groupCounting'),
description: t('ledTest.groupCountingDesc'),
effect_type: 'GroupCounting'
},
{
name: '单色扫描',
description: '单个LED依次点亮用于精确测试每个LED位置',
name: t('ledTest.singleScan'),
description: t('ledTest.singleScanDesc'),
effect_type: 'SingleScan'
},
{
name: '呼吸灯',
description: '整条灯带呼吸效果,用于测试整体亮度',
name: t('ledTest.breathing'),
description: t('ledTest.breathingDesc'),
effect_type: 'Breathing'
}
];

View File

@@ -10,6 +10,7 @@ import { BiRegularReset } from 'solid-icons/bi';
import { BsFullscreen, BsFullscreenExit } from 'solid-icons/bs';
import { getCurrentWindow } from '@tauri-apps/api/window';
import transparentBg from '../../assets/transparent-grid-background.svg?url';
import { useLanguage } from '../../i18n/index';
const Value: Component<{ value: number }> = (props) => {
return (
@@ -20,6 +21,7 @@ const Value: Component<{ value: number }> = (props) => {
};
export const WhiteBalance = () => {
const { t } = useLanguage();
const [isFullscreen, setIsFullscreen] = createSignal(false);
const [panelPosition, setPanelPosition] = createSignal({ x: 0, y: 0 });
const [isDragging, setIsDragging] = createSignal(false);
@@ -170,19 +172,19 @@ export const WhiteBalance = () => {
{!isFullscreen() && (
<div class="space-y-6">
<div class="flex items-center justify-between">
<h1 class="text-2xl font-bold text-base-content"></h1>
<h1 class="text-2xl font-bold text-base-content">{t('whiteBalance.title')}</h1>
<div class="flex gap-2">
<button class="btn btn-outline btn-sm" onClick={toggleFullscreen} title="进入全屏">
<button class="btn btn-outline btn-sm" onClick={toggleFullscreen} title={t('common.fullscreen')}>
<BsFullscreen size={16} />
{t('common.fullscreen')}
</button>
<button class="btn btn-outline btn-sm" onClick={reset} title="重置到100%">
<button class="btn btn-outline btn-sm" onClick={reset} title={t('common.reset')}>
<BiRegularReset size={16} />
{t('common.reset')}
</button>
<button class="btn btn-primary btn-sm" onClick={exit} title="返回">
<button class="btn btn-primary btn-sm" onClick={exit} title={t('whiteBalance.back')}>
<VsClose size={16} />
{t('whiteBalance.back')}
</button>
</div>
</div>
@@ -192,8 +194,8 @@ export const WhiteBalance = () => {
<div class="card bg-base-200 shadow-lg">
<div class="card-body p-4">
<div class="card-title text-base mb-3">
<span></span>
<div class="badge badge-info badge-outline"></div>
<span>{t('whiteBalance.colorTest')}</span>
<div class="badge badge-info badge-outline">{t('whiteBalance.clickToTest')}</div>
</div>
<div
class="aspect-square rounded-lg overflow-hidden border border-base-300"
@@ -204,7 +206,7 @@ export const WhiteBalance = () => {
<TestColorsBg />
</div>
<div class="text-xs text-base-content/50 mt-2">
💡
💡 {t('whiteBalance.colorTestTip')}
</div>
</div>
</div>
@@ -213,14 +215,14 @@ export const WhiteBalance = () => {
<div class="card bg-base-200 shadow-lg">
<div class="card-body p-4">
<div class="card-title text-base mb-3">
<span>RGB调节</span>
<div class="badge badge-secondary badge-outline"></div>
<span>{t('whiteBalance.rgbAdjustment')}</span>
<div class="badge badge-secondary badge-outline">{t('whiteBalance.realtimeAdjustment')}</div>
</div>
<div class="space-y-4">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-red-500"> (R)</span>
<span class="label-text font-semibold text-red-500">{t('whiteBalance.redChannel')}</span>
<Value value={ledStripStore.colorCalibration.r} />
</label>
<ColorSlider
@@ -237,7 +239,7 @@ export const WhiteBalance = () => {
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-green-500">绿 (G)</span>
<span class="label-text font-semibold text-green-500">{t('whiteBalance.greenChannel')}</span>
<Value value={ledStripStore.colorCalibration.g} />
</label>
<ColorSlider
@@ -254,7 +256,7 @@ export const WhiteBalance = () => {
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-blue-500"> (B)</span>
<span class="label-text font-semibold text-blue-500">{t('whiteBalance.blueChannel')}</span>
<Value value={ledStripStore.colorCalibration.b} />
</label>
<ColorSlider
@@ -271,7 +273,7 @@ export const WhiteBalance = () => {
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-amber-500"> (W)</span>
<span class="label-text font-semibold text-amber-500">{t('whiteBalance.whiteChannel')}</span>
<Value value={ledStripStore.colorCalibration.w} />
</label>
<ColorSlider
@@ -291,37 +293,37 @@ export const WhiteBalance = () => {
<div class="collapse collapse-arrow bg-base-100 mt-4">
<input type="checkbox" />
<div class="collapse-title text-sm font-medium text-base-content/80">
💡 使
💡 {t('whiteBalance.usageInstructions')}
</div>
<div class="collapse-content text-xs text-base-content/70 space-y-3">
<div class="space-y-2">
<p class="font-semibold text-primary">🎯 使</p>
<p class="font-semibold text-primary">{t('whiteBalance.recommendedMethod')}</p>
<ol class="list-decimal list-inside space-y-1 ml-2">
<li>"全屏"</li>
<li></li>
<li>{t('whiteBalance.fullscreenTip')}</li>
<li>{t('whiteBalance.dragTip')}</li>
<li>RGB控制面板拖拽到合适位置</li>
<li>LED灯条颜色与屏幕边缘颜色</li>
</ol>
</div>
<div class="space-y-2">
<p class="font-semibold text-secondary">🔧 </p>
<p class="font-semibold text-secondary">{t('whiteBalance.adjustmentTips')}</p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li><span class="text-red-500 font-medium"></span>R值LED会减少红色成分</li>
<li><span class="text-green-500 font-medium">绿</span>G值LED会减少绿色成分</li>
<li><span class="text-blue-500 font-medium"></span>B值LED会减少蓝色成分</li>
<li><span class="text-base-content font-medium"></span>B值R/G值</li>
<li><span class="text-base-content font-medium"></span>B值R/G值</li>
<li>{t('whiteBalance.redStrong')}</li>
<li>{t('whiteBalance.greenStrong')}</li>
<li>{t('whiteBalance.blueStrong')}</li>
<li>{t('whiteBalance.whiteYellow')}</li>
<li>{t('whiteBalance.whiteBlue')}</li>
</ul>
</div>
<div class="space-y-2">
<p class="font-semibold text-accent">📋 </p>
<p class="font-semibold text-accent">{t('whiteBalance.comparisonMethod')}</p>
<ul class="list-disc list-inside space-y-1 ml-2">
<li>LED白光与屏幕白色一致</li>
<li>LED颜色饱和度合适</li>
<li></li>
<li>"重置"</li>
<li>{t('whiteBalance.whiteComparison')}</li>
<li>{t('whiteBalance.colorComparison')}</li>
<li>{t('whiteBalance.environmentTest')}</li>
<li>{t('whiteBalance.resetNote')}</li>
</ul>
</div>
</div>
@@ -356,10 +358,10 @@ export const WhiteBalance = () => {
>
<div class="flex items-center gap-2">
<span class="text-xs opacity-60"></span>
<span>RGB调节</span>
<div class="badge badge-secondary badge-outline"></div>
<span>{t('whiteBalance.rgbAdjustment')}</span>
<div class="badge badge-secondary badge-outline">{t('whiteBalance.draggable')}</div>
</div>
<button class="btn btn-ghost btn-xs cursor-pointer" onClick={toggleFullscreen} title="退出全屏">
<button class="btn btn-ghost btn-xs cursor-pointer" onClick={toggleFullscreen} title={t('whiteBalance.exitFullscreen')}>
<BsFullscreenExit size={14} />
</button>
</div>
@@ -367,7 +369,7 @@ export const WhiteBalance = () => {
<div class="space-y-4">
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-red-500"> (R)</span>
<span class="label-text font-semibold text-red-500">{t('whiteBalance.redChannel')}</span>
<Value value={ledStripStore.colorCalibration.r} />
</label>
<ColorSlider
@@ -384,7 +386,7 @@ export const WhiteBalance = () => {
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-green-500">绿 (G)</span>
<span class="label-text font-semibold text-green-500">{t('whiteBalance.greenChannel')}</span>
<Value value={ledStripStore.colorCalibration.g} />
</label>
<ColorSlider
@@ -401,7 +403,7 @@ export const WhiteBalance = () => {
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-blue-500"> (B)</span>
<span class="label-text font-semibold text-blue-500">{t('whiteBalance.blueChannel')}</span>
<Value value={ledStripStore.colorCalibration.b} />
</label>
<ColorSlider
@@ -418,7 +420,7 @@ export const WhiteBalance = () => {
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-amber-500"> (W)</span>
<span class="label-text font-semibold text-amber-500">{t('whiteBalance.whiteChannel')}</span>
<Value value={ledStripStore.colorCalibration.w} />
</label>
<ColorSlider
@@ -435,25 +437,25 @@ export const WhiteBalance = () => {
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-base-content/70"> (W)</span>
<div class="badge badge-outline badge-sm"></div>
<span class="label-text font-semibold text-base-content/70">{t('whiteBalance.whiteChannel')}</span>
<div class="badge badge-outline badge-sm">{t('whiteBalance.notEnabled')}</div>
</label>
<ColorSlider class="from-yellow-50 to-cyan-50" disabled />
</div>
</div>
<div class="text-xs text-base-content/60 mt-3 p-2 bg-base-300/50 rounded">
💡 LED灯条RGB滑块使颜色一致
💡 {t('whiteBalance.fullscreenComparisonTip')}
</div>
<div class="flex gap-2 mt-4">
<button class="btn btn-outline btn-sm flex-1" onClick={reset} title="重置到100%">
<button class="btn btn-outline btn-sm flex-1" onClick={reset} title={t('common.reset')}>
<BiRegularReset size={14} />
{t('common.reset')}
</button>
<button class="btn btn-primary btn-sm flex-1" onClick={exit} title="返回">
<button class="btn btn-primary btn-sm flex-1" onClick={exit} title={t('whiteBalance.back')}>
<VsClose size={14} />
{t('whiteBalance.back')}
</button>
</div>
</div>