From 785e56f9b06c7624d5a0c145f60e48bc3cfd5553 Mon Sep 17 00:00:00 2001 From: Nastya Date: Mon, 11 Aug 2025 18:56:37 +0300 Subject: [PATCH 1/2] =?UTF-8?q?fix=20=D1=88=D0=B8=D0=BB=D1=8C=D0=B4=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B0?= =?UTF-8?q?=D1=8E=D1=82=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=86=D0=B2=D0=B5=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/assets/icons/NameplateLogoDark.tsx | 216 ++++++++++++++++++ .../ContactForm/ContactForm.tsx | 8 +- .../ViewPublicationPage/ResultForm.tsx | 8 +- .../StartPageViewPublication/index.tsx | 19 +- 4 files changed, 228 insertions(+), 23 deletions(-) create mode 100644 lib/assets/icons/NameplateLogoDark.tsx diff --git a/lib/assets/icons/NameplateLogoDark.tsx b/lib/assets/icons/NameplateLogoDark.tsx new file mode 100644 index 0000000..bb5da48 --- /dev/null +++ b/lib/assets/icons/NameplateLogoDark.tsx @@ -0,0 +1,216 @@ +import { FC, SVGProps } from "react"; + +// Fallback функция для получения языка, когда React Router недоступен +const getLanguageFromUrlFallback = (): string => { + const path = window.location.pathname; + const langMatch = path.match(/^\/(en|ru|uz)(\/|$)/i); + if (langMatch) { + return langMatch[1].toLowerCase(); + } + return "ru"; +}; + +export const NameplateLogoDark: FC> = () => { + let lang = "ru"; // fallback + + try { + // Пытаемся использовать React Router + const { useLocation } = require("react-router-dom"); + const location = useLocation(); + const pathname = location.pathname; + + const getLanguageFromUrl = () => { + const parts = pathname.split("/"); + if (parts.length >= 2) { + const langSegment = parts[1].toLowerCase(); + if (langSegment === "en") return "en"; + if (langSegment === "uz") return "uz"; + } + return "ru"; + }; + + lang = getLanguageFromUrl(); + } catch (error) { + // Если React Router недоступен (в виджете), используем fallback + lang = getLanguageFromUrlFallback(); + } + + if (lang === "ru") return ; + if (lang === "en") return ; + if (lang === "uz") return ; + + return ; // Fallback +}; + +const UZ: FC> = () => ( + + + + + + + +); +const RU: FC> = () => ( + + + + + + + + + + + + + + + + + + + + + + + +); +const EN: FC> = () => ( + + + + + + + +); diff --git a/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx b/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx index 4135659..b79f787 100644 --- a/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx +++ b/lib/components/ViewPublicationPage/ContactForm/ContactForm.tsx @@ -26,6 +26,7 @@ import type { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; import { isProduction } from "@/utils/defineDomain"; import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; +import { NameplateLogoDark } from "@/assets/icons/NameplateLogoDark"; type Props = { currentQuestion: AnyTypedQuizQuestion; @@ -461,12 +462,7 @@ export const ContactForm = ({ currentQuestion, onShowResult }: Props) => { margitTop: "auto", }} > - + {quizThemes[settings.cfg.theme].isLight ? : } )} diff --git a/lib/components/ViewPublicationPage/ResultForm.tsx b/lib/components/ViewPublicationPage/ResultForm.tsx index 1a992b0..670953c 100644 --- a/lib/components/ViewPublicationPage/ResultForm.tsx +++ b/lib/components/ViewPublicationPage/ResultForm.tsx @@ -20,6 +20,7 @@ import { sendFC, sendResult } from "@/api/quizRelase"; import { isProduction } from "@/utils/defineDomain"; import { useQuizStore } from "@/stores/useQuizStore"; import { useTranslation } from "react-i18next"; +import { NameplateLogoDark } from "@/assets/icons/NameplateLogoDark"; type ResultFormProps = { resultQuestion: QuizQuestionResult; @@ -308,12 +309,7 @@ export const ResultForm = ({ resultQuestion }: ResultFormProps) => { bottom: "90px", }} > - + {quizThemes[settings.cfg.theme].isLight ? : } )} { : undefined, }} > - + {settings.cfg.startpageType === "expanded" ? ( + + ) : quizThemes[settings.cfg.theme].isLight ? ( + + ) : ( + + )} ); -- 2.45.2 From 0c9b6e5b7a2eac8265cfa8c981f3d4b4db7bfe20 Mon Sep 17 00:00:00 2001 From: Nastya Date: Tue, 12 Aug 2025 04:26:22 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/components/QuizAnswerer.tsx | 63 ++- lib/components/ViewPublicationPage/Footer.tsx | 13 +- .../ViewPublicationPage.tsx | 18 +- .../ViewPublicationPage/tools/NextButton.tsx | 10 +- .../ViewPublicationPage/tools/PrevButton.tsx | 11 +- src/App.tsx | 8 + src/i18n/i18n.ts | 38 +- src/i18n/i18nWidget.ts | 525 ++++++++++-------- src/main.tsx | 10 + src/widget.tsx | 20 +- 10 files changed, 460 insertions(+), 256 deletions(-) diff --git a/lib/components/QuizAnswerer.tsx b/lib/components/QuizAnswerer.tsx index cb464e7..de57c47 100644 --- a/lib/components/QuizAnswerer.tsx +++ b/lib/components/QuizAnswerer.tsx @@ -22,6 +22,9 @@ import { HelmetProvider } from "react-helmet-async"; import "moment/dist/locale/ru"; import { useQuizStore, setQuizData, addquizid } from "@/stores/useQuizStore"; import { initDataManager, statusOfQuiz } from "@/utils/hooks/useQuestionFlowControl"; + +console.log("🚀 QuizAnswerer: Component module loaded"); + moment.locale("ru"); const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText; @@ -42,6 +45,13 @@ function QuizAnswererInner({ className, disableGlobalCss = false, }: Props) { + console.log("🔧 QuizAnswererInner: Component function called with props:", { + quizId, + preview, + changeFaviconAndTitle, + disableGlobalCss, + }); + const [quizViewStore] = useState(createQuizViewStore); const [rootContainerWidth, setRootContainerWidth] = useState(() => window.innerWidth); const rootContainerRef = useRef(null); @@ -51,18 +61,21 @@ function QuizAnswererInner({ const r = useQuizStore(); const { settings, questions } = useQuizStore(); + console.log("🔍 QuizAnswererInner: Component state initialized"); + useEffect(() => { + console.log("🔧 QuizAnswererInner: addquizid effect triggered"); addquizid(quizId); }, []); useEffect(() => { - console.log(settings); - console.log(questions); - console.log("r"); - console.log(r); + console.log("🔍 QuizAnswererInner: Debug effect - settings:", settings); + console.log("🔍 QuizAnswererInner: Debug effect - questions:", questions); + console.log("🔍 QuizAnswererInner: Debug effect - r:", r); }, [questions, settings]); useEffect(() => { + console.log("🔧 QuizAnswererInner: Metrics effect triggered"); setTimeout(() => { vkMetrics.quizOpened(); yandexMetrics.quizOpened(); @@ -72,7 +85,7 @@ function QuizAnswererInner({ useEffect(() => { //Хук на случай если данные переданы нам сразу, а не "нам нужно их запросить" if (quizSettings !== undefined) { - console.log("QuizAnswerer: calling setQuizData with quizSettings"); + console.log("🔧 QuizAnswererInner: setQuizData effect triggered with quizSettings"); setQuizData(quizSettings); initDataManager({ status: quizSettings.settings.status, @@ -82,6 +95,7 @@ function QuizAnswererInner({ }, [quizSettings]); useLayoutEffect(() => { + console.log("🔧 QuizAnswererInner: Layout effect triggered"); if (rootContainerRef.current) setRootContainerWidth(rootContainerRef.current.clientWidth); }, []); @@ -98,19 +112,44 @@ function QuizAnswererInner({ }; }, []); + console.log("🔍 QuizAnswererInner: About to render, current state:"); + console.log("🔍 QuizAnswererInner: isLoading:", isLoading); + console.log("🔍 QuizAnswererInner: error:", error); + console.log("🔍 QuizAnswererInner: settings keys count:", Object.keys(settings).length); + console.log("🔍 QuizAnswererInner: questions length:", questions.length); + console.log("settings"); console.log(settings); - if (isLoading && !questions.length) return ; + if (isLoading && !questions.length) { + console.log("🔍 QuizAnswererInner: Rendering LoadingSkeleton"); + return ; + } console.log("error"); console.log(error); - if (error) return ; + if (error) { + console.log("🔍 QuizAnswererInner: Rendering ApologyPage due to error"); + return ; + } - if (Object.keys(settings).length == 0) return ; - if (questions.length === 0) return ; + if (Object.keys(settings).length == 0) { + console.log("🔍 QuizAnswererInner: Rendering ApologyPage due to empty settings"); + return ; + } + if (questions.length === 0) { + console.log("🔍 QuizAnswererInner: Rendering ApologyPage due to no questions"); + return ; + } - if (questions.length === 1 && settings.cfg.noStartPage && statusOfQuiz != "ai") + if (questions.length === 1 && settings.cfg.noStartPage && statusOfQuiz != "ai") { + console.log("🔍 QuizAnswererInner: Rendering ApologyPage due to quiz is empty"); return ; - if (!quizId) return ; + } + if (!quizId) { + console.log("🔍 QuizAnswererInner: Rendering ApologyPage due to no quiz id"); + return ; + } + + console.log("🔍 QuizAnswererInner: Rendering main quiz container"); const quizContainer = ( { const theme = useTheme(); const { questions, settings } = useQuizStore(); const questionsAmount = questions.filter(({ type }) => type !== "result").length; - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); + + // Диагностика i18n в Footer + console.log("🔍 Footer: useTranslation called"); + console.log("🔍 Footer: i18n instance:", i18n); + console.log("🔍 Footer: i18n.isInitialized:", i18n.isInitialized); + console.log("🔍 Footer: i18n.language:", i18n.language); + console.log("🔍 Footer: Testing translations:"); + console.log("🔍 Footer: t('Step'):", t("Step")); + console.log("🔍 Footer: t('of'):", t("of")); + console.log("🔍 Footer: t('Prev'):", t("Prev")); + console.log("🔍 Footer: t('Next'):", t("Next")); return ( state.answers); const ownVariants = useQuizViewStore((state) => state.ownVariants); @@ -42,7 +46,12 @@ export default function ViewPublicationPage() { useYandexMetrics(settings?.cfg?.yandexMetricsNumber, preview); useVKMetrics(settings?.cfg?.vkMetricsNumber, preview); + console.log("🔍 ViewPublicationPage: Component state initialized"); + console.log("🔍 ViewPublicationPage: currentQuizStep:", currentQuizStep); + console.log("🔍 ViewPublicationPage: currentQuestion:", currentQuestion); + useEffect(() => { + console.log("🔧 ViewPublicationPage: unscreen effect triggered"); const root = document.getElementById("root"); const overlay = document.getElementById("hideoverlay"); @@ -70,7 +79,8 @@ export default function ViewPublicationPage() { if (settings.cfg.antifraud && recentlyCompleted) throw new Error("Quiz already completed"); if (currentQuizStep === "startpage" && settings.cfg.noStartPage) currentQuizStep = "question"; - if (!currentQuestion) + if (!currentQuestion) { + console.log("🔍 ViewPublicationPage: No current question, rendering error"); return ( ); + } const currentAnswer = answers.find(({ questionId }) => questionId === currentQuestion.id); + console.log("🔍 ViewPublicationPage: About to render quiz step:", currentQuizStep); + let quizStepElement: ReactElement; switch (currentQuizStep) { case "startpage": { + console.log("🔍 ViewPublicationPage: Rendering startpage"); quizStepElement = ; break; } case "question": { if (currentQuestion.type === "result") { + console.log("🔍 ViewPublicationPage: Rendering result form"); quizStepElement = ; break; } + console.log("🔍 ViewPublicationPage: Rendering question"); quizStepElement = ( { const path = window.location.pathname; @@ -10,11 +14,12 @@ const getLanguageFromURL = (): string => { const langMatch = path.match(/^\/(en|ru|uz)(\/|$)/i); if (langMatch) { - //console.log("Язык из URL:", langMatch[1]); - return langMatch[1].toLowerCase(); + const detectedLang = langMatch[1].toLowerCase(); + console.log("🔍 Main i18n: Detected language from URL:", detectedLang, "path:", path); + return detectedLang; } - //console.log('Язык не указан в URL, используем "ru"'); + console.log('🔍 Main i18n: Language not specified in URL, using "ru"'); return "ru"; // Жёсткий фолбэк }; @@ -46,11 +51,11 @@ i18n caches: [], // Не использовать localStorage }, parseMissingKeyHandler: (key) => { - console.warn("Missing translation:", key); + console.warn("⚠️ Main i18n: Missing translation:", key); return key; // Вернёт ключ вместо ошибки }, missingKeyHandler: (lngs, ns, key) => { - console.error("🚨 Missing i18n key:", { + console.error("🚨 Main i18n: Missing i18n key:", { key, languages: lngs, namespace: ns, @@ -59,34 +64,41 @@ i18n }, }) .then(() => { - console.log("i18n initialized. Current language:", i18n.language); - console.log("Loading translations from:", `/locales/${i18n.language}.json`); + console.log("✅ Main i18n: Initialization completed successfully"); + console.log("🔍 Main i18n: Current language:", i18n.language); + console.log("🔍 Main i18n: Loading translations from:", `/locales/${i18n.language}.json`); + console.log("🔍 Main i18n: i18n.isInitialized:", i18n.isInitialized); + console.log("🔍 Main i18n: i18n.store.data:", i18n.store.data); }) .catch((err) => { - console.error("Ошибка i18n:", err); + console.error("❌ Main i18n: Initialization failed:", err); }); // 3. Логирование всех событий i18n.on("languageChanged", (lng) => { - console.log("Язык изменён на:", lng); + console.log("🔄 Main i18n: Language changed to:", lng); +}); + +i18n.on("initialized", (options) => { + console.log("🎯 Main i18n: Initialized event fired with options:", options); }); i18n.on("failedLoading", (lng, ns, msg) => { - console.error(`Ошибка загрузки ${lng}.json:`, msg); + console.error(`💥 Main i18n: Failed loading ${lng}.json:`, msg); // Если не удалось загрузить русский, пробуем английский if (lng === "ru") { - console.log("Пробуем загрузить английский язык как fallback"); + console.log("🔄 Main i18n: Trying to load English as fallback"); i18n.changeLanguage("en"); } }); i18n.on("loaded", (loaded) => { - console.log("Переводы загружены:", loaded); + console.log("📦 Main i18n: Translations loaded:", loaded); }); i18n.on("missingKey", (lngs, namespace, key, res) => { - console.warn("Отсутствует ключ перевода:", { lngs, namespace, key, res }); + console.warn("⚠️ Main i18n: Missing key event:", { lngs, namespace, key, res }); }); export default i18n; diff --git a/src/i18n/i18nWidget.ts b/src/i18n/i18nWidget.ts index f02af5d..80fb2bc 100644 --- a/src/i18n/i18nWidget.ts +++ b/src/i18n/i18nWidget.ts @@ -5,249 +5,322 @@ import { initReactI18next } from "react-i18next"; const getLanguageFromURL = (): string => { const path = window.location.pathname; const langMatch = path.match(/^\/(en|ru|uz)(\/|$)/i); - return langMatch ? langMatch[1].toLowerCase() : "ru"; // Фолбэк на 'ru' + const detectedLang = langMatch ? langMatch[1].toLowerCase() : "ru"; // Фолбэк на 'ru' + console.log("🔍 Widget i18n: Detected language from URL:", detectedLang, "path:", path); + return detectedLang; }; // 2. Локали, встроенные прямо в конфиг const r = { ru: { - "quiz is inactive": "Квиз не активирован", - "no questions found": "Нет созданных вопросов", - "quiz is empty": "Квиз пуст", - "quiz already completed": "Вы уже прошли этот опрос", - "no quiz id": "Отсутствует id квиза", - "quiz data is null": "Не были переданы параметры квиза", - "invalid request data": "Такого квиза не существует", - "default message": "Что-то пошло не так", - "The request could not be sent": "Заявка не может быть отправлена", - "The number of points could not be sent": "Количество баллов не может быть отправлено", - "Your result": "Ваш результат", - "Your points": "Ваши баллы", - of: "из", - "View answers": "Посмотреть ответы", - "Find out more": "Узнать подробнее", - "Go to website": "Перейти на сайт", - "Question title": "Заголовок вопроса", - "Question without a title": "Вопрос без названия", - "Your answer": "Ваш ответ", - "Add image": "Добавить изображение", - "Accepts images": "Принимает изображения", - "Add video": "Добавить видео", - "Accepts .mp4 and .mov format - maximum 50mb": "Принимает .mp4 и .mov формат — максимум 50мб", - "Add audio file": "Добавить аудиофайл", - "Accepts audio files": "Принимает аудиофайлы", - "Add document": "Добавить документ", - "Accepts documents": "Принимает документы", - Next: "Далее", - Prev: "Назад", - From: "От", - До: "До", - "Enter your answer": "Введите свой ответ", - "Incorrect file type selected": "Выбран некорректный тип файла", - "File is too big. Maximum size is 50 MB": "Файл слишком большой. Максимальный размер 50 МБ", - "Acceptable file extensions": "Допустимые расширения файлов", - "You have uploaded": "Вы загрузили", - "The answer was not counted": "Ответ не был засчитан", - "Select an answer option below": "Выберите вариант ответа ниже", - "Select an answer option on the left": "Выберите вариант ответа слева", - "Fill out the form to receive your test results": "Заполните форму, чтобы получить результаты теста", - Enter: "Введите", - Name: "Имя", - "Phone number": "Номер телефона", - "Last name": "Фамилия", - Address: "Адрес", - "Incorrect email entered": "Введена некорректная почта", - "Please fill in the fields": "Пожалуйста, заполните поля", - "Please try again later": "повторите попытку позже", - "Regulation on the processing of personal data": "Положением об обработке персональных данных", - "Privacy Policy": "Политикой конфиденциальности", - familiarized: "ознакомлен", - and: "и", - "Get results": "Получить результаты", - "Data sent successfully": "Данные успешно отправлены", - Step: "Шаг", - "questions are not ready yet": "Вопросы для аудитории пока не готовы. Подождите", - "Add your image": "Добавьте своё изображение", - "select emoji": "выберите смайлик", - "Please complete the phone number": "Пожалуйста, заполните номер телефона до конца", - "": "", // Пустой ключ для fallback + translation: { + "quiz is inactive": "Квиз не активирован", + "no questions found": "Нет созданных вопросов", + "quiz is empty": "Квиз пуст", + "quiz already completed": "Вы уже прошли этот опрос", + "no quiz id": "Отсутствует id квиза", + "quiz data is null": "Не были переданы параметры квиза", + "invalid request data": "Такого квиза не существует", + "default message": "Что-то пошло не так", + "The request could not be sent": "Заявка не может быть отправлена", + "The number of points could not be sent": "Количество баллов не может быть отправлено", + "Your result": "Ваш результат", + "Your points": "Ваши баллы", + of: "из", + "View answers": "Посмотреть ответы", + "Find out more": "Узнать подробнее", + "Go to website": "Перейти на сайт", + "Question title": "Заголовок вопроса", + "Question without a title": "Вопрос без названия", + "Your answer": "Ваш ответ", + "Add image": "Добавить изображение", + "Accepts images": "Принимает изображения", + "Add video": "Добавить видео", + "Accepts .mp4 and .mov format - maximum 50mb": "Принимает .mp4 и .mov формат — максимум 50мб", + "Add audio file": "Добавить аудиофайл", + "Accepts audio files": "Принимает аудиофайлы", + "Add document": "Добавить документ", + "Accepts documents": "Принимает документы", + Next: "Далее", + Prev: "Назад", + From: "От", + До: "До", + "Enter your answer": "Введите свой ответ", + "Incorrect file type selected": "Выбран некорректный тип файла", + "File is too big. Maximum size is 50 MB": "Файл слишком большой. Максимальный размер 50 МБ", + "Acceptable file extensions": "Допустимые расширения файлов", + "You have uploaded": "Вы загрузили", + "The answer was not counted": "Ответ не был засчитан", + "Select an answer option below": "Выберите вариант ответа ниже", + "Select an answer option on the left": "Выберите вариант ответа слева", + "Fill out the form to receive your test results": "Заполните форму, чтобы получить результаты теста", + Enter: "Введите", + Name: "Имя", + "Phone number": "Номер телефона", + "Last name": "Фамилия", + Address: "Адрес", + "Incorrect email entered": "Введена некорректная почта", + "Please fill in the fields": "Пожалуйста, заполните поля", + "Please try again later": "повторите попытку позже", + "Regulation on the processing of personal data": "Положением об обработке персональных данных", + "Privacy Policy": "Политикой конфиденциальности", + familiarized: "ознакомлен", + and: "и", + "Get results": "Получить результаты", + "Data sent successfully": "Данные успешно отправлены", + Step: "Шаг", + "questions are not ready yet": "Вопросы для аудитории пока не готовы. Подождите", + "Add your image": "Добавьте своё изображение", + "select emoji": "выберите смайлик", + "Please complete the phone number": "Пожалуйста, заполните номер телефона до конца", + "": "", // Пустой ключ для fallback + }, }, en: { - "quiz is inactive": "Quiz is inactive", - "no questions found": "No questions found", - "quiz is empty": "Quiz is empty", - "quiz already completed": "You've already completed this quiz", - "no quiz id": "Missing quiz ID", - "quiz data is null": "No quiz parameters were provided", - "invalid request data": "This quiz doesn't exist", - "default message": "Something went wrong", - "The request could not be sent": "Request could not be sent", - "The number of points could not be sent": "Points could not be submitted", - "Your result": "Your result", - "Your points": "Your points", - of: "of", - "View answers": "View answers", - "Find out more": "Learn more", - "Go to website": "Go to website", - "Question title": "Question title", - "Question without a title": "Untitled question", - "Your answer": "Your answer", - "Add image": "Add image", - "Accepts images": "Accepts images", - "Add video": "Add video", - "Accepts .mp4 and .mov format - maximum 50mb": "Accepts .mp4 and .mov format - maximum 50MB", - "Add audio file": "Add audio file", - "Accepts audio files": "Accepts audio files", - "Add document": "Add document", - "Accepts documents": "Accepts documents", - Next: "Next", - Prev: "Previous", - From: "From", - До: "To", - "Enter your answer": "Enter your answer", - "Incorrect file type selected": "Invalid file type selected", - "File is too big. Maximum size is 50 MB": "File is too large. Maximum size is 50 MB", - "Acceptable file extensions": "Allowed file extensions", - "You have uploaded": "You've uploaded", - "The answer was not counted": "Answer wasn't counted", - "Select an answer option below": "Select an answer option below", - "Select an answer option on the left": "Select an answer option on the left", - "Fill out the form to receive your test results": "Fill out the form to receive your test results", - Enter: "Enter", - Name: "Name", - "Phone number": "Phone number", - "Last name": "Last name", - Address: "Address", - "Incorrect email entered": "Invalid email entered", - "Please fill in the fields": "Please fill in the fields", - "Please try again later": "Please try again later", - "Regulation on the processing of personal data": "Personal Data Processing Regulation", - "Privacy Policy": "Privacy Policy", - familiarized: "acknowledged", - and: "and", - "Get results": "Get results", - "Data sent successfully": "Data sent successfully", - Step: "Step", - "questions are not ready yet": "There are no questions for the audience yet. Please wait", - "Add your image": "Add your image", - "select emoji": "select emoji", - "Please complete the phone number": "Please complete the phone number", - "": "", // Пустой ключ для fallback + translation: { + "quiz is inactive": "Quiz is inactive", + "no questions found": "No questions found", + "quiz is empty": "Quiz is empty", + "quiz already completed": "You've already completed this quiz", + "no quiz id": "Missing quiz ID", + "quiz data is null": "No quiz parameters were provided", + "invalid request data": "This quiz doesn't exist", + "default message": "Something went wrong", + "The request could not be sent": "Request could not be sent", + "The number of points could not be sent": "Points could not be submitted", + "Your result": "Your result", + "Your points": "Your points", + of: "of", + "View answers": "View answers", + "Find out more": "Learn more", + "Go to website": "Go to website", + "Question title": "Question title", + "Question without a title": "Untitled question", + "Your answer": "Your answer", + "Add image": "Add image", + "Accepts images": "Accepts images", + "Add video": "Add video", + "Accepts .mp4 and .mov format - maximum 50mb": "Accepts .mp4 and .mov format - maximum 50MB", + "Add audio file": "Add audio file", + "Accepts audio files": "Accepts audio files", + "Add document": "Add document", + "Accepts documents": "Accepts documents", + Next: "Next", + Prev: "Previous", + From: "From", + До: "To", + "Enter your answer": "Enter your answer", + "Incorrect file type selected": "Invalid file type selected", + "File is too big. Maximum size is 50 MB": "File is too large. Maximum size is 50 MB", + "Acceptable file extensions": "Allowed file extensions", + "You have uploaded": "You've uploaded", + "The answer was not counted": "Answer wasn't counted", + "Select an answer option below": "Select an answer option below", + "Select an answer option on the left": "Select an answer option on the left", + "Fill out the form to receive your test results": "Fill out the form to receive your test results", + Enter: "Enter", + Name: "Name", + "Phone number": "Phone number", + "Last name": "Last name", + Address: "Address", + "Incorrect email entered": "Invalid email entered", + "Please fill in the fields": "Please fill in the fields", + "Please try again later": "Please try again later", + "Regulation on the processing of personal data": "Personal Data Processing Regulation", + "Privacy Policy": "Privacy Policy", + familiarized: "acknowledged", + and: "and", + "Get results": "Get results", + "Data sent successfully": "Data sent successfully", + Step: "Step", + "questions are not ready yet": "There are no questions for the audience yet. Please wait", + "Add your image": "Add your image", + "select emoji": "select emoji", + "Please complete the phone number": "Please complete the phone number", + "": "", // Пустой ключ для fallback + }, }, uz: { - "quiz is inactive": "Test faol emas", - "no questions found": "Savollar topilmadi", - "quiz is empty": "Test boʻsh", - "quiz already completed": "Siz bu testni allaqachon topshirgansiz", - "no quiz id": "Test IDsi yoʻq", - "quiz data is null": "Test parametrlari yuborilmagan", - "invalid request data": "Bunday test mavjud emas", - "default message": "Xatolik yuz berdi", - "The request could not be sent": "Soʻrov yuborib boʻlmadi", - "The number of points could not be sent": "Ballar yuborib boʻlmadi", - "Your result": "Sizning natijangiz", - "Your points": "Sizning ballaringiz", - of: "/", - "View answers": "Javoblarni koʻrish", - "Find out more": "Batafsil maʼlumot", - "Go to website": "Veb-saytga oʻtish", - "Question title": "Savol sarlavhasi", - "Question without a title": "Sarlavhasiz savol", - "Your answer": "Sizning javobingiz", - "Add image": "Rasm qoʻshish", - "Accepts images": "Rasmlarni qabul qiladi", - "Add video": "Video qoʻshish", - "Accepts .mp4 and .mov format - maximum 50mb": ".mp4 va .mov formatlarini qabul qiladi - maksimal 50MB", - "Add audio file": "Audio fayl qoʻshish", - "Accepts audio files": "Audio fayllarni qabul qiladi", - "Add document": "Hujjat qoʻshish", - "Accepts documents": "Hujjatlarni qabul qiladi", - Next: "Keyingi", - Prev: "Oldingi", - From: "Dan", - До: "Gacha", - "Enter your answer": "Javobingizni kiriting", - "Incorrect file type selected": "Notoʻgʻri fayl turi tanlandi", - "File is too big. Maximum size is 50 MB": "Fayl juda katta. Maksimal hajmi 50 MB", - "Acceptable file extensions": "Qabul qilinadigan fayl kengaytmalari", - "You have uploaded": "Siz yuklagansiz", - "The answer was not counted": "Javob hisobga olinmadi", - "Select an answer option below": "Quyidagi javob variantlaridan birini tanlang", - "Select an answer option on the left": "Chapdagi javob variantlaridan birini tanlang", - "Fill out the form to receive your test results": "Test natijalaringizni olish uchun shaklni toʻldiring", - Enter: "Kiriting", - Name: "Ism", - "Phone number": "Telefon raqami", - "Last name": "Familiya", - Address: "Manzil", - "Incorrect email entered": "Notoʻgʻri elektron pochta kiritildi", - "Please fill in the fields": "Iltimos, maydonlarni toʻldiring", - "Please try again later": "Iltimos, keyinroq urinib koʻring", - "Regulation on the processing of personal data": "Shaxsiy maʼlumotlarni qayta ishlash qoidalari", - "Privacy Policy": "Maxfiylik siyosati", - familiarized: "tanishdim", - and: "va", - "Get results": "Natijalarni olish", - "Data sent successfully": "Ma'lumotlar muvaffaqiyatli yuborildi", - Step: "Qadam", - "questions are not ready yet": "Tomoshabinlar uchun hozircha savollar yo'q. Iltimos kuting", - "Add your image": "Rasmingizni qo'shing", - "select emoji": "emoji tanlang", - "Please complete the phone number": "Iltimos, telefon raqamini to'liq kiriting", - "": "", // Пустой ключ для fallback + translation: { + "quiz is inactive": "Test faol emas", + "no questions found": "Savollar topilmadi", + "quiz is empty": "Test boʻsh", + "quiz already completed": "Siz bu testni allaqachon topshirgansiz", + "no quiz id": "Test IDsi yoʻq", + "quiz data is null": "Test parametrlari yuborilmagan", + "invalid request data": "Bunday test mavjud emas", + "default message": "Xatolik yuz berdi", + "The request could not be sent": "Soʻrov yuborib boʻlmadi", + "The number of points could not be sent": "Ballar yuborib boʻlmadi", + "Your result": "Sizning natijangiz", + "Your points": "Sizning ballaringiz", + of: "/", + "View answers": "Javoblarni koʻrish", + "Find out more": "Batafsil maʼlumot", + "Go to website": "Veb-saytga oʻtish", + "Question title": "Savol sarlavhasi", + "Question without a title": "Sarlavhasiz savol", + "Your answer": "Sizning javobingiz", + "Add image": "Rasm qoʻshish", + "Accepts images": "Rasmlarni qabul qiladi", + "Add video": "Video qoʻshish", + "Accepts .mp4 and .mov format - maximum 50mb": ".mp4 va .mov formatlarini qabul qiladi - maksimal 50MB", + "Add audio file": "Audio fayl qoʻshish", + "Accepts audio files": "Audio fayllarni qabul qiladi", + "Add document": "Hujjat qoʻshish", + "Accepts documents": "Hujjatlarni qabul qiladi", + Next: "Keyingi", + Prev: "Oldingi", + From: "Dan", + До: "Gacha", + "Enter your answer": "Javobingizni kiriting", + "Incorrect file type selected": "Notoʻgʻri fayl turi tanlandi", + "File is too big. Maximum size is 50 MB": "Fayl juda katta. Maksimal hajmi 50 MB", + "Acceptable file extensions": "Qabul qilinadigan fayl kengaytmalari", + "You have uploaded": "Siz yuklagansiz", + "The answer was not counted": "Javob hisobga olinmadi", + "Select an answer option below": "Quyidagi javob variantlaridan birini tanlang", + "Select an answer option on the left": "Chapdagi javob variantlaridan birini tanlang", + "Fill out the form to receive your test results": "Test natijalaringizni olish uchun shaklni toʻldiring", + Enter: "Kiriting", + Name: "Ism", + "Phone number": "Telefon raqami", + "Last name": "Familiya", + Address: "Manzil", + "Incorrect email entered": "Notoʻgʻri elektron pochta kiritildi", + "Please fill in the fields": "Iltimos, maydonlarni toʻldiring", + "Please try again later": "Iltimos, keyinroq urinib koʻring", + "Regulation on the processing of personal data": "Shaxsiy maʼlumotlarni qayta ishlash qoidalari", + "Privacy Policy": "Maxfiylik siyosati", + familiarized: "tanishdim", + and: "va", + "Get results": "Natijalarni olish", + "Data sent successfully": "Ma'lumotlar muvaffaqiyatli yuborildi", + Step: "Qadam", + "questions are not ready yet": "Tomoshabinlar uchun hozircha savollar yo'q. Iltimos kuting", + "Add your image": "Rasmingizni qo'shing", + "select emoji": "emoji tanlang", + "Please complete the phone number": "Iltimos, telefon raqamini to'liq kiriting", + "": "", // Пустой ключ для fallback + }, }, }; -// 3. Конфигурация i18n без Backend -i18n - .use(initReactI18next) - .init({ - resources: r, // Используем встроенные переводы - lng: getLanguageFromURL(), - fallbackLng: "ru", - supportedLngs: ["en", "ru", "uz"], - debug: true, - interpolation: { - escapeValue: false, - }, - react: { - useSuspense: false, - }, - detection: { - order: ["path"], - lookupFromPathIndex: 0, - caches: [], - }, - parseMissingKeyHandler: (key) => { - console.warn("Missing translation:", key); - return key; - }, - missingKeyHandler: (lngs, ns, key) => { - console.error("🚨 Missing i18n key:", { - key, - languages: lngs, - namespace: ns, - stack: new Error().stack, - }); - }, - }) - .then(() => { - console.log("i18n initialized. Current language:", i18n.language); - console.log("Available languages:", i18n.languages); - console.log("Available keys for ru:", Object.keys(r.ru)); - console.log("Available keys for en:", Object.keys(r.en)); - console.log("Available keys for uz:", Object.keys(r.uz)); - console.log("Looking for keys: Step, of, Prev, Next"); - console.log("Step in ru:", r.ru.Step); - console.log("of in ru:", r.ru.of); - console.log("Prev in ru:", r.ru.Prev); - console.log("Next in ru:", r.ru.Next); +console.log("🚀 Widget i18n: Starting initialization..."); +console.log("🔍 Widget i18n: i18next instance before init:", i18n); +console.log("🔍 Widget i18n: i18next isInitialized before init:", i18n.isInitialized); + +// Проверяем, не инициализирован ли уже i18n +if (i18n.isInitialized) { + console.log("⚠️ Widget i18n: i18n already initialized, adding resources to existing instance"); + + // Добавляем ресурсы к существующему экземпляру + (Object.keys(r) as Array<"ru" | "en" | "uz">).forEach((lng) => { + if (i18n.store.data[lng] && i18n.store.data[lng].translation) { + // Объединяем с существующими переводами + i18n.store.data[lng].translation = { + ...(i18n.store.data[lng].translation as Record), + ...r[lng].translation, + }; + } else { + // Добавляем новые переводы + i18n.store.data[lng] = { + ...(i18n.store.data[lng] as Record), + translation: r[lng].translation, + }; + } }); + console.log("✅ Widget i18n: Resources added to existing i18n instance"); + console.log("🔍 Widget i18n: Testing translations after adding resources:"); + console.log("🔍 Widget i18n: t('Step'):", i18n.t("Step")); + console.log("🔍 Widget i18n: t('of'):", i18n.t("of")); + console.log("🔍 Widget i18n: t('Prev'):", i18n.t("Prev")); + console.log("🔍 Widget i18n: t('Next'):", i18n.t("Next")); +} else { + // 3. Конфигурация i18n без Backend + i18n + .use(initReactI18next) + .init({ + resources: r, // Используем встроенные переводы + lng: getLanguageFromURL(), + fallbackLng: "ru", + supportedLngs: ["en", "ru", "uz"], + debug: true, + interpolation: { + escapeValue: false, + }, + react: { + useSuspense: false, + }, + detection: { + order: ["path"], + lookupFromPathIndex: 0, + caches: [], + }, + parseMissingKeyHandler: (key) => { + console.warn("⚠️ Widget i18n: Missing translation key:", key); + return key; + }, + missingKeyHandler: (lngs, ns, key) => { + console.error("🚨 Widget i18n: Missing i18n key:", { + key, + languages: lngs, + namespace: ns, + stack: new Error().stack, + }); + }, + }) + .then(() => { + console.log("✅ Widget i18n: Initialization completed successfully"); + console.log("🔍 Widget i18n: Current language:", i18n.language); + console.log("🔍 Widget i18n: Available languages:", i18n.languages); + console.log("🔍 Widget i18n: Available keys for ru:", Object.keys(r.ru.translation)); + console.log("🔍 Widget i18n: Available keys for en:", Object.keys(r.en.translation)); + console.log("🔍 Widget i18n: Available keys for uz:", Object.keys(r.uz.translation)); + console.log("🔍 Widget i18n: Looking for keys: Step, of, Prev, Next"); + console.log("🔍 Widget i18n: Step in ru:", r.ru.translation.Step); + console.log("🔍 Widget i18n: of in ru:", r.ru.translation.of); + console.log("🔍 Widget i18n: Prev in ru:", r.ru.translation.Prev); + console.log("🔍 Widget i18n: Next in ru:", r.ru.translation.Next); + + // Проверяем, что ключи доступны через i18n + console.log("🔍 Widget i18n: Testing translations through i18n:"); + console.log("🔍 Widget i18n: t('Step'):", i18n.t("Step")); + console.log("🔍 Widget i18n: t('of'):", i18n.t("of")); + console.log("🔍 Widget i18n: t('Prev'):", i18n.t("Prev")); + console.log("🔍 Widget i18n: t('Next'):", i18n.t("Next")); + + // Проверяем состояние i18n + console.log("🔍 Widget i18n: i18n.isInitialized:", i18n.isInitialized); + console.log("🔍 Widget i18n: i18n.store.data:", i18n.store.data); + console.log("🔍 Widget i18n: i18n.store.options:", i18n.store.options); + }) + .catch((error) => { + console.error("❌ Widget i18n: Initialization failed:", error); + }); +} + // 4. Логирование событий i18n.on("languageChanged", (lng) => { - console.log("Language changed to:", lng); + console.log("🔄 Widget i18n: Language changed to:", lng); +}); + +i18n.on("initialized", (options) => { + console.log("🎯 Widget i18n: Initialized event fired with options:", options); +}); + +i18n.on("loaded", (loaded) => { + console.log("📦 Widget i18n: Loaded event fired:", loaded); +}); + +i18n.on("failedLoading", (lng, ns, msg) => { + console.error("💥 Widget i18n: Failed loading:", { lng, ns, msg }); +}); + +i18n.on("missingKey", (lngs, namespace, key, res) => { + console.warn("⚠️ Widget i18n: Missing key event:", { lngs, namespace, key, res }); }); export default i18n; diff --git a/src/main.tsx b/src/main.tsx index 48b5bbc..ea5912d 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,8 +2,14 @@ import { createRoot } from "react-dom/client"; import { RouteObject, RouterProvider, createBrowserRouter } from "react-router-dom"; import App from "./App"; import { StrictMode, lazy } from "react"; + +console.log("🚀 Main: Starting main application..."); +console.log("🔍 Main: About to import main i18n..."); + import "./i18n/i18n"; +console.log("✅ Main: Main i18n imported successfully"); + const routes: RouteObject[] = [ { path: "/", @@ -38,8 +44,12 @@ const router = createBrowserRouter(routes); const root = createRoot(document.getElementById("root")!); +console.log("🔧 Main: About to render application..."); + root.render( ); + +console.log("✅ Main: Application rendered successfully"); diff --git a/src/widget.tsx b/src/widget.tsx index 97c62d8..79a827d 100644 --- a/src/widget.tsx +++ b/src/widget.tsx @@ -2,8 +2,14 @@ import QuizAnswerer from "@/components/QuizAnswerer"; import { createRoot } from "react-dom/client"; // eslint-disable-next-line react-refresh/only-export-components export * from "./widgets"; + +console.log("🚀 Widget: Starting widget initialization..."); +console.log("🔍 Widget: About to import i18nWidget..."); + import "./i18n/i18nWidget"; +console.log("✅ Widget: i18nWidget imported successfully"); + // old widget const widget = { create({ @@ -15,11 +21,18 @@ const widget = { quizId: string; changeFaviconAndTitle: boolean; }) { - const element = document.getElementById(selector); - if (!element) throw new Error("Element for widget doesn't exist"); + console.log("🔧 Widget: create() called with:", { selector, quizId, changeFaviconAndTitle }); + const element = document.getElementById(selector); + if (!element) { + console.error("❌ Widget: Element for widget doesn't exist:", selector); + throw new Error("Element for widget doesn't exist"); + } + + console.log("✅ Widget: Element found, creating root..."); const root = createRoot(element); + console.log("🔧 Widget: Rendering QuizAnswerer component..."); root.render( ); + console.log("✅ Widget: QuizAnswerer component rendered successfully"); }, }; +console.log("✅ Widget: Widget object created and exported"); + export default widget; -- 2.45.2