Compare commits
21 Commits
0b915db9eb
...
live
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9df5642a6e | ||
|
|
a50dd086c3 | ||
|
|
e88370ff3e | ||
|
|
5d3e0832fe | ||
|
|
3347ba2754 | ||
|
|
013a38ca98 | ||
|
|
2d6c8ff25f | ||
|
|
d2ac8d3fc1 | ||
|
|
8928b3f283 | ||
|
|
87db7ba3ea | ||
|
|
766fef2796 | ||
|
|
a08c90a521 | ||
|
|
10d60d5827 | ||
|
|
905eee57d5 | ||
|
|
3389a9b66c | ||
|
|
d63c385a97 | ||
|
|
1b51da69a9 | ||
|
|
da81479d9b | ||
|
|
d8678e261d | ||
|
|
ef91e50aa5 | ||
|
|
061bf5ff17 |
154
docs/nginx.conf
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
server {
|
||||||
|
client_max_body_size 64M;
|
||||||
|
listen 443 ssl;
|
||||||
|
http2 on;
|
||||||
|
server_name example.de;
|
||||||
|
ssl_certificate /etc/letsencrypt/live/example.de/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/example.de/privkey.pem;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_min_length 256;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_types
|
||||||
|
text/css
|
||||||
|
text/javascript
|
||||||
|
application/javascript
|
||||||
|
application/x-javascript
|
||||||
|
application/json
|
||||||
|
application/xml
|
||||||
|
image/svg+xml;
|
||||||
|
|
||||||
|
index index.html;
|
||||||
|
root /example/dist;
|
||||||
|
|
||||||
|
error_log logs/error.log info;
|
||||||
|
access_log logs/access.log combined;
|
||||||
|
|
||||||
|
location /socket.io/ {
|
||||||
|
proxy_pass http://localhost:9303/socket.io/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
|
||||||
|
proxy_connect_timeout 3600s;
|
||||||
|
proxy_send_timeout 3600s;
|
||||||
|
proxy_read_timeout 3600s;
|
||||||
|
send_timeout 3600s;
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_cache off;
|
||||||
|
|
||||||
|
keepalive_timeout 65;
|
||||||
|
keepalive_requests 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://localhost:9303/api/;
|
||||||
|
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
proxy_set_header User-Agent $http_user_agent;
|
||||||
|
proxy_set_header Content-Type $content_type;
|
||||||
|
proxy_set_header Content-Length $content_length;
|
||||||
|
|
||||||
|
proxy_set_header X-API-Key $http_x_api_key;
|
||||||
|
|
||||||
|
proxy_connect_timeout 30s;
|
||||||
|
proxy_send_timeout 30s;
|
||||||
|
proxy_read_timeout 30s;
|
||||||
|
proxy_buffering off;
|
||||||
|
|
||||||
|
client_max_body_size 10M;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /Kategorie/ {
|
||||||
|
types {}
|
||||||
|
default_type text/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /Artikel/ {
|
||||||
|
types {}
|
||||||
|
default_type text/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /sitemap.xml {
|
||||||
|
types {}
|
||||||
|
default_type application/xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/(datenschutz|impressum|batteriegesetzhinweise|widerrufsrecht|sitemap|agb|Kategorien|Konfigurator|404|profile|resetPassword|thc-test|filiale|aktionen|presseverleih|payment/success)(/|$) {
|
||||||
|
types {}
|
||||||
|
default_type text/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /404 {
|
||||||
|
error_page 404 =404 /404-big.html;
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /404-big.html {
|
||||||
|
internal;
|
||||||
|
alias /home/seb/src/growheads_de/dist/404;
|
||||||
|
default_type text/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 404 /404.html;
|
||||||
|
|
||||||
|
location = /404.html {
|
||||||
|
internal;
|
||||||
|
default_type text/html;
|
||||||
|
return 404 '<!doctype html><html><body>
|
||||||
|
<script>
|
||||||
|
if (!navigator.userAgent.includes("bot")) { location.href="/404"; }
|
||||||
|
</script>
|
||||||
|
</body></html>';
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(js|css)\?.*$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
add_header Vary Accept-Encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(js|css)$ {
|
||||||
|
if ($uri ~ "\.[a-f0-9]{7,}\.(js|css)$") {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
expires 1d;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
add_header Vary Accept-Encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(ttf|otf|woff|woff2|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
add_header Access-Control-Allow-Origin "*";
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(jpg|jpeg|png|gif|ico|svg|webp)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
add_header Vary Accept-Encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /prerender.css {
|
||||||
|
expires 1w;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
add_header Vary Accept-Encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /assets/ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public";
|
||||||
|
add_header Vary Accept-Encoding;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
package-lock.json
generated
@@ -4554,9 +4554,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001727",
|
"version": "1.0.30001757",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
|
||||||
"integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
|
"integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"start": "cross-env NODE_OPTIONS=\"--no-deprecation\" webpack serve --progress --mode development --no-open",
|
"start": "cross-env NODE_OPTIONS=\"--no-deprecation\" webpack serve --progress --mode development --no-open",
|
||||||
"start:seedheads": "cross-env PROXY_TARGET=https://seedheads.de NODE_OPTIONS=\"--no-deprecation\" webpack serve --progress --mode development --no-open",
|
"start:seedheads": "cross-env PROXY_TARGET=https://seedheads.de NODE_OPTIONS=\"--no-deprecation\" webpack serve --progress --mode development --no-open",
|
||||||
"prod": "webpack serve --progress --mode production --no-client-overlay --no-client --no-web-socket-server --no-open --no-live-reload --no-hot --compress --no-devtool",
|
"prod": "webpack serve --progress --mode production --no-client-overlay --no-client --no-web-socket-server --no-open --no-live-reload --no-hot --compress --no-devtool",
|
||||||
"build:client": "cross-env NODE_ENV=production webpack --progress --mode production && shx cp dist/index.html dist/index_template.html",
|
"build:client": "node scripts/convert-images-to-avif.cjs && cross-env NODE_ENV=production webpack --progress --mode production && shx cp dist/index.html dist/index_template.html",
|
||||||
"build": "npm run build:client",
|
"build": "npm run build:client",
|
||||||
"analyze": "cross-env ANALYZE=true NODE_ENV=production webpack --progress --mode production",
|
"analyze": "cross-env ANALYZE=true NODE_ENV=production webpack --progress --mode production",
|
||||||
"lint": "eslint src/**/*.{js,jsx}",
|
"lint": "eslint src/**/*.{js,jsx}",
|
||||||
|
|||||||
109
prerender.cjs
@@ -28,7 +28,7 @@ class CategoryService {
|
|||||||
const cacheKey = `${categoryId}_${language}`;
|
const cacheKey = `${categoryId}_${language}`;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(categoryId, language = "de") {
|
async get(categoryId, language = "de") {
|
||||||
const cacheKey = `${categoryId}_${language}`;
|
const cacheKey = `${categoryId}_${language}`;
|
||||||
return null;
|
return null;
|
||||||
@@ -159,6 +159,7 @@ const Batteriegesetzhinweise =
|
|||||||
const Widerrufsrecht = require("./src/pages/Widerrufsrecht.js").default;
|
const Widerrufsrecht = require("./src/pages/Widerrufsrecht.js").default;
|
||||||
const Sitemap = require("./src/pages/Sitemap.js").default;
|
const Sitemap = require("./src/pages/Sitemap.js").default;
|
||||||
const PrerenderSitemap = require("./src/PrerenderSitemap.js").default;
|
const PrerenderSitemap = require("./src/PrerenderSitemap.js").default;
|
||||||
|
const PrerenderCategoriesPage = require("./src/PrerenderCategoriesPage.js").default;
|
||||||
const AGB = require("./src/pages/AGB.js").default;
|
const AGB = require("./src/pages/AGB.js").default;
|
||||||
const NotFound404 = require("./src/pages/NotFound404.js").default;
|
const NotFound404 = require("./src/pages/NotFound404.js").default;
|
||||||
|
|
||||||
@@ -189,7 +190,7 @@ const renderProductWorker = async (productSeoNames, workerId, progressCallback,
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const productDetails = await fetchProductDetails(workerSocket, productSeoName);
|
const productDetails = await fetchProductDetails(workerSocket, productSeoName);
|
||||||
|
|
||||||
const actualSeoName = productDetails.product.seoName || productSeoName;
|
const actualSeoName = productDetails.product.seoName || productSeoName;
|
||||||
const productComponent = React.createElement(PrerenderProduct, {
|
const productComponent = React.createElement(PrerenderProduct, {
|
||||||
productData: productDetails,
|
productData: productDetails,
|
||||||
@@ -205,7 +206,7 @@ const renderProductWorker = async (productSeoNames, workerId, progressCallback,
|
|||||||
}, shopConfig.baseUrl, shopConfig);
|
}, shopConfig.baseUrl, shopConfig);
|
||||||
// Get category info from categoryMap if available
|
// Get category info from categoryMap if available
|
||||||
const categoryInfo = productDetails.product.categoryId ? categoryMap[productDetails.product.categoryId] : null;
|
const categoryInfo = productDetails.product.categoryId ? categoryMap[productDetails.product.categoryId] : null;
|
||||||
|
|
||||||
const jsonLdScript = generateProductJsonLd({
|
const jsonLdScript = generateProductJsonLd({
|
||||||
...productDetails.product,
|
...productDetails.product,
|
||||||
seoName: actualSeoName,
|
seoName: actualSeoName,
|
||||||
@@ -234,9 +235,9 @@ const renderProductWorker = async (productSeoNames, workerId, progressCallback,
|
|||||||
success,
|
success,
|
||||||
workerId
|
workerId
|
||||||
};
|
};
|
||||||
|
|
||||||
results.push(result);
|
results.push(result);
|
||||||
|
|
||||||
// Call progress callback if provided
|
// Call progress callback if provided
|
||||||
if (progressCallback) {
|
if (progressCallback) {
|
||||||
progressCallback(result);
|
progressCallback(result);
|
||||||
@@ -252,14 +253,14 @@ const renderProductWorker = async (productSeoNames, workerId, progressCallback,
|
|||||||
error: error.message,
|
error: error.message,
|
||||||
workerId
|
workerId
|
||||||
};
|
};
|
||||||
|
|
||||||
results.push(result);
|
results.push(result);
|
||||||
|
|
||||||
// Call progress callback if provided
|
// Call progress callback if provided
|
||||||
if (progressCallback) {
|
if (progressCallback) {
|
||||||
progressCallback(result);
|
progressCallback(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(processNextProduct, 25);
|
setTimeout(processNextProduct, 25);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -291,16 +292,16 @@ const renderProductsInParallel = async (allProductsArray, maxWorkers, totalProdu
|
|||||||
const barLength = 30;
|
const barLength = 30;
|
||||||
const filledLength = Math.round((barLength * current) / total);
|
const filledLength = Math.round((barLength * current) / total);
|
||||||
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
|
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
|
||||||
|
|
||||||
// @note Single line progress update to prevent flickering
|
// @note Single line progress update to prevent flickering
|
||||||
const truncatedName = productName ? ` - ${productName.substring(0, 25)}${productName.length > 25 ? '...' : ''}` : '';
|
const truncatedName = productName ? ` - ${productName.substring(0, 25)}${productName.length > 25 ? '...' : ''}` : '';
|
||||||
|
|
||||||
// Build worker stats on one line
|
// Build worker stats on one line
|
||||||
let workerStats = '';
|
let workerStats = '';
|
||||||
for (let i = 0; i < Math.min(maxWorkers, 8); i++) { // Limit to 8 workers to fit on screen
|
for (let i = 0; i < Math.min(maxWorkers, 8); i++) { // Limit to 8 workers to fit on screen
|
||||||
workerStats += `W${i + 1}:${workerCounts[i]}/${workerSuccess[i]} `;
|
workerStats += `W${i + 1}:${workerCounts[i]}/${workerSuccess[i]} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single line update without complex cursor movements
|
// Single line update without complex cursor movements
|
||||||
process.stdout.write(`\r [${bar}] ${percentage}% (${current}/${total})${truncatedName}\n ${workerStats}${current < total ? '\x1b[1A' : '\n'}`);
|
process.stdout.write(`\r [${bar}] ${percentage}% (${current}/${total})${truncatedName}\n ${workerStats}${current < total ? '\x1b[1A' : '\n'}`);
|
||||||
};
|
};
|
||||||
@@ -308,26 +309,26 @@ const renderProductsInParallel = async (allProductsArray, maxWorkers, totalProdu
|
|||||||
// Split products among workers
|
// Split products among workers
|
||||||
const productsPerWorker = Math.ceil(allProductsArray.length / maxWorkers);
|
const productsPerWorker = Math.ceil(allProductsArray.length / maxWorkers);
|
||||||
const workerPromises = [];
|
const workerPromises = [];
|
||||||
|
|
||||||
// Initial progress bar
|
// Initial progress bar
|
||||||
updateProgressBar(0, totalProducts);
|
updateProgressBar(0, totalProducts);
|
||||||
|
|
||||||
for (let i = 0; i < maxWorkers; i++) {
|
for (let i = 0; i < maxWorkers; i++) {
|
||||||
const start = i * productsPerWorker;
|
const start = i * productsPerWorker;
|
||||||
const end = Math.min(start + productsPerWorker, allProductsArray.length);
|
const end = Math.min(start + productsPerWorker, allProductsArray.length);
|
||||||
const productsForWorker = allProductsArray.slice(start, end);
|
const productsForWorker = allProductsArray.slice(start, end);
|
||||||
|
|
||||||
if (productsForWorker.length > 0) {
|
if (productsForWorker.length > 0) {
|
||||||
const promise = renderProductWorker(productsForWorker, i + 1, (result) => {
|
const promise = renderProductWorker(productsForWorker, i + 1, (result) => {
|
||||||
// Progress callback - called each time a product is completed
|
// Progress callback - called each time a product is completed
|
||||||
completedProducts++;
|
completedProducts++;
|
||||||
progressResults.push(result);
|
progressResults.push(result);
|
||||||
lastProductName = result.productName;
|
lastProductName = result.productName;
|
||||||
|
|
||||||
// Update per-worker counters
|
// Update per-worker counters
|
||||||
const workerIndex = result.workerId - 1; // Convert to 0-based index
|
const workerIndex = result.workerId - 1; // Convert to 0-based index
|
||||||
workerCounts[workerIndex]++;
|
workerCounts[workerIndex]++;
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
totalSuccessCount++;
|
totalSuccessCount++;
|
||||||
workerSuccess[workerIndex]++;
|
workerSuccess[workerIndex]++;
|
||||||
@@ -335,11 +336,11 @@ const renderProductsInParallel = async (allProductsArray, maxWorkers, totalProdu
|
|||||||
// Don't log errors immediately to avoid interfering with progress bar
|
// Don't log errors immediately to avoid interfering with progress bar
|
||||||
// Errors will be shown after completion
|
// Errors will be shown after completion
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update progress bar with worker stats
|
// Update progress bar with worker stats
|
||||||
updateProgressBar(completedProducts, totalProducts, lastProductName);
|
updateProgressBar(completedProducts, totalProducts, lastProductName);
|
||||||
}, categoryMap);
|
}, categoryMap);
|
||||||
|
|
||||||
workerPromises.push(promise);
|
workerPromises.push(promise);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,10 +348,10 @@ const renderProductsInParallel = async (allProductsArray, maxWorkers, totalProdu
|
|||||||
try {
|
try {
|
||||||
// Wait for all workers to complete
|
// Wait for all workers to complete
|
||||||
await Promise.all(workerPromises);
|
await Promise.all(workerPromises);
|
||||||
|
|
||||||
// Ensure final progress update
|
// Ensure final progress update
|
||||||
updateProgressBar(totalProducts, totalProducts, lastProductName);
|
updateProgressBar(totalProducts, totalProducts, lastProductName);
|
||||||
|
|
||||||
// Show any errors that occurred
|
// Show any errors that occurred
|
||||||
const errorResults = progressResults.filter(r => !r.success && r.error);
|
const errorResults = progressResults.filter(r => !r.success && r.error);
|
||||||
if (errorResults.length > 0) {
|
if (errorResults.length > 0) {
|
||||||
@@ -359,7 +360,7 @@ const renderProductsInParallel = async (allProductsArray, maxWorkers, totalProdu
|
|||||||
console.log(` - ${result.productSeoName}: ${result.error}`);
|
console.log(` - ${result.productSeoName}: ${result.error}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalSuccessCount;
|
return totalSuccessCount;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in parallel rendering:', error);
|
console.error('Error in parallel rendering:', error);
|
||||||
@@ -422,6 +423,14 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy index.html to resetPassword (no file extension) for SPA routing
|
||||||
|
if (config.isProduction) {
|
||||||
|
const indexPath = path.resolve(__dirname, config.outputDir, "index.html");
|
||||||
|
const resetPasswordPath = path.resolve(__dirname, config.outputDir, "resetPassword");
|
||||||
|
fs.copyFileSync(indexPath, resetPasswordPath);
|
||||||
|
console.log(`✅ Copied index.html to ${resetPasswordPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Render static pages
|
// Render static pages
|
||||||
console.log("\n📄 Rendering static pages...");
|
console.log("\n📄 Rendering static pages...");
|
||||||
|
|
||||||
@@ -457,6 +466,13 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
description: "Sitemap page",
|
description: "Sitemap page",
|
||||||
needsCategoryData: true,
|
needsCategoryData: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: PrerenderCategoriesPage,
|
||||||
|
path: "/Kategorien",
|
||||||
|
filename: "Kategorien",
|
||||||
|
description: "Categories page",
|
||||||
|
needsCategoryData: true,
|
||||||
|
},
|
||||||
{ component: AGB, path: "/agb", filename: "agb", description: "AGB page" },
|
{ component: AGB, path: "/agb", filename: "agb", description: "AGB page" },
|
||||||
{ component: NotFound404, path: "/404", filename: "404", description: "404 Not Found page" },
|
{ component: NotFound404, path: "/404", filename: "404", description: "404 Not Found page" },
|
||||||
{
|
{
|
||||||
@@ -551,8 +567,7 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
try {
|
try {
|
||||||
productData = await fetchCategoryProducts(socket, category.id);
|
productData = await fetchCategoryProducts(socket, category.id);
|
||||||
console.log(
|
console.log(
|
||||||
` ✅ Found ${
|
` ✅ Found ${productData.products ? productData.products.length : 0
|
||||||
productData.products ? productData.products.length : 0
|
|
||||||
} products`
|
} products`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -636,7 +651,7 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
const totalProducts = allProducts.size;
|
const totalProducts = allProducts.size;
|
||||||
const numCPUs = os.cpus().length;
|
const numCPUs = os.cpus().length;
|
||||||
const maxWorkers = Math.min(numCPUs, totalProducts, 8); // Cap at 8 workers to avoid overwhelming the server
|
const maxWorkers = Math.min(numCPUs, totalProducts, 8); // Cap at 8 workers to avoid overwhelming the server
|
||||||
|
|
||||||
// Create category map for breadcrumbs
|
// Create category map for breadcrumbs
|
||||||
const categoryMap = {};
|
const categoryMap = {};
|
||||||
allCategories.forEach(category => {
|
allCategories.forEach(category => {
|
||||||
@@ -645,11 +660,11 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
seoName: category.seoName
|
seoName: category.seoName
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`\n📦 Rendering ${totalProducts} individual product pages using ${maxWorkers} parallel workers...`
|
`\n📦 Rendering ${totalProducts} individual product pages using ${maxWorkers} parallel workers...`
|
||||||
);
|
);
|
||||||
|
|
||||||
const productPagesRendered = await renderProductsInParallel(
|
const productPagesRendered = await renderProductsInParallel(
|
||||||
Array.from(allProducts),
|
Array.from(allProducts),
|
||||||
maxWorkers,
|
maxWorkers,
|
||||||
@@ -701,21 +716,21 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
// Generate products.xml (Google Shopping feed) in parallel to sitemap.xml
|
// Generate products.xml (Google Shopping feed) in parallel to sitemap.xml
|
||||||
if (allProductsData.length > 0) {
|
if (allProductsData.length > 0) {
|
||||||
console.log("\n🛒 Generating products.xml (Google Shopping feed)...");
|
console.log("\n🛒 Generating products.xml (Google Shopping feed)...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const productsXml = generateProductsXml(allProductsData, shopConfig.baseUrl, shopConfig);
|
const productsXml = generateProductsXml(allProductsData, shopConfig.baseUrl, shopConfig);
|
||||||
|
|
||||||
const productsXmlPath = path.resolve(__dirname, config.outputDir, "products.xml");
|
const productsXmlPath = path.resolve(__dirname, config.outputDir, "products.xml");
|
||||||
|
|
||||||
// Write with explicit UTF-8 encoding
|
// Write with explicit UTF-8 encoding
|
||||||
fs.writeFileSync(productsXmlPath, productsXml, { encoding: 'utf8' });
|
fs.writeFileSync(productsXmlPath, productsXml, { encoding: 'utf8' });
|
||||||
|
|
||||||
console.log(`✅ products.xml generated: ${productsXmlPath}`);
|
console.log(`✅ products.xml generated: ${productsXmlPath}`);
|
||||||
console.log(` - Products included: ${allProductsData.length}`);
|
console.log(` - Products included: ${allProductsData.length}`);
|
||||||
console.log(` - Format: Google Shopping RSS 2.0 feed`);
|
console.log(` - Format: Google Shopping RSS 2.0 feed`);
|
||||||
console.log(` - Encoding: UTF-8`);
|
console.log(` - Encoding: UTF-8`);
|
||||||
console.log(` - Includes: title, description, price, availability, images`);
|
console.log(` - Includes: title, description, price, availability, images`);
|
||||||
|
|
||||||
// Verify the file is valid UTF-8
|
// Verify the file is valid UTF-8
|
||||||
try {
|
try {
|
||||||
const verification = fs.readFileSync(productsXmlPath, 'utf8');
|
const verification = fs.readFileSync(productsXmlPath, 'utf8');
|
||||||
@@ -723,18 +738,18 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
} catch (verifyError) {
|
} catch (verifyError) {
|
||||||
console.log(` - File verification: ⚠️ ${verifyError.message}`);
|
console.log(` - File verification: ⚠️ ${verifyError.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate XML against Google Shopping schema
|
// Validate XML against Google Shopping schema
|
||||||
try {
|
try {
|
||||||
const ProductsXmlValidator = require('./scripts/validate-products-xml.cjs');
|
const ProductsXmlValidator = require('./scripts/validate-products-xml.cjs');
|
||||||
const validator = new ProductsXmlValidator(productsXmlPath);
|
const validator = new ProductsXmlValidator(productsXmlPath);
|
||||||
const validationResults = await validator.validate();
|
const validationResults = await validator.validate();
|
||||||
|
|
||||||
if (validationResults.valid) {
|
if (validationResults.valid) {
|
||||||
console.log(` - Schema validation: ✅ Valid Google Shopping RSS 2.0`);
|
console.log(` - Schema validation: ✅ Valid Google Shopping RSS 2.0`);
|
||||||
} else {
|
} else {
|
||||||
console.log(` - Schema validation: ⚠️ ${validationResults.summary.errorCount} errors, ${validationResults.summary.warningCount} warnings`);
|
console.log(` - Schema validation: ⚠️ ${validationResults.summary.errorCount} errors, ${validationResults.summary.warningCount} warnings`);
|
||||||
|
|
||||||
// Show first few errors for quick debugging
|
// Show first few errors for quick debugging
|
||||||
if (validationResults.errors.length > 0) {
|
if (validationResults.errors.length > 0) {
|
||||||
console.log(` - First error: ${validationResults.errors[0].message}`);
|
console.log(` - First error: ${validationResults.errors[0].message}`);
|
||||||
@@ -743,7 +758,7 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
} catch (validationError) {
|
} catch (validationError) {
|
||||||
console.log(` - Schema validation: ⚠️ Validation failed: ${validationError.message}`);
|
console.log(` - Schema validation: ⚠️ Validation failed: ${validationError.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`❌ Error generating products.xml: ${error.message}`);
|
console.error(`❌ Error generating products.xml: ${error.message}`);
|
||||||
console.log("\n⚠️ Skipping products.xml generation due to errors");
|
console.log("\n⚠️ Skipping products.xml generation due to errors");
|
||||||
@@ -754,18 +769,18 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
|
|
||||||
// Generate llms.txt (LLM-friendly markdown sitemap) and category-specific files
|
// Generate llms.txt (LLM-friendly markdown sitemap) and category-specific files
|
||||||
console.log("\n🤖 Generating LLM sitemap files...");
|
console.log("\n🤖 Generating LLM sitemap files...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Generate main llms.txt overview file
|
// Generate main llms.txt overview file
|
||||||
const llmsTxt = generateLlmsTxt(allCategories, allProductsData, shopConfig.baseUrl, shopConfig);
|
const llmsTxt = generateLlmsTxt(allCategories, allProductsData, shopConfig.baseUrl, shopConfig);
|
||||||
const llmsTxtPath = path.resolve(__dirname, config.outputDir, "llms.txt");
|
const llmsTxtPath = path.resolve(__dirname, config.outputDir, "llms.txt");
|
||||||
fs.writeFileSync(llmsTxtPath, llmsTxt, { encoding: 'utf8' });
|
fs.writeFileSync(llmsTxtPath, llmsTxt, { encoding: 'utf8' });
|
||||||
|
|
||||||
console.log(`✅ Main llms.txt generated: ${llmsTxtPath}`);
|
console.log(`✅ Main llms.txt generated: ${llmsTxtPath}`);
|
||||||
console.log(` - Static pages: 8 pages`);
|
console.log(` - Static pages: 8 pages`);
|
||||||
console.log(` - Categories: ${allCategories.length} with links to detailed files`);
|
console.log(` - Categories: ${allCategories.length} with links to detailed files`);
|
||||||
console.log(` - File size: ${Math.round(llmsTxt.length / 1024)}KB`);
|
console.log(` - File size: ${Math.round(llmsTxt.length / 1024)}KB`);
|
||||||
|
|
||||||
// Group products by category for category-specific files
|
// Group products by category for category-specific files
|
||||||
const productsByCategory = {};
|
const productsByCategory = {};
|
||||||
allProductsData.forEach((product) => {
|
allProductsData.forEach((product) => {
|
||||||
@@ -775,20 +790,20 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
}
|
}
|
||||||
productsByCategory[categoryId].push(product);
|
productsByCategory[categoryId].push(product);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Generate category-specific LLM files with pagination
|
// Generate category-specific LLM files with pagination
|
||||||
let categoryFilesGenerated = 0;
|
let categoryFilesGenerated = 0;
|
||||||
let totalCategoryProducts = 0;
|
let totalCategoryProducts = 0;
|
||||||
let totalPaginatedFiles = 0;
|
let totalPaginatedFiles = 0;
|
||||||
|
|
||||||
for (const category of allCategories) {
|
for (const category of allCategories) {
|
||||||
if (category.seoName) {
|
if (category.seoName) {
|
||||||
const categoryProducts = productsByCategory[category.id] || [];
|
const categoryProducts = productsByCategory[category.id] || [];
|
||||||
const categorySlug = category.seoName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
const categorySlug = category.seoName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
||||||
|
|
||||||
// Generate all paginated files for this category
|
// Generate all paginated files for this category
|
||||||
const categoryPages = generateAllCategoryLlmsPages(category, categoryProducts, shopConfig.baseUrl, shopConfig);
|
const categoryPages = generateAllCategoryLlmsPages(category, categoryProducts, shopConfig.baseUrl, shopConfig);
|
||||||
|
|
||||||
// Write each paginated file
|
// Write each paginated file
|
||||||
for (const page of categoryPages) {
|
for (const page of categoryPages) {
|
||||||
const pagePath = path.resolve(__dirname, config.outputDir, page.fileName);
|
const pagePath = path.resolve(__dirname, config.outputDir, page.fileName);
|
||||||
@@ -806,22 +821,22 @@ const renderApp = async (categoryData, socket) => {
|
|||||||
|
|
||||||
console.log(` ✅ llms-${categorySlug}-page-*.txt - ${categoryProducts.length} products across ${pageCount} pages (${Math.round(totalSize / 1024)}KB total)`);
|
console.log(` ✅ llms-${categorySlug}-page-*.txt - ${categoryProducts.length} products across ${pageCount} pages (${Math.round(totalSize / 1024)}KB total)`);
|
||||||
console.log(` 📋 ${productList.fileName} - ${productList.productCount} products (${Math.round(productList.content.length / 1024)}KB)`);
|
console.log(` 📋 ${productList.fileName} - ${productList.productCount} products (${Math.round(productList.content.length / 1024)}KB)`);
|
||||||
|
|
||||||
categoryFilesGenerated++;
|
categoryFilesGenerated++;
|
||||||
totalCategoryProducts += categoryProducts.length;
|
totalCategoryProducts += categoryProducts.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(` 📄 Total paginated files generated: ${totalPaginatedFiles}`);
|
console.log(` 📄 Total paginated files generated: ${totalPaginatedFiles}`);
|
||||||
console.log(` 📦 Total products across all categories: ${totalCategoryProducts}`);
|
console.log(` 📦 Total products across all categories: ${totalCategoryProducts}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const verification = fs.readFileSync(llmsTxtPath, 'utf8');
|
const verification = fs.readFileSync(llmsTxtPath, 'utf8');
|
||||||
console.log(` - File verification: ✅ All files valid UTF-8`);
|
console.log(` - File verification: ✅ All files valid UTF-8`);
|
||||||
} catch (verifyError) {
|
} catch (verifyError) {
|
||||||
console.log(` - File verification: ⚠️ ${verifyError.message}`);
|
console.log(` - File verification: ⚠️ ${verifyError.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`❌ Error generating LLM sitemap files: ${error.message}`);
|
console.error(`❌ Error generating LLM sitemap files: ${error.message}`);
|
||||||
console.log("\n⚠️ Skipping LLM sitemap generation due to errors");
|
console.log("\n⚠️ Skipping LLM sitemap generation due to errors");
|
||||||
@@ -841,7 +856,7 @@ const fetchCategoryDataAndRender = () => {
|
|||||||
|
|
||||||
const socket = io(socketUrl, {
|
const socket = io(socketUrl, {
|
||||||
path: "/socket.io/",
|
path: "/socket.io/",
|
||||||
transports: [ "websocket"],
|
transports: ["websocket"],
|
||||||
reconnection: false,
|
reconnection: false,
|
||||||
timeout: 10000,
|
timeout: 10000,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ const saveProductImages = async (socket, products, categoryName, outputDir) => {
|
|||||||
"public",
|
"public",
|
||||||
"assets",
|
"assets",
|
||||||
"images",
|
"images",
|
||||||
"sh.png"
|
"sh.avif"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure assets/images directory exists
|
// Ensure assets/images directory exists
|
||||||
@@ -185,7 +185,7 @@ const saveProductImages = async (socket, products, categoryName, outputDir) => {
|
|||||||
if (imageIds.length > 0) {
|
if (imageIds.length > 0) {
|
||||||
// Process first image for each product
|
// Process first image for each product
|
||||||
const bildId = parseInt(imageIds[0]);
|
const bildId = parseInt(imageIds[0]);
|
||||||
const estimatedFilename = `prod${bildId}.jpg`; // We'll generate a filename based on the ID
|
const estimatedFilename = `prod${bildId}.avif`; // We'll generate a filename based on the ID
|
||||||
|
|
||||||
const imagePath = path.join(assetsPath, estimatedFilename);
|
const imagePath = path.join(assetsPath, estimatedFilename);
|
||||||
|
|
||||||
@@ -231,12 +231,12 @@ const saveProductImages = async (socket, products, categoryName, outputDir) => {
|
|||||||
opacity: 0.3,
|
opacity: 0.3,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
.jpeg() // Ensure output is JPEG
|
.avif() // Ensure output is AVIF
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
|
|
||||||
fs.writeFileSync(imagePath, processedImageBuffer);
|
fs.writeFileSync(imagePath, processedImageBuffer);
|
||||||
console.log(
|
console.log(
|
||||||
` ✅ Applied centered inverted sh.png overlay to ${estimatedFilename}`
|
` ✅ Applied centered inverted sh.avif overlay to ${estimatedFilename}`
|
||||||
);
|
);
|
||||||
} catch (overlayError) {
|
} catch (overlayError) {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -281,7 +281,7 @@ const saveCategoryImages = async (socket, categories, outputDir) => {
|
|||||||
// Debug: Log categories that will be processed
|
// Debug: Log categories that will be processed
|
||||||
console.log(" 🔍 Categories to process:");
|
console.log(" 🔍 Categories to process:");
|
||||||
categories.forEach((cat, index) => {
|
categories.forEach((cat, index) => {
|
||||||
console.log(` ${index + 1}. "${cat.name}" (ID: ${cat.id}) -> cat${cat.id}.jpg`);
|
console.log(` ${index + 1}. "${cat.name}" (ID: ${cat.id}) -> cat${cat.id}.avif`);
|
||||||
});
|
});
|
||||||
|
|
||||||
const assetsPath = path.resolve(
|
const assetsPath = path.resolve(
|
||||||
@@ -308,7 +308,7 @@ const saveCategoryImages = async (socket, categories, outputDir) => {
|
|||||||
for (const category of categories) {
|
for (const category of categories) {
|
||||||
categoriesProcessed++;
|
categoriesProcessed++;
|
||||||
|
|
||||||
const estimatedFilename = `cat${category.id}.jpg`; // Use 'cat' prefix with category ID
|
const estimatedFilename = `cat${category.id}.avif`; // Use 'cat' prefix with category ID
|
||||||
const imagePath = path.join(assetsPath, estimatedFilename);
|
const imagePath = path.join(assetsPath, estimatedFilename);
|
||||||
|
|
||||||
// Skip if image already exists
|
// Skip if image already exists
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
||||||
|
// Category IDs to skip (seeds, plants, headshop items)
|
||||||
|
const skipCategoryIds = [689, 706, 709, 711, 714, 748, 749, 896, 710, 924, 923, 922, 921, 916, 278, 259, 258];
|
||||||
|
|
||||||
|
// Check if category ID is in skip list
|
||||||
|
if (category.id && skipCategoryIds.includes(parseInt(category.id))) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
const categoryUrl = `${baseUrl}/Kategorie/${category.seoName}`;
|
const categoryUrl = `${baseUrl}/Kategorie/${category.seoName}`;
|
||||||
|
|
||||||
|
// Calculate price valid date (current date + 3 months)
|
||||||
|
const priceValidDate = new Date();
|
||||||
|
priceValidDate.setMonth(priceValidDate.getMonth() + 3);
|
||||||
|
const priceValidUntil = priceValidDate.toISOString().split("T")[0];
|
||||||
|
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
"@context": "https://schema.org/",
|
"@context": "https://schema.org/",
|
||||||
"@type": "CollectionPage",
|
"@type": "CollectionPage",
|
||||||
@@ -42,7 +55,7 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
|||||||
product.pictureList && product.pictureList.trim()
|
product.pictureList && product.pictureList.trim()
|
||||||
? `${baseUrl}/assets/images/prod${product.pictureList
|
? `${baseUrl}/assets/images/prod${product.pictureList
|
||||||
.split(",")[0]
|
.split(",")[0]
|
||||||
.trim()}.jpg`
|
.trim()}.avif`
|
||||||
: `${baseUrl}/assets/images/nopicture.jpg`,
|
: `${baseUrl}/assets/images/nopicture.jpg`,
|
||||||
description: product.description
|
description: product.description
|
||||||
? product.description.replace(/<[^>]*>/g, "").substring(0, 200)
|
? product.description.replace(/<[^>]*>/g, "").substring(0, 200)
|
||||||
@@ -57,6 +70,7 @@ const generateCategoryJsonLd = (category, products = [], baseUrl, config) => {
|
|||||||
url: `${baseUrl}/Artikel/${product.seoName}`,
|
url: `${baseUrl}/Artikel/${product.seoName}`,
|
||||||
price: product.price && !isNaN(product.price) ? product.price.toString() : "0.00",
|
price: product.price && !isNaN(product.price) ? product.price.toString() : "0.00",
|
||||||
priceCurrency: config.currency,
|
priceCurrency: config.currency,
|
||||||
|
priceValidUntil: priceValidUntil,
|
||||||
availability: product.available
|
availability: product.available
|
||||||
? "https://schema.org/InStock"
|
? "https://schema.org/InStock"
|
||||||
: "https://schema.org/OutOfStock",
|
: "https://schema.org/OutOfStock",
|
||||||
|
|||||||
@@ -535,7 +535,7 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => {
|
|||||||
|
|
||||||
// Generate image URL
|
// Generate image URL
|
||||||
const imageUrl = product.pictureList && product.pictureList.trim()
|
const imageUrl = product.pictureList && product.pictureList.trim()
|
||||||
? `${baseUrl}/assets/images/prod${product.pictureList.split(",")[0].trim()}.jpg`
|
? `${baseUrl}/assets/images/prod${product.pictureList.split(",")[0].trim()}.avif`
|
||||||
: `${baseUrl}/assets/images/nopicture.jpg`;
|
: `${baseUrl}/assets/images/nopicture.jpg`;
|
||||||
|
|
||||||
// Generate brand (manufacturer)
|
// Generate brand (manufacturer)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const generateProductMetaTags = (product, baseUrl, config) => {
|
|||||||
product.pictureList && product.pictureList.trim()
|
product.pictureList && product.pictureList.trim()
|
||||||
? `${baseUrl}/assets/images/prod${product.pictureList
|
? `${baseUrl}/assets/images/prod${product.pictureList
|
||||||
.split(",")[0]
|
.split(",")[0]
|
||||||
.trim()}.jpg`
|
.trim()}.avif`
|
||||||
: `${baseUrl}/assets/images/nopicture.jpg`;
|
: `${baseUrl}/assets/images/nopicture.jpg`;
|
||||||
|
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ const generateProductJsonLd = (product, baseUrl, config, categoryInfo = null) =>
|
|||||||
product.pictureList && product.pictureList.trim()
|
product.pictureList && product.pictureList.trim()
|
||||||
? `${baseUrl}/assets/images/prod${product.pictureList
|
? `${baseUrl}/assets/images/prod${product.pictureList
|
||||||
.split(",")[0]
|
.split(",")[0]
|
||||||
.trim()}.jpg`
|
.trim()}.avif`
|
||||||
: `${baseUrl}/assets/images/nopicture.jpg`;
|
: `${baseUrl}/assets/images/nopicture.jpg`;
|
||||||
|
|
||||||
// Clean description for JSON-LD (remove HTML tags)
|
// Clean description for JSON-LD (remove HTML tags)
|
||||||
|
|||||||
BIN
public/assets/images/cutlings.avif
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
public/assets/images/gg.avif
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
public/assets/images/konfigurator.avif
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
public/assets/images/konfigurator.png
Normal file
|
After Width: | Height: | Size: 362 KiB |
BIN
public/assets/images/maps.avif
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/assets/images/seeds.avif
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/assets/images/sh.avif
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
61
scripts/convert-images-to-avif.cjs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
const sharp = require('sharp');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const imagesToConvert = [
|
||||||
|
{ src: 'sh.png', dest: 'sh.avif' },
|
||||||
|
{ src: 'seeds.jpg', dest: 'seeds.avif' },
|
||||||
|
{ src: 'cutlings.jpg', dest: 'cutlings.avif' },
|
||||||
|
{ src: 'gg.png', dest: 'gg.avif' },
|
||||||
|
{ src: 'konfigurator.png', dest: 'konfigurator.avif' },
|
||||||
|
{ src: 'maps.png', dest: 'maps.avif' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
const imagesDir = path.join(__dirname, '../public/assets/images');
|
||||||
|
let hasError = false;
|
||||||
|
|
||||||
|
for (const image of imagesToConvert) {
|
||||||
|
const inputPath = path.join(imagesDir, image.src);
|
||||||
|
const outputPath = path.join(imagesDir, image.dest);
|
||||||
|
|
||||||
|
console.log('d');
|
||||||
|
if (!fs.existsSync(inputPath)) {
|
||||||
|
console.warn(`⚠️ Input file not found: ${inputPath}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if output file exists and compare modification times
|
||||||
|
// Only convert if source is newer or destination doesn't exist
|
||||||
|
let shouldConvert = true;
|
||||||
|
if (fs.existsSync(outputPath)) {
|
||||||
|
const inputStat = fs.statSync(inputPath);
|
||||||
|
const outputStat = fs.statSync(outputPath);
|
||||||
|
if (inputStat.mtime <= outputStat.mtime) {
|
||||||
|
shouldConvert = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldConvert) {
|
||||||
|
try {
|
||||||
|
await sharp(inputPath)
|
||||||
|
.toFormat('avif')
|
||||||
|
.toFile(outputPath);
|
||||||
|
console.log(`✅ Converted ${image.src} to ${image.dest}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Error converting ${image.src}:`, error.message);
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Silent skip if already up to date to keep logs clean, or use verbose flag
|
||||||
|
// console.log(`Skipping ${image.src} (already up to date)`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('dsfs');
|
||||||
|
run();
|
||||||
26
scripts/convert-logo-to-avif.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
const sharp = require('sharp');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const run = async () => {
|
||||||
|
const inputPath = path.join(__dirname, '../public/assets/images/sh.png');
|
||||||
|
const outputPath = path.join(__dirname, '../public/assets/images/sh.avif');
|
||||||
|
|
||||||
|
if (!fs.existsSync(inputPath)) {
|
||||||
|
console.error('Input file not found:', inputPath);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sharp(inputPath)
|
||||||
|
.toFormat('avif')
|
||||||
|
.toFile(outputPath);
|
||||||
|
console.log(`Successfully converted ${inputPath} to ${outputPath}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error converting image:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
32
src/App.js
@@ -50,6 +50,7 @@ const Datenschutz = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/D
|
|||||||
const AGB = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/AGB.js"));
|
const AGB = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/AGB.js"));
|
||||||
//const NotFound404 = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/NotFound404.js")); <Route path="/404" element={<NotFound404 />} />
|
//const NotFound404 = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/NotFound404.js")); <Route path="/404" element={<NotFound404 />} />
|
||||||
const Sitemap = lazy(() => import(/* webpackChunkName: "sitemap" */ "./pages/Sitemap.js"));
|
const Sitemap = lazy(() => import(/* webpackChunkName: "sitemap" */ "./pages/Sitemap.js"));
|
||||||
|
const CategoriesPage = lazy(() => import(/* webpackChunkName: "categories" */ "./pages/CategoriesPage.js"));
|
||||||
const Impressum = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Impressum.js"));
|
const Impressum = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Impressum.js"));
|
||||||
const Batteriegesetzhinweise = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Batteriegesetzhinweise.js"));
|
const Batteriegesetzhinweise = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Batteriegesetzhinweise.js"));
|
||||||
const Widerrufsrecht = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Widerrufsrecht.js"));
|
const Widerrufsrecht = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Widerrufsrecht.js"));
|
||||||
@@ -228,7 +229,7 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
|
|||||||
<TitleUpdater />
|
<TitleUpdater />
|
||||||
<ScrollToTop />
|
<ScrollToTop />
|
||||||
<Header active categoryId={categoryId} key={authVersion} />
|
<Header active categoryId={categoryId} key={authVersion} />
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box component="main" sx={{ flexGrow: 1 }}>
|
||||||
<Suspense fallback={
|
<Suspense fallback={
|
||||||
// Use prerender fallback if available, otherwise show loading spinner
|
// Use prerender fallback if available, otherwise show loading spinner
|
||||||
typeof window !== "undefined" && window.__PRERENDER_FALLBACK__ ? (
|
typeof window !== "undefined" && window.__PRERENDER_FALLBACK__ ? (
|
||||||
@@ -260,19 +261,19 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
|
|||||||
{/* Category page - Render Content in parallel */}
|
{/* Category page - Render Content in parallel */}
|
||||||
<Route
|
<Route
|
||||||
path="/Kategorie/:categoryId"
|
path="/Kategorie/:categoryId"
|
||||||
element={<Content/>}
|
element={<Content />}
|
||||||
/>
|
/>
|
||||||
{/* Single product page */}
|
{/* Single product page */}
|
||||||
<Route
|
<Route
|
||||||
path="/Artikel/:seoName"
|
path="/Artikel/:seoName"
|
||||||
element={<ProductDetail/>}
|
element={<ProductDetail />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Search page - Render Content in parallel */}
|
{/* Search page - Render Content in parallel */}
|
||||||
<Route path="/search" element={<Content/>} />
|
<Route path="/search" element={<Content />} />
|
||||||
|
|
||||||
{/* Profile page */}
|
{/* Profile page */}
|
||||||
<Route path="/profile" element={<ProfilePage/>} />
|
<Route path="/profile" element={<ProfilePage />} />
|
||||||
|
|
||||||
{/* Payment success page for Mollie redirects */}
|
{/* Payment success page for Mollie redirects */}
|
||||||
<Route path="/payment/success" element={<PaymentSuccess />} />
|
<Route path="/payment/success" element={<PaymentSuccess />} />
|
||||||
@@ -280,22 +281,23 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
|
|||||||
{/* Reset password page */}
|
{/* Reset password page */}
|
||||||
<Route
|
<Route
|
||||||
path="/resetPassword"
|
path="/resetPassword"
|
||||||
element={<ResetPassword/>}
|
element={<ResetPassword />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Admin page */}
|
{/* Admin page */}
|
||||||
<Route path="/admin" element={<AdminPage/>} />
|
<Route path="/admin" element={<AdminPage />} />
|
||||||
|
|
||||||
{/* Admin Users page */}
|
{/* Admin Users page */}
|
||||||
<Route path="/admin/users" element={<UsersPage/>} />
|
<Route path="/admin/users" element={<UsersPage />} />
|
||||||
|
|
||||||
{/* Admin Server Logs page */}
|
{/* Admin Server Logs page */}
|
||||||
<Route path="/admin/logs" element={<ServerLogsPage/>} />
|
<Route path="/admin/logs" element={<ServerLogsPage />} />
|
||||||
|
|
||||||
{/* Legal pages */}
|
{/* Legal pages */}
|
||||||
<Route path="/datenschutz" element={<Datenschutz />} />
|
<Route path="/datenschutz" element={<Datenschutz />} />
|
||||||
<Route path="/agb" element={<AGB />} />
|
<Route path="/agb" element={<AGB />} />
|
||||||
<Route path="/sitemap" element={<Sitemap />} />
|
<Route path="/sitemap" element={<Sitemap />} />
|
||||||
|
<Route path="/Kategorien" element={<CategoriesPage />} />
|
||||||
<Route path="/impressum" element={<Impressum />} />
|
<Route path="/impressum" element={<Impressum />} />
|
||||||
<Route
|
<Route
|
||||||
path="/batteriegesetzhinweise"
|
path="/batteriegesetzhinweise"
|
||||||
@@ -304,7 +306,7 @@ const AppContent = ({ currentTheme, dynamicTheme, onThemeChange }) => {
|
|||||||
<Route path="/widerrufsrecht" element={<Widerrufsrecht />} />
|
<Route path="/widerrufsrecht" element={<Widerrufsrecht />} />
|
||||||
|
|
||||||
{/* Grow Tent Configurator */}
|
{/* Grow Tent Configurator */}
|
||||||
<Route path="/Konfigurator" element={<GrowTentKonfigurator/>} />
|
<Route path="/Konfigurator" element={<GrowTentKonfigurator />} />
|
||||||
|
|
||||||
{/* Separate pages that are truly different */}
|
{/* Separate pages that are truly different */}
|
||||||
<Route path="/presseverleih" element={<PresseverleihPage />} />
|
<Route path="/presseverleih" element={<PresseverleihPage />} />
|
||||||
@@ -457,11 +459,11 @@ const App = () => {
|
|||||||
<ProductContextProvider>
|
<ProductContextProvider>
|
||||||
<CategoryContextProvider>
|
<CategoryContextProvider>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<AppContent
|
<AppContent
|
||||||
currentTheme={currentTheme}
|
currentTheme={currentTheme}
|
||||||
dynamicTheme={dynamicTheme}
|
dynamicTheme={dynamicTheme}
|
||||||
onThemeChange={handleThemeChange}
|
onThemeChange={handleThemeChange}
|
||||||
/>
|
/>
|
||||||
</CategoryContextProvider>
|
</CategoryContextProvider>
|
||||||
</ProductContextProvider>
|
</ProductContextProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const PrerenderAppContent = (socket) => (
|
|||||||
<CategoryList categoryId={209} activeCategoryId={null} socket={socket}/>
|
<CategoryList categoryId={209} activeCategoryId={null} socket={socket}/>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box component="main" sx={{ flexGrow: 1 }}>
|
||||||
<CarouselProvider>
|
<CarouselProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<MainPageLayout />} />
|
<Route path="/" element={<MainPageLayout />} />
|
||||||
|
|||||||
118
src/PrerenderCategoriesPage.js
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import LegalPage from './pages/LegalPage.js';
|
||||||
|
import CategoryBox from './components/CategoryBox.js';
|
||||||
|
|
||||||
|
const PrerenderCategoriesPage = ({ categoryData }) => {
|
||||||
|
// Helper function to recursively collect all categories from the tree
|
||||||
|
const collectAllCategories = (categoryNode, categories = [], level = 0) => {
|
||||||
|
if (!categoryNode) return categories;
|
||||||
|
|
||||||
|
// Add current category (skip root category 209)
|
||||||
|
if (categoryNode.id !== 209 && categoryNode.seoName) {
|
||||||
|
categories.push({
|
||||||
|
id: categoryNode.id,
|
||||||
|
name: categoryNode.name,
|
||||||
|
seoName: categoryNode.seoName,
|
||||||
|
level: level
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively add children
|
||||||
|
if (categoryNode.children) {
|
||||||
|
for (const child of categoryNode.children) {
|
||||||
|
collectAllCategories(child, categories, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return categories;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The categoryData passed prop is the root tree (id: 209)
|
||||||
|
const rootTree = categoryData;
|
||||||
|
|
||||||
|
const renderLevel1Section = (l1Node) => {
|
||||||
|
// Collect all descendants (excluding the L1 node itself, which collectAllCategories would include first)
|
||||||
|
const descendants = collectAllCategories(l1Node).slice(1);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
key={l1Node.id}
|
||||||
|
elevation={1}
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
mb: 3,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { xs: 'column', md: 'row' },
|
||||||
|
alignItems: { xs: 'flex-start', md: 'flex-start' },
|
||||||
|
gap: 3
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Level 1 Header/Box */}
|
||||||
|
<Box sx={{
|
||||||
|
minWidth: '150px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 1
|
||||||
|
}}>
|
||||||
|
<CategoryBox
|
||||||
|
id={l1Node.id}
|
||||||
|
name={l1Node.name}
|
||||||
|
seoName={l1Node.seoName}
|
||||||
|
sx={{
|
||||||
|
boxShadow: 4,
|
||||||
|
width: '150px',
|
||||||
|
height: '150px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Descendants area */}
|
||||||
|
<Box sx={{ flex: 1 }}>
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 2
|
||||||
|
}}>
|
||||||
|
{descendants.map((cat) => (
|
||||||
|
<CategoryBox
|
||||||
|
key={cat.id}
|
||||||
|
id={cat.id}
|
||||||
|
name={cat.name}
|
||||||
|
seoName={cat.seoName}
|
||||||
|
sx={{
|
||||||
|
width: '100px',
|
||||||
|
height: '100px',
|
||||||
|
minWidth: '100px',
|
||||||
|
minHeight: '100px',
|
||||||
|
boxShadow: 1,
|
||||||
|
fontSize: '0.9rem'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = (
|
||||||
|
<Box>
|
||||||
|
<Box>
|
||||||
|
{rootTree && rootTree.children && rootTree.children.map((child) => (
|
||||||
|
renderLevel1Section(child)
|
||||||
|
))}
|
||||||
|
{(!rootTree || !rootTree.children || rootTree.children.length === 0) && (
|
||||||
|
<Typography>Keine Kategorien gefunden.</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
return <LegalPage title="Kategorien" content={content} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PrerenderCategoriesPage;
|
||||||
@@ -111,7 +111,7 @@ const PrerenderCategory = ({ categoryId, categoryName, categorySeoName: _categor
|
|||||||
component="img"
|
component="img"
|
||||||
height="200"
|
height="200"
|
||||||
image={product.pictureList && product.pictureList.trim()
|
image={product.pictureList && product.pictureList.trim()
|
||||||
? `/assets/images/prod${product.pictureList.split(',')[0].trim()}.jpg`
|
? `/assets/images/prod${product.pictureList.split(',')[0].trim()}.avif`
|
||||||
: '/assets/images/nopicture.jpg'
|
: '/assets/images/nopicture.jpg'
|
||||||
}
|
}
|
||||||
alt={product.name}
|
alt={product.name}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class ArticleAvailabilityForm extends Component {
|
|||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: response.error || 'Ein Fehler ist aufgetreten'
|
error: response.error || this.props.t("productDialogs.errorGeneric")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,20 +114,21 @@ class ArticleAvailabilityForm extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { name, email, telegramId, notificationMethod, message, loading, success, error } = this.state;
|
const { name, email, telegramId, notificationMethod, message, loading, success, error } = this.state;
|
||||||
|
const { t } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper id="availability-form" sx={{ p: 3, mt: 4, borderRadius: 2, boxShadow: "0 2px 8px rgba(0,0,0,0.08)" }}>
|
<Paper id="availability-form" sx={{ p: 3, mt: 4, borderRadius: 2, boxShadow: "0 2px 8px rgba(0,0,0,0.08)" }}>
|
||||||
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600, color: '#333' }}>
|
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600, color: '#333' }}>
|
||||||
Verfügbarkeit anfragen
|
{t("productDialogs.availabilityTitle")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
Dieser Artikel ist derzeit nicht verfügbar. Gerne informieren wir Sie, sobald er wieder lieferbar ist.
|
{t("productDialogs.availabilitySubtitle")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{success && (
|
{success && (
|
||||||
<Alert severity="success" sx={{ mb: 3 }}>
|
<Alert severity="success" sx={{ mb: 3 }}>
|
||||||
Vielen Dank für Ihre Anfrage! Wir werden Sie {notificationMethod === 'email' ? 'per E-Mail' : 'über Telegram'} informieren, sobald der Artikel wieder verfügbar ist.
|
{notificationMethod === 'email' ? t("productDialogs.availabilitySuccessEmail") : t("productDialogs.availabilitySuccessTelegram")}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -139,18 +140,18 @@ class ArticleAvailabilityForm extends Component {
|
|||||||
|
|
||||||
<Box component="form" onSubmit={this.handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
<Box component="form" onSubmit={this.handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Name"
|
label={t("productDialogs.nameLabel")}
|
||||||
value={name}
|
value={name}
|
||||||
onChange={this.handleInputChange('name')}
|
onChange={this.handleInputChange('name')}
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="Ihr Name"
|
placeholder={t("productDialogs.namePlaceholder")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl component="fieldset" disabled={loading}>
|
<FormControl component="fieldset" disabled={loading}>
|
||||||
<FormLabel component="legend" sx={{ mb: 1 }}>
|
<FormLabel component="legend" sx={{ mb: 1 }}>
|
||||||
Wie möchten Sie benachrichtigt werden?
|
{t("productDialogs.notificationMethodLabel")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={notificationMethod}
|
value={notificationMethod}
|
||||||
@@ -160,51 +161,51 @@ class ArticleAvailabilityForm extends Component {
|
|||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="email"
|
value="email"
|
||||||
control={<Radio />}
|
control={<Radio />}
|
||||||
label="E-Mail"
|
label={t("productDialogs.emailLabel")}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="telegram"
|
value="telegram"
|
||||||
control={<Radio />}
|
control={<Radio />}
|
||||||
label="Telegram Bot"
|
label={t("productDialogs.telegramBotLabel")}
|
||||||
/>
|
/>
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
{notificationMethod === 'email' && (
|
{notificationMethod === 'email' && (
|
||||||
<TextField
|
<TextField
|
||||||
label="E-Mail"
|
label={t("productDialogs.emailLabel")}
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={this.handleInputChange('email')}
|
onChange={this.handleInputChange('email')}
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="ihre.email@example.com"
|
placeholder={t("productDialogs.emailPlaceholder")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{notificationMethod === 'telegram' && (
|
{notificationMethod === 'telegram' && (
|
||||||
<TextField
|
<TextField
|
||||||
label="Telegram ID"
|
label={t("productDialogs.telegramIdLabel")}
|
||||||
value={telegramId}
|
value={telegramId}
|
||||||
onChange={this.handleInputChange('telegramId')}
|
onChange={this.handleInputChange('telegramId')}
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="@IhrTelegramName oder Telegram ID"
|
placeholder={t("productDialogs.telegramPlaceholder")}
|
||||||
helperText="Geben Sie Ihren Telegram-Benutzernamen (mit @) oder Ihre Telegram-ID ein"
|
helperText={t("productDialogs.telegramHelper")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Nachricht (optional)"
|
label={t("productDialogs.messageLabel")}
|
||||||
value={message}
|
value={message}
|
||||||
onChange={this.handleInputChange('message')}
|
onChange={this.handleInputChange('message')}
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
rows={3}
|
rows={3}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="Zusätzliche Informationen oder Fragen..."
|
placeholder={t("productDialogs.messagePlaceholder")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -225,10 +226,10 @@ class ArticleAvailabilityForm extends Component {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<>
|
<>
|
||||||
<CircularProgress size={20} sx={{ mr: 1 }} />
|
<CircularProgress size={20} sx={{ mr: 1 }} />
|
||||||
Wird gesendet...
|
{t("productDialogs.sending")}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'Verfügbarkeit anfragen'
|
t("productDialogs.submitAvailability")
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class ArticleQuestionForm extends Component {
|
|||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: response.error || 'Ein Fehler ist aufgetreten'
|
error: response.error || this.props.t("productDialogs.errorGeneric")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ class ArticleQuestionForm extends Component {
|
|||||||
} catch {
|
} catch {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: 'Fehler beim Verarbeiten der Fotos'
|
error: this.props.t("productDialogs.errorPhotos")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,20 +140,21 @@ class ArticleQuestionForm extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { name, email, question, loading, success, error } = this.state;
|
const { name, email, question, loading, success, error } = this.state;
|
||||||
|
const { t } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper sx={{ p: 3, mt: 4, borderRadius: 2, boxShadow: "0 2px 8px rgba(0,0,0,0.08)" }}>
|
<Paper sx={{ p: 3, mt: 4, borderRadius: 2, boxShadow: "0 2px 8px rgba(0,0,0,0.08)" }}>
|
||||||
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600, color: '#333' }}>
|
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600, color: '#333' }}>
|
||||||
Frage zum Artikel
|
{t("productDialogs.questionTitle")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
Haben Sie eine Frage zu diesem Artikel? Wir helfen Ihnen gerne weiter.
|
{t("productDialogs.questionSubtitle")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{success && (
|
{success && (
|
||||||
<Alert severity="success" sx={{ mb: 3 }}>
|
<Alert severity="success" sx={{ mb: 3 }}>
|
||||||
Vielen Dank für Ihre Frage! Wir werden uns schnellstmöglich bei Ihnen melden.
|
{t("productDialogs.questionSuccess")}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -165,28 +166,28 @@ class ArticleQuestionForm extends Component {
|
|||||||
|
|
||||||
<Box component="form" onSubmit={this.handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
<Box component="form" onSubmit={this.handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Name"
|
label={t("productDialogs.nameLabel")}
|
||||||
value={name}
|
value={name}
|
||||||
onChange={this.handleInputChange('name')}
|
onChange={this.handleInputChange('name')}
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="Ihr Name"
|
placeholder={t("productDialogs.namePlaceholder")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="E-Mail"
|
label={t("productDialogs.emailLabel")}
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={this.handleInputChange('email')}
|
onChange={this.handleInputChange('email')}
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="ihre.email@example.com"
|
placeholder={t("productDialogs.emailPlaceholder")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Ihre Frage"
|
label={t("productDialogs.questionLabel")}
|
||||||
value={question}
|
value={question}
|
||||||
onChange={this.handleInputChange('question')}
|
onChange={this.handleInputChange('question')}
|
||||||
required
|
required
|
||||||
@@ -194,7 +195,7 @@ class ArticleQuestionForm extends Component {
|
|||||||
multiline
|
multiline
|
||||||
rows={4}
|
rows={4}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="Beschreiben Sie Ihre Frage zu diesem Artikel..."
|
placeholder={t("productDialogs.questionPlaceholder")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PhotoUpload
|
<PhotoUpload
|
||||||
@@ -202,7 +203,7 @@ class ArticleQuestionForm extends Component {
|
|||||||
onChange={this.handlePhotosChange}
|
onChange={this.handlePhotosChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
maxFiles={3}
|
maxFiles={3}
|
||||||
label="Fotos zur Frage anhängen (optional)"
|
label={t("productDialogs.photosLabelQuestion")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -219,10 +220,10 @@ class ArticleQuestionForm extends Component {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<>
|
<>
|
||||||
<CircularProgress size={20} sx={{ mr: 1 }} />
|
<CircularProgress size={20} sx={{ mr: 1 }} />
|
||||||
Wird gesendet...
|
{t("productDialogs.sending")}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'Frage senden'
|
t("productDialogs.submitQuestion")
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ class ArticleRatingForm extends Component {
|
|||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: response.error || 'Ein Fehler ist aufgetreten'
|
error: response.error || this.props.t("productDialogs.errorGeneric")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ class ArticleRatingForm extends Component {
|
|||||||
} catch {
|
} catch {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: 'Fehler beim Verarbeiten der Fotos'
|
error: this.props.t("productDialogs.errorPhotos")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,20 +149,21 @@ class ArticleRatingForm extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { name, email, rating, review, loading, success, error } = this.state;
|
const { name, email, rating, review, loading, success, error } = this.state;
|
||||||
|
const { t } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper sx={{ p: 3, mt: 4, borderRadius: 2, boxShadow: "0 2px 8px rgba(0,0,0,0.08)" }}>
|
<Paper sx={{ p: 3, mt: 4, borderRadius: 2, boxShadow: "0 2px 8px rgba(0,0,0,0.08)" }}>
|
||||||
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600, color: '#333' }}>
|
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600, color: '#333' }}>
|
||||||
Artikel Bewerten
|
{t("productDialogs.ratingTitle")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
Teilen Sie Ihre Erfahrungen mit diesem Artikel und helfen Sie anderen Kunden bei der Entscheidung.
|
{t("productDialogs.ratingSubtitle")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{success && (
|
{success && (
|
||||||
<Alert severity="success" sx={{ mb: 3 }}>
|
<Alert severity="success" sx={{ mb: 3 }}>
|
||||||
Vielen Dank für Ihre Bewertung! Sie wird nach Prüfung veröffentlicht.
|
{t("productDialogs.ratingSuccess")}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -174,30 +175,30 @@ class ArticleRatingForm extends Component {
|
|||||||
|
|
||||||
<Box component="form" onSubmit={this.handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
<Box component="form" onSubmit={this.handleSubmit} sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||||
<TextField
|
<TextField
|
||||||
label="Name"
|
label={t("productDialogs.nameLabel")}
|
||||||
value={name}
|
value={name}
|
||||||
onChange={this.handleInputChange('name')}
|
onChange={this.handleInputChange('name')}
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="Ihr Name"
|
placeholder={t("productDialogs.namePlaceholder")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="E-Mail"
|
label={t("productDialogs.emailLabel")}
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={this.handleInputChange('email')}
|
onChange={this.handleInputChange('email')}
|
||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="ihre.email@example.com"
|
placeholder={t("productDialogs.emailPlaceholder")}
|
||||||
helperText="Ihre E-Mail wird nicht veröffentlicht"
|
helperText={t("productDialogs.emailHelper")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||||
Bewertung *
|
{t("productDialogs.ratingLabel")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<Rating
|
<Rating
|
||||||
@@ -209,20 +210,20 @@ class ArticleRatingForm extends Component {
|
|||||||
emptyIcon={<StarIcon style={{ opacity: 0.55 }} fontSize="inherit" />}
|
emptyIcon={<StarIcon style={{ opacity: 0.55 }} fontSize="inherit" />}
|
||||||
/>
|
/>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
{rating > 0 ? `${rating} von 5 Sternen` : 'Bitte bewerten'}
|
{rating > 0 ? t("productDialogs.ratingStars", { rating }) : t("productDialogs.pleaseRate")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
label="Ihre Bewertung (optional)"
|
label={t("productDialogs.reviewLabel")}
|
||||||
value={review}
|
value={review}
|
||||||
onChange={this.handleInputChange('review')}
|
onChange={this.handleInputChange('review')}
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
rows={4}
|
rows={4}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
placeholder="Beschreiben Sie Ihre Erfahrungen mit diesem Artikel..."
|
placeholder={t("productDialogs.reviewPlaceholder")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PhotoUpload
|
<PhotoUpload
|
||||||
@@ -230,7 +231,7 @@ class ArticleRatingForm extends Component {
|
|||||||
onChange={this.handlePhotosChange}
|
onChange={this.handlePhotosChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
maxFiles={5}
|
maxFiles={5}
|
||||||
label="Fotos zur Bewertung anhängen (optional)"
|
label={t("productDialogs.photosLabelRating")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -247,10 +248,10 @@ class ArticleRatingForm extends Component {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<>
|
<>
|
||||||
<CircularProgress size={20} sx={{ mr: 1 }} />
|
<CircularProgress size={20} sx={{ mr: 1 }} />
|
||||||
Wird gesendet...
|
{t("productDialogs.sending")}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'Bewertung abgeben'
|
t("productDialogs.submitRating")
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class CartItem extends Component {
|
|||||||
|
|
||||||
window.socketManager.emit('getPic', { bildId:picid, size:'tiny' }, (res) => {
|
window.socketManager.emit('getPic', { bildId:picid, size:'tiny' }, (res) => {
|
||||||
if(res.success){
|
if(res.success){
|
||||||
window.tinyPicCache[picid] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
window.tinyPicCache[picid] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/avif' }));
|
||||||
this.setState({image: window.tinyPicCache[picid], loading: false});
|
this.setState({image: window.tinyPicCache[picid], loading: false});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const CategoryBox = ({
|
|||||||
// Create fresh blob URL from cached binary data
|
// Create fresh blob URL from cached binary data
|
||||||
try {
|
try {
|
||||||
const uint8Array = new Uint8Array(cachedImageData);
|
const uint8Array = new Uint8Array(cachedImageData);
|
||||||
const blob = new Blob([uint8Array], { type: 'image/jpeg' });
|
const blob = new Blob([uint8Array], { type: 'image/avif' });
|
||||||
objectUrl = URL.createObjectURL(blob);
|
objectUrl = URL.createObjectURL(blob);
|
||||||
setImageUrl(objectUrl);
|
setImageUrl(objectUrl);
|
||||||
setImageError(false);
|
setImageError(false);
|
||||||
@@ -73,7 +73,7 @@ const CategoryBox = ({
|
|||||||
try {
|
try {
|
||||||
// Convert binary data to blob URL
|
// Convert binary data to blob URL
|
||||||
const uint8Array = new Uint8Array(imageData);
|
const uint8Array = new Uint8Array(imageData);
|
||||||
const blob = new Blob([uint8Array], { type: 'image/jpeg' });
|
const blob = new Blob([uint8Array], { type: 'image/avif' });
|
||||||
objectUrl = URL.createObjectURL(blob);
|
objectUrl = URL.createObjectURL(blob);
|
||||||
setImageUrl(objectUrl);
|
setImageUrl(objectUrl);
|
||||||
setImageError(false);
|
setImageError(false);
|
||||||
@@ -158,7 +158,7 @@ const CategoryBox = ({
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
backgroundImage: ((typeof window !== 'undefined' && window.__PRERENDER_FALLBACK__) ||
|
backgroundImage: ((typeof window !== 'undefined' && window.__PRERENDER_FALLBACK__) ||
|
||||||
(typeof global !== 'undefined' && global.window && global.window.__PRERENDER_FALLBACK__))
|
(typeof global !== 'undefined' && global.window && global.window.__PRERENDER_FALLBACK__))
|
||||||
? `url("/assets/images/cat${id}.jpg")`
|
? `url("/assets/images/cat${id}.avif")`
|
||||||
: (imageUrl && !imageError ? `url("${imageUrl}")` : 'none'),
|
: (imageUrl && !imageError ? `url("${imageUrl}")` : 'none'),
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
|
|||||||
@@ -279,17 +279,25 @@ class Content extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processData(response) {
|
processData(response) {
|
||||||
const unfilteredProducts = response.products;
|
const rawProducts = response.products;
|
||||||
|
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n?.language || 'de';
|
||||||
|
|
||||||
if (!window.individualProductCache) {
|
if (!window.individualProductCache) {
|
||||||
window.individualProductCache = {};
|
window.individualProductCache = {};
|
||||||
}
|
}
|
||||||
//console.log("processData", unfilteredProducts);
|
|
||||||
if(unfilteredProducts) unfilteredProducts.forEach(product => {
|
const unfilteredProducts = [];
|
||||||
window.individualProductCache[product.id] = {
|
|
||||||
data: product,
|
//console.log("processData", rawProducts);
|
||||||
|
if(rawProducts) rawProducts.forEach(product => {
|
||||||
|
const effectiveProduct = product.translatedProduct || product;
|
||||||
|
const cacheKey = `${effectiveProduct.id}_${currentLanguage}`;
|
||||||
|
|
||||||
|
window.individualProductCache[cacheKey] = {
|
||||||
|
data: effectiveProduct,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
|
unfilteredProducts.push(effectiveProduct);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -450,7 +458,12 @@ class Content extends Component {
|
|||||||
{ query, language: currentLanguage, requestTranslation: currentLanguage === 'de' ? false : true },
|
{ query, language: currentLanguage, requestTranslation: currentLanguage === 'de' ? false : true },
|
||||||
(response) => {
|
(response) => {
|
||||||
if (response && response.products) {
|
if (response && response.products) {
|
||||||
this.processData(response);
|
// Map products to use translatedProduct if available
|
||||||
|
const enhancedResponse = {
|
||||||
|
...response,
|
||||||
|
products: response.products.map(p => p.translatedProduct || p)
|
||||||
|
};
|
||||||
|
this.processData(enhancedResponse);
|
||||||
} else {
|
} else {
|
||||||
console.log("fetchSearchData in Content failed", response);
|
console.log("fetchSearchData in Content failed", response);
|
||||||
}
|
}
|
||||||
@@ -681,6 +694,7 @@ class Content extends Component {
|
|||||||
onFilterChange={()=>{this.filterProducts()}}
|
onFilterChange={()=>{this.filterProducts()}}
|
||||||
dataType={this.state.dataType}
|
dataType={this.state.dataType}
|
||||||
dataParam={this.state.dataParam}
|
dataParam={this.state.dataParam}
|
||||||
|
categoryName={this.state.categoryName}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -722,7 +736,7 @@ class Content extends Component {
|
|||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
}}>
|
}}>
|
||||||
<img
|
<img
|
||||||
src="/assets/images/seeds.jpg"
|
src="/assets/images/seeds.avif"
|
||||||
alt="Seeds"
|
alt="Seeds"
|
||||||
fetchPriority="high"
|
fetchPriority="high"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
@@ -783,7 +797,7 @@ class Content extends Component {
|
|||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
}}>
|
}}>
|
||||||
<img
|
<img
|
||||||
src="/assets/images/cutlings.jpg"
|
src="/assets/images/cutlings.avif"
|
||||||
alt="Stecklinge"
|
alt="Stecklinge"
|
||||||
fetchPriority="high"
|
fetchPriority="high"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ class Footer extends Component {
|
|||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
src="/assets/images/gg.png"
|
src="/assets/images/gg.avif"
|
||||||
alt="Google Reviews"
|
alt="Google Reviews"
|
||||||
sx={{
|
sx={{
|
||||||
height: { xs: 50, md: 60 },
|
height: { xs: 50, md: 60 },
|
||||||
@@ -326,7 +326,7 @@ class Footer extends Component {
|
|||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
src="/assets/images/maps.png"
|
src="/assets/images/maps.avif"
|
||||||
alt="Google Maps"
|
alt="Google Maps"
|
||||||
sx={{
|
sx={{
|
||||||
height: { xs: 40, md: 50 },
|
height: { xs: 40, md: 50 },
|
||||||
@@ -352,6 +352,9 @@ class Footer extends Component {
|
|||||||
<Typography variant="body2" sx={{ fontSize: { xs: '11px', md: '14px' }, lineHeight: 1.5 }}>
|
<Typography variant="body2" sx={{ fontSize: { xs: '11px', md: '14px' }, lineHeight: 1.5 }}>
|
||||||
© {new Date().getFullYear()} <StyledDomainLink href="https://growheads.de" target="_blank" rel="noopener noreferrer">GrowHeads.de</StyledDomainLink>
|
© {new Date().getFullYear()} <StyledDomainLink href="https://growheads.de" target="_blank" rel="noopener noreferrer">GrowHeads.de</StyledDomainLink>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ fontSize: { xs: '9px', md: '9px' }, lineHeight: 1.5, mt: 1 }}>
|
||||||
|
<StyledDomainLink href="https://telegraf.growheads.de" target="_blank" rel="noreferrer">Telegraf - sicherer Chat mit unseren Mitarbeitern</StyledDomainLink>
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class Images extends Component {
|
|||||||
pics.push(window.tinyPicCache[bildId]);
|
pics.push(window.tinyPicCache[bildId]);
|
||||||
this.loadPic(this.props.fullscreenOpen ? 'large' : 'medium',bildId,newMainPic);
|
this.loadPic(this.props.fullscreenOpen ? 'large' : 'medium',bildId,newMainPic);
|
||||||
}else{
|
}else{
|
||||||
pics.push(`/assets/images/prod${bildId}.jpg`);
|
pics.push(`/assets/images/prod${bildId}.avif`);
|
||||||
this.loadPic(this.props.fullscreenOpen ? 'large' : 'medium',bildId,newMainPic);
|
this.loadPic(this.props.fullscreenOpen ? 'large' : 'medium',bildId,newMainPic);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
@@ -84,7 +84,7 @@ class Images extends Component {
|
|||||||
|
|
||||||
window.socketManager.emit('getPic', { bildId, size }, (res) => {
|
window.socketManager.emit('getPic', { bildId, size }, (res) => {
|
||||||
if(res.success){
|
if(res.success){
|
||||||
const url = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
const url = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/avif' }));
|
||||||
|
|
||||||
if(size === 'medium') window.mediumPicCache[bildId] = url;
|
if(size === 'medium') window.mediumPicCache[bildId] = url;
|
||||||
if(size === 'small') window.smallPicCache[bildId] = url;
|
if(size === 'small') window.smallPicCache[bildId] = url;
|
||||||
@@ -118,7 +118,7 @@ class Images extends Component {
|
|||||||
if (!this.props.pictureList || !this.props.pictureList.trim()) {
|
if (!this.props.pictureList || !this.props.pictureList.trim()) {
|
||||||
return '/assets/images/nopicture.jpg';
|
return '/assets/images/nopicture.jpg';
|
||||||
}
|
}
|
||||||
return `/assets/images/prod${this.props.pictureList.split(',')[0].trim()}.jpg`;
|
return `/assets/images/prod${this.props.pictureList.split(',')[0].trim()}.avif`;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -175,12 +175,12 @@ export class LoginComponent extends Component {
|
|||||||
const { location, navigate } = this.props;
|
const { location, navigate } = this.props;
|
||||||
|
|
||||||
if (!email || !password) {
|
if (!email || !password) {
|
||||||
this.setState({ error: 'Bitte füllen Sie alle Felder aus' });
|
this.setState({ error: this.props.t ? this.props.t('auth.errors.fillAllFields') : 'Bitte füllen Sie alle Felder aus' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.validateEmail(email)) {
|
if (!this.validateEmail(email)) {
|
||||||
this.setState({ error: 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
|
this.setState({ error: this.props.t ? this.props.t('auth.errors.invalidEmail') : 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ export class LoginComponent extends Component {
|
|||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: response.message || 'Anmeldung fehlgeschlagen'
|
error: response.message || (this.props.t ? this.props.t('auth.errors.loginFailed') : 'Anmeldung fehlgeschlagen')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -248,22 +248,22 @@ export class LoginComponent extends Component {
|
|||||||
const { email, password, confirmPassword } = this.state;
|
const { email, password, confirmPassword } = this.state;
|
||||||
|
|
||||||
if (!email || !password || !confirmPassword) {
|
if (!email || !password || !confirmPassword) {
|
||||||
this.setState({ error: 'Bitte füllen Sie alle Felder aus' });
|
this.setState({ error: this.props.t ? this.props.t('auth.errors.fillAllFields') : 'Bitte füllen Sie alle Felder aus' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.validateEmail(email)) {
|
if (!this.validateEmail(email)) {
|
||||||
this.setState({ error: 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
|
this.setState({ error: this.props.t ? this.props.t('auth.errors.invalidEmail') : 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password !== confirmPassword) {
|
if (password !== confirmPassword) {
|
||||||
this.setState({ error: 'Passwörter stimmen nicht überein' });
|
this.setState({ error: this.props.t ? this.props.t('auth.errors.passwordsNotMatchShort') : 'Passwörter stimmen nicht überein' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (password.length < 8) {
|
if (password.length < 8) {
|
||||||
this.setState({ error: 'Das Passwort muss mindestens 8 Zeichen lang sein' });
|
this.setState({ error: this.props.t ? this.props.t('auth.passwordMinLength') : 'Das Passwort muss mindestens 8 Zeichen lang sein' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,14 +274,14 @@ export class LoginComponent extends Component {
|
|||||||
if (response.success) {
|
if (response.success) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
success: 'Registrierung erfolgreich. Sie können sich jetzt anmelden.',
|
success: this.props.t ? this.props.t('auth.success.registerComplete') : 'Registrierung erfolgreich. Sie können sich jetzt anmelden.',
|
||||||
tabValue: 0 // Switch to login tab
|
tabValue: 0 // Switch to login tab
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let errorMessage = 'Registrierung fehlgeschlagen';
|
let errorMessage = this.props.t ? this.props.t('auth.errors.registerFailed') : 'Registrierung fehlgeschlagen';
|
||||||
|
|
||||||
if (response.cause === 'emailExists') {
|
if (response.cause === 'emailExists') {
|
||||||
errorMessage = 'Ein Benutzer mit dieser E-Mail-Adresse existiert bereits. Bitte verwenden Sie eine andere E-Mail-Adresse oder melden Sie sich an.';
|
errorMessage = this.props.t ? this.props.t('auth.errors.emailExists') : 'Ein Benutzer mit dieser E-Mail-Adresse existiert bereits. Bitte verwenden Sie eine andere E-Mail-Adresse oder melden Sie sich an.';
|
||||||
} else if (response.message) {
|
} else if (response.message) {
|
||||||
errorMessage = response.message;
|
errorMessage = response.message;
|
||||||
}
|
}
|
||||||
@@ -322,12 +322,12 @@ export class LoginComponent extends Component {
|
|||||||
const { email } = this.state;
|
const { email } = this.state;
|
||||||
|
|
||||||
if (!email) {
|
if (!email) {
|
||||||
this.setState({ error: 'Bitte geben Sie Ihre E-Mail-Adresse ein' });
|
this.setState({ error: this.props.t ? this.props.t('auth.errors.enterEmail') : 'Bitte geben Sie Ihre E-Mail-Adresse ein' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.validateEmail(email)) {
|
if (!this.validateEmail(email)) {
|
||||||
this.setState({ error: 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
|
this.setState({ error: this.props.t ? this.props.t('auth.errors.invalidEmail') : 'Bitte geben Sie eine gültige E-Mail-Adresse ein' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,12 +342,12 @@ export class LoginComponent extends Component {
|
|||||||
if (response.success) {
|
if (response.success) {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
success: 'Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.'
|
success: this.props.t ? this.props.t('auth.resetPassword.emailSent') : 'Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: response.message || 'Fehler beim Senden der E-Mail'
|
error: response.message || (this.props.t ? this.props.t('auth.resetPassword.emailError') : 'Fehler beim Senden der E-Mail')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -408,7 +408,7 @@ export class LoginComponent extends Component {
|
|||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
error: 'Google-Anmeldung fehlgeschlagen',
|
error: this.props.t ? this.props.t('auth.errors.googleLoginFailed') : 'Google-Anmeldung fehlgeschlagen',
|
||||||
showGoogleAuth: false // Reset Google auth state on failed login
|
showGoogleAuth: false // Reset Google auth state on failed login
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -418,7 +418,7 @@ export class LoginComponent extends Component {
|
|||||||
handleGoogleLoginError = (error) => {
|
handleGoogleLoginError = (error) => {
|
||||||
console.error('Google Login Error:', error);
|
console.error('Google Login Error:', error);
|
||||||
this.setState({
|
this.setState({
|
||||||
error: 'Google-Anmeldung fehlgeschlagen',
|
error: this.props.t ? this.props.t('auth.errors.googleLoginFailed') : 'Google-Anmeldung fehlgeschlagen',
|
||||||
showGoogleAuth: false, // Reset Google auth state on error
|
showGoogleAuth: false, // Reset Google auth state on error
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -163,8 +163,8 @@ const MainPageLayout = () => {
|
|||||||
|
|
||||||
const allContentBoxes = {
|
const allContentBoxes = {
|
||||||
home: [
|
home: [
|
||||||
{ title: t('sections.seeds'), image: "/assets/images/seeds.jpg", 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.jpg", bgcolor: "#e8f5d6", link: "/Kategorie/Stecklinge" }
|
{ title: t('sections.stecklinge'), image: "/assets/images/cutlings.avif", bgcolor: "#e8f5d6", link: "/Kategorie/Stecklinge" }
|
||||||
],
|
],
|
||||||
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" },
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import Delete from '@mui/icons-material/Delete';
|
import Delete from '@mui/icons-material/Delete';
|
||||||
import CloudUpload from '@mui/icons-material/CloudUpload';
|
import CloudUpload from '@mui/icons-material/CloudUpload';
|
||||||
|
import { withI18n } from '../i18n/withTranslation.js';
|
||||||
|
|
||||||
class PhotoUpload extends Component {
|
class PhotoUpload extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -30,7 +31,7 @@ class PhotoUpload extends Component {
|
|||||||
// Validate file count
|
// Validate file count
|
||||||
if (this.state.files.length + selectedFiles.length > maxFiles) {
|
if (this.state.files.length + selectedFiles.length > maxFiles) {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: `Maximal ${maxFiles} Dateien erlaubt`
|
error: this.props.t("productDialogs.photoUploadErrorMaxFiles", { max: maxFiles })
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -43,14 +44,14 @@ class PhotoUpload extends Component {
|
|||||||
for (const file of selectedFiles) {
|
for (const file of selectedFiles) {
|
||||||
if (!validTypes.includes(file.type)) {
|
if (!validTypes.includes(file.type)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: 'Nur Bilddateien (JPEG, PNG, GIF, WebP) sind erlaubt'
|
error: this.props.t("productDialogs.photoUploadErrorFileType")
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.size > maxSize) {
|
if (file.size > maxSize) {
|
||||||
this.setState({
|
this.setState({
|
||||||
error: `Datei zu groß. Maximum: ${Math.round(maxSize / (1024 * 1024))}MB`
|
error: this.props.t("productDialogs.photoUploadErrorFileSize", { maxSize: Math.round(maxSize / (1024 * 1024)) })
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -167,12 +168,12 @@ class PhotoUpload extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { files, previews, error } = this.state;
|
const { files, previews, error } = this.state;
|
||||||
const { disabled, label } = this.props;
|
const { disabled, label, t } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="body2" sx={{ mb: 1, fontWeight: 500 }}>
|
<Typography variant="body2" sx={{ mb: 1, fontWeight: 500 }}>
|
||||||
{label || 'Fotos anhängen (optional)'}
|
{label || t("productDialogs.photoUploadLabelDefault")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
@@ -192,7 +193,7 @@ class PhotoUpload extends Component {
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
>
|
>
|
||||||
Fotos auswählen
|
{t("productDialogs.photoUploadSelect")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
@@ -228,7 +229,7 @@ class PhotoUpload extends Component {
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={() => this.handleRemoveFile(index)}
|
onClick={() => this.handleRemoveFile(index)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
aria-label="Bild entfernen"
|
aria-label={t("productDialogs.photoUploadRemove")}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: 4,
|
top: 4,
|
||||||
@@ -269,10 +270,10 @@ class PhotoUpload extends Component {
|
|||||||
|
|
||||||
{files.length > 0 && (
|
{files.length > 0 && (
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
|
||||||
{files.length} Datei(en) ausgewählt
|
{t("productDialogs.photoUploadSelectedFiles", { count: files.length })}
|
||||||
{previews.length > 0 && previews.some(p => p.originalSize && p.compressedSize) && (
|
{previews.length > 0 && previews.some(p => p.originalSize && p.compressedSize) && (
|
||||||
<span style={{ marginLeft: '8px' }}>
|
<span style={{ marginLeft: '8px' }}>
|
||||||
(komprimiert für Upload)
|
{t("productDialogs.photoUploadCompressed")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -282,4 +283,4 @@ class PhotoUpload extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PhotoUpload;
|
export default withI18n()(PhotoUpload);
|
||||||
@@ -101,7 +101,7 @@ class Product extends Component {
|
|||||||
console.log('loadImagevisSocket', bildId);
|
console.log('loadImagevisSocket', bildId);
|
||||||
window.socketManager.emit('getPic', { bildId, size:'small' }, (res) => {
|
window.socketManager.emit('getPic', { bildId, size:'small' }, (res) => {
|
||||||
if(res.success){
|
if(res.success){
|
||||||
window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/avif' }));
|
||||||
if (this._isMounted) {
|
if (this._isMounted) {
|
||||||
this.setState({image: window.smallPicCache[bildId], loading: false});
|
this.setState({image: window.smallPicCache[bildId], loading: false});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -305,7 +305,7 @@ class ProductDetailPage extends Component {
|
|||||||
window.socketManager.emit('getPic', { bildId, size: 'small' }, (res) => {
|
window.socketManager.emit('getPic', { bildId, size: 'small' }, (res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
// Cache the image
|
// Cache the image
|
||||||
window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/avif' }));
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
@@ -546,7 +546,7 @@ class ProductDetailPage extends Component {
|
|||||||
console.log("getAttributePicture", res);
|
console.log("getAttributePicture", res);
|
||||||
if (res.success && !res.noPicture) {
|
if (res.success && !res.noPicture) {
|
||||||
const blob = new Blob([res.imageBuffer], {
|
const blob = new Blob([res.imageBuffer], {
|
||||||
type: "image/jpeg",
|
type: "image/avif",
|
||||||
});
|
});
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
@@ -599,13 +599,16 @@ class ProductDetailPage extends Component {
|
|||||||
const productData = res.translatedProduct || res.product;
|
const productData = res.translatedProduct || res.product;
|
||||||
productData.seoName = this.props.seoName;
|
productData.seoName = this.props.seoName;
|
||||||
|
|
||||||
|
// Use translated attributes if available
|
||||||
|
const attributesData = res.translatedAttributes || res.attributes;
|
||||||
|
|
||||||
// Initialize cache if it doesn't exist
|
// Initialize cache if it doesn't exist
|
||||||
if (!window.productDetailCache) {
|
if (!window.productDetailCache) {
|
||||||
window.productDetailCache = {};
|
window.productDetailCache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the complete response data (product + attributes) - cache the response with translated product
|
// Cache the complete response data (product + attributes) - cache the response with translated product
|
||||||
const cacheData = { ...res, product: productData };
|
const cacheData = { ...res, product: productData, attributes: attributesData };
|
||||||
window.productDetailCache[cacheKey] = cacheData;
|
window.productDetailCache[cacheKey] = cacheData;
|
||||||
|
|
||||||
// Clean up prerender fallback since we now have real data
|
// Clean up prerender fallback since we now have real data
|
||||||
@@ -628,7 +631,7 @@ class ProductDetailPage extends Component {
|
|||||||
upgrading: false, // Clear upgrading state since we now have complete data
|
upgrading: false, // Clear upgrading state since we now have complete data
|
||||||
error: null,
|
error: null,
|
||||||
imageDialogOpen: false,
|
imageDialogOpen: false,
|
||||||
attributes: res.attributes,
|
attributes: attributesData,
|
||||||
komponenten: komponenten,
|
komponenten: komponenten,
|
||||||
komponentenLoaded: komponenten.length === 0, // If no komponenten, mark as loaded
|
komponentenLoaded: komponenten.length === 0, // If no komponenten, mark as loaded
|
||||||
similarProducts: res.similarProducts || []
|
similarProducts: res.similarProducts || []
|
||||||
@@ -653,7 +656,7 @@ class ProductDetailPage extends Component {
|
|||||||
console.log("getProductView", res);
|
console.log("getProductView", res);
|
||||||
|
|
||||||
// Load attribute images
|
// Load attribute images
|
||||||
this.loadAttributeImages(res.attributes);
|
this.loadAttributeImages(attributesData);
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
"Error loading product:",
|
"Error loading product:",
|
||||||
@@ -762,7 +765,7 @@ class ProductDetailPage extends Component {
|
|||||||
handleEmbedShare = () => {
|
handleEmbedShare = () => {
|
||||||
const embedCode = `<iframe src="${this.getProductUrl()}" width="100%" height="600" frameborder="0"></iframe>`;
|
const embedCode = `<iframe src="${this.getProductUrl()}" width="100%" height="600" frameborder="0"></iframe>`;
|
||||||
navigator.clipboard.writeText(embedCode).then(() => {
|
navigator.clipboard.writeText(embedCode).then(() => {
|
||||||
this.showSnackbar("Einbettungscode wurde in die Zwischenablage kopiert!");
|
this.showSnackbar(this.props.t ? this.props.t("productDialogs.shareSuccessEmbed") : "Einbettungscode wurde in die Zwischenablage kopiert!");
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Fallback for older browsers
|
// Fallback for older browsers
|
||||||
try {
|
try {
|
||||||
@@ -772,9 +775,9 @@ class ProductDetailPage extends Component {
|
|||||||
textArea.select();
|
textArea.select();
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
document.body.removeChild(textArea);
|
document.body.removeChild(textArea);
|
||||||
this.showSnackbar("Einbettungscode wurde in die Zwischenablage kopiert!");
|
this.showSnackbar(this.props.t ? this.props.t("productDialogs.shareSuccessEmbed") : "Einbettungscode wurde in die Zwischenablage kopiert!");
|
||||||
} catch {
|
} catch {
|
||||||
this.showSnackbar("Fehler beim Kopieren des Einbettungscodes", "error");
|
this.showSnackbar(this.props.t ? this.props.t("productDialogs.shareErrorEmbed") : "Fehler beim Kopieren des Einbettungscodes", "error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.handleShareClose();
|
this.handleShareClose();
|
||||||
@@ -782,7 +785,10 @@ class ProductDetailPage extends Component {
|
|||||||
|
|
||||||
handleWhatsAppShare = () => {
|
handleWhatsAppShare = () => {
|
||||||
const url = this.getProductUrl();
|
const url = this.getProductUrl();
|
||||||
const text = `Schau dir dieses Produkt an: ${cleanProductName(this.state.product.name)}`;
|
const productName = cleanProductName(this.state.product.name);
|
||||||
|
const text = this.props.t
|
||||||
|
? this.props.t("productDialogs.shareWhatsAppText", { name: productName })
|
||||||
|
: `Schau dir dieses Produkt an: ${productName}`;
|
||||||
const whatsappUrl = `https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`;
|
const whatsappUrl = `https://wa.me/?text=${encodeURIComponent(text + ' ' + url)}`;
|
||||||
window.open(whatsappUrl, '_blank');
|
window.open(whatsappUrl, '_blank');
|
||||||
this.handleShareClose();
|
this.handleShareClose();
|
||||||
@@ -797,7 +803,10 @@ class ProductDetailPage extends Component {
|
|||||||
|
|
||||||
handleTelegramShare = () => {
|
handleTelegramShare = () => {
|
||||||
const url = this.getProductUrl();
|
const url = this.getProductUrl();
|
||||||
const text = `Schau dir dieses Produkt an: ${cleanProductName(this.state.product.name)}`;
|
const productName = cleanProductName(this.state.product.name);
|
||||||
|
const text = this.props.t
|
||||||
|
? this.props.t("productDialogs.shareTelegramText", { name: productName })
|
||||||
|
: `Schau dir dieses Produkt an: ${productName}`;
|
||||||
const telegramUrl = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`;
|
const telegramUrl = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`;
|
||||||
window.open(telegramUrl, '_blank');
|
window.open(telegramUrl, '_blank');
|
||||||
this.handleShareClose();
|
this.handleShareClose();
|
||||||
@@ -805,8 +814,18 @@ class ProductDetailPage extends Component {
|
|||||||
|
|
||||||
handleEmailShare = () => {
|
handleEmailShare = () => {
|
||||||
const url = this.getProductUrl();
|
const url = this.getProductUrl();
|
||||||
const subject = `Produktempfehlung: ${cleanProductName(this.state.product.name)}`;
|
const productName = cleanProductName(this.state.product.name);
|
||||||
const body = `Hallo,\n\nich möchte dir dieses Produkt empfehlen:\n\n${cleanProductName(this.state.product.name)}\n${url}\n\nViele Grüße`;
|
const subject = this.props.t
|
||||||
|
? `${this.props.t("productDialogs.shareEmailSubject")}: ${productName}`
|
||||||
|
: `Produktempfehlung: ${productName}`;
|
||||||
|
|
||||||
|
const body = this.props.t
|
||||||
|
? this.props.t("productDialogs.shareEmailBody", {
|
||||||
|
name: productName,
|
||||||
|
url: url
|
||||||
|
})
|
||||||
|
: `Hallo,\n\nich möchte dir dieses Produkt empfehlen:\n\n${productName}\n${url}\n\nViele Grüße`;
|
||||||
|
|
||||||
const emailUrl = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
const emailUrl = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
||||||
window.location.href = emailUrl;
|
window.location.href = emailUrl;
|
||||||
this.handleShareClose();
|
this.handleShareClose();
|
||||||
@@ -815,7 +834,7 @@ class ProductDetailPage extends Component {
|
|||||||
handleLinkCopy = () => {
|
handleLinkCopy = () => {
|
||||||
const url = this.getProductUrl();
|
const url = this.getProductUrl();
|
||||||
navigator.clipboard.writeText(url).then(() => {
|
navigator.clipboard.writeText(url).then(() => {
|
||||||
this.showSnackbar("Link wurde in die Zwischenablage kopiert!");
|
this.showSnackbar(this.props.t ? this.props.t("productDialogs.shareSuccessLink") : "Link wurde in die Zwischenablage kopiert!");
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Fallback for older browsers
|
// Fallback for older browsers
|
||||||
try {
|
try {
|
||||||
@@ -825,7 +844,7 @@ class ProductDetailPage extends Component {
|
|||||||
textArea.select();
|
textArea.select();
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
document.body.removeChild(textArea);
|
document.body.removeChild(textArea);
|
||||||
this.showSnackbar("Link wurde in die Zwischenablage kopiert!");
|
this.showSnackbar(this.props.t ? this.props.t("productDialogs.shareSuccessLink") : "Link wurde in die Zwischenablage kopiert!");
|
||||||
} catch {
|
} catch {
|
||||||
this.showSnackbar("Fehler beim Kopieren des Links", "error");
|
this.showSnackbar("Fehler beim Kopieren des Links", "error");
|
||||||
}
|
}
|
||||||
@@ -968,7 +987,7 @@ class ProductDetailPage extends Component {
|
|||||||
}).format(productData.price)}
|
}).format(productData.price)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
inkl. MwSt.
|
{this.props.t ? this.props.t('product.inclVatSimple') : 'inkl. MwSt.'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -1047,7 +1066,7 @@ class ProductDetailPage extends Component {
|
|||||||
console.log('loadEmbeddedProductImage response:', articleNr, res.success);
|
console.log('loadEmbeddedProductImage response:', articleNr, res.success);
|
||||||
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
const imageUrl = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
const imageUrl = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/avif' }));
|
||||||
this.setState(prevState => {
|
this.setState(prevState => {
|
||||||
console.log('Setting embedded product image for', articleNr);
|
console.log('Setting embedded product image for', articleNr);
|
||||||
return {
|
return {
|
||||||
@@ -1331,7 +1350,7 @@ class ProductDetailPage extends Component {
|
|||||||
whiteSpace: "nowrap"
|
whiteSpace: "nowrap"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Frage zum Artikel
|
{this.props.t ? this.props.t('productDialogs.questionTitle') : "Frage zum Artikel"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -1345,7 +1364,7 @@ class ProductDetailPage extends Component {
|
|||||||
whiteSpace: "nowrap"
|
whiteSpace: "nowrap"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Artikel Bewerten
|
{this.props.t ? this.props.t('productDialogs.ratingTitle') : "Artikel Bewerten"}
|
||||||
</Button>
|
</Button>
|
||||||
{(product.available !== 1 && product.availableSupplier !== 1) && (
|
{(product.available !== 1 && product.availableSupplier !== 1) && (
|
||||||
<Button
|
<Button
|
||||||
@@ -1366,7 +1385,7 @@ class ProductDetailPage extends Component {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Verfügbarkeit anfragen
|
{this.props.t ? this.props.t('productDialogs.availabilityTitle') : "Verfügbarkeit anfragen"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -1595,7 +1614,7 @@ class ProductDetailPage extends Component {
|
|||||||
}}
|
}}
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
Teilen
|
{this.props.t ? this.props.t("productDialogs.shareTitle") : "Teilen"}
|
||||||
</Button>
|
</Button>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -1669,7 +1688,7 @@ class ProductDetailPage extends Component {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<CodeIcon fontSize="small" />
|
<CodeIcon fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Einbetten" />
|
<ListItemText primary={this.props.t ? this.props.t("productDialogs.shareEmbed") : "Einbetten"} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={this.handleWhatsAppShare}>
|
<MenuItem onClick={this.handleWhatsAppShare}>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
@@ -1699,7 +1718,7 @@ class ProductDetailPage extends Component {
|
|||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<LinkIcon fontSize="small" />
|
<LinkIcon fontSize="small" />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText primary="Link kopieren" />
|
<ListItemText primary={this.props.t ? this.props.t("productDialogs.shareCopyLink") : "Link kopieren"} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -1953,7 +1972,7 @@ class ProductDetailPage extends Component {
|
|||||||
gap: 2
|
gap: 2
|
||||||
}}>
|
}}>
|
||||||
{this.state.similarProducts.map((similarProductData, index) => {
|
{this.state.similarProducts.map((similarProductData, index) => {
|
||||||
const product = similarProductData.product;
|
const product = similarProductData.translatedProduct || similarProductData.product;
|
||||||
return (
|
return (
|
||||||
<Box key={product.id} sx={{ display: 'flex', justifyContent: 'center' }}>
|
<Box key={product.id} sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||||
<Product
|
<Product
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ class ProductFilters extends Component {
|
|||||||
color: 'primary.main'
|
color: 'primary.main'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{this.props.dataParam}
|
{this.props.categoryName}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
@@ -27,7 +28,7 @@ class SharedCarousel extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const { i18n } = props;
|
const { i18n } = props;
|
||||||
|
|
||||||
// Don't load categories in constructor - will be loaded in componentDidMount with correct language
|
// Don't load categories in constructor - will be loaded in componentDidMount with correct language
|
||||||
this.state = {
|
this.state = {
|
||||||
categories: [],
|
categories: [],
|
||||||
@@ -41,7 +42,7 @@ class SharedCarousel extends React.Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this._isMounted = true;
|
this._isMounted = true;
|
||||||
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n.language;
|
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n.language;
|
||||||
|
|
||||||
// ALWAYS reload categories to ensure correct language
|
// ALWAYS reload categories to ensure correct language
|
||||||
console.log("SharedCarousel componentDidMount: ALWAYS RELOADING categories for language", currentLanguage);
|
console.log("SharedCarousel componentDidMount: ALWAYS RELOADING categories for language", currentLanguage);
|
||||||
window.categoryService.get(209, currentLanguage).then((response) => {
|
window.categoryService.get(209, currentLanguage).then((response) => {
|
||||||
@@ -60,12 +61,12 @@ class SharedCarousel extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
console.log("componentDidUpdate", prevProps.languageContext?.currentLanguage, this.props.languageContext?.currentLanguage);
|
console.log("componentDidUpdate", prevProps.languageContext?.currentLanguage, this.props.languageContext?.currentLanguage);
|
||||||
if(prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
|
if (prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
|
||||||
this.setState({ categories: [] },() => {
|
this.setState({ categories: [] }, () => {
|
||||||
window.categoryService.get(209,this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
|
window.categoryService.get(209, this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
|
||||||
console.log("response", response);
|
console.log("response", response);
|
||||||
if (response.children && response.children.length > 0) {
|
if (response.children && response.children.length > 0) {
|
||||||
this.originalCategories = response.children;
|
this.originalCategories = response.children;
|
||||||
this.categories = [...response.children, ...response.children];
|
this.categories = [...response.children, ...response.children];
|
||||||
this.setState({ categories: this.categories });
|
this.setState({ categories: this.categories });
|
||||||
this.startAutoScroll();
|
this.startAutoScroll();
|
||||||
@@ -123,7 +124,7 @@ class SharedCarousel extends React.Component {
|
|||||||
showScrollbarFlash = () => {
|
showScrollbarFlash = () => {
|
||||||
this.clearScrollbarTimer();
|
this.clearScrollbarTimer();
|
||||||
this.setState({ showScrollbar: true });
|
this.setState({ showScrollbar: true });
|
||||||
|
|
||||||
this.scrollbarTimer = setTimeout(() => {
|
this.scrollbarTimer = setTimeout(() => {
|
||||||
if (this._isMounted) {
|
if (this._isMounted) {
|
||||||
this.setState({ showScrollbar: false });
|
this.setState({ showScrollbar: false });
|
||||||
@@ -133,7 +134,7 @@ class SharedCarousel extends React.Component {
|
|||||||
|
|
||||||
handleAutoScroll = () => {
|
handleAutoScroll = () => {
|
||||||
if (!this.autoScrollActive || this.originalCategories.length === 0) return;
|
if (!this.autoScrollActive || this.originalCategories.length === 0) return;
|
||||||
|
|
||||||
this.translateX -= AUTO_SCROLL_SPEED;
|
this.translateX -= AUTO_SCROLL_SPEED;
|
||||||
this.updateTrackTransform();
|
this.updateTrackTransform();
|
||||||
|
|
||||||
@@ -172,7 +173,7 @@ class SharedCarousel extends React.Component {
|
|||||||
|
|
||||||
scrollBy = (direction) => {
|
scrollBy = (direction) => {
|
||||||
if (this.originalCategories.length === 0) return;
|
if (this.originalCategories.length === 0) return;
|
||||||
|
|
||||||
// direction: 1 = left (scroll content right), -1 = right (scroll content left)
|
// direction: 1 = left (scroll content right), -1 = right (scroll content left)
|
||||||
const originalItemCount = this.originalCategories.length;
|
const originalItemCount = this.originalCategories.length;
|
||||||
const maxScroll = ITEM_WIDTH * originalItemCount;
|
const maxScroll = ITEM_WIDTH * originalItemCount;
|
||||||
@@ -189,7 +190,7 @@ class SharedCarousel extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.updateTrackTransform();
|
this.updateTrackTransform();
|
||||||
|
|
||||||
// Force scrollbar to update immediately after wrap-around
|
// Force scrollbar to update immediately after wrap-around
|
||||||
if (this.state.showScrollbar) {
|
if (this.state.showScrollbar) {
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
@@ -204,11 +205,11 @@ class SharedCarousel extends React.Component {
|
|||||||
const originalItemCount = this.originalCategories.length;
|
const originalItemCount = this.originalCategories.length;
|
||||||
const viewportWidth = 1080; // carousel container max-width
|
const viewportWidth = 1080; // carousel container max-width
|
||||||
const itemsInView = Math.floor(viewportWidth / ITEM_WIDTH);
|
const itemsInView = Math.floor(viewportWidth / ITEM_WIDTH);
|
||||||
|
|
||||||
// Calculate which item is currently at the left edge (first visible)
|
// Calculate which item is currently at the left edge (first visible)
|
||||||
// Map translateX directly to item index using the same logic as scrollBy
|
// Map translateX directly to item index using the same logic as scrollBy
|
||||||
let currentItemIndex;
|
let currentItemIndex;
|
||||||
|
|
||||||
if (this.translateX === 0) {
|
if (this.translateX === 0) {
|
||||||
// At the beginning - item 0 is visible
|
// At the beginning - item 0 is visible
|
||||||
currentItemIndex = 0;
|
currentItemIndex = 0;
|
||||||
@@ -221,10 +222,10 @@ class SharedCarousel extends React.Component {
|
|||||||
// Normal negative scrolling - calculate which item is at left edge
|
// Normal negative scrolling - calculate which item is at left edge
|
||||||
currentItemIndex = Math.floor(Math.abs(this.translateX) / ITEM_WIDTH);
|
currentItemIndex = Math.floor(Math.abs(this.translateX) / ITEM_WIDTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we stay within bounds
|
// Ensure we stay within bounds
|
||||||
currentItemIndex = Math.max(0, Math.min(currentItemIndex, originalItemCount - 1));
|
currentItemIndex = Math.max(0, Math.min(currentItemIndex, originalItemCount - 1));
|
||||||
|
|
||||||
// Calculate scrollbar position: 0% when item 0 is first visible, 100% when last item is first visible
|
// Calculate scrollbar position: 0% when item 0 is first visible, 100% when last item is first visible
|
||||||
const lastPossibleFirstItem = Math.max(0, originalItemCount - itemsInView);
|
const lastPossibleFirstItem = Math.max(0, originalItemCount - itemsInView);
|
||||||
const thumbPosition = lastPossibleFirstItem > 0 ? Math.min((currentItemIndex / lastPossibleFirstItem) * 100, 100) : 0;
|
const thumbPosition = lastPossibleFirstItem > 0 ? Math.min((currentItemIndex / lastPossibleFirstItem) * 100, 100) : 0;
|
||||||
@@ -268,25 +269,41 @@ class SharedCarousel extends React.Component {
|
|||||||
const { t } = this.props;
|
const { t } = this.props;
|
||||||
const { categories } = this.state;
|
const { categories } = this.state;
|
||||||
|
|
||||||
if(!categories || categories.length === 0) {
|
if (!categories || categories.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ mt: 3 }}>
|
<Box sx={{ mt: 3 }}>
|
||||||
<Typography
|
<Box
|
||||||
variant="h4"
|
component={Link}
|
||||||
component="h1"
|
to="/Kategorien"
|
||||||
sx={{
|
sx={{
|
||||||
mb: 2,
|
display: "flex",
|
||||||
fontFamily: "SwashingtonCP",
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
textDecoration: "none",
|
||||||
color: "primary.main",
|
color: "primary.main",
|
||||||
textAlign: "center",
|
mb: 2,
|
||||||
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
|
transition: "all 0.3s ease",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "translateX(5px)",
|
||||||
|
color: "primary.dark"
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('navigation.categories')}
|
<Typography
|
||||||
</Typography>
|
variant="h4"
|
||||||
|
component="span"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "SwashingtonCP",
|
||||||
|
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('navigation.categories')}
|
||||||
|
</Typography>
|
||||||
|
<ChevronRight sx={{ fontSize: "2.5rem", ml: 1 }} />
|
||||||
|
</Box>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="carousel-wrapper"
|
className="carousel-wrapper"
|
||||||
@@ -394,7 +411,7 @@ class SharedCarousel extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Virtual Scrollbar */}
|
{/* Virtual Scrollbar */}
|
||||||
{this.renderVirtualScrollbar()}
|
{this.renderVirtualScrollbar()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class ExtrasSelector extends Component {
|
|||||||
this.loadingImages.add(bildId);
|
this.loadingImages.add(bildId);
|
||||||
window.socketManager.emit('getPic', { bildId, size:'small' }, (res) => {
|
window.socketManager.emit('getPic', { bildId, size:'small' }, (res) => {
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/avif' }));
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
this.loadingImages.delete(bildId);
|
this.loadingImages.delete(bildId);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import CardContent from '@mui/material/CardContent';
|
|||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Chip from '@mui/material/Chip';
|
import Chip from '@mui/material/Chip';
|
||||||
|
import { withI18n } from '../../i18n/withTranslation.js';
|
||||||
|
|
||||||
class TentShapeSelector extends Component {
|
class TentShapeSelector extends Component {
|
||||||
// Generate plant layout based on tent shape
|
// Generate plant layout based on tent shape
|
||||||
@@ -180,12 +181,20 @@ class TentShapeSelector extends Component {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Typography variant="body2" color="text.secondary" gutterBottom>
|
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||||||
{shape.description}
|
{this.props.t && shape.descriptionKey ? this.props.t(shape.descriptionKey) : shape.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
<Chip
|
<Chip
|
||||||
label={`${shape.minPlants}-${shape.maxPlants} Pflanzen`}
|
label={this.props.t
|
||||||
|
? (
|
||||||
|
shape.minPlants === 1 && shape.maxPlants === 2 ? this.props.t("kitConfig.plants1to2") :
|
||||||
|
shape.minPlants === 2 && shape.maxPlants === 4 ? this.props.t("kitConfig.plants2to4") :
|
||||||
|
shape.minPlants === 4 && shape.maxPlants === 6 ? this.props.t("kitConfig.plants4to6") :
|
||||||
|
shape.minPlants === 3 && shape.maxPlants === 6 ? this.props.t("kitConfig.plants3to6") :
|
||||||
|
`${shape.minPlants}-${shape.maxPlants} Pflanzen`
|
||||||
|
)
|
||||||
|
: `${shape.minPlants}-${shape.maxPlants} Pflanzen`}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{
|
sx={{
|
||||||
bgcolor: isSelected ? '#2e7d32' : '#f0f0f0',
|
bgcolor: isSelected ? '#2e7d32' : '#f0f0f0',
|
||||||
@@ -205,7 +214,7 @@ class TentShapeSelector extends Component {
|
|||||||
transition: 'opacity 0.3s ease'
|
transition: 'opacity 0.3s ease'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
✓ Ausgewählt
|
{this.props.t ? this.props.t("kitConfig.selected") : "✓ Ausgewählt"}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -238,4 +247,4 @@ class TentShapeSelector extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TentShapeSelector;
|
export default withI18n()(TentShapeSelector);
|
||||||
@@ -32,9 +32,9 @@ class CategoryList extends Component {
|
|||||||
console.log(" i18n.language:", this.props.i18n?.language);
|
console.log(" i18n.language:", this.props.i18n?.language);
|
||||||
console.log(" sessionStorage i18nextLng:", typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('i18nextLng') : 'N/A');
|
console.log(" sessionStorage i18nextLng:", typeof sessionStorage !== 'undefined' ? sessionStorage.getItem('i18nextLng') : 'N/A');
|
||||||
console.log(" localStorage i18nextLng:", typeof localStorage !== 'undefined' ? localStorage.getItem('i18nextLng') : 'N/A');
|
console.log(" localStorage i18nextLng:", typeof localStorage !== 'undefined' ? localStorage.getItem('i18nextLng') : 'N/A');
|
||||||
|
|
||||||
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n.language;
|
const currentLanguage = this.props.languageContext?.currentLanguage || this.props.i18n.language;
|
||||||
|
|
||||||
// ALWAYS reload categories to ensure correct language
|
// ALWAYS reload categories to ensure correct language
|
||||||
console.log("CategoryList componentDidMount: ALWAYS RELOADING categories for language", currentLanguage);
|
console.log("CategoryList componentDidMount: ALWAYS RELOADING categories for language", currentLanguage);
|
||||||
this.setState({ categories: [] }); // Clear any cached categories
|
this.setState({ categories: [] }); // Clear any cached categories
|
||||||
@@ -53,15 +53,15 @@ class CategoryList extends Component {
|
|||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
console.log("componentDidUpdate", prevProps.languageContext?.currentLanguage, this.props.languageContext?.currentLanguage);
|
console.log("componentDidUpdate", prevProps.languageContext?.currentLanguage, this.props.languageContext?.currentLanguage);
|
||||||
if(prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
|
if (prevProps.languageContext?.currentLanguage !== this.props.languageContext?.currentLanguage) {
|
||||||
this.setState({
|
this.setState({
|
||||||
categories: [],
|
categories: [],
|
||||||
activeCategoryId: null
|
activeCategoryId: null
|
||||||
},() => {
|
}, () => {
|
||||||
window.categoryService.get(209,this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
|
window.categoryService.get(209, this.props.languageContext?.currentLanguage || this.props.i18n.language).then((response) => {
|
||||||
console.log("response", response);
|
console.log("response", response);
|
||||||
if (response.children && response.children.length > 0) {
|
if (response.children && response.children.length > 0) {
|
||||||
this.setState({
|
this.setState({
|
||||||
categories: response.children,
|
categories: response.children,
|
||||||
activeCategoryId: this.setLevel1CategoryId(this.props.activeCategoryId)
|
activeCategoryId: this.setLevel1CategoryId(this.props.activeCategoryId)
|
||||||
});
|
});
|
||||||
@@ -69,14 +69,14 @@ class CategoryList extends Component {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (prevProps.activeCategoryId !== this.props.activeCategoryId) {
|
if (prevProps.activeCategoryId !== this.props.activeCategoryId) {
|
||||||
this.setLevel1CategoryId(this.props.activeCategoryId);
|
this.setLevel1CategoryId(this.props.activeCategoryId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setLevel1CategoryId = (input) => {
|
setLevel1CategoryId = (input) => {
|
||||||
if(input) {
|
if (input) {
|
||||||
const language = this.props.languageContext?.currentLanguage || this.props.i18n.language;
|
const language = this.props.languageContext?.currentLanguage || this.props.i18n.language;
|
||||||
const categoryTreeCache = window.categoryService.getSync(209, language);
|
const categoryTreeCache = window.categoryService.getSync(209, language);
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ class CategoryList extends Component {
|
|||||||
this.setState({ activeCategoryId: null });
|
this.setState({ activeCategoryId: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
handleMobileMenuToggle = () => {
|
handleMobileMenuToggle = () => {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
@@ -173,141 +173,141 @@ class CategoryList extends Component {
|
|||||||
py: 0.5, // Add vertical padding to prevent border clipping
|
py: 0.5, // Add vertical padding to prevent border clipping
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/"
|
to="/"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
size="small"
|
size="small"
|
||||||
aria-label="Zur Startseite"
|
aria-label="Zur Startseite"
|
||||||
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "0.75rem",
|
fontSize: "0.75rem",
|
||||||
textTransform: "none",
|
textTransform: "none",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
mx: isMobile ? 0 : 0.5,
|
mx: isMobile ? 0 : 0.5,
|
||||||
my: 0.25,
|
my: 0.25,
|
||||||
minWidth: isMobile ? "100%" : "auto",
|
minWidth: isMobile ? "100%" : "auto",
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
justifyContent: isMobile ? "flex-start" : "center",
|
justifyContent: isMobile ? "flex-start" : "center",
|
||||||
transition: "all 0.2s ease",
|
transition: "all 0.2s ease",
|
||||||
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
...(activeCategoryId === null && {
|
...(activeCategoryId === null && {
|
||||||
bgcolor: "#fff",
|
bgcolor: "#fff",
|
||||||
textShadow: "none",
|
textShadow: "none",
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
}),
|
}),
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
bgcolor: "#fff",
|
bgcolor: "#fff",
|
||||||
textShadow: "none",
|
textShadow: "none",
|
||||||
"& .MuiSvgIcon-root": {
|
"& .MuiSvgIcon-root": {
|
||||||
color: "#2e7d32 !important",
|
color: "#2e7d32 !important",
|
||||||
},
|
|
||||||
"& .bold-text": {
|
|
||||||
color: "#2e7d32 !important",
|
|
||||||
},
|
|
||||||
"& .thin-text": {
|
|
||||||
color: "transparent !important",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}}
|
"& .bold-text": {
|
||||||
>
|
color: "#2e7d32 !important",
|
||||||
<HomeIcon sx={{
|
},
|
||||||
fontSize: "1rem",
|
"& .thin-text": {
|
||||||
mr: isMobile ? 1 : 0,
|
color: "transparent !important",
|
||||||
color: activeCategoryId === null ? "#2e7d32" : "inherit"
|
},
|
||||||
}} />
|
},
|
||||||
{isMobile && (
|
}}
|
||||||
<Box sx={{ position: "relative", display: "inline-block" }}>
|
>
|
||||||
{/* Bold text (always rendered to set width) */}
|
<HomeIcon sx={{
|
||||||
<Box
|
fontSize: "1rem",
|
||||||
className="bold-text"
|
mr: isMobile ? 1 : 0,
|
||||||
sx={{
|
color: activeCategoryId === null ? "#2e7d32" : "inherit"
|
||||||
fontWeight: "bold",
|
}} />
|
||||||
color: activeCategoryId === null ? "#2e7d32" : "transparent",
|
{isMobile && (
|
||||||
position: "relative",
|
<Box sx={{ position: "relative", display: "inline-block" }}>
|
||||||
zIndex: 2,
|
{/* Bold text (always rendered to set width) */}
|
||||||
}}
|
<Box
|
||||||
>
|
className="bold-text"
|
||||||
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
sx={{
|
||||||
</Box>
|
fontWeight: "bold",
|
||||||
{/* Thin text (positioned on top) */}
|
color: activeCategoryId === null ? "#2e7d32" : "transparent",
|
||||||
<Box
|
position: "relative",
|
||||||
className="thin-text"
|
zIndex: 2,
|
||||||
sx={{
|
}}
|
||||||
fontWeight: "400",
|
>
|
||||||
color: activeCategoryId === null ? "transparent" : "inherit",
|
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
{/* Thin text (positioned on top) */}
|
||||||
</Button>
|
<Box
|
||||||
|
className="thin-text"
|
||||||
|
sx={{
|
||||||
|
fontWeight: "400",
|
||||||
|
color: activeCategoryId === null ? "transparent" : "inherit",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to="/Kategorie/neu"
|
to="/Kategorie/neu"
|
||||||
color="inherit"
|
color="inherit"
|
||||||
size="small"
|
size="small"
|
||||||
aria-label="Neuheiten"
|
aria-label="Neuheiten"
|
||||||
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "0.75rem",
|
fontSize: "0.75rem",
|
||||||
textTransform: "none",
|
textTransform: "none",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
mx: isMobile ? 0 : 0.5,
|
mx: isMobile ? 0 : 0.5,
|
||||||
my: 0.25,
|
my: 0.25,
|
||||||
minWidth: isMobile ? "100%" : "auto",
|
minWidth: isMobile ? "100%" : "auto",
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
justifyContent: isMobile ? "flex-start" : "center",
|
justifyContent: isMobile ? "flex-start" : "center",
|
||||||
transition: "all 0.2s ease",
|
transition: "all 0.2s ease",
|
||||||
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
||||||
position: "relative"
|
position: "relative"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FiberNewIcon sx={{
|
<FiberNewIcon sx={{
|
||||||
fontSize: "1rem",
|
fontSize: "1rem",
|
||||||
mr: isMobile ? 1 : 0
|
mr: isMobile ? 1 : 0
|
||||||
}} />
|
}} />
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<Box sx={{ position: "relative", display: "inline-block" }}>
|
<Box sx={{ position: "relative", display: "inline-block" }}>
|
||||||
{/* Bold text (always rendered to set width) */}
|
{/* Bold text (always rendered to set width) */}
|
||||||
<Box
|
<Box
|
||||||
className="bold-text"
|
className="bold-text"
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
color: "transparent",
|
color: "transparent",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
|
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
|
||||||
</Box>
|
|
||||||
{/* Thin text (positioned on top) */}
|
|
||||||
<Box
|
|
||||||
className="thin-text"
|
|
||||||
sx={{
|
|
||||||
fontWeight: "400",
|
|
||||||
color: "inherit",
|
|
||||||
position: "absolute",
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
{/* Thin text (positioned on top) */}
|
||||||
</Button>
|
<Box
|
||||||
|
className="thin-text"
|
||||||
|
sx={{
|
||||||
|
fontWeight: "400",
|
||||||
|
color: "inherit",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.t ? this.props.t('navigation.new') : 'Neuheiten'}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
|
||||||
{categories.length > 0 ? (
|
{categories.length > 0 ? (
|
||||||
@@ -385,100 +385,100 @@ class CategoryList extends Component {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
) : ( !isMobile && (
|
) : (!isMobile && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="caption"
|
variant="caption"
|
||||||
color="inherit"
|
|
||||||
sx={{
|
|
||||||
display: "inline-flex",
|
|
||||||
alignItems: "center",
|
|
||||||
height: "33px", // Match small button height
|
|
||||||
px: 1,
|
|
||||||
fontSize: "0.75rem",
|
|
||||||
opacity: 0.9,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
|
|
||||||
</Typography>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
to="/Konfigurator"
|
|
||||||
color="inherit"
|
color="inherit"
|
||||||
size="small"
|
|
||||||
aria-label="Zur Startseite"
|
|
||||||
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
|
||||||
sx={{
|
sx={{
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "33px", // Match small button height
|
||||||
|
px: 1,
|
||||||
fontSize: "0.75rem",
|
fontSize: "0.75rem",
|
||||||
textTransform: "none",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
mx: isMobile ? 0 : 0.5,
|
|
||||||
my: 0.25,
|
|
||||||
minWidth: isMobile ? "100%" : "auto",
|
|
||||||
borderRadius: 1,
|
|
||||||
justifyContent: isMobile ? "flex-start" : "center",
|
|
||||||
transition: "all 0.2s ease",
|
|
||||||
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
|
||||||
position: "relative",
|
|
||||||
...(activeCategoryId === null && {
|
|
||||||
bgcolor: "#fff",
|
|
||||||
textShadow: "none",
|
|
||||||
opacity: 1,
|
|
||||||
}),
|
|
||||||
"&:hover": {
|
|
||||||
opacity: 1,
|
|
||||||
bgcolor: "#fff",
|
|
||||||
textShadow: "none",
|
|
||||||
"& .MuiSvgIcon-root": {
|
|
||||||
color: "#2e7d32 !important",
|
|
||||||
},
|
|
||||||
"& .bold-text": {
|
|
||||||
color: "#2e7d32 !important",
|
|
||||||
},
|
|
||||||
"& .thin-text": {
|
|
||||||
color: "transparent !important",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SettingsIcon sx={{
|
|
||||||
fontSize: "1rem",
|
</Typography>
|
||||||
mr: isMobile ? 1 : 0,
|
)
|
||||||
color: activeCategoryId === null ? "#2e7d32" : "inherit"
|
)}
|
||||||
}} />
|
<Button
|
||||||
{isMobile && (
|
component={Link}
|
||||||
<Box sx={{ position: "relative", display: "inline-block" }}>
|
to="/Konfigurator"
|
||||||
{/* Bold text (always rendered to set width) */}
|
color="inherit"
|
||||||
<Box
|
size="small"
|
||||||
className="bold-text"
|
aria-label="Zur Startseite"
|
||||||
sx={{
|
onClick={isMobile ? this.handleMobileCategoryClick : undefined}
|
||||||
fontWeight: "bold",
|
sx={{
|
||||||
color: activeCategoryId === null ? "#2e7d32" : "transparent",
|
fontSize: "0.75rem",
|
||||||
position: "relative",
|
textTransform: "none",
|
||||||
zIndex: 2,
|
whiteSpace: "nowrap",
|
||||||
}}
|
opacity: 0.9,
|
||||||
>
|
mx: isMobile ? 0 : 0.5,
|
||||||
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
my: 0.25,
|
||||||
</Box>
|
minWidth: isMobile ? "100%" : "auto",
|
||||||
{/* Thin text (positioned on top) */}
|
borderRadius: 1,
|
||||||
<Box
|
justifyContent: isMobile ? "flex-start" : "center",
|
||||||
className="thin-text"
|
transition: "all 0.2s ease",
|
||||||
sx={{
|
textShadow: "0 1px 2px rgba(0,0,0,0.3)",
|
||||||
fontWeight: "400",
|
position: "relative",
|
||||||
color: activeCategoryId === null ? "transparent" : "inherit",
|
...(activeCategoryId === null && {
|
||||||
position: "absolute",
|
bgcolor: "#fff",
|
||||||
top: 0,
|
textShadow: "none",
|
||||||
left: 0,
|
opacity: 1,
|
||||||
zIndex: 1,
|
}),
|
||||||
}}
|
"&:hover": {
|
||||||
>
|
opacity: 1,
|
||||||
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
bgcolor: "#fff",
|
||||||
</Box>
|
textShadow: "none",
|
||||||
|
"& .MuiSvgIcon-root": {
|
||||||
|
color: "#2e7d32 !important",
|
||||||
|
},
|
||||||
|
"& .bold-text": {
|
||||||
|
color: "#2e7d32 !important",
|
||||||
|
},
|
||||||
|
"& .thin-text": {
|
||||||
|
color: "transparent !important",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SettingsIcon sx={{
|
||||||
|
fontSize: "1rem",
|
||||||
|
mr: isMobile ? 1 : 0,
|
||||||
|
color: activeCategoryId === null ? "#2e7d32" : "inherit"
|
||||||
|
}} />
|
||||||
|
{isMobile && (
|
||||||
|
<Box sx={{ position: "relative", display: "inline-block" }}>
|
||||||
|
{/* Bold text (always rendered to set width) */}
|
||||||
|
<Box
|
||||||
|
className="bold-text"
|
||||||
|
sx={{
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: activeCategoryId === null ? "#2e7d32" : "transparent",
|
||||||
|
position: "relative",
|
||||||
|
zIndex: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
{/* Thin text (positioned on top) */}
|
||||||
</Button>
|
<Box
|
||||||
|
className="thin-text"
|
||||||
|
sx={{
|
||||||
|
fontWeight: "400",
|
||||||
|
color: activeCategoryId === null ? "transparent" : "inherit",
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -516,11 +516,11 @@ class CategoryList extends Component {
|
|||||||
>
|
>
|
||||||
<Container maxWidth="lg" sx={{ px: 2 }}>
|
<Container maxWidth="lg" sx={{ px: 2 }}>
|
||||||
{/* Toggle Button */}
|
{/* Toggle Button */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
py: 1,
|
py: 1,
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
@@ -530,7 +530,7 @@ class CategoryList extends Component {
|
|||||||
onClick={this.handleMobileMenuToggle}
|
onClick={this.handleMobileMenuToggle}
|
||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
aria-label={this.props.t ?
|
aria-label={this.props.t ?
|
||||||
(mobileMenuOpen ? this.props.t('navigation.categoriesClose') : this.props.t('navigation.categoriesOpen')) :
|
(mobileMenuOpen ? this.props.t('navigation.categoriesClose') : this.props.t('navigation.categoriesOpen')) :
|
||||||
(mobileMenuOpen ? "Kategorien schließen" : "Kategorien öffnen")
|
(mobileMenuOpen ? "Kategorien schließen" : "Kategorien öffnen")
|
||||||
}
|
}
|
||||||
@@ -541,11 +541,11 @@ class CategoryList extends Component {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle2" color="inherit" sx={{
|
<Typography variant="subtitle2" color="inherit" sx={{
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
textShadow: "0 1px 2px rgba(0,0,0,0.3)"
|
textShadow: "0 1px 2px rgba(0,0,0,0.3)"
|
||||||
}}>
|
}}>
|
||||||
{this.props.t ? this.props.t('navigation.categories') : 'Kategorien'}
|
{this.props.t ? this.props.t('navigation.categories') : 'Kategorien'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
{mobileMenuOpen ? <CloseIcon /> : <MenuIcon />}
|
{mobileMenuOpen ? <CloseIcon /> : <MenuIcon />}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const Logo = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/assets/images/sh.png"
|
src="/assets/images/sh.avif"
|
||||||
alt="SH Logo"
|
alt="SH Logo"
|
||||||
width="108px"
|
width="108px"
|
||||||
height="45px"
|
height="45px"
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ const SearchBar = () => {
|
|||||||
(response) => {
|
(response) => {
|
||||||
if (response && response.products) {
|
if (response && response.products) {
|
||||||
// getSearchProducts returns response.products array
|
// getSearchProducts returns response.products array
|
||||||
const suggestions = response.products.slice(0, 8); // Limit to 8 suggestions
|
const suggestions = response.products.map(p => p.translatedProduct || p).slice(0, 8); // Limit to 8 suggestions
|
||||||
setSuggestions(suggestions);
|
setSuggestions(suggestions);
|
||||||
setShowSuggestions(suggestions.length > 0);
|
setShowSuggestions(suggestions.length > 0);
|
||||||
setSelectedIndex(-1); // Reset selection
|
setSelectedIndex(-1); // Reset selection
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ const getStatusTranslation = (status, t) => {
|
|||||||
new: t ? t('orders.status.new') : "in Bearbeitung",
|
new: t ? t('orders.status.new') : "in Bearbeitung",
|
||||||
pending: t ? t('orders.status.pending') : "Neu",
|
pending: t ? t('orders.status.pending') : "Neu",
|
||||||
processing: t ? t('orders.status.processing') : "in Bearbeitung",
|
processing: t ? t('orders.status.processing') : "in Bearbeitung",
|
||||||
|
paid: t ? t('orders.status.paid') : "Bezahlt",
|
||||||
cancelled: t ? t('orders.status.cancelled') : "Storniert",
|
cancelled: t ? t('orders.status.cancelled') : "Storniert",
|
||||||
shipped: t ? t('orders.status.shipped') : "Verschickt",
|
shipped: t ? t('orders.status.shipped') : "Verschickt",
|
||||||
delivered: t ? t('orders.status.delivered') : "Geliefert",
|
delivered: t ? t('orders.status.delivered') : "Geliefert",
|
||||||
@@ -39,29 +40,23 @@ const getStatusTranslation = (status, t) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const statusEmojis = {
|
const statusEmojis = {
|
||||||
"in Bearbeitung": "⚙️",
|
new: "⚙️",
|
||||||
pending: "⏳",
|
pending: "⏳",
|
||||||
processing: "🔄",
|
processing: "🔄",
|
||||||
|
paid: "🏦",
|
||||||
cancelled: "❌",
|
cancelled: "❌",
|
||||||
Verschickt: "🚚",
|
shipped: "🚚",
|
||||||
Geliefert: "✅",
|
delivered: "✅",
|
||||||
Storniert: "❌",
|
|
||||||
Retoure: "↩️",
|
|
||||||
"Teil Retoure": "↪️",
|
|
||||||
"Teil geliefert": "⚡",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusColors = {
|
const statusColors = {
|
||||||
"in Bearbeitung": "#ed6c02", // orange
|
new: "#ed6c02", // orange
|
||||||
pending: "#ff9800", // orange for pending
|
pending: "#ff9800", // orange for pending
|
||||||
processing: "#2196f3", // blue for processing
|
processing: "#2196f3", // blue for processing
|
||||||
|
paid: "#2e7d32", // green
|
||||||
cancelled: "#d32f2f", // red for cancelled
|
cancelled: "#d32f2f", // red for cancelled
|
||||||
Verschickt: "#2e7d32", // green
|
shipped: "#2e7d32", // green
|
||||||
Geliefert: "#2e7d32", // green
|
delivered: "#2e7d32", // green
|
||||||
Storniert: "#d32f2f", // red
|
|
||||||
Retoure: "#9c27b0", // purple
|
|
||||||
"Teil Retoure": "#9c27b0", // purple
|
|
||||||
"Teil geliefert": "#009688", // teal
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const currencyFormatter = new Intl.NumberFormat("de-DE", {
|
const currencyFormatter = new Intl.NumberFormat("de-DE", {
|
||||||
@@ -229,11 +224,11 @@ const OrdersTab = ({ orderIdFromHash, t }) => {
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: "8px",
|
gap: "8px",
|
||||||
color: getStatusColor(displayStatus),
|
color: getStatusColor(order.status),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ fontSize: "1.2rem" }}>
|
<span style={{ fontSize: "1.2rem" }}>
|
||||||
{getStatusEmoji(displayStatus)}
|
{getStatusEmoji(order.status)}
|
||||||
</span>
|
</span>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
@@ -243,6 +238,18 @@ const OrdersTab = ({ orderIdFromHash, t }) => {
|
|||||||
{displayStatus}
|
{displayStatus}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
{order.delivery_method === 'DHL' && order.trackingCode && (
|
||||||
|
<Box sx={{ mt: 0.5 }}>
|
||||||
|
<a
|
||||||
|
href={`https://www.dhl.de/de/privatkunden/dhl-sendungsverfolgung.html?piececode=${order.trackingCode}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{ fontSize: '0.85rem', color: '#d40511' }}
|
||||||
|
>
|
||||||
|
📦 {t ? t('orders.trackShipment') : 'Sendung verfolgen'}
|
||||||
|
</a>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{order.items
|
{order.items
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ const config = {
|
|||||||
|
|
||||||
// Images
|
// Images
|
||||||
images: {
|
images: {
|
||||||
logo: "/assets/images/sh.png",
|
logo: "/assets/images/sh.avif",
|
||||||
placeholder: "/assets/images/nopicture.jpg"
|
placeholder: "/assets/images/nopicture.jpg"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
// @note Dummy data for grow tent configurator - no backend calls
|
// @note Dummy data for grow tent configurator - no backend calls
|
||||||
|
// descriptions now keys for translation
|
||||||
export const tentShapes = [
|
export const tentShapes = [
|
||||||
{
|
{
|
||||||
id: '60x60',
|
id: '60x60',
|
||||||
name: '60x60cm',
|
name: '60x60cm',
|
||||||
description: 'Kompakt - ideal für kleine Räume',
|
descriptionKey: 'kitConfig.description60x60',
|
||||||
footprint: '60x60',
|
footprint: '60x60',
|
||||||
minPlants: 1,
|
minPlants: 1,
|
||||||
maxPlants: 2,
|
maxPlants: 2,
|
||||||
@@ -13,7 +14,7 @@ export const tentShapes = [
|
|||||||
{
|
{
|
||||||
id: '80x80',
|
id: '80x80',
|
||||||
name: '80x80cm',
|
name: '80x80cm',
|
||||||
description: 'Mittel - perfekte Balance',
|
descriptionKey: 'kitConfig.description80x80',
|
||||||
footprint: '80x80',
|
footprint: '80x80',
|
||||||
minPlants: 2,
|
minPlants: 2,
|
||||||
maxPlants: 4,
|
maxPlants: 4,
|
||||||
@@ -23,7 +24,7 @@ export const tentShapes = [
|
|||||||
{
|
{
|
||||||
id: '100x100',
|
id: '100x100',
|
||||||
name: '100x100cm',
|
name: '100x100cm',
|
||||||
description: 'Groß - für erfahrene Grower',
|
descriptionKey: 'kitConfig.description100x100',
|
||||||
footprint: '100x100',
|
footprint: '100x100',
|
||||||
minPlants: 4,
|
minPlants: 4,
|
||||||
maxPlants: 6,
|
maxPlants: 6,
|
||||||
@@ -33,7 +34,7 @@ export const tentShapes = [
|
|||||||
{
|
{
|
||||||
id: '120x60',
|
id: '120x60',
|
||||||
name: '120x60cm',
|
name: '120x60cm',
|
||||||
description: 'Rechteckig - maximale Raumnutzung',
|
descriptionKey: 'kitConfig.description120x60',
|
||||||
footprint: '120x60',
|
footprint: '120x60',
|
||||||
minPlants: 3,
|
minPlants: 3,
|
||||||
maxPlants: 6,
|
maxPlants: 6,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export default {
|
|||||||
"profile": "الملف الشخصي",
|
"profile": "الملف الشخصي",
|
||||||
"email": "البريد الإلكتروني",
|
"email": "البريد الإلكتروني",
|
||||||
"password": "كلمة المرور",
|
"password": "كلمة المرور",
|
||||||
|
"newPassword": "كلمة المرور الجديدة",
|
||||||
"confirmPassword": "تأكيد كلمة المرور",
|
"confirmPassword": "تأكيد كلمة المرور",
|
||||||
"forgotPassword": "هل نسيت كلمة المرور؟",
|
"forgotPassword": "هل نسيت كلمة المرور؟",
|
||||||
"loginWithGoogle": "تسجيل الدخول باستخدام جوجل",
|
"loginWithGoogle": "تسجيل الدخول باستخدام جوجل",
|
||||||
@@ -13,6 +14,7 @@ export default {
|
|||||||
"privacyPolicy": "سياسة الخصوصية",
|
"privacyPolicy": "سياسة الخصوصية",
|
||||||
"passwordMinLength": "يجب أن تكون كلمة المرور 8 أحرف على الأقل",
|
"passwordMinLength": "يجب أن تكون كلمة المرور 8 أحرف على الأقل",
|
||||||
"newPasswordMinLength": "يجب أن تكون كلمة المرور الجديدة 8 أحرف على الأقل",
|
"newPasswordMinLength": "يجب أن تكون كلمة المرور الجديدة 8 أحرف على الأقل",
|
||||||
|
"backToHome": "العودة إلى الصفحة الرئيسية",
|
||||||
"menu": {
|
"menu": {
|
||||||
"profile": "الملف الشخصي",
|
"profile": "الملف الشخصي",
|
||||||
"myProfile": "ملفي الشخصي",
|
"myProfile": "ملفي الشخصي",
|
||||||
@@ -21,5 +23,28 @@ export default {
|
|||||||
"settings": "الإعدادات",
|
"settings": "الإعدادات",
|
||||||
"adminDashboard": "لوحة تحكم المسؤول",
|
"adminDashboard": "لوحة تحكم المسؤول",
|
||||||
"adminUsers": "مستخدمو المسؤول"
|
"adminUsers": "مستخدمو المسؤول"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"title": "إعادة تعيين كلمة المرور",
|
||||||
|
"button": "إعادة تعيين كلمة المرور",
|
||||||
|
"success": "تم إعادة تعيين كلمة المرور بنجاح! سيتم توجيهك لتسجيل الدخول قريبًا...",
|
||||||
|
"invalidToken": "لم يتم العثور على رمز صالح. يرجى استخدام الرابط من بريدك الإلكتروني.",
|
||||||
|
"error": "حدث خطأ أثناء إعادة تعيين كلمة المرور",
|
||||||
|
"emailSent": "تم إرسال رابط لإعادة تعيين كلمة المرور إلى بريدك الإلكتروني.",
|
||||||
|
"emailError": "حدث خطأ أثناء إرسال البريد الإلكتروني"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fillAllFields": "يرجى ملء جميع الحقول",
|
||||||
|
"invalidEmail": "يرجى إدخال بريد إلكتروني صالح",
|
||||||
|
"passwordsNotMatch": "كلمات المرور غير متطابقة",
|
||||||
|
"passwordsNotMatchShort": "كلمات المرور غير متطابقة",
|
||||||
|
"enterEmail": "يرجى إدخال بريدك الإلكتروني",
|
||||||
|
"loginFailed": "فشل تسجيل الدخول",
|
||||||
|
"registerFailed": "فشل التسجيل",
|
||||||
|
"googleLoginFailed": "فشل تسجيل الدخول عبر جوجل",
|
||||||
|
"emailExists": "يوجد مستخدم بهذا البريد الإلكتروني بالفعل. يرجى استخدام بريد إلكتروني آخر أو تسجيل الدخول."
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"registerComplete": "تم التسجيل بنجاح. يمكنك الآن تسجيل الدخول."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import navigation from './navigation.js';
|
|||||||
import auth from './auth.js';
|
import auth from './auth.js';
|
||||||
import cart from './cart.js';
|
import cart from './cart.js';
|
||||||
import product from './product.js';
|
import product from './product.js';
|
||||||
|
import productDialogs from './productDialogs.js';
|
||||||
import search from './search.js';
|
import search from './search.js';
|
||||||
import sorting from './sorting.js';
|
import sorting from './sorting.js';
|
||||||
import chat from './chat.js';
|
import chat from './chat.js';
|
||||||
@@ -18,6 +19,7 @@ import pages from './pages.js';
|
|||||||
import orders from './orders.js';
|
import orders from './orders.js';
|
||||||
import settings from './settings.js';
|
import settings from './settings.js';
|
||||||
import common from './common.js';
|
import common from './common.js';
|
||||||
|
import kitConfig from './kitConfig.js';
|
||||||
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
||||||
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
||||||
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
||||||
@@ -35,6 +37,7 @@ export default {
|
|||||||
"auth": auth,
|
"auth": auth,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"product": product,
|
"product": product,
|
||||||
|
"productDialogs": productDialogs,
|
||||||
"search": search,
|
"search": search,
|
||||||
"sorting": sorting,
|
"sorting": sorting,
|
||||||
"chat": chat,
|
"chat": chat,
|
||||||
@@ -50,6 +53,7 @@ export default {
|
|||||||
"orders": orders,
|
"orders": orders,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"common": common,
|
"common": common,
|
||||||
|
"kitConfig": kitConfig,
|
||||||
"legalDatenschutzBasic": legalDatenschutzBasic,
|
"legalDatenschutzBasic": legalDatenschutzBasic,
|
||||||
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
||||||
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
||||||
|
|||||||
43
src/i18n/locales/ar/kitConfig.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export default {
|
||||||
|
"pageTitle": "🌱 مُكوّن جروبوكس",
|
||||||
|
"pageSubtitle": "ركّب إعداد النمو الداخلي المثالي بتاعك",
|
||||||
|
"bundleDiscountTitle": "🎯 احصل على خصم الباقة!",
|
||||||
|
"loadingProducts": "جارٍ تحميل منتجات الجروبوكس...",
|
||||||
|
"loadingLighting": "جارٍ تحميل منتجات الإضاءة...",
|
||||||
|
"loadingVentilation": "جارٍ تحميل منتجات التهوية...",
|
||||||
|
"loadingExtras": "جارٍ تحميل الإضافات...",
|
||||||
|
"noProductsAvailable": "لا توجد منتجات متاحة لهذا الحجم",
|
||||||
|
"noLightingAvailable": "لا توجد أضواء مناسبة لحجم الخيمة {{shape}}.",
|
||||||
|
"noVentilationAvailable": "لا توجد تهوية مناسبة لحجم الخيمة {{shape}}.",
|
||||||
|
"noExtrasAvailable": "لا توجد إضافات متاحة",
|
||||||
|
"selectShapeTitle": "1. اختر شكل الجروبوكس",
|
||||||
|
"selectShapeSubtitle": "اختار أولاً مساحة قاعدة الجروبوكس بتاعتك",
|
||||||
|
"selectProductTitle": "2. اختر منتج الجروبوكس",
|
||||||
|
"selectProductSubtitle": "اختار المنتج المناسب لجروبوكس {{shape}} بتاعك",
|
||||||
|
"selectLightingTitle": "3. اختر الإضاءة",
|
||||||
|
"selectLightingTitleShape": "3. اختر الإضاءة - {{shape}}",
|
||||||
|
"selectLightingSubtitle": "من فضلك اختار حجم الخيمة الأول.",
|
||||||
|
"selectVentilationTitle": "4. اختر التهوية",
|
||||||
|
"selectVentilationTitleShape": "4. اختر التهوية - {{shape}}",
|
||||||
|
"selectVentilationSubtitle": "من فضلك اختار حجم الخيمة الأول.",
|
||||||
|
"selectExtrasTitle": "5. أضف إضافات (اختياري)",
|
||||||
|
"yourConfiguration": "🎯 التكوين بتاعك",
|
||||||
|
"growboxLabel": "جروبوكس: {{name}}",
|
||||||
|
"lightingLabel": "الإضاءة: {{name}}",
|
||||||
|
"ventilationLabel": "التهوية: {{name}}",
|
||||||
|
"extraLabel": "إضافة: {{name}}",
|
||||||
|
"totalPrice": "السعر الكلي:",
|
||||||
|
"addToCart": "أضف إلى السلة",
|
||||||
|
"selected": "✓ تم الاختيار",
|
||||||
|
"notDeliverable": "غير متوفر للتوصيل",
|
||||||
|
"noPrice": "لا يوجد سعر",
|
||||||
|
"setName": "طقم جروبوكس - {{shape}}",
|
||||||
|
"description60x60": "مُدمج - مثالي للمساحات الصغيرة",
|
||||||
|
"description80x80": "متوسط - توازن مثالي",
|
||||||
|
"description100x100": "كبير - للمزارعين المتمرسين",
|
||||||
|
"description120x60": "مستطيل - استخدام أقصى للمساحة",
|
||||||
|
"plants1to2": "1-2 نباتات",
|
||||||
|
"plants2to4": "2-4 نباتات",
|
||||||
|
"plants4to6": "4-6 نباتات",
|
||||||
|
"plants3to6": "3-6 نباتات"
|
||||||
|
};
|
||||||
@@ -3,7 +3,8 @@ export default {
|
|||||||
"new": "قيد التنفيذ",
|
"new": "قيد التنفيذ",
|
||||||
"pending": "جديد",
|
"pending": "جديد",
|
||||||
"processing": "قيد التنفيذ",
|
"processing": "قيد التنفيذ",
|
||||||
"cancelled": "ملغاة",
|
"paid": "مدفوع",
|
||||||
|
"cancelled": "ملغي",
|
||||||
"shipped": "تم الشحن",
|
"shipped": "تم الشحن",
|
||||||
"delivered": "تم التوصيل",
|
"delivered": "تم التوصيل",
|
||||||
"return": "إرجاع",
|
"return": "إرجاع",
|
||||||
@@ -24,6 +25,7 @@ export default {
|
|||||||
"cancelOrder": "إلغاء الطلب"
|
"cancelOrder": "إلغاء الطلب"
|
||||||
},
|
},
|
||||||
"noOrders": "لم تقم بوضع أي طلبات بعد.",
|
"noOrders": "لم تقم بوضع أي طلبات بعد.",
|
||||||
|
"trackShipment": "تتبع الشحنة",
|
||||||
"details": {
|
"details": {
|
||||||
"title": "تفاصيل الطلب: {{orderId}}",
|
"title": "تفاصيل الطلب: {{orderId}}",
|
||||||
"deliveryAddress": "عنوان التوصيل",
|
"deliveryAddress": "عنوان التوصيل",
|
||||||
@@ -36,14 +38,13 @@ export default {
|
|||||||
"item": "العنصر",
|
"item": "العنصر",
|
||||||
"quantity": "الكمية",
|
"quantity": "الكمية",
|
||||||
"price": "السعر",
|
"price": "السعر",
|
||||||
"vat": "ضريبة القيمة المضافة",
|
|
||||||
"total": "الإجمالي",
|
"total": "الإجمالي",
|
||||||
"cancelOrder": "إلغاء الطلب"
|
"cancelOrder": "إلغاء الطلب"
|
||||||
},
|
},
|
||||||
"cancelConfirm": {
|
"cancelConfirm": {
|
||||||
"title": "إلغاء الطلب",
|
"title": "إلغاء الطلب",
|
||||||
"message": "هل أنت متأكد أنك تريد إلغاء هذا الطلب؟",
|
"message": "هل أنت متأكد أنك تريد إلغاء هذا الطلب؟",
|
||||||
"confirm": "إلغاء الطلب",
|
"confirm": "إلغاء",
|
||||||
"cancelling": "جارٍ الإلغاء..."
|
"cancelling": "جارٍ الإلغاء..."
|
||||||
},
|
},
|
||||||
"processing": "يتم إكمال الطلب..."
|
"processing": "يتم إكمال الطلب..."
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ export default {
|
|||||||
"notFoundDescription": "المنتج الذي تبحث عنه غير موجود أو تم إزالته.",
|
"notFoundDescription": "المنتج الذي تبحث عنه غير موجود أو تم إزالته.",
|
||||||
"backToHome": "العودة إلى الصفحة الرئيسية",
|
"backToHome": "العودة إلى الصفحة الرئيسية",
|
||||||
"error": "خطأ",
|
"error": "خطأ",
|
||||||
"articleNumber": "رقم الصنف",
|
"articleNumber": "رقم المنتج",
|
||||||
"manufacturer": "الشركة المصنعة",
|
"manufacturer": "الشركة المصنعة",
|
||||||
"inclVat": "شامل {{vat}}% ضريبة القيمة المضافة",
|
"inclVat": "شامل {{vat}}% ضريبة القيمة المضافة",
|
||||||
|
"inclVatSimple": "شامل ضريبة القيمة المضافة",
|
||||||
"priceUnit": "{{price}}/{{unit}}",
|
"priceUnit": "{{price}}/{{unit}}",
|
||||||
"new": "جديد",
|
"new": "جديد",
|
||||||
"weeks": "أسابيع",
|
"weeks": "أسابيع",
|
||||||
@@ -15,7 +16,7 @@ export default {
|
|||||||
"inclVatFooter": "شامل {{vat}}% ضريبة القيمة المضافة,*",
|
"inclVatFooter": "شامل {{vat}}% ضريبة القيمة المضافة,*",
|
||||||
"availability": "التوفر",
|
"availability": "التوفر",
|
||||||
"inStock": "متوفر في المخزون",
|
"inStock": "متوفر في المخزون",
|
||||||
"comingSoon": "قريبًا متوفر",
|
"comingSoon": "قريبًا",
|
||||||
"deliveryTime": "مدة التوصيل",
|
"deliveryTime": "مدة التوصيل",
|
||||||
"inclShort": "شامل",
|
"inclShort": "شامل",
|
||||||
"vatShort": "ضريبة القيمة المضافة",
|
"vatShort": "ضريبة القيمة المضافة",
|
||||||
@@ -32,10 +33,10 @@ export default {
|
|||||||
"similarProducts": "منتجات مشابهة",
|
"similarProducts": "منتجات مشابهة",
|
||||||
"countDisplay": {
|
"countDisplay": {
|
||||||
"noProducts": "0 منتجات",
|
"noProducts": "0 منتجات",
|
||||||
"oneProduct": "منتج واحد",
|
"oneProduct": "1 منتج",
|
||||||
"multipleProducts": "{{count}} منتجات",
|
"multipleProducts": "{{count}} منتجات",
|
||||||
"filteredProducts": "{{filtered}} من {{total}} منتجات",
|
"filteredProducts": "{{filtered}} من {{total}} منتجات",
|
||||||
"filteredOneProduct": "{{filtered}} من منتج واحد",
|
"filteredOneProduct": "{{filtered}} من 1 منتج",
|
||||||
"xOfYProducts": "{{x}} من {{y}} منتجات"
|
"xOfYProducts": "{{x}} من {{y}} منتجات"
|
||||||
},
|
},
|
||||||
"removeFiltersToSee": "قم بإزالة الفلاتر لرؤية المنتجات",
|
"removeFiltersToSee": "قم بإزالة الفلاتر لرؤية المنتجات",
|
||||||
|
|||||||
61
src/i18n/locales/ar/productDialogs.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export default {
|
||||||
|
"questionTitle": "سؤال عن المنتج",
|
||||||
|
"questionSubtitle": "هل لديك سؤال عن هذا المنتج؟ نحن سعداء بمساعدتك.",
|
||||||
|
"questionSuccess": "شكرًا على سؤالك! سنرد عليك في أقرب وقت ممكن.",
|
||||||
|
"nameLabel": "الاسم",
|
||||||
|
"namePlaceholder": "اسمك",
|
||||||
|
"emailLabel": "البريد الإلكتروني",
|
||||||
|
"emailPlaceholder": "your.email@example.com",
|
||||||
|
"questionLabel": "سؤالك",
|
||||||
|
"questionPlaceholder": "صف سؤالك عن هذا المنتج...",
|
||||||
|
"photosLabelQuestion": "أرفق صورًا مع سؤالك (اختياري)",
|
||||||
|
"submitQuestion": "إرسال السؤال",
|
||||||
|
"sending": "جارٍ الإرسال...",
|
||||||
|
|
||||||
|
"ratingTitle": "قيم المنتج",
|
||||||
|
"ratingSubtitle": "شارك تجربتك مع هذا المنتج وساعد العملاء الآخرين في اتخاذ قرارهم.",
|
||||||
|
"ratingSuccess": "شكرًا على تقييمك! سيتم نشره بعد المراجعة.",
|
||||||
|
"emailHelper": "لن يتم نشر بريدك الإلكتروني",
|
||||||
|
"ratingLabel": "التقييم *",
|
||||||
|
"pleaseRate": "يرجى التقييم",
|
||||||
|
"ratingStars": "{{rating}} من 5 نجوم",
|
||||||
|
"reviewLabel": "تقييمك (اختياري)",
|
||||||
|
"reviewPlaceholder": "صف تجاربك مع هذا المنتج...",
|
||||||
|
"photosLabelRating": "أرفق صورًا مع تقييمك (اختياري)",
|
||||||
|
"submitRating": "إرسال التقييم",
|
||||||
|
"errorGeneric": "حدث خطأ",
|
||||||
|
"errorPhotos": "خطأ في معالجة الصور",
|
||||||
|
|
||||||
|
"availabilityTitle": "طلب التوفر",
|
||||||
|
"availabilitySubtitle": "هذا المنتج غير متوفر حاليًا. سنكون سعداء بإبلاغك بمجرد عودته للمخزون.",
|
||||||
|
"availabilitySuccessEmail": "شكرًا على طلبك! سنخطرك عبر البريد الإلكتروني بمجرد توفر المنتج مرة أخرى.",
|
||||||
|
"availabilitySuccessTelegram": "شكرًا على طلبك! سنخطرك عبر تيليجرام بمجرد توفر المنتج مرة أخرى.",
|
||||||
|
"notificationMethodLabel": "كيف تود أن يتم إعلامك؟",
|
||||||
|
"telegramBotLabel": "بوت تيليجرام",
|
||||||
|
"telegramIdLabel": "معرف تيليجرام",
|
||||||
|
"telegramPlaceholder": "@اسمكعلىتيليجرام أو معرف تيليجرام",
|
||||||
|
"telegramHelper": "أدخل اسم المستخدم الخاص بك على تيليجرام (مع @) أو معرف تيليجرام",
|
||||||
|
"messageLabel": "رسالة (اختياري)",
|
||||||
|
"messagePlaceholder": "معلومات إضافية أو أسئلة...",
|
||||||
|
"submitAvailability": "طلب التوفر",
|
||||||
|
|
||||||
|
"photoUploadSelect": "اختر الصور",
|
||||||
|
"photoUploadErrorMaxFiles": "الحد الأقصى {{max}} ملفات مسموح بها",
|
||||||
|
"photoUploadErrorFileType": "مسموح فقط بملفات الصور (JPEG, PNG, GIF, WebP)",
|
||||||
|
"photoUploadErrorFileSize": "الملف كبير جدًا. الحد الأقصى: {{maxSize}} ميجابايت",
|
||||||
|
"photoUploadSelectedFiles": "{{count}} ملف(ملفات) مختارة",
|
||||||
|
"photoUploadCompressed": "(تم الضغط للرفع)",
|
||||||
|
"photoUploadRemove": "إزالة الصورة",
|
||||||
|
"photoUploadLabelDefault": "أرفق صورًا (اختياري)",
|
||||||
|
|
||||||
|
"shareTitle": "مشاركة",
|
||||||
|
"shareEmbed": "تضمين",
|
||||||
|
"shareCopyLink": "نسخ الرابط",
|
||||||
|
"shareSuccessEmbed": "تم نسخ كود التضمين إلى الحافظة!",
|
||||||
|
"shareErrorEmbed": "حدث خطأ أثناء نسخ كود التضمين",
|
||||||
|
"shareSuccessLink": "تم نسخ الرابط إلى الحافظة!",
|
||||||
|
"shareWhatsAppText": "شوف المنتج ده: {{name}}",
|
||||||
|
"shareTelegramText": "شوف المنتج ده: {{name}}",
|
||||||
|
"shareEmailSubject": "توصية بمنتج",
|
||||||
|
"shareEmailBody": "مرحبًا،\n\nحابب أوصي لك بالمنتج ده:\n\n{{name}}\n{{url}}\n\nمع أطيب التحيات"
|
||||||
|
};
|
||||||
@@ -5,14 +5,16 @@ export default {
|
|||||||
"profile": "Профил",
|
"profile": "Профил",
|
||||||
"email": "Имейл",
|
"email": "Имейл",
|
||||||
"password": "Парола",
|
"password": "Парола",
|
||||||
|
"newPassword": "Нова парола",
|
||||||
"confirmPassword": "Потвърдете паролата",
|
"confirmPassword": "Потвърдете паролата",
|
||||||
"forgotPassword": "Забравена парола?",
|
"forgotPassword": "Забравена парола?",
|
||||||
"loginWithGoogle": "Вход с Google",
|
"loginWithGoogle": "Вход с Google",
|
||||||
"or": "ИЛИ",
|
"or": "ИЛИ",
|
||||||
"privacyAccept": "С натискане на \"Вход с Google\" приемам",
|
"privacyAccept": "С натискането на \"Вход с Google\" приемам",
|
||||||
"privacyPolicy": "Политиката за поверителност",
|
"privacyPolicy": "Политиката за поверителност",
|
||||||
"passwordMinLength": "Паролата трябва да е поне 8 символа",
|
"passwordMinLength": "Паролата трябва да е поне 8 символа",
|
||||||
"newPasswordMinLength": "Новата парола трябва да е поне 8 символа",
|
"newPasswordMinLength": "Новата парола трябва да е поне 8 символа",
|
||||||
|
"backToHome": "Обратно към началната страница",
|
||||||
"menu": {
|
"menu": {
|
||||||
"profile": "Профил",
|
"profile": "Профил",
|
||||||
"myProfile": "Моят профил",
|
"myProfile": "Моят профил",
|
||||||
@@ -21,5 +23,28 @@ export default {
|
|||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
"adminDashboard": "Админ табло",
|
"adminDashboard": "Админ табло",
|
||||||
"adminUsers": "Админ потребители"
|
"adminUsers": "Админ потребители"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"title": "Нулиране на парола",
|
||||||
|
"button": "Нулиране на парола",
|
||||||
|
"success": "Вашата парола беше успешно нулирана! Скоро ще бъдете пренасочени към вход...",
|
||||||
|
"invalidToken": "Няма валиден токен. Моля, използвайте линка от имейла си.",
|
||||||
|
"error": "Грешка при нулиране на паролата",
|
||||||
|
"emailSent": "Линк за нулиране на паролата беше изпратен на вашия имейл.",
|
||||||
|
"emailError": "Грешка при изпращане на имейла"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fillAllFields": "Моля, попълнете всички полета",
|
||||||
|
"invalidEmail": "Моля, въведете валиден имейл адрес",
|
||||||
|
"passwordsNotMatch": "Паролите не съвпадат",
|
||||||
|
"passwordsNotMatchShort": "Паролите не съвпадат",
|
||||||
|
"enterEmail": "Моля, въведете вашия имейл адрес",
|
||||||
|
"loginFailed": "Входът не бе успешен",
|
||||||
|
"registerFailed": "Регистрацията не бе успешна",
|
||||||
|
"googleLoginFailed": "Вход с Google не бе успешен",
|
||||||
|
"emailExists": "Потребител с този имейл вече съществува. Моля, използвайте друг имейл или влезте в системата."
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"registerComplete": "Регистрацията беше успешна. Сега можете да влезете."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import navigation from './navigation.js';
|
|||||||
import auth from './auth.js';
|
import auth from './auth.js';
|
||||||
import cart from './cart.js';
|
import cart from './cart.js';
|
||||||
import product from './product.js';
|
import product from './product.js';
|
||||||
|
import productDialogs from './productDialogs.js';
|
||||||
import search from './search.js';
|
import search from './search.js';
|
||||||
import sorting from './sorting.js';
|
import sorting from './sorting.js';
|
||||||
import chat from './chat.js';
|
import chat from './chat.js';
|
||||||
@@ -18,6 +19,7 @@ import pages from './pages.js';
|
|||||||
import orders from './orders.js';
|
import orders from './orders.js';
|
||||||
import settings from './settings.js';
|
import settings from './settings.js';
|
||||||
import common from './common.js';
|
import common from './common.js';
|
||||||
|
import kitConfig from './kitConfig.js';
|
||||||
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
||||||
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
||||||
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
||||||
@@ -35,6 +37,7 @@ export default {
|
|||||||
"auth": auth,
|
"auth": auth,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"product": product,
|
"product": product,
|
||||||
|
"productDialogs": productDialogs,
|
||||||
"search": search,
|
"search": search,
|
||||||
"sorting": sorting,
|
"sorting": sorting,
|
||||||
"chat": chat,
|
"chat": chat,
|
||||||
@@ -50,6 +53,7 @@ export default {
|
|||||||
"orders": orders,
|
"orders": orders,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"common": common,
|
"common": common,
|
||||||
|
"kitConfig": kitConfig,
|
||||||
"legalDatenschutzBasic": legalDatenschutzBasic,
|
"legalDatenschutzBasic": legalDatenschutzBasic,
|
||||||
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
||||||
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
||||||
|
|||||||
43
src/i18n/locales/bg/kitConfig.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export default {
|
||||||
|
"pageTitle": "🌱 Конфигуратор за Growbox",
|
||||||
|
"pageSubtitle": "Създайте перфектната си вътрешна система за отглеждане",
|
||||||
|
"bundleDiscountTitle": "🎯 Вземете отстъпка за комплект!",
|
||||||
|
"loadingProducts": "Зареждане на продукти за growbox...",
|
||||||
|
"loadingLighting": "Зареждане на осветителни продукти...",
|
||||||
|
"loadingVentilation": "Зареждане на вентилационни продукти...",
|
||||||
|
"loadingExtras": "Зареждане на допълнителни продукти...",
|
||||||
|
"noProductsAvailable": "Няма налични продукти за този размер",
|
||||||
|
"noLightingAvailable": "Няма подходящи лампи за размер на палатка {{shape}}.",
|
||||||
|
"noVentilationAvailable": "Няма подходяща вентилация за размер на палатка {{shape}}.",
|
||||||
|
"noExtrasAvailable": "Няма налични допълнения",
|
||||||
|
"selectShapeTitle": "1. Изберете форма на growbox",
|
||||||
|
"selectShapeSubtitle": "Първо изберете основната площ на вашия growbox",
|
||||||
|
"selectProductTitle": "2. Изберете продукт за growbox",
|
||||||
|
"selectProductSubtitle": "Изберете подходящия продукт за вашия {{shape}} growbox",
|
||||||
|
"selectLightingTitle": "3. Изберете осветление",
|
||||||
|
"selectLightingTitleShape": "3. Изберете осветление - {{shape}}",
|
||||||
|
"selectLightingSubtitle": "Моля, първо изберете размер на палатка.",
|
||||||
|
"selectVentilationTitle": "4. Изберете вентилация",
|
||||||
|
"selectVentilationTitleShape": "4. Изберете вентилация - {{shape}}",
|
||||||
|
"selectVentilationSubtitle": "Моля, първо изберете размер на палатка.",
|
||||||
|
"selectExtrasTitle": "5. Добавете допълнения (по избор)",
|
||||||
|
"yourConfiguration": "🎯 Вашата конфигурация",
|
||||||
|
"growboxLabel": "Growbox: {{name}}",
|
||||||
|
"lightingLabel": "Осветление: {{name}}",
|
||||||
|
"ventilationLabel": "Вентилация: {{name}}",
|
||||||
|
"extraLabel": "Допълнение: {{name}}",
|
||||||
|
"totalPrice": "Обща цена:",
|
||||||
|
"addToCart": "Добави в количката",
|
||||||
|
"selected": "✓ Избрано",
|
||||||
|
"notDeliverable": "Не е налично за доставка",
|
||||||
|
"noPrice": "Няма цена",
|
||||||
|
"setName": "Комплект Growbox - {{shape}}",
|
||||||
|
"description60x60": "Компактен - идеален за малки пространства",
|
||||||
|
"description80x80": "Среден - перфектен баланс",
|
||||||
|
"description100x100": "Голям - за опитни отглеждачи",
|
||||||
|
"description120x60": "Правоъгълен - максимално използване на пространството",
|
||||||
|
"plants1to2": "1-2 растения",
|
||||||
|
"plants2to4": "2-4 растения",
|
||||||
|
"plants4to6": "4-6 растения",
|
||||||
|
"plants3to6": "3-6 растения"
|
||||||
|
};
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
export default {
|
export default {
|
||||||
"status": {
|
"status": {
|
||||||
"new": "В процес",
|
"new": "в процес",
|
||||||
"pending": "Нова",
|
"pending": "Ново",
|
||||||
"processing": "В процес",
|
"processing": "в процес",
|
||||||
"cancelled": "Отменена",
|
"paid": "Платено",
|
||||||
"shipped": "Изпратена",
|
"cancelled": "Отменено",
|
||||||
"delivered": "Доставена",
|
"shipped": "Изпратено",
|
||||||
|
"delivered": "Доставено",
|
||||||
"return": "Връщане",
|
"return": "Връщане",
|
||||||
"partialReturn": "Частично връщане",
|
"partialReturn": "Частично връщане",
|
||||||
"partialDelivered": "Частично доставена"
|
"partialDelivered": "Частично доставено"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"orderNumber": "Номер на поръчка",
|
"orderNumber": "Номер на поръчка",
|
||||||
@@ -24,6 +25,7 @@ export default {
|
|||||||
"cancelOrder": "Отмени поръчката"
|
"cancelOrder": "Отмени поръчката"
|
||||||
},
|
},
|
||||||
"noOrders": "Все още не сте направили поръчки.",
|
"noOrders": "Все още не сте направили поръчки.",
|
||||||
|
"trackShipment": "Проследи пратката",
|
||||||
"details": {
|
"details": {
|
||||||
"title": "Подробности за поръчка: {{orderId}}",
|
"title": "Подробности за поръчка: {{orderId}}",
|
||||||
"deliveryAddress": "Адрес за доставка",
|
"deliveryAddress": "Адрес за доставка",
|
||||||
@@ -36,15 +38,14 @@ export default {
|
|||||||
"item": "Артикул",
|
"item": "Артикул",
|
||||||
"quantity": "Количество",
|
"quantity": "Количество",
|
||||||
"price": "Цена",
|
"price": "Цена",
|
||||||
"vat": "ДДС",
|
|
||||||
"total": "Общо",
|
"total": "Общо",
|
||||||
"cancelOrder": "Отмени поръчката"
|
"cancelOrder": "Отмени поръчката"
|
||||||
},
|
},
|
||||||
"cancelConfirm": {
|
"cancelConfirm": {
|
||||||
"title": "Отмяна на поръчка",
|
"title": "Отмени поръчката",
|
||||||
"message": "Сигурни ли сте, че искате да отмените тази поръчка?",
|
"message": "Сигурни ли сте, че искате да отмените тази поръчка?",
|
||||||
"confirm": "Отмени поръчката",
|
"confirm": "Отмени",
|
||||||
"cancelling": "Отмяна..."
|
"cancelling": "Отмяна..."
|
||||||
},
|
},
|
||||||
"processing": "Поръчката се обработва...",
|
"processing": "Поръчката се обработва..."
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,24 +8,25 @@ export default {
|
|||||||
"articleNumber": "Номер на артикул",
|
"articleNumber": "Номер на артикул",
|
||||||
"manufacturer": "Производител",
|
"manufacturer": "Производител",
|
||||||
"inclVat": "вкл. {{vat}}% ДДС",
|
"inclVat": "вкл. {{vat}}% ДДС",
|
||||||
|
"inclVatSimple": "вкл. ДДС",
|
||||||
"priceUnit": "{{price}}/{{unit}}",
|
"priceUnit": "{{price}}/{{unit}}",
|
||||||
"new": "Нов",
|
"new": "Нов",
|
||||||
"weeks": "седмици",
|
"weeks": "Седмици",
|
||||||
"arriving": "Пристигане:",
|
"arriving": "Пристигане:",
|
||||||
"inclVatFooter": "вкл. {{vat}}% ДДС,*",
|
"inclVatFooter": "вкл. {{vat}}% ДДС,*",
|
||||||
"availability": "Наличност",
|
"availability": "Наличност",
|
||||||
"inStock": "налично",
|
"inStock": "налично на склад",
|
||||||
"comingSoon": "Очаква се скоро",
|
"comingSoon": "Очаква се скоро",
|
||||||
"deliveryTime": "Срок на доставка",
|
"deliveryTime": "Срок на доставка",
|
||||||
"inclShort": "вкл.",
|
"inclShort": "вкл.",
|
||||||
"vatShort": "ДДС",
|
"vatShort": "ДДС",
|
||||||
"weight": "Тегло: {{weight}} кг",
|
"weight": "Тегло: {{weight}} кг",
|
||||||
"youSave": "Спестявате: {{amount}}",
|
"youSave": "Спестявате: {{amount}}",
|
||||||
"cheaperThanIndividual": "По-евтино от индивидуална покупка",
|
"cheaperThanIndividual": "По-евтино от закупуване поотделно",
|
||||||
"pickupPrice": "Цена за вземане: 19,90 € на резник.",
|
"pickupPrice": "Цена за вземане: 19,90 € на резник.",
|
||||||
"consistsOf": "Състои се от:",
|
"consistsOf": "Състои се от:",
|
||||||
"loadingComponentDetails": "{{index}}. Зареждане на детайли за компонента...",
|
"loadingComponentDetails": "{{index}}. Зареждане на детайли за компонента...",
|
||||||
"loadingProduct": "Продуктът се зарежда...",
|
"loadingProduct": "Зареждане на продукта...",
|
||||||
"individualPriceTotal": "Обща индивидуална цена:",
|
"individualPriceTotal": "Обща индивидуална цена:",
|
||||||
"setPrice": "Цена на комплекта:",
|
"setPrice": "Цена на комплекта:",
|
||||||
"yourSavings": "Вашите спестявания:",
|
"yourSavings": "Вашите спестявания:",
|
||||||
|
|||||||
61
src/i18n/locales/bg/productDialogs.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export default {
|
||||||
|
"questionTitle": "Въпрос за продукта",
|
||||||
|
"questionSubtitle": "Имате ли въпрос за този продукт? Ще се радваме да ви помогнем.",
|
||||||
|
"questionSuccess": "Благодарим ви за въпроса! Ще се свържем с вас възможно най-скоро.",
|
||||||
|
"nameLabel": "Име",
|
||||||
|
"namePlaceholder": "Вашето име",
|
||||||
|
"emailLabel": "Имейл",
|
||||||
|
"emailPlaceholder": "your.email@example.com",
|
||||||
|
"questionLabel": "Вашият въпрос",
|
||||||
|
"questionPlaceholder": "Опишете въпроса си за този продукт...",
|
||||||
|
"photosLabelQuestion": "Прикачете снимки към въпроса си (по избор)",
|
||||||
|
"submitQuestion": "Изпрати въпроса",
|
||||||
|
"sending": "Изпращане...",
|
||||||
|
|
||||||
|
"ratingTitle": "Оценете продукта",
|
||||||
|
"ratingSubtitle": "Споделете опита си с този продукт и помогнете на други клиенти да вземат решение.",
|
||||||
|
"ratingSuccess": "Благодарим ви за вашия отзив! Той ще бъде публикуван след проверка.",
|
||||||
|
"emailHelper": "Вашият имейл няма да бъде публикуван",
|
||||||
|
"ratingLabel": "Оценка *",
|
||||||
|
"pleaseRate": "Моля, оценете",
|
||||||
|
"ratingStars": "{{rating}} от 5 звезди",
|
||||||
|
"reviewLabel": "Вашият отзив (по избор)",
|
||||||
|
"reviewPlaceholder": "Опишете опита си с този продукт...",
|
||||||
|
"photosLabelRating": "Прикачете снимки към отзива си (по избор)",
|
||||||
|
"submitRating": "Изпрати отзива",
|
||||||
|
"errorGeneric": "Възникна грешка",
|
||||||
|
"errorPhotos": "Грешка при обработка на снимките",
|
||||||
|
|
||||||
|
"availabilityTitle": "Запитване за наличност",
|
||||||
|
"availabilitySubtitle": "Този продукт в момента не е наличен. Ще се радваме да ви уведомим веднага щом бъде наличен отново.",
|
||||||
|
"availabilitySuccessEmail": "Благодарим ви за запитването! Ще ви уведомим по имейл веднага щом продуктът отново е наличен.",
|
||||||
|
"availabilitySuccessTelegram": "Благодарим ви за запитването! Ще ви уведомим чрез Telegram веднага щом продуктът отново е наличен.",
|
||||||
|
"notificationMethodLabel": "Как бихте искали да бъдете уведомени?",
|
||||||
|
"telegramBotLabel": "Telegram Bot",
|
||||||
|
"telegramIdLabel": "Telegram ID",
|
||||||
|
"telegramPlaceholder": "@yourTelegramName or Telegram ID",
|
||||||
|
"telegramHelper": "Въведете вашето потребителско име в Telegram (с @) или Telegram ID",
|
||||||
|
"messageLabel": "Съобщение (по избор)",
|
||||||
|
"messagePlaceholder": "Допълнителна информация или въпроси...",
|
||||||
|
"submitAvailability": "Запитване за наличност",
|
||||||
|
|
||||||
|
"photoUploadSelect": "Изберете снимки",
|
||||||
|
"photoUploadErrorMaxFiles": "Максимум {{max}} файла са разрешени",
|
||||||
|
"photoUploadErrorFileType": "Разрешени са само файлове с изображения (JPEG, PNG, GIF, WebP)",
|
||||||
|
"photoUploadErrorFileSize": "Файлът е твърде голям. Максимум: {{maxSize}}MB",
|
||||||
|
"photoUploadSelectedFiles": "{{count}} файл(ове) избрани",
|
||||||
|
"photoUploadCompressed": "(компресиран за качване)",
|
||||||
|
"photoUploadRemove": "Премахни изображението",
|
||||||
|
"photoUploadLabelDefault": "Прикачи снимки (по избор)",
|
||||||
|
|
||||||
|
"shareTitle": "Сподели",
|
||||||
|
"shareEmbed": "Вграждане",
|
||||||
|
"shareCopyLink": "Копирай линка",
|
||||||
|
"shareSuccessEmbed": "Кодът за вграждане е копиран в клипборда!",
|
||||||
|
"shareErrorEmbed": "Грешка при копиране на кода за вграждане",
|
||||||
|
"shareSuccessLink": "Линкът е копиран в клипборда!",
|
||||||
|
"shareWhatsAppText": "Виж този продукт: {{name}}",
|
||||||
|
"shareTelegramText": "Виж този продукт: {{name}}",
|
||||||
|
"shareEmailSubject": "Препоръка за продукт",
|
||||||
|
"shareEmailBody": "Здравейте,\n\nИскам да ви препоръчам този продукт:\n\n{{name}}\n{{url}}\n\nПоздрави"
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ export default {
|
|||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": "Heslo",
|
"password": "Heslo",
|
||||||
|
"newPassword": "Nové heslo",
|
||||||
"confirmPassword": "Potvrdit heslo",
|
"confirmPassword": "Potvrdit heslo",
|
||||||
"forgotPassword": "Zapomněli jste heslo?",
|
"forgotPassword": "Zapomněli jste heslo?",
|
||||||
"loginWithGoogle": "Přihlásit se přes Google",
|
"loginWithGoogle": "Přihlásit se přes Google",
|
||||||
@@ -13,6 +14,7 @@ export default {
|
|||||||
"privacyPolicy": "Zásadami ochrany osobních údajů",
|
"privacyPolicy": "Zásadami ochrany osobních údajů",
|
||||||
"passwordMinLength": "Heslo musí mít alespoň 8 znaků",
|
"passwordMinLength": "Heslo musí mít alespoň 8 znaků",
|
||||||
"newPasswordMinLength": "Nové heslo musí mít alespoň 8 znaků",
|
"newPasswordMinLength": "Nové heslo musí mít alespoň 8 znaků",
|
||||||
|
"backToHome": "Zpět na domovskou stránku",
|
||||||
"menu": {
|
"menu": {
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"myProfile": "Můj profil",
|
"myProfile": "Můj profil",
|
||||||
@@ -21,5 +23,28 @@ export default {
|
|||||||
"settings": "Nastavení",
|
"settings": "Nastavení",
|
||||||
"adminDashboard": "Admin Dashboard",
|
"adminDashboard": "Admin Dashboard",
|
||||||
"adminUsers": "Admin Users"
|
"adminUsers": "Admin Users"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"title": "Obnovení hesla",
|
||||||
|
"button": "Obnovit heslo",
|
||||||
|
"success": "Vaše heslo bylo úspěšně obnoveno! Brzy budete přesměrováni na přihlášení...",
|
||||||
|
"invalidToken": "Nebyl nalezen platný token. Použijte prosím odkaz z vašeho e-mailu.",
|
||||||
|
"error": "Chyba při obnově hesla",
|
||||||
|
"emailSent": "Odkaz pro obnovení hesla byl odeslán na vaši e-mailovou adresu.",
|
||||||
|
"emailError": "Chyba při odesílání e-mailu"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fillAllFields": "Vyplňte prosím všechna pole",
|
||||||
|
"invalidEmail": "Zadejte platnou e-mailovou adresu",
|
||||||
|
"passwordsNotMatch": "Hesla se neshodují",
|
||||||
|
"passwordsNotMatchShort": "Hesla se neshodují",
|
||||||
|
"enterEmail": "Zadejte prosím svou e-mailovou adresu",
|
||||||
|
"loginFailed": "Přihlášení selhalo",
|
||||||
|
"registerFailed": "Registrace selhala",
|
||||||
|
"googleLoginFailed": "Přihlášení přes Google selhalo",
|
||||||
|
"emailExists": "Uživatel s touto e-mailovou adresou již existuje. Použijte prosím jinou e-mailovou adresu nebo se přihlaste."
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"registerComplete": "Registrace byla úspěšná. Nyní se můžete přihlásit."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import navigation from './navigation.js';
|
|||||||
import auth from './auth.js';
|
import auth from './auth.js';
|
||||||
import cart from './cart.js';
|
import cart from './cart.js';
|
||||||
import product from './product.js';
|
import product from './product.js';
|
||||||
|
import productDialogs from './productDialogs.js';
|
||||||
import search from './search.js';
|
import search from './search.js';
|
||||||
import sorting from './sorting.js';
|
import sorting from './sorting.js';
|
||||||
import chat from './chat.js';
|
import chat from './chat.js';
|
||||||
@@ -18,6 +19,7 @@ import pages from './pages.js';
|
|||||||
import orders from './orders.js';
|
import orders from './orders.js';
|
||||||
import settings from './settings.js';
|
import settings from './settings.js';
|
||||||
import common from './common.js';
|
import common from './common.js';
|
||||||
|
import kitConfig from './kitConfig.js';
|
||||||
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
||||||
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
||||||
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
||||||
@@ -35,6 +37,7 @@ export default {
|
|||||||
"auth": auth,
|
"auth": auth,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"product": product,
|
"product": product,
|
||||||
|
"productDialogs": productDialogs,
|
||||||
"search": search,
|
"search": search,
|
||||||
"sorting": sorting,
|
"sorting": sorting,
|
||||||
"chat": chat,
|
"chat": chat,
|
||||||
@@ -50,6 +53,7 @@ export default {
|
|||||||
"orders": orders,
|
"orders": orders,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"common": common,
|
"common": common,
|
||||||
|
"kitConfig": kitConfig,
|
||||||
"legalDatenschutzBasic": legalDatenschutzBasic,
|
"legalDatenschutzBasic": legalDatenschutzBasic,
|
||||||
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
||||||
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
||||||
|
|||||||
43
src/i18n/locales/cs/kitConfig.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export default {
|
||||||
|
"pageTitle": "🌱 Konfigurátor Growboxu",
|
||||||
|
"pageSubtitle": "Sestavte si svůj dokonalý indoor grow setup",
|
||||||
|
"bundleDiscountTitle": "🎯 Zajistěte si slevu na balíček!",
|
||||||
|
"loadingProducts": "Načítání produktů growboxu...",
|
||||||
|
"loadingLighting": "Načítání osvětlení...",
|
||||||
|
"loadingVentilation": "Načítání ventilace...",
|
||||||
|
"loadingExtras": "Načítání doplňků...",
|
||||||
|
"noProductsAvailable": "Pro tuto velikost nejsou k dispozici žádné produkty",
|
||||||
|
"noLightingAvailable": "Pro velikost stanu {{shape}} nejsou k dispozici žádná vhodná světla.",
|
||||||
|
"noVentilationAvailable": "Pro velikost stanu {{shape}} není k dispozici vhodná ventilace.",
|
||||||
|
"noExtrasAvailable": "Žádné doplňky nejsou k dispozici",
|
||||||
|
"selectShapeTitle": "1. Vyberte tvar growboxu",
|
||||||
|
"selectShapeSubtitle": "Nejprve vyberte základní plochu vašeho growboxu",
|
||||||
|
"selectProductTitle": "2. Vyberte produkt growboxu",
|
||||||
|
"selectProductSubtitle": "Vyberte správný produkt pro váš growbox {{shape}}",
|
||||||
|
"selectLightingTitle": "3. Vyberte osvětlení",
|
||||||
|
"selectLightingTitleShape": "3. Vyberte osvětlení - {{shape}}",
|
||||||
|
"selectLightingSubtitle": "Nejprve prosím vyberte velikost stanu.",
|
||||||
|
"selectVentilationTitle": "4. Vyberte ventilaci",
|
||||||
|
"selectVentilationTitleShape": "4. Vyberte ventilaci - {{shape}}",
|
||||||
|
"selectVentilationSubtitle": "Nejprve prosím vyberte velikost stanu.",
|
||||||
|
"selectExtrasTitle": "5. Přidejte doplňky (volitelné)",
|
||||||
|
"yourConfiguration": "🎯 Vaše konfigurace",
|
||||||
|
"growboxLabel": "Growbox: {{name}}",
|
||||||
|
"lightingLabel": "Osvětlení: {{name}}",
|
||||||
|
"ventilationLabel": "Ventilace: {{name}}",
|
||||||
|
"extraLabel": "Doplněk: {{name}}",
|
||||||
|
"totalPrice": "Celková cena:",
|
||||||
|
"addToCart": "Přidat do košíku",
|
||||||
|
"selected": "✓ Vybráno",
|
||||||
|
"notDeliverable": "Nedodává se",
|
||||||
|
"noPrice": "Bez ceny",
|
||||||
|
"setName": "Sada growboxu - {{shape}}",
|
||||||
|
"description60x60": "Kompaktní - ideální pro malé prostory",
|
||||||
|
"description80x80": "Střední - perfektní rovnováha",
|
||||||
|
"description100x100": "Velký - pro zkušené pěstitele",
|
||||||
|
"description120x60": "Obdélníkový - maximální využití prostoru",
|
||||||
|
"plants1to2": "1-2 rostliny",
|
||||||
|
"plants2to4": "2-4 rostliny",
|
||||||
|
"plants4to6": "4-6 rostlin",
|
||||||
|
"plants3to6": "3-6 rostlin"
|
||||||
|
};
|
||||||
@@ -1,50 +1,51 @@
|
|||||||
export default {
|
export default {
|
||||||
"status": {
|
"status": {
|
||||||
"new": "Probíhá",
|
"new": "probíhá",
|
||||||
"pending": "Nová",
|
"pending": "Nové",
|
||||||
"processing": "Probíhá",
|
"processing": "probíhá",
|
||||||
"cancelled": "Zrušeno",
|
"paid": "Zaplaceno",
|
||||||
"shipped": "Odesláno",
|
"cancelled": "Zrušeno",
|
||||||
"delivered": "Doručeno",
|
"shipped": "Odesláno",
|
||||||
"return": "Vrácení",
|
"delivered": "Doručeno",
|
||||||
"partialReturn": "Částečné vrácení",
|
"return": "Vrácení",
|
||||||
"partialDelivered": "Částečně doručeno"
|
"partialReturn": "Částečné vrácení",
|
||||||
|
"partialDelivered": "Částečně doručeno"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"orderNumber": "Číslo objednávky",
|
"orderNumber": "Číslo objednávky",
|
||||||
"date": "Datum",
|
"date": "Datum",
|
||||||
"status": "Stav",
|
"status": "Stav",
|
||||||
"items": "Položky",
|
"items": "Položky",
|
||||||
"total": "Celkem",
|
"total": "Celkem",
|
||||||
"actions": "Akce",
|
"actions": "Akce",
|
||||||
"viewDetails": "Zobrazit detaily"
|
"viewDetails": "Zobrazit detaily"
|
||||||
},
|
},
|
||||||
"tooltips": {
|
"tooltips": {
|
||||||
"viewDetails": "Zobrazit detaily",
|
"viewDetails": "Zobrazit detaily",
|
||||||
"cancelOrder": "Zrušit objednávku"
|
"cancelOrder": "Zrušit objednávku"
|
||||||
},
|
},
|
||||||
"noOrders": "Ještě jste neprovedli žádné objednávky.",
|
"noOrders": "Ještě jste neprovedli žádné objednávky.",
|
||||||
|
"trackShipment": "Sledovat zásilku",
|
||||||
"details": {
|
"details": {
|
||||||
"title": "Detaily objednávky: {{orderId}}",
|
"title": "Detaily objednávky: {{orderId}}",
|
||||||
"deliveryAddress": "Dodací adresa",
|
"deliveryAddress": "Dodací adresa",
|
||||||
"invoiceAddress": "Fakturační adresa",
|
"invoiceAddress": "Fakturační adresa",
|
||||||
"orderDetails": "Detaily objednávky",
|
"orderDetails": "Detaily objednávky",
|
||||||
"deliveryMethod": "Způsob doručení:",
|
"deliveryMethod": "Způsob doručení:",
|
||||||
"paymentMethod": "Způsob platby:",
|
"paymentMethod": "Způsob platby:",
|
||||||
"notSpecified": "Nespecifikováno",
|
"notSpecified": "Nespecifikováno",
|
||||||
"orderedItems": "Objednané položky",
|
"orderedItems": "Objednané položky",
|
||||||
"item": "Položka",
|
"item": "Položka",
|
||||||
"quantity": "Množství",
|
"quantity": "Množství",
|
||||||
"price": "Cena",
|
"price": "Cena",
|
||||||
"vat": "DPH",
|
"total": "Celkem",
|
||||||
"total": "Celkem",
|
"cancelOrder": "Zrušit objednávku"
|
||||||
"cancelOrder": "Zrušit objednávku"
|
|
||||||
},
|
},
|
||||||
"cancelConfirm": {
|
"cancelConfirm": {
|
||||||
"title": "Zrušit objednávku",
|
"title": "Zrušit objednávku",
|
||||||
"message": "Opravdu chcete tuto objednávku zrušit?",
|
"message": "Opravdu chcete tuto objednávku zrušit?",
|
||||||
"confirm": "Zrušit objednávku",
|
"confirm": "Zrušit",
|
||||||
"cancelling": "Rušení..."
|
"cancelling": "Rušení..."
|
||||||
},
|
},
|
||||||
"processing": "Objednávka se dokončuje...",
|
"processing": "Objednávka se dokončuje..."
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ export default {
|
|||||||
"articleNumber": "Číslo artiklu",
|
"articleNumber": "Číslo artiklu",
|
||||||
"manufacturer": "Výrobce",
|
"manufacturer": "Výrobce",
|
||||||
"inclVat": "včetně {{vat}}% DPH",
|
"inclVat": "včetně {{vat}}% DPH",
|
||||||
|
"inclVatSimple": "včetně DPH",
|
||||||
"priceUnit": "{{price}}/{{unit}}",
|
"priceUnit": "{{price}}/{{unit}}",
|
||||||
"new": "Nové",
|
"new": "Nové",
|
||||||
"weeks": "týdny",
|
"weeks": "Týdny",
|
||||||
"arriving": "Příchod:",
|
"arriving": "Příjezd:",
|
||||||
"inclVatFooter": "včetně {{vat}}% DPH,*",
|
"inclVatFooter": "včetně {{vat}}% DPH,*",
|
||||||
"availability": "Dostupnost",
|
"availability": "Dostupnost",
|
||||||
"inStock": "skladem",
|
"inStock": "skladem",
|
||||||
@@ -22,10 +23,10 @@ export default {
|
|||||||
"weight": "Hmotnost: {{weight}} kg",
|
"weight": "Hmotnost: {{weight}} kg",
|
||||||
"youSave": "Ušetříte: {{amount}}",
|
"youSave": "Ušetříte: {{amount}}",
|
||||||
"cheaperThanIndividual": "Levnější než nákup jednotlivě",
|
"cheaperThanIndividual": "Levnější než nákup jednotlivě",
|
||||||
"pickupPrice": "Cena za vyzvednutí: 19,90 € za řízek.",
|
"pickupPrice": "Cena za odběr: 19,90 € za řízek.",
|
||||||
"consistsOf": "Skládá se z:",
|
"consistsOf": "Skládá se z:",
|
||||||
"loadingComponentDetails": "{{index}}. Načítání detailů komponenty...",
|
"loadingComponentDetails": "{{index}}. Načítání detailů komponenty...",
|
||||||
"loadingProduct": "Produkt se načítá...",
|
"loadingProduct": "Načítání produktu...",
|
||||||
"individualPriceTotal": "Celková cena jednotlivě:",
|
"individualPriceTotal": "Celková cena jednotlivě:",
|
||||||
"setPrice": "Cena sady:",
|
"setPrice": "Cena sady:",
|
||||||
"yourSavings": "Vaše úspory:",
|
"yourSavings": "Vaše úspory:",
|
||||||
|
|||||||
61
src/i18n/locales/cs/productDialogs.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export default {
|
||||||
|
"questionTitle": "Otázka ohledně produktu",
|
||||||
|
"questionSubtitle": "Máte otázku ohledně tohoto produktu? Rádi vám pomůžeme.",
|
||||||
|
"questionSuccess": "Děkujeme za vaši otázku! Ozveme se vám co nejdříve.",
|
||||||
|
"nameLabel": "Jméno",
|
||||||
|
"namePlaceholder": "Vaše jméno",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailPlaceholder": "vas.email@priklad.cz",
|
||||||
|
"questionLabel": "Vaše otázka",
|
||||||
|
"questionPlaceholder": "Popište svou otázku ohledně tohoto produktu...",
|
||||||
|
"photosLabelQuestion": "Přiložte fotografie k vaší otázce (volitelné)",
|
||||||
|
"submitQuestion": "Odeslat otázku",
|
||||||
|
"sending": "Odesílání...",
|
||||||
|
|
||||||
|
"ratingTitle": "Ohodnoťte produkt",
|
||||||
|
"ratingSubtitle": "Podělte se o své zkušenosti s tímto produktem a pomozte ostatním zákazníkům s rozhodnutím.",
|
||||||
|
"ratingSuccess": "Děkujeme za vaši recenzi! Bude zveřejněna po ověření.",
|
||||||
|
"emailHelper": "Váš email nebude zveřejněn",
|
||||||
|
"ratingLabel": "Hodnocení *",
|
||||||
|
"pleaseRate": "Prosím ohodnoťte",
|
||||||
|
"ratingStars": "{{rating}} z 5 hvězdiček",
|
||||||
|
"reviewLabel": "Vaše recenze (volitelné)",
|
||||||
|
"reviewPlaceholder": "Popište své zkušenosti s tímto produktem...",
|
||||||
|
"photosLabelRating": "Přiložte fotografie k vaší recenzi (volitelné)",
|
||||||
|
"submitRating": "Odeslat recenzi",
|
||||||
|
"errorGeneric": "Došlo k chybě",
|
||||||
|
"errorPhotos": "Chyba při zpracování fotografií",
|
||||||
|
|
||||||
|
"availabilityTitle": "Požádejte o dostupnost",
|
||||||
|
"availabilitySubtitle": "Tento produkt momentálně není dostupný. Rádi vás informujeme, jakmile bude opět skladem.",
|
||||||
|
"availabilitySuccessEmail": "Děkujeme za váš požadavek! Jakmile bude produkt opět dostupný, budeme vás informovat e-mailem.",
|
||||||
|
"availabilitySuccessTelegram": "Děkujeme za váš požadavek! Jakmile bude produkt opět dostupný, budeme vás informovat přes Telegram.",
|
||||||
|
"notificationMethodLabel": "Jak chcete být informováni?",
|
||||||
|
"telegramBotLabel": "Telegram Bot",
|
||||||
|
"telegramIdLabel": "Telegram ID",
|
||||||
|
"telegramPlaceholder": "@vaseTelegramJmeno nebo Telegram ID",
|
||||||
|
"telegramHelper": "Zadejte své uživatelské jméno na Telegramu (s @) nebo Telegram ID",
|
||||||
|
"messageLabel": "Zpráva (volitelné)",
|
||||||
|
"messagePlaceholder": "Další informace nebo otázky...",
|
||||||
|
"submitAvailability": "Požádat o dostupnost",
|
||||||
|
|
||||||
|
"photoUploadSelect": "Vybrat fotografie",
|
||||||
|
"photoUploadErrorMaxFiles": "Maximálně {{max}} souborů povoleno",
|
||||||
|
"photoUploadErrorFileType": "Jsou povoleny pouze obrazové soubory (JPEG, PNG, GIF, WebP)",
|
||||||
|
"photoUploadErrorFileSize": "Soubor je příliš velký. Maximum: {{maxSize}}MB",
|
||||||
|
"photoUploadSelectedFiles": "Vybráno {{count}} souborů",
|
||||||
|
"photoUploadCompressed": "(komprimováno pro nahrání)",
|
||||||
|
"photoUploadRemove": "Odstranit obrázek",
|
||||||
|
"photoUploadLabelDefault": "Přiložit fotografie (volitelné)",
|
||||||
|
|
||||||
|
"shareTitle": "Sdílet",
|
||||||
|
"shareEmbed": "Vložit",
|
||||||
|
"shareCopyLink": "Kopírovat odkaz",
|
||||||
|
"shareSuccessEmbed": "Kód pro vložení zkopírován do schránky!",
|
||||||
|
"shareErrorEmbed": "Chyba při kopírování kódu pro vložení",
|
||||||
|
"shareSuccessLink": "Odkaz zkopírován do schránky!",
|
||||||
|
"shareWhatsAppText": "Podívejte se na tento produkt: {{name}}",
|
||||||
|
"shareTelegramText": "Podívejte se na tento produkt: {{name}}",
|
||||||
|
"shareEmailSubject": "Doporučení produktu",
|
||||||
|
"shareEmailBody": "Dobrý den,\n\nrád bych vám doporučil tento produkt:\n\n{{name}}\n{{url}}\n\nS pozdravem"
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ export default {
|
|||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"email": "E-Mail",
|
"email": "E-Mail",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
|
"newPassword": "Neues Passwort",
|
||||||
"confirmPassword": "Passwort bestätigen",
|
"confirmPassword": "Passwort bestätigen",
|
||||||
"forgotPassword": "Passwort vergessen?",
|
"forgotPassword": "Passwort vergessen?",
|
||||||
"loginWithGoogle": "Mit Google anmelden",
|
"loginWithGoogle": "Mit Google anmelden",
|
||||||
@@ -13,6 +14,7 @@ export default {
|
|||||||
"privacyPolicy": "Datenschutzbestimmungen",
|
"privacyPolicy": "Datenschutzbestimmungen",
|
||||||
"passwordMinLength": "Das Passwort muss mindestens 8 Zeichen lang sein",
|
"passwordMinLength": "Das Passwort muss mindestens 8 Zeichen lang sein",
|
||||||
"newPasswordMinLength": "Das neue Passwort muss mindestens 8 Zeichen lang sein",
|
"newPasswordMinLength": "Das neue Passwort muss mindestens 8 Zeichen lang sein",
|
||||||
|
"backToHome": "Zurück zur Startseite",
|
||||||
"menu": {
|
"menu": {
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"myProfile": "Mein Profil",
|
"myProfile": "Mein Profil",
|
||||||
@@ -21,5 +23,28 @@ export default {
|
|||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"adminDashboard": "Admin Dashboard",
|
"adminDashboard": "Admin Dashboard",
|
||||||
"adminUsers": "Admin Users"
|
"adminUsers": "Admin Users"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"title": "Passwort zurücksetzen",
|
||||||
|
"button": "Passwort zurücksetzen",
|
||||||
|
"success": "Ihr Passwort wurde erfolgreich zurückgesetzt! Sie werden in Kürze zur Anmeldung weitergeleitet...",
|
||||||
|
"invalidToken": "Kein gültiger Token gefunden. Bitte verwenden Sie den Link aus Ihrer E-Mail.",
|
||||||
|
"error": "Fehler beim Zurücksetzen des Passworts",
|
||||||
|
"emailSent": "Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.",
|
||||||
|
"emailError": "Fehler beim Senden der E-Mail"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fillAllFields": "Bitte füllen Sie alle Felder aus",
|
||||||
|
"invalidEmail": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
|
||||||
|
"passwordsNotMatch": "Die Passwörter stimmen nicht überein",
|
||||||
|
"passwordsNotMatchShort": "Passwörter stimmen nicht überein",
|
||||||
|
"enterEmail": "Bitte geben Sie Ihre E-Mail-Adresse ein",
|
||||||
|
"loginFailed": "Anmeldung fehlgeschlagen",
|
||||||
|
"registerFailed": "Registrierung fehlgeschlagen",
|
||||||
|
"googleLoginFailed": "Google-Anmeldung fehlgeschlagen",
|
||||||
|
"emailExists": "Ein Benutzer mit dieser E-Mail-Adresse existiert bereits. Bitte verwenden Sie eine andere E-Mail-Adresse oder melden Sie sich an."
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"registerComplete": "Registrierung erfolgreich. Sie können sich jetzt anmelden."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -3,6 +3,7 @@ import navigation from './navigation.js';
|
|||||||
import auth from './auth.js';
|
import auth from './auth.js';
|
||||||
import cart from './cart.js';
|
import cart from './cart.js';
|
||||||
import product from './product.js';
|
import product from './product.js';
|
||||||
|
import productDialogs from './productDialogs.js';
|
||||||
import search from './search.js';
|
import search from './search.js';
|
||||||
import sorting from './sorting.js';
|
import sorting from './sorting.js';
|
||||||
import chat from './chat.js';
|
import chat from './chat.js';
|
||||||
@@ -18,6 +19,7 @@ import pages from './pages.js';
|
|||||||
import orders from './orders.js';
|
import orders from './orders.js';
|
||||||
import settings from './settings.js';
|
import settings from './settings.js';
|
||||||
import common from './common.js';
|
import common from './common.js';
|
||||||
|
import kitConfig from './kitConfig.js';
|
||||||
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
||||||
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
||||||
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
||||||
@@ -35,6 +37,7 @@ export default {
|
|||||||
"auth": auth,
|
"auth": auth,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"product": product,
|
"product": product,
|
||||||
|
"productDialogs": productDialogs,
|
||||||
"search": search,
|
"search": search,
|
||||||
"sorting": sorting,
|
"sorting": sorting,
|
||||||
"chat": chat,
|
"chat": chat,
|
||||||
@@ -50,6 +53,7 @@ export default {
|
|||||||
"orders": orders,
|
"orders": orders,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"common": common,
|
"common": common,
|
||||||
|
"kitConfig": kitConfig,
|
||||||
"legalDatenschutzBasic": legalDatenschutzBasic,
|
"legalDatenschutzBasic": legalDatenschutzBasic,
|
||||||
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
||||||
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
||||||
|
|||||||
44
src/i18n/locales/de/kitConfig.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
export default {
|
||||||
|
"pageTitle": "🌱 Growbox Konfigurator",
|
||||||
|
"pageSubtitle": "Stelle dein perfektes Indoor Grow Setup zusammen",
|
||||||
|
"bundleDiscountTitle": "🎯 Bundle-Rabatt sichern!",
|
||||||
|
"loadingProducts": "Lade Growbox-Produkte...",
|
||||||
|
"loadingLighting": "Lade Beleuchtungs-Produkte...",
|
||||||
|
"loadingVentilation": "Lade Belüftungs-Produkte...",
|
||||||
|
"loadingExtras": "Lade Extras...",
|
||||||
|
"noProductsAvailable": "Keine Produkte für diese Größe verfügbar",
|
||||||
|
"noLightingAvailable": "Keine passenden Lampen für Zeltgröße {{shape}} verfügbar.",
|
||||||
|
"noVentilationAvailable": "Keine passenden Belüftung für Zeltgröße {{shape}} verfügbar.",
|
||||||
|
"noExtrasAvailable": "Keine Extras verfügbar",
|
||||||
|
"selectShapeTitle": "1. Growbox-Form auswählen",
|
||||||
|
"selectShapeSubtitle": "Wähle zuerst die Grundfläche deiner Growbox aus",
|
||||||
|
"selectProductTitle": "2. Growbox Produkt auswählen",
|
||||||
|
"selectProductSubtitle": "Wähle das passende Produkt für deine {{shape}} Growbox",
|
||||||
|
"selectLightingTitle": "3. Beleuchtung wählen",
|
||||||
|
"selectLightingTitleShape": "3. Beleuchtung wählen - {{shape}}",
|
||||||
|
"selectLightingSubtitle": "Bitte wählen Sie zuerst eine Zeltgröße aus.",
|
||||||
|
"selectVentilationTitle": "4. Belüftung auswählen",
|
||||||
|
"selectVentilationTitleShape": "4. Belüftung auswählen - {{shape}}",
|
||||||
|
"selectVentilationSubtitle": "Bitte wählen Sie zuerst eine Zeltgröße aus.",
|
||||||
|
"selectExtrasTitle": "5. Extras hinzufügen (optional)",
|
||||||
|
"yourConfiguration": "🎯 Ihre Konfiguration",
|
||||||
|
"growboxLabel": "Growbox: {{name}}",
|
||||||
|
"lightingLabel": "Beleuchtung: {{name}}",
|
||||||
|
"ventilationLabel": "Belüftung: {{name}}",
|
||||||
|
"extraLabel": "Extra: {{name}}",
|
||||||
|
"totalPrice": "Gesamtpreis:",
|
||||||
|
"addToCart": "In den Warenkorb",
|
||||||
|
"selected": "✓ Ausgewählt",
|
||||||
|
"notDeliverable": "Nicht lieferbar",
|
||||||
|
"noPrice": "Kein Preis",
|
||||||
|
"setName": "Growbox Set - {{shape}}",
|
||||||
|
"description60x60": "Kompakt - ideal für kleine Räume",
|
||||||
|
"description80x80": "Mittel - perfekte Balance",
|
||||||
|
"description100x100": "Groß - für erfahrene Grower",
|
||||||
|
"description120x60": "Rechteckig - maximale Raumnutzung",
|
||||||
|
"plants1to2": "1-2 Pflanzen",
|
||||||
|
"plants2to4": "2-4 Pflanzen",
|
||||||
|
"plants4to6": "4-6 Pflanzen",
|
||||||
|
"plants3to6": "3-6 Pflanzen"
|
||||||
|
};
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@ export default {
|
|||||||
"new": "in Bearbeitung",
|
"new": "in Bearbeitung",
|
||||||
"pending": "Neu",
|
"pending": "Neu",
|
||||||
"processing": "in Bearbeitung",
|
"processing": "in Bearbeitung",
|
||||||
|
"paid": "Bezahlt",
|
||||||
"cancelled": "Storniert",
|
"cancelled": "Storniert",
|
||||||
"shipped": "Verschickt",
|
"shipped": "Verschickt",
|
||||||
"delivered": "Geliefert",
|
"delivered": "Geliefert",
|
||||||
@@ -24,6 +25,7 @@ export default {
|
|||||||
"cancelOrder": "Bestellung stornieren"
|
"cancelOrder": "Bestellung stornieren"
|
||||||
},
|
},
|
||||||
"noOrders": "Sie haben noch keine Bestellungen aufgegeben.",
|
"noOrders": "Sie haben noch keine Bestellungen aufgegeben.",
|
||||||
|
"trackShipment": "Sendung verfolgen",
|
||||||
"details": {
|
"details": {
|
||||||
"title": "Bestelldetails: {{orderId}}",
|
"title": "Bestelldetails: {{orderId}}",
|
||||||
"deliveryAddress": "Lieferadresse",
|
"deliveryAddress": "Lieferadresse",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export default {
|
|||||||
"articleNumber": "Artikelnummer",
|
"articleNumber": "Artikelnummer",
|
||||||
"manufacturer": "Hersteller",
|
"manufacturer": "Hersteller",
|
||||||
"inclVat": "inkl. {{vat}}% MwSt.",
|
"inclVat": "inkl. {{vat}}% MwSt.",
|
||||||
|
"inclVatSimple": "inkl. MwSt.",
|
||||||
"priceUnit": "{{price}}/{{unit}}",
|
"priceUnit": "{{price}}/{{unit}}",
|
||||||
"new": "Neu",
|
"new": "Neu",
|
||||||
"weeks": "Wochen",
|
"weeks": "Wochen",
|
||||||
|
|||||||
62
src/i18n/locales/de/productDialogs.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
export default {
|
||||||
|
"questionTitle": "Frage zum Artikel",
|
||||||
|
"questionSubtitle": "Haben Sie eine Frage zu diesem Artikel? Wir helfen Ihnen gerne weiter.",
|
||||||
|
"questionSuccess": "Vielen Dank für Ihre Frage! Wir werden uns schnellstmöglich bei Ihnen melden.",
|
||||||
|
"nameLabel": "Name",
|
||||||
|
"namePlaceholder": "Ihr Name",
|
||||||
|
"emailLabel": "E-Mail",
|
||||||
|
"emailPlaceholder": "ihre.email@example.com",
|
||||||
|
"questionLabel": "Ihre Frage",
|
||||||
|
"questionPlaceholder": "Beschreiben Sie Ihre Frage zu diesem Artikel...",
|
||||||
|
"photosLabelQuestion": "Fotos zur Frage anhängen (optional)",
|
||||||
|
"submitQuestion": "Frage senden",
|
||||||
|
"sending": "Wird gesendet...",
|
||||||
|
|
||||||
|
"ratingTitle": "Artikel Bewerten",
|
||||||
|
"ratingSubtitle": "Teilen Sie Ihre Erfahrungen mit diesem Artikel und helfen Sie anderen Kunden bei der Entscheidung.",
|
||||||
|
"ratingSuccess": "Vielen Dank für Ihre Bewertung! Sie wird nach Prüfung veröffentlicht.",
|
||||||
|
"emailHelper": "Ihre E-Mail wird nicht veröffentlicht",
|
||||||
|
"ratingLabel": "Bewertung *",
|
||||||
|
"pleaseRate": "Bitte bewerten",
|
||||||
|
"ratingStars": "{{rating}} von 5 Sternen",
|
||||||
|
"reviewLabel": "Ihre Bewertung (optional)",
|
||||||
|
"reviewPlaceholder": "Beschreiben Sie Ihre Erfahrungen mit diesem Artikel...",
|
||||||
|
"photosLabelRating": "Fotos zur Bewertung anhängen (optional)",
|
||||||
|
"submitRating": "Bewertung abgeben",
|
||||||
|
"errorGeneric": "Ein Fehler ist aufgetreten",
|
||||||
|
"errorPhotos": "Fehler beim Verarbeiten der Fotos",
|
||||||
|
|
||||||
|
"availabilityTitle": "Verfügbarkeit anfragen",
|
||||||
|
"availabilitySubtitle": "Dieser Artikel ist derzeit nicht verfügbar. Gerne informieren wir Sie, sobald er wieder lieferbar ist.",
|
||||||
|
"availabilitySuccessEmail": "Vielen Dank für Ihre Anfrage! Wir werden Sie per E-Mail informieren, sobald der Artikel wieder verfügbar ist.",
|
||||||
|
"availabilitySuccessTelegram": "Vielen Dank für Ihre Anfrage! Wir werden Sie über Telegram informieren, sobald der Artikel wieder verfügbar ist.",
|
||||||
|
"notificationMethodLabel": "Wie möchten Sie benachrichtigt werden?",
|
||||||
|
"telegramBotLabel": "Telegram Bot",
|
||||||
|
"telegramIdLabel": "Telegram ID",
|
||||||
|
"telegramPlaceholder": "@IhrTelegramName oder Telegram ID",
|
||||||
|
"telegramHelper": "Geben Sie Ihren Telegram-Benutzernamen (mit @) oder Ihre Telegram-ID ein",
|
||||||
|
"messageLabel": "Nachricht (optional)",
|
||||||
|
"messagePlaceholder": "Zusätzliche Informationen oder Fragen...",
|
||||||
|
"submitAvailability": "Verfügbarkeit anfragen",
|
||||||
|
|
||||||
|
"photoUploadSelect": "Fotos auswählen",
|
||||||
|
"photoUploadErrorMaxFiles": "Maximal {{max}} Dateien erlaubt",
|
||||||
|
"photoUploadErrorFileType": "Nur Bilddateien (JPEG, PNG, GIF, WebP) sind erlaubt",
|
||||||
|
"photoUploadErrorFileSize": "Datei zu groß. Maximum: {{maxSize}}MB",
|
||||||
|
"photoUploadSelectedFiles": "{{count}} Datei(en) ausgewählt",
|
||||||
|
"photoUploadCompressed": "(komprimiert für Upload)",
|
||||||
|
"photoUploadRemove": "Bild entfernen",
|
||||||
|
"photoUploadLabelDefault": "Fotos anhängen (optional)",
|
||||||
|
|
||||||
|
"shareTitle": "Teilen",
|
||||||
|
"shareEmbed": "Einbetten",
|
||||||
|
"shareCopyLink": "Link kopieren",
|
||||||
|
"shareSuccessEmbed": "Einbettungscode wurde in die Zwischenablage kopiert!",
|
||||||
|
"shareErrorEmbed": "Fehler beim Kopieren des Einbettungscodes",
|
||||||
|
"shareSuccessLink": "Link wurde in die Zwischenablage kopiert!",
|
||||||
|
"shareWhatsAppText": "Schau dir dieses Produkt an: {{name}}",
|
||||||
|
"shareTelegramText": "Schau dir dieses Produkt an: {{name}}",
|
||||||
|
"shareEmailSubject": "Produktempfehlung",
|
||||||
|
"shareEmailBody": "Hallo,\n\nich möchte dir dieses Produkt empfehlen:\n\n{{name}}\n{{url}}\n\nViele Grüße"
|
||||||
|
};
|
||||||
|
|
||||||
@@ -5,14 +5,16 @@ export default {
|
|||||||
"profile": "Προφίλ",
|
"profile": "Προφίλ",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": "Κωδικός",
|
"password": "Κωδικός",
|
||||||
|
"newPassword": "Νέος κωδικός",
|
||||||
"confirmPassword": "Επιβεβαίωση κωδικού",
|
"confirmPassword": "Επιβεβαίωση κωδικού",
|
||||||
"forgotPassword": "Ξεχάσατε τον κωδικό;",
|
"forgotPassword": "Ξεχάσατε τον κωδικό;",
|
||||||
"loginWithGoogle": "Σύνδεση με Google",
|
"loginWithGoogle": "Σύνδεση με Google",
|
||||||
"or": "Ή",
|
"or": "Ή",
|
||||||
"privacyAccept": "Κάνοντας κλικ στο \"Σύνδεση με Google\" αποδέχομαι την",
|
"privacyAccept": "Κάνοντας κλικ στο \"Σύνδεση με Google\" αποδέχομαι την",
|
||||||
"privacyPolicy": "Πολιτική Απορρήτου",
|
"privacyPolicy": "Πολιτική απορρήτου",
|
||||||
"passwordMinLength": "Ο κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες",
|
"passwordMinLength": "Ο κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες",
|
||||||
"newPasswordMinLength": "Ο νέος κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες",
|
"newPasswordMinLength": "Ο νέος κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες",
|
||||||
|
"backToHome": "Επιστροφή στην αρχική σελίδα",
|
||||||
"menu": {
|
"menu": {
|
||||||
"profile": "Προφίλ",
|
"profile": "Προφίλ",
|
||||||
"myProfile": "Το προφίλ μου",
|
"myProfile": "Το προφίλ μου",
|
||||||
@@ -21,5 +23,28 @@ export default {
|
|||||||
"settings": "Ρυθμίσεις",
|
"settings": "Ρυθμίσεις",
|
||||||
"adminDashboard": "Πίνακας διαχείρισης",
|
"adminDashboard": "Πίνακας διαχείρισης",
|
||||||
"adminUsers": "Διαχειριστές"
|
"adminUsers": "Διαχειριστές"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"title": "Επαναφορά κωδικού",
|
||||||
|
"button": "Επαναφορά κωδικού",
|
||||||
|
"success": "Ο κωδικός σας επαναφέρθηκε με επιτυχία! Θα ανακατευθυνθείτε στη σύνδεση σύντομα...",
|
||||||
|
"invalidToken": "Δεν βρέθηκε έγκυρο διακριτικό. Παρακαλώ χρησιμοποιήστε τον σύνδεσμο από το email σας.",
|
||||||
|
"error": "Σφάλμα κατά την επαναφορά του κωδικού",
|
||||||
|
"emailSent": "Ένας σύνδεσμος για επαναφορά του κωδικού σας έχει σταλεί στη διεύθυνση email σας.",
|
||||||
|
"emailError": "Σφάλμα κατά την αποστολή του email"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fillAllFields": "Παρακαλώ συμπληρώστε όλα τα πεδία",
|
||||||
|
"invalidEmail": "Παρακαλώ εισάγετε μια έγκυρη διεύθυνση email",
|
||||||
|
"passwordsNotMatch": "Οι κωδικοί δεν ταιριάζουν",
|
||||||
|
"passwordsNotMatchShort": "Οι κωδικοί δεν ταιριάζουν",
|
||||||
|
"enterEmail": "Παρακαλώ εισάγετε τη διεύθυνση email σας",
|
||||||
|
"loginFailed": "Η σύνδεση απέτυχε",
|
||||||
|
"registerFailed": "Η εγγραφή απέτυχε",
|
||||||
|
"googleLoginFailed": "Η σύνδεση με Google απέτυχε",
|
||||||
|
"emailExists": "Υπάρχει ήδη χρήστης με αυτή τη διεύθυνση email. Παρακαλώ χρησιμοποιήστε άλλη διεύθυνση ή συνδεθείτε."
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"registerComplete": "Η εγγραφή ολοκληρώθηκε με επιτυχία. Μπορείτε τώρα να συνδεθείτε."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import navigation from './navigation.js';
|
|||||||
import auth from './auth.js';
|
import auth from './auth.js';
|
||||||
import cart from './cart.js';
|
import cart from './cart.js';
|
||||||
import product from './product.js';
|
import product from './product.js';
|
||||||
|
import productDialogs from './productDialogs.js';
|
||||||
import search from './search.js';
|
import search from './search.js';
|
||||||
import sorting from './sorting.js';
|
import sorting from './sorting.js';
|
||||||
import chat from './chat.js';
|
import chat from './chat.js';
|
||||||
@@ -18,6 +19,7 @@ import pages from './pages.js';
|
|||||||
import orders from './orders.js';
|
import orders from './orders.js';
|
||||||
import settings from './settings.js';
|
import settings from './settings.js';
|
||||||
import common from './common.js';
|
import common from './common.js';
|
||||||
|
import kitConfig from './kitConfig.js';
|
||||||
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
||||||
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
||||||
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
||||||
@@ -35,6 +37,7 @@ export default {
|
|||||||
"auth": auth,
|
"auth": auth,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"product": product,
|
"product": product,
|
||||||
|
"productDialogs": productDialogs,
|
||||||
"search": search,
|
"search": search,
|
||||||
"sorting": sorting,
|
"sorting": sorting,
|
||||||
"chat": chat,
|
"chat": chat,
|
||||||
@@ -50,6 +53,7 @@ export default {
|
|||||||
"orders": orders,
|
"orders": orders,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"common": common,
|
"common": common,
|
||||||
|
"kitConfig": kitConfig,
|
||||||
"legalDatenschutzBasic": legalDatenschutzBasic,
|
"legalDatenschutzBasic": legalDatenschutzBasic,
|
||||||
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
||||||
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
||||||
|
|||||||
43
src/i18n/locales/el/kitConfig.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export default {
|
||||||
|
"pageTitle": "🌱 Διαμορφωτής Growbox",
|
||||||
|
"pageSubtitle": "Συνθέστε το τέλειο εσωτερικό σύστημα καλλιέργειας",
|
||||||
|
"bundleDiscountTitle": "🎯 Εξασφαλίστε έκπτωση πακέτου!",
|
||||||
|
"loadingProducts": "Φόρτωση προϊόντων growbox...",
|
||||||
|
"loadingLighting": "Φόρτωση προϊόντων φωτισμού...",
|
||||||
|
"loadingVentilation": "Φόρτωση προϊόντων αερισμού...",
|
||||||
|
"loadingExtras": "Φόρτωση επιπλέον...",
|
||||||
|
"noProductsAvailable": "Δεν υπάρχουν διαθέσιμα προϊόντα για αυτό το μέγεθος",
|
||||||
|
"noLightingAvailable": "Δεν υπάρχουν κατάλληλα φώτα για το μέγεθος σκηνής {{shape}}.",
|
||||||
|
"noVentilationAvailable": "Δεν υπάρχει κατάλληλος αερισμός για το μέγεθος σκηνής {{shape}}.",
|
||||||
|
"noExtrasAvailable": "Δεν υπάρχουν επιπλέον διαθέσιμα",
|
||||||
|
"selectShapeTitle": "1. Επιλέξτε το σχήμα του growbox",
|
||||||
|
"selectShapeSubtitle": "Επιλέξτε πρώτα την επιφάνεια βάσης του growbox σας",
|
||||||
|
"selectProductTitle": "2. Επιλέξτε προϊόν growbox",
|
||||||
|
"selectProductSubtitle": "Επιλέξτε το κατάλληλο προϊόν για το growbox {{shape}} σας",
|
||||||
|
"selectLightingTitle": "3. Επιλέξτε φωτισμό",
|
||||||
|
"selectLightingTitleShape": "3. Επιλέξτε φωτισμό - {{shape}}",
|
||||||
|
"selectLightingSubtitle": "Παρακαλώ επιλέξτε πρώτα το μέγεθος της σκηνής.",
|
||||||
|
"selectVentilationTitle": "4. Επιλέξτε αερισμό",
|
||||||
|
"selectVentilationTitleShape": "4. Επιλέξτε αερισμό - {{shape}}",
|
||||||
|
"selectVentilationSubtitle": "Παρακαλώ επιλέξτε πρώτα το μέγεθος της σκηνής.",
|
||||||
|
"selectExtrasTitle": "5. Προσθέστε επιπλέον (προαιρετικά)",
|
||||||
|
"yourConfiguration": "🎯 Η διαμόρφωσή σας",
|
||||||
|
"growboxLabel": "Growbox: {{name}}",
|
||||||
|
"lightingLabel": "Φωτισμός: {{name}}",
|
||||||
|
"ventilationLabel": "Αερισμός: {{name}}",
|
||||||
|
"extraLabel": "Επιπλέον: {{name}}",
|
||||||
|
"totalPrice": "Συνολική τιμή:",
|
||||||
|
"addToCart": "Προσθήκη στο καλάθι",
|
||||||
|
"selected": "✓ Επιλεγμένο",
|
||||||
|
"notDeliverable": "Μη διαθέσιμο για παράδοση",
|
||||||
|
"noPrice": "Χωρίς τιμή",
|
||||||
|
"setName": "Σετ Growbox - {{shape}}",
|
||||||
|
"description60x60": "Συμπαγές - ιδανικό για μικρούς χώρους",
|
||||||
|
"description80x80": "Μεσαίο - τέλεια ισορροπία",
|
||||||
|
"description100x100": "Μεγάλο - για έμπειρους καλλιεργητές",
|
||||||
|
"description120x60": "Ορθογώνιο - μέγιστη χρήση χώρου",
|
||||||
|
"plants1to2": "1-2 φυτά",
|
||||||
|
"plants2to4": "2-4 φυτά",
|
||||||
|
"plants4to6": "4-6 φυτά",
|
||||||
|
"plants3to6": "3-6 φυτά"
|
||||||
|
};
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
export default {
|
export default {
|
||||||
"status": {
|
"status": {
|
||||||
"new": "Σε εξέλιξη",
|
"new": "σε εξέλιξη",
|
||||||
"pending": "Νέο",
|
"pending": "Νέο",
|
||||||
"processing": "Σε εξέλιξη",
|
"processing": "σε εξέλιξη",
|
||||||
|
"paid": "Πληρωμένο",
|
||||||
"cancelled": "Ακυρώθηκε",
|
"cancelled": "Ακυρώθηκε",
|
||||||
"shipped": "Απεστάλη",
|
"shipped": "Απεσταλμένο",
|
||||||
"delivered": "Παραδόθηκε",
|
"delivered": "Παραδόθηκε",
|
||||||
"return": "Επιστροφή",
|
"return": "Επιστροφή",
|
||||||
"partialReturn": "Μερική επιστροφή",
|
"partialReturn": "Μερική επιστροφή",
|
||||||
@@ -24,10 +25,11 @@ export default {
|
|||||||
"cancelOrder": "Ακύρωση παραγγελίας"
|
"cancelOrder": "Ακύρωση παραγγελίας"
|
||||||
},
|
},
|
||||||
"noOrders": "Δεν έχετε κάνει ακόμα καμία παραγγελία.",
|
"noOrders": "Δεν έχετε κάνει ακόμα καμία παραγγελία.",
|
||||||
|
"trackShipment": "Παρακολούθηση αποστολής",
|
||||||
"details": {
|
"details": {
|
||||||
"title": "Λεπτομέρειες παραγγελίας: {{orderId}}",
|
"title": "Λεπτομέρειες παραγγελίας: {{orderId}}",
|
||||||
"deliveryAddress": "Διεύθυνση παράδοσης",
|
"deliveryAddress": "Διεύθυνση παράδοσης",
|
||||||
"invoiceAddress": "Διεύθυνση τιμολόγησης",
|
"invoiceAddress": "Διεύθυνση τιμολογίου",
|
||||||
"orderDetails": "Λεπτομέρειες παραγγελίας",
|
"orderDetails": "Λεπτομέρειες παραγγελίας",
|
||||||
"deliveryMethod": "Τρόπος παράδοσης:",
|
"deliveryMethod": "Τρόπος παράδοσης:",
|
||||||
"paymentMethod": "Τρόπος πληρωμής:",
|
"paymentMethod": "Τρόπος πληρωμής:",
|
||||||
@@ -36,15 +38,14 @@ export default {
|
|||||||
"item": "Είδος",
|
"item": "Είδος",
|
||||||
"quantity": "Ποσότητα",
|
"quantity": "Ποσότητα",
|
||||||
"price": "Τιμή",
|
"price": "Τιμή",
|
||||||
"vat": "ΦΠΑ",
|
|
||||||
"total": "Σύνολο",
|
"total": "Σύνολο",
|
||||||
"cancelOrder": "Ακύρωση παραγγελίας"
|
"cancelOrder": "Ακύρωση παραγγελίας"
|
||||||
},
|
},
|
||||||
"cancelConfirm": {
|
"cancelConfirm": {
|
||||||
"title": "Ακύρωση παραγγελίας",
|
"title": "Ακύρωση παραγγελίας",
|
||||||
"message": "Είστε σίγουροι ότι θέλετε να ακυρώσετε αυτήν την παραγγελία;",
|
"message": "Είστε σίγουροι ότι θέλετε να ακυρώσετε αυτή την παραγγελία;",
|
||||||
"confirm": "Ακύρωση παραγγελίας",
|
"confirm": "Ακύρωση",
|
||||||
"cancelling": "Ακύρωση..."
|
"cancelling": "Ακύρωση σε εξέλιξη..."
|
||||||
},
|
},
|
||||||
"processing": "Η παραγγελία ολοκληρώνεται..."
|
"processing": "Η παραγγελία ολοκληρώνεται..."
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ export default {
|
|||||||
"articleNumber": "Αριθμός άρθρου",
|
"articleNumber": "Αριθμός άρθρου",
|
||||||
"manufacturer": "Κατασκευαστής",
|
"manufacturer": "Κατασκευαστής",
|
||||||
"inclVat": "συμπ. {{vat}}% ΦΠΑ",
|
"inclVat": "συμπ. {{vat}}% ΦΠΑ",
|
||||||
|
"inclVatSimple": "συμπ. ΦΠΑ",
|
||||||
"priceUnit": "{{price}}/{{unit}}",
|
"priceUnit": "{{price}}/{{unit}}",
|
||||||
"new": "Νέο",
|
"new": "Νέο",
|
||||||
"weeks": "εβδομάδες",
|
"weeks": "Εβδομάδες",
|
||||||
"arriving": "Άφιξη:",
|
"arriving": "Άφιξη:",
|
||||||
"inclVatFooter": "συμπ. {{vat}}% ΦΠΑ,*",
|
"inclVatFooter": "συμπ. {{vat}}% ΦΠΑ,*",
|
||||||
"availability": "Διαθεσιμότητα",
|
"availability": "Διαθεσιμότητα",
|
||||||
@@ -28,7 +29,7 @@ export default {
|
|||||||
"loadingProduct": "Φόρτωση προϊόντος...",
|
"loadingProduct": "Φόρτωση προϊόντος...",
|
||||||
"individualPriceTotal": "Συνολική τιμή μεμονωμένων:",
|
"individualPriceTotal": "Συνολική τιμή μεμονωμένων:",
|
||||||
"setPrice": "Τιμή σετ:",
|
"setPrice": "Τιμή σετ:",
|
||||||
"yourSavings": "Η εξοικονόμησή σας:",
|
"yourSavings": "Οι εξοικονομήσεις σας:",
|
||||||
"similarProducts": "Παρόμοια προϊόντα",
|
"similarProducts": "Παρόμοια προϊόντα",
|
||||||
"countDisplay": {
|
"countDisplay": {
|
||||||
"noProducts": "0 προϊόντα",
|
"noProducts": "0 προϊόντα",
|
||||||
|
|||||||
61
src/i18n/locales/el/productDialogs.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export default {
|
||||||
|
"questionTitle": "Ερώτηση σχετικά με το προϊόν",
|
||||||
|
"questionSubtitle": "Έχετε κάποια ερώτηση για αυτό το προϊόν; Είμαστε εδώ για να σας βοηθήσουμε.",
|
||||||
|
"questionSuccess": "Ευχαριστούμε για την ερώτησή σας! Θα επικοινωνήσουμε μαζί σας το συντομότερο δυνατό.",
|
||||||
|
"nameLabel": "Όνομα",
|
||||||
|
"namePlaceholder": "Το όνομά σας",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailPlaceholder": "your.email@example.com",
|
||||||
|
"questionLabel": "Η ερώτησή σας",
|
||||||
|
"questionPlaceholder": "Περιγράψτε την ερώτησή σας σχετικά με αυτό το προϊόν...",
|
||||||
|
"photosLabelQuestion": "Επισυνάψτε φωτογραφίες στην ερώτησή σας (προαιρετικό)",
|
||||||
|
"submitQuestion": "Αποστολή ερώτησης",
|
||||||
|
"sending": "Αποστολή...",
|
||||||
|
|
||||||
|
"ratingTitle": "Αξιολογήστε το προϊόν",
|
||||||
|
"ratingSubtitle": "Μοιραστείτε την εμπειρία σας με αυτό το προϊόν και βοηθήστε άλλους πελάτες να πάρουν την απόφασή τους.",
|
||||||
|
"ratingSuccess": "Ευχαριστούμε για την αξιολόγησή σας! Θα δημοσιευτεί μετά από έλεγχο.",
|
||||||
|
"emailHelper": "Το email σας δεν θα δημοσιευτεί",
|
||||||
|
"ratingLabel": "Αξιολόγηση *",
|
||||||
|
"pleaseRate": "Παρακαλώ αξιολογήστε",
|
||||||
|
"ratingStars": "{{rating}} από 5 αστέρια",
|
||||||
|
"reviewLabel": "Η κριτική σας (προαιρετικό)",
|
||||||
|
"reviewPlaceholder": "Περιγράψτε τις εμπειρίες σας με αυτό το προϊόν...",
|
||||||
|
"photosLabelRating": "Επισυνάψτε φωτογραφίες στην κριτική σας (προαιρετικό)",
|
||||||
|
"submitRating": "Υποβολή κριτικής",
|
||||||
|
"errorGeneric": "Παρουσιάστηκε σφάλμα",
|
||||||
|
"errorPhotos": "Σφάλμα κατά την επεξεργασία των φωτογραφιών",
|
||||||
|
|
||||||
|
"availabilityTitle": "Ζητήστε διαθεσιμότητα",
|
||||||
|
"availabilitySubtitle": "Αυτό το προϊόν δεν είναι διαθέσιμο αυτή τη στιγμή. Θα χαρούμε να σας ενημερώσουμε μόλις είναι ξανά διαθέσιμο.",
|
||||||
|
"availabilitySuccessEmail": "Ευχαριστούμε για το αίτημά σας! Θα σας ενημερώσουμε μέσω email μόλις το προϊόν είναι ξανά διαθέσιμο.",
|
||||||
|
"availabilitySuccessTelegram": "Ευχαριστούμε για το αίτημά σας! Θα σας ενημερώσουμε μέσω Telegram μόλις το προϊόν είναι ξανά διαθέσιμο.",
|
||||||
|
"notificationMethodLabel": "Πώς θέλετε να ειδοποιηθείτε;",
|
||||||
|
"telegramBotLabel": "Telegram Bot",
|
||||||
|
"telegramIdLabel": "Telegram ID",
|
||||||
|
"telegramPlaceholder": "@yourTelegramName or Telegram ID",
|
||||||
|
"telegramHelper": "Εισάγετε το όνομα χρήστη Telegram (με @) ή το Telegram ID σας",
|
||||||
|
"messageLabel": "Μήνυμα (προαιρετικό)",
|
||||||
|
"messagePlaceholder": "Επιπλέον πληροφορίες ή ερωτήσεις...",
|
||||||
|
"submitAvailability": "Ζητήστε διαθεσιμότητα",
|
||||||
|
|
||||||
|
"photoUploadSelect": "Επιλέξτε φωτογραφίες",
|
||||||
|
"photoUploadErrorMaxFiles": "Επιτρέπονται έως {{max}} αρχεία",
|
||||||
|
"photoUploadErrorFileType": "Επιτρέπονται μόνο αρχεία εικόνας (JPEG, PNG, GIF, WebP)",
|
||||||
|
"photoUploadErrorFileSize": "Το αρχείο είναι πολύ μεγάλο. Μέγιστο: {{maxSize}}MB",
|
||||||
|
"photoUploadSelectedFiles": "Επιλέχθηκαν {{count}} αρχεία",
|
||||||
|
"photoUploadCompressed": "(συμπιεσμένο για αποστολή)",
|
||||||
|
"photoUploadRemove": "Αφαίρεση εικόνας",
|
||||||
|
"photoUploadLabelDefault": "Επισύναψη φωτογραφιών (προαιρετικό)",
|
||||||
|
|
||||||
|
"shareTitle": "Κοινοποίηση",
|
||||||
|
"shareEmbed": "Ενσωμάτωση",
|
||||||
|
"shareCopyLink": "Αντιγραφή συνδέσμου",
|
||||||
|
"shareSuccessEmbed": "Ο κώδικας ενσωμάτωσης αντιγράφηκε στο πρόχειρο!",
|
||||||
|
"shareErrorEmbed": "Σφάλμα κατά την αντιγραφή του κώδικα ενσωμάτωσης",
|
||||||
|
"shareSuccessLink": "Ο σύνδεσμος αντιγράφηκε στο πρόχειρο!",
|
||||||
|
"shareWhatsAppText": "Δείτε αυτό το προϊόν: {{name}}",
|
||||||
|
"shareTelegramText": "Δείτε αυτό το προϊόν: {{name}}",
|
||||||
|
"shareEmailSubject": "Σύσταση προϊόντος",
|
||||||
|
"shareEmailBody": "Γεια σας,\n\nΘα ήθελα να σας προτείνω αυτό το προϊόν:\n\n{{name}}\n{{url}}\n\nΜε εκτίμηση"
|
||||||
|
};
|
||||||
@@ -5,14 +5,16 @@ export default {
|
|||||||
"profile": "Profile", // Profil
|
"profile": "Profile", // Profil
|
||||||
"email": "Email", // E-Mail
|
"email": "Email", // E-Mail
|
||||||
"password": "Password", // Passwort
|
"password": "Password", // Passwort
|
||||||
|
"newPassword": "New password", // Neues Passwort
|
||||||
"confirmPassword": "Confirm password", // Passwort bestätigen
|
"confirmPassword": "Confirm password", // Passwort bestätigen
|
||||||
"forgotPassword": "Forgot password?", // Passwort vergessen?
|
"forgotPassword": "Forgot password?", // Passwort vergessen?
|
||||||
"loginWithGoogle": "Sign in with Google", // Mit Google anmelden
|
"loginWithGoogle": "Sign in with Google", // Mit Google anmelden
|
||||||
"or": "OR", // ODER
|
"or": "OR", // ODER
|
||||||
"privacyAccept": "By clicking \"Sign in with Google\" I accept the", // Mit dem Click auf "Mit Google anmelden" akzeptiere ich die
|
"privacyAccept": "By clicking on \"Sign in with Google\" I accept the", // Mit dem Click auf "Mit Google anmelden" akzeptiere ich die
|
||||||
"privacyPolicy": "Privacy Policy", // Datenschutzbestimmungen
|
"privacyPolicy": "Privacy policy", // Datenschutzbestimmungen
|
||||||
"passwordMinLength": "The password must be at least 8 characters long", // Das Passwort muss mindestens 8 Zeichen lang sein
|
"passwordMinLength": "The password must be at least 8 characters long", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||||
"newPasswordMinLength": "The new password must be at least 8 characters long", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
"newPasswordMinLength": "The new password must be at least 8 characters long", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||||
|
"backToHome": "Back to homepage", // Zurück zur Startseite
|
||||||
"menu": {
|
"menu": {
|
||||||
"profile": "Profile", // Profil
|
"profile": "Profile", // Profil
|
||||||
"myProfile": "My profile", // Mein Profil
|
"myProfile": "My profile", // Mein Profil
|
||||||
@@ -21,5 +23,28 @@ export default {
|
|||||||
"settings": "Settings", // Einstellungen
|
"settings": "Settings", // Einstellungen
|
||||||
"adminDashboard": "Admin Dashboard", // Admin Dashboard
|
"adminDashboard": "Admin Dashboard", // Admin Dashboard
|
||||||
"adminUsers": "Admin Users" // Admin Users
|
"adminUsers": "Admin Users" // Admin Users
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"title": "Reset password", // Passwort zurücksetzen
|
||||||
|
"button": "Reset password", // Passwort zurücksetzen
|
||||||
|
"success": "Your password has been reset successfully! You will be redirected to login shortly...", // Ihr Passwort wurde erfolgreich zurückgesetzt! Sie werden in Kürze zur Anmeldung weitergeleitet...
|
||||||
|
"invalidToken": "No valid token found. Please use the link from your email.", // Kein gültiger Token gefunden. Bitte verwenden Sie den Link aus Ihrer E-Mail.
|
||||||
|
"error": "Error resetting password", // Fehler beim Zurücksetzen des Passworts
|
||||||
|
"emailSent": "A link to reset your password has been sent to your email address.", // Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.
|
||||||
|
"emailError": "Error sending email" // Fehler beim Senden der E-Mail
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fillAllFields": "Please fill in all fields", // Bitte füllen Sie alle Felder aus
|
||||||
|
"invalidEmail": "Please enter a valid email address", // Bitte geben Sie eine gültige E-Mail-Adresse ein
|
||||||
|
"passwordsNotMatch": "The passwords do not match", // Die Passwörter stimmen nicht überein
|
||||||
|
"passwordsNotMatchShort": "Passwords do not match", // Passwörter stimmen nicht überein
|
||||||
|
"enterEmail": "Please enter your email address", // Bitte geben Sie Ihre E-Mail-Adresse ein
|
||||||
|
"loginFailed": "Login failed", // Anmeldung fehlgeschlagen
|
||||||
|
"registerFailed": "Registration failed", // Registrierung fehlgeschlagen
|
||||||
|
"googleLoginFailed": "Google login failed", // Google-Anmeldung fehlgeschlagen
|
||||||
|
"emailExists": "A user with this email address already exists. Please use another email address or log in." // Ein Benutzer mit dieser E-Mail-Adresse existiert bereits. Bitte verwenden Sie eine andere E-Mail-Adresse oder melden Sie sich an.
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"registerComplete": "Registration successful. You can now log in." // Registrierung erfolgreich. Sie können sich jetzt anmelden.
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import navigation from './navigation.js';
|
|||||||
import auth from './auth.js';
|
import auth from './auth.js';
|
||||||
import cart from './cart.js';
|
import cart from './cart.js';
|
||||||
import product from './product.js';
|
import product from './product.js';
|
||||||
|
import productDialogs from './productDialogs.js';
|
||||||
import search from './search.js';
|
import search from './search.js';
|
||||||
import sorting from './sorting.js';
|
import sorting from './sorting.js';
|
||||||
import chat from './chat.js';
|
import chat from './chat.js';
|
||||||
@@ -18,6 +19,7 @@ import pages from './pages.js';
|
|||||||
import orders from './orders.js';
|
import orders from './orders.js';
|
||||||
import settings from './settings.js';
|
import settings from './settings.js';
|
||||||
import common from './common.js';
|
import common from './common.js';
|
||||||
|
import kitConfig from './kitConfig.js';
|
||||||
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
||||||
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
||||||
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
||||||
@@ -35,6 +37,7 @@ export default {
|
|||||||
"auth": auth,
|
"auth": auth,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"product": product,
|
"product": product,
|
||||||
|
"productDialogs": productDialogs,
|
||||||
"search": search,
|
"search": search,
|
||||||
"sorting": sorting,
|
"sorting": sorting,
|
||||||
"chat": chat,
|
"chat": chat,
|
||||||
@@ -50,6 +53,7 @@ export default {
|
|||||||
"orders": orders,
|
"orders": orders,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"common": common,
|
"common": common,
|
||||||
|
"kitConfig": kitConfig,
|
||||||
"legalDatenschutzBasic": legalDatenschutzBasic,
|
"legalDatenschutzBasic": legalDatenschutzBasic,
|
||||||
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
||||||
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
||||||
|
|||||||
43
src/i18n/locales/en/kitConfig.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export default {
|
||||||
|
"pageTitle": "🌱 Growbox Configurator", // 🌱 Growbox Konfigurator
|
||||||
|
"pageSubtitle": "Put together your perfect indoor grow setup", // Stelle dein perfektes Indoor Grow Setup zusammen
|
||||||
|
"bundleDiscountTitle": "🎯 Secure bundle discount!", // 🎯 Bundle-Rabatt sichern!
|
||||||
|
"loadingProducts": "Loading growbox products...", // Lade Growbox-Produkte...
|
||||||
|
"loadingLighting": "Loading lighting products...", // Lade Beleuchtungs-Produkte...
|
||||||
|
"loadingVentilation": "Loading ventilation products...", // Lade Belüftungs-Produkte...
|
||||||
|
"loadingExtras": "Loading extras...", // Lade Extras...
|
||||||
|
"noProductsAvailable": "No products available for this size", // Keine Produkte für diese Größe verfügbar
|
||||||
|
"noLightingAvailable": "No suitable lights available for tent size {{shape}}.", // Keine passenden Lampen für Zeltgröße {{shape}} verfügbar.
|
||||||
|
"noVentilationAvailable": "No suitable ventilation available for tent size {{shape}}.", // Keine passenden Belüftung für Zeltgröße {{shape}} verfügbar.
|
||||||
|
"noExtrasAvailable": "No extras available", // Keine Extras verfügbar
|
||||||
|
"selectShapeTitle": "1. Select growbox shape", // 1. Growbox-Form auswählen
|
||||||
|
"selectShapeSubtitle": "First select the base area of your growbox", // Wähle zuerst die Grundfläche deiner Growbox aus
|
||||||
|
"selectProductTitle": "2. Select growbox product", // 2. Growbox Produkt auswählen
|
||||||
|
"selectProductSubtitle": "Choose the right product for your {{shape}} growbox", // Wähle das passende Produkt für deine {{shape}} Growbox
|
||||||
|
"selectLightingTitle": "3. Choose lighting", // 3. Beleuchtung wählen
|
||||||
|
"selectLightingTitleShape": "3. Choose lighting - {{shape}}", // 3. Beleuchtung wählen - {{shape}}
|
||||||
|
"selectLightingSubtitle": "Please select a tent size first.", // Bitte wählen Sie zuerst eine Zeltgröße aus.
|
||||||
|
"selectVentilationTitle": "4. Select ventilation", // 4. Belüftung auswählen
|
||||||
|
"selectVentilationTitleShape": "4. Select ventilation - {{shape}}", // 4. Belüftung auswählen - {{shape}}
|
||||||
|
"selectVentilationSubtitle": "Please select a tent size first.", // Bitte wählen Sie zuerst eine Zeltgröße aus.
|
||||||
|
"selectExtrasTitle": "5. Add extras (optional)", // 5. Extras hinzufügen (optional)
|
||||||
|
"yourConfiguration": "🎯 Your configuration", // 🎯 Ihre Konfiguration
|
||||||
|
"growboxLabel": "Growbox: {{name}}", // Growbox: {{name}}
|
||||||
|
"lightingLabel": "Lighting: {{name}}", // Beleuchtung: {{name}}
|
||||||
|
"ventilationLabel": "Ventilation: {{name}}", // Belüftung: {{name}}
|
||||||
|
"extraLabel": "Extra: {{name}}", // Extra: {{name}}
|
||||||
|
"totalPrice": "Total price:", // Gesamtpreis:
|
||||||
|
"addToCart": "Add to cart", // In den Warenkorb
|
||||||
|
"selected": "✓ Selected", // ✓ Ausgewählt
|
||||||
|
"notDeliverable": "Not deliverable", // Nicht lieferbar
|
||||||
|
"noPrice": "No price", // Kein Preis
|
||||||
|
"setName": "Growbox set - {{shape}}", // Growbox Set - {{shape}}
|
||||||
|
"description60x60": "Compact - ideal for small spaces", // Kompakt - ideal für kleine Räume
|
||||||
|
"description80x80": "Medium - perfect balance", // Mittel - perfekte Balance
|
||||||
|
"description100x100": "Large - for experienced growers", // Groß - für erfahrene Grower
|
||||||
|
"description120x60": "Rectangular - maximum space usage", // Rechteckig - maximale Raumnutzung
|
||||||
|
"plants1to2": "1-2 plants", // 1-2 Pflanzen
|
||||||
|
"plants2to4": "2-4 plants", // 2-4 Pflanzen
|
||||||
|
"plants4to6": "4-6 plants", // 4-6 Pflanzen
|
||||||
|
"plants3to6": "3-6 plants" // 3-6 Pflanzen
|
||||||
|
};
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
"status": {
|
"status": {
|
||||||
"new": "In progress", // in Bearbeitung
|
"new": "in progress", // in Bearbeitung
|
||||||
"pending": "New", // Neu
|
"pending": "New", // Neu
|
||||||
"processing": "In progress", // in Bearbeitung
|
"processing": "in progress", // in Bearbeitung
|
||||||
|
"paid": "Paid", // Bezahlt
|
||||||
"cancelled": "Cancelled", // Storniert
|
"cancelled": "Cancelled", // Storniert
|
||||||
"shipped": "Shipped", // Verschickt
|
"shipped": "Shipped", // Verschickt
|
||||||
"delivered": "Delivered", // Geliefert
|
"delivered": "Delivered", // Geliefert
|
||||||
@@ -24,6 +25,7 @@ export default {
|
|||||||
"cancelOrder": "Cancel order" // Bestellung stornieren
|
"cancelOrder": "Cancel order" // Bestellung stornieren
|
||||||
},
|
},
|
||||||
"noOrders": "You have not placed any orders yet.", // Sie haben noch keine Bestellungen aufgegeben.
|
"noOrders": "You have not placed any orders yet.", // Sie haben noch keine Bestellungen aufgegeben.
|
||||||
|
"trackShipment": "Track shipment", // Sendung verfolgen
|
||||||
"details": {
|
"details": {
|
||||||
"title": "Order details: {{orderId}}", // Bestelldetails: {{orderId}}
|
"title": "Order details: {{orderId}}", // Bestelldetails: {{orderId}}
|
||||||
"deliveryAddress": "Delivery address", // Lieferadresse
|
"deliveryAddress": "Delivery address", // Lieferadresse
|
||||||
@@ -36,15 +38,14 @@ export default {
|
|||||||
"item": "Item", // Artikel
|
"item": "Item", // Artikel
|
||||||
"quantity": "Quantity", // Menge
|
"quantity": "Quantity", // Menge
|
||||||
"price": "Price", // Preis
|
"price": "Price", // Preis
|
||||||
"vat": "VAT", // MwSt.
|
|
||||||
"total": "Total", // Gesamt
|
"total": "Total", // Gesamt
|
||||||
"cancelOrder": "Cancel order" // Bestellung stornieren
|
"cancelOrder": "Cancel order" // Bestellung stornieren
|
||||||
},
|
},
|
||||||
"cancelConfirm": {
|
"cancelConfirm": {
|
||||||
"title": "Cancel Order",
|
"title": "Cancel order", // Bestellung stornieren
|
||||||
"message": "Are you sure you want to cancel this order?",
|
"message": "Are you sure you want to cancel this order?", // Sind Sie sicher, dass Sie diese Bestellung stornieren möchten?
|
||||||
"confirm": "Cancel Order",
|
"confirm": "Cancel", // Stornieren
|
||||||
"cancelling": "Cancelling..."
|
"cancelling": "Cancelling..." // Wird storniert...
|
||||||
},
|
},
|
||||||
"processing": "Order is being completed...", // Bestellung wird abgeschlossen...
|
"processing": "Order is being completed..." // Bestellung wird abgeschlossen...
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ export default {
|
|||||||
"articleNumber": "Article number", // Artikelnummer
|
"articleNumber": "Article number", // Artikelnummer
|
||||||
"manufacturer": "Manufacturer", // Hersteller
|
"manufacturer": "Manufacturer", // Hersteller
|
||||||
"inclVat": "incl. {{vat}}% VAT", // inkl. {{vat}}% MwSt.
|
"inclVat": "incl. {{vat}}% VAT", // inkl. {{vat}}% MwSt.
|
||||||
|
"inclVatSimple": "incl. VAT", // inkl. MwSt.
|
||||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||||
"new": "New", // Neu
|
"new": "New", // Neu
|
||||||
"weeks": "weeks", // Wochen
|
"weeks": "Weeks", // Wochen
|
||||||
"arriving": "Arrival:", // Ankunft:
|
"arriving": "Arrival:", // Ankunft:
|
||||||
"inclVatFooter": "incl. {{vat}}% VAT,*", // inkl. {{vat}}% MwSt.,*
|
"inclVatFooter": "incl. {{vat}}% VAT,*", // inkl. {{vat}}% MwSt.,*
|
||||||
"availability": "Availability", // Verfügbarkeit
|
"availability": "Availability", // Verfügbarkeit
|
||||||
@@ -23,13 +24,13 @@ export default {
|
|||||||
"youSave": "You save: {{amount}}", // Sie sparen: {{amount}}
|
"youSave": "You save: {{amount}}", // Sie sparen: {{amount}}
|
||||||
"cheaperThanIndividual": "Cheaper than buying individually", // Günstiger als Einzelkauf
|
"cheaperThanIndividual": "Cheaper than buying individually", // Günstiger als Einzelkauf
|
||||||
"pickupPrice": "Pickup price: €19.90 per cutting.", // Abholpreis: 19,90 € pro Steckling.
|
"pickupPrice": "Pickup price: €19.90 per cutting.", // Abholpreis: 19,90 € pro Steckling.
|
||||||
"consistsOf": "Consists of:", // Bestehend aus:
|
"consistsOf": "Consisting of:", // Bestehend aus:
|
||||||
"loadingComponentDetails": "{{index}}. Loading component details...", // {{index}}. Lädt Komponent-Details...
|
"loadingComponentDetails": "{{index}}. Loading component details...", // {{index}}. Lädt Komponent-Details...
|
||||||
"loadingProduct": "Product is loading...", // Produkt wird geladen...
|
"loadingProduct": "Loading product...", // Produkt wird geladen...
|
||||||
"individualPriceTotal": "Total individual price:", // Einzelpreis gesamt:
|
"individualPriceTotal": "Total individual price:", // Einzelpreis gesamt:
|
||||||
"setPrice": "Set price:", // Set-Preis:
|
"setPrice": "Set price:", // Set-Preis:
|
||||||
"yourSavings": "Your savings:", // Ihre Ersparnis:
|
"yourSavings": "Your savings:", // Ihre Ersparnis:
|
||||||
"similarProducts": "Similar Products", // Ähnliche Produkte
|
"similarProducts": "Similar products", // Ähnliche Produkte
|
||||||
"countDisplay": {
|
"countDisplay": {
|
||||||
"noProducts": "0 products", // 0 Produkte
|
"noProducts": "0 products", // 0 Produkte
|
||||||
"oneProduct": "1 product", // 1 Produkt
|
"oneProduct": "1 product", // 1 Produkt
|
||||||
|
|||||||
61
src/i18n/locales/en/productDialogs.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export default {
|
||||||
|
"questionTitle": "Question about the product", // Frage zum Artikel
|
||||||
|
"questionSubtitle": "Do you have a question about this product? We are happy to help you.", // Haben Sie eine Frage zu diesem Artikel? Wir helfen Ihnen gerne weiter.
|
||||||
|
"questionSuccess": "Thank you for your question! We will get back to you as soon as possible.", // Vielen Dank für Ihre Frage! Wir werden uns schnellstmöglich bei Ihnen melden.
|
||||||
|
"nameLabel": "Name", // Name
|
||||||
|
"namePlaceholder": "Your name", // Ihr Name
|
||||||
|
"emailLabel": "Email", // E-Mail
|
||||||
|
"emailPlaceholder": "your.email@example.com", // ihre.email@example.com
|
||||||
|
"questionLabel": "Your question", // Ihre Frage
|
||||||
|
"questionPlaceholder": "Describe your question about this product...", // Beschreiben Sie Ihre Frage zu diesem Artikel...
|
||||||
|
"photosLabelQuestion": "Attach photos to your question (optional)", // Fotos zur Frage anhängen (optional)
|
||||||
|
"submitQuestion": "Send question", // Frage senden
|
||||||
|
"sending": "Sending...", // Wird gesendet...
|
||||||
|
|
||||||
|
"ratingTitle": "Rate product", // Artikel Bewerten
|
||||||
|
"ratingSubtitle": "Share your experience with this product and help other customers make their decision.", // Teilen Sie Ihre Erfahrungen mit diesem Artikel und helfen Sie anderen Kunden bei der Entscheidung.
|
||||||
|
"ratingSuccess": "Thank you for your review! It will be published after verification.", // Vielen Dank für Ihre Bewertung! Sie wird nach Prüfung veröffentlicht.
|
||||||
|
"emailHelper": "Your email will not be published", // Ihre E-Mail wird nicht veröffentlicht
|
||||||
|
"ratingLabel": "Rating *", // Bewertung *
|
||||||
|
"pleaseRate": "Please rate", // Bitte bewerten
|
||||||
|
"ratingStars": "{{rating}} out of 5 stars", // {{rating}} von 5 Sternen
|
||||||
|
"reviewLabel": "Your review (optional)", // Ihre Bewertung (optional)
|
||||||
|
"reviewPlaceholder": "Describe your experiences with this product...", // Beschreiben Sie Ihre Erfahrungen mit diesem Artikel...
|
||||||
|
"photosLabelRating": "Attach photos to your review (optional)", // Fotos zur Bewertung anhängen (optional)
|
||||||
|
"submitRating": "Submit review", // Bewertung abgeben
|
||||||
|
"errorGeneric": "An error occurred", // Ein Fehler ist aufgetreten
|
||||||
|
"errorPhotos": "Error processing photos", // Fehler beim Verarbeiten der Fotos
|
||||||
|
|
||||||
|
"availabilityTitle": "Request availability", // Verfügbarkeit anfragen
|
||||||
|
"availabilitySubtitle": "This product is currently unavailable. We will be happy to inform you as soon as it is back in stock.", // Dieser Artikel ist derzeit nicht verfügbar. Gerne informieren wir Sie, sobald er wieder lieferbar ist.
|
||||||
|
"availabilitySuccessEmail": "Thank you for your request! We will notify you by email as soon as the product is available again.", // Vielen Dank für Ihre Anfrage! Wir werden Sie per E-Mail informieren, sobald der Artikel wieder verfügbar ist.
|
||||||
|
"availabilitySuccessTelegram": "Thank you for your request! We will notify you via Telegram as soon as the product is available again.", // Vielen Dank für Ihre Anfrage! Wir werden Sie über Telegram informieren, sobald der Artikel wieder verfügbar ist.
|
||||||
|
"notificationMethodLabel": "How would you like to be notified?", // Wie möchten Sie benachrichtigt werden?
|
||||||
|
"telegramBotLabel": "Telegram Bot", // Telegram Bot
|
||||||
|
"telegramIdLabel": "Telegram ID", // Telegram ID
|
||||||
|
"telegramPlaceholder": "@yourTelegramName or Telegram ID", // @IhrTelegramName oder Telegram ID
|
||||||
|
"telegramHelper": "Enter your Telegram username (with @) or Telegram ID", // Geben Sie Ihren Telegram-Benutzernamen (mit @) oder Ihre Telegram-ID ein
|
||||||
|
"messageLabel": "Message (optional)", // Nachricht (optional)
|
||||||
|
"messagePlaceholder": "Additional information or questions...", // Zusätzliche Informationen oder Fragen...
|
||||||
|
"submitAvailability": "Request availability", // Verfügbarkeit anfragen
|
||||||
|
|
||||||
|
"photoUploadSelect": "Select photos", // Fotos auswählen
|
||||||
|
"photoUploadErrorMaxFiles": "Maximum {{max}} files allowed", // Maximal {{max}} Dateien erlaubt
|
||||||
|
"photoUploadErrorFileType": "Only image files (JPEG, PNG, GIF, WebP) are allowed", // Nur Bilddateien (JPEG, PNG, GIF, WebP) sind erlaubt
|
||||||
|
"photoUploadErrorFileSize": "File too large. Maximum: {{maxSize}}MB", // Datei zu groß. Maximum: {{maxSize}}MB
|
||||||
|
"photoUploadSelectedFiles": "{{count}} file(s) selected", // {{count}} Datei(en) ausgewählt
|
||||||
|
"photoUploadCompressed": "(compressed for upload)", // (komprimiert für Upload)
|
||||||
|
"photoUploadRemove": "Remove image", // Bild entfernen
|
||||||
|
"photoUploadLabelDefault": "Attach photos (optional)", // Fotos anhängen (optional)
|
||||||
|
|
||||||
|
"shareTitle": "Share", // Teilen
|
||||||
|
"shareEmbed": "Embed", // Einbetten
|
||||||
|
"shareCopyLink": "Copy link", // Link kopieren
|
||||||
|
"shareSuccessEmbed": "Embed code copied to clipboard!", // Einbettungscode wurde in die Zwischenablage kopiert!
|
||||||
|
"shareErrorEmbed": "Error copying the embed code", // Fehler beim Kopieren des Einbettungscodes
|
||||||
|
"shareSuccessLink": "Link copied to clipboard!", // Link wurde in die Zwischenablage kopiert!
|
||||||
|
"shareWhatsAppText": "Check out this product: {{name}}", // Schau dir dieses Produkt an: {{name}}
|
||||||
|
"shareTelegramText": "Check out this product: {{name}}", // Schau dir dieses Produkt an: {{name}}
|
||||||
|
"shareEmailSubject": "Product recommendation", // Produktempfehlung
|
||||||
|
"shareEmailBody": "Hello,\n\nI'd like to recommend this product to you:\n\n{{name}}\n{{url}}\n\nBest regards", // Hallo,\n\nich möchte dir dieses Produkt empfehlen:\n\n{{name}}\n{{url}}\n\nViele Grüße
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ export default {
|
|||||||
"profile": "Perfil",
|
"profile": "Perfil",
|
||||||
"email": "Correo electrónico",
|
"email": "Correo electrónico",
|
||||||
"password": "Contraseña",
|
"password": "Contraseña",
|
||||||
|
"newPassword": "Nueva contraseña",
|
||||||
"confirmPassword": "Confirmar contraseña",
|
"confirmPassword": "Confirmar contraseña",
|
||||||
"forgotPassword": "¿Olvidaste tu contraseña?",
|
"forgotPassword": "¿Olvidaste tu contraseña?",
|
||||||
"loginWithGoogle": "Iniciar sesión con Google",
|
"loginWithGoogle": "Iniciar sesión con Google",
|
||||||
@@ -13,6 +14,7 @@ export default {
|
|||||||
"privacyPolicy": "Política de privacidad",
|
"privacyPolicy": "Política de privacidad",
|
||||||
"passwordMinLength": "La contraseña debe tener al menos 8 caracteres",
|
"passwordMinLength": "La contraseña debe tener al menos 8 caracteres",
|
||||||
"newPasswordMinLength": "La nueva contraseña debe tener al menos 8 caracteres",
|
"newPasswordMinLength": "La nueva contraseña debe tener al menos 8 caracteres",
|
||||||
|
"backToHome": "Volver a la página principal",
|
||||||
"menu": {
|
"menu": {
|
||||||
"profile": "Perfil",
|
"profile": "Perfil",
|
||||||
"myProfile": "Mi perfil",
|
"myProfile": "Mi perfil",
|
||||||
@@ -21,5 +23,28 @@ export default {
|
|||||||
"settings": "Configuración",
|
"settings": "Configuración",
|
||||||
"adminDashboard": "Panel de administración",
|
"adminDashboard": "Panel de administración",
|
||||||
"adminUsers": "Usuarios administradores"
|
"adminUsers": "Usuarios administradores"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"title": "Restablecer contraseña",
|
||||||
|
"button": "Restablecer contraseña",
|
||||||
|
"success": "¡Tu contraseña ha sido restablecida con éxito! Serás redirigido para iniciar sesión en breve...",
|
||||||
|
"invalidToken": "No se encontró un token válido. Por favor, usa el enlace de tu correo electrónico.",
|
||||||
|
"error": "Error al restablecer la contraseña",
|
||||||
|
"emailSent": "Se ha enviado un enlace para restablecer tu contraseña a tu dirección de correo electrónico.",
|
||||||
|
"emailError": "Error al enviar el correo electrónico"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fillAllFields": "Por favor, completa todos los campos",
|
||||||
|
"invalidEmail": "Por favor, introduce una dirección de correo electrónico válida",
|
||||||
|
"passwordsNotMatch": "Las contraseñas no coinciden",
|
||||||
|
"passwordsNotMatchShort": "Las contraseñas no coinciden",
|
||||||
|
"enterEmail": "Por favor, introduce tu dirección de correo electrónico",
|
||||||
|
"loginFailed": "Error al iniciar sesión",
|
||||||
|
"registerFailed": "Error al registrarse",
|
||||||
|
"googleLoginFailed": "Error al iniciar sesión con Google",
|
||||||
|
"emailExists": "Ya existe un usuario con esta dirección de correo electrónico. Por favor, usa otra dirección de correo electrónico o inicia sesión."
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"registerComplete": "Registro exitoso. Ahora puedes iniciar sesión."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import navigation from './navigation.js';
|
|||||||
import auth from './auth.js';
|
import auth from './auth.js';
|
||||||
import cart from './cart.js';
|
import cart from './cart.js';
|
||||||
import product from './product.js';
|
import product from './product.js';
|
||||||
|
import productDialogs from './productDialogs.js';
|
||||||
import search from './search.js';
|
import search from './search.js';
|
||||||
import sorting from './sorting.js';
|
import sorting from './sorting.js';
|
||||||
import chat from './chat.js';
|
import chat from './chat.js';
|
||||||
@@ -18,6 +19,7 @@ import pages from './pages.js';
|
|||||||
import orders from './orders.js';
|
import orders from './orders.js';
|
||||||
import settings from './settings.js';
|
import settings from './settings.js';
|
||||||
import common from './common.js';
|
import common from './common.js';
|
||||||
|
import kitConfig from './kitConfig.js';
|
||||||
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
||||||
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
||||||
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
||||||
@@ -35,6 +37,7 @@ export default {
|
|||||||
"auth": auth,
|
"auth": auth,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"product": product,
|
"product": product,
|
||||||
|
"productDialogs": productDialogs,
|
||||||
"search": search,
|
"search": search,
|
||||||
"sorting": sorting,
|
"sorting": sorting,
|
||||||
"chat": chat,
|
"chat": chat,
|
||||||
@@ -50,6 +53,7 @@ export default {
|
|||||||
"orders": orders,
|
"orders": orders,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"common": common,
|
"common": common,
|
||||||
|
"kitConfig": kitConfig,
|
||||||
"legalDatenschutzBasic": legalDatenschutzBasic,
|
"legalDatenschutzBasic": legalDatenschutzBasic,
|
||||||
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
||||||
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
||||||
|
|||||||
43
src/i18n/locales/es/kitConfig.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export default {
|
||||||
|
"pageTitle": "🌱 Configurador de Growbox",
|
||||||
|
"pageSubtitle": "Arma tu configuración perfecta para cultivo en interior",
|
||||||
|
"bundleDiscountTitle": "🎯 ¡Asegura el descuento por paquete!",
|
||||||
|
"loadingProducts": "Cargando productos de growbox...",
|
||||||
|
"loadingLighting": "Cargando productos de iluminación...",
|
||||||
|
"loadingVentilation": "Cargando productos de ventilación...",
|
||||||
|
"loadingExtras": "Cargando extras...",
|
||||||
|
"noProductsAvailable": "No hay productos disponibles para este tamaño",
|
||||||
|
"noLightingAvailable": "No hay luces adecuadas disponibles para el tamaño de tienda {{shape}}.",
|
||||||
|
"noVentilationAvailable": "No hay ventilación adecuada disponible para el tamaño de tienda {{shape}}.",
|
||||||
|
"noExtrasAvailable": "No hay extras disponibles",
|
||||||
|
"selectShapeTitle": "1. Selecciona la forma de la growbox",
|
||||||
|
"selectShapeSubtitle": "Primero selecciona el área base de tu growbox",
|
||||||
|
"selectProductTitle": "2. Selecciona el producto de growbox",
|
||||||
|
"selectProductSubtitle": "Elige el producto adecuado para tu growbox {{shape}}",
|
||||||
|
"selectLightingTitle": "3. Elige la iluminación",
|
||||||
|
"selectLightingTitleShape": "3. Elige la iluminación - {{shape}}",
|
||||||
|
"selectLightingSubtitle": "Por favor selecciona primero un tamaño de tienda.",
|
||||||
|
"selectVentilationTitle": "4. Selecciona la ventilación",
|
||||||
|
"selectVentilationTitleShape": "4. Selecciona la ventilación - {{shape}}",
|
||||||
|
"selectVentilationSubtitle": "Por favor selecciona primero un tamaño de tienda.",
|
||||||
|
"selectExtrasTitle": "5. Añade extras (opcional)",
|
||||||
|
"yourConfiguration": "🎯 Tu configuración",
|
||||||
|
"growboxLabel": "Growbox: {{name}}",
|
||||||
|
"lightingLabel": "Iluminación: {{name}}",
|
||||||
|
"ventilationLabel": "Ventilación: {{name}}",
|
||||||
|
"extraLabel": "Extra: {{name}}",
|
||||||
|
"totalPrice": "Precio total:",
|
||||||
|
"addToCart": "Añadir al carrito",
|
||||||
|
"selected": "✓ Seleccionado",
|
||||||
|
"notDeliverable": "No entregable",
|
||||||
|
"noPrice": "Sin precio",
|
||||||
|
"setName": "Set de growbox - {{shape}}",
|
||||||
|
"description60x60": "Compacto - ideal para espacios pequeños",
|
||||||
|
"description80x80": "Mediano - equilibrio perfecto",
|
||||||
|
"description100x100": "Grande - para cultivadores experimentados",
|
||||||
|
"description120x60": "Rectangular - uso máximo del espacio",
|
||||||
|
"plants1to2": "1-2 plantas",
|
||||||
|
"plants2to4": "2-4 plantas",
|
||||||
|
"plants4to6": "4-6 plantas",
|
||||||
|
"plants3to6": "3-6 plantas"
|
||||||
|
};
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
"status": {
|
"status": {
|
||||||
"new": "En progreso",
|
"new": "en progreso",
|
||||||
"pending": "Nuevo",
|
"pending": "Nuevo",
|
||||||
"processing": "En progreso",
|
"processing": "en progreso",
|
||||||
|
"paid": "Pagado",
|
||||||
"cancelled": "Cancelado",
|
"cancelled": "Cancelado",
|
||||||
"shipped": "Enviado",
|
"shipped": "Enviado",
|
||||||
"delivered": "Entregado",
|
"delivered": "Entregado",
|
||||||
@@ -24,6 +25,7 @@ export default {
|
|||||||
"cancelOrder": "Cancelar pedido"
|
"cancelOrder": "Cancelar pedido"
|
||||||
},
|
},
|
||||||
"noOrders": "Aún no has realizado ningún pedido.",
|
"noOrders": "Aún no has realizado ningún pedido.",
|
||||||
|
"trackShipment": "Rastrear envío",
|
||||||
"details": {
|
"details": {
|
||||||
"title": "Detalles del pedido: {{orderId}}",
|
"title": "Detalles del pedido: {{orderId}}",
|
||||||
"deliveryAddress": "Dirección de entrega",
|
"deliveryAddress": "Dirección de entrega",
|
||||||
@@ -36,15 +38,14 @@ export default {
|
|||||||
"item": "Artículo",
|
"item": "Artículo",
|
||||||
"quantity": "Cantidad",
|
"quantity": "Cantidad",
|
||||||
"price": "Precio",
|
"price": "Precio",
|
||||||
"vat": "IVA",
|
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"cancelOrder": "Cancelar pedido"
|
"cancelOrder": "Cancelar pedido"
|
||||||
},
|
},
|
||||||
"cancelConfirm": {
|
"cancelConfirm": {
|
||||||
"title": "Cancelar pedido",
|
"title": "Cancelar pedido",
|
||||||
"message": "¿Estás seguro de que deseas cancelar este pedido?",
|
"message": "¿Está seguro de que desea cancelar este pedido?",
|
||||||
"confirm": "Cancelar pedido",
|
"confirm": "Cancelar",
|
||||||
"cancelling": "Cancelando..."
|
"cancelling": "Cancelando..."
|
||||||
},
|
},
|
||||||
"processing": "El pedido se está completando...",
|
"processing": "El pedido se está completando..."
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ export default {
|
|||||||
"articleNumber": "Número de artículo",
|
"articleNumber": "Número de artículo",
|
||||||
"manufacturer": "Fabricante",
|
"manufacturer": "Fabricante",
|
||||||
"inclVat": "incl. {{vat}}% IVA",
|
"inclVat": "incl. {{vat}}% IVA",
|
||||||
|
"inclVatSimple": "incl. IVA",
|
||||||
"priceUnit": "{{price}}/{{unit}}",
|
"priceUnit": "{{price}}/{{unit}}",
|
||||||
"new": "Nuevo",
|
"new": "Nuevo",
|
||||||
"weeks": "semanas",
|
"weeks": "Semanas",
|
||||||
"arriving": "Llegada:",
|
"arriving": "Llegada:",
|
||||||
"inclVatFooter": "incl. {{vat}}% IVA,*",
|
"inclVatFooter": "incl. {{vat}}% IVA,*",
|
||||||
"availability": "Disponibilidad",
|
"availability": "Disponibilidad",
|
||||||
@@ -25,7 +26,7 @@ export default {
|
|||||||
"pickupPrice": "Precio de recogida: 19,90 € por esqueje.",
|
"pickupPrice": "Precio de recogida: 19,90 € por esqueje.",
|
||||||
"consistsOf": "Consiste en:",
|
"consistsOf": "Consiste en:",
|
||||||
"loadingComponentDetails": "{{index}}. Cargando detalles del componente...",
|
"loadingComponentDetails": "{{index}}. Cargando detalles del componente...",
|
||||||
"loadingProduct": "Producto cargando...",
|
"loadingProduct": "Cargando producto...",
|
||||||
"individualPriceTotal": "Precio individual total:",
|
"individualPriceTotal": "Precio individual total:",
|
||||||
"setPrice": "Precio del set:",
|
"setPrice": "Precio del set:",
|
||||||
"yourSavings": "Tus ahorros:",
|
"yourSavings": "Tus ahorros:",
|
||||||
|
|||||||
61
src/i18n/locales/es/productDialogs.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export default {
|
||||||
|
"questionTitle": "Pregunta sobre el producto",
|
||||||
|
"questionSubtitle": "¿Tiene alguna pregunta sobre este producto? Estamos encantados de ayudarle.",
|
||||||
|
"questionSuccess": "¡Gracias por su pregunta! Nos pondremos en contacto con usted lo antes posible.",
|
||||||
|
"nameLabel": "Nombre",
|
||||||
|
"namePlaceholder": "Su nombre",
|
||||||
|
"emailLabel": "Correo electrónico",
|
||||||
|
"emailPlaceholder": "su.email@ejemplo.com",
|
||||||
|
"questionLabel": "Su pregunta",
|
||||||
|
"questionPlaceholder": "Describa su pregunta sobre este producto...",
|
||||||
|
"photosLabelQuestion": "Adjunte fotos a su pregunta (opcional)",
|
||||||
|
"submitQuestion": "Enviar pregunta",
|
||||||
|
"sending": "Enviando...",
|
||||||
|
|
||||||
|
"ratingTitle": "Calificar producto",
|
||||||
|
"ratingSubtitle": "Comparta su experiencia con este producto y ayude a otros clientes a tomar su decisión.",
|
||||||
|
"ratingSuccess": "¡Gracias por su reseña! Se publicará después de la verificación.",
|
||||||
|
"emailHelper": "Su correo electrónico no será publicado",
|
||||||
|
"ratingLabel": "Calificación *",
|
||||||
|
"pleaseRate": "Por favor califique",
|
||||||
|
"ratingStars": "{{rating}} de 5 estrellas",
|
||||||
|
"reviewLabel": "Su reseña (opcional)",
|
||||||
|
"reviewPlaceholder": "Describa sus experiencias con este producto...",
|
||||||
|
"photosLabelRating": "Adjunte fotos a su reseña (opcional)",
|
||||||
|
"submitRating": "Enviar reseña",
|
||||||
|
"errorGeneric": "Ocurrió un error",
|
||||||
|
"errorPhotos": "Error al procesar las fotos",
|
||||||
|
|
||||||
|
"availabilityTitle": "Solicitar disponibilidad",
|
||||||
|
"availabilitySubtitle": "Este producto no está disponible actualmente. Le informaremos tan pronto como vuelva a estar en stock.",
|
||||||
|
"availabilitySuccessEmail": "¡Gracias por su solicitud! Le notificaremos por correo electrónico tan pronto como el producto esté disponible nuevamente.",
|
||||||
|
"availabilitySuccessTelegram": "¡Gracias por su solicitud! Le notificaremos vía Telegram tan pronto como el producto esté disponible nuevamente.",
|
||||||
|
"notificationMethodLabel": "¿Cómo desea ser notificado?",
|
||||||
|
"telegramBotLabel": "Bot de Telegram",
|
||||||
|
"telegramIdLabel": "ID de Telegram",
|
||||||
|
"telegramPlaceholder": "@suNombreTelegram o ID de Telegram",
|
||||||
|
"telegramHelper": "Ingrese su nombre de usuario de Telegram (con @) o ID de Telegram",
|
||||||
|
"messageLabel": "Mensaje (opcional)",
|
||||||
|
"messagePlaceholder": "Información adicional o preguntas...",
|
||||||
|
"submitAvailability": "Solicitar disponibilidad",
|
||||||
|
|
||||||
|
"photoUploadSelect": "Seleccionar fotos",
|
||||||
|
"photoUploadErrorMaxFiles": "Máximo {{max}} archivos permitidos",
|
||||||
|
"photoUploadErrorFileType": "Solo se permiten archivos de imagen (JPEG, PNG, GIF, WebP)",
|
||||||
|
"photoUploadErrorFileSize": "Archivo demasiado grande. Máximo: {{maxSize}}MB",
|
||||||
|
"photoUploadSelectedFiles": "{{count}} archivo(s) seleccionado(s)",
|
||||||
|
"photoUploadCompressed": "(comprimido para subir)",
|
||||||
|
"photoUploadRemove": "Eliminar imagen",
|
||||||
|
"photoUploadLabelDefault": "Adjuntar fotos (opcional)",
|
||||||
|
|
||||||
|
"shareTitle": "Compartir",
|
||||||
|
"shareEmbed": "Insertar",
|
||||||
|
"shareCopyLink": "Copiar enlace",
|
||||||
|
"shareSuccessEmbed": "¡Código de inserción copiado al portapapeles!",
|
||||||
|
"shareErrorEmbed": "Error al copiar el código de inserción",
|
||||||
|
"shareSuccessLink": "¡Enlace copiado al portapapeles!",
|
||||||
|
"shareWhatsAppText": "Mira este producto: {{name}}",
|
||||||
|
"shareTelegramText": "Mira este producto: {{name}}",
|
||||||
|
"shareEmailSubject": "Recomendación de producto",
|
||||||
|
"shareEmailBody": "Hola,\n\nQuisiera recomendarte este producto:\n\n{{name}}\n{{url}}\n\nSaludos cordiales"
|
||||||
|
};
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
export default {
|
export default {
|
||||||
"login": "Connexion",
|
"login": "Connexion",
|
||||||
"register": "S'inscrire",
|
"register": "Inscription",
|
||||||
"logout": "Déconnexion",
|
"logout": "Déconnexion",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": "Mot de passe",
|
"password": "Mot de passe",
|
||||||
|
"newPassword": "Nouveau mot de passe",
|
||||||
"confirmPassword": "Confirmer le mot de passe",
|
"confirmPassword": "Confirmer le mot de passe",
|
||||||
"forgotPassword": "Mot de passe oublié ?",
|
"forgotPassword": "Mot de passe oublié ?",
|
||||||
"loginWithGoogle": "Se connecter avec Google",
|
"loginWithGoogle": "Se connecter avec Google",
|
||||||
@@ -13,6 +14,7 @@ export default {
|
|||||||
"privacyPolicy": "Politique de confidentialité",
|
"privacyPolicy": "Politique de confidentialité",
|
||||||
"passwordMinLength": "Le mot de passe doit contenir au moins 8 caractères",
|
"passwordMinLength": "Le mot de passe doit contenir au moins 8 caractères",
|
||||||
"newPasswordMinLength": "Le nouveau mot de passe doit contenir au moins 8 caractères",
|
"newPasswordMinLength": "Le nouveau mot de passe doit contenir au moins 8 caractères",
|
||||||
|
"backToHome": "Retour à la page d'accueil",
|
||||||
"menu": {
|
"menu": {
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"myProfile": "Mon profil",
|
"myProfile": "Mon profil",
|
||||||
@@ -21,5 +23,28 @@ export default {
|
|||||||
"settings": "Paramètres",
|
"settings": "Paramètres",
|
||||||
"adminDashboard": "Tableau de bord Admin",
|
"adminDashboard": "Tableau de bord Admin",
|
||||||
"adminUsers": "Utilisateurs Admin"
|
"adminUsers": "Utilisateurs Admin"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"title": "Réinitialiser le mot de passe",
|
||||||
|
"button": "Réinitialiser le mot de passe",
|
||||||
|
"success": "Votre mot de passe a été réinitialisé avec succès ! Vous serez redirigé vers la connexion sous peu...",
|
||||||
|
"invalidToken": "Aucun jeton valide trouvé. Veuillez utiliser le lien de votre email.",
|
||||||
|
"error": "Erreur lors de la réinitialisation du mot de passe",
|
||||||
|
"emailSent": "Un lien pour réinitialiser votre mot de passe a été envoyé à votre adresse email.",
|
||||||
|
"emailError": "Erreur lors de l'envoi de l'email"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fillAllFields": "Veuillez remplir tous les champs",
|
||||||
|
"invalidEmail": "Veuillez entrer une adresse email valide",
|
||||||
|
"passwordsNotMatch": "Les mots de passe ne correspondent pas",
|
||||||
|
"passwordsNotMatchShort": "Les mots de passe ne correspondent pas",
|
||||||
|
"enterEmail": "Veuillez entrer votre adresse email",
|
||||||
|
"loginFailed": "Échec de la connexion",
|
||||||
|
"registerFailed": "Échec de l'inscription",
|
||||||
|
"googleLoginFailed": "Échec de la connexion Google",
|
||||||
|
"emailExists": "Un utilisateur avec cette adresse email existe déjà. Veuillez utiliser une autre adresse email ou vous connecter."
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"registerComplete": "Inscription réussie. Vous pouvez maintenant vous connecter."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import navigation from './navigation.js';
|
|||||||
import auth from './auth.js';
|
import auth from './auth.js';
|
||||||
import cart from './cart.js';
|
import cart from './cart.js';
|
||||||
import product from './product.js';
|
import product from './product.js';
|
||||||
|
import productDialogs from './productDialogs.js';
|
||||||
import search from './search.js';
|
import search from './search.js';
|
||||||
import sorting from './sorting.js';
|
import sorting from './sorting.js';
|
||||||
import chat from './chat.js';
|
import chat from './chat.js';
|
||||||
@@ -18,6 +19,7 @@ import pages from './pages.js';
|
|||||||
import orders from './orders.js';
|
import orders from './orders.js';
|
||||||
import settings from './settings.js';
|
import settings from './settings.js';
|
||||||
import common from './common.js';
|
import common from './common.js';
|
||||||
|
import kitConfig from './kitConfig.js';
|
||||||
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
||||||
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
||||||
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
||||||
@@ -35,6 +37,7 @@ export default {
|
|||||||
"auth": auth,
|
"auth": auth,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"product": product,
|
"product": product,
|
||||||
|
"productDialogs": productDialogs,
|
||||||
"search": search,
|
"search": search,
|
||||||
"sorting": sorting,
|
"sorting": sorting,
|
||||||
"chat": chat,
|
"chat": chat,
|
||||||
@@ -50,6 +53,7 @@ export default {
|
|||||||
"orders": orders,
|
"orders": orders,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"common": common,
|
"common": common,
|
||||||
|
"kitConfig": kitConfig,
|
||||||
"legalDatenschutzBasic": legalDatenschutzBasic,
|
"legalDatenschutzBasic": legalDatenschutzBasic,
|
||||||
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
||||||
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
||||||
|
|||||||
43
src/i18n/locales/fr/kitConfig.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export default {
|
||||||
|
"pageTitle": "🌱 Configurateur Growbox",
|
||||||
|
"pageSubtitle": "Assemblez votre installation de culture d'intérieur parfaite",
|
||||||
|
"bundleDiscountTitle": "🎯 Profitez d'une remise sur le pack !",
|
||||||
|
"loadingProducts": "Chargement des produits growbox...",
|
||||||
|
"loadingLighting": "Chargement des produits d'éclairage...",
|
||||||
|
"loadingVentilation": "Chargement des produits de ventilation...",
|
||||||
|
"loadingExtras": "Chargement des extras...",
|
||||||
|
"noProductsAvailable": "Aucun produit disponible pour cette taille",
|
||||||
|
"noLightingAvailable": "Aucun éclairage adapté disponible pour la taille de tente {{shape}}.",
|
||||||
|
"noVentilationAvailable": "Aucune ventilation adaptée disponible pour la taille de tente {{shape}}.",
|
||||||
|
"noExtrasAvailable": "Aucun extra disponible",
|
||||||
|
"selectShapeTitle": "1. Sélectionnez la forme de la growbox",
|
||||||
|
"selectShapeSubtitle": "Sélectionnez d'abord la surface de base de votre growbox",
|
||||||
|
"selectProductTitle": "2. Sélectionnez le produit growbox",
|
||||||
|
"selectProductSubtitle": "Choisissez le produit adapté pour votre growbox {{shape}}",
|
||||||
|
"selectLightingTitle": "3. Choisissez l'éclairage",
|
||||||
|
"selectLightingTitleShape": "3. Choisissez l'éclairage - {{shape}}",
|
||||||
|
"selectLightingSubtitle": "Veuillez d'abord sélectionner une taille de tente.",
|
||||||
|
"selectVentilationTitle": "4. Sélectionnez la ventilation",
|
||||||
|
"selectVentilationTitleShape": "4. Sélectionnez la ventilation - {{shape}}",
|
||||||
|
"selectVentilationSubtitle": "Veuillez d'abord sélectionner une taille de tente.",
|
||||||
|
"selectExtrasTitle": "5. Ajoutez des extras (optionnel)",
|
||||||
|
"yourConfiguration": "🎯 Votre configuration",
|
||||||
|
"growboxLabel": "Growbox : {{name}}",
|
||||||
|
"lightingLabel": "Éclairage : {{name}}",
|
||||||
|
"ventilationLabel": "Ventilation : {{name}}",
|
||||||
|
"extraLabel": "Extra : {{name}}",
|
||||||
|
"totalPrice": "Prix total :",
|
||||||
|
"addToCart": "Ajouter au panier",
|
||||||
|
"selected": "✓ Sélectionné",
|
||||||
|
"notDeliverable": "Non livrable",
|
||||||
|
"noPrice": "Pas de prix",
|
||||||
|
"setName": "Set Growbox - {{shape}}",
|
||||||
|
"description60x60": "Compact - idéal pour les petits espaces",
|
||||||
|
"description80x80": "Moyen - équilibre parfait",
|
||||||
|
"description100x100": "Grand - pour cultivateurs expérimentés",
|
||||||
|
"description120x60": "Rectangulaire - utilisation maximale de l'espace",
|
||||||
|
"plants1to2": "1-2 plantes",
|
||||||
|
"plants2to4": "2-4 plantes",
|
||||||
|
"plants4to6": "4-6 plantes",
|
||||||
|
"plants3to6": "3-6 plantes"
|
||||||
|
};
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
"status": {
|
"status": {
|
||||||
"new": "En cours",
|
"new": "en cours",
|
||||||
"pending": "Nouveau",
|
"pending": "Nouveau",
|
||||||
"processing": "En cours",
|
"processing": "en cours",
|
||||||
|
"paid": "Payé",
|
||||||
"cancelled": "Annulé",
|
"cancelled": "Annulé",
|
||||||
"shipped": "Expédié",
|
"shipped": "Expédié",
|
||||||
"delivered": "Livré",
|
"delivered": "Livré",
|
||||||
@@ -24,6 +25,7 @@ export default {
|
|||||||
"cancelOrder": "Annuler la commande"
|
"cancelOrder": "Annuler la commande"
|
||||||
},
|
},
|
||||||
"noOrders": "Vous n'avez pas encore passé de commandes.",
|
"noOrders": "Vous n'avez pas encore passé de commandes.",
|
||||||
|
"trackShipment": "Suivre l'envoi",
|
||||||
"details": {
|
"details": {
|
||||||
"title": "Détails de la commande : {{orderId}}",
|
"title": "Détails de la commande : {{orderId}}",
|
||||||
"deliveryAddress": "Adresse de livraison",
|
"deliveryAddress": "Adresse de livraison",
|
||||||
@@ -36,14 +38,13 @@ export default {
|
|||||||
"item": "Article",
|
"item": "Article",
|
||||||
"quantity": "Quantité",
|
"quantity": "Quantité",
|
||||||
"price": "Prix",
|
"price": "Prix",
|
||||||
"vat": "TVA",
|
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"cancelOrder": "Annuler la commande"
|
"cancelOrder": "Annuler la commande"
|
||||||
},
|
},
|
||||||
"cancelConfirm": {
|
"cancelConfirm": {
|
||||||
"title": "Annuler la commande",
|
"title": "Annuler la commande",
|
||||||
"message": "Êtes-vous sûr de vouloir annuler cette commande ?",
|
"message": "Êtes-vous sûr de vouloir annuler cette commande ?",
|
||||||
"confirm": "Annuler la commande",
|
"confirm": "Annuler",
|
||||||
"cancelling": "Annulation en cours..."
|
"cancelling": "Annulation en cours..."
|
||||||
},
|
},
|
||||||
"processing": "La commande est en cours de traitement..."
|
"processing": "La commande est en cours de traitement..."
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ export default {
|
|||||||
"articleNumber": "Numéro d'article",
|
"articleNumber": "Numéro d'article",
|
||||||
"manufacturer": "Fabricant",
|
"manufacturer": "Fabricant",
|
||||||
"inclVat": "TTC {{vat}}%",
|
"inclVat": "TTC {{vat}}%",
|
||||||
|
"inclVatSimple": "TTC",
|
||||||
"priceUnit": "{{price}}/{{unit}}",
|
"priceUnit": "{{price}}/{{unit}}",
|
||||||
"new": "Nouveau",
|
"new": "Nouveau",
|
||||||
"weeks": "semaines",
|
"weeks": "Semaines",
|
||||||
"arriving": "Arrivée :",
|
"arriving": "Arrivée :",
|
||||||
"inclVatFooter": "TTC {{vat}}%,*",
|
"inclVatFooter": "TTC {{vat}}%,*",
|
||||||
"availability": "Disponibilité",
|
"availability": "Disponibilité",
|
||||||
@@ -25,7 +26,7 @@ export default {
|
|||||||
"pickupPrice": "Prix de retrait : 19,90 € par bouture.",
|
"pickupPrice": "Prix de retrait : 19,90 € par bouture.",
|
||||||
"consistsOf": "Composé de :",
|
"consistsOf": "Composé de :",
|
||||||
"loadingComponentDetails": "{{index}}. Chargement des détails du composant...",
|
"loadingComponentDetails": "{{index}}. Chargement des détails du composant...",
|
||||||
"loadingProduct": "Le produit est en cours de chargement...",
|
"loadingProduct": "Chargement du produit...",
|
||||||
"individualPriceTotal": "Prix individuel total :",
|
"individualPriceTotal": "Prix individuel total :",
|
||||||
"setPrice": "Prix du lot :",
|
"setPrice": "Prix du lot :",
|
||||||
"yourSavings": "Vos économies :",
|
"yourSavings": "Vos économies :",
|
||||||
|
|||||||
61
src/i18n/locales/fr/productDialogs.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export default {
|
||||||
|
"questionTitle": "Question sur le produit",
|
||||||
|
"questionSubtitle": "Vous avez une question sur ce produit ? Nous sommes heureux de vous aider.",
|
||||||
|
"questionSuccess": "Merci pour votre question ! Nous vous répondrons dès que possible.",
|
||||||
|
"nameLabel": "Nom",
|
||||||
|
"namePlaceholder": "Votre nom",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailPlaceholder": "votre.email@exemple.com",
|
||||||
|
"questionLabel": "Votre question",
|
||||||
|
"questionPlaceholder": "Décrivez votre question à propos de ce produit...",
|
||||||
|
"photosLabelQuestion": "Joindre des photos à votre question (optionnel)",
|
||||||
|
"submitQuestion": "Envoyer la question",
|
||||||
|
"sending": "Envoi en cours...",
|
||||||
|
|
||||||
|
"ratingTitle": "Noter le produit",
|
||||||
|
"ratingSubtitle": "Partagez votre expérience avec ce produit et aidez les autres clients à prendre leur décision.",
|
||||||
|
"ratingSuccess": "Merci pour votre avis ! Il sera publié après vérification.",
|
||||||
|
"emailHelper": "Votre email ne sera pas publié",
|
||||||
|
"ratingLabel": "Note *",
|
||||||
|
"pleaseRate": "Veuillez noter",
|
||||||
|
"ratingStars": "{{rating}} sur 5 étoiles",
|
||||||
|
"reviewLabel": "Votre avis (optionnel)",
|
||||||
|
"reviewPlaceholder": "Décrivez vos expériences avec ce produit...",
|
||||||
|
"photosLabelRating": "Joindre des photos à votre avis (optionnel)",
|
||||||
|
"submitRating": "Soumettre l'avis",
|
||||||
|
"errorGeneric": "Une erreur est survenue",
|
||||||
|
"errorPhotos": "Erreur lors du traitement des photos",
|
||||||
|
|
||||||
|
"availabilityTitle": "Demander la disponibilité",
|
||||||
|
"availabilitySubtitle": "Ce produit est actuellement indisponible. Nous serons heureux de vous informer dès qu'il sera de nouveau en stock.",
|
||||||
|
"availabilitySuccessEmail": "Merci pour votre demande ! Nous vous informerons par email dès que le produit sera à nouveau disponible.",
|
||||||
|
"availabilitySuccessTelegram": "Merci pour votre demande ! Nous vous informerons via Telegram dès que le produit sera à nouveau disponible.",
|
||||||
|
"notificationMethodLabel": "Comment souhaitez-vous être informé ?",
|
||||||
|
"telegramBotLabel": "Bot Telegram",
|
||||||
|
"telegramIdLabel": "ID Telegram",
|
||||||
|
"telegramPlaceholder": "@votreNomTelegram ou ID Telegram",
|
||||||
|
"telegramHelper": "Entrez votre nom d'utilisateur Telegram (avec @) ou votre ID Telegram",
|
||||||
|
"messageLabel": "Message (optionnel)",
|
||||||
|
"messagePlaceholder": "Informations supplémentaires ou questions...",
|
||||||
|
"submitAvailability": "Demander la disponibilité",
|
||||||
|
|
||||||
|
"photoUploadSelect": "Sélectionner des photos",
|
||||||
|
"photoUploadErrorMaxFiles": "Maximum {{max}} fichiers autorisés",
|
||||||
|
"photoUploadErrorFileType": "Seuls les fichiers image (JPEG, PNG, GIF, WebP) sont autorisés",
|
||||||
|
"photoUploadErrorFileSize": "Fichier trop volumineux. Maximum : {{maxSize}}Mo",
|
||||||
|
"photoUploadSelectedFiles": "{{count}} fichier(s) sélectionné(s)",
|
||||||
|
"photoUploadCompressed": "(compressé pour l'envoi)",
|
||||||
|
"photoUploadRemove": "Supprimer l'image",
|
||||||
|
"photoUploadLabelDefault": "Joindre des photos (optionnel)",
|
||||||
|
|
||||||
|
"shareTitle": "Partager",
|
||||||
|
"shareEmbed": "Intégrer",
|
||||||
|
"shareCopyLink": "Copier le lien",
|
||||||
|
"shareSuccessEmbed": "Code d'intégration copié dans le presse-papiers !",
|
||||||
|
"shareErrorEmbed": "Erreur lors de la copie du code d'intégration",
|
||||||
|
"shareSuccessLink": "Lien copié dans le presse-papiers !",
|
||||||
|
"shareWhatsAppText": "Découvrez ce produit : {{name}}",
|
||||||
|
"shareTelegramText": "Découvrez ce produit : {{name}}",
|
||||||
|
"shareEmailSubject": "Recommandation de produit",
|
||||||
|
"shareEmailBody": "Bonjour,\n\nJe souhaite vous recommander ce produit :\n\n{{name}}\n{{url}}\n\nCordialement"
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ export default {
|
|||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": "Lozinka",
|
"password": "Lozinka",
|
||||||
|
"newPassword": "Nova lozinka",
|
||||||
"confirmPassword": "Potvrdi lozinku",
|
"confirmPassword": "Potvrdi lozinku",
|
||||||
"forgotPassword": "Zaboravili ste lozinku?",
|
"forgotPassword": "Zaboravili ste lozinku?",
|
||||||
"loginWithGoogle": "Prijavite se putem Googlea",
|
"loginWithGoogle": "Prijavite se putem Googlea",
|
||||||
@@ -13,6 +14,7 @@ export default {
|
|||||||
"privacyPolicy": "Pravila privatnosti",
|
"privacyPolicy": "Pravila privatnosti",
|
||||||
"passwordMinLength": "Lozinka mora imati najmanje 8 znakova",
|
"passwordMinLength": "Lozinka mora imati najmanje 8 znakova",
|
||||||
"newPasswordMinLength": "Nova lozinka mora imati najmanje 8 znakova",
|
"newPasswordMinLength": "Nova lozinka mora imati najmanje 8 znakova",
|
||||||
|
"backToHome": "Natrag na početnu stranicu",
|
||||||
"menu": {
|
"menu": {
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"myProfile": "Moj profil",
|
"myProfile": "Moj profil",
|
||||||
@@ -21,5 +23,28 @@ export default {
|
|||||||
"settings": "Postavke",
|
"settings": "Postavke",
|
||||||
"adminDashboard": "Admin nadzorna ploča",
|
"adminDashboard": "Admin nadzorna ploča",
|
||||||
"adminUsers": "Admin korisnici"
|
"adminUsers": "Admin korisnici"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"title": "Resetiraj lozinku",
|
||||||
|
"button": "Resetiraj lozinku",
|
||||||
|
"success": "Vaša lozinka je uspješno resetirana! Uskoro ćete biti preusmjereni na prijavu...",
|
||||||
|
"invalidToken": "Nije pronađen valjani token. Molimo koristite link iz vaše e-pošte.",
|
||||||
|
"error": "Pogreška pri resetiranju lozinke",
|
||||||
|
"emailSent": "Link za resetiranje lozinke poslan je na vašu e-mail adresu.",
|
||||||
|
"emailError": "Pogreška pri slanju e-pošte"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fillAllFields": "Molimo ispunite sva polja",
|
||||||
|
"invalidEmail": "Molimo unesite valjanu e-mail adresu",
|
||||||
|
"passwordsNotMatch": "Lozinke se ne podudaraju",
|
||||||
|
"passwordsNotMatchShort": "Lozinke se ne podudaraju",
|
||||||
|
"enterEmail": "Molimo unesite vašu e-mail adresu",
|
||||||
|
"loginFailed": "Prijava nije uspjela",
|
||||||
|
"registerFailed": "Registracija nije uspjela",
|
||||||
|
"googleLoginFailed": "Prijava putem Googlea nije uspjela",
|
||||||
|
"emailExists": "Korisnik s ovom e-mail adresom već postoji. Molimo koristite drugu e-mail adresu ili se prijavite."
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"registerComplete": "Registracija uspješna. Sada se možete prijaviti."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import navigation from './navigation.js';
|
|||||||
import auth from './auth.js';
|
import auth from './auth.js';
|
||||||
import cart from './cart.js';
|
import cart from './cart.js';
|
||||||
import product from './product.js';
|
import product from './product.js';
|
||||||
|
import productDialogs from './productDialogs.js';
|
||||||
import search from './search.js';
|
import search from './search.js';
|
||||||
import sorting from './sorting.js';
|
import sorting from './sorting.js';
|
||||||
import chat from './chat.js';
|
import chat from './chat.js';
|
||||||
@@ -18,6 +19,7 @@ import pages from './pages.js';
|
|||||||
import orders from './orders.js';
|
import orders from './orders.js';
|
||||||
import settings from './settings.js';
|
import settings from './settings.js';
|
||||||
import common from './common.js';
|
import common from './common.js';
|
||||||
|
import kitConfig from './kitConfig.js';
|
||||||
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
||||||
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
||||||
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
||||||
@@ -35,6 +37,7 @@ export default {
|
|||||||
"auth": auth,
|
"auth": auth,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"product": product,
|
"product": product,
|
||||||
|
"productDialogs": productDialogs,
|
||||||
"search": search,
|
"search": search,
|
||||||
"sorting": sorting,
|
"sorting": sorting,
|
||||||
"chat": chat,
|
"chat": chat,
|
||||||
@@ -50,6 +53,7 @@ export default {
|
|||||||
"orders": orders,
|
"orders": orders,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"common": common,
|
"common": common,
|
||||||
|
"kitConfig": kitConfig,
|
||||||
"legalDatenschutzBasic": legalDatenschutzBasic,
|
"legalDatenschutzBasic": legalDatenschutzBasic,
|
||||||
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
||||||
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
||||||
|
|||||||
43
src/i18n/locales/hr/kitConfig.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
export default {
|
||||||
|
"pageTitle": "🌱 Konfigurator Growboxa",
|
||||||
|
"pageSubtitle": "Sastavite svoju savršenu unutarnju grow postavu",
|
||||||
|
"bundleDiscountTitle": "🎯 Osigurajte popust na paket!",
|
||||||
|
"loadingProducts": "Učitavanje growbox proizvoda...",
|
||||||
|
"loadingLighting": "Učitavanje proizvoda za osvjetljenje...",
|
||||||
|
"loadingVentilation": "Učitavanje proizvoda za ventilaciju...",
|
||||||
|
"loadingExtras": "Učitavanje dodataka...",
|
||||||
|
"noProductsAvailable": "Nema dostupnih proizvoda za ovu veličinu",
|
||||||
|
"noLightingAvailable": "Nema prikladnih svjetala za veličinu šatora {{shape}}.",
|
||||||
|
"noVentilationAvailable": "Nema prikladne ventilacije za veličinu šatora {{shape}}.",
|
||||||
|
"noExtrasAvailable": "Nema dodataka",
|
||||||
|
"selectShapeTitle": "1. Odaberite oblik growboxa",
|
||||||
|
"selectShapeSubtitle": "Prvo odaberite osnovnu površinu vašeg growboxa",
|
||||||
|
"selectProductTitle": "2. Odaberite growbox proizvod",
|
||||||
|
"selectProductSubtitle": "Odaberite pravi proizvod za vaš {{shape}} growbox",
|
||||||
|
"selectLightingTitle": "3. Odaberite osvjetljenje",
|
||||||
|
"selectLightingTitleShape": "3. Odaberite osvjetljenje - {{shape}}",
|
||||||
|
"selectLightingSubtitle": "Molimo prvo odaberite veličinu šatora.",
|
||||||
|
"selectVentilationTitle": "4. Odaberite ventilaciju",
|
||||||
|
"selectVentilationTitleShape": "4. Odaberite ventilaciju - {{shape}}",
|
||||||
|
"selectVentilationSubtitle": "Molimo prvo odaberite veličinu šatora.",
|
||||||
|
"selectExtrasTitle": "5. Dodajte dodatke (opcionalno)",
|
||||||
|
"yourConfiguration": "🎯 Vaša konfiguracija",
|
||||||
|
"growboxLabel": "Growbox: {{name}}",
|
||||||
|
"lightingLabel": "Osvjetljenje: {{name}}",
|
||||||
|
"ventilationLabel": "Ventilacija: {{name}}",
|
||||||
|
"extraLabel": "Dodatak: {{name}}",
|
||||||
|
"totalPrice": "Ukupna cijena:",
|
||||||
|
"addToCart": "Dodaj u košaricu",
|
||||||
|
"selected": "✓ Odabrano",
|
||||||
|
"notDeliverable": "Nije dostupno za dostavu",
|
||||||
|
"noPrice": "Nema cijene",
|
||||||
|
"setName": "Growbox set - {{shape}}",
|
||||||
|
"description60x60": "Kompaktan - idealan za male prostore",
|
||||||
|
"description80x80": "Srednji - savršen balans",
|
||||||
|
"description100x100": "Veliki - za iskusne uzgajivače",
|
||||||
|
"description120x60": "Pravokutni - maksimalno iskorištavanje prostora",
|
||||||
|
"plants1to2": "1-2 biljke",
|
||||||
|
"plants2to4": "2-4 biljke",
|
||||||
|
"plants4to6": "4-6 biljaka",
|
||||||
|
"plants3to6": "3-6 biljaka"
|
||||||
|
};
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
"status": {
|
"status": {
|
||||||
"new": "U tijeku",
|
"new": "u tijeku",
|
||||||
"pending": "Novo",
|
"pending": "Novo",
|
||||||
"processing": "U tijeku",
|
"processing": "u tijeku",
|
||||||
|
"paid": "Plaćeno",
|
||||||
"cancelled": "Otkazano",
|
"cancelled": "Otkazano",
|
||||||
"shipped": "Poslano",
|
"shipped": "Poslano",
|
||||||
"delivered": "Isporučeno",
|
"delivered": "Isporučeno",
|
||||||
@@ -23,7 +24,8 @@ export default {
|
|||||||
"viewDetails": "Pogledaj detalje",
|
"viewDetails": "Pogledaj detalje",
|
||||||
"cancelOrder": "Otkaži narudžbu"
|
"cancelOrder": "Otkaži narudžbu"
|
||||||
},
|
},
|
||||||
"noOrders": "Još niste napravili nijednu narudžbu.",
|
"noOrders": "Još niste izvršili nijednu narudžbu.",
|
||||||
|
"trackShipment": "Prati pošiljku",
|
||||||
"details": {
|
"details": {
|
||||||
"title": "Detalji narudžbe: {{orderId}}",
|
"title": "Detalji narudžbe: {{orderId}}",
|
||||||
"deliveryAddress": "Adresa dostave",
|
"deliveryAddress": "Adresa dostave",
|
||||||
@@ -36,15 +38,14 @@ export default {
|
|||||||
"item": "Artikl",
|
"item": "Artikl",
|
||||||
"quantity": "Količina",
|
"quantity": "Količina",
|
||||||
"price": "Cijena",
|
"price": "Cijena",
|
||||||
"vat": "PDV",
|
|
||||||
"total": "Ukupno",
|
"total": "Ukupno",
|
||||||
"cancelOrder": "Otkaži narudžbu"
|
"cancelOrder": "Otkaži narudžbu"
|
||||||
},
|
},
|
||||||
"cancelConfirm": {
|
"cancelConfirm": {
|
||||||
"title": "Otkaži narudžbu",
|
"title": "Otkaži narudžbu",
|
||||||
"message": "Jeste li sigurni da želite otkazati ovu narudžbu?",
|
"message": "Jeste li sigurni da želite otkazati ovu narudžbu?",
|
||||||
"confirm": "Otkaži narudžbu",
|
"confirm": "Otkaži",
|
||||||
"cancelling": "Otkazivanje..."
|
"cancelling": "Otkazivanje..."
|
||||||
},
|
},
|
||||||
"processing": "Narudžba se dovršava...",
|
"processing": "Narudžba se obrađuje..."
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ export default {
|
|||||||
"loadingDescription": "Učitavanje opisa proizvoda...",
|
"loadingDescription": "Učitavanje opisa proizvoda...",
|
||||||
"notFound": "Proizvod nije pronađen",
|
"notFound": "Proizvod nije pronađen",
|
||||||
"notFoundDescription": "Proizvod koji tražite ne postoji ili je uklonjen.",
|
"notFoundDescription": "Proizvod koji tražite ne postoji ili je uklonjen.",
|
||||||
"backToHome": "Povratak na početnu stranicu",
|
"backToHome": "Natrag na početnu stranicu",
|
||||||
"error": "Greška",
|
"error": "Greška",
|
||||||
"articleNumber": "Broj artikla",
|
"articleNumber": "Broj artikla",
|
||||||
"manufacturer": "Proizvođač",
|
"manufacturer": "Proizvođač",
|
||||||
"inclVat": "uključujući {{vat}}% PDV-a",
|
"inclVat": "uključujući {{vat}}% PDV-a",
|
||||||
|
"inclVatSimple": "uključujući PDV",
|
||||||
"priceUnit": "{{price}}/{{unit}}",
|
"priceUnit": "{{price}}/{{unit}}",
|
||||||
"new": "Novo",
|
"new": "Novo",
|
||||||
"weeks": "tjedana",
|
"weeks": "Tjedni",
|
||||||
"arriving": "Dolazak:",
|
"arriving": "Dolazak:",
|
||||||
"inclVatFooter": "uključujući {{vat}}% PDV-a,*",
|
"inclVatFooter": "uključujući {{vat}}% PDV-a,*",
|
||||||
"availability": "Dostupnost",
|
"availability": "Dostupnost",
|
||||||
@@ -25,7 +26,7 @@ export default {
|
|||||||
"pickupPrice": "Cijena preuzimanja: 19,90 € po reznici.",
|
"pickupPrice": "Cijena preuzimanja: 19,90 € po reznici.",
|
||||||
"consistsOf": "Sastoji se od:",
|
"consistsOf": "Sastoji se od:",
|
||||||
"loadingComponentDetails": "{{index}}. Učitavanje detalja komponente...",
|
"loadingComponentDetails": "{{index}}. Učitavanje detalja komponente...",
|
||||||
"loadingProduct": "Proizvod se učitava...",
|
"loadingProduct": "Učitavanje proizvoda...",
|
||||||
"individualPriceTotal": "Ukupna pojedinačna cijena:",
|
"individualPriceTotal": "Ukupna pojedinačna cijena:",
|
||||||
"setPrice": "Cijena seta:",
|
"setPrice": "Cijena seta:",
|
||||||
"yourSavings": "Vaša ušteda:",
|
"yourSavings": "Vaša ušteda:",
|
||||||
@@ -38,7 +39,7 @@ export default {
|
|||||||
"filteredOneProduct": "{{filtered}} od 1 proizvoda",
|
"filteredOneProduct": "{{filtered}} od 1 proizvoda",
|
||||||
"xOfYProducts": "{{x}} od {{y}} proizvoda"
|
"xOfYProducts": "{{x}} od {{y}} proizvoda"
|
||||||
},
|
},
|
||||||
"removeFiltersToSee": "Uklonite filtere da vidite proizvode",
|
"removeFiltersToSee": "Uklonite filtre da vidite proizvode",
|
||||||
"outOfStock": "Nema na skladištu",
|
"outOfStock": "Nema na skladištu",
|
||||||
"fromXProducts": "od {{count}} proizvoda",
|
"fromXProducts": "od {{count}} proizvoda",
|
||||||
"discount": {
|
"discount": {
|
||||||
|
|||||||
61
src/i18n/locales/hr/productDialogs.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export default {
|
||||||
|
"questionTitle": "Pitanje o proizvodu",
|
||||||
|
"questionSubtitle": "Imate li pitanje o ovom proizvodu? Rado ćemo vam pomoći.",
|
||||||
|
"questionSuccess": "Hvala na vašem pitanju! Javili smo vam se što je prije moguće.",
|
||||||
|
"nameLabel": "Ime",
|
||||||
|
"namePlaceholder": "Vaše ime",
|
||||||
|
"emailLabel": "Email",
|
||||||
|
"emailPlaceholder": "your.email@example.com",
|
||||||
|
"questionLabel": "Vaše pitanje",
|
||||||
|
"questionPlaceholder": "Opišite svoje pitanje o ovom proizvodu...",
|
||||||
|
"photosLabelQuestion": "Priložite fotografije uz svoje pitanje (opcionalno)",
|
||||||
|
"submitQuestion": "Pošalji pitanje",
|
||||||
|
"sending": "Šalje se...",
|
||||||
|
|
||||||
|
"ratingTitle": "Ocijenite proizvod",
|
||||||
|
"ratingSubtitle": "Podijelite svoje iskustvo s ovim proizvodom i pomozite drugim kupcima u odluci.",
|
||||||
|
"ratingSuccess": "Hvala na vašoj recenziji! Bit će objavljena nakon provjere.",
|
||||||
|
"emailHelper": "Vaš email neće biti objavljen",
|
||||||
|
"ratingLabel": "Ocjena *",
|
||||||
|
"pleaseRate": "Molimo ocijenite",
|
||||||
|
"ratingStars": "{{rating}} od 5 zvjezdica",
|
||||||
|
"reviewLabel": "Vaša recenzija (opcionalno)",
|
||||||
|
"reviewPlaceholder": "Opišite svoja iskustva s ovim proizvodom...",
|
||||||
|
"photosLabelRating": "Priložite fotografije uz svoju recenziju (opcionalno)",
|
||||||
|
"submitRating": "Pošalji recenziju",
|
||||||
|
"errorGeneric": "Došlo je do pogreške",
|
||||||
|
"errorPhotos": "Pogreška pri obradi fotografija",
|
||||||
|
|
||||||
|
"availabilityTitle": "Zatražite dostupnost",
|
||||||
|
"availabilitySubtitle": "Ovaj proizvod trenutno nije dostupan. Obavijestit ćemo vas čim ponovno bude na skladištu.",
|
||||||
|
"availabilitySuccessEmail": "Hvala na vašem zahtjevu! Obavijestit ćemo vas putem emaila čim proizvod ponovno bude dostupan.",
|
||||||
|
"availabilitySuccessTelegram": "Hvala na vašem zahtjevu! Obavijestit ćemo vas putem Telegrama čim proizvod ponovno bude dostupan.",
|
||||||
|
"notificationMethodLabel": "Kako želite biti obaviješteni?",
|
||||||
|
"telegramBotLabel": "Telegram Bot",
|
||||||
|
"telegramIdLabel": "Telegram ID",
|
||||||
|
"telegramPlaceholder": "@yourTelegramName or Telegram ID",
|
||||||
|
"telegramHelper": "Unesite svoje Telegram korisničko ime (s @) ili Telegram ID",
|
||||||
|
"messageLabel": "Poruka (opcionalno)",
|
||||||
|
"messagePlaceholder": "Dodatne informacije ili pitanja...",
|
||||||
|
"submitAvailability": "Zatražite dostupnost",
|
||||||
|
|
||||||
|
"photoUploadSelect": "Odaberite fotografije",
|
||||||
|
"photoUploadErrorMaxFiles": "Dozvoljeno maksimalno {{max}} datoteka",
|
||||||
|
"photoUploadErrorFileType": "Dozvoljene su samo slikovne datoteke (JPEG, PNG, GIF, WebP)",
|
||||||
|
"photoUploadErrorFileSize": "Datoteka je prevelika. Maksimum: {{maxSize}}MB",
|
||||||
|
"photoUploadSelectedFiles": "{{count}} datoteka je odabrano",
|
||||||
|
"photoUploadCompressed": "(komprimirano za prijenos)",
|
||||||
|
"photoUploadRemove": "Ukloni sliku",
|
||||||
|
"photoUploadLabelDefault": "Priložite fotografije (opcionalno)",
|
||||||
|
|
||||||
|
"shareTitle": "Podijeli",
|
||||||
|
"shareEmbed": "Ugradi",
|
||||||
|
"shareCopyLink": "Kopiraj link",
|
||||||
|
"shareSuccessEmbed": "Kod za ugradnju kopiran u međuspremnik!",
|
||||||
|
"shareErrorEmbed": "Pogreška pri kopiranju koda za ugradnju",
|
||||||
|
"shareSuccessLink": "Link kopiran u međuspremnik!",
|
||||||
|
"shareWhatsAppText": "Pogledajte ovaj proizvod: {{name}}",
|
||||||
|
"shareTelegramText": "Pogledajte ovaj proizvod: {{name}}",
|
||||||
|
"shareEmailSubject": "Preporuka proizvoda",
|
||||||
|
"shareEmailBody": "Pozdrav,\n\nŽelio/la bih vam preporučiti ovaj proizvod:\n\n{{name}}\n{{url}}\n\nSrdačan pozdrav"
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ export default {
|
|||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"password": "Jelszó",
|
"password": "Jelszó",
|
||||||
|
"newPassword": "Új jelszó",
|
||||||
"confirmPassword": "Jelszó megerősítése",
|
"confirmPassword": "Jelszó megerősítése",
|
||||||
"forgotPassword": "Elfelejtett jelszó?",
|
"forgotPassword": "Elfelejtett jelszó?",
|
||||||
"loginWithGoogle": "Bejelentkezés Google-lal",
|
"loginWithGoogle": "Bejelentkezés Google-lal",
|
||||||
@@ -13,13 +14,37 @@ export default {
|
|||||||
"privacyPolicy": "Adatvédelmi szabályzatot",
|
"privacyPolicy": "Adatvédelmi szabályzatot",
|
||||||
"passwordMinLength": "A jelszónak legalább 8 karakter hosszúnak kell lennie",
|
"passwordMinLength": "A jelszónak legalább 8 karakter hosszúnak kell lennie",
|
||||||
"newPasswordMinLength": "Az új jelszónak legalább 8 karakter hosszúnak kell lennie",
|
"newPasswordMinLength": "Az új jelszónak legalább 8 karakter hosszúnak kell lennie",
|
||||||
|
"backToHome": "Vissza a kezdőlapra",
|
||||||
"menu": {
|
"menu": {
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
"myProfile": "Saját profilom",
|
"myProfile": "Saját profil",
|
||||||
"checkout": "Pénztár",
|
"checkout": "Pénztár",
|
||||||
"orders": "Rendelések",
|
"orders": "Rendelések",
|
||||||
"settings": "Beállítások",
|
"settings": "Beállítások",
|
||||||
"adminDashboard": "Admin Vezérlőpult",
|
"adminDashboard": "Admin Vezérlőpult",
|
||||||
"adminUsers": "Admin Felhasználók"
|
"adminUsers": "Admin Felhasználók"
|
||||||
|
},
|
||||||
|
"resetPassword": {
|
||||||
|
"title": "Jelszó visszaállítása",
|
||||||
|
"button": "Jelszó visszaállítása",
|
||||||
|
"success": "A jelszavad sikeresen visszaállítva! Hamarosan átirányítunk a bejelentkezéshez...",
|
||||||
|
"invalidToken": "Érvényes token nem található. Kérjük, használd az emailedben található linket.",
|
||||||
|
"error": "Hiba történt a jelszó visszaállítása során",
|
||||||
|
"emailSent": "Egy link a jelszó visszaállításához elküldésre került az email címedre.",
|
||||||
|
"emailError": "Hiba történt az email küldése során"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"fillAllFields": "Kérjük, tölts ki minden mezőt",
|
||||||
|
"invalidEmail": "Kérjük, adj meg egy érvényes email címet",
|
||||||
|
"passwordsNotMatch": "A jelszavak nem egyeznek",
|
||||||
|
"passwordsNotMatchShort": "A jelszavak nem egyeznek",
|
||||||
|
"enterEmail": "Kérjük, add meg az email címed",
|
||||||
|
"loginFailed": "Bejelentkezés sikertelen",
|
||||||
|
"registerFailed": "Regisztráció sikertelen",
|
||||||
|
"googleLoginFailed": "Google bejelentkezés sikertelen",
|
||||||
|
"emailExists": "Már létezik felhasználó ezzel az email címmel. Kérjük, használj másik email címet vagy jelentkezz be."
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"registerComplete": "Sikeres regisztráció. Most már bejelentkezhetsz."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import navigation from './navigation.js';
|
|||||||
import auth from './auth.js';
|
import auth from './auth.js';
|
||||||
import cart from './cart.js';
|
import cart from './cart.js';
|
||||||
import product from './product.js';
|
import product from './product.js';
|
||||||
|
import productDialogs from './productDialogs.js';
|
||||||
import search from './search.js';
|
import search from './search.js';
|
||||||
import sorting from './sorting.js';
|
import sorting from './sorting.js';
|
||||||
import chat from './chat.js';
|
import chat from './chat.js';
|
||||||
@@ -18,6 +19,7 @@ import pages from './pages.js';
|
|||||||
import orders from './orders.js';
|
import orders from './orders.js';
|
||||||
import settings from './settings.js';
|
import settings from './settings.js';
|
||||||
import common from './common.js';
|
import common from './common.js';
|
||||||
|
import kitConfig from './kitConfig.js';
|
||||||
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
import legalDatenschutzBasic from './legal-datenschutz-basic.js';
|
||||||
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
import legalDatenschutzCustomer from './legal-datenschutz-customer.js';
|
||||||
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
import legalDatenschutzGoogleOrders from './legal-datenschutz-google-orders.js';
|
||||||
@@ -35,6 +37,7 @@ export default {
|
|||||||
"auth": auth,
|
"auth": auth,
|
||||||
"cart": cart,
|
"cart": cart,
|
||||||
"product": product,
|
"product": product,
|
||||||
|
"productDialogs": productDialogs,
|
||||||
"search": search,
|
"search": search,
|
||||||
"sorting": sorting,
|
"sorting": sorting,
|
||||||
"chat": chat,
|
"chat": chat,
|
||||||
@@ -50,6 +53,7 @@ export default {
|
|||||||
"orders": orders,
|
"orders": orders,
|
||||||
"settings": settings,
|
"settings": settings,
|
||||||
"common": common,
|
"common": common,
|
||||||
|
"kitConfig": kitConfig,
|
||||||
"legalDatenschutzBasic": legalDatenschutzBasic,
|
"legalDatenschutzBasic": legalDatenschutzBasic,
|
||||||
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
"legalDatenschutzCustomer": legalDatenschutzCustomer,
|
||||||
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
"legalDatenschutzGoogleOrders": legalDatenschutzGoogleOrders,
|
||||||
|
|||||||