diff --git a/src/hooks/useLanguageDetection.ts b/src/hooks/useLanguageDetection.ts
new file mode 100644
index 0000000..74d6bed
--- /dev/null
+++ b/src/hooks/useLanguageDetection.ts
@@ -0,0 +1,28 @@
+import { useEffect } from "react";
+import { useTranslation } from "react-i18next";
+import api from "../utils/api";
+
+export function useLanguageDetection() {
+ const { i18n } = useTranslation();
+
+ useEffect(() => {
+ async function detectLanguage() {
+ try {
+ const { countryCode, error }: { countryCode: string; error: string } =
+ await api.get("getCountryCode").json();
+
+ if (!error && countryCode && countryCode !== "RU") {
+ await i18n.changeLanguage("en");
+ } else if (!error && countryCode === "RU") {
+ await i18n.changeLanguage("ru");
+ }
+ } catch (error) {
+ console.error("Failed to get country code:", error);
+ // Fallback to browser language detection (handled by i18next-browser-languagedetector)
+ }
+ }
+
+ void detectLanguage();
+ }, [i18n]);
+}
+
diff --git a/src/i18n.ts b/src/i18n.ts
index 6c6d48d..553df44 100644
--- a/src/i18n.ts
+++ b/src/i18n.ts
@@ -1,5 +1,6 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
+import LanguageDetector from "i18next-browser-languagedetector";
const resources = {
ru: {
@@ -118,6 +119,90 @@ const resources = {
getAccess: "Получите доступ у администратора трансляции!",
controlReceived: "Управление получено!",
},
+ feedbackSuccess: {
+ title: "Заявка отправлена",
+ thankYou: "Спасибо за подачу заявки!",
+ message:
+ "Мы ценим ваш интерес к нашей компании и в ближайшее время свяжемся с вами для уточнения деталей проекта.",
+ },
+ sidebarTab5: {
+ linkLabel: "Ссылка для подключения",
+ linkDescription:
+ "Ссылка, получаемая пользователем на почтовый адрес, для подключения к демонстрации.",
+ copyButton: "Скопировать",
+ },
+ tooltips: {
+ returnControl: "Вернуть управление",
+ requestControl: "Запросить управление",
+ turnOffMic: "Выключить микрофон",
+ turnOnMic: "Включить микрофон",
+ turnOffCamera: "Выключить камеру",
+ turnOnCamera: "Включить камеру",
+ hideParticipants: "Скрыть участников",
+ showParticipants: "Показать участников",
+ hideChat: "Скрыть чат",
+ showChat: "Показать чат",
+ share: "Поделиться",
+ windowedMode: "Оконный режим",
+ fullscreenMode: "Полноэкранный режим",
+ actions: "Действия",
+ },
+ toasts: {
+ requestPermission: "запрашивает разрешение на управление",
+ receivedPermission: "Вы получили разрешение на управление",
+ linkCopied: "Ссылка скопирована в буфер обмена",
+ linkCopiedExclamation: "Ссылка скопирована в буфер обмена!",
+ invitationSent: "Приглашение отправлено",
+ needPermission: "Необходимо запросить разрешение на управление",
+ },
+ calendar: {
+ mon: "пн",
+ tue: "вт",
+ wed: "ср",
+ thu: "чт",
+ fri: "пт",
+ sat: "сб",
+ sun: "вс",
+ },
+ errors: {
+ unknownError: "Неизвестная ошибка",
+ unknownCountryError: "Неизвестная ошибка при получении кода страны",
+ invalidCredentials: "Неверное имя пользователя или пароль",
+ failedToFetchData: "Не удалось получить данные",
+ noConnection: "Нет соединения с сервером, попробуйте позже",
+ },
+ userActions: {
+ transferControl: "Передать управление",
+ kick: "Исключить",
+ },
+ speedtest: {
+ pleaseWait: "Пожалуйста, подождите",
+ checkingConnection: "Проверяем качество вашего
интернет-соединения",
+ checking: "Проверка",
+ secondsLeft: "Осталось {{count}} секунд",
+ },
+ setName: {
+ hello: "Здравствуйте!",
+ introduceYourself: "Представьтесь, пожалуйста",
+ howToAddress: "Так мы будем знать, как к вам обратиться",
+ name: "Имя",
+ skip: "Не указывать",
+ continue: "Продолжить",
+ },
+ chat: {
+ placeholder: "Написать сообщение...",
+ title: "Чат",
+ demoChat: "Чат демонстрации",
+ },
+ login: {
+ title: "Вход в личный кабинет",
+ username: "Имя пользователя",
+ password: "Пароль",
+ loginButton: "Войти",
+ },
+ stream: {
+ rotateDevice: "Поверните устройство",
+ },
},
},
en: {
@@ -140,7 +225,6 @@ const resources = {
allow: "Allow", // Разрешить
members: "Members", // Участники
invite: "Invite", // Пригласить
- chat: "Chat", // Чат
scanQRCode: "Scan the QR code
to join the demonstration", // Отсканируйте QR-код, чтобы присоединиться к демонстрации
copyLinkToConnect: "Copy link to connect", // Скопировать ссылку для подключения
loading: "Loading",
@@ -252,16 +336,108 @@ const resources = {
getAccess: "Get access from the stream administrator!",
controlReceived: "Control received!",
},
+ feedbackSuccess: {
+ title: "Request sent",
+ thankYou: "Thank you for submitting your request!",
+ message:
+ "We appreciate your interest in our company and will contact you shortly to discuss project details.",
+ },
+ sidebarTab5: {
+ linkLabel: "Connection link",
+ linkDescription:
+ "Link sent to the user's email address to connect to the demonstration.",
+ copyButton: "Copy",
+ },
+ tooltips: {
+ returnControl: "Return control",
+ requestControl: "Request control",
+ turnOffMic: "Turn off microphone",
+ turnOnMic: "Turn on microphone",
+ turnOffCamera: "Turn off camera",
+ turnOnCamera: "Turn on camera",
+ hideParticipants: "Hide participants",
+ showParticipants: "Show participants",
+ hideChat: "Hide chat",
+ showChat: "Show chat",
+ share: "Share",
+ windowedMode: "Windowed mode",
+ fullscreenMode: "Fullscreen mode",
+ actions: "Actions",
+ },
+ toasts: {
+ requestPermission: "requests permission to control",
+ receivedPermission: "You have received permission to control",
+ linkCopied: "Link copied to clipboard",
+ linkCopiedExclamation: "Link copied to clipboard!",
+ invitationSent: "Invitation sent",
+ needPermission: "You need to request permission to control",
+ },
+ calendar: {
+ mon: "Mo",
+ tue: "Tu",
+ wed: "We",
+ thu: "Th",
+ fri: "Fr",
+ sat: "Sa",
+ sun: "Su",
+ },
+ errors: {
+ unknownError: "Unknown error",
+ unknownCountryError: "Unknown error while getting country code",
+ invalidCredentials: "Invalid username or password",
+ failedToFetchData: "Failed to fetch data",
+ noConnection: "No connection to server, please try again later",
+ },
+ userActions: {
+ transferControl: "Transfer control",
+ kick: "Kick",
+ },
+ speedtest: {
+ pleaseWait: "Please wait",
+ checkingConnection: "Checking your
internet connection quality",
+ checking: "Checking",
+ secondsLeft: "{{count}} seconds left",
+ },
+ setName: {
+ hello: "Hello!",
+ introduceYourself: "Please introduce yourself",
+ howToAddress: "This way we will know how to address you",
+ name: "Name",
+ skip: "Skip",
+ continue: "Continue",
+ },
+ chat: {
+ placeholder: "Write a message...",
+ title: "Chat",
+ demoChat: "Demonstration chat",
+ },
+ login: {
+ title: "Login to personal account",
+ username: "Username",
+ password: "Password",
+ loginButton: "Login",
+ },
+ stream: {
+ rotateDevice: "Rotate device",
+ },
},
},
};
-void i18n.use(initReactI18next).init({
- resources,
- fallbackLng: "ru",
- interpolation: {
- escapeValue: false,
- },
-});
+void i18n
+ .use(LanguageDetector)
+ .use(initReactI18next)
+ .init({
+ resources,
+ fallbackLng: "ru",
+ supportedLngs: ["ru", "en"],
+ detection: {
+ order: ["navigator", "htmlTag"],
+ caches: [], // Отключаем кеширование в localStorage
+ },
+ interpolation: {
+ escapeValue: false,
+ },
+ });
export default i18n;
diff --git a/src/main.tsx b/src/main.tsx
index f60c714..727db09 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -8,6 +8,7 @@ import App from "./App";
import HistoryPage from "./HistoryPage";
import ScheduledPage from "./ScheduledPage";
import StreamPage from "./pages/StreamPage";
+import LanguageDetector from "./components/LanguageDetector";
const router = createBrowserRouter([
{
@@ -30,5 +31,7 @@ const router = createBrowserRouter([
]);
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
-
+
+
+
);
diff --git a/src/pages/StreamPage.tsx b/src/pages/StreamPage.tsx
index fddc43c..2e8425b 100644
--- a/src/pages/StreamPage.tsx
+++ b/src/pages/StreamPage.tsx
@@ -24,7 +24,7 @@ import MicroOnIcon from "../components/icons/MicroOnIcon";
import MicroOffIcon from "../components/icons/MicroOffIcon";
import CameraOnIcon from "../components/icons/CameraOnIcon";
import CameraOffIcon from "../components/icons/CameraOffIcon";
-import { Trans } from "react-i18next";
+import { Trans, useTranslation } from "react-i18next";
import { isIOS, isMobile, useMobileOrientation } from "react-device-detect";
import WindowIcon from "../components/icons/WindowIcon";
import FullscreenIcon from "../components/icons/FullscreenIcon";
@@ -56,6 +56,7 @@ import InternetSpeedMediumIcon from "../components/icons/InternetSpeedMediumIcon
const userId = uuidv4();
function StreamPage() {
+ const { t } = useTranslation();
const params = useParams();
const [searchParams] = useSearchParams();
const [WSUrl, setWSUrl] = useState
("");
@@ -223,13 +224,13 @@ function StreamPage() {
if (user?.id === me?.id || !me?.isAdmin) return;
- toast.info(`${user?.name} запрашивает разрешение на управление`);
+ toast.info(`${user?.name} ${t("toasts.requestPermission")}`);
});
socket.on("transfer-control", (userId) => {
if (me?.id !== userId) return;
- toast.info(`Вы получили разрешение на управление`);
+ toast.info(t("toasts.receivedPermission"));
});
socket.on("kick", (userId) => {
@@ -481,11 +482,11 @@ function StreamPage() {
}
/>
{me?.isAdmin && !me.isControlAllowed && (
-
+
)}
{!me?.isAdmin && !me?.isControlAllowed && (
-
+
)}