localization in header

This commit is contained in:
2026-04-30 16:16:59 +07:00
parent 6891e64c4f
commit 6d87ddf674
6 changed files with 43 additions and 37 deletions
+10 -12
View File
@@ -1,4 +1,5 @@
import { useEffect, useState, useMemo, useDeferredValue } from "react";
import { useTranslation } from "react-i18next";
import {
Dialog,
DialogContent,
@@ -11,21 +12,20 @@ import { Switch } from "@/components/ui/switch";
import { getInstalledApps, AppInfo } from "vpn-plugin";
import { useSettingsStore } from "@/store/useSettingsStore";
import { isAndroid } from "@/lib/platform";
import { Loader2 } from "lucide-react"; // Для индикации загрузки
import { Loader2 } from "lucide-react";
export function AppExclusions() {
const { t } = useTranslation();
const [apps, setApps] = useState<AppInfo[]>([]);
const [search, setSearch] = useState("");
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
// Оптимизация: точечные селекторы вместо деструктуризации всего стора
const excludedApps = useSettingsStore((state) => state.excludedApps);
const setExcludedApps = useSettingsStore((state) => state.setExcludedApps);
const deferredSearch = useDeferredValue(search);
// Загрузка приложений только при открытии и если список пуст
useEffect(() => {
if (isOpen && apps.length === 0) {
setIsLoading(true);
@@ -35,7 +35,6 @@ export function AppExclusions() {
.catch(console.error)
.finally(() => setIsLoading(false));
} else {
// Имитация для десктопа/разработки
setTimeout(() => {
setApps([
{ appName: "Google Chrome", packageName: "com.android.chrome" },
@@ -48,7 +47,6 @@ export function AppExclusions() {
}
}, [isOpen, apps.length]);
// Оптимизированная фильтрация
const filteredApps = useMemo(() => {
const term = deferredSearch.toLowerCase();
return apps.filter(
@@ -73,7 +71,9 @@ export function AppExclusions() {
variant="outline"
className="w-full flex justify-between items-center h-12 rounded-xl bg-white/5 border-white/10 hover:bg-white/10 transition-all"
>
<span className="font-medium text-foreground">Excluded Apps</span>
<span className="font-medium text-foreground">
{t("excluded_apps")}
</span>
<span className="bg-primary/20 text-primary px-2.5 py-0.5 rounded-full text-xs font-bold">
{excludedApps.length}
</span>
@@ -82,13 +82,13 @@ export function AppExclusions() {
<DialogContent className="max-h-[85vh] flex flex-col p-0 overflow-hidden border-white/10 bg-background/95 backdrop-blur-2xl">
<DialogHeader className="p-6 pb-2">
<DialogTitle>Выберите приложения</DialogTitle>
<DialogTitle>{t("select_apps")}</DialogTitle>
</DialogHeader>
<div className="px-6 pb-4">
<input
type="text"
placeholder="Поиск по имени или пакету..."
placeholder={t("search_apps_placeholder")}
value={search}
onChange={(e) => setSearch(e.target.value)}
className="w-full p-3 rounded-xl bg-white/5 border border-white/10 text-sm outline-none focus:border-primary transition-colors text-foreground"
@@ -99,9 +99,7 @@ export function AppExclusions() {
{isLoading ? (
<div className="flex flex-col items-center justify-center py-20 gap-3 text-muted-foreground">
<Loader2 className="size-8 animate-spin text-primary" />
<p className="text-sm font-medium">
Получение списка приложений...
</p>
<p className="text-sm font-medium">{t("loading_apps")}</p>
</div>
) : (
<div className="space-y-2">
@@ -129,7 +127,7 @@ export function AppExclusions() {
{!isLoading && filteredApps.length === 0 && (
<p className="text-center text-muted-foreground text-sm py-12">
Ничего не найдено
{t("no_apps_found")}
</p>
)}
</div>
+4 -2
View File
@@ -1,6 +1,7 @@
import { Menu } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ThemeToggle } from "./ThemeToggle";
import { LanguageToggle } from "./ToggleLanguge";
export function Header({ onMenuClick }: { onMenuClick: () => void }) {
return (
@@ -21,8 +22,9 @@ export function Header({ onMenuClick }: { onMenuClick: () => void }) {
</h1>
</div>
{/* Справа: Тема (занимает место по контенту) */}
<div className="flex justify-end">
{/* Справа: Смена языка и темы */}
<div className="flex justify-end gap-2">
<LanguageToggle />
<ThemeToggle />
</div>
</div>
+7 -2
View File
@@ -7,13 +7,18 @@ export function LanguageToggle() {
const toggleLanguage = () => {
const currentLang = i18n.language || "en";
const newLang = currentLang.startsWith("en") ? "ru" : "en";
i18n.changeLanguage(newLang);
};
return (
<Button variant="ghost" onClick={toggleLanguage} className="min-w-10">
<Button
variant="outline"
size="icon"
onClick={toggleLanguage}
className="rounded-full transition-all duration-300 hover:scale-105 font-bold text-xs"
>
{i18n.language?.substring(0, 2).toUpperCase() || "EN"}
<span className="sr-only">Toggle language</span>
</Button>
);
}