diff --git a/bun.lock b/bun.lock
index 33695e4..2985a67 100644
--- a/bun.lock
+++ b/bun.lock
@@ -12,7 +12,6 @@
"caniuse-lite": "^1.0.30001764",
"date-fns": "^2.30.0",
"i18next": "^23.8.2",
- "i18next-browser-languagedetector": "^8.2.0",
"ky": "^1.1.3",
"peerjs": "^1.5.4",
"react": "^18.2.0",
@@ -409,8 +408,6 @@
"i18next": ["i18next@23.16.8", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg=="],
- "i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g=="],
-
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
diff --git a/package.json b/package.json
index 790b8af..2741bc3 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,6 @@
"caniuse-lite": "^1.0.30001764",
"date-fns": "^2.30.0",
"i18next": "^23.8.2",
- "i18next-browser-languagedetector": "^8.2.0",
"ky": "^1.1.3",
"peerjs": "^1.5.4",
"react": "^18.2.0",
diff --git a/public/images/cards/aivaz.jpg b/public/images/cards/aivaz.jpg
index c0c5295..c490a49 100644
Binary files a/public/images/cards/aivaz.jpg and b/public/images/cards/aivaz.jpg differ
diff --git a/public/images/cards/liferes.jpg b/public/images/cards/liferes.jpg
index d2f434f..4b9db2b 100644
Binary files a/public/images/cards/liferes.jpg and b/public/images/cards/liferes.jpg differ
diff --git a/public/images/cards/nks.jpg b/public/images/cards/nks.jpg
index cbf428e..5da79d1 100644
Binary files a/public/images/cards/nks.jpg and b/public/images/cards/nks.jpg differ
diff --git a/public/images/cards/shipyard.jpg b/public/images/cards/shipyard.jpg
index 285c2f5..fe0b80f 100644
Binary files a/public/images/cards/shipyard.jpg and b/public/images/cards/shipyard.jpg differ
diff --git a/public/images/cards/upside.jpg b/public/images/cards/upside.jpg
index db13d61..656d0ae 100644
Binary files a/public/images/cards/upside.jpg and b/public/images/cards/upside.jpg differ
diff --git a/src/App.tsx b/src/App.tsx
index d4ac8e9..24e4cbd 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -21,7 +21,7 @@ import { useNavigate, useSearchParams } from "react-router-dom";
import { Bounce, ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import InfoIcon from "./components/icons/InfoIcon";
-import { detectUserRegion, getRegionHeaders } from "./utils/api";
+import { detectUserRegion, getRegionHeaders, getUserRegion } from "./utils/api";
import { handleApiError, isErrorResponse } from "./utils/errorHandler";
function App() {
@@ -104,15 +104,18 @@ function App() {
navigate(streamUrl);
}, [streamUrl]);
- // Определяем регион пользователя при первой загрузке
+ // Определяем регион пользователя при первой загрузке (пропускаем API, если ?lang= уже задал регион)
useEffect(() => {
async function initializeRegion() {
+ if (getUserRegion()) {
+ setRegionDetected(true);
+ return;
+ }
try {
await detectUserRegion();
setRegionDetected(true);
} catch (error) {
console.error("Failed to detect user region:", error);
- // Даже при ошибке продолжаем работу с дефолтным регионом
setRegionDetected(true);
}
}
@@ -305,33 +308,41 @@ function App() {
*/}
>
) : (
-
-
+ <>
+
+
-
-
-
- IMI Saudi Shipyard
-
-
Saudi Arabia
+
+
+
+
+ Upside Towers
+
+
+
+
+ Russia, Moscow
+
+
+
+
+
-
-
-
+ >
)}
diff --git a/src/components/LanguageDetector.tsx b/src/components/LanguageDetector.tsx
index e7d00e0..2859ba1 100644
--- a/src/components/LanguageDetector.tsx
+++ b/src/components/LanguageDetector.tsx
@@ -1,12 +1,22 @@
import { ReactNode } from "react";
import { useLanguageDetection } from "../hooks/useLanguageDetection";
+import LoaderIcon from "./icons/LoaderIcon";
interface LanguageDetectorProps {
children: ReactNode;
}
function LanguageDetector({ children }: LanguageDetectorProps) {
- useLanguageDetection();
+ const { isReady } = useLanguageDetection();
+
+ if (!isReady) {
+ return (
+
+
+
+ );
+ }
+
return <>{children}>;
}
diff --git a/src/hooks/useLanguageDetection.ts b/src/hooks/useLanguageDetection.ts
index fe8ad00..6c63ab2 100644
--- a/src/hooks/useLanguageDetection.ts
+++ b/src/hooks/useLanguageDetection.ts
@@ -1,12 +1,36 @@
-import { useEffect } from "react";
+import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
-import { detectUserRegion } from "../utils/api";
+import { useSearchParams } from "react-router-dom";
+import { detectUserRegion, setUserRegion } from "../utils/api";
+
+const SUPPORTED_LANGS = ["ru", "en"] as const;
export function useLanguageDetection() {
const { i18n } = useTranslation();
+ const [searchParams] = useSearchParams();
+ const userChoseLangFromUrl = useRef(false);
+ const [isReady, setIsReady] = useState(false);
useEffect(() => {
async function detectLanguage() {
+ const langParam = searchParams.get("lang")?.toLowerCase();
+
+ // Приоритет 1: query-параметр ?lang=ru или ?lang=en — без запроса в API
+ if (langParam && SUPPORTED_LANGS.includes(langParam as (typeof SUPPORTED_LANGS)[number])) {
+ userChoseLangFromUrl.current = true;
+ setUserRegion(langParam === "ru" ? "RU" : "EN");
+ await i18n.changeLanguage(langParam);
+ setIsReady(true);
+ return;
+ }
+
+ // Если пользователь ранее выбрал язык через URL — не переопределять при навигации
+ if (userChoseLangFromUrl.current) {
+ setIsReady(true);
+ return;
+ }
+
+ // Приоритет 2: определение по региону
try {
const countryCode = await detectUserRegion();
@@ -17,11 +41,15 @@ export function useLanguageDetection() {
}
} catch (error) {
console.error("Failed to get country code:", error);
- // Fallback to browser language detection (handled by i18next-browser-languagedetector)
+ // Оставляем fallbackLng из i18n (ru)
+ } finally {
+ setIsReady(true);
}
}
void detectLanguage();
- }, [i18n]);
+ }, [i18n, searchParams]);
+
+ return { isReady };
}
diff --git a/src/i18n.ts b/src/i18n.ts
index 2043aec..0962e02 100644
--- a/src/i18n.ts
+++ b/src/i18n.ts
@@ -1,6 +1,5 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
-import LanguageDetector from "i18next-browser-languagedetector";
const resources = {
ru: {
@@ -468,20 +467,13 @@ const resources = {
},
};
-void i18n
- .use(LanguageDetector)
- .use(initReactI18next)
- .init({
- resources,
- fallbackLng: "ru",
- supportedLngs: ["ru", "en"],
- detection: {
- order: ["navigator", "htmlTag"],
- caches: [], // Отключаем кеширование в localStorage
- },
- interpolation: {
- escapeValue: false,
- },
- });
+void i18n.use(initReactI18next).init({
+ resources,
+ fallbackLng: "ru",
+ supportedLngs: ["ru", "en"],
+ interpolation: {
+ escapeValue: false,
+ },
+});
export default i18n;
diff --git a/src/main.tsx b/src/main.tsx
index 727db09..ec53af0 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,6 +1,6 @@
// import React from "react";
import ReactDOM from "react-dom/client";
-import { createBrowserRouter, RouterProvider } from "react-router-dom";
+import { createBrowserRouter, Outlet, RouterProvider } from "react-router-dom";
import "./index.css";
import "./i18n";
import App from "./App";
@@ -12,26 +12,33 @@ import LanguageDetector from "./components/LanguageDetector";
const router = createBrowserRouter([
{
- path: "/",
- element: ,
- // errorElement: ,
- },
- {
- path: "/stream/:id",
- element: ,
- },
- {
- path: "/history",
- element: ,
- },
- {
- path: "/scheduled/:sessionId",
- element: ,
+ element: (
+
+
+
+ ),
+ children: [
+ {
+ path: "/",
+ element: ,
+ // errorElement: ,
+ },
+ {
+ path: "/stream/:id",
+ element: ,
+ },
+ {
+ path: "/history",
+ element: ,
+ },
+ {
+ path: "/scheduled/:sessionId",
+ element: ,
+ },
+ ],
},
]);
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
-
-
-
+
);