- {/* ── 显示 ── */}
-
-
- goSelect("屏幕亮度", SCREEN_LIGHT_OPTIONS, screenLightIdx, "screenLight")} />
- goSelect("自动锁屏", SCREEN_OFF_OPTIONS, screenOffIdx, "screenOff")} />
-
-
- {/* ── 灯光 ── */}
-
-
- goSelect("旋钮亮度", KNOB_LIGHT_OPTIONS, knobLightIdx, "knob_breathlight")} />
- 0}
- onToggle={(v) => updateSetting({ knob_breathlight: v ? 2 : 0 })} />
- updateSetting({ buttonLight: v ? 0 : 1 })} />
-
-
- {/* ── 电源管理 ── */}
-
-
- goSelect("休眠时间", SLEEP_OPTIONS, sleepIdx, "sleep")} />
-
-
- {/* ── 灯光预览 ── */}
-
-
-
- {/* Knob preview */}
-
-
-
0
- ? `0 0 ${knobLightIdx * 10}px rgba(0,255,246,${knobLightIdx * 0.2})`
- : "none",
- }} />
-
0 ? `inset 0 0 8px rgba(0,255,246,0.15)` : "none",
- }} />
-
-
旋钮
-
- {/* Button preview */}
-
-
- {[1, 2, 3].map(i => (
-
- ))}
-
-
按键
-
-
-
-
-
-
-
- );
-}
diff --git a/client/src/pages/DspPage.tsx b/client/src/pages/DspPage.tsx
deleted file mode 100644
index 1b168de..0000000
--- a/client/src/pages/DspPage.tsx
+++ /dev/null
@@ -1,145 +0,0 @@
-/* ============================================================
- DSP & DAC PAGE
- Design: iOS Settings style
- Sections: DSP引擎 / DAC配置 / 模拟级 / 音频格式
- All selectable options → navigate to /select page
- ============================================================ */
-import { useDevice } from "@/contexts/DeviceContext";
-import { ChevronLeft, ChevronRight } from "lucide-react";
-import { useLocation } from "wouter";
-import BottomNav from "@/components/BottomNav";
-import { navigateToSelect } from "./SelectPage";
-
-function IOSToggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
- return (
-
- );
-}
-
-function SectionHeader({ title }: { title: string }) {
- return (
-
- {title}
-
- );
-}
-
-function ListRow({ label, description, value, onClick, toggle, checked, onToggle }: {
- label: string; description?: string; value?: string;
- onClick?: () => void; toggle?: boolean; checked?: boolean; onToggle?: (v: boolean) => void;
-}) {
- if (toggle) {
- return (
-
-
-
{label}
- {description &&
{description}
}
-
-
{})} />
-
- );
- }
- return (
-
- );
-}
-
-const DAC_ARC_OPTIONS = ["关闭", "模式1", "模式2", "模式3"];
-const DAC_GAIN_OPTIONS = ["0dB", "+3dB", "+6dB", "+9dB"];
-const DAC_IMP_OPTIONS = ["32Ω", "150Ω", "300Ω"];
-const ANALOG_GAIN_OPTIONS = ["低", "中低", "中高", "高"];
-const XLR_OPTIONS = ["正常", "反转"];
-
-export default function DspPage() {
- const [, setLocation] = useLocation();
- const { deviceState, updateSetting } = useDevice();
- const ds = deviceState;
-
- const dspOn = (ds?.dsp_enable ?? 0) === 0;
- const peqOn = (ds?.peqEnable ?? 0) === 1;
- const dacArcIdx = ds?.dacArc ?? 0;
- const dacGainIdx = ds?.dacGain ?? 0;
- const dacImpIdx = ds?.dacImpedance ?? 0;
- const analogGainIdx = ds?.analogGain ?? 0;
- const xlrIdx = ds?.xlr ?? 0;
-
- const goSelect = (title: string, options: string[], selected: number, key: string) => {
- navigateToSelect(title, options, selected, "/dsp", key);
- setLocation("/select");
- };
-
- return (
-
-
-
-
DSP & DAC
-
-
-
-
- {/* ── DSP 引擎 ── */}
-
-
- updateSetting({ dsp_enable: v ? 0 : 1 })} />
- updateSetting({ peqEnable: v ? 1 : 0 })} />
-
-
- {/* ── DAC 配置 ── */}
-
-
- goSelect("ARC 模式", DAC_ARC_OPTIONS, dacArcIdx, "dacArc")} />
- goSelect("DAC 增益", DAC_GAIN_OPTIONS, dacGainIdx, "dacGain")} />
- goSelect("DAC 阻抗", DAC_IMP_OPTIONS, dacImpIdx, "dacImpedance")} />
-
-
- {/* ── 模拟级 ── */}
-
-
- goSelect("模拟增益", ANALOG_GAIN_OPTIONS, analogGainIdx, "analogGain")} />
- goSelect("XLR 极性", XLR_OPTIONS, xlrIdx, "xlr")} />
-
-
- {/* ── 音频格式 ── */}
-
-
-
-
-
- {ds?.audioFormat ?? "PCM 44.1kHz"}
-
-
-
-
-
-
-
- );
-}
diff --git a/client/src/pages/EQPage.tsx b/client/src/pages/EQPage.tsx
index 24f86cd..899727f 100644
--- a/client/src/pages/EQPage.tsx
+++ b/client/src/pages/EQPage.tsx
@@ -29,8 +29,9 @@ import {
} from "@/lib/luxsinApi";
import { getFilterType, getFilterShortName } from "@/lib/peqAudio";
import localeZh from "@/locales/data-zh.json";
-import localeZhHK from "@/locales/data-zh-HK.json";
import localeEn from "@/locales/data-en.json";
+import { resolveLocalePack } from "@/locales/resolveLocale";
+import { usePageTitleWithConnection } from "@/hooks/usePageTitleWithConnection";
import { FreqChart } from "./eq/components/FreqChart";
import { CyanSlider, IOSToggle } from "./eq/components/EqPrimitives";
import { BandParamDialog } from "./eq/components/BandParamDialog";
@@ -95,18 +96,18 @@ export default function EQPage() {
const eqOn = peqOn && !bypassOn;
const eqUi = useMemo((): PeqEqUi => {
- const lang = deviceState?.language;
- const pack = lang === 0 ? localeEn : lang === 1 ? localeZhHK : localeZh;
+ const pack = resolveLocalePack(deviceState?.language);
const peq = pack.peq as typeof localeZh.peq;
- return (peq.eqUi ?? (localeZh.peq as typeof localeZh.peq).eqUi) as PeqEqUi;
+ return (peq.eqUi ?? (localeEn.peq as typeof localeEn.peq).eqUi) as PeqEqUi;
}, [deviceState?.language]);
const peqCardLabels = useMemo(() => {
- const lang = deviceState?.language;
- const pack = lang === 0 ? localeEn : lang === 1 ? localeZhHK : localeZh;
- return pack.peq ?? localeZh.peq;
+ const pack = resolveLocalePack(deviceState?.language);
+ return pack.peq ?? localeEn.peq;
}, [deviceState?.language]);
+ const eqPageTitle = usePageTitleWithConnection("HP-EQ");
+
const [bandsByMode, setBandsByMode] = useState
>({
A: cloneBands(DEFAULT_BANDS),
B: cloneBands(DEFAULT_BANDS),
@@ -1310,7 +1311,7 @@ export default function EQPage() {
- HP-EQ
+ {eqPageTitle}
{
diff --git a/client/src/pages/EffectsPage.tsx b/client/src/pages/EffectsPage.tsx
index 594bb12..7a36e45 100644
--- a/client/src/pages/EffectsPage.tsx
+++ b/client/src/pages/EffectsPage.tsx
@@ -15,8 +15,9 @@ import { FeatureGate } from "@/components/FeatureGate";
import { navigateToSelect } from "./SelectPage";
import { cn } from "@/lib/utils";
import localeZh from "@/locales/data-zh.json";
-import localeZhHK from "@/locales/data-zh-HK.json";
import localeEn from "@/locales/data-en.json";
+import { resolveLocalePack } from "@/locales/resolveLocale";
+import { usePageTitleWithConnection } from "@/hooks/usePageTitleWithConnection";
type EffectLocale = NonNullable<(typeof localeZh)["effect"]>;
@@ -96,11 +97,12 @@ export default function EffectsPage() {
const { deviceState, updateSetting } = useDevice();
const effectText = useMemo((): EffectLocale => {
- const lang = deviceState?.language;
- const pack = lang === 0 ? localeEn : lang === 1 ? localeZhHK : localeZh;
- return (pack.effect ?? localeZh.effect) as EffectLocale;
+ const pack = resolveLocalePack(deviceState?.language);
+ return (pack.effect ?? localeEn.effect) as EffectLocale;
}, [deviceState?.language]);
+ const pageTitle = usePageTitleWithConnection(effectText.pageTitle ?? "Effects");
+
const sceneLabels = useMemo(() => {
const opts = effectText.style?.options ?? [];
return [...opts].sort((a, b) => a.index - b.index).map((o) => o.label);
@@ -180,7 +182,7 @@ export default function EffectsPage() {
- {effectText.pageTitle}
+ {pageTitle}
{
diff --git a/client/src/pages/IOPage.tsx b/client/src/pages/IOPage.tsx
index 0f87638..de7afeb 100644
--- a/client/src/pages/IOPage.tsx
+++ b/client/src/pages/IOPage.tsx
@@ -12,8 +12,9 @@ import { ChevronLeft } from "lucide-react";
import { useLocation } from "wouter";
import { useMemo } from "react";
import localeZh from "@/locales/data-zh.json";
-import localeZhHK from "@/locales/data-zh-HK.json";
import localeEn from "@/locales/data-en.json";
+import { resolveLocalePack } from "@/locales/resolveLocale";
+import { usePageTitleWithConnection } from "@/hooks/usePageTitleWithConnection";
type SourceLocale = NonNullable<(typeof localeZh)["source"]>;
@@ -180,8 +181,7 @@ export default function IOPage() {
const { deviceState, setInput, setOutput } = useDevice();
const sourceText = useMemo((): SourceLocale => {
- const lang = deviceState?.language;
- const pack = lang === 0 ? localeEn : lang === 1 ? localeZhHK : localeZh;
+ const pack = resolveLocalePack(deviceState?.language);
return (pack.source ?? {}) as SourceLocale;
}, [deviceState?.language]);
@@ -195,7 +195,9 @@ export default function IOPage() {
[sourceText.outputOptions],
);
- const ioPageTitle = sourceText.pageTitle ?? sourceText.label ?? "输入输出";
+ const ioPageTitle = usePageTitleWithConnection(
+ sourceText.pageTitle ?? sourceText.label ?? "I/O",
+ );
const inputSectionTitle = sourceText.inputSection ?? "输入";
const outputSectionTitle = sourceText.outputSection ?? "输出";
diff --git a/client/src/pages/SelectPage.tsx b/client/src/pages/SelectPage.tsx
index 7a904da..03410dc 100644
--- a/client/src/pages/SelectPage.tsx
+++ b/client/src/pages/SelectPage.tsx
@@ -26,8 +26,9 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import localeZh from "@/locales/data-zh.json";
-import localeZhHK from "@/locales/data-zh-HK.json";
import localeEn from "@/locales/data-en.json";
+import { resolveLocalePack } from "@/locales/resolveLocale";
+import { usePageTitleWithConnection } from "@/hooks/usePageTitleWithConnection";
type DacVolumeDirectConfirmLocale = NonNullable<
NonNullable<(typeof localeZh)["dac"]>["dacVolumeDirectConfirm"]
@@ -98,10 +99,11 @@ export default function SelectPage() {
const [saving, setSaving] = useState(false);
const [volumeDirectConfirmIdx, setVolumeDirectConfirmIdx] = useState<1 | 2 | null>(null);
+ const pageTitle = usePageTitleWithConnection(state?.title ?? "");
+
const volumeDirectConfirmText = useMemo((): DacVolumeDirectConfirmLocale => {
- const lang = deviceState?.language;
- const pack = lang === 0 ? localeEn : lang === 1 ? localeZhHK : localeZh;
- return (pack.dac?.dacVolumeDirectConfirm ?? localeZh.dac!.dacVolumeDirectConfirm) as DacVolumeDirectConfirmLocale;
+ const pack = resolveLocalePack(deviceState?.language);
+ return (pack.dac?.dacVolumeDirectConfirm ?? localeEn.dac!.dacVolumeDirectConfirm) as DacVolumeDirectConfirmLocale;
}, [deviceState?.language]);
// Re-read global state whenever we enter /select (avoids stale key/options after remount)
@@ -172,7 +174,7 @@ export default function SelectPage() {
className="mr-4 text-white/60 active:text-white transition-colors">
- {title}
+ {pageTitle}
diff --git a/client/src/pages/SystemPage.tsx b/client/src/pages/SystemPage.tsx
index 0170243..b2e7c92 100644
--- a/client/src/pages/SystemPage.tsx
+++ b/client/src/pages/SystemPage.tsx
@@ -20,9 +20,9 @@ import {
parseFirmwareVersion,
supportsAmbientLed,
} from "@/lib/luxsinApi";
-import localeZh from "@/locales/data-zh.json";
-import localeZhHK from "@/locales/data-zh-HK.json";
import localeEn from "@/locales/data-en.json";
+import { DEFAULT_DEVICE_LANGUAGE, resolveDeviceLanguage, resolveLocalePack } from "@/locales/resolveLocale";
+import { usePageTitleWithConnection } from "@/hooks/usePageTitleWithConnection";
function IOSToggle({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
return (
@@ -160,12 +160,6 @@ type LocaleSettings = {
};
};
-const LOCALE_BY_LANG: Record