From 5202ff6e3ee6bbe6d958d43b088902c849399c82 Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Sun, 20 Jul 2025 15:44:50 +0200 Subject: [PATCH] feat: implement lazy loading for languages in i18n, enhance LanguageSwitcher to handle language changes asynchronously, and update available languages management --- src/components/LanguageSwitcher.js | 60 ++-- src/i18n/index.js | 492 ++++++++++------------------- src/i18n/withTranslation.js | 56 +++- 3 files changed, 246 insertions(+), 362 deletions(-) diff --git a/src/components/LanguageSwitcher.js b/src/components/LanguageSwitcher.js index 288a68e..6513943 100644 --- a/src/components/LanguageSwitcher.js +++ b/src/components/LanguageSwitcher.js @@ -24,10 +24,14 @@ class LanguageSwitcher extends Component { this.setState({ anchorEl: null }); }; - handleLanguageChange = (language) => { + handleLanguageChange = async (language) => { const { languageContext } = this.props; if (languageContext) { - languageContext.changeLanguage(language); + try { + await languageContext.changeLanguage(language); + } catch (error) { + console.error('Failed to change language:', error); + } } this.handleClose(); }; @@ -126,8 +130,8 @@ class LanguageSwitcher extends Component { const { languageContext } = this.props; if (anchorEl && !prevState.anchorEl && languageContext) { - // Menu just opened, lazy load all flags - languageContext.availableLanguages.forEach(lang => { + // Menu just opened, lazy load flags for all languages (not just available ones) + languageContext.allLanguages.forEach(lang => { if (!this.state.loadedFlags[lang]) { this.loadFlagComponent(lang); } @@ -197,7 +201,7 @@ class LanguageSwitcher extends Component { return null; } - const { currentLanguage, availableLanguages } = languageContext; + const { currentLanguage, allLanguages } = languageContext; const open = Boolean(anchorEl); return ( @@ -237,29 +241,31 @@ class LanguageSwitcher extends Component { horizontal: 'right', }} > - {availableLanguages.map((language) => ( - this.handleLanguageChange(language)} - selected={language === currentLanguage} - sx={{ - minWidth: 160, - display: 'flex', - justifyContent: 'space-between', - gap: 2 - }} - > - - {this.getLanguageFlag(language)} - - {this.getLanguageName(language)} + {allLanguages.map((language) => { + return ( + this.handleLanguageChange(language)} + selected={language === currentLanguage} + sx={{ + minWidth: 160, + display: 'flex', + justifyContent: 'space-between', + gap: 2 + }} + > + + {this.getLanguageFlag(language)} + + {this.getLanguageName(language)} + + + + {this.getLanguageLabel(language)} - - - {this.getLanguageLabel(language)} - - - ))} + + ); + })} ); diff --git a/src/i18n/index.js b/src/i18n/index.js index 14f2775..f300b82 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -1,178 +1,128 @@ import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; -import LanguageDetector from 'i18next-browser-languagedetector'; +// Note: LanguageDetector not used - we have custom detector -// Import all translation files +// Only import German translations by default import translationDE from './locales/de/index.js'; -import translationEN from './locales/en/index.js'; -import translationAR from './locales/ar/translation.js'; -import translationBG from './locales/bg/translation.js'; -import translationCS from './locales/cs/translation.js'; -import translationEL from './locales/el/translation.js'; -import translationES from './locales/es/translation.js'; -import translationFR from './locales/fr/translation.js'; -import translationHR from './locales/hr/translation.js'; -import translationHU from './locales/hu/translation.js'; -import translationIT from './locales/it/translation.js'; -import translationPL from './locales/pl/translation.js'; -import translationRO from './locales/ro/translation.js'; -import translationRU from './locales/ru/translation.js'; -import translationSK from './locales/sk/translation.js'; -import translationSL from './locales/sl/translation.js'; -import translationSR from './locales/sr/translation.js'; -import translationSV from './locales/sv/translation.js'; -import translationTR from './locales/tr/translation.js'; -import translationUK from './locales/uk/translation.js'; -import translationZH from './locales/zh/translation.js'; - -// Import legal translations for all languages -// German import legalAgbDE from './locales/de/legal-agb.js'; import legalDatenschutzDE from './locales/de/legal-datenschutz.js'; import legalImpressumDE from './locales/de/legal-impressum.js'; import legalWiderrufDE from './locales/de/legal-widerruf.js'; import legalBatterieDE from './locales/de/legal-batterie.js'; -// English -import legalAgbEN from './locales/en/legal-agb.js'; -import legalDatenschutzEN from './locales/en/legal-datenschutz.js'; -import legalImpressumEN from './locales/en/legal-impressum.js'; -import legalWiderrufEN from './locales/en/legal-widerruf.js'; -import legalBatterieEN from './locales/en/legal-batterie.js'; +// Language loading cache to prevent duplicate loads +const languageCache = new Set(['de']); +const loadingPromises = new Map(); -// Arabic -import legalAgbAR from './locales/ar/legal-agb.js'; -import legalDatenschutzAR from './locales/ar/legal-datenschutz.js'; -import legalImpressumAR from './locales/ar/legal-impressum.js'; -import legalWiderrufAR from './locales/ar/legal-widerruf.js'; -import legalBatterieAR from './locales/ar/legal-batterie.js'; +// Lazy loading function for languages +const loadLanguage = async (language) => { + if (languageCache.has(language)) { + return; // Already loaded + } -// Bulgarian -import legalAgbBG from './locales/bg/legal-agb.js'; -import legalDatenschutzBG from './locales/bg/legal-datenschutz.js'; -import legalImpressumBG from './locales/bg/legal-impressum.js'; -import legalWiderrufBG from './locales/bg/legal-widerruf.js'; -import legalBatterieBG from './locales/bg/legal-batterie.js'; + if (loadingPromises.has(language)) { + return loadingPromises.get(language); // Already loading + } -// Czech -import legalAgbCS from './locales/cs/legal-agb.js'; -import legalDatenschutzCS from './locales/cs/legal-datenschutz.js'; -import legalImpressumCS from './locales/cs/legal-impressum.js'; -import legalWiderrufCS from './locales/cs/legal-widerruf.js'; -import legalBatterieCS from './locales/cs/legal-batterie.js'; + const loadingPromise = (async () => { + try { + console.log(`🌍 Lazy loading language: ${language}`); + + // Dynamic imports for lazy loading + const [ + translation, + legalAgb, + legalDatenschutz, + legalImpressum, + legalWiderruf, + legalBatterie + ] = await Promise.all([ + import(`./locales/${language}/index.js`), + import(`./locales/${language}/legal-agb.js`), + import(`./locales/${language}/legal-datenschutz.js`), + import(`./locales/${language}/legal-impressum.js`), + import(`./locales/${language}/legal-widerruf.js`), + import(`./locales/${language}/legal-batterie.js`) + ]); -// Greek -import legalAgbEL from './locales/el/legal-agb.js'; -import legalDatenschutzEL from './locales/el/legal-datenschutz.js'; -import legalImpressumEL from './locales/el/legal-impressum.js'; -import legalWiderrufEL from './locales/el/legal-widerruf.js'; -import legalBatterieEL from './locales/el/legal-batterie.js'; + // Add the loaded resources to i18n + i18n.addResourceBundle(language, 'translation', translation.default); + i18n.addResourceBundle(language, 'legal-agb', legalAgb.default); + i18n.addResourceBundle(language, 'legal-datenschutz', legalDatenschutz.default); + i18n.addResourceBundle(language, 'legal-impressum', legalImpressum.default); + i18n.addResourceBundle(language, 'legal-widerruf', legalWiderruf.default); + i18n.addResourceBundle(language, 'legal-batterie', legalBatterie.default); -// Spanish -import legalAgbES from './locales/es/legal-agb.js'; -import legalDatenschutzES from './locales/es/legal-datenschutz.js'; -import legalImpressumES from './locales/es/legal-impressum.js'; -import legalWiderrufES from './locales/es/legal-widerruf.js'; -import legalBatterieES from './locales/es/legal-batterie.js'; + languageCache.add(language); + console.log(`✅ Language ${language} loaded successfully`); + } catch (error) { + console.error(`❌ Failed to load language ${language}:`, error); + throw error; + } finally { + loadingPromises.delete(language); + } + })(); -// French -import legalAgbFR from './locales/fr/legal-agb.js'; -import legalDatenschutzFR from './locales/fr/legal-datenschutz.js'; -import legalImpressumFR from './locales/fr/legal-impressum.js'; -import legalWiderrufFR from './locales/fr/legal-widerruf.js'; -import legalBatterieFR from './locales/fr/legal-batterie.js'; + loadingPromises.set(language, loadingPromise); + return loadingPromise; +}; -// Croatian -import legalAgbHR from './locales/hr/legal-agb.js'; -import legalDatenschutzHR from './locales/hr/legal-datenschutz.js'; -import legalImpressumHR from './locales/hr/legal-impressum.js'; -import legalWiderrufHR from './locales/hr/legal-widerruf.js'; -import legalBatterieHR from './locales/hr/legal-batterie.js'; +// Custom language detector that prioritizes session storage and defaults to German +const customDetector = { + name: 'customDetector', + lookup() { + // Only try storage in browser environment + if (typeof window === 'undefined') { + return 'de'; + } -// Hungarian -import legalAgbHU from './locales/hu/legal-agb.js'; -import legalDatenschutzHU from './locales/hu/legal-datenschutz.js'; -import legalImpressumHU from './locales/hu/legal-impressum.js'; -import legalWiderrufHU from './locales/hu/legal-widerruf.js'; -import legalBatterieHU from './locales/hu/legal-batterie.js'; + // 1. Check session storage first + try { + if (typeof sessionStorage !== 'undefined') { + const sessionLang = sessionStorage.getItem('i18nextLng'); + if (sessionLang && sessionLang !== 'de') { + return sessionLang; + } + } + } catch { + // Session storage not available + } -// Italian -import legalAgbIT from './locales/it/legal-agb.js'; -import legalDatenschutzIT from './locales/it/legal-datenschutz.js'; -import legalImpressumIT from './locales/it/legal-impressum.js'; -import legalWiderrufIT from './locales/it/legal-widerruf.js'; -import legalBatterieIT from './locales/it/legal-batterie.js'; + // 2. Check localStorage + try { + if (typeof localStorage !== 'undefined') { + const localLang = localStorage.getItem('i18nextLng'); + if (localLang && localLang !== 'de') { + return localLang; + } + } + } catch { + // LocalStorage not available + } -// Polish -import legalAgbPL from './locales/pl/legal-agb.js'; -import legalDatenschutzPL from './locales/pl/legal-datenschutz.js'; -import legalImpressumPL from './locales/pl/legal-impressum.js'; -import legalWiderrufPL from './locales/pl/legal-widerruf.js'; -import legalBatteriePL from './locales/pl/legal-batterie.js'; + // 3. Always default to German (don't detect browser language) + return 'de'; + }, + cacheUserLanguage(lng) { + // Only cache in browser environment + if (typeof window === 'undefined') { + return; + } -// Romanian -import legalAgbRO from './locales/ro/legal-agb.js'; -import legalDatenschutzRO from './locales/ro/legal-datenschutz.js'; -import legalImpressumRO from './locales/ro/legal-impressum.js'; -import legalWiderrufRO from './locales/ro/legal-widerruf.js'; -import legalBatterieRO from './locales/ro/legal-batterie.js'; - -// Russian -import legalAgbRU from './locales/ru/legal-agb.js'; -import legalDatenschutzRU from './locales/ru/legal-datenschutz.js'; -import legalImpressumRU from './locales/ru/legal-impressum.js'; -import legalWiderrufRU from './locales/ru/legal-widerruf.js'; -import legalBatterieRU from './locales/ru/legal-batterie.js'; - -// Slovak -import legalAgbSK from './locales/sk/legal-agb.js'; -import legalDatenschutzSK from './locales/sk/legal-datenschutz.js'; -import legalImpressumSK from './locales/sk/legal-impressum.js'; -import legalWiderrufSK from './locales/sk/legal-widerruf.js'; -import legalBatterieSK from './locales/sk/legal-batterie.js'; - -// Slovenian -import legalAgbSL from './locales/sl/legal-agb.js'; -import legalDatenschutzSL from './locales/sl/legal-datenschutz.js'; -import legalImpressumSL from './locales/sl/legal-impressum.js'; -import legalWiderrufSL from './locales/sl/legal-widerruf.js'; -import legalBatterieSL from './locales/sl/legal-batterie.js'; - -// Serbian -import legalAgbSR from './locales/sr/legal-agb.js'; -import legalDatenschutzSR from './locales/sr/legal-datenschutz.js'; -import legalImpressumSR from './locales/sr/legal-impressum.js'; -import legalWiderrufSR from './locales/sr/legal-widerruf.js'; -import legalBatterieSR from './locales/sr/legal-batterie.js'; - -// Swedish -import legalAgbSV from './locales/sv/legal-agb.js'; -import legalDatenschutzSV from './locales/sv/legal-datenschutz.js'; -import legalImpressumSV from './locales/sv/legal-impressum.js'; -import legalWiderrufSV from './locales/sv/legal-widerruf.js'; -import legalBatterieSV from './locales/sv/legal-batterie.js'; - -// Turkish -import legalAgbTR from './locales/tr/legal-agb.js'; -import legalDatenschutzTR from './locales/tr/legal-datenschutz.js'; -import legalImpressumTR from './locales/tr/legal-impressum.js'; -import legalWiderrufTR from './locales/tr/legal-widerruf.js'; -import legalBatterieTR from './locales/tr/legal-batterie.js'; - -// Ukrainian -import legalAgbUK from './locales/uk/legal-agb.js'; -import legalDatenschutzUK from './locales/uk/legal-datenschutz.js'; -import legalImpressumUK from './locales/uk/legal-impressum.js'; -import legalWiderrufUK from './locales/uk/legal-widerruf.js'; -import legalBatterieUK from './locales/uk/legal-batterie.js'; - -// Chinese -import legalAgbZH from './locales/zh/legal-agb.js'; -import legalDatenschutzZH from './locales/zh/legal-datenschutz.js'; -import legalImpressumZH from './locales/zh/legal-impressum.js'; -import legalWiderrufZH from './locales/zh/legal-widerruf.js'; -import legalBatterieZH from './locales/zh/legal-batterie.js'; + try { + if (typeof sessionStorage !== 'undefined') { + sessionStorage.setItem('i18nextLng', lng); + } + if (typeof localStorage !== 'undefined') { + localStorage.setItem('i18nextLng', lng); + } + } catch { + // Storage not available + } + } +}; +// Initialize i18n with only German resources const resources = { de: { translation: translationDE, @@ -181,185 +131,28 @@ const resources = { 'legal-impressum': legalImpressumDE, 'legal-widerruf': legalWiderrufDE, 'legal-batterie': legalBatterieDE - }, - en: { - translation: translationEN, - 'legal-agb': legalAgbEN, - 'legal-datenschutz': legalDatenschutzEN, - 'legal-impressum': legalImpressumEN, - 'legal-widerruf': legalWiderrufEN, - 'legal-batterie': legalBatterieEN - }, - ar: { - translation: translationAR, - 'legal-agb': legalAgbAR, - 'legal-datenschutz': legalDatenschutzAR, - 'legal-impressum': legalImpressumAR, - 'legal-widerruf': legalWiderrufAR, - 'legal-batterie': legalBatterieAR - }, - bg: { - translation: translationBG, - 'legal-agb': legalAgbBG, - 'legal-datenschutz': legalDatenschutzBG, - 'legal-impressum': legalImpressumBG, - 'legal-widerruf': legalWiderrufBG, - 'legal-batterie': legalBatterieBG - }, - cs: { - translation: translationCS, - 'legal-agb': legalAgbCS, - 'legal-datenschutz': legalDatenschutzCS, - 'legal-impressum': legalImpressumCS, - 'legal-widerruf': legalWiderrufCS, - 'legal-batterie': legalBatterieCS - }, - el: { - translation: translationEL, - 'legal-agb': legalAgbEL, - 'legal-datenschutz': legalDatenschutzEL, - 'legal-impressum': legalImpressumEL, - 'legal-widerruf': legalWiderrufEL, - 'legal-batterie': legalBatterieEL - }, - es: { - translation: translationES, - 'legal-agb': legalAgbES, - 'legal-datenschutz': legalDatenschutzES, - 'legal-impressum': legalImpressumES, - 'legal-widerruf': legalWiderrufES, - 'legal-batterie': legalBatterieES - }, - fr: { - translation: translationFR, - 'legal-agb': legalAgbFR, - 'legal-datenschutz': legalDatenschutzFR, - 'legal-impressum': legalImpressumFR, - 'legal-widerruf': legalWiderrufFR, - 'legal-batterie': legalBatterieFR - }, - hr: { - translation: translationHR, - 'legal-agb': legalAgbHR, - 'legal-datenschutz': legalDatenschutzHR, - 'legal-impressum': legalImpressumHR, - 'legal-widerruf': legalWiderrufHR, - 'legal-batterie': legalBatterieHR - }, - hu: { - translation: translationHU, - 'legal-agb': legalAgbHU, - 'legal-datenschutz': legalDatenschutzHU, - 'legal-impressum': legalImpressumHU, - 'legal-widerruf': legalWiderrufHU, - 'legal-batterie': legalBatterieHU - }, - it: { - translation: translationIT, - 'legal-agb': legalAgbIT, - 'legal-datenschutz': legalDatenschutzIT, - 'legal-impressum': legalImpressumIT, - 'legal-widerruf': legalWiderrufIT, - 'legal-batterie': legalBatterieIT - }, - pl: { - translation: translationPL, - 'legal-agb': legalAgbPL, - 'legal-datenschutz': legalDatenschutzPL, - 'legal-impressum': legalImpressumPL, - 'legal-widerruf': legalWiderrufPL, - 'legal-batterie': legalBatteriePL - }, - ro: { - translation: translationRO, - 'legal-agb': legalAgbRO, - 'legal-datenschutz': legalDatenschutzRO, - 'legal-impressum': legalImpressumRO, - 'legal-widerruf': legalWiderrufRO, - 'legal-batterie': legalBatterieRO - }, - ru: { - translation: translationRU, - 'legal-agb': legalAgbRU, - 'legal-datenschutz': legalDatenschutzRU, - 'legal-impressum': legalImpressumRU, - 'legal-widerruf': legalWiderrufRU, - 'legal-batterie': legalBatterieRU - }, - sk: { - translation: translationSK, - 'legal-agb': legalAgbSK, - 'legal-datenschutz': legalDatenschutzSK, - 'legal-impressum': legalImpressumSK, - 'legal-widerruf': legalWiderrufSK, - 'legal-batterie': legalBatterieSK - }, - sl: { - translation: translationSL, - 'legal-agb': legalAgbSL, - 'legal-datenschutz': legalDatenschutzSL, - 'legal-impressum': legalImpressumSL, - 'legal-widerruf': legalWiderrufSL, - 'legal-batterie': legalBatterieSL - }, - sr: { - translation: translationSR, - 'legal-agb': legalAgbSR, - 'legal-datenschutz': legalDatenschutzSR, - 'legal-impressum': legalImpressumSR, - 'legal-widerruf': legalWiderrufSR, - 'legal-batterie': legalBatterieSR - }, - sv: { - translation: translationSV, - 'legal-agb': legalAgbSV, - 'legal-datenschutz': legalDatenschutzSV, - 'legal-impressum': legalImpressumSV, - 'legal-widerruf': legalWiderrufSV, - 'legal-batterie': legalBatterieSV - }, - tr: { - translation: translationTR, - 'legal-agb': legalAgbTR, - 'legal-datenschutz': legalDatenschutzTR, - 'legal-impressum': legalImpressumTR, - 'legal-widerruf': legalWiderrufTR, - 'legal-batterie': legalBatterieTR - }, - uk: { - translation: translationUK, - 'legal-agb': legalAgbUK, - 'legal-datenschutz': legalDatenschutzUK, - 'legal-impressum': legalImpressumUK, - 'legal-widerruf': legalWiderrufUK, - 'legal-batterie': legalBatterieUK - }, - zh: { - translation: translationZH, - 'legal-agb': legalAgbZH, - 'legal-datenschutz': legalDatenschutzZH, - 'legal-impressum': legalImpressumZH, - 'legal-widerruf': legalWiderrufZH, - 'legal-batterie': legalBatterieZH } }; i18n - .use(LanguageDetector) + .use({ + type: 'languageDetector', + async: false, + detect: customDetector.lookup, + init() {}, + cacheUserLanguage: customDetector.cacheUserLanguage + }) .use(initReactI18next) .init({ resources, - fallbackLng: 'de', // German as fallback since it's your primary language + lng: 'de', // Force German as default + fallbackLng: 'de', debug: process.env.NODE_ENV === 'development', - // Language detection options + // Disable automatic language detection from browser detection: { - // Order of language detection methods - order: ['localStorage', 'navigator', 'htmlTag'], - // Cache the language selection - caches: ['localStorage'], - // Check for language in localStorage - lookupLocalStorage: 'i18nextLng' + order: ['customDetector'], + caches: ['localStorage', 'sessionStorage'] }, interpolation: { @@ -372,10 +165,57 @@ i18n // React-specific options react: { useSuspense: false // Disable suspense for class components compatibility - } + }, + + // Load missing keys as fallback + saveMissing: process.env.NODE_ENV === 'development' }); -// Export withI18n and other utilities for easy access -export { withI18n, withTranslation, withLanguage, LanguageContext, LanguageProvider } from './withTranslation.js'; +// Override changeLanguage to load languages on demand +const originalChangeLanguage = i18n.changeLanguage.bind(i18n); +i18n.changeLanguage = async (language) => { + if (language !== 'de' && !languageCache.has(language)) { + try { + await loadLanguage(language); + } catch { + console.error(`Failed to load language ${language}, falling back to German`); + language = 'de'; + } + } + + return originalChangeLanguage(language); +}; -export default i18n; \ No newline at end of file +// Check session storage on initialization and load language if needed +const initializeLanguage = async () => { + // Only run in browser environment + if (typeof window === 'undefined' || typeof sessionStorage === 'undefined') { + return; + } + + try { + const sessionLang = sessionStorage.getItem('i18nextLng'); + if (sessionLang && sessionLang !== 'de' && !languageCache.has(sessionLang)) { + console.log(`🔄 Restoring session language: ${sessionLang}`); + await loadLanguage(sessionLang); + await i18n.changeLanguage(sessionLang); + } + } catch { + console.warn('Failed to restore session language'); + } +}; + +// Initialize language on DOM ready (browser only) +if (typeof window !== 'undefined' && typeof document !== 'undefined') { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initializeLanguage); + } else { + initializeLanguage(); + } +} + +export default i18n; +export { loadLanguage }; + +// Re-export withI18n and other utilities for compatibility +export { withI18n, withTranslation, withLanguage, LanguageContext, LanguageProvider } from './withTranslation.js'; \ No newline at end of file diff --git a/src/i18n/withTranslation.js b/src/i18n/withTranslation.js index c3d096b..80bc964 100644 --- a/src/i18n/withTranslation.js +++ b/src/i18n/withTranslation.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import { withTranslation as reactI18nextWithTranslation } from 'react-i18next'; +import { loadLanguage } from './index.js'; // HOC to provide translation functions to class components export const withTranslation = (namespaces = 'translation') => (WrappedComponent) => { @@ -10,7 +11,7 @@ export const withTranslation = (namespaces = 'translation') => (WrappedComponent export const LanguageContext = React.createContext({ currentLanguage: 'de', changeLanguage: () => {}, - availableLanguages: ['ar', 'bg', 'cs', 'de', 'el', 'en', 'es', 'fr', 'hr', 'hu', 'it', 'pl', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'tr', 'uk', 'zh'] + availableLanguages: ['de'] // Start with only German }); // Provider component for language management @@ -18,14 +19,16 @@ export class LanguageProvider extends Component { constructor(props) { super(props); - // Get initial language from i18n instance - const currentLanguage = props.i18n?.language || 'de'; + // Always start with German + const currentLanguage = 'de'; this.state = { currentLanguage, - availableLanguages: ['ar', 'bg', 'cs', 'de', 'el', 'en', 'es', 'fr', 'hr', 'hu', 'it', 'pl', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'tr', 'uk', 'zh'], - demoMode: false, // Enable demo mode - currentLanguageIndex: 0 + availableLanguages: ['de'], // Start with only German visible + allLanguages: ['ar', 'bg', 'cs', 'de', 'el', 'en', 'es', 'fr', 'hr', 'hu', 'it', 'pl', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'tr', 'uk', 'zh'], + demoMode: false, + currentLanguageIndex: 0, + loadingLanguages: new Set() }; this.demoInterval = null; @@ -124,14 +127,47 @@ export class LanguageProvider extends Component { } }; - changeLanguage = (language) => { - if (this.props.i18n && this.state.availableLanguages.includes(language)) { + changeLanguage = async (language) => { + if (this.props.i18n && this.state.allLanguages.includes(language)) { // Stop demo mode if user manually changes language if (this.state.demoMode) { this.stopDemo(); } - this.props.i18n.changeLanguage(language); + // If language is not German and not already available, load it + if (language !== 'de' && !this.state.availableLanguages.includes(language)) { + // Check if already loading + if (this.state.loadingLanguages.has(language)) { + return; // Already loading + } + + // Mark as loading + this.setState(prevState => ({ + loadingLanguages: new Set([...prevState.loadingLanguages, language]) + })); + + try { + console.log(`🌍 Loading language: ${language}`); + await loadLanguage(language); + + // Add to available languages after successful load + this.setState(prevState => ({ + availableLanguages: [...prevState.availableLanguages, language], + loadingLanguages: new Set([...prevState.loadingLanguages].filter(l => l !== language)) + })); + + console.log(`✅ Language ${language} now available`); + } catch (error) { + console.error(`❌ Failed to load language ${language}:`, error); + this.setState(prevState => ({ + loadingLanguages: new Set([...prevState.loadingLanguages].filter(l => l !== language)) + })); + return; // Don't change language if loading failed + } + } + + // Change the language + await this.props.i18n.changeLanguage(language); } }; @@ -140,6 +176,8 @@ export class LanguageProvider extends Component { currentLanguage: this.state.currentLanguage, changeLanguage: this.changeLanguage, availableLanguages: this.state.availableLanguages, + allLanguages: this.state.allLanguages, + loadingLanguages: this.state.loadingLanguages, demoMode: this.state.demoMode, stopDemo: this.stopDemo };