From 8ea2e5043283f8786f274a83a43eb54a5b5b0ead Mon Sep 17 00:00:00 2001 From: sebseb7 Date: Sat, 15 Nov 2025 08:51:23 +0100 Subject: [PATCH] feat(i18n): enhance data fetching and caching with language support - Update data-fetching functions to include language and translation request parameters for improved internationalization. - Modify caching logic in Content and GrowTentKonfigurator components to utilize language-aware cache keys. - Ensure ProductDetailPage retrieves cached data based on the current language, enhancing user experience across different locales. - Integrate language handling in various components to maintain consistency in data management and rendering. --- prerender/data-fetching.cjs | 16 +++++++-- prerender/renderer.cjs | 5 ++- src/components/Content.js | 3 +- src/components/ProductDetailPage.js | 50 +++++++++++++++++++---------- src/pages/GrowTentKonfigurator.js | 49 ++++++++++++++++------------ 5 files changed, 82 insertions(+), 41 deletions(-) diff --git a/prerender/data-fetching.cjs b/prerender/data-fetching.cjs index f2df144..652460f 100644 --- a/prerender/data-fetching.cjs +++ b/prerender/data-fetching.cjs @@ -37,9 +37,15 @@ const fetchCategoryProducts = (socket, categoryId) => { reject(new Error(`Timeout fetching products for category ${categoryId}`)); }, 5000); + // Prerender system fetches German version by default socket.emit( "getCategoryProducts", - { full:true, categoryId: categoryId === "neu" ? "neu" : parseInt(categoryId) }, + { + full: true, + categoryId: categoryId === "neu" ? "neu" : parseInt(categoryId), + language: 'de', + requestTranslation: false + }, (response) => { clearTimeout(timeout); if (response && response.products !== undefined) { @@ -68,7 +74,13 @@ const fetchProductDetails = (socket, productSeoName) => { ); }, 5000); - socket.emit("getProductView", { seoName: productSeoName, nocount: true }, (response) => { + // Prerender system fetches German version by default + socket.emit("getProductView", { + seoName: productSeoName, + nocount: true, + language: 'de', + requestTranslation: false + }, (response) => { clearTimeout(timeout); if (response && response.product) { response.product.seoName = productSeoName; diff --git a/prerender/renderer.cjs b/prerender/renderer.cjs index 80d252b..9d371da 100644 --- a/prerender/renderer.cjs +++ b/prerender/renderer.cjs @@ -193,14 +193,17 @@ const renderPage = ( let productDetailCacheScript = ''; if (productData && productData.product) { // Cache the entire response object (includes product, attributes, etc.) + // Use language-aware cache key (prerender defaults to German) const productDetailCacheData = JSON.stringify(productData); + const language = 'de'; // Prerender system caches German version + const cacheKey = `product_${productData.product.seoName}_${language}`; productDetailCacheScript = ` `; } diff --git a/src/components/Content.js b/src/components/Content.js index 506740b..caa2151 100644 --- a/src/components/Content.js +++ b/src/components/Content.js @@ -176,7 +176,8 @@ function setCachedCategoryData(categoryId, data, language = 'de') { try { const cacheKey = `categoryProducts_${categoryId}_${language}`; if(data.products) for(const product of data.products) { - window.productDetailCache[product.id] = product; + const productCacheKey = `product_${product.id}_${language}`; + window.productDetailCache[productCacheKey] = product; } window.productCache[cacheKey] = { ...data, diff --git a/src/components/ProductDetailPage.js b/src/components/ProductDetailPage.js index be3362a..1837241 100644 --- a/src/components/ProductDetailPage.js +++ b/src/components/ProductDetailPage.js @@ -34,16 +34,20 @@ class ProductDetailPage extends Component { let cachedData = null; let partialProduct = null; let isUpgrading = false; - - if (window.productDetailCache && window.productDetailCache[this.props.seoName]) { - cachedData = window.productDetailCache[this.props.seoName]; + + // Get current language for cache key + const currentLanguage = this.props.i18n?.language || 'de'; + const cacheKey = `product_${this.props.seoName}_${currentLanguage}`; + + if (window.productDetailCache && window.productDetailCache[cacheKey]) { + cachedData = window.productDetailCache[cacheKey]; } else if (window.productDetailCache) { // If not found by seoName, search for partial data by checking all cached products // Look for a product where the seoName matches this.props.seoName for (const key in window.productDetailCache) { const cached = window.productDetailCache[key]; - if (cached && cached.seoName === this.props.seoName) { - partialProduct = cached; + if (cached && cached.product && cached.product.seoName === this.props.seoName) { + partialProduct = cached.product; isUpgrading = true; break; } @@ -296,9 +300,12 @@ class ProductDetailPage extends Component { window.productDetailCache = {}; } + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const cacheKey = `product_${id}_${currentLanguage}`; + // Check if this komponent is already cached - if (window.productDetailCache[id]) { - const cachedProduct = window.productDetailCache[id]; + if (window.productDetailCache[cacheKey]) { + const cachedProduct = window.productDetailCache[cacheKey]; // Load komponent image if available if (cachedProduct.pictureList) { @@ -367,9 +374,7 @@ class ProductDetailPage extends Component { })); console.log('loadKomponent', id, count); - const currentLanguage = this.props.languageContext?.currentLanguage - const currentLanguage2 = this.props.i18n?.language || 'de'; - console.log('debuglanguage', currentLanguage, currentLanguage2); + window.socketManager.emit( "getProductView", { articleId: id, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true}, @@ -377,9 +382,9 @@ class ProductDetailPage extends Component { if (res.success) { // Use translated product if available, otherwise use original product const productData = res.translatedProduct || res.product; - + // Cache the successful response - window.productDetailCache[id] = productData; + window.productDetailCache[cacheKey] = productData; // Load komponent image if available if (productData.pictureList) { @@ -556,6 +561,9 @@ class ProductDetailPage extends Component { console.log('loadProductData', this.props.seoName); const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; console.log('debuglanguage', currentLanguage); + + const cacheKey = `product_${this.props.seoName}_${currentLanguage}`; + window.socketManager.emit( "getProductView", { seoName: this.props.seoName, language: currentLanguage, requestTranslation: currentLanguage === "de" ? false : true}, @@ -564,15 +572,15 @@ class ProductDetailPage extends Component { // Use translated product if available, otherwise use original product const productData = res.translatedProduct || res.product; productData.seoName = this.props.seoName; - + // Initialize cache if it doesn't exist if (!window.productDetailCache) { window.productDetailCache = {}; } - + // Cache the complete response data (product + attributes) - cache the response with translated product const cacheData = { ...res, product: productData }; - window.productDetailCache[this.props.seoName] = cacheData; + window.productDetailCache[cacheKey] = cacheData; // Clean up prerender fallback since we now have real data if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) { @@ -1317,8 +1325,16 @@ class ProductDetailPage extends Component { > {product.description ? (() => { try { - // Sanitize HTML to remove invalid tags - return parse(sanitizeHtml(product.description)); + // Sanitize HTML to remove invalid tags, but preserve style attributes + return parse(sanitizeHtml(product.description, { + allowedTags: sanitizeHtml.defaults.allowedTags.concat(['img']), + allowedAttributes: { + '*': ['class', 'style'], + 'a': ['href', 'title'], + 'img': ['src', 'alt', 'width', 'height'] + }, + disallowedTagsMode: 'discard' + })); } catch (error) { console.warn('Failed to parse product description HTML:', error); // Fallback to rendering as plain text if HTML parsing fails diff --git a/src/pages/GrowTentKonfigurator.js b/src/pages/GrowTentKonfigurator.js index 7b34b13..65b05cb 100644 --- a/src/pages/GrowTentKonfigurator.js +++ b/src/pages/GrowTentKonfigurator.js @@ -22,7 +22,7 @@ import { Link } from 'react-router-dom'; import IconButton from '@mui/material/IconButton'; import ZoomInIcon from '@mui/icons-material/ZoomIn'; -function setCachedCategoryData(categoryId, data) { +function setCachedCategoryData(categoryId, data, language = 'de') { if (!window.productCache) { window.productCache = {}; } @@ -31,9 +31,10 @@ function setCachedCategoryData(categoryId, data) { } try { - const cacheKey = `categoryProducts_${categoryId}`; + const cacheKey = `categoryProducts_${categoryId}_${language}`; if(data.products) for(const product of data.products) { - window.productDetailCache[product.id] = product; + const productCacheKey = `product_${product.id}_${language}`; + window.productDetailCache[productCacheKey] = product; } window.productCache[cacheKey] = { ...data, @@ -43,13 +44,13 @@ function setCachedCategoryData(categoryId, data) { console.error('Error writing to cache:', err); } } -function getCachedCategoryData(categoryId) { +function getCachedCategoryData(categoryId, language = 'de') { if (!window.productCache) { window.productCache = {}; } try { - const cacheKey = `categoryProducts_${categoryId}`; + const cacheKey = `categoryProducts_${categoryId}_${language}`; const cachedData = window.productCache[cacheKey]; if (cachedData) { @@ -167,7 +168,8 @@ class GrowTentKonfigurator extends Component { } } fetchCategoryData(categoryId) { - const cachedData = getCachedCategoryData(categoryId); + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const cachedData = getCachedCategoryData(categoryId, currentLanguage); if (cachedData) { this.setState(prevState => ({ categoryLoadStatus: { @@ -183,7 +185,7 @@ class GrowTentKonfigurator extends Component { window.socketManager.off(`productList:${categoryId}`); window.socketManager.on(`productList:${categoryId}`,(response) => { - setCachedCategoryData(categoryId, response); + setCachedCategoryData(categoryId, response, currentLanguage); this.setState(prevState => ({ categoryLoadStatus: { ...prevState.categoryLoadStatus, @@ -191,8 +193,6 @@ class GrowTentKonfigurator extends Component { } })); }); - - const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; window.socketManager.emit( "getCategoryProducts", { categoryId: categoryId, language: currentLanguage, requestTranslation: currentLanguage === 'de' ? false : true }, @@ -202,10 +202,11 @@ class GrowTentKonfigurator extends Component { checkCacheValidity() { const categories = ["Zelte", "Lampen", "Abluft-sets", "Set-zubehoer"]; + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; let needsUpdate = false; const newStatus = { ...this.state.categoryLoadStatus }; for (const categoryId of categories) { - if (newStatus[categoryId] && !getCachedCategoryData(categoryId)) { + if (newStatus[categoryId] && !getCachedCategoryData(categoryId, currentLanguage)) { console.log(`Refetching ${categoryId} due to cache miss/expiry`); newStatus[categoryId] = false; needsUpdate = true; @@ -275,7 +276,8 @@ class GrowTentKonfigurator extends Component { } // Extras - const extrasData = getCachedCategoryData('Set-zubehoer'); + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage); if (extrasData && Array.isArray(extrasData.products)) { this.state.selectedExtras.forEach(extraId => { const extra = extrasData.products.find(e => e.id === extraId); @@ -398,7 +400,8 @@ class GrowTentKonfigurator extends Component { // Get lamps for selected tent shape getLampsForTentShape(shapeId) { - const cachedData = getCachedCategoryData('Lampen'); + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const cachedData = getCachedCategoryData('Lampen', currentLanguage); if (!cachedData || !cachedData.products || !cachedData.attributes) { return []; } @@ -408,7 +411,8 @@ class GrowTentKonfigurator extends Component { // Get ventilation products for selected tent shape getVentilationForTentShape(shapeId) { - const cachedData = getCachedCategoryData('Abluft-sets'); + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const cachedData = getCachedCategoryData('Abluft-sets', currentLanguage); if (!cachedData || !cachedData.products || !cachedData.attributes) { return []; } @@ -523,7 +527,8 @@ class GrowTentKonfigurator extends Component { // Get real tent products for the selected shape getTentsForShape(shapeId) { - const cachedData = getCachedCategoryData('Zelte'); + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const cachedData = getCachedCategoryData('Zelte', currentLanguage); if (!cachedData || !cachedData.products || !cachedData.attributes) { return []; } @@ -572,8 +577,9 @@ class GrowTentKonfigurator extends Component { itemCount++; } } - - const extrasData = getCachedCategoryData('Set-zubehoer'); + + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage); if (!extrasData || !Array.isArray(extrasData.products)) { console.warn('Extras data not available; skipping extras price in total calculation'); } else { @@ -650,8 +656,9 @@ class GrowTentKonfigurator extends Component { itemCount++; } } - - const extrasData = getCachedCategoryData('Set-zubehoer'); + + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage); if (!extrasData || !Array.isArray(extrasData.products)) { console.warn('Extras data not available; skipping extras in savings calculation'); } else { @@ -1156,7 +1163,8 @@ class GrowTentKonfigurator extends Component { ); } - const extrasData = getCachedCategoryData('Set-zubehoer'); + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage); if (!extrasData) { return ( @@ -1212,7 +1220,8 @@ class GrowTentKonfigurator extends Component { const selectedLight = availableLamps.find(l => l.id === selectedLightType); const availableVentilation = this.state.selectedTentShape ? this.getVentilationForTentShape(this.state.selectedTentShape) : []; const selectedVentilation = availableVentilation.find(v => v.id === selectedVentilationType && v.isDeliverable); - const extrasData = getCachedCategoryData('Set-zubehoer'); + const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; + const extrasData = getCachedCategoryData('Set-zubehoer', currentLanguage); const selectedExtrasItems = Array.isArray(extrasData?.products) ? extrasData.products.filter(e => selectedExtras.includes(e.id)) : [];