适配新语言

This commit is contained in:
eafonyang
2026-10-22 13:50:27 +08:00
parent e89dca8c2f
commit ff62849d18
18 changed files with 4492 additions and 73 deletions
+3 -3
View File
@@ -99,7 +99,7 @@ export default function BottomNav() {
const [location, setLocation] = useLocation();
const isLgUp = useIsLgUp();
const { open: aiDrawerOpen, setOpen: setAiDrawerOpen } = useAIDrawer();
const { deviceState } = useDevice();
const { effectiveLanguage } = useDevice();
const { isNavHidden } = useNavConfig();
const festival = useFestival();
@@ -110,9 +110,9 @@ export default function BottomNav() {
);
const navText = useMemo((): BottomNavLocale => {
const pack = resolveLocalePack(deviceState?.language);
const pack = resolveLocalePack(effectiveLanguage);
return (pack.bottomNav ?? localeEn.bottomNav) as BottomNavLocale;
}, [deviceState?.language]);
}, [effectiveLanguage]);
return (
<nav
+10 -10
View File
@@ -4,18 +4,18 @@ import { isLikelyNetworkFetchError } from "@/lib/networkError";
import { Loader2, Wifi } from "lucide-react";
import BottomNav from "@/components/BottomNav";
import localeZh from "@/locales/data-zh.json";
import localeZhHK from "@/locales/data-zh-HK.json";
import localeEn from "@/locales/data-en.json";
import { getUiLanguageOverride, isKnownLanguage, resolveLocalePack } from "@/locales/resolveLocale";
type ConnectLocale = (typeof localeZh)["connect"];
function pickConnectLocale(): ConnectLocale {
function pickConnectLocale(effectiveLanguage: number): ConnectLocale {
// 前端语言覆盖(de/es/fr)优先;否则沿用 URL ?lang=(设备语言);最后回退到生效语言。
const override = getUiLanguageOverride();
if (override != null) return resolveLocalePack(override).connect as ConnectLocale;
const params = new URLSearchParams(window.location.search);
const lang = params.get("lang");
if (lang === "0") return localeEn.connect;
if (lang === "1") return localeZhHK.connect;
if (lang === "2") return localeZh.connect;
return localeEn.connect;
const langParam = Number(params.get("lang"));
const lang = isKnownLanguage(langParam) ? langParam : effectiveLanguage;
return resolveLocalePack(lang).connect as ConnectLocale;
}
function fmt(template: string, vars: Record<string, string>): string {
@@ -28,8 +28,8 @@ function fmt(template: string, vars: Record<string, string>): string {
/** 自动连接设备时的占位(替代原 ConnectScreen) */
export default function ConnectionPlaceholder() {
const { ip, isConnecting, error, ipMissing } = useDevice();
const text = useMemo(pickConnectLocale, []);
const { ip, isConnecting, error, ipMissing, effectiveLanguage } = useDevice();
const text = useMemo(() => pickConnectLocale(effectiveLanguage), [effectiveLanguage]);
return (
<div className="min-h-screen bg-black flex flex-col items-center justify-center px-6 pb-24">
+52
View File
@@ -12,6 +12,12 @@ import {
} from "@/lib/luxsinApi";
import { getDeviceIpFromUrl, resolveInitialDeviceIp } from "@/lib/deviceIp";
import { isLikelyNetworkFetchError } from "@/lib/networkError";
import {
DEFAULT_DEVICE_LANGUAGE,
getUiLanguageOverride,
isDeviceLanguage,
setUiLanguageOverride,
} from "@/locales/resolveLocale";
interface DeviceContextType {
ip: string;
@@ -22,6 +28,16 @@ interface DeviceContextType {
isDemoMode: boolean;
setDemoMode: (demo: boolean) => void;
deviceState: DeviceState | null;
/**
* 生效的界面显示语言索引(0-9)。前端语言覆盖(非设备语言 3-9)优先,
* 否则跟随设备真实语言(0/1/2)。所有界面文案都应基于此值解析语言包。
*/
effectiveLanguage: number;
/**
* 设置界面语言:选择设备语言(0/1/2)会下发设备并清除前端覆盖;
* 选择其他语言(3-9)仅做前端切换并持久化,绝不下发设备。
*/
setUiLanguage: (index: number) => Promise<void>;
peqState: PeqState | null;
lastUpdated: Date | null;
error: string | null;
@@ -56,6 +72,10 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
const [error, setError] = useState<string | null>(null);
const [api, setApi] = useState<LuxsinAPI | null>(null);
// 前端语言覆盖(仅非设备语言 3-9),初始值从 localStorage 恢复;null 表示跟随设备语言。
const [uiLanguage, setUiLanguageState] = useState<number | null>(() => getUiLanguageOverride());
// 记录上一次设备真实语言,用于检测设备侧语言变化(如轮询/刷新)后清除前端覆盖。
const prevDeviceLanguageRef = useRef<number | undefined>(undefined);
const lastMsgCountRef = useRef<number>(-1);
const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const autoConnectStartedRef = useRef(false);
@@ -177,6 +197,37 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
setDeviceState(prev => prev ? { ...prev, ...params } : prev);
}, [api, isDemoMode]);
// 生效的界面语言:前端覆盖优先,其次设备真实语言,最后默认。
const effectiveLanguage = uiLanguage ?? deviceState?.language ?? DEFAULT_DEVICE_LANGUAGE;
const setUiLanguage = useCallback(async (index: number) => {
if (isDeviceLanguage(index)) {
// 设备支持的语言:清除前端覆盖并下发设备(沿用既有 setting 流程)。
setUiLanguageOverride(null);
setUiLanguageState(null);
await updateSetting({ language: index });
return;
}
// 非设备语言(3-9):仅前端切换 + 持久化,不下发设备。
setUiLanguageOverride(index);
setUiLanguageState(getUiLanguageOverride());
}, [updateSetting]);
// 设备侧语言发生变化(用户在设备上切换语言,经 msgCount 轮询/刷新同步下来)时,
// 清除前端语言覆盖,让 controller 跟随设备语言。
// 说明:仅在设备语言「值发生变化」时触发;首次观测(prev 为 undefined)不清除,
// 以保留跨会话持久化的前端覆盖(如上次选择了德语,刷新后仍应为德语)。
useEffect(() => {
const devLang = deviceState?.language;
if (devLang === undefined) return;
const prev = prevDeviceLanguageRef.current;
prevDeviceLanguageRef.current = devLang;
if (prev !== undefined && prev !== devLang) {
setUiLanguageOverride(null);
setUiLanguageState(null);
}
}, [deviceState?.language]);
const updatePeq = useCallback(async (filters: PeqFilter[]) => {
if (isDemoMode) {
setPeqState({ filters });
@@ -285,6 +336,7 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
deviceState, peqState, lastUpdated, error,
connect, disconnect, refresh, api,
updateSetting, updatePeq, upgradePeqChange, upgradePeqApply, syncPeqCatalog,
effectiveLanguage, setUiLanguage,
setVolume, setInput, setOutput, setBalance,
}}>
{children}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+106 -6
View File
@@ -1,19 +1,119 @@
import localeEn from "./data-en.json";
import localeZh from "./data-zh.json";
import localeZhHK from "./data-zh-HK.json";
import localeDe from "./data-de.json";
import localeEs from "./data-es.json";
import localeFr from "./data-fr.json";
import localeKo from "./data-ko.json";
import localeVi from "./data-vi.json";
import localePt from "./data-pt.json";
import localePl from "./data-pl.json";
/** Device language index: 0=en, 1=zh-HK, 2=zh-CN */
/**
* 语言索引映射:
* 0=en, 1=zh-HK, 2=zh-CN → 设备固件支持,选择后会下发 dev/info.cgi?action=setting&language=
* 3=de, 4=es, 5=fr, 6=ko, 7=vi, 8=pt, 9=pl → 设备不支持,仅在 controller 前端做界面语言切换,绝不下发设备
*/
export const DEFAULT_DEVICE_LANGUAGE = 0;
/** 设备固件真正支持的语言索引,只有这些允许下发到设备。 */
export const DEVICE_LANGUAGE_INDICES = [0, 1, 2] as const;
export type LocalePack = typeof localeEn;
const LOCALE_PACKS = [
localeEn,
localeZhHK,
localeZh,
localeDe,
localeEs,
localeFr,
localeKo,
localeVi,
localePt,
localePl,
] as LocalePack[];
/** 语言选择器可展示的全部语言数量(含仅前端语言)。 */
export const LANGUAGE_COUNT = LOCALE_PACKS.length;
/**
* 语言选择器固定显示的「母语名称」,与当前界面语言无关。
* 数组下标即语言索引(0=en … 9=pl),选择时直接作为语言值使用。
*/
export const LANGUAGE_NATIVE_LABELS = [
"English",
"繁體中文",
"简体中文",
"Deutsch",
"Español",
"Français",
"한국어",
"Tiếng Việt",
"Português (Portugal)",
"Polski",
];
/** 是否为设备支持的语言(0/1/2),只有这些才会下发设备。 */
export function isDeviceLanguage(index: number | undefined | null): boolean {
return index === 0 || index === 1 || index === 2;
}
/** 是否为已知的语言索引(0..LANGUAGE_COUNT-1)。 */
export function isKnownLanguage(index: number | undefined | null): boolean {
return typeof index === "number" && index >= 0 && index < LOCALE_PACKS.length;
}
/** 设备真实语言(不含前端覆盖),仅用于需要区分设备语言的场景。 */
export function resolveDeviceLanguage(language: number | undefined): number {
return language ?? DEFAULT_DEVICE_LANGUAGE;
}
export function resolveLocalePack(language: number | undefined): LocalePack {
const lang = resolveDeviceLanguage(language);
if (lang === 0) return localeEn;
if (lang === 1) return localeZhHK;
return localeZh;
/* ── 前端语言覆盖(仅非设备语言 3..9,持久化到 localStorage,绝不下发设备) ── */
const UI_LANGUAGE_OVERRIDE_KEY = "luxsin.uiLanguageOverride";
function readStoredOverride(): number | null {
try {
const raw = localStorage.getItem(UI_LANGUAGE_OVERRIDE_KEY);
if (raw == null) return null;
const n = Number(raw);
return isKnownLanguage(n) && !isDeviceLanguage(n) ? n : null;
} catch {
return null;
}
}
let uiLanguageOverride: number | null = readStoredOverride();
/** 读取当前前端语言覆盖;无覆盖(跟随设备语言)时返回 null。 */
export function getUiLanguageOverride(): number | null {
return uiLanguageOverride;
}
/**
* 设置前端语言覆盖。传入仅前端语言(3..9)时生效并持久化;
* 传入设备语言(0/1/2)或 null 时清除覆盖,恢复跟随设备语言。
*/
export function setUiLanguageOverride(index: number | null): number | null {
uiLanguageOverride =
isKnownLanguage(index) && !isDeviceLanguage(index) ? (index as number) : null;
try {
if (uiLanguageOverride == null) localStorage.removeItem(UI_LANGUAGE_OVERRIDE_KEY);
else localStorage.setItem(UI_LANGUAGE_OVERRIDE_KEY, String(uiLanguageOverride));
} catch {
/* localStorage 不可用时忽略,仅保留内存态 */
}
return uiLanguageOverride;
}
/** 生效的显示语言:前端覆盖优先,其次设备语言,最后默认。 */
export function resolveEffectiveLanguage(language: number | undefined): number {
if (uiLanguageOverride != null) return uiLanguageOverride;
const l = language ?? DEFAULT_DEVICE_LANGUAGE;
return isKnownLanguage(l) ? l : DEFAULT_DEVICE_LANGUAGE;
}
export function resolveLocalePack(language: number | undefined): LocalePack {
const l = language ?? DEFAULT_DEVICE_LANGUAGE;
return LOCALE_PACKS[isKnownLanguage(l) ? l : DEFAULT_DEVICE_LANGUAGE] ?? localeEn;
}
+3 -3
View File
@@ -71,7 +71,7 @@ import { Streamdown } from "streamdown";
import { toast } from "sonner";
import localeZh from "@/locales/data-zh.json";
import localeEn from "@/locales/data-en.json";
import { resolveDeviceLanguage, resolveLocalePack } from "@/locales/resolveLocale";
import { resolveLocalePack } from "@/locales/resolveLocale";
type AILocale = NonNullable<(typeof localeZh)["ai"]>;
@@ -224,9 +224,9 @@ export default function AIPage({ variant = "page", onClose }: AIPageProps = {})
const [routePath, setLocation] = useLocation();
const isLgUp = useIsLgUp();
const { setOpen: setAiPanelOpen } = useAIDrawer();
const { isConnected, deviceState, api, updateSetting } = useDevice();
const { isConnected, deviceState, api, updateSetting, effectiveLanguage } = useDevice();
const mac = deviceState?.mac ?? "";
const language = resolveDeviceLanguage(deviceState?.language);
const language = effectiveLanguage;
const deviceName = deviceState?.device?.trim() || "Luxsin BH9";
const aiText = useMemo(() => resolveAILocale(language), [language]);
+2 -2
View File
@@ -179,10 +179,10 @@ function bootVolumeRowLabel(dacText: LocaleDac["dac"]): string {
export default function AudioPage() {
const [location, setLocation] = useLocation();
const { deviceState, updateSetting, refresh, isConnected, api } = useDevice();
const { deviceState, updateSetting, refresh, isConnected, api, effectiveLanguage } = useDevice();
const ds = deviceState;
const vuSensor = (ds as ({ vuSensor?: number } | null))?.vuSensor;
const localeData = resolveLocalePack(ds?.language) as LocaleDac;
const localeData = resolveLocalePack(effectiveLanguage) as LocaleDac;
const dacText = localeData.dac ?? {};
// balance 从接口获取的是乘以 10 后的值,需要除以 10 显示(例如:-130 → -13.0)
+3 -3
View File
@@ -17,12 +17,12 @@ type HomeLocale = NonNullable<(typeof localeZh)["home"]>;
export default function ChangelogPage() {
const [, setLocation] = useLocation();
const { deviceState } = useDevice();
const { effectiveLanguage } = useDevice();
const homeText = useMemo((): HomeLocale => {
const pack = resolveLocalePack(deviceState?.language);
const pack = resolveLocalePack(effectiveLanguage);
return (pack.home ?? {}) as HomeLocale;
}, [deviceState?.language]);
}, [effectiveLanguage]);
const pageTitle = usePageTitleWithConnection(homeText.changelog ?? "更新说明");
+5 -4
View File
@@ -110,16 +110,17 @@ export default function EQPage() {
upgradePeqChange,
upgradePeqApply,
syncPeqCatalog,
effectiveLanguage,
} = useDevice();
const bypassOn = (deviceState?.dsp_enable ?? 0) === 0;
const peqOn = (deviceState?.peqEnable ?? 0) === 1;
const eqOn = peqOn && !bypassOn;
const eqUi = useMemo((): PeqEqUi => {
const pack = resolveLocalePack(deviceState?.language);
const pack = resolveLocalePack(effectiveLanguage);
const peq = pack.peq as typeof localeZh.peq;
return (peq.eqUi ?? (localeEn.peq as typeof localeEn.peq).eqUi) as PeqEqUi;
}, [deviceState?.language]);
}, [effectiveLanguage]);
const firmwareVersion = useMemo(
() => parseFirmwareVersion(deviceState?.version),
@@ -128,9 +129,9 @@ export default function EQPage() {
const peqReorderEnabled = supportsPeqPresetRenameAndSort(firmwareVersion);
const peqCardLabels = useMemo(() => {
const pack = resolveLocalePack(deviceState?.language);
const pack = resolveLocalePack(effectiveLanguage);
return pack.peq ?? localeEn.peq;
}, [deviceState?.language]);
}, [effectiveLanguage]);
const eqPageTitle = usePageTitleWithConnection("HP-EQ");
+5 -5
View File
@@ -19,7 +19,7 @@ import { parseFirmwareVersion, parseHearingData } from "@/lib/luxsinApi";
import { isFirmwareFeatureAvailable } from "@/config/firmwareFeatures";
import localeZh from "@/locales/data-zh.json";
import localeEn from "@/locales/data-en.json";
import { resolveDeviceLanguage, resolveLocalePack } from "@/locales/resolveLocale";
import { resolveLocalePack } from "@/locales/resolveLocale";
import { usePageTitleWithConnection } from "@/hooks/usePageTitleWithConnection";
import { toast } from "sonner";
@@ -224,12 +224,12 @@ function CyanSlider({ value, min, max, step = 0.1, disabled = false, onChange, o
export default function EffectsPage() {
const [, setLocation] = useLocation();
const { deviceState, updateSetting } = useDevice();
const { deviceState, updateSetting, effectiveLanguage } = useDevice();
const effectText = useMemo((): EffectLocale => {
const pack = resolveLocalePack(deviceState?.language);
const pack = resolveLocalePack(effectiveLanguage);
return (pack.effect ?? localeEn.effect) as EffectLocale;
}, [deviceState?.language]);
}, [effectiveLanguage]);
const pageTitle = usePageTitleWithConnection(effectText.pageTitle ?? "Effects");
@@ -350,7 +350,7 @@ export default function EffectsPage() {
const [delayInputTarget, setDelayInputTarget] = useState<DelayInputTarget | null>(null);
const [delayInputValue, setDelayInputValue] = useState("");
const delayInputEnglish = resolveDeviceLanguage(deviceState?.language) === 0;
const delayInputEnglish = effectiveLanguage === 0;
const maxDelayMs = SUBWOOFER_DELAY_MAX / SUBWOOFER_DELAY_MS_DIVISOR;
const openDelayInput = (target: DelayInputTarget) => {
+4 -7
View File
@@ -69,8 +69,7 @@ import { useLocation } from "wouter";
import { useRef, useState, useEffect, useCallback, useMemo } from "react";
import { toast } from "sonner";
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";
type HomeLocale = NonNullable<(typeof localeZh)["home"]>;
@@ -200,7 +199,7 @@ function ListRow({
}
export default function Home() {
const { isConnected, deviceState, setVolume, updateSetting, api, isDemoMode, ip } = useDevice();
const { isConnected, deviceState, setVolume, updateSetting, api, isDemoMode, ip, effectiveLanguage } = useDevice();
const [, setLocation] = useLocation();
const festival = useFestival();
const isChristmas = festival === "christmas";
@@ -219,10 +218,8 @@ export default function Home() {
const homeText = useMemo((): HomeLocale => {
if (!deviceState) return {} as HomeLocale;
const loc =
deviceState.language === 0 ? localeEn : deviceState.language === 1 ? localeZhHK : localeZh;
return (loc.home ?? {}) as HomeLocale;
}, [deviceState]);
return (resolveLocalePack(effectiveLanguage).home ?? {}) as HomeLocale;
}, [deviceState, effectiveLanguage]);
const volumeConfirmCopy = useMemo(() => {
if (volumeConfirm === "min") {
+3 -3
View File
@@ -142,12 +142,12 @@ function IOCircleButton({
export default function IOPage() {
const [, setLocation] = useLocation();
const { deviceState, setInput, setOutput } = useDevice();
const { deviceState, setInput, setOutput, effectiveLanguage } = useDevice();
const sourceText = useMemo((): SourceLocale => {
const pack = resolveLocalePack(deviceState?.language);
const pack = resolveLocalePack(effectiveLanguage);
return (pack.source ?? {}) as SourceLocale;
}, [deviceState?.language]);
}, [effectiveLanguage]);
const inputOptions = useMemo(
() => withLabels(INPUT_OPTION_DEFS, sourceText.inputOptions, FALLBACK_INPUT_LABELS),
+18 -2
View File
@@ -218,7 +218,7 @@ export default function SelectPage() {
const [location, setLocation] = useLocation();
const { updateSetting, deviceState } = useDevice();
const { updateSetting, effectiveLanguage, setUiLanguage } = useDevice();
const [state, setState] = useState<{
@@ -240,7 +240,7 @@ export default function SelectPage() {
const pageTitle = usePageTitleWithConnection(state?.title ?? "");
const dacVolumeDirectText = resolveLocalePack(deviceState?.language).dac?.dacVolumeDirect;
const dacVolumeDirectText = resolveLocalePack(effectiveLanguage).dac?.dacVolumeDirect;
@@ -318,6 +318,22 @@ export default function SelectPage() {
// 语言选择:设备语言(0/1/2)下发设备,de/es/fr(3/4/5)仅前端切换,
// 由 setUiLanguage 内部区分,避免向不支持多语言的设备下发无效值。
if (key === "language") {
setSaving(true);
try {
await setUiLanguage(idx);
} finally {
setSaving(false);
}
selectPageState = null;
setLocation(back);
return;
}
const apiField = KEY_TO_SETTING[key];
if (apiField) {
+7 -22
View File
@@ -16,7 +16,7 @@ import BottomNav from "@/components/BottomNav";
import { formatFirmwareVersionDisplay } from "@/lib/luxsinApi";
import { navigateToSelect } from "./SelectPage";
import localeEn from "@/locales/data-en.json";
import { DEFAULT_DEVICE_LANGUAGE, resolveDeviceLanguage, resolveLocalePack } from "@/locales/resolveLocale";
import { DEFAULT_DEVICE_LANGUAGE, isKnownLanguage, LANGUAGE_NATIVE_LABELS, resolveLocalePack } from "@/locales/resolveLocale";
import { usePageTitleWithConnection } from "@/hooks/usePageTitleWithConnection";
function SectionHeader({ title }: { title: string }) {
@@ -78,21 +78,9 @@ function optionLabels(items: Array<{ index: number; label: string }> | undefined
return sorted.map((item) => item.label);
}
/** Device language index: 0=en, 1=zh-HK, 2=zh-CN */
const SUPPORTED_LANGUAGE_INDICES = new Set([0, 1, 2]);
function languageLabels(
items: Array<{ index: number; label: string }> | undefined,
fallback: string[],
) {
const filtered = (items ?? []).filter((o) => SUPPORTED_LANGUAGE_INDICES.has(o.index));
if (filtered.length === 0) return fallback;
return [...filtered].sort((a, b) => a.index - b.index).map((o) => o.label);
}
export default function SystemPage() {
const [, setLocation] = useLocation();
const { deviceState, refresh, isConnected, api, ip } = useDevice();
const { deviceState, refresh, isConnected, api, ip, effectiveLanguage } = useDevice();
const ds = deviceState;
// 页面加载且已连接时刷新数据
@@ -106,20 +94,17 @@ export default function SystemPage() {
const screenOffIdx = ds?.screenOff ?? 2;
const sleepIdx = ds?.sleep ?? 0;
const autoHomeIdx = ds?.autoHome ?? 1;
const rawLangIdx = resolveDeviceLanguage(ds?.language);
const langIdx = SUPPORTED_LANGUAGE_INDICES.has(rawLangIdx) ? rawLangIdx : DEFAULT_DEVICE_LANGUAGE;
const localeData = resolveLocalePack(ds?.language) as LocaleSettings;
const langIdx = isKnownLanguage(effectiveLanguage) ? effectiveLanguage : DEFAULT_DEVICE_LANGUAGE;
const localeData = resolveLocalePack(effectiveLanguage) as LocaleSettings;
const settingsText = localeData.settings;
const ui = settingsText.systemPage;
const SCREEN_LIGHT_OPTIONS = optionLabels(settingsText.screenBrightness?.options, ["亮", "中等", "暗"]);
const SCREEN_OFF_OPTIONS = optionLabels(settingsText.turnOffScreen?.options, ["常亮", "30秒", "1分钟", "3分钟", "5分钟"]);
const SLEEP_OPTIONS = optionLabels(settingsText.sleepTime?.options ?? settingsText.sleep?.options, ["关闭", "1分钟", "5分钟", "10分钟"]);
const AUTO_HOME_OPTIONS = optionLabels(settingsText.Automatic?.options, ["关闭", "20秒", "40秒", "60秒"]);
const LANG_OPTIONS = languageLabels(settingsText.language?.options, [
"English",
"繁體中文",
"简体中文",
]);
// 语言选项固定显示各语言的母语名称,与当前界面语言无关;
// 数组下标即语言索引(0=en … 9=pl),与 SelectPage 回传的值一致。
const LANG_OPTIONS = [...LANGUAGE_NATIVE_LABELS];
const sleepRowLabel =
settingsText.sleepTime?.label ??
+3 -3
View File
@@ -32,13 +32,13 @@ const VU_STYLES: VUStyle[] = Array.from({ length: 17 }, (_, i) => ({
export default function VUPage() {
const [, setLocation] = useLocation();
const { deviceState, updateSetting } = useDevice();
const { deviceState, updateSetting, effectiveLanguage } = useDevice();
const currentVU = deviceState?.vu ?? 0;
const vuPageText = useMemo((): VUPageLocale => {
const loc = resolveLocalePack(deviceState?.language);
const loc = resolveLocalePack(effectiveLanguage);
return (loc.vuPage ?? localeEn.vuPage ?? {}) as VUPageLocale;
}, [deviceState?.language]);
}, [effectiveLanguage]);
const pageTitle = usePageTitleWithConnection(vuPageText.title ?? "VU");