feat: Refine i18n content across multiple locales and improve LLM SEO data processing for catalog generation.

This commit is contained in:
sebseb7
2025-12-14 09:47:51 +01:00
parent 9df5642a6e
commit 57515bfb85
49 changed files with 528 additions and 479 deletions

View File

@@ -55,29 +55,29 @@ GrowHeads.de is a German online shop and local store in Dresden specializing in
const categorySlug = category.seoName.toLowerCase().replace(/[^a-z0-9]/g, '-'); const categorySlug = category.seoName.toLowerCase().replace(/[^a-z0-9]/g, '-');
const productsPerPage = 50; const productsPerPage = 50;
const totalPages = Math.ceil(productCount / productsPerPage); const totalPages = Math.ceil(productCount / productsPerPage);
llmsTxt += `#### ${category.name} (${productCount} products)`; llmsTxt += `#### ${category.name} (${productCount} products)`;
if (totalPages > 1) { if (totalPages > 1) {
llmsTxt += ` llmsTxt += `
- **Product Catalog**: ${totalPages} pages available - **Product Catalog**: ${totalPages} pages available
- **Page 1**: ${baseUrl}/llms-${categorySlug}-page-1.txt (Products 1-${Math.min(productsPerPage, productCount)})`; - **Page 1**: ${baseUrl}/llms-${categorySlug}-page-1.txt (Products 1-${Math.min(productsPerPage, productCount)})`;
if (totalPages > 2) { if (totalPages > 2) {
llmsTxt += ` llmsTxt += `
- **Page 2**: ${baseUrl}/llms-${categorySlug}-page-2.txt (Products ${productsPerPage + 1}-${Math.min(productsPerPage * 2, productCount)})`; - **Page 2**: ${baseUrl}/llms-${categorySlug}-page-2.txt (Products ${productsPerPage + 1}-${Math.min(productsPerPage * 2, productCount)})`;
} }
if (totalPages > 3) { if (totalPages > 3) {
llmsTxt += ` llmsTxt += `
- **...**: Additional pages available`; - **...**: Additional pages available`;
} }
if (totalPages > 2) { if (totalPages > 2) {
llmsTxt += ` llmsTxt += `
- **Page ${totalPages}**: ${baseUrl}/llms-${categorySlug}-page-${totalPages}.txt (Products ${((totalPages - 1) * productsPerPage) + 1}-${productCount})`; - **Page ${totalPages}**: ${baseUrl}/llms-${categorySlug}-page-${totalPages}.txt (Products ${((totalPages - 1) * productsPerPage) + 1}-${productCount})`;
} }
llmsTxt += ` llmsTxt += `
- **Access Pattern**: Replace "page-X" with desired page number (1-${totalPages})`; - **Access Pattern**: Replace "page-X" with desired page number (1-${totalPages})`;
} else if (productCount > 0) { } else if (productCount > 0) {
@@ -87,7 +87,7 @@ GrowHeads.de is a German online shop and local store in Dresden specializing in
llmsTxt += ` llmsTxt += `
- **Product Catalog**: No products available`; - **Product Catalog**: No products available`;
} }
llmsTxt += ` llmsTxt += `
`; `;
@@ -106,7 +106,7 @@ GrowHeads.de is a German online shop and local store in Dresden specializing in
const generateCategoryLlmsTxt = (category, categoryProducts = [], baseUrl, config, pageNumber = 1, productsPerPage = 50) => { const generateCategoryLlmsTxt = (category, categoryProducts = [], baseUrl, config, pageNumber = 1, productsPerPage = 50) => {
const currentDate = new Date().toISOString().split("T")[0]; // YYYY-MM-DD format const currentDate = new Date().toISOString().split("T")[0]; // YYYY-MM-DD format
const categorySlug = category.seoName.toLowerCase().replace(/[^a-z0-9]/g, '-'); const categorySlug = category.seoName.toLowerCase().replace(/[^a-z0-9]/g, '-');
// Calculate pagination // Calculate pagination
const totalProducts = categoryProducts.length; const totalProducts = categoryProducts.length;
const totalPages = Math.ceil(totalProducts / productsPerPage); const totalPages = Math.ceil(totalProducts / productsPerPage);
@@ -140,28 +140,28 @@ This file contains products ${startIndex + 1}-${endIndex} of ${totalProducts} in
**How to access other pages in this category:** **How to access other pages in this category:**
`; `;
if (pageNumber > 1) { if (pageNumber > 1) {
categoryLlmsTxt += `- **Previous Page**: ${baseUrl}/llms-${categorySlug}-page-${pageNumber - 1}.txt categoryLlmsTxt += `- **Previous Page**: ${baseUrl}/llms-${categorySlug}-page-${pageNumber - 1}.txt
`; `;
} }
if (pageNumber < totalPages) { if (pageNumber < totalPages) {
categoryLlmsTxt += `- **Next Page**: ${baseUrl}/llms-${categorySlug}-page-${pageNumber + 1}.txt categoryLlmsTxt += `- **Next Page**: ${baseUrl}/llms-${categorySlug}-page-${pageNumber + 1}.txt
`; `;
} }
categoryLlmsTxt += `- **First Page**: ${baseUrl}/llms-${categorySlug}-page-1.txt categoryLlmsTxt += `- **First Page**: ${baseUrl}/llms-${categorySlug}-page-1.txt
- **Last Page**: ${baseUrl}/llms-${categorySlug}-page-${totalPages}.txt - **Last Page**: ${baseUrl}/llms-${categorySlug}-page-${totalPages}.txt
**All pages in this category:** **All pages in this category:**
`; `;
for (let i = 1; i <= totalPages; i++) { for (let i = 1; i <= totalPages; i++) {
categoryLlmsTxt += `- **Page ${i}**: ${baseUrl}/llms-${categorySlug}-page-${i}.txt (Products ${((i-1) * productsPerPage) + 1}-${Math.min(i * productsPerPage, totalProducts)}) categoryLlmsTxt += `- **Page ${i}**: ${baseUrl}/llms-${categorySlug}-page-${i}.txt (Products ${((i - 1) * productsPerPage) + 1}-${Math.min(i * productsPerPage, totalProducts)})
`; `;
} }
categoryLlmsTxt += ` categoryLlmsTxt += `
`; `;
@@ -173,10 +173,10 @@ This file contains products ${startIndex + 1}-${endIndex} of ${totalProducts} in
// Clean description for markdown (remove HTML tags and limit length) // Clean description for markdown (remove HTML tags and limit length)
const cleanDescription = product.description const cleanDescription = product.description
? product.description ? product.description
.replace(/<[^>]*>/g, "") .replace(/<[^>]*>/g, "")
.replace(/\n/g, " ") .replace(/\n/g, " ")
.trim() .trim()
.substring(0, 300) .substring(0, 300)
: ""; : "";
const globalIndex = startIndex + index + 1; const globalIndex = startIndex + index + 1;
@@ -234,13 +234,13 @@ This category currently contains no products.
if (pageNumber > 1) { if (pageNumber > 1) {
categoryLlmsTxt += `← [Previous Page](${baseUrl}/llms-${categorySlug}-page-${pageNumber - 1}.txt) | `; categoryLlmsTxt += `← [Previous Page](${baseUrl}/llms-${categorySlug}-page-${pageNumber - 1}.txt) | `;
} }
categoryLlmsTxt += `[Category Overview](${baseUrl}/llms-${categorySlug}-page-1.txt)`; categoryLlmsTxt += `[Category Overview](${baseUrl}/llms-${categorySlug}-page-1.txt)`;
if (pageNumber < totalPages) { if (pageNumber < totalPages) {
categoryLlmsTxt += ` | [Next Page](${baseUrl}/llms-${categorySlug}-page-${pageNumber + 1}.txt) →`; categoryLlmsTxt += ` | [Next Page](${baseUrl}/llms-${categorySlug}-page-${pageNumber + 1}.txt) →`;
} }
categoryLlmsTxt += ` categoryLlmsTxt += `
`; `;
@@ -260,7 +260,7 @@ const generateCategoryProductList = (category, categoryProducts = []) => {
const fileName = `llms-${categorySlug}-list.txt`; const fileName = `llms-${categorySlug}-list.txt`;
const subcategoryIds = (category.subcategories || []).join(','); const subcategoryIds = (category.subcategories || []).join(',');
let content = `${String(category.name)},${String(category.id)},[${subcategoryIds}]\n`; let content = '';//`${String(category.name)},${String(category.id)},[${subcategoryIds}]\n`;
categoryProducts.forEach((product) => { categoryProducts.forEach((product) => {
const artnr = String(product.articleNumber || ''); const artnr = String(product.articleNumber || '');

View File

@@ -1,9 +1,10 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
// Read the input file // Read the input file from public
const inputFile = path.join(__dirname, 'dist', 'llms-cat.txt'); const inputFile = path.join(__dirname, 'public', 'llms-cat.txt');
const outputFile = path.join(__dirname, 'output.csv'); // Write the output file to dist
const outputFile = path.join(__dirname, 'dist', 'llms-cat.txt');
// Function to parse a CSV line with escaped quotes // Function to parse a CSV line with escaped quotes
function parseCSVLine(line) { function parseCSVLine(line) {
@@ -38,44 +39,65 @@ function parseCSVLine(line) {
} }
try { try {
if (!fs.existsSync(inputFile)) {
throw new Error(`Input file not found: ${inputFile}`);
}
const data = fs.readFileSync(inputFile, 'utf8'); const data = fs.readFileSync(inputFile, 'utf8');
const lines = data.trim().split('\n'); const lines = data.trim().split('\n');
const outputLines = ['URL,SEO Description']; // Keep the header as intended: URL and Description
const outputLines = ['URL of product list for article numbers,SEO Description'];
for (const line of lines) { let skippedLines = 0;
let processedLines = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.trim() === '') continue; if (line.trim() === '') continue;
// Skip comment lines or lines not starting with a number/quote (simple heuristic for header/comments)
// The file starts with text "this file has..." and then header "categoryId..."
// Actual data lines start with "
if (!line.trim().startsWith('"')) {
continue;
}
// Parse the CSV line properly handling escaped quotes // Parse the CSV line properly handling escaped quotes
const fields = parseCSVLine(line); const fields = parseCSVLine(line);
if (fields.length !== 3) { if (fields.length !== 3) {
console.warn(`Skipping malformed line (got ${fields.length} fields): ${line.substring(0, 100)}...`); console.warn(`Skipping malformed line ${i + 1} (got ${fields.length} fields): ${line.substring(0, 50)}...`);
skippedLines++;
continue; continue;
} }
const [field1, field2, field3] = fields; // Input: categoryId, listFileName, seoDescription
const url = field2; // Output: URL, SEO Description
const [categoryId, listFileName, seoDescription] = fields;
// field3 is a JSON string - parse it directly // Use listFileName as URL
let seoDescription = ''; const url = listFileName;
try {
const parsed = JSON.parse(field3);
seoDescription = parsed.seo_description || '';
} catch (e) {
console.warn(`Failed to parse JSON for URL ${url}: ${e.message}`);
console.warn(`JSON string: ${field3.substring(0, 200)}...`);
}
// Escape quotes for CSV output - URL doesn't need quotes, description does // Use seoDescription as description directly (it's already a string)
const escapedDescription = '"' + seoDescription.replace(/"/g, '""') + '"'; const description = seoDescription;
// Escape quotes for CSV output
const escapedDescription = '"' + description.replace(/"/g, '""') + '"';
outputLines.push(`${url},${escapedDescription}`); outputLines.push(`${url},${escapedDescription}`);
processedLines++;
}
// Ensure dist directory exists
const distDir = path.dirname(outputFile);
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
} }
// Write the output CSV // Write the output CSV
fs.writeFileSync(outputFile, outputLines.join('\n'), 'utf8'); fs.writeFileSync(outputFile, outputLines.join('\n'), 'utf8');
console.log(`Processed ${lines.length} lines and created ${outputFile}`); console.log(`Processed ${processedLines} lines (skipped ${skippedLines}) and created ${outputFile}`);
} catch (error) { } catch (error) {
console.error('Error processing file:', error.message); console.error('Error processing file:', error.message);

View File

@@ -1,3 +1,5 @@
this file has the list of category overview lists, where you can find article numbers
categoryId,listFileName,seoDescription categoryId,listFileName,seoDescription
"703","https://growheads.de/llms-abluft-sets-list.txt","Abluft-Sets für Growbox & Indoor-Grow: leise, energiesparend & mit Aktivkohlefilter zur Geruchsneutralisation. Ideal für Zelte von 60 cm bis 1 m²." "703","https://growheads.de/llms-abluft-sets-list.txt","Abluft-Sets für Growbox & Indoor-Grow: leise, energiesparend & mit Aktivkohlefilter zur Geruchsneutralisation. Ideal für Zelte von 60 cm bis 1 m²."
"317","https://growheads.de/llms-air-pot-list.txt","Air-Pot Pflanztöpfe für maximales Wurzelwachstum: Air-Pruning, optimale Belüftung & Drainage. Ideal für Indoor, Outdoor, Hydroponik & Anzucht." "317","https://growheads.de/llms-air-pot-list.txt","Air-Pot Pflanztöpfe für maximales Wurzelwachstum: Air-Pruning, optimale Belüftung & Drainage. Ideal für Indoor, Outdoor, Hydroponik & Anzucht."

View File

@@ -36,11 +36,11 @@ function getCachedCategoryData(categoryId, language = 'de') {
try { try {
const cacheKey = `categoryProducts_${categoryId}_${language}`; const cacheKey = `categoryProducts_${categoryId}_${language}`;
const cachedData = window.productCache[cacheKey]; const cachedData = window.productCache[cacheKey];
if (cachedData) { if (cachedData) {
const { timestamp } = cachedData; const { timestamp } = cachedData;
const cacheAge = Date.now() - timestamp; const cacheAge = Date.now() - timestamp;
const tenMinutes = 10 * 60 * 1000; const tenMinutes = 10 * 60 * 1000;
if (cacheAge < tenMinutes) { if (cacheAge < tenMinutes) {
return cachedData; return cachedData;
} }
@@ -58,21 +58,21 @@ function getFilteredProducts(unfilteredProducts, attributes, t) {
const attributeSettings = getAllSettingsWithPrefix('filter_attribute_'); const attributeSettings = getAllSettingsWithPrefix('filter_attribute_');
const manufacturerSettings = getAllSettingsWithPrefix('filter_manufacturer_'); const manufacturerSettings = getAllSettingsWithPrefix('filter_manufacturer_');
const availabilitySettings = getAllSettingsWithPrefix('filter_availability_'); const availabilitySettings = getAllSettingsWithPrefix('filter_availability_');
const attributeFilters = []; const attributeFilters = [];
Object.keys(attributeSettings).forEach(key => { Object.keys(attributeSettings).forEach(key => {
if (attributeSettings[key] === 'true') { if (attributeSettings[key] === 'true') {
attributeFilters.push(key.split('_')[2]); attributeFilters.push(key.split('_')[2]);
} }
}); });
const manufacturerFilters = []; const manufacturerFilters = [];
Object.keys(manufacturerSettings).forEach(key => { Object.keys(manufacturerSettings).forEach(key => {
if (manufacturerSettings[key] === 'true') { if (manufacturerSettings[key] === 'true') {
manufacturerFilters.push(key.split('_')[2]); manufacturerFilters.push(key.split('_')[2]);
} }
}); });
const availabilityFilters = []; const availabilityFilters = [];
Object.keys(availabilitySettings).forEach(key => { Object.keys(availabilitySettings).forEach(key => {
if (availabilitySettings[key] === 'true') { if (availabilitySettings[key] === 'true') {
@@ -82,9 +82,9 @@ function getFilteredProducts(unfilteredProducts, attributes, t) {
const uniqueAttributes = [...new Set((attributes || []).map(attr => attr.kMerkmalWert ? attr.kMerkmalWert.toString() : ''))]; const uniqueAttributes = [...new Set((attributes || []).map(attr => attr.kMerkmalWert ? attr.kMerkmalWert.toString() : ''))];
const uniqueManufacturers = [...new Set((unfilteredProducts || []).filter(product => product.manufacturerId).map(product => product.manufacturerId ? product.manufacturerId.toString() : ''))]; const uniqueManufacturers = [...new Set((unfilteredProducts || []).filter(product => product.manufacturerId).map(product => product.manufacturerId ? product.manufacturerId.toString() : ''))];
const uniqueManufacturersWithName = [...new Set((unfilteredProducts || []).filter(product => product.manufacturerId).map(product => ({id:product.manufacturerId ? product.manufacturerId.toString() : '',value:product.manufacturer})))]; const uniqueManufacturersWithName = [...new Set((unfilteredProducts || []).filter(product => product.manufacturerId).map(product => ({ id: product.manufacturerId ? product.manufacturerId.toString() : '', value: product.manufacturer })))];
const activeAttributeFilters = attributeFilters.filter(filter => uniqueAttributes.includes(filter)); const activeAttributeFilters = attributeFilters.filter(filter => uniqueAttributes.includes(filter));
const activeManufacturerFilters = manufacturerFilters.filter(filter => uniqueManufacturers.includes(filter)); const activeManufacturerFilters = manufacturerFilters.filter(filter => uniqueManufacturers.includes(filter));
const attributeFiltersByGroup = {}; const attributeFiltersByGroup = {};
for (const filterId of activeAttributeFilters) { for (const filterId of activeAttributeFilters) {
const attribute = attributes.find(attr => attr.kMerkmalWert.toString() === filterId); const attribute = attributes.find(attr => attr.kMerkmalWert.toString() === filterId);
@@ -95,26 +95,26 @@ function getFilteredProducts(unfilteredProducts, attributes, t) {
attributeFiltersByGroup[attribute.cName].push(filterId); attributeFiltersByGroup[attribute.cName].push(filterId);
} }
} }
let filteredProducts = (unfilteredProducts || []).filter(product => { let filteredProducts = (unfilteredProducts || []).filter(product => {
const availabilityFilter = sessionStorage.getItem('filter_availability'); const availabilityFilter = sessionStorage.getItem('filter_availability');
let inStockMatch = availabilityFilter == 1 ? true : (product.available>0); let inStockMatch = availabilityFilter == 1 ? true : (product.available > 0);
// Check if there are any new products in the entire set // Check if there are any new products in the entire set
const hasNewProducts = (unfilteredProducts || []).some(product => isNew(product.neu)); const hasNewProducts = (unfilteredProducts || []).some(product => isNew(product.neu));
// Only apply the new filter if there are actually new products and the filter is active // Only apply the new filter if there are actually new products and the filter is active
const isNewMatch = availabilityFilters.includes('2') && hasNewProducts ? isNew(product.neu) : true; const isNewMatch = availabilityFilters.includes('2') && hasNewProducts ? isNew(product.neu) : true;
let soonMatch = availabilityFilters.includes('3') ? !product.available && product.incoming : true; let soonMatch = availabilityFilters.includes('3') ? !product.available && product.incoming : true;
const soon2Match = (availabilityFilter != 1)&&availabilityFilters.includes('3') ? (product.available) || (!product.available && product.incoming) : true; const soon2Match = (availabilityFilter != 1) && availabilityFilters.includes('3') ? (product.available) || (!product.available && product.incoming) : true;
if( (availabilityFilter != 1)&&availabilityFilters.includes('3') && ((product.available) || (!product.available && product.incoming))){ if ((availabilityFilter != 1) && availabilityFilters.includes('3') && ((product.available) || (!product.available && product.incoming))) {
inStockMatch = true; inStockMatch = true;
soonMatch = true; soonMatch = true;
console.log("soon2Match", product.cName); console.log("soon2Match", product.cName);
} }
const manufacturerMatch = activeManufacturerFilters.length === 0 || const manufacturerMatch = activeManufacturerFilters.length === 0 ||
(product.manufacturerId && activeManufacturerFilters.includes(product.manufacturerId.toString())); (product.manufacturerId && activeManufacturerFilters.includes(product.manufacturerId.toString()));
if (Object.keys(attributeFiltersByGroup).length === 0) { if (Object.keys(attributeFiltersByGroup).length === 0) {
@@ -130,41 +130,41 @@ function getFilteredProducts(unfilteredProducts, attributes, t) {
}); });
return manufacturerMatch && attributeMatch && soon2Match && inStockMatch && soonMatch && isNewMatch; return manufacturerMatch && attributeMatch && soon2Match && inStockMatch && soonMatch && isNewMatch;
}); });
const activeAttributeFiltersWithNames = activeAttributeFilters.map(filter => { const activeAttributeFiltersWithNames = activeAttributeFilters.map(filter => {
const attribute = attributes.find(attr => attr.kMerkmalWert.toString() === filter); const attribute = attributes.find(attr => attr.kMerkmalWert.toString() === filter);
return {name: attribute.cName, value: attribute.cWert, id: attribute.kMerkmalWert}; return { name: attribute.cName, value: attribute.cWert, id: attribute.kMerkmalWert };
}); });
const activeManufacturerFiltersWithNames = activeManufacturerFilters.map(filter => { const activeManufacturerFiltersWithNames = activeManufacturerFilters.map(filter => {
const manufacturer = uniqueManufacturersWithName.find(manufacturer => manufacturer.id === filter); const manufacturer = uniqueManufacturersWithName.find(manufacturer => manufacturer.id === filter);
return {name: manufacturer.value, value: manufacturer.id}; return { name: manufacturer.value, value: manufacturer.id };
}); });
// Extract active availability filters // Extract active availability filters
const availabilityFilter = sessionStorage.getItem('filter_availability'); const availabilityFilter = sessionStorage.getItem('filter_availability');
const activeAvailabilityFilters = []; const activeAvailabilityFilters = [];
// Check if there are actually products with these characteristics // Check if there are actually products with these characteristics
const hasNewProducts = (unfilteredProducts || []).some(product => isNew(product.neu)); const hasNewProducts = (unfilteredProducts || []).some(product => isNew(product.neu));
const hasComingSoonProducts = (unfilteredProducts || []).some(product => !product.available && product.incoming); const hasComingSoonProducts = (unfilteredProducts || []).some(product => !product.available && product.incoming);
// Check for "auf Lager" filter (in stock) - it's active when filter_availability is NOT set to '1' // Check for "auf Lager" filter (in stock) - it's active when filter_availability is NOT set to '1'
if (availabilityFilter !== '1') { if (availabilityFilter !== '1') {
activeAvailabilityFilters.push({id: '1', name: t ? t('product.inStock') : 'auf Lager'}); activeAvailabilityFilters.push({ id: '1', name: t ? t('product.inStock') : 'auf Lager' });
}
// Check for "Neu" filter (new) - only show if there are actually new products and filter is active
if (availabilityFilters.includes('2') && hasNewProducts) {
activeAvailabilityFilters.push({id: '2', name: t ? t('product.new') : 'Neu'});
}
// Check for "Bald verfügbar" filter (coming soon) - only show if there are actually coming soon products and filter is active
if (availabilityFilters.includes('3') && hasComingSoonProducts) {
activeAvailabilityFilters.push({id: '3', name: t ? t('product.comingSoon') : 'Bald verfügbar'});
} }
return {filteredProducts,activeAttributeFilters:activeAttributeFiltersWithNames,activeManufacturerFilters:activeManufacturerFiltersWithNames,activeAvailabilityFilters}; // Check for "Neu" filter (new) - only show if there are actually new products and filter is active
if (availabilityFilters.includes('2') && hasNewProducts) {
activeAvailabilityFilters.push({ id: '2', name: t ? t('product.new') : 'Neu' });
}
// Check for "Bald verfügbar" filter (coming soon) - only show if there are actually coming soon products and filter is active
if (availabilityFilters.includes('3') && hasComingSoonProducts) {
activeAvailabilityFilters.push({ id: '3', name: t ? t('product.comingSoon') : 'Bald verfügbar' });
}
return { filteredProducts, activeAttributeFilters: activeAttributeFiltersWithNames, activeManufacturerFilters: activeManufacturerFiltersWithNames, activeAvailabilityFilters };
} }
function setCachedCategoryData(categoryId, data, language = 'de') { function setCachedCategoryData(categoryId, data, language = 'de') {
if (!window.productCache) { if (!window.productCache) {
@@ -176,7 +176,7 @@ function setCachedCategoryData(categoryId, data, language = 'de') {
try { try {
const cacheKey = `categoryProducts_${categoryId}_${language}`; const cacheKey = `categoryProducts_${categoryId}_${language}`;
if(data.products) for(const product of data.products) { if (data.products) for (const product of data.products) {
const productCacheKey = `product_${product.id}_${language}`; const productCacheKey = `product_${product.id}_${language}`;
window.productDetailCache[productCacheKey] = product; window.productDetailCache[productCacheKey] = product;
} }
@@ -195,7 +195,7 @@ class Content extends Component {
this.state = { this.state = {
loaded: false, loaded: false,
categoryName: null, categoryName: null,
unfilteredProducts: [], unfilteredProducts: [],
filteredProducts: [], filteredProducts: [],
attributes: [], attributes: [],
@@ -206,11 +206,13 @@ class Content extends Component {
componentDidMount() { componentDidMount() {
const currentLanguage = this.props.i18n?.language || 'de'; const currentLanguage = this.props.i18n?.language || 'de';
if(this.props.params.categoryId) {this.setState({loaded: false, unfilteredProducts: [], filteredProducts: [], attributes: [], categoryName: null, childCategories: [], lastFetchedLanguage: currentLanguage}, () => { if (this.props.params.categoryId) {
this.setState({ loaded: false, unfilteredProducts: [], filteredProducts: [], attributes: [], categoryName: null, childCategories: [], lastFetchedLanguage: currentLanguage }, () => {
this.fetchCategoryData(this.props.params.categoryId); this.fetchCategoryData(this.props.params.categoryId);
})} })
}
else if (this.props.searchParams?.get('q')) { else if (this.props.searchParams?.get('q')) {
this.setState({loaded: false, unfilteredProducts: [], filteredProducts: [], attributes: [], categoryName: null, childCategories: [], lastFetchedLanguage: currentLanguage}, () => { this.setState({ loaded: false, unfilteredProducts: [], filteredProducts: [], attributes: [], categoryName: null, childCategories: [], lastFetchedLanguage: currentLanguage }, () => {
this.fetchSearchData(this.props.searchParams?.get('q')); this.fetchSearchData(this.props.searchParams?.get('q'));
}) })
} }
@@ -220,29 +222,29 @@ class Content extends Component {
const currentLanguage = this.props.i18n?.language || 'de'; const currentLanguage = this.props.i18n?.language || 'de';
const categoryChanged = this.props.params.categoryId && (prevProps.params.categoryId !== this.props.params.categoryId); const categoryChanged = this.props.params.categoryId && (prevProps.params.categoryId !== this.props.params.categoryId);
const searchChanged = this.props.searchParams?.get('q') && (prevProps.searchParams?.get('q') !== this.props.searchParams?.get('q')); const searchChanged = this.props.searchParams?.get('q') && (prevProps.searchParams?.get('q') !== this.props.searchParams?.get('q'));
if(categoryChanged) {
// Clear context for new category loading
if (this.props.categoryContext && this.props.categoryContext.setCurrentCategory) {
this.props.categoryContext.setCurrentCategory(null);
}
window.currentSearchQuery = null; if (categoryChanged) {
this.setState({loaded: false, unfilteredProducts: [], filteredProducts: [], attributes: [], categoryName: null, childCategories: [], lastFetchedLanguage: currentLanguage}, () => { // Clear context for new category loading
this.fetchCategoryData(this.props.params.categoryId); if (this.props.categoryContext && this.props.categoryContext.setCurrentCategory) {
}); this.props.categoryContext.setCurrentCategory(null);
return; // Don't check language change if category changed }
}
window.currentSearchQuery = null;
this.setState({ loaded: false, unfilteredProducts: [], filteredProducts: [], attributes: [], categoryName: null, childCategories: [], lastFetchedLanguage: currentLanguage }, () => {
this.fetchCategoryData(this.props.params.categoryId);
});
return; // Don't check language change if category changed
}
else if (searchChanged) { else if (searchChanged) {
this.setState({loaded: false, unfilteredProducts: [], filteredProducts: [], attributes: [], categoryName: null, childCategories: [], lastFetchedLanguage: currentLanguage}, () => { this.setState({ loaded: false, unfilteredProducts: [], filteredProducts: [], attributes: [], categoryName: null, childCategories: [], lastFetchedLanguage: currentLanguage }, () => {
this.fetchSearchData(this.props.searchParams?.get('q')); this.fetchSearchData(this.props.searchParams?.get('q'));
}); });
return; // Don't check language change if search changed return; // Don't check language change if search changed
} }
// Re-fetch products when language changes to get translated content // Re-fetch products when language changes to get translated content
const languageChanged = currentLanguage !== this.state.lastFetchedLanguage; const languageChanged = currentLanguage !== this.state.lastFetchedLanguage;
console.log('Content componentDidUpdate:', { console.log('Content componentDidUpdate:', {
languageChanged, languageChanged,
lastFetchedLang: this.state.lastFetchedLanguage, lastFetchedLang: this.state.lastFetchedLanguage,
@@ -252,27 +254,27 @@ class Content extends Component {
categoryId: this.props.params.categoryId, categoryId: this.props.params.categoryId,
hasSearchQuery: !!this.props.searchParams?.get('q') hasSearchQuery: !!this.props.searchParams?.get('q')
}); });
if(languageChanged) { if (languageChanged) {
console.log('Content: Language changed! Re-fetching data...'); console.log('Content: Language changed! Re-fetching data...');
// Re-fetch current data with new language // Re-fetch current data with new language
// Note: Language is now part of the cache key, so it will automatically fetch fresh data // Note: Language is now part of the cache key, so it will automatically fetch fresh data
if(this.props.params.categoryId) { if (this.props.params.categoryId) {
// Re-fetch category data with new language // Re-fetch category data with new language
console.log('Content: Re-fetching category', this.props.params.categoryId); console.log('Content: Re-fetching category', this.props.params.categoryId);
this.setState({loaded: false, lastFetchedLanguage: currentLanguage}, () => { this.setState({ loaded: false, lastFetchedLanguage: currentLanguage }, () => {
this.fetchCategoryData(this.props.params.categoryId); this.fetchCategoryData(this.props.params.categoryId);
}); });
} else if(this.props.searchParams?.get('q')) { } else if (this.props.searchParams?.get('q')) {
// Re-fetch search data with new language // Re-fetch search data with new language
console.log('Content: Re-fetching search', this.props.searchParams?.get('q')); console.log('Content: Re-fetching search', this.props.searchParams?.get('q'));
this.setState({loaded: false, lastFetchedLanguage: currentLanguage}, () => { this.setState({ loaded: false, lastFetchedLanguage: currentLanguage }, () => {
this.fetchSearchData(this.props.searchParams?.get('q')); this.fetchSearchData(this.props.searchParams?.get('q'));
}); });
} else { } else {
// If not viewing category or search, just re-filter existing products // If not viewing category or search, just re-filter existing products
console.log('Content: Just re-filtering existing products'); console.log('Content: Just re-filtering existing products');
this.setState({lastFetchedLanguage: currentLanguage}); this.setState({ lastFetchedLanguage: currentLanguage });
this.filterProducts(); this.filterProducts();
} }
} }
@@ -281,7 +283,7 @@ class Content extends Component {
processData(response) { processData(response) {
const rawProducts = response.products; const rawProducts = response.products;
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
if (!window.individualProductCache) { if (!window.individualProductCache) {
window.individualProductCache = {}; window.individualProductCache = {};
} }
@@ -289,10 +291,10 @@ class Content extends Component {
const unfilteredProducts = []; const unfilteredProducts = [];
//console.log("processData", rawProducts); //console.log("processData", rawProducts);
if(rawProducts) rawProducts.forEach(product => { if (rawProducts) rawProducts.forEach(product => {
const effectiveProduct = product.translatedProduct || product; const effectiveProduct = product.translatedProduct || product;
const cacheKey = `${effectiveProduct.id}_${currentLanguage}`; const cacheKey = `${effectiveProduct.id}_${currentLanguage}`;
window.individualProductCache[cacheKey] = { window.individualProductCache[cacheKey] = {
data: effectiveProduct, data: effectiveProduct,
timestamp: Date.now() timestamp: Date.now()
@@ -319,16 +321,16 @@ class Content extends Component {
categoryName: response.categoryName, categoryName: response.categoryName,
name: response.name name: response.name
}); });
if (this.props.categoryContext && this.props.categoryContext.setCurrentCategory) { if (this.props.categoryContext && this.props.categoryContext.setCurrentCategory) {
if (response.categoryName || response.name) { if (response.categoryName || response.name) {
console.log('Content: Setting category context'); console.log('Content: Setting category context');
this.props.categoryContext.setCurrentCategory({ this.props.categoryContext.setCurrentCategory({
id: this.props.params.categoryId, id: this.props.params.categoryId,
name: response.categoryName || response.name name: response.categoryName || response.name
}); });
} else { } else {
console.log('Content: No category name found to set in context'); console.log('Content: No category name found to set in context');
} }
} else { } else {
console.warn('Content: categoryContext prop is missing!'); console.warn('Content: categoryContext prop is missing!');
@@ -352,7 +354,7 @@ class Content extends Component {
// Track if we've received the full response to ignore stub response if needed // Track if we've received the full response to ignore stub response if needed
let receivedFullResponse = false; let receivedFullResponse = false;
window.socketManager.on(`productList:${categoryId}`,(response) => { window.socketManager.on(`productList:${categoryId}`, (response) => {
console.log("getCategoryProducts full response", response); console.log("getCategoryProducts full response", response);
receivedFullResponse = true; receivedFullResponse = true;
setCachedCategoryData(categoryId, response, currentLanguage); setCachedCategoryData(categoryId, response, currentLanguage);
@@ -382,7 +384,7 @@ class Content extends Component {
} }
); );
} }
processDataWithCategoryTree(response, categoryId) { processDataWithCategoryTree(response, categoryId) {
console.log("---------------processDataWithCategoryTree", response, categoryId); console.log("---------------processDataWithCategoryTree", response, categoryId);
// Get child categories from the cached category tree // Get child categories from the cached category tree
@@ -392,10 +394,10 @@ class Content extends Component {
const categoryTreeCache = window.categoryService.getSync(209, currentLanguage); const categoryTreeCache = window.categoryService.getSync(209, currentLanguage);
if (categoryTreeCache) { if (categoryTreeCache) {
// If categoryId is a string (SEO name), find by seoName, otherwise by ID // If categoryId is a string (SEO name), find by seoName, otherwise by ID
const targetCategory = typeof categoryId === 'string' const targetCategory = typeof categoryId === 'string'
? this.findCategoryBySeoName(categoryTreeCache, categoryId) ? this.findCategoryBySeoName(categoryTreeCache, categoryId)
: this.findCategoryById(categoryTreeCache, categoryId); : this.findCategoryById(categoryTreeCache, categoryId);
if (targetCategory && targetCategory.children) { if (targetCategory && targetCategory.children) {
childCategories = targetCategory.children; childCategories = targetCategory.children;
} }
@@ -403,7 +405,7 @@ class Content extends Component {
} catch (err) { } catch (err) {
console.error('Error getting child categories from tree:', err); console.error('Error getting child categories from tree:', err);
} }
// Add child categories to the response // Add child categories to the response
const enhancedResponse = { const enhancedResponse = {
...response, ...response,
@@ -412,42 +414,42 @@ class Content extends Component {
// Attempt to set category name from the tree if missing in response // Attempt to set category name from the tree if missing in response
if (!enhancedResponse.categoryName && !enhancedResponse.name) { if (!enhancedResponse.categoryName && !enhancedResponse.name) {
// Try to find name in the tree using the ID or SEO name // Try to find name in the tree using the ID or SEO name
try { try {
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
const categoryTreeCache = window.categoryService.getSync(209, currentLanguage); const categoryTreeCache = window.categoryService.getSync(209, currentLanguage);
if (categoryTreeCache) { if (categoryTreeCache) {
const targetCategory = typeof categoryId === 'string' const targetCategory = typeof categoryId === 'string'
? this.findCategoryBySeoName(categoryTreeCache, categoryId) ? this.findCategoryBySeoName(categoryTreeCache, categoryId)
: this.findCategoryById(categoryTreeCache, categoryId); : this.findCategoryById(categoryTreeCache, categoryId);
if (targetCategory && targetCategory.name) { if (targetCategory && targetCategory.name) {
enhancedResponse.categoryName = targetCategory.name; enhancedResponse.categoryName = targetCategory.name;
} }
} }
} catch (err) { } catch (err) {
console.error('Error finding category name in tree:', err); console.error('Error finding category name in tree:', err);
} }
} }
this.processData(enhancedResponse); this.processData(enhancedResponse);
} }
findCategoryById(category, targetId) { findCategoryById(category, targetId) {
if (!category) return null; if (!category) return null;
if (category.id === targetId) { if (category.id === targetId) {
return category; return category;
} }
if (category.children) { if (category.children) {
for (let child of category.children) { for (let child of category.children) {
const found = this.findCategoryById(child, targetId); const found = this.findCategoryById(child, targetId);
if (found) return found; if (found) return found;
} }
} }
return null; return null;
} }
@@ -472,7 +474,7 @@ class Content extends Component {
} }
filterProducts() { filterProducts() {
this.setState({ this.setState({
...getFilteredProducts( ...getFilteredProducts(
this.state.unfilteredProducts, this.state.unfilteredProducts,
this.state.attributes, this.state.attributes,
@@ -484,25 +486,25 @@ class Content extends Component {
// Helper function to find category by seoName // Helper function to find category by seoName
findCategoryBySeoName = (categoryNode, seoName) => { findCategoryBySeoName = (categoryNode, seoName) => {
if (!categoryNode) return null; if (!categoryNode) return null;
if (categoryNode.seoName === seoName) { if (categoryNode.seoName === seoName) {
return categoryNode; return categoryNode;
} }
if (categoryNode.children) { if (categoryNode.children) {
for (const child of categoryNode.children) { for (const child of categoryNode.children) {
const found = this.findCategoryBySeoName(child, seoName); const found = this.findCategoryBySeoName(child, seoName);
if (found) return found; if (found) return found;
} }
} }
return null; return null;
} }
// Helper function to get current category ID from seoName // Helper function to get current category ID from seoName
getCurrentCategoryId = () => { getCurrentCategoryId = () => {
const seoName = this.props.params.categoryId; const seoName = this.props.params.categoryId;
// Get the category tree from cache // Get the category tree from cache
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
const categoryTreeCache = window.categoryService.getSync(209, currentLanguage); const categoryTreeCache = window.categoryService.getSync(209, currentLanguage);
@@ -521,7 +523,7 @@ class Content extends Component {
renderParentCategoryNavigation = () => { renderParentCategoryNavigation = () => {
const currentCategoryId = this.getCurrentCategoryId(); const currentCategoryId = this.getCurrentCategoryId();
if (!currentCategoryId) return null; if (!currentCategoryId) return null;
// Get the category tree from cache // Get the category tree from cache
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de'; const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
const categoryTreeCache = window.categoryService.getSync(209, currentLanguage); const categoryTreeCache = window.categoryService.getSync(209, currentLanguage);
@@ -558,289 +560,289 @@ class Content extends Component {
render() { render() {
// console.log('Content props:', this.props); // console.log('Content props:', this.props);
// Check if we should show category boxes instead of product list // Check if we should show category boxes instead of product list
const showCategoryBoxes = this.state.loaded && const showCategoryBoxes = this.state.loaded &&
this.state.unfilteredProducts.length === 0 && this.state.unfilteredProducts.length === 0 &&
this.state.childCategories.length > 0; this.state.childCategories.length > 0;
console.log("showCategoryBoxes", showCategoryBoxes, this.state.unfilteredProducts.length, this.state.childCategories.length); console.log("showCategoryBoxes", showCategoryBoxes, this.state.unfilteredProducts.length, this.state.childCategories.length);
return ( return (
<Container maxWidth="xl" sx={{ py: { xs: 0, sm: 2 }, px: { xs: 0, sm: 3 }, flexGrow: 1, display: 'grid', gridTemplateRows: '1fr' }}> <Container maxWidth="xl" sx={{ py: { xs: 0, sm: 2 }, px: { xs: 0, sm: 3 }, flexGrow: 1, display: 'grid', gridTemplateRows: '1fr' }}>
{showCategoryBoxes ? ( {showCategoryBoxes ? (
// Show category boxes layout when no products but have child categories // Show category boxes layout when no products but have child categories
<CategoryBoxGrid <CategoryBoxGrid
categories={this.state.childCategories} categories={this.state.childCategories}
title={this.state.categoryName} title={this.state.categoryName}
/> />
) : ( ) : (
<> <>
{/* Show subcategories above main layout when there are both products and child categories */} {/* Show subcategories above main layout when there are both products and child categories */}
{this.state.loaded && {this.state.loaded &&
this.state.unfilteredProducts.length > 0 && this.state.unfilteredProducts.length > 0 &&
this.state.childCategories.length > 0 && ( this.state.childCategories.length > 0 && (
<Box sx={{ mb: 4 }}> <Box sx={{ mb: 4 }}>
{(() => { {(() => {
const parentCategory = this.renderParentCategoryNavigation(); const parentCategory = this.renderParentCategoryNavigation();
if (parentCategory) { if (parentCategory) {
// Show parent category to the left of subcategories // Show parent category to the left of subcategories
return ( return (
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 3, flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 3, flexWrap: 'wrap' }}>
{/* Parent Category Box */} {/* Parent Category Box */}
<Box sx={{ mt:2,position: 'relative', flexShrink: 0 }}> <Box sx={{ mt: 2, position: 'relative', flexShrink: 0 }}>
<CategoryBox <CategoryBox
id={parentCategory.id} id={parentCategory.id}
seoName={parentCategory.seoName} seoName={parentCategory.seoName}
name={parentCategory.name} name={parentCategory.name}
image={parentCategory.image} image={parentCategory.image}
height={130} height={130}
fontSize="1.0rem" fontSize="1.0rem"
/> />
{/* Up Arrow Overlay */} {/* Up Arrow Overlay */}
<Box sx={{ <Box sx={{
position: 'absolute', position: 'absolute',
top: 8, top: 8,
right: 8, right: 8,
bgcolor: 'rgba(27, 94, 32, 0.8)', bgcolor: 'rgba(27, 94, 32, 0.8)',
borderRadius: '50%', borderRadius: '50%',
zIndex: 100, zIndex: 100,
width: 32, width: 32,
height: 32, height: 32,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
pointerEvents: 'none' pointerEvents: 'none'
}}> }}>
<KeyboardArrowUpIcon sx={{ color: 'white', fontSize: '1.2rem' }} /> <KeyboardArrowUpIcon sx={{ color: 'white', fontSize: '1.2rem' }} />
</Box>
</Box>
{/* Subcategories Grid */}
<Box sx={{ flexGrow: 1 }}>
<CategoryBoxGrid categories={this.state.childCategories} />
</Box> </Box>
</Box> </Box>
);
{/* Subcategories Grid */} } else {
<Box sx={{ flexGrow: 1 }}> // No parent category, just show subcategories
<CategoryBoxGrid categories={this.state.childCategories} /> return <CategoryBoxGrid categories={this.state.childCategories} />;
</Box> }
</Box> })()}
); </Box>
} else { )}
// No parent category, just show subcategories
return <CategoryBoxGrid categories={this.state.childCategories} />;
}
})()}
</Box>
)}
{/* Show standalone parent category navigation when there are only products */} {/* Show standalone parent category navigation when there are only products */}
{this.state.loaded && {this.state.loaded &&
this.props.params.categoryId && this.props.params.categoryId &&
!(this.state.unfilteredProducts.length > 0 && this.state.childCategories.length > 0) && (() => { !(this.state.unfilteredProducts.length > 0 && this.state.childCategories.length > 0) && (() => {
const parentCategory = this.renderParentCategoryNavigation(); const parentCategory = this.renderParentCategoryNavigation();
if (parentCategory) { if (parentCategory) {
return ( return (
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 3 }}>
<Box sx={{ position: 'relative', width: 'fit-content' }}> <Box sx={{ position: 'relative', width: 'fit-content' }}>
<CategoryBox <CategoryBox
id={parentCategory.id} id={parentCategory.id}
seoName={parentCategory.seoName} seoName={parentCategory.seoName}
name={parentCategory.name} name={parentCategory.name}
image={parentCategory.image} image={parentCategory.image}
height={130} height={130}
fontSize="1.0rem" fontSize="1.0rem"
/> />
{/* Up Arrow Overlay */} {/* Up Arrow Overlay */}
<Box sx={{ <Box sx={{
position: 'absolute', position: 'absolute',
top: 8, top: 8,
right: 8, right: 8,
bgcolor: 'rgba(27, 94, 32, 0.8)', bgcolor: 'rgba(27, 94, 32, 0.8)',
borderRadius: '50%', borderRadius: '50%',
zIndex: 100, zIndex: 100,
width: 32, width: 32,
height: 32, height: 32,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
pointerEvents: 'none' pointerEvents: 'none'
}}> }}>
<KeyboardArrowUpIcon sx={{ color: 'white', fontSize: '1.2rem' }} /> <KeyboardArrowUpIcon sx={{ color: 'white', fontSize: '1.2rem' }} />
</Box>
</Box> </Box>
</Box> </Box>
</Box> );
); }
} return null;
return null; })()}
})()}
{/* Show normal product list layout */} {/* Show normal product list layout */}
<Box sx={{ <Box sx={{
display: 'grid', display: 'grid',
gridTemplateColumns: { xs: '1fr', sm: '1fr 2fr', md: '1fr 3fr', lg: '1fr 4fr', xl: '1fr 4fr' }, gridTemplateColumns: { xs: '1fr', sm: '1fr 2fr', md: '1fr 3fr', lg: '1fr 4fr', xl: '1fr 4fr' },
gap: { xs: 0, sm: 3 } gap: { xs: 0, sm: 3 }
}}> }}>
<Stack direction="row" spacing={0} sx={{ <Stack direction="row" spacing={0} sx={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
minHeight: { xs: 'min-content', sm: '100%' } minHeight: { xs: 'min-content', sm: '100%' }
}}>
<Box >
<ProductFilters
products={this.state.unfilteredProducts}
filteredProducts={this.state.filteredProducts}
attributes={this.state.attributes}
searchParams={this.props.searchParams}
onFilterChange={()=>{this.filterProducts()}}
dataType={this.state.dataType}
dataParam={this.state.dataParam}
categoryName={this.state.categoryName}
/>
</Box>
{(this.props.params.categoryId == 'Stecklinge' || this.props.params.categoryId == 'Seeds') &&
<Box sx={{ display: { xs: 'none', sm: 'block' } }}>
<Typography variant="h6" sx={{mt:3}}>
{this.props.t ? this.props.t('navigation.otherCategories') : 'Andere Kategorien'}
</Typography>
</Box>
}
{this.props.params.categoryId == 'Stecklinge' && <Paper
component={Link}
to="/Kategorie/Seeds"
sx={{
p:0,
mt: 1,
textDecoration: 'none',
color: 'text.primary',
borderRadius: 2,
overflow: 'hidden',
height: 300,
transition: 'all 0.3s ease',
boxShadow: 10,
display: { xs: 'none', sm: 'block' },
'&:hover': {
transform: 'translateY(-5px)',
boxShadow: 20
}
}}
>
{/* Image Container - Place your seeds image here */}
<Box sx={{
height: '100%',
bgcolor: '#e1f0d3',
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<img
src="/assets/images/seeds.avif"
alt="Seeds"
fetchPriority="high"
loading="eager"
style={{
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
}}
/>
{/* Overlay text - optional */}
<Box sx={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
bgcolor: 'rgba(27, 94, 32, 0.8)',
p: 2,
}}> }}>
<Typography sx={{ fontSize: '1.3rem', color: 'white', fontFamily: 'SwashingtonCP' }}>
{this.props.t('sections.seeds')} <Box >
</Typography>
<ProductFilters
products={this.state.unfilteredProducts}
filteredProducts={this.state.filteredProducts}
attributes={this.state.attributes}
searchParams={this.props.searchParams}
onFilterChange={() => { this.filterProducts() }}
dataType={this.state.dataType}
dataParam={this.state.dataParam}
categoryName={this.state.categoryName}
/>
</Box>
{(this.props.params.categoryId == 'Stecklinge___' || this.props.params.categoryId == 'Seeds___') &&
<Box sx={{ display: { xs: 'none', sm: 'block' } }}>
<Typography variant="h6" sx={{ mt: 3 }}>
{this.props.t ? this.props.t('navigation.otherCategories') : 'Andere Kategorien'}
</Typography>
</Box>
}
{this.props.params.categoryId == 'Stecklinge' && <Paper
component={Link}
to="/Kategorie/Seeds"
sx={{
p: 0,
mt: 1,
textDecoration: 'none',
color: 'text.primary',
borderRadius: 2,
overflow: 'hidden',
height: 300,
transition: 'all 0.3s ease',
boxShadow: 10,
display: { xs: 'none', sm: 'block' },
'&:hover': {
transform: 'translateY(-5px)',
boxShadow: 20
}
}}
>
{/* Image Container - Place your seeds image here */}
<Box sx={{
height: '100%',
bgcolor: '#e1f0d3',
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<img
src="/assets/images/seeds.avif"
alt="Seeds"
fetchPriority="high"
loading="eager"
style={{
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
}}
/>
{/* Overlay text - optional */}
<Box sx={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
bgcolor: 'rgba(27, 94, 32, 0.8)',
p: 2,
}}>
<Typography sx={{ fontSize: '1.3rem', color: 'white', fontFamily: 'SwashingtonCP' }}>
{this.props.t('sections.seeds')}
</Typography>
</Box>
</Box>
</Paper>
}
{this.props.params.categoryId == 'Seeds___' && <Paper
component={Link}
to="/Kategorie/Stecklinge"
sx={{
p: 0,
mt: 1,
textDecoration: 'none',
color: 'text.primary',
borderRadius: 2,
overflow: 'hidden',
height: 300,
boxShadow: 10,
transition: 'all 0.3s ease',
display: { xs: 'none', sm: 'block' },
'&:hover': {
transform: 'translateY(-5px)',
boxShadow: 20
}
}}
>
{/* Image Container - Place your cutlings image here */}
<Box sx={{
height: '100%',
bgcolor: '#e8f5d6',
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<img
src="/assets/images/cutlings.avif"
alt="Stecklinge"
fetchPriority="high"
loading="eager"
style={{
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
}}
/>
{/* Overlay text - optional */}
<Box sx={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
bgcolor: 'rgba(27, 94, 32, 0.8)',
p: 2,
}}>
<Typography sx={{ fontSize: '1.3rem', color: 'white', fontFamily: 'SwashingtonCP' }}>
{this.props.t('sections.stecklinge')}
</Typography>
</Box>
</Box>
</Paper>}
</Stack>
<Box>
<ProductList
totalProductCount={(this.state.unfilteredProducts || []).length}
products={this.state.filteredProducts || []}
activeAttributeFilters={this.state.activeAttributeFilters || []}
activeManufacturerFilters={this.state.activeManufacturerFilters || []}
activeAvailabilityFilters={this.state.activeAvailabilityFilters || []}
onFilterChange={() => { this.filterProducts() }}
dataType={this.state.dataType}
dataParam={this.state.dataParam}
/>
</Box> </Box>
</Box> </Box>
</Paper>
}
{this.props.params.categoryId == 'Seeds' && <Paper
component={Link}
to="/Kategorie/Stecklinge"
sx={{
p: 0,
mt: 1,
textDecoration: 'none',
color: 'text.primary',
borderRadius: 2,
overflow: 'hidden',
height: 300,
boxShadow: 10,
transition: 'all 0.3s ease',
display: { xs: 'none', sm: 'block' },
'&:hover': {
transform: 'translateY(-5px)',
boxShadow: 20
}
}}
>
{/* Image Container - Place your cutlings image here */}
<Box sx={{
height: '100%',
bgcolor: '#e8f5d6',
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<img
src="/assets/images/cutlings.avif"
alt="Stecklinge"
fetchPriority="high"
loading="eager"
style={{
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)'
}}
/>
{/* Overlay text - optional */}
<Box sx={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
bgcolor: 'rgba(27, 94, 32, 0.8)',
p: 2,
}}>
<Typography sx={{ fontSize: '1.3rem', color: 'white', fontFamily: 'SwashingtonCP' }}>
{this.props.t('sections.stecklinge')}
</Typography>
</Box>
</Box>
</Paper>}
</Stack>
<Box>
<ProductList
totalProductCount={(this.state.unfilteredProducts || []).length}
products={this.state.filteredProducts || []}
activeAttributeFilters={this.state.activeAttributeFilters || []}
activeManufacturerFilters={this.state.activeManufacturerFilters || []}
activeAvailabilityFilters={this.state.activeAvailabilityFilters || []}
onFilterChange={()=>{this.filterProducts()}}
dataType={this.state.dataType}
dataParam={this.state.dataParam}
/>
</Box>
</Box>
</> </>
)} )}
</Container> </Container>

View File

@@ -156,7 +156,7 @@ const MainPageLayout = () => {
}; };
const allTitles = { const allTitles = {
home: t('titles.home') , home: t('titles.home'),
aktionen: t('titles.aktionen'), aktionen: t('titles.aktionen'),
filiale: t('titles.filiale') filiale: t('titles.filiale')
}; };
@@ -164,7 +164,7 @@ const MainPageLayout = () => {
const allContentBoxes = { const allContentBoxes = {
home: [ home: [
{ title: t('sections.seeds'), image: "/assets/images/seeds.avif", bgcolor: "#e1f0d3", link: "/Kategorie/Seeds" }, { title: t('sections.seeds'), image: "/assets/images/seeds.avif", bgcolor: "#e1f0d3", link: "/Kategorie/Seeds" },
{ title: t('sections.stecklinge'), image: "/assets/images/cutlings.avif", bgcolor: "#e8f5d6", link: "/Kategorie/Stecklinge" } { title: t('sections.konfigurator'), image: "/assets/images/konfigurator.avif", bgcolor: "#e8f5d6", link: "/Konfigurator" }
], ],
aktionen: [ aktionen: [
{ title: t('sections.oilPress'), image: "/assets/images/presse.jpg", bgcolor: "#e1f0d3", link: "/presseverleih" }, { title: t('sections.oilPress'), image: "/assets/images/presse.jpg", bgcolor: "#e1f0d3", link: "/presseverleih" },
@@ -262,16 +262,16 @@ const MainPageLayout = () => {
position: pageType === "home" ? "relative" : "absolute", top: 0, left: 0, width: "100%", pointerEvents: getOpacity(pageType) === 1 ? "auto" : "none" position: pageType === "home" ? "relative" : "absolute", top: 0, left: 0, width: "100%", pointerEvents: getOpacity(pageType) === 1 ? "auto" : "none"
}}> }}>
{contentBoxes.map((box, index) => ( {contentBoxes.map((box, index) => (
<ContentBox <ContentBox
key={`${pageType}-${index}`} key={`${pageType}-${index}`}
box={box} box={box}
index={index} index={index}
pageType={pageType} pageType={pageType}
starHovered={starHovered} starHovered={starHovered}
setStarHovered={setStarHovered} setStarHovered={setStarHovered}
opacity={getOpacity(pageType)} opacity={getOpacity(pageType)}
translatedContent={translatedContent} translatedContent={translatedContent}
/> />
))} ))}
</Grid> </Grid>
))} ))}

View File

@@ -1,11 +1,12 @@
export default { export default {
"seeds": "بذور", "seeds": "بذور",
"stecklinge": "قصاصات", "stecklinge": "قصاصات",
"oilPress": "استعارة معصرة الزيت", "konfigurator": "المُكوّن",
"oilPress": "استعارة مكبس الزيت",
"thcTest": "اختبار THC", "thcTest": "اختبار THC",
"address1": "Trachenberger Straße 14", "address1": "شارع تراشينبرجر 14",
"address2": "01129 Dresden", "address2": "01129 دريسدن",
"showUsPhoto": "ورينا أجمل صورة عندك", "showUsPhoto": "اعرض لنا أجمل صورة لديك",
"selectSeedRate": "اختار البذرة واضغط تقييم", "selectSeedRate": "اختر البذرة، واضغط للتقييم",
"indoorSeason": "موسم الزراعة الداخلية بدأ" "indoorSeason": "بدأ موسم الزراعة الداخلية"
}; };

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "بذور وقصاصات القنب الممتازة", "home": "بذور القنب الممتازة",
"aktionen": "العروض والتخفيضات الحالية", "aktionen": "العروض والتخفيضات الحالية",
"filiale": "متجرنا في دريسدن", "filiale": "متجرنا في دريسدن"
}; };

View File

@@ -1,7 +1,8 @@
export default { export default {
"seeds": "Семена", "seeds": "Семена",
"stecklinge": "Резници", "stecklinge": "Резници",
"oilPress": "Наеми преса за масло", "konfigurator": "Конфигуратор",
"oilPress": "Наеми преса за олио",
"thcTest": "THC тест", "thcTest": "THC тест",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",
"address2": "01129 Dresden", "address2": "01129 Dresden",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Фини семена и резници от канабис", "home": "Качествени канабис семена",
"aktionen": "Текущи промоции и оферти", "aktionen": "Текущи промоции и оферти",
"filiale": "Нашият магазин в Дрезден", "filiale": "Нашият магазин в Дрезден"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "Semena", "seeds": "Semena",
"stecklinge": "Řízky", "stecklinge": "Řízky",
"konfigurator": "Konfigurátor",
"oilPress": "Půjčit lis na olej", "oilPress": "Půjčit lis na olej",
"thcTest": "THC test", "thcTest": "THC test",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Kvalitní semena a řízky konopí", "home": "Kvalitní semena konopí",
"aktionen": "Aktuální akce a nabídky", "aktionen": "Aktuální akce a nabídky",
"filiale": "Naše prodejna v Drážďanech", "filiale": "Naše prodejna v Drážďanech"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "Seeds", "seeds": "Seeds",
"stecklinge": "Stecklinge", "stecklinge": "Stecklinge",
"konfigurator": "Konfigurator",
"oilPress": "Ölpresse ausleihen", "oilPress": "Ölpresse ausleihen",
"thcTest": "THC Test", "thcTest": "THC Test",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Fine Cannabis Seeds & Cuttings", "home": "Fine Cannabis Seeds",
"aktionen": "Aktuelle Aktionen & Angebote", "aktionen": "Aktuelle Aktionen & Angebote",
"filiale": "Unsere Filiale in Dresden" "filiale": "Unsere Filiale in Dresden"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "Σπόροι", "seeds": "Σπόροι",
"stecklinge": "Μοσχεύματα", "stecklinge": "Μοσχεύματα",
"konfigurator": "Διαμορφωτής",
"oilPress": "Δανείσου πρέσα λαδιού", "oilPress": "Δανείσου πρέσα λαδιού",
"thcTest": "Τεστ THC", "thcTest": "Τεστ THC",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Ποιοτικοί Σπόροι & Μοσχεύματα Κάνναβης", "home": "Ποιοτικοί Σπόροι Κάνναβης",
"aktionen": "Τρέχουσες Προσφορές & Εκπτώσεις", "aktionen": "Τρέχουσες προσφορές & εκπτώσεις",
"filiale": "Το Κατάστημά μας στη Δρέσδη", "filiale": "Το κατάστημά μας στη Δρέσδη"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "Seeds", // Seeds "seeds": "Seeds", // Seeds
"stecklinge": "Cuttings", // Stecklinge "stecklinge": "Cuttings", // Stecklinge
"konfigurator": "Configurator", // Konfigurator
"oilPress": "Borrow oil press", // Ölpresse ausleihen "oilPress": "Borrow oil press", // Ölpresse ausleihen
"thcTest": "THC test", // THC Test "thcTest": "THC test", // THC Test
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14 "address1": "Trachenberger Straße 14", // Trachenberger Straße 14

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Fine Cannabis Seeds & Cuttings", // Fine Cannabis Samen & Stecklinge "home": "Fine Cannabis Seeds", // Fine Cannabis Seeds
"aktionen": "Current Promotions & Offers", // Aktuelle Aktionen & Angebote "aktionen": "Current promotions & offers", // Aktuelle Aktionen & Angebote
"filiale": "Our Store in Dresden", // Unsere Filiale in Dresden "filiale": "Our store in Dresden" // Unsere Filiale in Dresden
}; };

View File

@@ -1,11 +1,12 @@
export default { export default {
"seeds": "Semillas", "seeds": "Semillas",
"stecklinge": "Esquejes", "stecklinge": "Esquejes",
"konfigurator": "Configurador",
"oilPress": "Pedir prestada prensa de aceite", "oilPress": "Pedir prestada prensa de aceite",
"thcTest": "Prueba de THC", "thcTest": "Prueba de THC",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",
"address2": "01129 Dresden", "address2": "01129 Dresden",
"showUsPhoto": "Muéstranos tu foto más hermosa", "showUsPhoto": "Muéstranos tu foto más hermosa",
"selectSeedRate": "Selecciona semilla, haz clic para valorar", "selectSeedRate": "Selecciona semilla, haz clic en valorar",
"indoorSeason": "Comienza la temporada de interior" "indoorSeason": "Comienza la temporada de interior"
}; };

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Semillas y esquejes de cannabis de calidad", "home": "Semillas de Cannabis de Calidad",
"aktionen": "Promociones y ofertas actuales", "aktionen": "Promociones y ofertas actuales",
"filiale": "Nuestra tienda en Dresden", "filiale": "Nuestra tienda en Dresden"
}; };

View File

@@ -1,11 +1,12 @@
export default { export default {
"seeds": "Graines", "seeds": "Graines",
"stecklinge": "Boutures", "stecklinge": "Boutures",
"oilPress": "Emprunter la presse à huile", "konfigurator": "Configurateur",
"oilPress": "Emprunter une presse à huile",
"thcTest": "Test THC", "thcTest": "Test THC",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",
"address2": "01129 Dresden", "address2": "01129 Dresden",
"showUsPhoto": "Montre-nous ta plus belle photo", "showUsPhoto": "Montre-nous ta plus belle photo",
"selectSeedRate": "Sélectionne une graine, clique pour noter", "selectSeedRate": "Sélectionne une graine, clique pour noter",
"indoorSeason": "La saison en intérieur commence" "indoorSeason": "La saison indoor commence"
}; };

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Graines et boutures de cannabis de qualité", "home": "Graines de cannabis de qualité",
"aktionen": "Promotions et offres actuelles", "aktionen": "Promotions et offres en cours",
"filiale": "Notre magasin à Dresde", "filiale": "Notre magasin à Dresde"
}; };

View File

@@ -1,11 +1,12 @@
export default { export default {
"seeds": "Sjemenke", "seeds": "Sjemenke",
"stecklinge": "Reznice", "stecklinge": "Reznice",
"konfigurator": "Konfigurator",
"oilPress": "Posudi prešu za ulje", "oilPress": "Posudi prešu za ulje",
"thcTest": "THC test", "thcTest": "THC test",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",
"address2": "01129 Dresden", "address2": "01129 Dresden",
"showUsPhoto": "Pokaži nam svoju najljepšu fotografiju", "showUsPhoto": "Pokaži nam svoju najljepšu fotografiju",
"selectSeedRate": "Odaberi sjeme, klikni ocjenu", "selectSeedRate": "Odaberi sjeme, klikni ocijeni",
"indoorSeason": "Počinje sezona uzgoja u zatvorenom" "indoorSeason": "Počinje sezona uzgoja u zatvorenom"
}; };

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Kvalitetne sjemenke i reznice kanabisa", "home": "Kvalitetne sjemenke kanabisa",
"aktionen": "Trenutne promocije i ponude", "aktionen": "Trenutne promocije i ponude",
"filiale": "Naša trgovina u Dresdenu", "filiale": "Naša trgovina u Dresdenu"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "Magok", "seeds": "Magok",
"stecklinge": "Cserepek", "stecklinge": "Cserepek",
"konfigurator": "Konfigurátor",
"oilPress": "Olajprés kölcsönzése", "oilPress": "Olajprés kölcsönzése",
"thcTest": "THC teszt", "thcTest": "THC teszt",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Minőségi kannabisz magok és dugványok", "home": "Minőségi kannabisz magok",
"aktionen": "Aktuális promóciók és ajánlatok", "aktionen": "Aktuális akciók és ajánlatok",
"filiale": "Üzletünk Drezdában", "filiale": "Üzletünk Drezdában"
}; };

View File

@@ -1,11 +1,12 @@
export default { export default {
"seeds": "Semi", "seeds": "Semi",
"stecklinge": "Talee", "stecklinge": "Talee",
"oilPress": "Prendere in prestito la pressa per olio", "konfigurator": "Configuratore",
"oilPress": "Noleggia pressa per olio",
"thcTest": "Test THC", "thcTest": "Test THC",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",
"address2": "01129 Dresden", "address2": "01129 Dresden",
"showUsPhoto": "Mostraci la tua foto più bella", "showUsPhoto": "Mostraci la tua foto più bella",
"selectSeedRate": "Seleziona il seme, clicca per valutare", "selectSeedRate": "Seleziona il seme, clicca per valutare",
"indoorSeason": "Inizia la stagione indoor" "indoorSeason": "La stagione indoor inizia"
}; };

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Semi e talee di cannabis di alta qualità", "home": "Semi di Cannabis di Qualità",
"aktionen": "Promozioni e offerte attuali", "aktionen": "Promozioni e offerte attuali",
"filiale": "Il nostro negozio a Dresda", "filiale": "Il nostro negozio a Dresda"
}; };

View File

@@ -1,11 +1,13 @@
export default { export default {
"seeds": "Nasiona", "seeds": "Nasiona",
"stecklinge": "Sadzonki", "stecklinge": "Sadzonki",
"konfigurator": "Konfigurator",
"oilPress": "Wypożycz prasę do oleju", "oilPress": "Wypożycz prasę do oleju",
"thcTest": "Test THC", "thcTest": "Test THC",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",
"address2": "01129 Dresden", "address2": "01129 Dresden",
"showUsPhoto": "Pokaż nam swoje najpiękniejsze zdjęcie", "showUsPhoto": "Pokaż nam swoje najpiękniejsze zdjęcie",
"selectSeedRate": "Wybierz nasiono, kliknij ocenę", "selectSeedRate": "Wybierz nasiono, kliknij ocenę",
"indoorSeason": "Sezon indoor się zaczyna" "indoorSeason": "Sezon indoorowy się zaczyna",
"locale": "pl"
}; };

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Dobre Nasiona i Szczepki Konopi", "home": "Dobre nasiona konopi",
"aktionen": "Aktualne Promocje i Oferty", "aktionen": "Aktualne promocje i oferty",
"filiale": "Nasz Sklep w Dreźnie", "filiale": "Nasz sklep w Dreźnie"
}; };

View File

@@ -1,11 +1,12 @@
export default { export default {
"seeds": "Semințe", "seeds": "Semințe",
"stecklinge": "Butășe", "stecklinge": "Butășe",
"konfigurator": "Configurator",
"oilPress": "Împrumută presa de ulei", "oilPress": "Împrumută presa de ulei",
"thcTest": "Test THC", "thcTest": "Test THC",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",
"address2": "01129 Dresden", "address2": "01129 Dresden",
"showUsPhoto": "Arată-ne cea mai frumoasă fotografie a ta", "showUsPhoto": "Arată-ne cea mai frumoasă fotografie a ta",
"selectSeedRate": "Selectează sămânța, apasă pentru evaluare", "selectSeedRate": "Selectează sămânța, apasă evaluează",
"indoorSeason": "Sezonul indoor începe" "indoorSeason": "Sezonul indoor începe"
}; };

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Semințe și butași de cannabis de calitate", "home": "Semințe fine de cannabis",
"aktionen": "Promoții și oferte curente", "aktionen": "Promoții și oferte curente",
"filiale": "Magazinul nostru din Dresda", "filiale": "Magazinul nostru din Dresden"
}; };

View File

@@ -1,11 +1,12 @@
export default { export default {
"seeds": "Семена", "seeds": "Семена",
"stecklinge": "Черенки", "stecklinge": "Черенки",
"konfigurator": "Конфигуратор",
"oilPress": "Взять напрокат маслопресс", "oilPress": "Взять напрокат маслопресс",
"thcTest": "Тест на THC", "thcTest": "Тест на THC",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",
"address2": "01129 Dresden", "address2": "01129 Dresden",
"showUsPhoto": "Покажи нам свою самую красивую фотографию", "showUsPhoto": "Покажи нам свою самую красивую фотографию",
"selectSeedRate": "Выберите семя, нажмите оценить", "selectSeedRate": "Выберите семена, нажмите оценить",
"indoorSeason": "Начинается сезон для выращивания в помещении" "indoorSeason": "Начинается сезон для выращивания в помещении"
}; };

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Качественные семена и черенки каннабиса", "home": "Качественные семена каннабиса",
"aktionen": "Текущие акции и предложения", "aktionen": "Текущие акции и предложения",
"filiale": "Наш магазин в Дрездене", "filiale": "Наш магазин в Дрездене"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "Semienka", "seeds": "Semienka",
"stecklinge": "Rezky", "stecklinge": "Rezky",
"konfigurator": "Konfigurátor",
"oilPress": "Požičajte si lis na olej", "oilPress": "Požičajte si lis na olej",
"thcTest": "THC test", "thcTest": "THC test",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Kvalitné semená a odrezky konope", "home": "Kvalitné semená konope",
"aktionen": "Aktuálne akcie a ponuky", "aktionen": "Aktuálne akcie a ponuky",
"filiale": "Naša predajňa v Drážďanoch", "filiale": "Náš obchod v Drážďanoch"
}; };

View File

@@ -1,11 +1,12 @@
export default { export default {
"seeds": "Semena", "seeds": "Semena",
"stecklinge": "Rezalci", "stecklinge": "Rezalci",
"oilPress": "Izposodi si stiskalnico za olje", "konfigurator": "Konfigurator",
"oilPress": "Izposodi si stiskalnico olja",
"thcTest": "THC test", "thcTest": "THC test",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",
"address2": "01129 Dresden", "address2": "01129 Dresden",
"showUsPhoto": "Pokaži nam svojo najlepšo fotografijo", "showUsPhoto": "Pokaži nam svojo najlepšo fotografijo",
"selectSeedRate": "Izberi seme, klikni oceno", "selectSeedRate": "Izberi seme, klikni oceni",
"indoorSeason": "Začne se sezona gojenja v zaprtih prostorih" "indoorSeason": "Začne se sezona gojenja v zaprtih prostorih"
}; };

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Kakovostna semena in reznice konoplje", "home": "Kakovostna semena konoplje",
"aktionen": "Trenutne promocije in ponudbe", "aktionen": "Trenutne promocije in ponudbe",
"filiale": "Naša trgovina v Dresdnu", "filiale": "Naša trgovina v Dresdnu"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "Farëra", "seeds": "Farëra",
"stecklinge": "Përkëmbëza", "stecklinge": "Përkëmbëza",
"konfigurator": "Konfiguruesi",
"oilPress": "Huazo shtypësin e vajit", "oilPress": "Huazo shtypësin e vajit",
"thcTest": "Testi THC", "thcTest": "Testi THC",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Farëra dhe Gjethe Kanabisi të Kualitetit të Lartë", "home": "Farëra të mira kanabisi",
"aktionen": "Promocionet dhe Ofertat Aktualë", "aktionen": "Promocionet dhe ofertat aktuale",
"filiale": "Dyqani Ynë në Dresden", "filiale": "Dyqani ynë në Dresden"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "Semena", "seeds": "Semena",
"stecklinge": "Reznice", "stecklinge": "Reznice",
"konfigurator": "Konfigurator",
"oilPress": "Pozajmi prešu za ulje", "oilPress": "Pozajmi prešu za ulje",
"thcTest": "THC test", "thcTest": "THC test",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Kvalitetni seme i reznice kanabisa", "home": "Kvalitetne semenke kanabisa",
"aktionen": "Trenutne promocije i ponude", "aktionen": "Trenutne promocije i ponude",
"filiale": "Naša prodavnica u Dresdenu", "filiale": "Naša prodavnica u Dresdenu"
}; };

View File

@@ -1,11 +1,12 @@
export default { export default {
"seeds": "Frön", "seeds": "Frön",
"stecklinge": "Sticklingar", "stecklinge": "Sticklingar",
"konfigurator": "Konfigurator",
"oilPress": "Låna oljepress", "oilPress": "Låna oljepress",
"thcTest": "THC-test", "thcTest": "THC-test",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",
"address2": "01129 Dresden", "address2": "01129 Dresden",
"showUsPhoto": "Visa oss ditt vackraste foto", "showUsPhoto": "Visa oss din vackraste bild",
"selectSeedRate": "Välj frö, klicka betyg", "selectSeedRate": "Välj frö, klicka betyg",
"indoorSeason": "Inomhussäsongen börjar" "indoorSeason": "Inomhussäsongen börjar"
}; };

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Fina cannabisfrön & sticklingar", "home": "Fina cannabisfrön",
"aktionen": "Aktuella kampanjer & erbjudanden", "aktionen": "Aktuella kampanjer & erbjudanden",
"filiale": "Vår butik i Dresden", "filiale": "Vår butik i Dresden"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "Tohumlar", "seeds": "Tohumlar",
"stecklinge": "Çelikler", "stecklinge": "Çelikler",
"konfigurator": "Konfigüratör",
"oilPress": "Yağ presini ödünç al", "oilPress": "Yağ presini ödünç al",
"thcTest": "THC testi", "thcTest": "THC testi",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Kaliteli Kenevir Tohumları ve Çelikleri", "home": "Kaliteli Kenevir Tohumları",
"aktionen": "Güncel Kampanyalar ve Teklifler", "aktionen": "Mevcut promosyonlar ve teklifler",
"filiale": "Dresden'deki Mağazamız", "filiale": "Dresden'deki mağazamız"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "Насіння", "seeds": "Насіння",
"stecklinge": "Живці", "stecklinge": "Живці",
"konfigurator": "Конфігуратор",
"oilPress": "Позичити олійний прес", "oilPress": "Позичити олійний прес",
"thcTest": "Тест на THC", "thcTest": "Тест на THC",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "Якісне насіння та живці канабісу", "home": "Якісне насіння канабісу",
"aktionen": "Поточні акції та пропозиції", "aktionen": "Поточні акції та пропозиції",
"filiale": "Наш магазин у Дрездені", "filiale": "Наш магазин у Дрездені"
}; };

View File

@@ -1,6 +1,7 @@
export default { export default {
"seeds": "种子", "seeds": "种子",
"stecklinge": "插枝", "stecklinge": "插枝",
"konfigurator": "配置器",
"oilPress": "借用榨油机", "oilPress": "借用榨油机",
"thcTest": "THC 测试", "thcTest": "THC 测试",
"address1": "Trachenberger Straße 14", "address1": "Trachenberger Straße 14",

View File

@@ -1,5 +1,5 @@
export default { export default {
"home": "优质大麻种子和插枝", "home": "优质大麻种子",
"aktionen": "当前促销与优惠", "aktionen": "当前促销与优惠",
"filiale": "我们在德累斯顿的门店", "filiale": "我们在德累斯顿的门店"
}; };