Локализует приложение на 10 языков + RTL для арабского и персидского
Добавляет zh-CN, fa, ar, tr, uk, be, vi, uz локали (81 ключ, идентичная структура) к существующим en/ru; ToggleLanguge.tsx переделан из бинарного ru/en тумблера в выпадающий список всех 10 языков. TelegramWeb.tsx был полностью на захардкоженном русском несмотря на импорт useTranslation — переведён на i18n-ключи. rtlLanguages.ts выносит список RTL-языков в отдельный .ts-файл (именованный импорт из .js падал на allowJs). App.tsx реактивно переключает document.dir/lang при смене языка. Логические CSS-свойства (ps-/pe-/start-/end-/border-e) вместо физических в badge/button/dialog/select и BurgerMenu — Flexbox иначе визуально переворачивает детей под dir=rtl, ломая анимацию бургер-меню. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { RTL_LANGUAGES } from "@/lib/rtlLanguages";
|
||||
interface BurgerMenuProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
@@ -7,7 +9,10 @@ interface BurgerMenuProps {
|
||||
|
||||
export function BurgerMenu({ isOpen, onClose }: BurgerMenuProps) {
|
||||
if (!isOpen) return null;
|
||||
const { t } = useTranslation();
|
||||
const { t, i18n } = useTranslation();
|
||||
// slide-in-from-left/right — буквальные keyframe-имена (не логические
|
||||
// start/end), поэтому направление анимации приходится выбирать вручную.
|
||||
const isRtl = RTL_LANGUAGES.includes((i18n.language || "").split("-")[0]);
|
||||
return (
|
||||
<>
|
||||
{/* затемнение только под хедером */}
|
||||
@@ -16,8 +21,13 @@ export function BurgerMenu({ isOpen, onClose }: BurgerMenuProps) {
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* меню */}
|
||||
<div className="fixed left-0 top-25 z-60 h-[calc(100vh-4rem)] w-64 bg-card border-r p-6 shadow-xl animate-in slide-in-from-left">
|
||||
{/* меню — открывается от смыслового "начала" строки: слева в LTR, справа в RTL */}
|
||||
<div
|
||||
className={cn(
|
||||
"fixed start-0 top-25 z-60 h-[calc(100vh-4rem)] w-64 bg-card border-e p-6 shadow-xl animate-in",
|
||||
isRtl ? "slide-in-from-right" : "slide-in-from-left",
|
||||
)}
|
||||
>
|
||||
<nav className="flex flex-col gap-6" onClick={onClose}>
|
||||
<Link
|
||||
to="/"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface SplashScreenProps {
|
||||
onComplete: () => void;
|
||||
@@ -9,6 +10,7 @@ interface SplashScreenProps {
|
||||
|
||||
export function SplashScreen({ onComplete }: SplashScreenProps) {
|
||||
const [isFadingOut, setIsFadingOut] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -51,11 +53,11 @@ export function SplashScreen({ onComplete }: SplashScreenProps) {
|
||||
{/* Контентная часть */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-[12px] font-mono text-white font-bold tracking-[0.5em] uppercase">
|
||||
The System Watches Everyone
|
||||
{t("splash_tagline_1")}
|
||||
</p>
|
||||
|
||||
<p className="text-xl md:text-2xl font-mono font-black tracking-[0.8em] uppercase text-[#00E5F2] drop-shadow-[0_0_15px_#00E5F2] animate-pulse ml-[0.8em]">
|
||||
Except You
|
||||
{t("splash_tagline_2")}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useTheme } from "@/ThemeProvider";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { isDark, toggleDark } = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -21,7 +23,7 @@ export function ThemeToggle() {
|
||||
) : (
|
||||
<Moon className="h-[1.2rem] w-[1.2rem]" />
|
||||
)}
|
||||
<span className="sr-only">Переключить тему</span>
|
||||
<span className="sr-only">{t("theme_toggle")}</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,24 +1,51 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
|
||||
// Код -> нативное название языка (не переводим — так проще узнать свой язык
|
||||
// в списке, даже если сейчас выставлен совсем другой).
|
||||
const LANGUAGES: { code: string; label: string }[] = [
|
||||
{ code: "en", label: "English" },
|
||||
{ code: "ru", label: "Русский" },
|
||||
{ code: "zh-CN", label: "中文" },
|
||||
{ code: "fa", label: "فارسی" },
|
||||
{ code: "ar", label: "العربية" },
|
||||
{ code: "tr", label: "Türkçe" },
|
||||
{ code: "uk", label: "Українська" },
|
||||
{ code: "be", label: "Беларуская" },
|
||||
{ code: "vi", label: "Tiếng Việt" },
|
||||
{ code: "uz", label: "Oʻzbekcha" },
|
||||
];
|
||||
|
||||
export function LanguageToggle() {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const toggleLanguage = () => {
|
||||
const currentLang = i18n.language || "en";
|
||||
const newLang = currentLang.startsWith("en") ? "ru" : "en";
|
||||
i18n.changeLanguage(newLang);
|
||||
};
|
||||
const { i18n, t } = useTranslation();
|
||||
const current =
|
||||
LANGUAGES.find((l) => l.code === i18n.language)?.code ??
|
||||
LANGUAGES.find((l) => i18n.language?.startsWith(l.code.split("-")[0]))
|
||||
?.code ??
|
||||
"en";
|
||||
|
||||
return (
|
||||
<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>
|
||||
<Select value={current} onValueChange={(lang) => i18n.changeLanguage(lang)}>
|
||||
<SelectTrigger
|
||||
size="sm"
|
||||
aria-label={t("language_toggle")}
|
||||
className="rounded-full font-bold text-xs px-3"
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
{LANGUAGES.map(({ code, label }) => (
|
||||
<SelectItem key={code} value={code}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user