适配新语言

This commit is contained in:
eafonyang
2026-10-22 13:50:22 +08:00
parent beb221b52d
commit c5cdbb4a64
18 changed files with 4164 additions and 73 deletions
+52
View File
@@ -11,6 +11,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;
@@ -21,6 +27,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;
@@ -55,6 +71,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);
@@ -176,6 +196,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 });
@@ -277,6 +328,7 @@ export function DeviceProvider({ children }: { children: React.ReactNode }) {
ip, setIp: setDeviceIp, ipMissing,
isConnected, isConnecting, isDemoMode, setDemoMode,
deviceState, peqState, lastUpdated, error,
effectiveLanguage, setUiLanguage,
connect, disconnect, refresh, api,
updateSetting, updatePeq, upgradePeqChange, upgradePeqApply, syncPeqCatalog,
setVolume, setInput, setOutput, setBalance,