Refactor BottomNav to use localized labels for navigation items; added support for dynamic language selection based on device state. Updated locale files for English, Traditional Chinese, and Simplified Chinese to include bottom navigation labels.
This commit is contained in:
+51
-34
@@ -1,7 +1,7 @@
|
||||
/* ============================================================
|
||||
VU PAGE — VU Meter Gallery
|
||||
Design: Reference luxsin_x8_VU表.png
|
||||
- Card-based VU meter style selector
|
||||
- 3-column grid of VU style cards
|
||||
- Active = cyan check circle below card
|
||||
- Inactive = empty circle
|
||||
============================================================ */
|
||||
@@ -10,6 +10,12 @@ import { cn } from "@/lib/utils";
|
||||
import BottomNav from "@/components/BottomNav";
|
||||
import { Check, 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";
|
||||
|
||||
type VUPageLocale = NonNullable<(typeof localeZh)["vuPage"]>;
|
||||
|
||||
interface VUStyle {
|
||||
index: number;
|
||||
@@ -28,61 +34,72 @@ export default function VUPage() {
|
||||
const { deviceState, updateSetting } = useDevice();
|
||||
const currentVU = deviceState?.vu ?? 0;
|
||||
|
||||
const vuPageText = useMemo((): VUPageLocale => {
|
||||
const lang = deviceState?.language;
|
||||
const loc =
|
||||
lang === 0 ? localeEn : lang === 1 ? localeZhHK : localeZh;
|
||||
return (loc.vuPage ?? {}) as VUPageLocale;
|
||||
}, [deviceState?.language]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-black">
|
||||
<div className="page-header">
|
||||
<button onClick={() => setLocation("/")} className="mr-4 text-white/60 active:text-white transition-colors">
|
||||
<ChevronLeft size={24} />
|
||||
</button>
|
||||
<h1 className="flex-1 text-center text-[17px] font-semibold text-white">VU 表库</h1>
|
||||
<h1 className="flex-1 text-center text-[17px] font-semibold text-white">
|
||||
{vuPageText.title ?? "VU 表库"}
|
||||
</h1>
|
||||
<div className="w-8" />
|
||||
</div>
|
||||
|
||||
<div className="px-4 pb-32 pt-4 space-y-5">
|
||||
{VU_STYLES.map((style) => {
|
||||
const isActive = currentVU === style.index;
|
||||
return (
|
||||
<div key={style.index} className="space-y-3">
|
||||
{/* Card */}
|
||||
<div
|
||||
className={cn(
|
||||
"ios-list-group p-3 cursor-pointer transition-all duration-200",
|
||||
isActive && "border-[#00FFF6]/30"
|
||||
)}
|
||||
style={isActive ? { borderColor: "rgba(0,255,246,0.3)", boxShadow: "0 0 20px rgba(0,255,246,0.08)" } : {}}
|
||||
onClick={() => updateSetting({ vu: style.index })}
|
||||
>
|
||||
<img
|
||||
src={style.preview}
|
||||
alt={style.name}
|
||||
className="w-full h-28 object-contain rounded-xl bg-black/20"
|
||||
/>
|
||||
</div>
|
||||
<div className="px-4 pb-32 pt-4">
|
||||
<div className="grid grid-cols-3 gap-x-2 gap-y-4">
|
||||
{VU_STYLES.map((style) => {
|
||||
const isActive = currentVU === style.index;
|
||||
return (
|
||||
<div key={style.index} className="flex flex-col items-center gap-1.5 min-w-0">
|
||||
<div
|
||||
className={cn(
|
||||
"ios-list-group p-1.5 cursor-pointer transition-all duration-200 w-full",
|
||||
isActive && "border-[#00FFF6]/30"
|
||||
)}
|
||||
style={isActive ? { borderColor: "rgba(0,255,246,0.3)", boxShadow: "0 0 16px rgba(0,255,246,0.08)" } : {}}
|
||||
onClick={() => updateSetting({ vu: style.index })}
|
||||
>
|
||||
<img
|
||||
src={style.preview}
|
||||
alt={style.name}
|
||||
className="w-full h-[4.5rem] sm:h-24 object-contain rounded-lg bg-black/20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Selection indicator */}
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => updateSetting({ vu: style.index })}
|
||||
className={cn(
|
||||
"w-7 h-7 rounded-full flex items-center justify-center transition-all duration-200",
|
||||
"w-6 h-6 rounded-full flex items-center justify-center transition-all duration-200 shrink-0",
|
||||
isActive
|
||||
? "bg-[#00FFF6] text-black shadow-[0_0_12px_rgba(0,255,246,0.5)]"
|
||||
? "bg-[#00FFF6] text-black shadow-[0_0_10px_rgba(0,255,246,0.45)]"
|
||||
: "border-2 border-white/20 text-transparent"
|
||||
)}
|
||||
aria-label={isActive ? `${style.name}(已选)` : `选择${style.name}`}
|
||||
>
|
||||
{isActive && <Check size={14} strokeWidth={3} />}
|
||||
{isActive && <Check size={12} strokeWidth={3} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Style name */}
|
||||
<div className="text-center">
|
||||
<p className={cn("text-[15px] font-medium", isActive ? "text-[#00FFF6]" : "text-white/60")}>
|
||||
<p
|
||||
className={cn(
|
||||
"text-center text-[11px] sm:text-xs font-medium truncate w-full px-0.5",
|
||||
isActive ? "text-[#00FFF6]" : "text-white/60"
|
||||
)}
|
||||
>
|
||||
{style.name}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<BottomNav />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user