Compare commits
41 Commits
mollie
...
e02b18e17f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e02b18e17f | ||
|
|
9ffbd5b84e | ||
|
|
f8dbb24823 | ||
|
|
13f1e14a3d | ||
|
|
6b7bcf4155 | ||
|
|
45258ac522 | ||
|
|
080515af68 | ||
|
|
33b229728f | ||
|
|
8d69b0566b | ||
|
|
280916224a | ||
|
|
fd77fc8f7f | ||
|
|
f5d6778def | ||
|
|
11a3522a97 | ||
|
|
51471d4a55 | ||
|
|
859a2c06d8 | ||
|
|
5c90d048fb | ||
|
|
cff9c88808 | ||
|
|
b78de53786 | ||
|
|
925667fc2c | ||
|
|
251352c660 | ||
|
|
88c757fd35 | ||
|
|
d8c802c2f1 | ||
|
|
056b63efa0 | ||
|
|
c7afad68b0 | ||
|
|
5157b7d781 | ||
|
|
9072a3c977 | ||
|
|
838e2fd786 | ||
|
|
abbb5e222d | ||
|
|
c216154bd7 | ||
|
|
9000b28ce5 | ||
|
|
8f2253f155 | ||
|
|
b33ece2875 | ||
|
|
02aff1e456 | ||
|
|
9e14827c91 | ||
|
|
8698816875 | ||
|
|
987de641e4 | ||
|
|
23e1742e40 | ||
|
|
205558d06c | ||
|
|
046979a64d | ||
|
|
161e377de4 | ||
|
|
73a88f508b |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -56,6 +56,8 @@ yarn-error.log*
|
||||
# Local configuration
|
||||
src/config.local.js
|
||||
|
||||
taxonomy-with-ids.de-DE*
|
||||
|
||||
# Local development notes
|
||||
dev-notes.md
|
||||
dev-notes.local.md
|
||||
21
.vscode/launch.json
vendored
21
.vscode/launch.json
vendored
@@ -3,20 +3,31 @@
|
||||
// This will install dependencies before starting the dev server
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"type": "node-terminal",
|
||||
"name": "Start with API propxy to seedheads.de (Install Deps)",
|
||||
"request": "launch",
|
||||
"command": "npm run start:seedheads",
|
||||
"preLaunchTask": "npm: install",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}, {
|
||||
"type": "node-terminal",
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
},{
|
||||
"name": "Start",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"command": "npm run start",
|
||||
"cwd": "${workspaceFolder}"
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
209
MULTILINGUAL_IMPLEMENTATION.md
Normal file
209
MULTILINGUAL_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Multilingual Implementation Guide
|
||||
|
||||
## Overview
|
||||
|
||||
Your website now supports multiple languages using **react-i18next**. The implementation is designed to work seamlessly with your existing class components and provides:
|
||||
|
||||
- **German (default)** and **English** support
|
||||
- Language persistence in localStorage
|
||||
- Dynamic language switching
|
||||
- SEO-friendly language attributes
|
||||
- Class component compatibility
|
||||
|
||||
## Features Implemented
|
||||
|
||||
### 1. Language Switcher
|
||||
- Located in the header next to the login/profile button
|
||||
- Shows current language (DE/EN) with flag icon
|
||||
- Dropdown menu for language selection
|
||||
- Persists selection in browser storage
|
||||
|
||||
### 2. Translated Components
|
||||
- **Header navigation**: Categories, Home links
|
||||
- **Authentication**: Login/register forms, profile menu
|
||||
- **Main pages**: Home, Actions, Store pages
|
||||
- **Cart**: Shopping cart title and sync dialog
|
||||
- **Product pages**: Basic UI elements (more can be added)
|
||||
- **Footer**: Basic elements (can be expanded)
|
||||
|
||||
### 3. Architecture
|
||||
- `src/i18n/index.js` - Main i18n configuration
|
||||
- `src/i18n/withTranslation.js` - HOCs for class components
|
||||
- `src/i18n/locales/de/translation.json` - German translations
|
||||
- `src/i18n/locales/en/translation.json` - English translations
|
||||
- `src/components/LanguageSwitcher.js` - Language selection component
|
||||
|
||||
## Usage for Developers
|
||||
|
||||
### Using Translations in Class Components
|
||||
|
||||
```javascript
|
||||
import { withI18n } from '../i18n/withTranslation.js';
|
||||
|
||||
class MyComponent extends Component {
|
||||
render() {
|
||||
const { t } = this.props; // Translation function
|
||||
|
||||
return (
|
||||
<Typography>
|
||||
{t('navigation.home')} // Translates to "Startseite" or "Home"
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withI18n()(MyComponent);
|
||||
```
|
||||
|
||||
### Using Translations in Function Components
|
||||
|
||||
```javascript
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const MyComponent = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Typography>
|
||||
{t('navigation.home')}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Language Context Access
|
||||
|
||||
```javascript
|
||||
import { withLanguage } from '../i18n/withTranslation.js';
|
||||
|
||||
class MyComponent extends Component {
|
||||
render() {
|
||||
const { languageContext } = this.props;
|
||||
|
||||
return (
|
||||
<Button onClick={() => languageContext.changeLanguage('en')}>
|
||||
Switch to English
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withLanguage(MyComponent);
|
||||
```
|
||||
|
||||
## Adding New Translations
|
||||
|
||||
### 1. Add to German (`src/i18n/locales/de/translation.json`)
|
||||
```json
|
||||
{
|
||||
"newSection": {
|
||||
"title": "Neuer Titel",
|
||||
"description": "Neue Beschreibung"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Add to English (`src/i18n/locales/en/translation.json`)
|
||||
```json
|
||||
{
|
||||
"newSection": {
|
||||
"title": "New Title",
|
||||
"description": "New Description"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Use in Components
|
||||
```javascript
|
||||
{t('newSection.title')}
|
||||
```
|
||||
|
||||
## Adding New Languages
|
||||
|
||||
### 1. Create Translation File
|
||||
Create `src/i18n/locales/fr/translation.json` for French
|
||||
|
||||
### 2. Update i18n Configuration
|
||||
```javascript
|
||||
// src/i18n/index.js
|
||||
import translationFR from './locales/fr/translation.json';
|
||||
|
||||
const resources = {
|
||||
de: { translation: translationDE },
|
||||
en: { translation: translationEN },
|
||||
fr: { translation: translationFR } // Add new language
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Update Language Provider
|
||||
```javascript
|
||||
// src/i18n/withTranslation.js
|
||||
availableLanguages: ['de', 'en', 'fr'] // Add to available languages
|
||||
```
|
||||
|
||||
### 4. Update Language Switcher
|
||||
```javascript
|
||||
// src/components/LanguageSwitcher.js
|
||||
const names = {
|
||||
'de': 'Deutsch',
|
||||
'en': 'English',
|
||||
'fr': 'Français' // Add language name
|
||||
};
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Language Detection Order
|
||||
Currently set to: `['localStorage', 'navigator', 'htmlTag']`
|
||||
- First checks localStorage for saved preference
|
||||
- Falls back to browser language
|
||||
- Finally checks HTML lang attribute
|
||||
|
||||
### Fallback Language
|
||||
Set to German (`de`) as your primary language
|
||||
|
||||
### Debug Mode
|
||||
Enabled in development mode for easier debugging
|
||||
|
||||
## SEO Considerations
|
||||
|
||||
- HTML `lang` attribute updates automatically
|
||||
- Config object provides language-specific metadata
|
||||
- Descriptions and keywords are language-aware
|
||||
- Can be extended for hreflang tags and URL localization
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Namespace your translations** - Use nested objects for organization
|
||||
2. **Provide fallbacks** - Always have German as fallback since it's your primary market
|
||||
3. **Use interpolation** - For dynamic content: `t('welcome', { name: 'John' })`
|
||||
4. **Keep translations consistent** - Use same structure in all language files
|
||||
5. **Test thoroughly** - Verify all UI elements in both languages
|
||||
|
||||
## Current Translation Coverage
|
||||
|
||||
- ✅ Navigation and menus
|
||||
- ✅ Authentication flows
|
||||
- ✅ Basic product elements
|
||||
- ✅ Cart functionality
|
||||
- ✅ Main page content
|
||||
- ⏳ Detailed product descriptions (can be added)
|
||||
- ⏳ Legal pages content (can be added)
|
||||
- ⏳ Form validation messages (can be added)
|
||||
- ⏳ Error messages (can be added)
|
||||
|
||||
## Performance
|
||||
|
||||
- Translations are bundled and loaded immediately
|
||||
- No additional network requests
|
||||
- Lightweight implementation
|
||||
- Language switching is instant
|
||||
|
||||
## Browser Support
|
||||
|
||||
Works with all modern browsers that support:
|
||||
- ES6 modules
|
||||
- localStorage
|
||||
- React 19
|
||||
|
||||
The implementation is production-ready and can be extended based on your specific needs!
|
||||
1710
package-lock.json
generated
1710
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -13,7 +13,11 @@
|
||||
"lint": "eslint src/**/*.{js,jsx}",
|
||||
"prerender": "node prerender.cjs",
|
||||
"prerender:prod": "cross-env NODE_ENV=production node prerender.cjs",
|
||||
"build:prerender": "npm run build:client && npm run prerender:prod"
|
||||
"build:prerender": "npm run build:client && npm run prerender:prod",
|
||||
"translate": "node translate-i18n.js",
|
||||
"translate:english": "node translate-i18n.js --only-english",
|
||||
"translate:skip-english": "node translate-i18n.js --skip-english",
|
||||
"translate:others": "node translate-i18n.js --skip-english"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -27,10 +31,15 @@
|
||||
"@stripe/react-stripe-js": "^3.7.0",
|
||||
"@stripe/stripe-js": "^7.3.1",
|
||||
"chart.js": "^4.5.0",
|
||||
"country-flag-icons": "^1.5.19",
|
||||
"html-react-parser": "^5.2.5",
|
||||
"i18next": "^25.3.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"openai": "^4.0.0",
|
||||
"react": "^19.1.0",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-i18next": "^15.6.0",
|
||||
"react-router-dom": "^7.6.2",
|
||||
"sharp": "^0.34.2",
|
||||
"socket.io-client": "^4.7.5"
|
||||
|
||||
@@ -27,6 +27,74 @@ const io = require("socket.io-client");
|
||||
const os = require("os");
|
||||
const { Worker, isMainThread, parentPort, workerData } = require("worker_threads");
|
||||
|
||||
// Initialize i18n for prerendering with German as default
|
||||
const i18n = require("i18next");
|
||||
const { initReactI18next } = require("react-i18next");
|
||||
|
||||
// Import all translation files
|
||||
const translationDE = require("./src/i18n/locales/de/translation.js").default;
|
||||
const translationEN = require("./src/i18n/locales/en/translation.js").default;
|
||||
const translationAR = require("./src/i18n/locales/ar/translation.js").default;
|
||||
const translationBG = require("./src/i18n/locales/bg/translation.js").default;
|
||||
const translationCS = require("./src/i18n/locales/cs/translation.js").default;
|
||||
const translationEL = require("./src/i18n/locales/el/translation.js").default;
|
||||
const translationES = require("./src/i18n/locales/es/translation.js").default;
|
||||
const translationFR = require("./src/i18n/locales/fr/translation.js").default;
|
||||
const translationHR = require("./src/i18n/locales/hr/translation.js").default;
|
||||
const translationHU = require("./src/i18n/locales/hu/translation.js").default;
|
||||
const translationIT = require("./src/i18n/locales/it/translation.js").default;
|
||||
const translationPL = require("./src/i18n/locales/pl/translation.js").default;
|
||||
const translationRO = require("./src/i18n/locales/ro/translation.js").default;
|
||||
const translationRU = require("./src/i18n/locales/ru/translation.js").default;
|
||||
const translationSK = require("./src/i18n/locales/sk/translation.js").default;
|
||||
const translationSL = require("./src/i18n/locales/sl/translation.js").default;
|
||||
const translationSR = require("./src/i18n/locales/sr/translation.js").default;
|
||||
const translationSV = require("./src/i18n/locales/sv/translation.js").default;
|
||||
const translationTR = require("./src/i18n/locales/tr/translation.js").default;
|
||||
const translationUK = require("./src/i18n/locales/uk/translation.js").default;
|
||||
const translationZH = require("./src/i18n/locales/zh/translation.js").default;
|
||||
|
||||
// Initialize i18n for prerendering
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources: {
|
||||
de: { translation: translationDE },
|
||||
en: { translation: translationEN },
|
||||
ar: { translation: translationAR },
|
||||
bg: { translation: translationBG },
|
||||
cs: { translation: translationCS },
|
||||
el: { translation: translationEL },
|
||||
es: { translation: translationES },
|
||||
fr: { translation: translationFR },
|
||||
hr: { translation: translationHR },
|
||||
hu: { translation: translationHU },
|
||||
it: { translation: translationIT },
|
||||
pl: { translation: translationPL },
|
||||
ro: { translation: translationRO },
|
||||
ru: { translation: translationRU },
|
||||
sk: { translation: translationSK },
|
||||
sl: { translation: translationSL },
|
||||
sr: { translation: translationSR },
|
||||
sv: { translation: translationSV },
|
||||
tr: { translation: translationTR },
|
||||
uk: { translation: translationUK },
|
||||
zh: { translation: translationZH }
|
||||
},
|
||||
lng: 'de', // Default to German for prerendering
|
||||
fallbackLng: 'de',
|
||||
debug: false,
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
},
|
||||
react: {
|
||||
useSuspense: false
|
||||
}
|
||||
});
|
||||
|
||||
// Make i18n available globally for components
|
||||
global.i18n = i18n;
|
||||
|
||||
// Import split modules
|
||||
const config = require("./prerender/config.cjs");
|
||||
|
||||
|
||||
@@ -11,6 +11,99 @@ Crawl-delay: 0
|
||||
return robotsTxt;
|
||||
};
|
||||
|
||||
// Helper function to determine unit pricing data based on product data
|
||||
const determineUnitPricingData = (product) => {
|
||||
const result = {
|
||||
unit_pricing_measure: null,
|
||||
unit_pricing_base_measure: null
|
||||
};
|
||||
|
||||
// Unit mapping from German to Google Shopping accepted units
|
||||
const unitMapping = {
|
||||
// Volume (German -> Google)
|
||||
'Milliliter': 'ml',
|
||||
'milliliter': 'ml',
|
||||
'ml': 'ml',
|
||||
'Liter': 'l',
|
||||
'liter': 'l',
|
||||
'l': 'l',
|
||||
'Zentiliter': 'cl',
|
||||
'zentiliter': 'cl',
|
||||
'cl': 'cl',
|
||||
|
||||
// Weight (German -> Google)
|
||||
'Gramm': 'g',
|
||||
'gramm': 'g',
|
||||
'g': 'g',
|
||||
'Kilogramm': 'kg',
|
||||
'kilogramm': 'kg',
|
||||
'kg': 'kg',
|
||||
'Milligramm': 'mg',
|
||||
'milligramm': 'mg',
|
||||
'mg': 'mg',
|
||||
|
||||
// Length (German -> Google)
|
||||
'Meter': 'm',
|
||||
'meter': 'm',
|
||||
'm': 'm',
|
||||
'Zentimeter': 'cm',
|
||||
'zentimeter': 'cm',
|
||||
'cm': 'cm',
|
||||
|
||||
// Count (German -> Google)
|
||||
'Stück': 'ct',
|
||||
'stück': 'ct',
|
||||
'Stk': 'ct',
|
||||
'stk': 'ct',
|
||||
'ct': 'ct',
|
||||
'Blatt': 'sheet',
|
||||
'blatt': 'sheet',
|
||||
'sheet': 'sheet'
|
||||
};
|
||||
|
||||
// Helper function to convert German unit to Google Shopping unit
|
||||
const convertUnit = (unit) => {
|
||||
if (!unit) return null;
|
||||
const trimmedUnit = unit.trim();
|
||||
return unitMapping[trimmedUnit] || trimmedUnit.toLowerCase();
|
||||
};
|
||||
|
||||
// unit_pricing_measure: The quantity unit of the product as it's sold
|
||||
if (product.fEinheitMenge && product.cEinheit) {
|
||||
const amount = parseFloat(product.fEinheitMenge);
|
||||
const unit = convertUnit(product.cEinheit);
|
||||
|
||||
if (amount > 0 && unit) {
|
||||
result.unit_pricing_measure = `${amount} ${unit}`;
|
||||
}
|
||||
}
|
||||
|
||||
// unit_pricing_base_measure: The base quantity unit for unit pricing
|
||||
if (product.cGrundEinheit && product.cGrundEinheit.trim()) {
|
||||
const baseUnit = convertUnit(product.cGrundEinheit);
|
||||
if (baseUnit) {
|
||||
// Base measure usually needs a quantity (like 100g, 1l, etc.)
|
||||
// If it's just a unit, we'll add a default quantity
|
||||
if (baseUnit.match(/^[a-z]+$/)) {
|
||||
// For weight/volume units, use standard base quantities
|
||||
if (['g', 'kg', 'mg'].includes(baseUnit)) {
|
||||
result.unit_pricing_base_measure = baseUnit === 'kg' ? '1 kg' : '100 g';
|
||||
} else if (['ml', 'l', 'cl'].includes(baseUnit)) {
|
||||
result.unit_pricing_base_measure = baseUnit === 'l' ? '1 l' : '100 ml';
|
||||
} else if (['m', 'cm'].includes(baseUnit)) {
|
||||
result.unit_pricing_base_measure = baseUnit === 'm' ? '1 m' : '100 cm';
|
||||
} else {
|
||||
result.unit_pricing_base_measure = `1 ${baseUnit}`;
|
||||
}
|
||||
} else {
|
||||
result.unit_pricing_base_measure = baseUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const generateProductsXml = (allProductsData = [], baseUrl, config) => {
|
||||
const currentDate = new Date().toISOString();
|
||||
|
||||
@@ -23,124 +116,131 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => {
|
||||
const getGoogleProductCategory = (categoryId) => {
|
||||
const categoryMappings = {
|
||||
// Seeds & Plants
|
||||
689: "Home & Garden > Plants > Seeds",
|
||||
706: "Home & Garden > Plants", // Stecklinge (cuttings)
|
||||
376: "Home & Garden > Plants > Plant & Herb Growing Kits", // Grow-Sets
|
||||
689: "543561", // Seeds (Saatgut)
|
||||
706: "543561", // Stecklinge (cuttings) – ebenfalls Pflanzen/Saatgut
|
||||
376: "2802", // Grow-Sets – Pflanzen- & Kräuteranbausets
|
||||
|
||||
// Headshop & Accessories
|
||||
709: "Arts & Entertainment > Hobbies & Creative Arts", // Headshop
|
||||
711: "Arts & Entertainment > Hobbies & Creative Arts", // Bongs
|
||||
714: "Arts & Entertainment > Hobbies & Creative Arts", // Zubehör
|
||||
748: "Arts & Entertainment > Hobbies & Creative Arts", // Köpfe
|
||||
749: "Arts & Entertainment > Hobbies & Creative Arts", // Chillums / Diffusoren / Kupplungen
|
||||
896: "Electronics > Electronics Accessories", // Vaporizer
|
||||
710: "Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils", // Grinder
|
||||
709: "4082", // Headshop – Rauchzubehör
|
||||
711: "4082", // Headshop > Bongs – Rauchzubehör
|
||||
714: "4082", // Headshop > Bongs > Zubehör – Rauchzubehör
|
||||
748: "4082", // Headshop > Bongs > Köpfe – Rauchzubehör
|
||||
749: "4082", // Headshop > Bongs > Chillums/Diffusoren/Kupplungen – Rauchzubehör
|
||||
896: "3151", // Headshop > Vaporizer – Vaporizer
|
||||
710: "5109", // Headshop > Grinder – Gewürzmühlen (Küchenhelfer)
|
||||
|
||||
// Measuring & Packaging
|
||||
186: "Business & Industrial", // Wiegen & Verpacken
|
||||
187: "Business & Industrial > Science & Laboratory > Lab Equipment", // Waagen
|
||||
346: "Home & Garden > Kitchen & Dining > Food Storage", // Vakuumbeutel
|
||||
355: "Home & Garden > Kitchen & Dining > Food Storage", // Boveda & Integra Boost
|
||||
407: "Home & Garden > Kitchen & Dining > Food Storage", // Grove Bags
|
||||
449: "Home & Garden > Kitchen & Dining > Food Storage", // Cliptütchen
|
||||
539: "Home & Garden > Kitchen & Dining > Food Storage", // Gläser & Dosen
|
||||
186: "5631", // Headshop > Wiegen & Verpacken – Aufbewahrung/Zubehör
|
||||
187: "4767", // Headshop > Waagen – Personenwaagen (Medizinisch)
|
||||
346: "7118", // Headshop > Vakuumbeutel – Vakuumierer-Beutel
|
||||
355: "606", // Headshop > Boveda & Integra Boost – Luftentfeuchter (nächstmögliche)
|
||||
407: "3561", // Headshop > Grove Bags – Aufbewahrungsbehälter
|
||||
449: "1496", // Headshop > Cliptütchen – Lebensmittelverpackungsmaterial
|
||||
539: "3110", // Headshop > Gläser & Dosen – Lebensmittelbehälter
|
||||
|
||||
// Lighting & Equipment
|
||||
694: "Home & Garden > Lighting", // Lampen
|
||||
261: "Home & Garden > Lighting", // Lampenzubehör
|
||||
694: "3006", // Lampen – Lampen (Beleuchtung)
|
||||
261: "3006", // Zubehör > Lampenzubehör – Lampen
|
||||
|
||||
// Plants & Growing
|
||||
691: "Home & Garden > Lawn & Garden > Fertilizers", // Dünger
|
||||
692: "Home & Garden > Lawn & Garden > Fertilizers", // Dünger - Zubehör
|
||||
693: "Sporting Goods > Outdoor Recreation > Camping & Hiking > Tents", // Zelte
|
||||
691: "500033", // Dünger – Dünger
|
||||
692: "5633", // Zubehör > Dünger-Zubehör – Zubehör für Gartenarbeit
|
||||
693: "5655", // Zelte – Zelte
|
||||
|
||||
// Pots & Containers
|
||||
219: "Home & Garden > Decor > Planters & Pots", // Töpfe
|
||||
220: "Home & Garden > Decor > Planters & Pots", // Untersetzer
|
||||
301: "Home & Garden > Decor > Planters & Pots", // Stofftöpfe
|
||||
317: "Home & Garden > Decor > Planters & Pots", // Air-Pot
|
||||
364: "Home & Garden > Decor > Planters & Pots", // Kunststofftöpfe
|
||||
292: "Home & Garden > Decor > Planters & Pots", // Trays & Fluttische
|
||||
219: "113", // Töpfe – Blumentöpfe & Pflanzgefäße
|
||||
220: "3173", // Töpfe > Untersetzer – Gartentopfuntersetzer und Trays
|
||||
301: "113", // Töpfe > Stofftöpfe – (Blumentöpfe/Pflanzgefäße)
|
||||
317: "113", // Töpfe > Air-Pot – (Blumentöpfe/Pflanzgefäße)
|
||||
364: "113", // Töpfe > Kunststofftöpfe – (Blumentöpfe/Pflanzgefäße)
|
||||
292: "3568", // Bewässerung > Trays & Fluttische – Bewässerungssysteme
|
||||
|
||||
// Ventilation & Climate
|
||||
703: "Home & Garden > Outdoor Power Tools", // Abluft-Sets
|
||||
247: "Home & Garden > Outdoor Power Tools", // Belüftung
|
||||
214: "Home & Garden > Outdoor Power Tools", // Umluft-Ventilatoren
|
||||
308: "Home & Garden > Outdoor Power Tools", // Ab- und Zuluft
|
||||
609: "Home & Garden > Outdoor Power Tools", // Schalldämpfer
|
||||
248: "Home & Garden > Pool & Spa > Pool & Spa Filters", // Aktivkohlefilter
|
||||
392: "Home & Garden > Pool & Spa > Pool & Spa Filters", // Zuluftfilter
|
||||
658: "Home & Garden > Climate Control > Dehumidifiers", // Luftbe- und entfeuchter
|
||||
310: "Home & Garden > Climate Control > Heating", // Heizmatten
|
||||
379: "Home & Garden > Household Supplies > Air Fresheners", // Geruchsneutralisation
|
||||
703: "2802", // Grow-Sets > Abluft-Sets – (verwendet Pflanzen-Kräuter-Anbausets)
|
||||
247: "1700", // Belüftung – Ventilatoren (Klimatisierung)
|
||||
214: "1700", // Belüftung > Umluft-Ventilatoren – Ventilatoren
|
||||
308: "1700", // Belüftung > Ab- und Zuluft – Ventilatoren
|
||||
609: "1700", // Belüftung > Ab- und Zuluft > Schalldämpfer – Ventilatoren
|
||||
248: "1700", // Belüftung > Aktivkohlefilter – Ventilatoren (nächstmögliche)
|
||||
392: "1700", // Belüftung > Ab- und Zuluft > Zuluftfilter – Ventilatoren
|
||||
658: "606", // Belüftung > Luftbe- und -entfeuchter – Luftentfeuchter
|
||||
310: "2802", // Anzucht > Heizmatten – Pflanzen- & Kräuteranbausets
|
||||
379: "5631", // Belüftung > Geruchsneutralisation – Haushaltsbedarf: Aufbewahrung
|
||||
|
||||
// Irrigation & Watering
|
||||
221: "Home & Garden > Lawn & Garden > Watering Equipment", // Bewässerung
|
||||
250: "Home & Garden > Lawn & Garden > Watering Equipment", // Schläuche
|
||||
297: "Home & Garden > Lawn & Garden > Watering Equipment", // Pumpen
|
||||
354: "Home & Garden > Lawn & Garden > Watering Equipment", // Sprüher
|
||||
372: "Home & Garden > Lawn & Garden > Watering Equipment", // AutoPot
|
||||
389: "Home & Garden > Lawn & Garden > Watering Equipment", // Blumat
|
||||
405: "Home & Garden > Lawn & Garden > Watering Equipment", // Schläuche
|
||||
425: "Home & Garden > Lawn & Garden > Watering Equipment", // Wassertanks
|
||||
480: "Home & Garden > Lawn & Garden > Watering Equipment", // Tropfer
|
||||
519: "Home & Garden > Lawn & Garden > Watering Equipment", // Pumpsprüher
|
||||
221: "3568", // Bewässerung – Bewässerungssysteme (Gesamt)
|
||||
250: "6318", // Bewässerung > Schläuche – Gartenschläuche
|
||||
297: "500100", // Bewässerung > Pumpen – Bewässerung-/Sprinklerpumpen
|
||||
354: "3780", // Bewässerung > Sprüher – Sprinkler & Sprühköpfe
|
||||
372: "3568", // Bewässerung > AutoPot – Bewässerungssysteme
|
||||
389: "3568", // Bewässerung > Blumat – Bewässerungssysteme
|
||||
405: "6318", // Bewässerung > Schläuche – Gartenschläuche
|
||||
425: "3568", // Bewässerung > Wassertanks – Bewässerungssysteme
|
||||
480: "3568", // Bewässerung > Tropfer – Bewässerungssysteme
|
||||
519: "3568", // Bewässerung > Pumpsprüher – Bewässerungssysteme
|
||||
|
||||
// Growing Media & Soils
|
||||
242: "Home & Garden > Lawn & Garden > Fertilizers", // Böden
|
||||
243: "Home & Garden > Lawn & Garden > Fertilizers", // Erde
|
||||
269: "Home & Garden > Lawn & Garden > Fertilizers", // Kokos
|
||||
580: "Home & Garden > Lawn & Garden > Fertilizers", // Perlite & Blähton
|
||||
242: "543677", // Böden – Gartenerde
|
||||
243: "543677", // Böden > Erde – Gartenerde
|
||||
269: "543677", // Böden > Kokos – Gartenerde
|
||||
580: "543677", // Böden > Perlite & Blähton – Gartenerde
|
||||
|
||||
// Propagation & Starting
|
||||
286: "Home & Garden > Plants", // Anzucht
|
||||
298: "Home & Garden > Plants", // Steinwolltrays
|
||||
421: "Home & Garden > Plants", // Vermehrungszubehör
|
||||
489: "Home & Garden > Plants", // EazyPlug & Jiffy
|
||||
359: "Home & Garden > Outdoor Structures > Greenhouses", // Gewächshäuser
|
||||
286: "2802", // Anzucht – Pflanzen- & Kräuteranbausets
|
||||
298: "2802", // Anzucht > Steinwolltrays – Pflanzen- & Kräuteranbausets
|
||||
421: "2802", // Anzucht > Vermehrungszubehör – Pflanzen- & Kräuteranbausets
|
||||
489: "2802", // Anzucht > EazyPlug & Jiffy – Pflanzen- & Kräuteranbausets
|
||||
359: "3103", // Anzucht > Gewächshäuser – Gewächshäuser
|
||||
|
||||
// Tools & Equipment
|
||||
373: "Home & Garden > Tools > Hand Tools", // GrowTool
|
||||
403: "Home & Garden > Kitchen & Dining > Kitchen Tools & Utensils", // Messbecher & mehr
|
||||
259: "Home & Garden > Tools > Hand Tools", // Pressen
|
||||
280: "Home & Garden > Tools > Hand Tools", // Erntescheeren
|
||||
258: "Home & Garden > Tools", // Ernte & Verarbeitung
|
||||
278: "Home & Garden > Tools", // Extraktion
|
||||
302: "Home & Garden > Tools", // Erntemaschinen
|
||||
373: "3568", // Bewässerung > GrowTool – Bewässerungssysteme
|
||||
403: "3999", // Bewässerung > Messbecher & mehr – Messbecher & Dosierlöffel
|
||||
259: "756", // Zubehör > Ernte & Verarbeitung > Pressen – Nudelmaschinen
|
||||
280: "2948", // Zubehör > Ernte & Verarbeitung > Erntescheeren – Küchenmesser
|
||||
258: "684", // Zubehör > Ernte & Verarbeitung – Abfallzerkleinerer
|
||||
278: "5057", // Zubehör > Ernte & Verarbeitung > Extraktion – Slush-Eis-Maschinen
|
||||
302: "7332", // Zubehör > Ernte & Verarbeitung > Erntemaschinen – Gartenmaschinen
|
||||
|
||||
// Hardware & Plumbing
|
||||
222: "Hardware > Plumbing", // PE-Teile
|
||||
374: "Hardware > Plumbing > Plumbing Fittings", // Verbindungsteile
|
||||
222: "3568", // Bewässerung > PE-Teile – Bewässerungssysteme
|
||||
374: "1700", // Belüftung > Ab- und Zuluft > Verbindungsteile – Ventilatoren
|
||||
|
||||
// Electronics & Control
|
||||
314: "Electronics > Electronics Accessories", // Steuergeräte
|
||||
408: "Electronics > Electronics Accessories", // GrowControl
|
||||
344: "Business & Industrial > Science & Laboratory > Lab Equipment", // Messgeräte
|
||||
555: "Business & Industrial > Science & Laboratory > Lab Equipment > Microscopes", // Mikroskope
|
||||
314: "1700", // Belüftung > Steuergeräte – Ventilatoren
|
||||
408: "1700", // Belüftung > Steuergeräte > GrowControl – Ventilatoren
|
||||
344: "1207", // Zubehör > Messgeräte – Messwerkzeuge & Messwertgeber
|
||||
555: "4555", // Zubehör > Anbauzubehör > Mikroskope – Mikroskope
|
||||
|
||||
// Camping & Outdoor
|
||||
226: "Sporting Goods > Outdoor Recreation > Camping & Hiking", // Zeltzubehör
|
||||
226: "5655", // Zubehör > Zeltzubehör – Zelte
|
||||
|
||||
// Plant Care & Protection
|
||||
239: "Home & Garden > Lawn & Garden > Pest Control", // Pflanzenschutz
|
||||
240: "Home & Garden > Plants", // Anbauzubehör
|
||||
239: "4085", // Zubehör > Anbauzubehör > Pflanzenschutz – Herbizide
|
||||
240: "5633", // Zubehör > Anbauzubehör – Zubehör für Gartenarbeit
|
||||
|
||||
// Office & Media
|
||||
424: "Office Supplies > Labels", // Etiketten & Schilder
|
||||
387: "Media > Books", // Literatur
|
||||
424: "4377", // Zubehör > Anbauzubehör > Etiketten & Schilder – Etiketten & Anhängerschilder
|
||||
387: "543541", // Zubehör > Anbauzubehör > Literatur – Bücher
|
||||
|
||||
// General categories
|
||||
705: "Home & Garden", // Set-Konfigurator
|
||||
686: "Home & Garden", // Zubehör
|
||||
741: "Home & Garden", // Zubehör
|
||||
294: "Home & Garden", // Zubehör
|
||||
695: "Home & Garden", // Zubehör
|
||||
293: "Home & Garden", // Trockennetze
|
||||
4: "Home & Garden", // Sonstiges
|
||||
450: "Home & Garden", // Restposten
|
||||
705: "2802", // Grow-Sets > Set-Konfigurator – (ebenfalls Pflanzen-Anbausets)
|
||||
686: "1700", // Belüftung > Aktivkohlefilter > Zubehör – Ventilatoren
|
||||
741: "1700", // Belüftung > Ab- und Zuluft > Zubehör – Ventilatoren
|
||||
294: "3568", // Bewässerung > Zubehör – Bewässerungssysteme
|
||||
695: "5631", // Zubehör – Haushaltsbedarf: Aufbewahrung
|
||||
293: "5631", // Zubehör > Ernte & Verarbeitung > Trockennetze – Haushaltsbedarf: Aufbewahrung
|
||||
4: "5631", // Zubehör > Anbauzubehör > Sonstiges – Haushaltsbedarf: Aufbewahrung
|
||||
450: "5631", // Zubehör > Anbauzubehör > Restposten – Haushaltsbedarf: Aufbewahrung
|
||||
};
|
||||
|
||||
return categoryMappings[categoryId] || "Home & Garden > Plants";
|
||||
const categoryId_str = categoryMappings[categoryId] || "5631"; // Default to Haushaltsbedarf: Aufbewahrung
|
||||
|
||||
// Validate that the category ID is not empty
|
||||
if (!categoryId_str || categoryId_str.trim() === "") {
|
||||
return "5631"; // Haushaltsbedarf: Aufbewahrung
|
||||
}
|
||||
|
||||
return categoryId_str;
|
||||
};
|
||||
|
||||
let productsXml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -150,7 +250,7 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => {
|
||||
<link>${baseUrl}</link>
|
||||
<description>${config.descriptions.short}</description>
|
||||
<lastBuildDate>${currentDate}</lastBuildDate>
|
||||
<language>${config.language}</language>`;
|
||||
<language>de-DE</language>`;
|
||||
|
||||
// Helper function to clean text content of problematic characters
|
||||
const cleanTextContent = (text) => {
|
||||
@@ -318,6 +418,17 @@ const generateProductsXml = (allProductsData = [], baseUrl, config) => {
|
||||
<g:shipping_weight>${parseFloat(product.weight).toFixed(2)} g</g:shipping_weight>`;
|
||||
}
|
||||
|
||||
// Add unit pricing data (required by German law for many products)
|
||||
const unitPricingData = determineUnitPricingData(product);
|
||||
if (unitPricingData.unit_pricing_measure) {
|
||||
productsXml += `
|
||||
<g:unit_pricing_measure>${unitPricingData.unit_pricing_measure}</g:unit_pricing_measure>`;
|
||||
}
|
||||
if (unitPricingData.unit_pricing_base_measure) {
|
||||
productsXml += `
|
||||
<g:unit_pricing_base_measure>${unitPricingData.unit_pricing_base_measure}</g:unit_pricing_base_measure>`;
|
||||
}
|
||||
|
||||
productsXml += `
|
||||
</item>`;
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 85 KiB |
BIN
public/assets/images/filiale1.jpg
Normal file
BIN
public/assets/images/filiale1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 208 KiB |
BIN
public/assets/images/filiale2.jpg
Normal file
BIN
public/assets/images/filiale2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 242 KiB |
BIN
public/assets/images/presse.jpg
Normal file
BIN
public/assets/images/presse.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
BIN
public/assets/images/purpl.jpg
Normal file
BIN
public/assets/images/purpl.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 68 KiB |
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<html lang="de" data-i18n-lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
172
src/App.js
172
src/App.js
@@ -18,13 +18,19 @@ import BugReportIcon from "@mui/icons-material/BugReport";
|
||||
|
||||
import SocketProvider from "./providers/SocketProvider.js";
|
||||
import SocketContext from "./contexts/SocketContext.js";
|
||||
import { CarouselProvider } from "./contexts/CarouselContext.js";
|
||||
import config from "./config.js";
|
||||
import ScrollToTop from "./components/ScrollToTop.js";
|
||||
|
||||
// Import i18n
|
||||
import './i18n/index.js';
|
||||
import { LanguageProvider } from './i18n/withTranslation.js';
|
||||
import i18n from './i18n/index.js';
|
||||
//import TelemetryService from './services/telemetryService.js';
|
||||
|
||||
import Header from "./components/Header.js";
|
||||
import Footer from "./components/Footer.js";
|
||||
import Home from "./pages/Home.js";
|
||||
import MainPageLayout from "./components/MainPageLayout.js";
|
||||
|
||||
// Lazy load all route components to reduce initial bundle size
|
||||
const Content = lazy(() => import(/* webpackChunkName: "content" */ "./components/Content.js"));
|
||||
@@ -40,7 +46,7 @@ const ServerLogsPage = lazy(() => import(/* webpackChunkName: "admin-logs" */ ".
|
||||
// Lazy load legal pages - rarely accessed
|
||||
const Datenschutz = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Datenschutz.js"));
|
||||
const AGB = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/AGB.js"));
|
||||
const NotFound404 = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/NotFound404.js"));
|
||||
//const NotFound404 = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/NotFound404.js")); <Route path="/404" element={<NotFound404 />} />
|
||||
const Sitemap = lazy(() => import(/* webpackChunkName: "sitemap" */ "./pages/Sitemap.js"));
|
||||
const Impressum = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Impressum.js"));
|
||||
const Batteriegesetzhinweise = lazy(() => import(/* webpackChunkName: "legal" */ "./pages/Batteriegesetzhinweise.js"));
|
||||
@@ -50,6 +56,13 @@ const Widerrufsrecht = lazy(() => import(/* webpackChunkName: "legal" */ "./page
|
||||
const GrowTentKonfigurator = lazy(() => import(/* webpackChunkName: "konfigurator" */ "./pages/GrowTentKonfigurator.js"));
|
||||
const ChatAssistant = lazy(() => import(/* webpackChunkName: "chat" */ "./components/ChatAssistant.js"));
|
||||
|
||||
// Lazy load separate pages that are truly different
|
||||
const PresseverleihPage = lazy(() => import(/* webpackChunkName: "presseverleih" */ "./pages/PresseverleihPage.js"));
|
||||
const ThcTestPage = lazy(() => import(/* webpackChunkName: "thc-test" */ "./pages/ThcTestPage.js"));
|
||||
|
||||
// Lazy load payment success page
|
||||
const PaymentSuccess = lazy(() => import(/* webpackChunkName: "payment" */ "./components/PaymentSuccess.js"));
|
||||
|
||||
// Import theme from separate file to reduce main bundle size
|
||||
import defaultTheme from "./theme.js";
|
||||
// Lazy load theme customizer for development only
|
||||
@@ -195,60 +208,70 @@ const AppContent = ({ currentTheme, onThemeChange }) => {
|
||||
<CircularProgress color="primary" />
|
||||
</Box>
|
||||
}>
|
||||
<Routes>
|
||||
{/* Home page with text only */}
|
||||
<Route path="/" element={<Home />} />
|
||||
<CarouselProvider>
|
||||
<Routes>
|
||||
{/* Main pages using unified component */}
|
||||
<Route path="/" element={<MainPageLayout />} />
|
||||
<Route path="/aktionen" element={<MainPageLayout />} />
|
||||
<Route path="/filiale" element={<MainPageLayout />} />
|
||||
|
||||
{/* Category page - Render Content in parallel */}
|
||||
<Route
|
||||
path="/Kategorie/:categoryId"
|
||||
element={<Content socket={socket} socketB={socketB} />}
|
||||
/>
|
||||
{/* Single product page */}
|
||||
<Route
|
||||
path="/Artikel/:seoName"
|
||||
element={<ProductDetailWithSocket />}
|
||||
/>
|
||||
{/* Category page - Render Content in parallel */}
|
||||
<Route
|
||||
path="/Kategorie/:categoryId"
|
||||
element={<Content socket={socket} socketB={socketB} />}
|
||||
/>
|
||||
{/* Single product page */}
|
||||
<Route
|
||||
path="/Artikel/:seoName"
|
||||
element={<ProductDetailWithSocket />}
|
||||
/>
|
||||
|
||||
{/* Search page - Render Content in parallel */}
|
||||
<Route path="/search" element={<Content socket={socket} socketB={socketB} />} />
|
||||
{/* Search page - Render Content in parallel */}
|
||||
<Route path="/search" element={<Content socket={socket} socketB={socketB} />} />
|
||||
|
||||
{/* Profile page */}
|
||||
<Route path="/profile" element={<ProfilePageWithSocket />} />
|
||||
{/* Profile page */}
|
||||
<Route path="/profile" element={<ProfilePageWithSocket />} />
|
||||
|
||||
{/* Reset password page */}
|
||||
<Route
|
||||
path="/resetPassword"
|
||||
element={<ResetPassword socket={socket} socketB={socketB} />}
|
||||
/>
|
||||
{/* Payment success page for Mollie redirects */}
|
||||
<Route path="/payment/success" element={<PaymentSuccess />} />
|
||||
|
||||
{/* Admin page */}
|
||||
<Route path="/admin" element={<AdminPage socket={socket} socketB={socketB} />} />
|
||||
|
||||
{/* Admin Users page */}
|
||||
<Route path="/admin/users" element={<UsersPage socket={socket} socketB={socketB} />} />
|
||||
|
||||
{/* Admin Server Logs page */}
|
||||
<Route path="/admin/logs" element={<ServerLogsPage socket={socket} socketB={socketB} />} />
|
||||
{/* Reset password page */}
|
||||
<Route
|
||||
path="/resetPassword"
|
||||
element={<ResetPassword socket={socket} socketB={socketB} />}
|
||||
/>
|
||||
|
||||
{/* Legal pages */}
|
||||
<Route path="/datenschutz" element={<Datenschutz />} />
|
||||
<Route path="/agb" element={<AGB />} />
|
||||
<Route path="/404" element={<NotFound404 />} />
|
||||
<Route path="/sitemap" element={<Sitemap />} />
|
||||
<Route path="/impressum" element={<Impressum />} />
|
||||
<Route
|
||||
path="/batteriegesetzhinweise"
|
||||
element={<Batteriegesetzhinweise />}
|
||||
/>
|
||||
<Route path="/widerrufsrecht" element={<Widerrufsrecht />} />
|
||||
{/* Admin page */}
|
||||
<Route path="/admin" element={<AdminPage socket={socket} socketB={socketB} />} />
|
||||
|
||||
{/* Admin Users page */}
|
||||
<Route path="/admin/users" element={<UsersPage socket={socket} socketB={socketB} />} />
|
||||
|
||||
{/* Admin Server Logs page */}
|
||||
<Route path="/admin/logs" element={<ServerLogsPage socket={socket} socketB={socketB} />} />
|
||||
|
||||
{/* Grow Tent Configurator */}
|
||||
<Route path="/Konfigurator" element={<GrowTentKonfigurator />} />
|
||||
{/* Legal pages */}
|
||||
<Route path="/datenschutz" element={<Datenschutz />} />
|
||||
<Route path="/agb" element={<AGB />} />
|
||||
<Route path="/sitemap" element={<Sitemap />} />
|
||||
<Route path="/impressum" element={<Impressum />} />
|
||||
<Route
|
||||
path="/batteriegesetzhinweise"
|
||||
element={<Batteriegesetzhinweise />}
|
||||
/>
|
||||
<Route path="/widerrufsrecht" element={<Widerrufsrecht />} />
|
||||
|
||||
{/* Fallback for undefined routes */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
{/* Grow Tent Configurator */}
|
||||
<Route path="/Konfigurator" element={<GrowTentKonfigurator socket={socket} socketB={socketB} />} />
|
||||
|
||||
{/* Separate pages that are truly different */}
|
||||
<Route path="/presseverleih" element={<PresseverleihPage />} />
|
||||
<Route path="/thc-test" element={<ThcTestPage />} />
|
||||
|
||||
{/* Fallback for undefined routes */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</CarouselProvider>
|
||||
</Suspense>
|
||||
</Box>
|
||||
{/* Conditionally render the Chat Assistant */}
|
||||
@@ -343,30 +366,37 @@ const App = () => {
|
||||
setDynamicTheme(createTheme(newTheme));
|
||||
};
|
||||
|
||||
// Make config globally available for language switching
|
||||
useEffect(() => {
|
||||
window.shopConfig = config;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={dynamicTheme}>
|
||||
<CssBaseline />
|
||||
<SocketProvider
|
||||
url={config.apiBaseUrl}
|
||||
fallback={
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100vh",
|
||||
}}
|
||||
>
|
||||
<CircularProgress color="primary" />
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<AppContent
|
||||
currentTheme={currentTheme}
|
||||
onThemeChange={handleThemeChange}
|
||||
/>
|
||||
</SocketProvider>
|
||||
</ThemeProvider>
|
||||
<LanguageProvider i18n={i18n}>
|
||||
<ThemeProvider theme={dynamicTheme}>
|
||||
<CssBaseline />
|
||||
<SocketProvider
|
||||
url={config.apiBaseUrl}
|
||||
fallback={
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: "100vh",
|
||||
}}
|
||||
>
|
||||
<CircularProgress color="primary" />
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
<AppContent
|
||||
currentTheme={currentTheme}
|
||||
onThemeChange={handleThemeChange}
|
||||
/>
|
||||
</SocketProvider>
|
||||
</ThemeProvider>
|
||||
</LanguageProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import { Box, AppBar, Toolbar, Container} from '@mui/material';
|
||||
import { Routes, Route } from 'react-router-dom';
|
||||
import Footer from './components/Footer.js';
|
||||
import { Logo, CategoryList } from './components/header/index.js';
|
||||
import Home from './pages/Home.js';
|
||||
import MainPageLayout from './components/MainPageLayout.js';
|
||||
import { CarouselProvider } from './contexts/CarouselContext.js';
|
||||
|
||||
const PrerenderAppContent = (socket) => (
|
||||
<Box
|
||||
@@ -44,9 +45,11 @@ const PrerenderAppContent = (socket) => (
|
||||
</AppBar>
|
||||
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
</Routes>
|
||||
<CarouselProvider>
|
||||
<Routes>
|
||||
<Route path="/" element={<MainPageLayout />} />
|
||||
</Routes>
|
||||
</CarouselProvider>
|
||||
</Box>
|
||||
|
||||
<Footer/>
|
||||
|
||||
@@ -7,7 +7,8 @@ const {
|
||||
} = require('@mui/material');
|
||||
const Footer = require('./components/Footer.js').default;
|
||||
const { Logo, CategoryList } = require('./components/header/index.js');
|
||||
const Home = require('./pages/Home.js').default;
|
||||
const MainPageLayout = require('./components/MainPageLayout.js').default;
|
||||
const { CarouselProvider } = require('./contexts/CarouselContext.js');
|
||||
|
||||
class PrerenderHome extends React.Component {
|
||||
render() {
|
||||
@@ -62,7 +63,7 @@ class PrerenderHome extends React.Component {
|
||||
React.createElement(
|
||||
Box,
|
||||
{ sx: { flexGrow: 1 } },
|
||||
React.createElement(Home)
|
||||
React.createElement(CarouselProvider, null, React.createElement(MainPageLayout))
|
||||
),
|
||||
React.createElement(Footer)
|
||||
);
|
||||
|
||||
92
src/PrerenderNotFound.js
Normal file
92
src/PrerenderNotFound.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const React = require('react');
|
||||
const {
|
||||
Box,
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Container
|
||||
} = require('@mui/material');
|
||||
const Footer = require('./components/Footer.js').default;
|
||||
const { Logo } = require('./components/header/index.js');
|
||||
const NotFound404 = require('./pages/NotFound404.js').default;
|
||||
|
||||
class PrerenderNotFound extends React.Component {
|
||||
render() {
|
||||
return React.createElement(
|
||||
Box,
|
||||
{
|
||||
sx: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: '100vh',
|
||||
mb: 0,
|
||||
pb: 0,
|
||||
bgcolor: 'background.default'
|
||||
}
|
||||
},
|
||||
React.createElement(
|
||||
AppBar,
|
||||
{ position: 'sticky', color: 'primary', elevation: 0, sx: { zIndex: 1100 } },
|
||||
React.createElement(
|
||||
Toolbar,
|
||||
{ sx: { minHeight: 64, py: { xs: 0.5, sm: 0 } } },
|
||||
React.createElement(
|
||||
Container,
|
||||
{
|
||||
maxWidth: 'lg',
|
||||
sx: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
px: { xs: 0, sm: 3 }
|
||||
}
|
||||
},
|
||||
React.createElement(
|
||||
Box,
|
||||
{ sx: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
flexDirection: { xs: 'column', sm: 'row' }
|
||||
}
|
||||
},
|
||||
React.createElement(
|
||||
Box,
|
||||
{ sx: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
justifyContent: { xs: 'space-between', sm: 'flex-start' },
|
||||
minHeight: { xs: 52, sm: 'auto' },
|
||||
px: { xs: 0, sm: 0 }
|
||||
}
|
||||
},
|
||||
React.createElement(Logo)
|
||||
),
|
||||
// Reserve space for SearchBar on mobile (invisible placeholder)
|
||||
React.createElement(
|
||||
Box,
|
||||
{ sx: {
|
||||
display: { xs: 'block', sm: 'none' },
|
||||
width: '100%',
|
||||
mt: { xs: 1, sm: 0 },
|
||||
mb: { xs: 0.5, sm: 0 },
|
||||
px: { xs: 0, sm: 0 },
|
||||
height: 40, // Reserve space for SearchBar
|
||||
opacity: 0 // Invisible placeholder
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
React.createElement(
|
||||
Box,
|
||||
{ sx: { flexGrow: 1 } },
|
||||
React.createElement(NotFound404)
|
||||
),
|
||||
React.createElement(Footer)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { default: PrerenderNotFound };
|
||||
@@ -10,6 +10,7 @@ import AddIcon from "@mui/icons-material/Add";
|
||||
import RemoveIcon from "@mui/icons-material/Remove";
|
||||
import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import { withI18n } from "../i18n/withTranslation.js";
|
||||
|
||||
if (!Array.isArray(window.cart)) window.cart = [];
|
||||
|
||||
@@ -51,11 +52,14 @@ class AddToCartButton extends Component {
|
||||
seoName: this.props.seoName,
|
||||
pictureList: this.props.pictureList,
|
||||
price: this.props.price,
|
||||
fGrundPreis: this.props.fGrundPreis,
|
||||
cGrundEinheit: this.props.cGrundEinheit,
|
||||
quantity: 1,
|
||||
weight: this.props.weight,
|
||||
vat: this.props.vat,
|
||||
versandklasse: this.props.versandklasse,
|
||||
availableSupplier: this.props.availableSupplier,
|
||||
komponenten: this.props.komponenten,
|
||||
available: this.props.available
|
||||
});
|
||||
} else {
|
||||
@@ -150,12 +154,17 @@ class AddToCartButton extends Component {
|
||||
},
|
||||
}}
|
||||
>
|
||||
Ab{" "}
|
||||
{new Date(incoming).toLocaleDateString("de-DE", {
|
||||
{this.props.t ? this.props.t('cart.availableFrom', {
|
||||
date: new Date(incoming).toLocaleDateString("de-DE", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})
|
||||
}) : `Ab ${new Date(incoming).toLocaleDateString("de-DE", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
})}
|
||||
})}`}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -181,7 +190,9 @@ class AddToCartButton extends Component {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{this.props.steckling ? "Als Steckling vorbestellen" : "In den Korb"}
|
||||
{this.props.steckling ?
|
||||
(this.props.t ? this.props.t('cart.preorderCutting') : "Als Steckling vorbestellen") :
|
||||
(this.props.t ? this.props.t('cart.addToCart') : "In den Korb")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -259,7 +270,7 @@ class AddToCartButton extends Component {
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
|
||||
<Tooltip title="Aus dem Warenkorb entfernen" arrow>
|
||||
<Tooltip title={this.props.t ? this.props.t('cart.removeFromCart') : 'Aus dem Warenkorb entfernen'} arrow>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={this.handleClearCart}
|
||||
@@ -272,7 +283,7 @@ class AddToCartButton extends Component {
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{this.props.cartButton && (
|
||||
<Tooltip title="Warenkorb öffnen" arrow>
|
||||
<Tooltip title={this.props.t ? this.props.t('cart.openCart') : 'Warenkorb öffnen'} arrow>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={this.toggleCart}
|
||||
@@ -302,7 +313,7 @@ class AddToCartButton extends Component {
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Out of Stock
|
||||
{this.props.t ? this.props.t('product.outOfStock') : 'Out of Stock'}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -327,7 +338,9 @@ class AddToCartButton extends Component {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{this.props.steckling ? "Als Steckling vorbestellen" : "In den Korb"}
|
||||
{this.props.steckling ?
|
||||
(this.props.t ? this.props.t('cart.preorderCutting') : "Als Steckling vorbestellen") :
|
||||
(this.props.t ? this.props.t('cart.addToCart') : "In den Korb")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -404,7 +417,7 @@ class AddToCartButton extends Component {
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
|
||||
<Tooltip title="Aus dem Warenkorb entfernen" arrow>
|
||||
<Tooltip title={this.props.t ? this.props.t('cart.removeFromCart') : 'Aus dem Warenkorb entfernen'} arrow>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={this.handleClearCart}
|
||||
@@ -417,7 +430,7 @@ class AddToCartButton extends Component {
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{this.props.cartButton && (
|
||||
<Tooltip title="Warenkorb öffnen" arrow>
|
||||
<Tooltip title={this.props.t ? this.props.t('cart.openCart') : 'Warenkorb öffnen'} arrow>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
onClick={this.toggleCart}
|
||||
@@ -436,4 +449,4 @@ class AddToCartButton extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default AddToCartButton;
|
||||
export default withI18n()(AddToCartButton);
|
||||
|
||||
@@ -8,6 +8,7 @@ import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import CartItem from './CartItem.js';
|
||||
import { withI18n } from '../i18n/withTranslation.js';
|
||||
|
||||
|
||||
class CartDropdown extends Component {
|
||||
@@ -53,8 +54,8 @@ class CartDropdown extends Component {
|
||||
currency: 'EUR'
|
||||
});
|
||||
|
||||
const shippingNetPrice = deliveryCost / (1 + 19 / 100);
|
||||
const shippingVat = deliveryCost - shippingNetPrice;
|
||||
const shippingNetPrice = deliveryCost > 0 ? deliveryCost / (1 + 19 / 100) : 0;
|
||||
const shippingVat = deliveryCost > 0 ? deliveryCost - shippingNetPrice : 0;
|
||||
const totalVat7 = priceCalculations.vat7;
|
||||
const totalVat19 = priceCalculations.vat19 + shippingVat;
|
||||
const totalGross = priceCalculations.totalGross + deliveryCost;
|
||||
@@ -119,7 +120,7 @@ class CartDropdown extends Component {
|
||||
)}
|
||||
{totalVat7 > 0 && (
|
||||
<TableRow>
|
||||
<TableCell>7% Mehrwertsteuer:</TableCell>
|
||||
<TableCell>{this.props.t ? this.props.t('tax.vat7') : '7% Mehrwertsteuer'}:</TableCell>
|
||||
<TableCell align="right">
|
||||
{currencyFormatter.format(totalVat7)}
|
||||
</TableCell>
|
||||
@@ -127,7 +128,7 @@ class CartDropdown extends Component {
|
||||
)}
|
||||
{totalVat19 > 0 && (
|
||||
<TableRow>
|
||||
<TableCell>19% Mehrwertsteuer (inkl. Versand):</TableCell>
|
||||
<TableCell>{this.props.t ? this.props.t('tax.vat19WithShipping') : '19% Mehrwertsteuer (inkl. Versand)'}:</TableCell>
|
||||
<TableCell align="right">
|
||||
{currencyFormatter.format(totalVat19)}
|
||||
</TableCell>
|
||||
@@ -139,14 +140,23 @@ class CartDropdown extends Component {
|
||||
{currencyFormatter.format(priceCalculations.totalGross)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{deliveryCost > 0 && (
|
||||
<TableRow>
|
||||
<TableCell sx={{ fontWeight: 'bold' }}>Versandkosten:</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>
|
||||
{currencyFormatter.format(deliveryCost)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow>
|
||||
<TableCell sx={{ fontWeight: 'bold' }}>
|
||||
Versandkosten:
|
||||
{deliveryCost === 0 && priceCalculations.totalGross < 100 && (
|
||||
<span style={{ color: '#2e7d32', fontSize: '0.8em', marginLeft: '4px' }}>
|
||||
(kostenlos ab 100€)
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>
|
||||
{deliveryCost === 0 ? (
|
||||
<span style={{ color: '#2e7d32' }}>kostenlos</span>
|
||||
) : (
|
||||
currencyFormatter.format(deliveryCost)
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow sx={{ borderTop: '1px solid #e0e0e0' }}>
|
||||
<TableCell sx={{ fontWeight: 'bold', pt: 2 }}>Gesamtsumme:</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold', pt: 2 }}>
|
||||
@@ -161,14 +171,14 @@ class CartDropdown extends Component {
|
||||
<Table size="small">
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>Gesamtnettopreis:</TableCell>
|
||||
<TableCell>{this.props.t ? this.props.t('tax.totalNet') : 'Gesamtnettopreis'}:</TableCell>
|
||||
<TableCell align="right">
|
||||
{new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(priceCalculations.totalNet)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{priceCalculations.vat7 > 0 && (
|
||||
<TableRow>
|
||||
<TableCell>7% Mehrwertsteuer:</TableCell>
|
||||
<TableCell>{this.props.t ? this.props.t('tax.vat7') : '7% Mehrwertsteuer'}:</TableCell>
|
||||
<TableCell align="right">
|
||||
{new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(priceCalculations.vat7)}
|
||||
</TableCell>
|
||||
@@ -176,14 +186,14 @@ class CartDropdown extends Component {
|
||||
)}
|
||||
{priceCalculations.vat19 > 0 && (
|
||||
<TableRow>
|
||||
<TableCell>19% Mehrwertsteuer:</TableCell>
|
||||
<TableCell>{this.props.t ? this.props.t('tax.vat19') : '19% Mehrwertsteuer'}:</TableCell>
|
||||
<TableCell align="right">
|
||||
{new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(priceCalculations.vat19)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow>
|
||||
<TableCell sx={{ fontWeight: 'bold' }}>Gesamtbruttopreis ohne Versand:</TableCell>
|
||||
<TableCell sx={{ fontWeight: 'bold' }}>{this.props.t ? this.props.t('tax.totalGross') : 'Gesamtbruttopreis ohne Versand'}:</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>
|
||||
{new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(priceCalculations.totalGross)}
|
||||
</TableCell>
|
||||
@@ -201,7 +211,7 @@ class CartDropdown extends Component {
|
||||
fullWidth
|
||||
onClick={onClose}
|
||||
>
|
||||
Weiter einkaufen
|
||||
{this.props.t ? this.props.t('cart.continueShopping') : 'Weiter einkaufen'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -213,7 +223,7 @@ class CartDropdown extends Component {
|
||||
sx={{ mt: 2 }}
|
||||
onClick={onCheckout}
|
||||
>
|
||||
Weiter zur Kasse
|
||||
{this.props.t ? this.props.t('cart.proceedToCheckout') : 'Weiter zur Kasse'}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
@@ -223,4 +233,4 @@ class CartDropdown extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default CartDropdown;
|
||||
export default withI18n()(CartDropdown);
|
||||
@@ -6,6 +6,7 @@ import Typography from '@mui/material/Typography';
|
||||
import Box from '@mui/material/Box';
|
||||
import { Link } from 'react-router-dom';
|
||||
import AddToCartButton from './AddToCartButton.js';
|
||||
import { withI18n } from '../i18n/withTranslation.js';
|
||||
|
||||
class CartItem extends Component {
|
||||
|
||||
@@ -126,9 +127,9 @@ class CartItem extends Component {
|
||||
fontStyle="italic"
|
||||
component="div"
|
||||
>
|
||||
inkl. {new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(
|
||||
{this.props.t ? this.props.t('product.inclShort') : 'inkl.'} {new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(
|
||||
(item.price * item.quantity) - ((item.price * item.quantity) / (1 + item.vat / 100))
|
||||
)} MwSt. ({item.vat}%)
|
||||
)} {this.props.t ? this.props.t('product.vatShort') : 'MwSt.'} ({item.vat}%)
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
@@ -146,11 +147,14 @@ class CartItem extends Component {
|
||||
display: "block"
|
||||
}}
|
||||
>
|
||||
{this.props.id.toString().endsWith("steckling") ? "Lieferzeit: 14 Tage" :
|
||||
item.available == 1 ? "Lieferzeit: 2-3 Tage" :
|
||||
item.availableSupplier == 1 ? "Lieferzeit: 7-9 Tage" : ""}
|
||||
{this.props.id.toString().endsWith("steckling") ?
|
||||
(this.props.t ? this.props.t('delivery.times.cutting14Days') : "Lieferzeit: 14 Tage") :
|
||||
item.available == 1 ?
|
||||
(this.props.t ? this.props.t('delivery.times.standard2to3Days') : "Lieferzeit: 2-3 Tage") :
|
||||
item.availableSupplier == 1 ?
|
||||
(this.props.t ? this.props.t('delivery.times.supplier7to9Days') : "Lieferzeit: 7-9 Tage") : ""}
|
||||
</Typography>
|
||||
<AddToCartButton available={1} id={this.props.id} availableSupplier={item.availableSupplier} price={item.price} seoName={item.seoName} name={item.name} weight={item.weight} vat={item.vat} versandklasse={item.versandklasse}/>
|
||||
<AddToCartButton available={1} id={this.props.id} komponenten={item.komponenten} availableSupplier={item.availableSupplier} price={item.price} seoName={item.seoName} name={item.name} weight={item.weight} vat={item.vat} versandklasse={item.versandklasse}/>
|
||||
</Box>
|
||||
</Box>
|
||||
</ListItem>
|
||||
@@ -159,4 +163,4 @@ class CartItem extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default CartItem;
|
||||
export default withI18n()(CartItem);
|
||||
@@ -16,7 +16,7 @@ const CategoryBox = ({
|
||||
name,
|
||||
seoName,
|
||||
bgcolor,
|
||||
fontSize = '0.8rem',
|
||||
fontSize = '1.2rem',
|
||||
...props
|
||||
}) => {
|
||||
const [imageUrl, setImageUrl] = useState(null);
|
||||
@@ -186,7 +186,7 @@ const CategoryBox = ({
|
||||
fontFamily: 'SwashingtonCP, "Times New Roman", Georgia, serif',
|
||||
fontWeight: 'normal',
|
||||
lineHeight: '1.2',
|
||||
padding: '0 8px'
|
||||
padding: '12px 8px'
|
||||
}}>
|
||||
{name}
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@ import CategoryBox from './CategoryBox.js';
|
||||
|
||||
import { useParams, useSearchParams } from 'react-router-dom';
|
||||
import { getAllSettingsWithPrefix } from '../utils/sessionStorage.js';
|
||||
import { withI18n } from '../i18n/withTranslation.js';
|
||||
|
||||
const isNew = (neu) => neu && (new Date().getTime() - new Date(neu).getTime() < 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
@@ -52,7 +53,7 @@ function getCachedCategoryData(categoryId) {
|
||||
|
||||
|
||||
|
||||
function getFilteredProducts(unfilteredProducts, attributes) {
|
||||
function getFilteredProducts(unfilteredProducts, attributes, t) {
|
||||
const attributeSettings = getAllSettingsWithPrefix('filter_attribute_');
|
||||
const manufacturerSettings = getAllSettingsWithPrefix('filter_manufacturer_');
|
||||
const availabilitySettings = getAllSettingsWithPrefix('filter_availability_');
|
||||
@@ -149,17 +150,17 @@ function getFilteredProducts(unfilteredProducts, attributes) {
|
||||
|
||||
// Check for "auf Lager" filter (in stock) - it's active when filter_availability is NOT set to '1'
|
||||
if (availabilityFilter !== '1') {
|
||||
activeAvailabilityFilters.push({id: '1', name: 'auf Lager'});
|
||||
activeAvailabilityFilters.push({id: '1', name: t ? t('product.inStock') : 'auf Lager'});
|
||||
}
|
||||
|
||||
// Check for "Neu" filter (new) - only show if there are actually new products and filter is active
|
||||
if (availabilityFilters.includes('2') && hasNewProducts) {
|
||||
activeAvailabilityFilters.push({id: '2', name: 'Neu'});
|
||||
activeAvailabilityFilters.push({id: '2', name: t ? t('product.new') : 'Neu'});
|
||||
}
|
||||
|
||||
// Check for "Bald verfügbar" filter (coming soon) - only show if there are actually coming soon products and filter is active
|
||||
if (availabilityFilters.includes('3') && hasComingSoonProducts) {
|
||||
activeAvailabilityFilters.push({id: '3', name: 'Bald verfügbar'});
|
||||
activeAvailabilityFilters.push({id: '3', name: t ? t('product.comingSoon') : 'Bald verfügbar'});
|
||||
}
|
||||
|
||||
return {filteredProducts,activeAttributeFilters:activeAttributeFiltersWithNames,activeManufacturerFilters:activeManufacturerFiltersWithNames,activeAvailabilityFilters};
|
||||
@@ -256,7 +257,8 @@ class Content extends Component {
|
||||
unfilteredProducts: unfilteredProducts,
|
||||
...getFilteredProducts(
|
||||
unfilteredProducts,
|
||||
response.attributes
|
||||
response.attributes,
|
||||
this.props.t
|
||||
),
|
||||
categoryName: response.categoryName || response.name || null,
|
||||
dataType: response.dataType,
|
||||
@@ -385,7 +387,8 @@ class Content extends Component {
|
||||
this.setState({
|
||||
...getFilteredProducts(
|
||||
this.state.unfilteredProducts,
|
||||
this.state.attributes
|
||||
this.state.attributes,
|
||||
this.props.t
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -602,7 +605,7 @@ class Content extends Component {
|
||||
{(this.getCurrentCategoryId() == 706 || this.getCurrentCategoryId() == 689) &&
|
||||
<Box sx={{ display: { xs: 'none', sm: 'block' } }}>
|
||||
<Typography variant="h6" sx={{mt:3}}>
|
||||
Andere Kategorien
|
||||
{this.props.t ? this.props.t('navigation.otherCategories') : 'Andere Kategorien'}
|
||||
</Typography>
|
||||
</Box>
|
||||
}
|
||||
@@ -647,7 +650,7 @@ class Content extends Component {
|
||||
p: 2,
|
||||
}}>
|
||||
<Typography sx={{ fontSize: '1.3rem', color: 'white', fontFamily: 'SwashingtonCP' }}>
|
||||
Seeds
|
||||
{this.props.t('sections.seeds')}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -694,7 +697,7 @@ class Content extends Component {
|
||||
p: 2,
|
||||
}}>
|
||||
<Typography sx={{ fontSize: '1.3rem', color: 'white', fontFamily: 'SwashingtonCP' }}>
|
||||
Stecklinge
|
||||
{this.props.t('sections.stecklinge')}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -723,4 +726,4 @@ class Content extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Content);
|
||||
export default withRouter(withI18n()(Content));
|
||||
@@ -6,6 +6,7 @@ import Link from '@mui/material/Link';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import { withI18n } from '../i18n/withTranslation.js';
|
||||
|
||||
// Styled component for the router links
|
||||
const StyledRouterLink = styled(RouterLink)(() => ({
|
||||
@@ -229,9 +230,9 @@ class Footer extends Component {
|
||||
alignItems={{ xs: 'center', md: 'left' }}
|
||||
flexWrap="wrap"
|
||||
>
|
||||
<StyledRouterLink to="/datenschutz">Datenschutz</StyledRouterLink>
|
||||
<StyledRouterLink to="/agb">AGB</StyledRouterLink>
|
||||
<StyledRouterLink to="/sitemap">Sitemap</StyledRouterLink>
|
||||
<StyledRouterLink to="/datenschutz">{this.props.t ? this.props.t('footer.legal.datenschutz') : 'Datenschutz'}</StyledRouterLink>
|
||||
<StyledRouterLink to="/agb">{this.props.t ? this.props.t('footer.legal.agb') : 'AGB'}</StyledRouterLink>
|
||||
<StyledRouterLink to="/sitemap">{this.props.t ? this.props.t('footer.legal.sitemap') : 'Sitemap'}</StyledRouterLink>
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
@@ -241,9 +242,9 @@ class Footer extends Component {
|
||||
alignItems={{ xs: 'center', md: 'left' }}
|
||||
flexWrap="wrap"
|
||||
>
|
||||
<StyledRouterLink to="/impressum">Impressum</StyledRouterLink>
|
||||
<StyledRouterLink to="/batteriegesetzhinweise">Batteriegesetzhinweise</StyledRouterLink>
|
||||
<StyledRouterLink to="/widerrufsrecht">Widerrufsrecht</StyledRouterLink>
|
||||
<StyledRouterLink to="/impressum">{this.props.t ? this.props.t('footer.legal.impressum') : 'Impressum'}</StyledRouterLink>
|
||||
<StyledRouterLink to="/batteriegesetzhinweise">{this.props.t ? this.props.t('footer.legal.batteriegesetzhinweise') : 'Batteriegesetzhinweise'}</StyledRouterLink>
|
||||
<StyledRouterLink to="/widerrufsrecht">{this.props.t ? this.props.t('footer.legal.widerrufsrecht') : 'Widerrufsrecht'}</StyledRouterLink>
|
||||
</Stack>
|
||||
|
||||
{/* Payment Methods Section */}
|
||||
@@ -338,7 +339,7 @@ class Footer extends Component {
|
||||
{/* Copyright Section */}
|
||||
<Box sx={{ pb:'20px',textAlign: 'center', filter: 'drop-shadow(0 4px 8px rgba(0, 0, 0, 0.3))', opacity: 0.7 }}>
|
||||
<Typography variant="body2" sx={{ mb: 1, fontSize: { xs: '11px', md: '14px' }, lineHeight: 1.5 }}>
|
||||
* Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
{this.props.t ? this.props.t('footer.allPricesIncl') : '* Alle Preise inkl. gesetzlicher USt., zzgl. Versand'}
|
||||
</Typography>
|
||||
<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>
|
||||
@@ -351,4 +352,4 @@ class Footer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
export default withI18n()(Footer);
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import GoogleIcon from '@mui/icons-material/Google';
|
||||
import GoogleAuthContext from '../contexts/GoogleAuthContext.js';
|
||||
import { withI18n } from '../i18n/index.js';
|
||||
|
||||
class GoogleLoginButton extends Component {
|
||||
static contextType = GoogleAuthContext;
|
||||
@@ -186,7 +187,7 @@ class GoogleLoginButton extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { disabled, style, className, text = 'Mit Google anmelden' } = this.props;
|
||||
const { disabled, style, className, text = (this.props.t ? this.props.t('auth.loginWithGoogle') : 'Mit Google anmelden') } = this.props;
|
||||
const { isInitializing, isPrompting } = this.state;
|
||||
const isLoading = isInitializing || isPrompting || (this.context && !this.context.isLoaded);
|
||||
|
||||
@@ -205,4 +206,4 @@ class GoogleLoginButton extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default GoogleLoginButton;
|
||||
export default withI18n(GoogleLoginButton);
|
||||
@@ -38,7 +38,7 @@ class Header extends Component {
|
||||
render() {
|
||||
// Get socket directly from context in render method
|
||||
const {socket,socketB} = this.context;
|
||||
const { isHomePage, isProfilePage } = this.props;
|
||||
const { isHomePage, isProfilePage, isAktionenPage, isFilialePage } = this.props;
|
||||
|
||||
return (
|
||||
<AppBar position="sticky" color="primary" elevation={0} sx={{ zIndex: 1100 }}>
|
||||
@@ -94,7 +94,7 @@ class Header extends Component {
|
||||
</Box>
|
||||
</Container>
|
||||
</Toolbar>
|
||||
{(isHomePage || this.props.categoryId || isProfilePage) && <CategoryList categoryId={209} activeCategoryId={this.props.categoryId} socket={socket} socketB={socketB} />}
|
||||
{(isHomePage || this.props.categoryId || isProfilePage || isAktionenPage || isFilialePage) && <CategoryList categoryId={209} activeCategoryId={this.props.categoryId} socket={socket} socketB={socketB} />}
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
@@ -105,10 +105,12 @@ const HeaderWithContext = (props) => {
|
||||
const location = useLocation();
|
||||
const isHomePage = location.pathname === '/';
|
||||
const isProfilePage = location.pathname === '/profile';
|
||||
const isAktionenPage = location.pathname === '/aktionen';
|
||||
const isFilialePage = location.pathname === '/filiale';
|
||||
|
||||
return (
|
||||
<SocketContext.Consumer>
|
||||
{({socket,socketB}) => <Header {...props} socket={socket} socketB={socketB} isHomePage={isHomePage} isProfilePage={isProfilePage} />}
|
||||
{({socket,socketB}) => <Header {...props} socket={socket} socketB={socketB} isHomePage={isHomePage} isProfilePage={isProfilePage} isAktionenPage={isAktionenPage} isFilialePage={isFilialePage} />}
|
||||
</SocketContext.Consumer>
|
||||
);
|
||||
};
|
||||
|
||||
269
src/components/LanguageSwitcher.js
Normal file
269
src/components/LanguageSwitcher.js
Normal file
@@ -0,0 +1,269 @@
|
||||
import React, { Component } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import Menu from '@mui/material/Menu';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { withI18n } from '../i18n/withTranslation.js';
|
||||
|
||||
class LanguageSwitcher extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
anchorEl: null,
|
||||
loadedFlags: {}
|
||||
};
|
||||
}
|
||||
|
||||
handleClick = (event) => {
|
||||
this.setState({ anchorEl: event.currentTarget });
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ anchorEl: null });
|
||||
};
|
||||
|
||||
handleLanguageChange = (language) => {
|
||||
const { languageContext } = this.props;
|
||||
if (languageContext) {
|
||||
languageContext.changeLanguage(language);
|
||||
}
|
||||
this.handleClose();
|
||||
};
|
||||
|
||||
// Lazy load flag components
|
||||
loadFlagComponent = async (lang) => {
|
||||
if (this.state.loadedFlags[lang]) {
|
||||
return this.state.loadedFlags[lang];
|
||||
}
|
||||
|
||||
try {
|
||||
const flagMap = {
|
||||
'ar': () => import('country-flag-icons/react/3x2').then(m => m.EG),
|
||||
'bg': () => import('country-flag-icons/react/3x2').then(m => m.BG),
|
||||
'cs': () => import('country-flag-icons/react/3x2').then(m => m.CZ),
|
||||
'de': () => import('country-flag-icons/react/3x2').then(m => m.DE),
|
||||
'el': () => import('country-flag-icons/react/3x2').then(m => m.GR),
|
||||
'en': () => import('country-flag-icons/react/3x2').then(m => m.US),
|
||||
'es': () => import('country-flag-icons/react/3x2').then(m => m.ES),
|
||||
'fr': () => import('country-flag-icons/react/3x2').then(m => m.FR),
|
||||
'hr': () => import('country-flag-icons/react/3x2').then(m => m.HR),
|
||||
'hu': () => import('country-flag-icons/react/3x2').then(m => m.HU),
|
||||
'it': () => import('country-flag-icons/react/3x2').then(m => m.IT),
|
||||
'pl': () => import('country-flag-icons/react/3x2').then(m => m.PL),
|
||||
'ro': () => import('country-flag-icons/react/3x2').then(m => m.RO),
|
||||
'ru': () => import('country-flag-icons/react/3x2').then(m => m.RU),
|
||||
'sk': () => import('country-flag-icons/react/3x2').then(m => m.SK),
|
||||
'sl': () => import('country-flag-icons/react/3x2').then(m => m.SI),
|
||||
'sr': () => import('country-flag-icons/react/3x2').then(m => m.RS),
|
||||
'sv': () => import('country-flag-icons/react/3x2').then(m => m.SE),
|
||||
'tr': () => import('country-flag-icons/react/3x2').then(m => m.TR),
|
||||
'uk': () => import('country-flag-icons/react/3x2').then(m => m.UA),
|
||||
'zh': () => import('country-flag-icons/react/3x2').then(m => m.CN)
|
||||
};
|
||||
|
||||
const flagLoader = flagMap[lang];
|
||||
if (flagLoader) {
|
||||
const FlagComponent = await flagLoader();
|
||||
this.setState(prevState => ({
|
||||
loadedFlags: {
|
||||
...prevState.loadedFlags,
|
||||
[lang]: FlagComponent
|
||||
}
|
||||
}));
|
||||
return FlagComponent;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load flag for language: ${lang}`, error);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
getLanguageFlag = (lang) => {
|
||||
const FlagComponent = this.state.loadedFlags[lang];
|
||||
|
||||
if (FlagComponent) {
|
||||
return (
|
||||
<FlagComponent
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '14px',
|
||||
borderRadius: '2px',
|
||||
border: '1px solid #ddd'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Loading placeholder or fallback
|
||||
return (
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minWidth: '20px',
|
||||
height: '14px',
|
||||
backgroundColor: '#f5f5f5',
|
||||
color: '#666',
|
||||
fontSize: '8px',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '2px',
|
||||
fontFamily: 'monospace',
|
||||
border: '1px solid #ddd'
|
||||
}}
|
||||
>
|
||||
{this.getLanguageLabel(lang)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
// Load flags when menu opens
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { anchorEl } = this.state;
|
||||
const { languageContext } = this.props;
|
||||
|
||||
if (anchorEl && !prevState.anchorEl && languageContext) {
|
||||
// Menu just opened, lazy load all flags
|
||||
languageContext.availableLanguages.forEach(lang => {
|
||||
if (!this.state.loadedFlags[lang]) {
|
||||
this.loadFlagComponent(lang);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getLanguageLabel = (lang) => {
|
||||
const labels = {
|
||||
'ar': 'EG',
|
||||
'bg': 'BG',
|
||||
'cs': 'CZ',
|
||||
'de': 'DE',
|
||||
'el': 'GR',
|
||||
'en': 'US',
|
||||
'es': 'ES',
|
||||
'fr': 'FR',
|
||||
'hr': 'HR',
|
||||
'hu': 'HU',
|
||||
'it': 'IT',
|
||||
'pl': 'PL',
|
||||
'ro': 'RO',
|
||||
'ru': 'RU',
|
||||
'sk': 'SK',
|
||||
'sl': 'SI',
|
||||
'sr': 'RS',
|
||||
'sv': 'SE',
|
||||
'tr': 'TR',
|
||||
'uk': 'UA',
|
||||
'zh': 'CN'
|
||||
};
|
||||
return labels[lang] || lang.toUpperCase();
|
||||
};
|
||||
|
||||
getLanguageName = (lang) => {
|
||||
const names = {
|
||||
'ar': 'العربية',
|
||||
'bg': 'Български',
|
||||
'cs': 'Čeština',
|
||||
'de': 'Deutsch',
|
||||
'el': 'Ελληνικά',
|
||||
'en': 'English',
|
||||
'es': 'Español',
|
||||
'fr': 'Français',
|
||||
'hr': 'Hrvatski',
|
||||
'hu': 'Magyar',
|
||||
'it': 'Italiano',
|
||||
'pl': 'Polski',
|
||||
'ro': 'Română',
|
||||
'ru': 'Русский',
|
||||
'sk': 'Slovenčina',
|
||||
'sl': 'Slovenščina',
|
||||
'sr': 'Српски',
|
||||
'sv': 'Svenska',
|
||||
'tr': 'Türkçe',
|
||||
'uk': 'Українська',
|
||||
'zh': '中文'
|
||||
};
|
||||
return names[lang] || lang;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { languageContext } = this.props;
|
||||
const { anchorEl } = this.state;
|
||||
|
||||
if (!languageContext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { currentLanguage, availableLanguages } = languageContext;
|
||||
const open = Boolean(anchorEl);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Button
|
||||
aria-controls={open ? 'language-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
onClick={this.handleClick}
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{
|
||||
my: 1,
|
||||
mx: 0.5,
|
||||
minWidth: 'auto',
|
||||
textTransform: 'none',
|
||||
fontSize: '0.875rem'
|
||||
}}
|
||||
>
|
||||
{this.getLanguageLabel(currentLanguage)}
|
||||
</Button>
|
||||
<Menu
|
||||
id="language-menu"
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={this.handleClose}
|
||||
disableScrollLock={true}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'language-button',
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
{availableLanguages.map((language) => (
|
||||
<MenuItem
|
||||
key={language}
|
||||
onClick={() => this.handleLanguageChange(language)}
|
||||
selected={language === currentLanguage}
|
||||
sx={{
|
||||
minWidth: 160,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: 2
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
{this.getLanguageFlag(language)}
|
||||
<Typography variant="body2">
|
||||
{this.getLanguageName(language)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ ml: 'auto' }}>
|
||||
{this.getLanguageLabel(language)}
|
||||
</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withI18n()(LanguageSwitcher);
|
||||
@@ -22,6 +22,7 @@ import GoogleLoginButton from './GoogleLoginButton.js';
|
||||
import CartSyncDialog from './CartSyncDialog.js';
|
||||
import { localAndArchiveServer, mergeCarts } from '../utils/cartUtils.js';
|
||||
import config from '../config.js';
|
||||
import { withI18n } from '../i18n/withTranslation.js';
|
||||
|
||||
// Lazy load GoogleAuthProvider
|
||||
const GoogleAuthProvider = lazy(() => import('../providers/GoogleAuthProvider.js'));
|
||||
@@ -510,7 +511,7 @@ export class LoginComponent extends Component {
|
||||
color={isAdmin ? 'secondary' : 'inherit'}
|
||||
sx={{ my: 1, mx: 1.5 }}
|
||||
>
|
||||
Profil
|
||||
{this.props.t ? this.props.t('auth.profile') : 'Profil'}
|
||||
</Button>
|
||||
<Menu
|
||||
disableScrollLock={true}
|
||||
@@ -526,14 +527,28 @@ export class LoginComponent extends Component {
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
<MenuItem component={Link} to="/profile" onClick={this.handleUserMenuClose}>Profil</MenuItem>
|
||||
<MenuItem component={Link} to="/profile#cart" onClick={this.handleUserMenuClose} sx={{ pl: 4 }}>Bestellabschluss</MenuItem>
|
||||
<MenuItem component={Link} to="/profile#orders" onClick={this.handleUserMenuClose} sx={{ pl: 4 }}>Bestellungen</MenuItem>
|
||||
<MenuItem component={Link} to="/profile#settings" onClick={this.handleUserMenuClose} sx={{ pl: 4 }}>Einstellungen</MenuItem>
|
||||
<MenuItem component={Link} to="/profile" onClick={this.handleUserMenuClose}>
|
||||
{this.props.t ? this.props.t('auth.menu.profile') : 'Profil'}
|
||||
</MenuItem>
|
||||
<MenuItem component={Link} to="/profile#cart" onClick={this.handleUserMenuClose} sx={{ pl: 4 }}>
|
||||
{this.props.t ? this.props.t('auth.menu.checkout') : 'Bestellabschluss'}
|
||||
</MenuItem>
|
||||
<MenuItem component={Link} to="/profile#orders" onClick={this.handleUserMenuClose} sx={{ pl: 4 }}>
|
||||
{this.props.t ? this.props.t('auth.menu.orders') : 'Bestellungen'}
|
||||
</MenuItem>
|
||||
<MenuItem component={Link} to="/profile#settings" onClick={this.handleUserMenuClose} sx={{ pl: 4 }}>
|
||||
{this.props.t ? this.props.t('auth.menu.settings') : 'Einstellungen'}
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
{isAdmin ? <MenuItem component={Link} to="/admin" onClick={this.handleUserMenuClose}>Admin Dashboard</MenuItem> : null}
|
||||
{isAdmin ? <MenuItem component={Link} to="/admin/users" onClick={this.handleUserMenuClose}>Admin Users</MenuItem> : null}
|
||||
<MenuItem onClick={this.handleLogout}>Abmelden</MenuItem>
|
||||
{isAdmin ? <MenuItem component={Link} to="/admin" onClick={this.handleUserMenuClose}>
|
||||
{this.props.t ? this.props.t('auth.menu.adminDashboard') : 'Admin Dashboard'}
|
||||
</MenuItem> : null}
|
||||
{isAdmin ? <MenuItem component={Link} to="/admin/users" onClick={this.handleUserMenuClose}>
|
||||
{this.props.t ? this.props.t('auth.menu.adminUsers') : 'Admin Users'}
|
||||
</MenuItem> : null}
|
||||
<MenuItem onClick={this.handleLogout}>
|
||||
{this.props.t ? this.props.t('auth.logout') : 'Abmelden'}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</>
|
||||
) : (
|
||||
@@ -543,7 +558,7 @@ export class LoginComponent extends Component {
|
||||
onClick={this.handleOpen}
|
||||
sx={{ my: 1, mx: 1.5 }}
|
||||
>
|
||||
Login
|
||||
{this.props.t ? this.props.t('auth.login') : 'Login'}
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
@@ -558,7 +573,10 @@ export class LoginComponent extends Component {
|
||||
<DialogTitle sx={{ bgcolor: 'white', pb: 0 }}>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h6" color="#2e7d32" fontWeight="bold">
|
||||
{tabValue === 0 ? 'Anmelden' : 'Registrieren'}
|
||||
{tabValue === 0 ?
|
||||
(this.props.t ? this.props.t('auth.login') : 'Anmelden') :
|
||||
(this.props.t ? this.props.t('auth.register') : 'Registrieren')
|
||||
}
|
||||
</Typography>
|
||||
<IconButton edge="end" onClick={this.handleClose} aria-label="close">
|
||||
<CloseIcon />
|
||||
@@ -578,14 +596,14 @@ export class LoginComponent extends Component {
|
||||
textColor="inherit"
|
||||
>
|
||||
<Tab
|
||||
label="ANMELDEN"
|
||||
label={this.props.t ? this.props.t('auth.login').toUpperCase() : "ANMELDEN"}
|
||||
sx={{
|
||||
color: tabValue === 0 ? '#2e7d32' : 'inherit',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="REGISTRIEREN"
|
||||
label={this.props.t ? this.props.t('auth.register').toUpperCase() : "REGISTRIEREN"}
|
||||
sx={{
|
||||
color: tabValue === 1 ? '#2e7d32' : 'inherit',
|
||||
fontWeight: 'bold'
|
||||
@@ -598,7 +616,14 @@ export class LoginComponent extends Component {
|
||||
<Box sx={{ width: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', mb: 2 }}>
|
||||
{!privacyConfirmed && (
|
||||
<Typography variant="caption" sx={{ mb: 1, textAlign: 'center' }}>
|
||||
Mit dem Click auf "Mit Google anmelden" akzeptiere ich die <Link to="/datenschutz" style={{ color: '#4285F4' }}>Datenschutzbestimmungen</Link>
|
||||
{this.props.t ?
|
||||
<>
|
||||
{this.props.t('auth.privacyAccept')} <Link to="/datenschutz" style={{ color: '#4285F4' }}>{this.props.t('auth.privacyPolicy')}</Link>
|
||||
</> :
|
||||
<>
|
||||
Mit dem Click auf "Mit Google anmelden" akzeptiere ich die <Link to="/datenschutz" style={{ color: '#4285F4' }}>Datenschutzbestimmungen</Link>
|
||||
</>
|
||||
}
|
||||
</Typography>
|
||||
)}
|
||||
{!showGoogleAuth && (
|
||||
@@ -611,7 +636,7 @@ export class LoginComponent extends Component {
|
||||
}}
|
||||
sx={{ width: '100%', backgroundColor: '#4285F4', color: 'white' }}
|
||||
>
|
||||
Mit Google anmelden
|
||||
{this.props.t ? this.props.t('auth.loginWithGoogle') : 'Mit Google anmelden'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -643,7 +668,9 @@ export class LoginComponent extends Component {
|
||||
{/* OR Divider */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', my: 2 }}>
|
||||
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#e0e0e0' }} />
|
||||
<Typography variant="body2" sx={{ px: 2, color: '#757575' }}>ODER</Typography>
|
||||
<Typography variant="body2" sx={{ px: 2, color: '#757575' }}>
|
||||
{this.props.t ? this.props.t('auth.or') : 'ODER'}
|
||||
</Typography>
|
||||
<Box sx={{ flex: 1, height: '1px', backgroundColor: '#e0e0e0' }} />
|
||||
</Box>
|
||||
|
||||
@@ -654,7 +681,7 @@ export class LoginComponent extends Component {
|
||||
<Box sx={{ py: 1 }}>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="E-Mail"
|
||||
label={this.props.t ? this.props.t('auth.email') : 'E-Mail'}
|
||||
type="email"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -665,7 +692,7 @@ export class LoginComponent extends Component {
|
||||
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Passwort"
|
||||
label={this.props.t ? this.props.t('auth.password') : 'Passwort'}
|
||||
type="password"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
@@ -687,7 +714,7 @@ export class LoginComponent extends Component {
|
||||
'&:hover': { backgroundColor: 'transparent', textDecoration: 'underline' }
|
||||
}}
|
||||
>
|
||||
Passwort vergessen?
|
||||
{this.props.t ? this.props.t('auth.forgotPassword') : 'Passwort vergessen?'}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
@@ -717,7 +744,7 @@ export class LoginComponent extends Component {
|
||||
onClick={tabValue === 0 ? this.handleLogin : this.handleRegister}
|
||||
sx={{ mt: 2, bgcolor: '#2e7d32', '&:hover': { bgcolor: '#1b5e20' } }}
|
||||
>
|
||||
{tabValue === 0 ? 'ANMELDEN' : 'REGISTRIEREN'}
|
||||
{tabValue === 0 ? (this.props.t ? this.props.t('auth.login').toUpperCase() : 'ANMELDEN') : (this.props.t ? this.props.t('auth.register').toUpperCase() : 'REGISTRIEREN')}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
@@ -740,4 +767,4 @@ export class LoginComponent extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(LoginComponent);
|
||||
export default withRouter(withI18n()(LoginComponent));
|
||||
407
src/components/MainPageLayout.js
Normal file
407
src/components/MainPageLayout.js
Normal file
@@ -0,0 +1,407 @@
|
||||
import React from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import Container from "@mui/material/Container";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import ChevronLeft from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRight from "@mui/icons-material/ChevronRight";
|
||||
import { Link } from "react-router-dom";
|
||||
import SharedCarousel from "./SharedCarousel.js";
|
||||
import { getCombinedAnimatedBorderStyles } from "../utils/animatedBorderStyles.js";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const MainPageLayout = () => {
|
||||
const location = useLocation();
|
||||
const currentPath = location.pathname;
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Determine which page we're on
|
||||
const isHome = currentPath === "/";
|
||||
const isAktionen = currentPath === "/aktionen";
|
||||
const isFiliale = currentPath === "/filiale";
|
||||
|
||||
// Get navigation config based on current page
|
||||
const getNavigationConfig = () => {
|
||||
if (isHome) {
|
||||
return {
|
||||
leftNav: { text: t('navigation.aktionen'), link: "/aktionen" },
|
||||
rightNav: { text: t('navigation.filiale'), link: "/filiale" }
|
||||
};
|
||||
} else if (isAktionen) {
|
||||
return {
|
||||
leftNav: { text: t('navigation.filiale'), link: "/filiale" },
|
||||
rightNav: { text: t('navigation.home'), link: "/" }
|
||||
};
|
||||
} else if (isFiliale) {
|
||||
return {
|
||||
leftNav: { text: t('navigation.home'), link: "/" },
|
||||
rightNav: { text: t('navigation.aktionen'), link: "/aktionen" }
|
||||
};
|
||||
}
|
||||
return { leftNav: null, rightNav: null };
|
||||
};
|
||||
|
||||
const allTitles = {
|
||||
home: t('titles.home'),
|
||||
aktionen: t('titles.aktionen'),
|
||||
filiale: t('titles.filiale')
|
||||
};
|
||||
|
||||
// Define all content boxes for layered rendering
|
||||
const allContentBoxes = {
|
||||
home: [
|
||||
{
|
||||
title: t('sections.seeds'),
|
||||
image: "/assets/images/seeds.jpg",
|
||||
bgcolor: "#e1f0d3",
|
||||
link: "/Kategorie/Seeds"
|
||||
},
|
||||
{
|
||||
title: t('sections.stecklinge'),
|
||||
image: "/assets/images/cutlings.jpg",
|
||||
bgcolor: "#e8f5d6",
|
||||
link: "/Kategorie/Stecklinge"
|
||||
}
|
||||
],
|
||||
aktionen: [
|
||||
{
|
||||
title: t('sections.oilPress'),
|
||||
image: "/assets/images/presse.jpg",
|
||||
bgcolor: "#e1f0d3",
|
||||
link: "/presseverleih"
|
||||
},
|
||||
{
|
||||
title: t('sections.thcTest'),
|
||||
image: "/assets/images/purpl.jpg",
|
||||
bgcolor: "#e8f5d6",
|
||||
link: "/thc-test"
|
||||
}
|
||||
],
|
||||
filiale: [
|
||||
{
|
||||
title: t('sections.address1'),
|
||||
image: "/assets/images/filiale1.jpg",
|
||||
bgcolor: "#e1f0d3",
|
||||
link: "/filiale"
|
||||
},
|
||||
{
|
||||
title: t('sections.address2'),
|
||||
image: "/assets/images/filiale2.jpg",
|
||||
bgcolor: "#e8f5d6",
|
||||
link: "/filiale"
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Get opacity for each page layer
|
||||
const getOpacity = (pageType) => {
|
||||
if (pageType === "home" && isHome) return 1;
|
||||
if (pageType === "aktionen" && isAktionen) return 1;
|
||||
if (pageType === "filiale" && isFiliale) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
const navConfig = getNavigationConfig();
|
||||
|
||||
// Navigation text mapping for translation
|
||||
const navTexts = [
|
||||
{ key: 'aktionen', text: t('navigation.aktionen'), link: '/aktionen' },
|
||||
{ key: 'filiale', text: t('navigation.filiale'), link: '/filiale' },
|
||||
{ key: 'home', text: t('navigation.home'), link: '/' }
|
||||
];
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ py: 2 }}>
|
||||
<style>{getCombinedAnimatedBorderStyles(['seeds', 'cutlings'])}</style>
|
||||
|
||||
{/* Main Navigation Header */}
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
mb: 4,
|
||||
mt: 2,
|
||||
px: 0,
|
||||
transition: "all 0.3s ease-in-out",
|
||||
// Portrait phone: stack title above navigation
|
||||
flexDirection: {
|
||||
xs: "column",
|
||||
sm: "row"
|
||||
}
|
||||
}}>
|
||||
{/* Title for portrait phones - shown first */}
|
||||
<Box sx={{
|
||||
display: { xs: "block", sm: "none" },
|
||||
mb: { xs: 2, sm: 0 },
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
position: "relative"
|
||||
}}>
|
||||
{Object.entries(allTitles).map(([pageType, title]) => (
|
||||
<Typography
|
||||
key={pageType}
|
||||
variant="h3"
|
||||
component="h1"
|
||||
sx={{
|
||||
fontFamily: "SwashingtonCP",
|
||||
fontSize: { xs: "2.125rem", sm: "2.125rem", md: "3rem" },
|
||||
textAlign: "center",
|
||||
color: "primary.main",
|
||||
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)",
|
||||
transition: "opacity 0.5s ease-in-out",
|
||||
opacity: getOpacity(pageType),
|
||||
position: pageType === "home" ? "relative" : "absolute",
|
||||
top: pageType !== "home" ? 0 : "auto",
|
||||
left: pageType !== "home" ? 0 : "auto",
|
||||
transform: "none",
|
||||
width: "100%",
|
||||
pointerEvents: getOpacity(pageType) === 1 ? "auto" : "none",
|
||||
lineHeight: { xs: "1.2", sm: "1.2", md: "1.1" },
|
||||
wordWrap: "break-word",
|
||||
hyphens: "auto"
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Navigation container for portrait phones */}
|
||||
<Box sx={{
|
||||
display: { xs: "flex", sm: "contents" },
|
||||
width: { xs: "100%", sm: "auto" },
|
||||
justifyContent: { xs: "space-between", sm: "initial" },
|
||||
alignItems: "center"
|
||||
}}>
|
||||
{/* Left Navigation - Layered rendering */}
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexShrink: 0,
|
||||
justifyContent: "flex-start",
|
||||
position: "relative",
|
||||
mr: { xs: 0, sm: 2 }
|
||||
}}>
|
||||
{navTexts.map((navItem, index) => {
|
||||
const isActive = navConfig.leftNav && navConfig.leftNav.text === navItem.text;
|
||||
const link = navItem.link;
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={navItem.key}
|
||||
component={Link}
|
||||
to={link}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
transition: "all 0.3s ease",
|
||||
opacity: isActive ? 1 : 0,
|
||||
position: index === 0 ? "relative" : "absolute",
|
||||
left: index !== 0 ? 0 : "auto",
|
||||
pointerEvents: isActive ? "auto" : "none",
|
||||
"&:hover": {
|
||||
transform: "translateX(-5px)",
|
||||
color: "primary.main"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ChevronLeft sx={{ fontSize: "2rem", mr: 1 }} />
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "SwashingtonCP",
|
||||
fontSize: { xs: "1.25rem", sm: "1.25rem", md: "2.125rem" },
|
||||
textShadow: "2px 2px 4px rgba(0, 0, 0, 0.3)",
|
||||
lineHeight: { xs: "1.2", sm: "1.2", md: "1.1" },
|
||||
whiteSpace: "nowrap"
|
||||
}}
|
||||
>
|
||||
{navItem.text}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
||||
{/* Center Title - Layered rendering - Hidden on portrait phones, shown on larger screens */}
|
||||
<Box sx={{
|
||||
flex: 1,
|
||||
display: { xs: "none", sm: "flex" },
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
px: 0,
|
||||
position: "relative",
|
||||
minWidth: 0
|
||||
}}>
|
||||
{Object.entries(allTitles).map(([pageType, title]) => (
|
||||
<Typography
|
||||
key={pageType}
|
||||
variant="h3"
|
||||
component="h1"
|
||||
sx={{
|
||||
fontFamily: "SwashingtonCP",
|
||||
fontSize: { xs: "2.125rem", sm: "2.125rem", md: "3rem" },
|
||||
textAlign: "center",
|
||||
color: "primary.main",
|
||||
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)",
|
||||
transition: "opacity 0.5s ease-in-out",
|
||||
opacity: getOpacity(pageType),
|
||||
position: pageType === "home" ? "relative" : "absolute",
|
||||
top: pageType !== "home" ? "50%" : "auto",
|
||||
left: pageType !== "home" ? "50%" : "auto",
|
||||
transform: pageType !== "home" ? "translate(-50%, -50%)" : "none",
|
||||
width: "100%",
|
||||
pointerEvents: getOpacity(pageType) === 1 ? "auto" : "none",
|
||||
lineHeight: { xs: "1.2", sm: "1.2", md: "1.1" },
|
||||
wordWrap: "break-word",
|
||||
hyphens: "auto"
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Right Navigation - Layered rendering */}
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexShrink: 0,
|
||||
justifyContent: "flex-end",
|
||||
position: "relative",
|
||||
ml: { xs: 0, sm: 2 }
|
||||
}}>
|
||||
{navTexts.map((navItem, index) => {
|
||||
const isActive = navConfig.rightNav && navConfig.rightNav.text === navItem.text;
|
||||
const link = navItem.link;
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={navItem.key}
|
||||
component={Link}
|
||||
to={link}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
transition: "all 0.3s ease",
|
||||
opacity: isActive ? 1 : 0,
|
||||
position: index === 0 ? "relative" : "absolute",
|
||||
right: index !== 0 ? 0 : "auto",
|
||||
pointerEvents: isActive ? "auto" : "none",
|
||||
"&:hover": {
|
||||
transform: "translateX(5px)",
|
||||
color: "primary.main"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: "SwashingtonCP",
|
||||
fontSize: { xs: "1.25rem", sm: "1.25rem", md: "2.125rem" },
|
||||
textShadow: "2px 2px 4px rgba(0, 0, 0, 0.3)",
|
||||
lineHeight: { xs: "1.2", sm: "1.2", md: "1.1" },
|
||||
whiteSpace: "nowrap"
|
||||
}}
|
||||
>
|
||||
{navItem.text}
|
||||
</Typography>
|
||||
<ChevronRight sx={{ fontSize: "2rem", ml: 1 }} />
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Content Boxes - Layered rendering */}
|
||||
<Box sx={{ position: "relative", mb: 4 }}>
|
||||
{Object.entries(allContentBoxes).map(([pageType, contentBoxes]) => (
|
||||
<Grid
|
||||
key={pageType}
|
||||
container
|
||||
spacing={0}
|
||||
sx={{
|
||||
transition: "opacity 0.5s ease-in-out",
|
||||
opacity: getOpacity(pageType),
|
||||
position: pageType === "home" ? "relative" : "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
pointerEvents: getOpacity(pageType) === 1 ? "auto" : "none"
|
||||
}}
|
||||
>
|
||||
{contentBoxes.map((box, index) => (
|
||||
<Grid key={`${pageType}-${index}`} item xs={12} sm={6} sx={{ p: 2, width: "50%" }}>
|
||||
<div className={`animated-border-card ${index === 0 ? 'seeds-card' : 'cutlings-card'}`}>
|
||||
<Paper
|
||||
component={Link}
|
||||
to={box.link}
|
||||
sx={{
|
||||
p: 0,
|
||||
textDecoration: "none",
|
||||
color: "text.primary",
|
||||
borderRadius: 2,
|
||||
overflow: "hidden",
|
||||
height: { xs: 150, sm: 200, md: 300 },
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
boxShadow: 10,
|
||||
transition: "all 0.3s ease",
|
||||
"&:hover": {
|
||||
transform: "translateY(-5px)",
|
||||
boxShadow: 20,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
bgcolor: box.bgcolor,
|
||||
backgroundImage: `url("${box.image}")`,
|
||||
backgroundSize: "contain",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bgcolor: "rgba(27, 94, 32, 0.8)",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "1.6rem",
|
||||
color: "white",
|
||||
fontFamily: "SwashingtonCP",
|
||||
}}
|
||||
>
|
||||
{box.title}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</div>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Shared Carousel */}
|
||||
<SharedCarousel />
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainPageLayout;
|
||||
@@ -1,381 +0,0 @@
|
||||
import React, { Component, useState } from "react";
|
||||
import { Button, Box, Typography, CircularProgress } from "@mui/material";
|
||||
import config from "../config.js";
|
||||
|
||||
// Function to lazy load Mollie script
|
||||
const loadMollie = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Check if Mollie is already loaded
|
||||
if (window.Mollie) {
|
||||
resolve(window.Mollie);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create script element
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://js.mollie.com/v1/mollie.js';
|
||||
script.async = true;
|
||||
|
||||
script.onload = () => {
|
||||
if (window.Mollie) {
|
||||
resolve(window.Mollie);
|
||||
} else {
|
||||
reject(new Error('Mollie failed to load'));
|
||||
}
|
||||
};
|
||||
|
||||
script.onerror = () => {
|
||||
reject(new Error('Failed to load Mollie script'));
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
};
|
||||
|
||||
const CheckoutForm = ({ mollie }) => {
|
||||
const [errorMessage, setErrorMessage] = useState(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!mollie) return;
|
||||
|
||||
let mountedComponents = {
|
||||
cardNumber: null,
|
||||
cardHolder: null,
|
||||
expiryDate: null,
|
||||
verificationCode: null
|
||||
};
|
||||
|
||||
try {
|
||||
// Create Mollie components
|
||||
const cardNumber = mollie.createComponent('cardNumber');
|
||||
const cardHolder = mollie.createComponent('cardHolder');
|
||||
const expiryDate = mollie.createComponent('expiryDate');
|
||||
const verificationCode = mollie.createComponent('verificationCode');
|
||||
|
||||
// Store references for cleanup
|
||||
mountedComponents = {
|
||||
cardNumber,
|
||||
cardHolder,
|
||||
expiryDate,
|
||||
verificationCode
|
||||
};
|
||||
|
||||
// Mount components
|
||||
cardNumber.mount('#card-number');
|
||||
cardHolder.mount('#card-holder');
|
||||
expiryDate.mount('#expiry-date');
|
||||
verificationCode.mount('#verification-code');
|
||||
|
||||
// Set up error handling
|
||||
cardNumber.addEventListener('change', event => {
|
||||
const errorElement = document.querySelector('#card-number-error');
|
||||
if (errorElement) {
|
||||
if (event.error && event.touched) {
|
||||
errorElement.textContent = event.error;
|
||||
} else {
|
||||
errorElement.textContent = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cardHolder.addEventListener('change', event => {
|
||||
const errorElement = document.querySelector('#card-holder-error');
|
||||
if (errorElement) {
|
||||
if (event.error && event.touched) {
|
||||
errorElement.textContent = event.error;
|
||||
} else {
|
||||
errorElement.textContent = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expiryDate.addEventListener('change', event => {
|
||||
const errorElement = document.querySelector('#expiry-date-error');
|
||||
if (errorElement) {
|
||||
if (event.error && event.touched) {
|
||||
errorElement.textContent = event.error;
|
||||
} else {
|
||||
errorElement.textContent = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
verificationCode.addEventListener('change', event => {
|
||||
const errorElement = document.querySelector('#verification-code-error');
|
||||
if (errorElement) {
|
||||
if (event.error && event.touched) {
|
||||
errorElement.textContent = event.error;
|
||||
} else {
|
||||
errorElement.textContent = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Components are now mounted and ready
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating Mollie components:', error);
|
||||
setErrorMessage('Failed to initialize payment form. Please try again.');
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
try {
|
||||
if (mountedComponents.cardNumber) mountedComponents.cardNumber.unmount();
|
||||
if (mountedComponents.cardHolder) mountedComponents.cardHolder.unmount();
|
||||
if (mountedComponents.expiryDate) mountedComponents.expiryDate.unmount();
|
||||
if (mountedComponents.verificationCode) mountedComponents.verificationCode.unmount();
|
||||
} catch (error) {
|
||||
console.error('Error cleaning up Mollie components:', error);
|
||||
}
|
||||
};
|
||||
}, [mollie]);
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!mollie || isSubmitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
setErrorMessage(null);
|
||||
|
||||
try {
|
||||
const { token, error } = await mollie.createToken();
|
||||
|
||||
if (error) {
|
||||
setErrorMessage(error.message || 'Payment failed. Please try again.');
|
||||
setIsSubmitting(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token) {
|
||||
// Handle successful token creation
|
||||
// Create a payment completion event similar to Stripe
|
||||
const mollieCompletionData = {
|
||||
mollieToken: token,
|
||||
paymentMethod: 'mollie'
|
||||
};
|
||||
|
||||
// Dispatch a custom event to notify the parent component
|
||||
const completionEvent = new CustomEvent('molliePaymentComplete', {
|
||||
detail: mollieCompletionData
|
||||
});
|
||||
window.dispatchEvent(completionEvent);
|
||||
|
||||
// For now, redirect to profile with completion data
|
||||
const returnUrl = `${window.location.origin}/profile?complete&mollie_token=${token}`;
|
||||
window.location.href = returnUrl;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating Mollie token:', error);
|
||||
setErrorMessage('Payment failed. Please try again.');
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ maxWidth: 600, mx: 'auto', p: 3 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Kreditkarte oder Sofortüberweisung
|
||||
</Typography>
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
Kartennummer
|
||||
</Typography>
|
||||
<Box
|
||||
id="card-number"
|
||||
sx={{
|
||||
p: 2,
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 1,
|
||||
minHeight: 40,
|
||||
backgroundColor: '#fff'
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
id="card-number-error"
|
||||
variant="caption"
|
||||
sx={{ color: 'error.main', minHeight: 16, display: 'block' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
Karteninhaber
|
||||
</Typography>
|
||||
<Box
|
||||
id="card-holder"
|
||||
sx={{
|
||||
p: 2,
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 1,
|
||||
minHeight: 40,
|
||||
backgroundColor: '#fff'
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
id="card-holder-error"
|
||||
variant="caption"
|
||||
sx={{ color: 'error.main', minHeight: 16, display: 'block' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 3 }}>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
Ablaufdatum
|
||||
</Typography>
|
||||
<Box
|
||||
id="expiry-date"
|
||||
sx={{
|
||||
p: 2,
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 1,
|
||||
minHeight: 40,
|
||||
backgroundColor: '#fff'
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
id="expiry-date-error"
|
||||
variant="caption"
|
||||
sx={{ color: 'error.main', minHeight: 16, display: 'block' }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
Sicherheitscode
|
||||
</Typography>
|
||||
<Box
|
||||
id="verification-code"
|
||||
sx={{
|
||||
p: 2,
|
||||
border: '1px solid #e0e0e0',
|
||||
borderRadius: 1,
|
||||
minHeight: 40,
|
||||
backgroundColor: '#fff'
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
id="verification-code-error"
|
||||
variant="caption"
|
||||
sx={{ color: 'error.main', minHeight: 16, display: 'block' }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={!mollie || isSubmitting}
|
||||
type="submit"
|
||||
fullWidth
|
||||
sx={{
|
||||
mt: 2,
|
||||
backgroundColor: '#2e7d32',
|
||||
'&:hover': {
|
||||
backgroundColor: '#1b5e20'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<CircularProgress size={20} sx={{ mr: 1, color: 'white' }} />
|
||||
Verarbeitung...
|
||||
</>
|
||||
) : (
|
||||
'Bezahlung Abschließen'
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{errorMessage && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{ color: 'error.main', mt: 2, textAlign: 'center' }}
|
||||
>
|
||||
{errorMessage}
|
||||
</Typography>
|
||||
)}
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
class Mollie extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
mollie: null,
|
||||
loading: true,
|
||||
error: null,
|
||||
};
|
||||
this.molliePromise = loadMollie();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.molliePromise
|
||||
.then((MollieClass) => {
|
||||
try {
|
||||
// Initialize Mollie with profile key
|
||||
const mollie = MollieClass(config.mollieProfileKey, {
|
||||
locale: 'de_DE',
|
||||
testmode: true // Set to false for production
|
||||
});
|
||||
this.setState({ mollie, loading: false });
|
||||
} catch (error) {
|
||||
console.error('Error initializing Mollie:', error);
|
||||
this.setState({
|
||||
error: 'Failed to initialize payment system. Please try again.',
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error loading Mollie:', error);
|
||||
this.setState({
|
||||
error: 'Failed to load payment system. Please try again.',
|
||||
loading: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { mollie, loading, error } = this.state;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||
<CircularProgress sx={{ color: '#2e7d32' }} />
|
||||
<Typography variant="body1" sx={{ mt: 2 }}>
|
||||
Zahlungskomponente wird geladen...
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||
<Typography variant="body1" sx={{ color: 'error.main' }}>
|
||||
{error}
|
||||
</Typography>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => window.location.reload()}
|
||||
sx={{ mt: 2 }}
|
||||
>
|
||||
Seite neu laden
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return <CheckoutForm mollie={mollie} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default Mollie;
|
||||
168
src/components/PaymentSuccess.js
Normal file
168
src/components/PaymentSuccess.js
Normal file
@@ -0,0 +1,168 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Navigate } from 'react-router-dom';
|
||||
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||
import SocketContext from '../contexts/SocketContext.js';
|
||||
|
||||
class PaymentSuccess extends Component {
|
||||
static contextType = SocketContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
redirectUrl: null,
|
||||
processing: true,
|
||||
error: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.processMolliePayment();
|
||||
}
|
||||
|
||||
processMolliePayment = () => {
|
||||
try {
|
||||
// Get the stored payment ID from localStorage
|
||||
const pendingPayment = localStorage.getItem('pendingPayment');
|
||||
|
||||
if (!pendingPayment) {
|
||||
console.error('No pending payment found in localStorage');
|
||||
this.setState({
|
||||
redirectUrl: '/profile#cart',
|
||||
processing: false,
|
||||
error: 'No payment information found'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let paymentData;
|
||||
try {
|
||||
paymentData = JSON.parse(pendingPayment);
|
||||
// Clear the pending payment data
|
||||
localStorage.removeItem('pendingPayment');
|
||||
} catch (error) {
|
||||
console.error('Error parsing pending payment data:', error);
|
||||
this.setState({
|
||||
redirectUrl: '/profile#cart',
|
||||
processing: false,
|
||||
error: 'Invalid payment data'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!paymentData.paymentId) {
|
||||
console.error('No payment ID found in stored payment data');
|
||||
this.setState({
|
||||
redirectUrl: '/profile#cart',
|
||||
processing: false,
|
||||
error: 'Missing payment ID'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check payment status with backend
|
||||
this.checkMolliePaymentStatus(paymentData.paymentId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error processing Mollie payment:', error);
|
||||
this.setState({
|
||||
redirectUrl: '/profile#cart',
|
||||
processing: false,
|
||||
error: 'Payment processing failed'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
checkMolliePaymentStatus = (paymentId) => {
|
||||
const { socket } = this.context;
|
||||
|
||||
if (!socket || !socket.connected) {
|
||||
console.error('Socket not connected');
|
||||
this.setState({
|
||||
redirectUrl: '/profile#cart',
|
||||
processing: false,
|
||||
error: 'Connection error'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
socket.emit('checkMollieIntent', { paymentId }, (response) => {
|
||||
if (response.success) {
|
||||
console.log('Payment Status:', response.payment.status);
|
||||
console.log('Is Paid:', response.payment.isPaid);
|
||||
console.log('Order Created:', response.order.created);
|
||||
|
||||
if (response.order.orderId) {
|
||||
console.log('Order ID:', response.order.orderId);
|
||||
}
|
||||
|
||||
// Build the redirect URL with Mollie completion parameters
|
||||
const profileUrl = new URL('/profile', window.location.origin);
|
||||
profileUrl.searchParams.set('mollieComplete', 'true');
|
||||
profileUrl.searchParams.set('mollie_payment_id', paymentId);
|
||||
profileUrl.searchParams.set('mollie_status', response.payment.status);
|
||||
profileUrl.searchParams.set('mollie_amount', response.payment.amount);
|
||||
profileUrl.searchParams.set('mollie_timestamp', Date.now().toString());
|
||||
profileUrl.searchParams.set('mollie_is_paid', response.payment.isPaid.toString());
|
||||
|
||||
if (response.order.orderId) {
|
||||
profileUrl.searchParams.set('mollie_order_id', response.order.orderId.toString());
|
||||
}
|
||||
|
||||
// Set hash to cart tab
|
||||
profileUrl.hash = '#cart';
|
||||
|
||||
this.setState({
|
||||
redirectUrl: profileUrl.pathname + profileUrl.search + profileUrl.hash,
|
||||
processing: false
|
||||
});
|
||||
} else {
|
||||
console.error('Failed to check payment status:', response.error);
|
||||
this.setState({
|
||||
redirectUrl: '/profile#cart',
|
||||
processing: false,
|
||||
error: response.error || 'Payment verification failed'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { redirectUrl, processing, error } = this.state;
|
||||
|
||||
if (processing) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
minHeight: '60vh',
|
||||
gap: 2
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={60} />
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
Zahlung wird überprüft...
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Bitte warten Sie, während wir Ihre Zahlung bei Mollie überprüfen.
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Navigate to="/profile#cart" replace />;
|
||||
}
|
||||
|
||||
if (redirectUrl) {
|
||||
return <Navigate to={redirectUrl} replace />;
|
||||
}
|
||||
|
||||
// Fallback redirect to profile
|
||||
return <Navigate to="/profile#cart" replace />;
|
||||
}
|
||||
}
|
||||
|
||||
export default PaymentSuccess;
|
||||
@@ -68,8 +68,8 @@ class Product extends Component {
|
||||
render() {
|
||||
const {
|
||||
id, name, price, available, manufacturer, seoName,
|
||||
currency, vat, massMenge, massEinheit, thc,
|
||||
floweringWeeks,incoming, neu, weight, versandklasse, availableSupplier
|
||||
currency, vat, cGrundEinheit, fGrundPreis, thc,
|
||||
floweringWeeks,incoming, neu, weight, versandklasse, availableSupplier, komponenten
|
||||
} = this.props;
|
||||
|
||||
const isNew = neu && (new Date().getTime() - new Date(neu).getTime() < 30 * 24 * 60 * 60 * 1000);
|
||||
@@ -341,8 +341,8 @@ class Product extends Component {
|
||||
|
||||
|
||||
</Typography>
|
||||
{massMenge != 1 && massEinheit && (<Typography variant="body2" color="text.secondary" sx={{ m: 0,p: 0 }}>
|
||||
({new Intl.NumberFormat('de-DE', {style: 'currency', currency: currency || 'EUR'}).format(price/massMenge)}/{massEinheit})
|
||||
{cGrundEinheit && fGrundPreis && fGrundPreis != price && (<Typography variant="body2" color="text.secondary" sx={{ m: 0,p: 0 }}>
|
||||
({new Intl.NumberFormat('de-DE', {style: 'currency', currency: currency || 'EUR'}).format(fGrundPreis)}/{cGrundEinheit})
|
||||
</Typography> )}
|
||||
</div>
|
||||
{/*incoming*/}
|
||||
@@ -358,7 +358,7 @@ class Product extends Component {
|
||||
>
|
||||
<ZoomInIcon />
|
||||
</IconButton>
|
||||
<AddToCartButton cartButton={true} availableSupplier={availableSupplier} available={available} incoming={incoming} seoName={seoName} pictureList={this.props.pictureList} id={id} price={price} vat={vat} weight={weight} name={name} versandklasse={versandklasse}/>
|
||||
<AddToCartButton cartButton={true} availableSupplier={availableSupplier} komponenten={komponenten} cGrundEinheit={cGrundEinheit} fGrundPreis={fGrundPreis} available={available} incoming={incoming} seoName={seoName} pictureList={this.props.pictureList} id={id} price={price} vat={vat} weight={weight} name={name} versandklasse={versandklasse}/>
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
@@ -29,6 +29,12 @@ class ProductDetailPage extends Component {
|
||||
attributes: [],
|
||||
isSteckling: false,
|
||||
imageDialogOpen: false,
|
||||
komponenten: [],
|
||||
komponentenLoaded: false,
|
||||
komponentenData: {}, // Store individual komponent data with loading states
|
||||
komponentenImages: {}, // Store tiny pictures for komponenten
|
||||
totalKomponentenPrice: 0,
|
||||
totalSavings: 0
|
||||
};
|
||||
} else {
|
||||
this.state = {
|
||||
@@ -39,6 +45,12 @@ class ProductDetailPage extends Component {
|
||||
attributes: [],
|
||||
isSteckling: false,
|
||||
imageDialogOpen: false,
|
||||
komponenten: [],
|
||||
komponentenLoaded: false,
|
||||
komponentenData: {}, // Store individual komponent data with loading states
|
||||
komponentenImages: {}, // Store tiny pictures for komponenten
|
||||
totalKomponentenPrice: 0,
|
||||
totalSavings: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -64,6 +76,248 @@ class ProductDetailPage extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
loadKomponentImage = (komponentId, pictureList) => {
|
||||
// Initialize cache if it doesn't exist
|
||||
if (!window.smallPicCache) {
|
||||
window.smallPicCache = {};
|
||||
}
|
||||
|
||||
// Skip if no pictureList
|
||||
if (!pictureList || pictureList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the first image ID from pictureList
|
||||
const bildId = pictureList.split(',')[0];
|
||||
|
||||
// Check if already cached
|
||||
if (window.smallPicCache[bildId]) {
|
||||
this.setState(prevState => ({
|
||||
komponentenImages: {
|
||||
...prevState.komponentenImages,
|
||||
[komponentId]: window.smallPicCache[bildId]
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if socketB is available
|
||||
if (!this.props.socketB || !this.props.socketB.connected) {
|
||||
console.log("SocketB not connected yet, skipping image load for komponent:", komponentId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch image from server
|
||||
this.props.socketB.emit('getPic', { bildId, size: 'small' }, (res) => {
|
||||
if (res.success) {
|
||||
// Cache the image
|
||||
window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
||||
|
||||
// Update state
|
||||
this.setState(prevState => ({
|
||||
komponentenImages: {
|
||||
...prevState.komponentenImages,
|
||||
[komponentId]: window.smallPicCache[bildId]
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
console.log('Error loading komponent image:', res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadKomponent = (id, count) => {
|
||||
// Initialize cache if it doesn't exist
|
||||
if (!window.productDetailCache) {
|
||||
window.productDetailCache = {};
|
||||
}
|
||||
|
||||
// Check if this komponent is already cached
|
||||
if (window.productDetailCache[id]) {
|
||||
const cachedProduct = window.productDetailCache[id];
|
||||
|
||||
// Load komponent image if available
|
||||
if (cachedProduct.pictureList) {
|
||||
this.loadKomponentImage(id, cachedProduct.pictureList);
|
||||
}
|
||||
|
||||
// Update state with cached data
|
||||
this.setState(prevState => {
|
||||
const newKomponentenData = {
|
||||
...prevState.komponentenData,
|
||||
[id]: {
|
||||
...cachedProduct,
|
||||
count: parseInt(count),
|
||||
loaded: true
|
||||
}
|
||||
};
|
||||
|
||||
// Check if all remaining komponenten are loaded
|
||||
const allLoaded = prevState.komponenten.every(k =>
|
||||
newKomponentenData[k.id] && newKomponentenData[k.id].loaded
|
||||
);
|
||||
|
||||
// Calculate totals if all loaded
|
||||
let totalKomponentenPrice = 0;
|
||||
let totalSavings = 0;
|
||||
|
||||
if (allLoaded) {
|
||||
totalKomponentenPrice = prevState.komponenten.reduce((sum, k) => {
|
||||
const komponentData = newKomponentenData[k.id];
|
||||
if (komponentData && komponentData.loaded) {
|
||||
return sum + (komponentData.price * parseInt(k.count));
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
// Calculate savings (difference between buying individually vs as set)
|
||||
const setPrice = prevState.product ? prevState.product.price : 0;
|
||||
totalSavings = Math.max(0, totalKomponentenPrice - setPrice);
|
||||
}
|
||||
|
||||
console.log("Cached komponent loaded:", id, "data:", newKomponentenData[id]);
|
||||
console.log("All loaded (cached):", allLoaded);
|
||||
|
||||
return {
|
||||
komponentenData: newKomponentenData,
|
||||
komponentenLoaded: allLoaded,
|
||||
totalKomponentenPrice,
|
||||
totalSavings
|
||||
};
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If not cached, fetch from server (similar to loadProductData)
|
||||
if (!this.props.socket || !this.props.socket.connected) {
|
||||
console.log("Socket not connected yet, waiting for connection to load komponent data");
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark this komponent as loading
|
||||
this.setState(prevState => ({
|
||||
komponentenData: {
|
||||
...prevState.komponentenData,
|
||||
[id]: {
|
||||
...prevState.komponentenData[id],
|
||||
loading: true,
|
||||
loaded: false,
|
||||
count: parseInt(count)
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this.props.socket.emit(
|
||||
"getProductView",
|
||||
{ articleId: id },
|
||||
(res) => {
|
||||
if (res.success) {
|
||||
// Cache the successful response
|
||||
window.productDetailCache[id] = res.product;
|
||||
|
||||
// Load komponent image if available
|
||||
if (res.product.pictureList) {
|
||||
this.loadKomponentImage(id, res.product.pictureList);
|
||||
}
|
||||
|
||||
// Update state with loaded data
|
||||
this.setState(prevState => {
|
||||
const newKomponentenData = {
|
||||
...prevState.komponentenData,
|
||||
[id]: {
|
||||
...res.product,
|
||||
count: parseInt(count),
|
||||
loading: false,
|
||||
loaded: true
|
||||
}
|
||||
};
|
||||
|
||||
// Check if all remaining komponenten are loaded
|
||||
const allLoaded = prevState.komponenten.every(k =>
|
||||
newKomponentenData[k.id] && newKomponentenData[k.id].loaded
|
||||
);
|
||||
|
||||
// Calculate totals if all loaded
|
||||
let totalKomponentenPrice = 0;
|
||||
let totalSavings = 0;
|
||||
|
||||
if (allLoaded) {
|
||||
totalKomponentenPrice = prevState.komponenten.reduce((sum, k) => {
|
||||
const komponentData = newKomponentenData[k.id];
|
||||
if (komponentData && komponentData.loaded) {
|
||||
return sum + (komponentData.price * parseInt(k.count));
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
// Calculate savings (difference between buying individually vs as set)
|
||||
const setPrice = prevState.product ? prevState.product.price : 0;
|
||||
totalSavings = Math.max(0, totalKomponentenPrice - setPrice);
|
||||
}
|
||||
|
||||
console.log("Updated komponentenData for", id, ":", newKomponentenData[id]);
|
||||
console.log("All loaded:", allLoaded);
|
||||
|
||||
return {
|
||||
komponentenData: newKomponentenData,
|
||||
komponentenLoaded: allLoaded,
|
||||
totalKomponentenPrice,
|
||||
totalSavings
|
||||
};
|
||||
});
|
||||
|
||||
console.log("getProductView (komponent)", res);
|
||||
} else {
|
||||
console.error("Error loading komponent:", res.error || "Unknown error", res);
|
||||
|
||||
// Remove failed komponent from the list and check if all remaining are loaded
|
||||
this.setState(prevState => {
|
||||
const newKomponenten = prevState.komponenten.filter(k => k.id !== id);
|
||||
const newKomponentenData = { ...prevState.komponentenData };
|
||||
|
||||
// Remove failed komponent from data
|
||||
delete newKomponentenData[id];
|
||||
|
||||
// Check if all remaining komponenten are loaded
|
||||
const allLoaded = newKomponenten.length === 0 || newKomponenten.every(k =>
|
||||
newKomponentenData[k.id] && newKomponentenData[k.id].loaded
|
||||
);
|
||||
|
||||
// Calculate totals if all loaded
|
||||
let totalKomponentenPrice = 0;
|
||||
let totalSavings = 0;
|
||||
|
||||
if (allLoaded && newKomponenten.length > 0) {
|
||||
totalKomponentenPrice = newKomponenten.reduce((sum, k) => {
|
||||
const komponentData = newKomponentenData[k.id];
|
||||
if (komponentData && komponentData.loaded) {
|
||||
return sum + (komponentData.price * parseInt(k.count));
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
// Calculate savings (difference between buying individually vs as set)
|
||||
const setPrice = this.state.product ? this.state.product.price : 0;
|
||||
totalSavings = Math.max(0, totalKomponentenPrice - setPrice);
|
||||
}
|
||||
|
||||
console.log("Removed failed komponent:", id, "remaining:", newKomponenten.length);
|
||||
console.log("All loaded after removal:", allLoaded);
|
||||
|
||||
return {
|
||||
komponenten: newKomponenten,
|
||||
komponentenData: newKomponentenData,
|
||||
komponentenLoaded: allLoaded,
|
||||
totalKomponentenPrice,
|
||||
totalSavings
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadProductData = () => {
|
||||
if (!this.props.socket || !this.props.socket.connected) {
|
||||
// Socket not connected yet, but don't show error immediately on first load
|
||||
@@ -78,12 +332,37 @@ class ProductDetailPage extends Component {
|
||||
(res) => {
|
||||
if (res.success) {
|
||||
res.product.seoName = this.props.seoName;
|
||||
|
||||
// Initialize cache if it doesn't exist
|
||||
if (!window.productDetailCache) {
|
||||
window.productDetailCache = {};
|
||||
}
|
||||
|
||||
// Cache the product data
|
||||
window.productDetailCache[this.props.seoName] = res.product;
|
||||
|
||||
const komponenten = [];
|
||||
if(res.product.komponenten) {
|
||||
for(const komponent of res.product.komponenten.split(",")) {
|
||||
// Handle both "x" and "×" as separators
|
||||
const [id, count] = komponent.split(/[x×]/);
|
||||
komponenten.push({id: id.trim(), count: count.trim()});
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
product: res.product,
|
||||
loading: false,
|
||||
error: null,
|
||||
imageDialogOpen: false,
|
||||
attributes: res.attributes
|
||||
attributes: res.attributes,
|
||||
komponenten: komponenten,
|
||||
komponentenLoaded: komponenten.length === 0 // If no komponenten, mark as loaded
|
||||
}, () => {
|
||||
if(komponenten.length > 0) {
|
||||
for(const komponent of komponenten) {
|
||||
this.loadKomponent(komponent.id, komponent.count);
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log("getProductView", res);
|
||||
|
||||
@@ -180,7 +459,7 @@ class ProductDetailPage extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { product, loading, error, attributeImages, isSteckling, attributes } =
|
||||
const { product, loading, error, attributeImages, isSteckling, attributes, komponentenLoaded, komponentenData, komponentenImages, totalKomponentenPrice, totalSavings } =
|
||||
this.state;
|
||||
|
||||
if (loading) {
|
||||
@@ -211,7 +490,7 @@ class ProductDetailPage extends Component {
|
||||
<Typography>{error}</Typography>
|
||||
<Link to="/" style={{ textDecoration: "none" }}>
|
||||
<Typography color="primary" sx={{ mt: 2 }}>
|
||||
Zurück zur Startseite
|
||||
{this.props.t ? this.props.t('product.backToHome') : 'Zurück zur Startseite'}
|
||||
</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
@@ -229,7 +508,7 @@ class ProductDetailPage extends Component {
|
||||
</Typography>
|
||||
<Link to="/" style={{ textDecoration: "none" }}>
|
||||
<Typography color="primary" sx={{ mt: 2 }}>
|
||||
Zurück zur Startseite
|
||||
{this.props.t ? this.props.t('product.backToHome') : 'Zurück zur Startseite'}
|
||||
</Typography>
|
||||
</Link>
|
||||
</Box>
|
||||
@@ -294,7 +573,7 @@ class ProductDetailPage extends Component {
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Zurück
|
||||
{this.props.t ? this.props.t('common.back') : 'Zurück'}
|
||||
</Link>
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -355,7 +634,7 @@ class ProductDetailPage extends Component {
|
||||
{/* Product identifiers */}
|
||||
<Box sx={{ mb: 1 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Artikelnummer: {product.articleNumber} {product.gtin ? ` | GTIN: ${product.gtin}` : ""}
|
||||
{this.props.t ? this.props.t('product.articleNumber') : 'Artikelnummer'}: {product.articleNumber} {product.gtin ? ` | GTIN: ${product.gtin}` : ""}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
@@ -373,7 +652,7 @@ class ProductDetailPage extends Component {
|
||||
{product.manufacturer && (
|
||||
<Box sx={{ display: "flex", alignItems: "center", mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ fontStyle: "italic" }}>
|
||||
Hersteller: {product.manufacturer}
|
||||
{this.props.t ? this.props.t('product.manufacturer') : 'Hersteller'}: {product.manufacturer}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
@@ -386,18 +665,12 @@ class ProductDetailPage extends Component {
|
||||
.map((attribute) => {
|
||||
const key = attribute.kMerkmalWert;
|
||||
return (
|
||||
<Box key={key} sx={{ mb: 1 }}>
|
||||
<Box key={key} sx={{ mb: 1,border: "1px solid #e0e0e0", borderRadius: 1 }}>
|
||||
<CardMedia
|
||||
component="img"
|
||||
style={{ width: "72px", height: "98px" }}
|
||||
image={attributeImages[key]}
|
||||
alt={`Attribute ${key}`}
|
||||
sx={{
|
||||
maxWidth: "100px",
|
||||
maxHeight: "100px",
|
||||
objectFit: "contain",
|
||||
border: "1px solid #e0e0e0",
|
||||
borderRadius: 1,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
@@ -452,7 +725,11 @@ class ProductDetailPage extends Component {
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
inkl. {product.vat}% MwSt.
|
||||
{product.cGrundEinheit && product.fGrundPreis && (
|
||||
<>; {new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(product.fGrundPreis)}/{product.cGrundEinheit}</>
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
{product.versandklasse &&
|
||||
product.versandklasse != "standard" &&
|
||||
product.versandklasse != "kostenlos" && (
|
||||
@@ -461,6 +738,37 @@ class ProductDetailPage extends Component {
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Savings comparison - positioned between price and cart button */}
|
||||
{product.komponenten && komponentenLoaded && totalKomponentenPrice > product.price &&
|
||||
(totalKomponentenPrice - product.price >= 2 &&
|
||||
(totalKomponentenPrice - product.price) / product.price >= 0.02) && (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minWidth: { xs: "100%", sm: "200px" }
|
||||
}}>
|
||||
<Box sx={{ p: 2, borderRadius: 1, backgroundColor: "#e8f5e8", textAlign: "center" }}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
fontWeight: "bold",
|
||||
color: "success.main"
|
||||
}}
|
||||
>
|
||||
Sie sparen: {new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(totalKomponentenPrice - product.price)}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Günstiger als Einzelkauf
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -487,6 +795,7 @@ class ProductDetailPage extends Component {
|
||||
vat={product.vat}
|
||||
weight={product.weight}
|
||||
availableSupplier={product.availableSupplier}
|
||||
komponenten={product.komponenten}
|
||||
name={cleanProductName(product.name) + " Stecklingsvorbestellung 1 Stück"}
|
||||
versandklasse={"nur Abholung"}
|
||||
/>
|
||||
@@ -516,12 +825,16 @@ class ProductDetailPage extends Component {
|
||||
available={product.available}
|
||||
id={product.id}
|
||||
availableSupplier={product.availableSupplier}
|
||||
komponenten={product.komponenten}
|
||||
cGrundEinheit={product.cGrundEinheit}
|
||||
fGrundPreis={product.fGrundPreis}
|
||||
price={product.price}
|
||||
vat={product.vat}
|
||||
weight={product.weight}
|
||||
name={cleanProductName(product.name)}
|
||||
versandklasse={product.versandklasse}
|
||||
/>
|
||||
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
@@ -565,6 +878,206 @@ class ProductDetailPage extends Component {
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{product.komponenten && product.komponenten.split(",").length > 0 && (
|
||||
<Box sx={{ mt: 4, p: 4, background: "#fff", borderRadius: 2, boxShadow: "0 2px 8px rgba(0,0,0,0.08)" }}>
|
||||
<Typography variant="h4" gutterBottom>Bestehend aus:</Typography>
|
||||
<Box sx={{ maxWidth: 800, mx: "auto" }}>
|
||||
|
||||
{(console.log("komponentenLoaded:", komponentenLoaded), komponentenLoaded) ? (
|
||||
<>
|
||||
{console.log("Rendering loaded komponenten:", this.state.komponenten.length, "komponentenData:", Object.keys(komponentenData).length)}
|
||||
{this.state.komponenten.map((komponent, index) => {
|
||||
const komponentData = komponentenData[komponent.id];
|
||||
console.log(`Rendering komponent ${komponent.id}:`, komponentData);
|
||||
|
||||
// Don't show border on last item (pricing section has its own top border)
|
||||
const isLastItem = index === this.state.komponenten.length - 1;
|
||||
const showBorder = !isLastItem;
|
||||
|
||||
if (!komponentData || !komponentData.loaded) {
|
||||
return (
|
||||
<Box key={komponent.id} sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
py: 1,
|
||||
borderBottom: showBorder ? "1px solid #eee" : "none",
|
||||
minHeight: "70px" // Consistent height to prevent layout shifts
|
||||
}}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Box sx={{ width: 50, height: 50, flexShrink: 0, backgroundColor: "#f5f5f5", borderRadius: 1, border: "1px solid #e0e0e0" }}>
|
||||
{/* Empty placeholder for image */}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body1">
|
||||
{index + 1}. Lädt...
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{komponent.count}x
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
-
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const itemPrice = komponentData.price * parseInt(komponent.count);
|
||||
const formattedPrice = new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(itemPrice);
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={komponent.id}
|
||||
component={Link}
|
||||
to={`/Artikel/${komponentData.seoName}`}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
py: 1,
|
||||
borderBottom: showBorder ? "1px solid #eee" : "none",
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
minHeight: "70px", // Consistent height to prevent layout shifts
|
||||
"&:hover": {
|
||||
backgroundColor: "#f5f5f5"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Box sx={{ width: 50, height: 50, flexShrink: 0 }}>
|
||||
{komponentenImages[komponent.id] ? (
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="50"
|
||||
image={komponentenImages[komponent.id]}
|
||||
alt={komponentData.name}
|
||||
sx={{
|
||||
objectFit: "contain",
|
||||
borderRadius: 1,
|
||||
border: "1px solid #e0e0e0"
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="50"
|
||||
image="/assets/images/nopicture.jpg"
|
||||
alt={komponentData.name}
|
||||
sx={{
|
||||
objectFit: "contain",
|
||||
borderRadius: 1,
|
||||
border: "1px solid #e0e0e0"
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
{index + 1}. {cleanProductName(komponentData.name)}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{komponent.count}x à {new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(komponentData.price)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
{formattedPrice}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Total price and savings display - only show when prices differ meaningfully */}
|
||||
{totalKomponentenPrice > product.price &&
|
||||
(totalKomponentenPrice - product.price >= 2 &&
|
||||
(totalKomponentenPrice - product.price) / product.price >= 0.02) && (
|
||||
<Box sx={{ mt: 3, pt: 2, borderTop: "2px solid #eee" }}>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1 }}>
|
||||
<Typography variant="h6">
|
||||
Einzelpreis gesamt:
|
||||
</Typography>
|
||||
<Typography variant="h6" sx={{ textDecoration: "line-through", color: "text.secondary" }}>
|
||||
{new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(totalKomponentenPrice)}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1 }}>
|
||||
<Typography variant="h6">
|
||||
Set-Preis:
|
||||
</Typography>
|
||||
<Typography variant="h6" color="primary" sx={{ fontWeight: "bold" }}>
|
||||
{new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(product.price)}
|
||||
</Typography>
|
||||
</Box>
|
||||
{totalSavings > 0 && (
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mt: 2, p: 2, backgroundColor: "#e8f5e8", borderRadius: 1 }}>
|
||||
<Typography variant="h6" color="success.main" sx={{ fontWeight: "bold" }}>
|
||||
Ihre Ersparnis:
|
||||
</Typography>
|
||||
<Typography variant="h6" color="success.main" sx={{ fontWeight: "bold" }}>
|
||||
{new Intl.NumberFormat("de-DE", {
|
||||
style: "currency",
|
||||
currency: "EUR",
|
||||
}).format(totalSavings)}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// Loading state
|
||||
<Box>
|
||||
{this.state.komponenten.map((komponent, index) => {
|
||||
// For loading state, we don't know if pricing will be shown, so show all borders
|
||||
return (
|
||||
<Box key={komponent.id} sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
py: 1,
|
||||
borderBottom: "1px solid #eee",
|
||||
minHeight: "70px" // Consistent height to prevent layout shifts
|
||||
}}>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
|
||||
<Box sx={{ width: 50, height: 50, flexShrink: 0, backgroundColor: "#f5f5f5", borderRadius: 1, border: "1px solid #e0e0e0" }}>
|
||||
{/* Empty placeholder for image */}
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body1">
|
||||
{index + 1}. Lädt Komponent-Details...
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{komponent.count}x
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||
-
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import Typography from '@mui/material/Typography';
|
||||
import Filter from './Filter.js';
|
||||
import { useParams, useSearchParams, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { setSessionSetting, removeSessionSetting, clearAllSessionSettings } from '../utils/sessionStorage.js';
|
||||
import { withI18n } from '../i18n/withTranslation.js';
|
||||
|
||||
const isNew = (neu) => neu && (new Date().getTime() - new Date(neu).getTime() < 30 * 24 * 60 * 60 * 1000);
|
||||
|
||||
@@ -93,14 +94,14 @@ class ProductFilters extends Component {
|
||||
}
|
||||
|
||||
_getAvailabilityValues = (products) => {
|
||||
const filters = [{id:1,name:'auf Lager'}];
|
||||
const filters = [{id:1,name: this.props.t ? this.props.t('product.inStock') : 'auf Lager'}];
|
||||
|
||||
for(const product of products){
|
||||
if(isNew(product.neu)){
|
||||
if(!filters.find(filter => filter.id == 2)) filters.push({id:2,name:'Neu'});
|
||||
if(!filters.find(filter => filter.id == 2)) filters.push({id:2,name: this.props.t ? this.props.t('product.new') : 'Neu'});
|
||||
}
|
||||
if(!product.available && product.incomingDate){
|
||||
if(!filters.find(filter => filter.id == 3)) filters.push({id:3,name:'Bald verfügbar'});
|
||||
if(!filters.find(filter => filter.id == 3)) filters.push({id:3,name: this.props.t ? this.props.t('product.comingSoon') : 'Bald verfügbar'});
|
||||
}
|
||||
}
|
||||
return filters
|
||||
@@ -193,7 +194,7 @@ class ProductFilters extends Component {
|
||||
|
||||
{this.props.products.length > 0 && (
|
||||
<><Filter
|
||||
title="Verfügbarkeit"
|
||||
title={this.props.t ? this.props.t('filters.availability') : 'Verfügbarkeit'}
|
||||
options={this.state.availabilityValues}
|
||||
searchParams={this.props.searchParams}
|
||||
products={this.props.products}
|
||||
@@ -236,7 +237,7 @@ class ProductFilters extends Component {
|
||||
{this.generateAttributeFilters()}
|
||||
|
||||
<Filter
|
||||
title="Hersteller"
|
||||
title={this.props.t ? this.props.t('filters.manufacturer') : 'Hersteller'}
|
||||
options={this.state.uniqueManufacturerArray}
|
||||
filterType="manufacturer"
|
||||
products={this.props.products}
|
||||
@@ -257,4 +258,4 @@ class ProductFilters extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(ProductFilters);
|
||||
export default withRouter(withI18n()(ProductFilters));
|
||||
@@ -11,6 +11,7 @@ import Chip from '@mui/material/Chip';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Product from './Product.js';
|
||||
import { removeSessionSetting } from '../utils/sessionStorage.js';
|
||||
import { withI18n } from '../i18n/withTranslation.js';
|
||||
|
||||
// Sort products by fuzzy similarity to their name/description
|
||||
function sortProductsByFuzzySimilarity(products, searchTerm) {
|
||||
@@ -141,12 +142,12 @@ class ProductList extends Component {
|
||||
onChange={this.handlePageChange}
|
||||
color="primary"
|
||||
size={"large"}
|
||||
siblingCount={window.innerWidth < 600 ? 0 : 1}
|
||||
boundaryCount={window.innerWidth < 600 ? 1 : 1}
|
||||
hideNextButton={false}
|
||||
hidePrevButton={false}
|
||||
showFirstButton={window.innerWidth >= 600}
|
||||
showLastButton={window.innerWidth >= 600}
|
||||
siblingCount={1}
|
||||
boundaryCount={1}
|
||||
hideNextButton={true}
|
||||
hidePrevButton={true}
|
||||
showFirstButton={false}
|
||||
showLastButton={false}
|
||||
sx={{
|
||||
'& .MuiPagination-ul': {
|
||||
flexWrap: 'nowrap',
|
||||
@@ -184,7 +185,7 @@ class ProductList extends Component {
|
||||
px: 2
|
||||
}}>
|
||||
<Typography variant="h6" color="text.secondary" sx={{ textAlign: 'center' }}>
|
||||
Entferne Filter um Produkte zu sehen
|
||||
{this.props.t ? this.props.t('product.removeFiltersToSee') : 'Entferne Filter um Produkte zu sehen'}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
@@ -327,13 +328,13 @@ class ProductList extends Component {
|
||||
minWidth: { xs: 120, sm: 140 }
|
||||
}}
|
||||
>
|
||||
<InputLabel id="sort-by-label">Sortierung</InputLabel>
|
||||
<InputLabel id="sort-by-label">{this.props.t ? this.props.t('filters.sorting') : 'Sortierung'}</InputLabel>
|
||||
<Select
|
||||
size="small"
|
||||
labelId="sort-by-label"
|
||||
value={(this.state.sortBy==='searchField')&&(window.currentSearchQuery)?this.state.sortBy:this.state.sortBy==='price-low-high'?this.state.sortBy:this.state.sortBy==='price-low-high'?this.state.sortBy:'name'}
|
||||
onChange={this.handleSortChange}
|
||||
label="Sortierung"
|
||||
label={this.props.t ? this.props.t('filters.sorting') : 'Sortierung'}
|
||||
MenuProps={{
|
||||
disableScrollLock: true,
|
||||
anchorOrigin: {
|
||||
@@ -353,10 +354,10 @@ class ProductList extends Component {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<MenuItem value="name">Name</MenuItem>
|
||||
{window.currentSearchQuery && <MenuItem value="searchField">Suchbegriff</MenuItem>}
|
||||
<MenuItem value="price-low-high">Preis: Niedrig zu Hoch</MenuItem>
|
||||
<MenuItem value="price-high-low">Preis: Hoch zu Niedrig</MenuItem>
|
||||
<MenuItem value="name">{this.props.t ? this.props.t('sorting.name') : 'Name'}</MenuItem>
|
||||
{window.currentSearchQuery && <MenuItem value="searchField">{this.props.t ? this.props.t('sorting.searchField') : 'Suchbegriff'}</MenuItem>}
|
||||
<MenuItem value="price-low-high">{this.props.t ? this.props.t('sorting.priceLowHigh') : 'Preis: Niedrig zu Hoch'}</MenuItem>
|
||||
<MenuItem value="price-high-low">{this.props.t ? this.props.t('sorting.priceHighLow') : 'Preis: Hoch zu Niedrig'}</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
@@ -368,12 +369,12 @@ class ProductList extends Component {
|
||||
minWidth: { xs: 80, sm: 100 }
|
||||
}}
|
||||
>
|
||||
<InputLabel id="products-per-page-label">pro Seite</InputLabel>
|
||||
<InputLabel id="products-per-page-label">{this.props.t ? this.props.t('filters.perPage') : 'pro Seite'}</InputLabel>
|
||||
<Select
|
||||
labelId="products-per-page-label"
|
||||
value={this.state.itemsPerPage}
|
||||
onChange={this.handleProductsPerPageChange}
|
||||
label="pro Seite"
|
||||
label={this.props.t ? this.props.t('filters.perPage') : 'pro Seite'}
|
||||
MenuProps={{
|
||||
disableScrollLock: true,
|
||||
anchorOrigin: {
|
||||
@@ -462,8 +463,8 @@ class ProductList extends Component {
|
||||
available={product.available}
|
||||
manufacturer={product.manufacturer}
|
||||
vat={product.vat}
|
||||
massMenge={product.massMenge}
|
||||
massEinheit={product.massEinheit}
|
||||
cGrundEinheit={product.cGrundEinheit}
|
||||
fGrundPreis={product.fGrundPreis}
|
||||
incoming={product.incomingDate}
|
||||
neu={product.neu}
|
||||
thc={product.thc}
|
||||
@@ -474,6 +475,7 @@ class ProductList extends Component {
|
||||
socketB={this.props.socketB}
|
||||
pictureList={product.pictureList}
|
||||
availableSupplier={product.availableSupplier}
|
||||
komponenten={product.komponenten}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
@@ -495,4 +497,4 @@ class ProductList extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default ProductList;
|
||||
export default withI18n()(ProductList);
|
||||
231
src/components/SharedCarousel.js
Normal file
231
src/components/SharedCarousel.js
Normal file
@@ -0,0 +1,231 @@
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import ChevronLeft from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRight from "@mui/icons-material/ChevronRight";
|
||||
import CategoryBox from "./CategoryBox.js";
|
||||
import SocketContext from "../contexts/SocketContext.js";
|
||||
import { useCarousel } from "../contexts/CarouselContext.js";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// Helper to process and set categories
|
||||
const processCategoryTree = (categoryTree) => {
|
||||
if (
|
||||
categoryTree &&
|
||||
categoryTree.id === 209 &&
|
||||
Array.isArray(categoryTree.children)
|
||||
) {
|
||||
return categoryTree.children;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// Check for cached data
|
||||
const getProductCache = () => {
|
||||
if (typeof window !== "undefined" && window.productCache) {
|
||||
return window.productCache;
|
||||
}
|
||||
if (
|
||||
typeof global !== "undefined" &&
|
||||
global.window &&
|
||||
global.window.productCache
|
||||
) {
|
||||
return global.window.productCache;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Initialize categories
|
||||
const initializeCategories = () => {
|
||||
const productCache = getProductCache();
|
||||
|
||||
if (productCache && productCache["categoryTree_209"]) {
|
||||
const cached = productCache["categoryTree_209"];
|
||||
if (cached.categoryTree) {
|
||||
return processCategoryTree(cached.categoryTree);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const SharedCarousel = () => {
|
||||
const { carouselRef, filteredCategories, setFilteredCategories, moveCarousel } = useCarousel();
|
||||
const context = useContext(SocketContext);
|
||||
const { t } = useTranslation();
|
||||
const [rootCategories, setRootCategories] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const initialCategories = initializeCategories();
|
||||
setRootCategories(initialCategories);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Only fetch from socket if we don't already have categories
|
||||
if (
|
||||
rootCategories.length === 0 &&
|
||||
context && context.socket && context.socket.connected &&
|
||||
typeof window !== "undefined"
|
||||
) {
|
||||
context.socket.emit("categoryList", { categoryId: 209 }, (response) => {
|
||||
if (response && response.categoryTree) {
|
||||
// Store in cache
|
||||
try {
|
||||
if (!window.productCache) window.productCache = {};
|
||||
window.productCache["categoryTree_209"] = {
|
||||
categoryTree: response.categoryTree,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
setRootCategories(response.categoryTree.children || []);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [context, context?.socket?.connected, rootCategories.length]);
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = rootCategories.filter(
|
||||
(cat) => cat.id !== 689 && cat.id !== 706
|
||||
);
|
||||
setFilteredCategories(filtered);
|
||||
}, [rootCategories, setFilteredCategories]);
|
||||
|
||||
// Create duplicated array for seamless scrolling
|
||||
const displayCategories = [...filteredCategories, ...filteredCategories];
|
||||
|
||||
if (filteredCategories.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h1"
|
||||
sx={{
|
||||
mb: 2,
|
||||
fontFamily: "SwashingtonCP",
|
||||
color: "primary.main",
|
||||
textAlign: "center",
|
||||
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
|
||||
}}
|
||||
>
|
||||
{t('navigation.categories')}
|
||||
</Typography>
|
||||
|
||||
<div
|
||||
className="carousel-wrapper"
|
||||
style={{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
padding: '0 20px',
|
||||
boxSizing: 'border-box'
|
||||
}}
|
||||
>
|
||||
{/* Left Arrow */}
|
||||
<IconButton
|
||||
onClick={() => moveCarousel("left")}
|
||||
aria-label="Vorherige Kategorien anzeigen"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '8px',
|
||||
transform: 'translateY(-50%)',
|
||||
zIndex: 1200,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
>
|
||||
<ChevronLeft />
|
||||
</IconButton>
|
||||
|
||||
{/* Right Arrow */}
|
||||
<IconButton
|
||||
onClick={() => moveCarousel("right")}
|
||||
aria-label="Nächste Kategorien anzeigen"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
right: '8px',
|
||||
transform: 'translateY(-50%)',
|
||||
zIndex: 1200,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
>
|
||||
<ChevronRight />
|
||||
</IconButton>
|
||||
|
||||
<div
|
||||
className="carousel-container"
|
||||
style={{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
padding: '20px 0',
|
||||
width: '100%',
|
||||
maxWidth: '1080px',
|
||||
margin: '0 auto',
|
||||
zIndex: 1,
|
||||
boxSizing: 'border-box'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="home-carousel-track"
|
||||
ref={carouselRef}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '16px',
|
||||
transition: 'none',
|
||||
alignItems: 'flex-start',
|
||||
width: 'fit-content',
|
||||
overflow: 'visible',
|
||||
position: 'relative',
|
||||
transform: 'translateX(0px)',
|
||||
margin: '0 auto'
|
||||
}}
|
||||
>
|
||||
{displayCategories.map((category, index) => (
|
||||
<div
|
||||
key={`${category.id}-${index}`}
|
||||
className="carousel-item"
|
||||
style={{
|
||||
flex: '0 0 130px',
|
||||
width: '130px',
|
||||
maxWidth: '130px',
|
||||
minWidth: '130px',
|
||||
height: '130px',
|
||||
maxHeight: '130px',
|
||||
minHeight: '130px',
|
||||
boxSizing: 'border-box',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<CategoryBox
|
||||
id={category.id}
|
||||
name={category.name}
|
||||
seoName={category.seoName}
|
||||
image={category.image}
|
||||
bgcolor={category.bgcolor}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SharedCarousel;
|
||||
@@ -10,7 +10,9 @@ import CloseIcon from '@mui/icons-material/Close';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import LoginComponent from '../LoginComponent.js';
|
||||
import CartDropdown from '../CartDropdown.js';
|
||||
import LanguageSwitcher from '../LanguageSwitcher.js';
|
||||
import { isUserLoggedIn } from '../LoginComponent.js';
|
||||
import { withI18n } from '../../i18n/withTranslation.js';
|
||||
|
||||
function getBadgeNumber() {
|
||||
let count = 0;
|
||||
@@ -116,14 +118,14 @@ class ButtonGroup extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { socket, navigate } = this.props;
|
||||
const { socket, navigate, t } = this.props;
|
||||
const { isCartOpen } = this.state;
|
||||
const cartItems = Array.isArray(window.cart) ? window.cart : [];
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', gap: { xs: 0.5, sm: 1 } }}>
|
||||
|
||||
|
||||
<LanguageSwitcher />
|
||||
<LoginComponent socket={socket} />
|
||||
|
||||
<IconButton
|
||||
@@ -164,7 +166,7 @@ class ButtonGroup extends Component {
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6">Warenkorb</Typography>
|
||||
<Typography variant="h6">{t ? t('cart.title') : 'Warenkorb'}</Typography>
|
||||
</Box>
|
||||
<Divider sx={{ mb: 2 }} />
|
||||
|
||||
@@ -173,7 +175,7 @@ class ButtonGroup extends Component {
|
||||
|
||||
if (isUserLoggedIn().isLoggedIn) {
|
||||
this.toggleCart(); // Close the cart drawer
|
||||
navigate('/profile');
|
||||
navigate('/profile#cart');
|
||||
} else if (window.openLoginDrawer) {
|
||||
window.openLoginDrawer(); // Call global function to open login drawer
|
||||
this.toggleCart(); // Close the cart drawer
|
||||
@@ -189,10 +191,11 @@ class ButtonGroup extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper for ButtonGroup to provide navigate function
|
||||
// Wrapper for ButtonGroup to provide navigate function and translations
|
||||
const ButtonGroupWithRouter = (props) => {
|
||||
const navigate = useNavigate();
|
||||
return <ButtonGroup {...props} navigate={navigate} />;
|
||||
const ButtonGroupWithTranslation = withI18n()(ButtonGroup);
|
||||
return <ButtonGroupWithTranslation {...props} navigate={navigate} />;
|
||||
};
|
||||
|
||||
export default ButtonGroupWithRouter;
|
||||
@@ -8,6 +8,7 @@ import { Link } from "react-router-dom";
|
||||
import HomeIcon from "@mui/icons-material/Home";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { withI18n } from "../../i18n/withTranslation.js";
|
||||
|
||||
class CategoryList extends Component {
|
||||
findCategoryById = (category, targetId) => {
|
||||
@@ -410,7 +411,7 @@ class CategoryList extends Component {
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
Startseite
|
||||
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
||||
</Box>
|
||||
{/* Thin text (positioned on top) */}
|
||||
<Box
|
||||
@@ -424,7 +425,7 @@ class CategoryList extends Component {
|
||||
zIndex: 1,
|
||||
}}
|
||||
>
|
||||
Startseite
|
||||
{this.props.t ? this.props.t('navigation.home') : 'Startseite'}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
@@ -595,7 +596,10 @@ class CategoryList extends Component {
|
||||
onClick={this.handleMobileMenuToggle}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={mobileMenuOpen ? "Kategorien schließen" : "Kategorien öffnen"}
|
||||
aria-label={this.props.t ?
|
||||
(mobileMenuOpen ? this.props.t('navigation.categoriesClose') : this.props.t('navigation.categoriesOpen')) :
|
||||
(mobileMenuOpen ? "Kategorien schließen" : "Kategorien öffnen")
|
||||
}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
@@ -607,7 +611,7 @@ class CategoryList extends Component {
|
||||
fontWeight: "bold",
|
||||
textShadow: "0 1px 2px rgba(0,0,0,0.3)"
|
||||
}}>
|
||||
Kategorien
|
||||
{this.props.t ? this.props.t('navigation.categories') : 'Kategorien'}
|
||||
</Typography>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
{mobileMenuOpen ? <CloseIcon /> : <MenuIcon />}
|
||||
@@ -628,4 +632,4 @@ class CategoryList extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default CategoryList;
|
||||
export default withI18n()(CategoryList);
|
||||
|
||||
@@ -8,7 +8,9 @@ import ListItem from "@mui/material/ListItem";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import SearchIcon from "@mui/icons-material/Search";
|
||||
import KeyboardReturnIcon from "@mui/icons-material/KeyboardReturn";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import SocketContext from "../../contexts/SocketContext.js";
|
||||
|
||||
@@ -184,6 +186,15 @@ const SearchBar = () => {
|
||||
}, 200);
|
||||
};
|
||||
|
||||
// Handle enter icon click
|
||||
const handleEnterClick = () => {
|
||||
delete window.currentSearchQuery;
|
||||
setShowSuggestions(false);
|
||||
if (searchQuery.trim()) {
|
||||
navigate(`/search?q=${encodeURIComponent(searchQuery)}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Clean up timers on unmount
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
@@ -244,9 +255,23 @@ const SearchBar = () => {
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
endAdornment: loadingSuggestions && (
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<CircularProgress size={16} />
|
||||
{loadingSuggestions && <CircularProgress size={16} />}
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={handleEnterClick}
|
||||
sx={{
|
||||
ml: loadingSuggestions ? 0.5 : 0,
|
||||
p: 0.5,
|
||||
color: "text.secondary",
|
||||
"&:hover": {
|
||||
color: "primary.main",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<KeyboardReturnIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
sx: { borderRadius: 2, bgcolor: "background.paper" },
|
||||
|
||||
@@ -6,6 +6,7 @@ import PaymentConfirmationDialog from "./PaymentConfirmationDialog.js";
|
||||
import OrderProcessingService from "./OrderProcessingService.js";
|
||||
import CheckoutValidation from "./CheckoutValidation.js";
|
||||
import SocketContext from "../../contexts/SocketContext.js";
|
||||
import { withI18n } from "../../i18n/index.js";
|
||||
|
||||
class CartTab extends Component {
|
||||
constructor(props) {
|
||||
@@ -51,9 +52,6 @@ class CartTab extends Component {
|
||||
showStripePayment: false,
|
||||
StripeComponent: null,
|
||||
isLoadingStripe: false,
|
||||
showMolliePayment: false,
|
||||
MollieComponent: null,
|
||||
isLoadingMollie: false,
|
||||
showPaymentConfirmation: false,
|
||||
orderCompleted: false,
|
||||
originalCartItems: []
|
||||
@@ -119,7 +117,7 @@ class CartTab extends Component {
|
||||
// Determine payment method - respect constraints
|
||||
let prefillPaymentMethod = template.payment_method || "wire";
|
||||
const paymentMethodMap = {
|
||||
"credit_card": "mollie",//stripe
|
||||
"credit_card": "mollie",/*stripe*/
|
||||
"bank_transfer": "wire",
|
||||
"cash_on_delivery": "onDelivery",
|
||||
"cash": "cash"
|
||||
@@ -322,27 +320,6 @@ class CartTab extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
loadMollieComponent = async () => {
|
||||
this.setState({ isLoadingMollie: true });
|
||||
|
||||
try {
|
||||
const { default: Mollie } = await import("../Mollie.js");
|
||||
this.setState({
|
||||
MollieComponent: Mollie,
|
||||
showMolliePayment: true,
|
||||
isCompletingOrder: false,
|
||||
isLoadingMollie: false,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to load Mollie component:", error);
|
||||
this.setState({
|
||||
isCompletingOrder: false,
|
||||
isLoadingMollie: false,
|
||||
completionError: "Failed to load payment component. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleCompleteOrder = () => {
|
||||
this.setState({ completionError: null }); // Clear previous errors
|
||||
|
||||
@@ -387,23 +364,38 @@ class CartTab extends Component {
|
||||
this.orderService.createStripeIntent(totalAmount, this.loadStripeComponent);
|
||||
return;
|
||||
}
|
||||
// Handle Mollie payment differently
|
||||
// Handle molllie payment differently
|
||||
if (paymentMethod === "mollie") {
|
||||
// Store the cart items used for Mollie payment in sessionStorage for later reference
|
||||
// Store the cart items used for mollie payment in sessionStorage for later reference
|
||||
try {
|
||||
sessionStorage.setItem('molliePaymentCart', JSON.stringify(cartItems));
|
||||
} catch (error) {
|
||||
console.error("Failed to store Mollie payment cart:", error);
|
||||
console.error("Failed to store mollie payment cart:", error);
|
||||
}
|
||||
|
||||
// Calculate total amount for Mollie
|
||||
// Calculate total amount for mollie
|
||||
const subtotal = cartItems.reduce(
|
||||
(total, item) => total + item.price * item.quantity,
|
||||
0
|
||||
);
|
||||
const totalAmount = Math.round((subtotal + deliveryCost) * 100); // Convert to cents
|
||||
const totalAmount = Math.round((subtotal + deliveryCost) * 100) / 100;
|
||||
|
||||
this.orderService.createMollieIntent(totalAmount, this.loadMollieComponent);
|
||||
// Prepare complete order data for Mollie intent creation
|
||||
const mollieOrderData = {
|
||||
amount: totalAmount,
|
||||
items: cartItems,
|
||||
invoiceAddress,
|
||||
deliveryAddress: useSameAddress ? invoiceAddress : deliveryAddress,
|
||||
deliveryMethod,
|
||||
paymentMethod: "mollie",
|
||||
deliveryCost,
|
||||
note,
|
||||
domain: window.location.origin,
|
||||
saveAddressForFuture,
|
||||
};
|
||||
|
||||
this.orderService.createMollieIntent(mollieOrderData);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -441,9 +433,6 @@ class CartTab extends Component {
|
||||
showStripePayment,
|
||||
StripeComponent,
|
||||
isLoadingStripe,
|
||||
showMolliePayment,
|
||||
MollieComponent,
|
||||
isLoadingMollie,
|
||||
showPaymentConfirmation,
|
||||
orderCompleted,
|
||||
} = this.state;
|
||||
@@ -480,7 +469,7 @@ class CartTab extends Component {
|
||||
<CartDropdown
|
||||
cartItems={cartItems}
|
||||
socket={this.context.socket}
|
||||
showDetailedSummary={showStripePayment || showMolliePayment}
|
||||
showDetailedSummary={showStripePayment}
|
||||
deliveryMethod={deliveryMethod}
|
||||
deliveryCost={deliveryCost}
|
||||
/>
|
||||
@@ -488,7 +477,7 @@ class CartTab extends Component {
|
||||
|
||||
{cartItems.length > 0 && (
|
||||
<Box sx={{ mt: 3 }}>
|
||||
{isLoadingStripe || isLoadingMollie ? (
|
||||
{isLoadingStripe ? (
|
||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||
<Typography variant="body1">
|
||||
Zahlungskomponente wird geladen...
|
||||
@@ -509,34 +498,14 @@ class CartTab extends Component {
|
||||
}
|
||||
}}
|
||||
>
|
||||
← Zurück zur Bestellung
|
||||
{this.props.t ? this.props.t('cart.backToOrder') : '← Zurück zur Bestellung'}
|
||||
</Button>
|
||||
</Box>
|
||||
<StripeComponent clientSecret={stripeClientSecret} />
|
||||
</>
|
||||
) : showMolliePayment && MollieComponent ? (
|
||||
<>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => this.setState({ showMolliePayment: false })}
|
||||
sx={{
|
||||
color: '#2e7d32',
|
||||
borderColor: '#2e7d32',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(46, 125, 50, 0.04)',
|
||||
borderColor: '#1b5e20'
|
||||
}
|
||||
}}
|
||||
>
|
||||
← Zurück zur Bestellung
|
||||
</Button>
|
||||
</Box>
|
||||
<MollieComponent />
|
||||
</>
|
||||
) : (
|
||||
<CheckoutForm
|
||||
paymentMethod={paymentMethod}
|
||||
paymentMethod={paymentMethod}
|
||||
invoiceAddress={invoiceAddress}
|
||||
deliveryAddress={deliveryAddress}
|
||||
useSameAddress={useSameAddress}
|
||||
@@ -544,7 +513,7 @@ class CartTab extends Component {
|
||||
addressFormErrors={addressFormErrors}
|
||||
termsAccepted={termsAccepted}
|
||||
note={note}
|
||||
deliveryMethod={deliveryMethod}
|
||||
deliveryMethod={deliveryMethod}
|
||||
hasStecklinge={hasStecklinge}
|
||||
isPickupOnly={isPickupOnly}
|
||||
deliveryCost={deliveryCost}
|
||||
@@ -573,4 +542,4 @@ class CartTab extends Component {
|
||||
// Set static contextType to access the socket
|
||||
CartTab.contextType = SocketContext;
|
||||
|
||||
export default CartTab;
|
||||
export default withI18n()(CartTab);
|
||||
|
||||
@@ -93,6 +93,7 @@ class CheckoutForm extends Component {
|
||||
deliveryMethod={deliveryMethod}
|
||||
onChange={onDeliveryMethodChange}
|
||||
isPickupOnly={isPickupOnly || hasStecklinge}
|
||||
cartItems={cartItems}
|
||||
/>
|
||||
|
||||
{(deliveryMethod === "DHL" || deliveryMethod === "DPD") && (
|
||||
|
||||
@@ -82,7 +82,7 @@ class CheckoutValidation {
|
||||
|
||||
// Prefer stripe when available and meets minimum amount
|
||||
if (deliveryMethod === "DHL" || deliveryMethod === "DPD" || deliveryMethod === "Abholung") {
|
||||
return "stripe";
|
||||
return "mollie";/*stripe*/
|
||||
}
|
||||
|
||||
// Fall back to wire transfer
|
||||
@@ -106,11 +106,21 @@ class CheckoutValidation {
|
||||
newPaymentMethod = "wire";
|
||||
}
|
||||
|
||||
// Allow mollie for DHL, DPD, and Abholung delivery methods, but check minimum amount
|
||||
if (deliveryMethod !== "DHL" && deliveryMethod !== "DPD" && deliveryMethod !== "Abholung" && paymentMethod === "mollie") {
|
||||
newPaymentMethod = "wire";
|
||||
}
|
||||
|
||||
// Check minimum amount for stripe payments
|
||||
if (paymentMethod === "stripe" && totalAmount < 0.50) {
|
||||
newPaymentMethod = "wire";
|
||||
}
|
||||
|
||||
// Check minimum amount for mollie payments
|
||||
if (paymentMethod === "mollie" && totalAmount < 0.50) {
|
||||
newPaymentMethod = "wire";
|
||||
}
|
||||
|
||||
if (deliveryMethod !== "Abholung" && paymentMethod === "cash") {
|
||||
newPaymentMethod = "wire";
|
||||
}
|
||||
|
||||
@@ -4,20 +4,27 @@ import Typography from '@mui/material/Typography';
|
||||
import Radio from '@mui/material/Radio';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
|
||||
const DeliveryMethodSelector = ({ deliveryMethod, onChange, isPickupOnly }) => {
|
||||
const DeliveryMethodSelector = ({ deliveryMethod, onChange, isPickupOnly, cartItems = [] }) => {
|
||||
// Calculate cart value for free shipping threshold
|
||||
const cartValue = cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
|
||||
const isFreeShipping = cartValue >= 100;
|
||||
const remainingForFreeShipping = Math.max(0, 100 - cartValue);
|
||||
|
||||
const deliveryOptions = [
|
||||
{
|
||||
id: 'DHL',
|
||||
name: 'DHL',
|
||||
description: isPickupOnly ? "nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können" : 'Standardversand',
|
||||
price: '6,99 €',
|
||||
description: isPickupOnly ? "nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können" :
|
||||
isFreeShipping ? 'Standardversand - KOSTENLOS ab 100€ Warenwert!' : 'Standardversand',
|
||||
price: isFreeShipping ? 'kostenlos' : '6,99 €',
|
||||
disabled: isPickupOnly
|
||||
},
|
||||
{
|
||||
id: 'DPD',
|
||||
name: 'DPD',
|
||||
description: isPickupOnly ? "nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können" : 'Standardversand',
|
||||
price: '4,90 €',
|
||||
description: isPickupOnly ? "nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können" :
|
||||
isFreeShipping ? 'Standardversand - KOSTENLOS ab 100€ Warenwert!' : 'Standardversand',
|
||||
price: isFreeShipping ? 'kostenlos' : '4,90 €',
|
||||
disabled: isPickupOnly
|
||||
},
|
||||
{
|
||||
@@ -114,6 +121,41 @@ const DeliveryMethodSelector = ({ deliveryMethod, onChange, isPickupOnly }) => {
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{/* Free shipping information */}
|
||||
{!isFreeShipping && remainingForFreeShipping > 0 && (
|
||||
<Box sx={{
|
||||
mt: 2,
|
||||
p: 2,
|
||||
backgroundColor: '#f0f8ff',
|
||||
borderRadius: 1,
|
||||
border: '1px solid #2196f3'
|
||||
}}>
|
||||
<Typography variant="body2" color="primary" sx={{ fontWeight: 'medium' }}>
|
||||
💡 Versandkostenfrei ab 100€ Warenwert!
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Noch {remainingForFreeShipping.toFixed(2).replace('.', ',')}€ für kostenlosen Versand hinzufügen.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isFreeShipping && (
|
||||
<Box sx={{
|
||||
mt: 2,
|
||||
p: 2,
|
||||
backgroundColor: '#e8f5e8',
|
||||
borderRadius: 1,
|
||||
border: '1px solid #2e7d32'
|
||||
}}>
|
||||
<Typography variant="body2" color="success.main" sx={{ fontWeight: 'medium' }}>
|
||||
🎉 Glückwunsch! Sie erhalten kostenlosen Versand!
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Ihr Warenkorb von {cartValue.toFixed(2).replace('.', ',')}€ qualifiziert sich für kostenlosen Versand.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -15,8 +15,11 @@ import {
|
||||
TableRow,
|
||||
Paper
|
||||
} from '@mui/material';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const OrderDetailsDialog = ({ open, onClose, order }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!order) {
|
||||
return null;
|
||||
}
|
||||
@@ -108,7 +111,7 @@ const OrderDetailsDialog = ({ open, onClose, order }) => {
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} />
|
||||
<TableCell align="right">
|
||||
<Typography fontWeight="bold">Gesamtnettopreis</Typography>
|
||||
<Typography fontWeight="bold">{t ? t('tax.totalNet') : 'Gesamtnettopreis'}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography fontWeight="bold">{currencyFormatter.format(vatCalculations.totalNet)}</Typography>
|
||||
@@ -117,21 +120,21 @@ const OrderDetailsDialog = ({ open, onClose, order }) => {
|
||||
{vatCalculations.vat7 > 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} />
|
||||
<TableCell align="right">7% Mehrwertsteuer</TableCell>
|
||||
<TableCell align="right">{t ? t('tax.vat7') : '7% Mehrwertsteuer'}</TableCell>
|
||||
<TableCell align="right">{currencyFormatter.format(vatCalculations.vat7)}</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{vatCalculations.vat19 > 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} />
|
||||
<TableCell align="right">19% Mehrwertsteuer</TableCell>
|
||||
<TableCell align="right">{t ? t('tax.vat19') : '19% Mehrwertsteuer'}</TableCell>
|
||||
<TableCell align="right">{currencyFormatter.format(vatCalculations.vat19)}</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow>
|
||||
<TableCell colSpan={2} />
|
||||
<TableCell align="right">
|
||||
<Typography fontWeight="bold">Zwischensumme</Typography>
|
||||
<Typography fontWeight="bold">{t ? t('tax.subtotal') : 'Zwischensumme'}</Typography>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<Typography fontWeight="bold">{currencyFormatter.format(subtotal)}</Typography>
|
||||
@@ -162,7 +165,7 @@ const OrderDetailsDialog = ({ open, onClose, order }) => {
|
||||
Bestellung stornieren
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={onClose}>Schließen</Button>
|
||||
<Button onClick={onClose}>{t ? t('common.close') : 'Schließen'}</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -49,18 +49,29 @@ class OrderProcessingService {
|
||||
waitForVerifyTokenAndProcessOrder() {
|
||||
// Check if window.cart is already populated (verifyToken already completed)
|
||||
if (Array.isArray(window.cart) && window.cart.length > 0) {
|
||||
this.processStripeOrderWithCart(window.cart);
|
||||
if (this.paymentCompletionData && this.paymentCompletionData.paymentType === 'mollie') {
|
||||
this.processMollieOrderWithCart(window.cart);
|
||||
} else {
|
||||
this.processStripeOrderWithCart(window.cart);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Listen for cart event which is dispatched after verifyToken completes
|
||||
this.verifyTokenHandler = () => {
|
||||
if (Array.isArray(window.cart) && window.cart.length > 0) {
|
||||
this.processStripeOrderWithCart([...window.cart]); // Copy the cart
|
||||
const cartCopy = [...window.cart]; // Copy the cart
|
||||
|
||||
// Clear window.cart after copying
|
||||
window.cart = [];
|
||||
window.dispatchEvent(new CustomEvent("cart"));
|
||||
|
||||
// Process based on payment type
|
||||
if (this.paymentCompletionData && this.paymentCompletionData.paymentType === 'mollie') {
|
||||
this.processMollieOrderWithCart(cartCopy);
|
||||
} else {
|
||||
this.processStripeOrderWithCart(cartCopy);
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
completionError: "Cart is empty. Please add items to your cart before placing an order."
|
||||
@@ -111,6 +122,21 @@ class OrderProcessingService {
|
||||
});
|
||||
}
|
||||
|
||||
processMollieOrderWithCart(cartItems) {
|
||||
// Clear timeout if it exists
|
||||
if (this.verifyTokenTimeout) {
|
||||
clearTimeout(this.verifyTokenTimeout);
|
||||
this.verifyTokenTimeout = null;
|
||||
}
|
||||
|
||||
// Store cart items in state and process order
|
||||
this.setState({
|
||||
originalCartItems: cartItems
|
||||
}, () => {
|
||||
this.processMollieOrder();
|
||||
});
|
||||
}
|
||||
|
||||
processStripeOrder() {
|
||||
// If no original cart items, don't process
|
||||
if (!this.getState().originalCartItems || this.getState().originalCartItems.length === 0) {
|
||||
@@ -205,6 +231,20 @@ class OrderProcessingService {
|
||||
});
|
||||
}
|
||||
|
||||
processMollieOrder() {
|
||||
// For Mollie payments, the backend handles order creation automatically
|
||||
// when payment is successful. We just need to show success state.
|
||||
this.setState({
|
||||
isCompletingOrder: false,
|
||||
orderCompleted: true,
|
||||
completionError: null,
|
||||
});
|
||||
|
||||
// Clear the cart since order was created by backend
|
||||
window.cart = [];
|
||||
window.dispatchEvent(new CustomEvent("cart"));
|
||||
}
|
||||
|
||||
// Process regular (non-Stripe) orders
|
||||
processRegularOrder(orderData) {
|
||||
const context = this.getContext();
|
||||
@@ -270,14 +310,44 @@ class OrderProcessingService {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create Mollie payment intent
|
||||
createMollieIntent(totalAmount, loadMollieComponent) {
|
||||
loadMollieComponent();
|
||||
createMollieIntent(mollieOrderData) {
|
||||
const context = this.getContext();
|
||||
if (context && context.socket && context.socket.connected) {
|
||||
context.socket.emit(
|
||||
"createMollieIntent",
|
||||
mollieOrderData,
|
||||
(response) => {
|
||||
if (response.success) {
|
||||
// Store pending payment info and redirect
|
||||
localStorage.setItem('pendingPayment', JSON.stringify({
|
||||
paymentId: response.paymentId,
|
||||
amount: mollieOrderData.amount,
|
||||
timestamp: Date.now()
|
||||
}));
|
||||
window.location.href = response.checkoutUrl;
|
||||
} else {
|
||||
console.error("Error:", response.error);
|
||||
this.setState({
|
||||
isCompletingOrder: false,
|
||||
completionError: response.error || "Failed to create Mollie payment intent. Please try again.",
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
console.error("Socket context not available");
|
||||
this.setState({
|
||||
isCompletingOrder: false,
|
||||
completionError: "Cannot connect to server. Please try again later.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate delivery cost
|
||||
getDeliveryCost() {
|
||||
const { deliveryMethod, paymentMethod } = this.getState();
|
||||
const { deliveryMethod, paymentMethod, cartItems } = this.getState();
|
||||
let cost = 0;
|
||||
|
||||
switch (deliveryMethod) {
|
||||
@@ -297,7 +367,16 @@ class OrderProcessingService {
|
||||
cost = 6.99;
|
||||
}
|
||||
|
||||
// Add onDelivery surcharge if selected
|
||||
// Check for free shipping threshold (>= 100€ cart value)
|
||||
// Free shipping applies to DHL, DPD, and Sperrgut deliveries when cart value >= 100€
|
||||
if (cartItems && Array.isArray(cartItems) && deliveryMethod !== "Abholung") {
|
||||
const cartValue = cartItems.reduce((total, item) => total + item.price * item.quantity, 0);
|
||||
if (cartValue >= 100) {
|
||||
cost = 0; // Free shipping for orders >= 100€
|
||||
}
|
||||
}
|
||||
|
||||
// Add onDelivery surcharge if selected (still applies even with free shipping)
|
||||
if (paymentMethod === "onDelivery") {
|
||||
cost += 8.99;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
import TableCell from '@mui/material/TableCell';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const OrderSummary = ({ deliveryCost, cartItems = [] }) => {
|
||||
const { t } = useTranslation();
|
||||
const currencyFormatter = new Intl.NumberFormat('de-DE', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
@@ -30,9 +32,9 @@ const OrderSummary = ({ deliveryCost, cartItems = [] }) => {
|
||||
return acc;
|
||||
}, { totalGross: 0, totalNet: 0, vat7: 0, vat19: 0 });
|
||||
|
||||
// Calculate shipping VAT (19% VAT for shipping costs)
|
||||
const shippingNetPrice = deliveryCost / (1 + 19 / 100);
|
||||
const shippingVat = deliveryCost - shippingNetPrice;
|
||||
// Calculate shipping VAT (19% VAT for shipping costs) - only if there are shipping costs
|
||||
const shippingNetPrice = deliveryCost > 0 ? deliveryCost / (1 + 19 / 100) : 0;
|
||||
const shippingVat = deliveryCost > 0 ? deliveryCost - shippingNetPrice : 0;
|
||||
|
||||
// Combine totals - add shipping VAT to the 19% VAT total
|
||||
const totalVat7 = cartVatCalculations.vat7;
|
||||
@@ -63,7 +65,7 @@ const OrderSummary = ({ deliveryCost, cartItems = [] }) => {
|
||||
)}
|
||||
{totalVat7 > 0 && (
|
||||
<TableRow>
|
||||
<TableCell>7% Mehrwertsteuer:</TableCell>
|
||||
<TableCell>{t ? t('tax.vat7') : '7% Mehrwertsteuer'}:</TableCell>
|
||||
<TableCell align="right">
|
||||
{currencyFormatter.format(totalVat7)}
|
||||
</TableCell>
|
||||
@@ -71,7 +73,7 @@ const OrderSummary = ({ deliveryCost, cartItems = [] }) => {
|
||||
)}
|
||||
{totalVat19 > 0 && (
|
||||
<TableRow>
|
||||
<TableCell>19% Mehrwertsteuer (inkl. Versand):</TableCell>
|
||||
<TableCell>{t ? t('tax.vat19WithShipping') : '19% Mehrwertsteuer (inkl. Versand)'}:</TableCell>
|
||||
<TableCell align="right">
|
||||
{currencyFormatter.format(totalVat19)}
|
||||
</TableCell>
|
||||
@@ -83,14 +85,23 @@ const OrderSummary = ({ deliveryCost, cartItems = [] }) => {
|
||||
{currencyFormatter.format(cartVatCalculations.totalGross)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{deliveryCost > 0 && (
|
||||
<TableRow>
|
||||
<TableCell sx={{ fontWeight: 'bold' }}>Versandkosten:</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>
|
||||
{currencyFormatter.format(deliveryCost)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow>
|
||||
<TableCell sx={{ fontWeight: 'bold' }}>
|
||||
Versandkosten:
|
||||
{deliveryCost === 0 && cartVatCalculations.totalGross < 100 && (
|
||||
<span style={{ color: '#2e7d32', fontSize: '0.8em', marginLeft: '4px' }}>
|
||||
(kostenlos ab 100€)
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold' }}>
|
||||
{deliveryCost === 0 ? (
|
||||
<span style={{ color: '#2e7d32' }}>kostenlos</span>
|
||||
) : (
|
||||
currencyFormatter.format(deliveryCost)
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow sx={{ borderTop: '1px solid #e0e0e0' }}>
|
||||
<TableCell sx={{ fontWeight: 'bold', pt: 2 }}>Gesamtsumme:</TableCell>
|
||||
<TableCell align="right" sx={{ fontWeight: 'bold', pt: 2 }}>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { Component } from "react";
|
||||
import { Box, Typography, Button } from "@mui/material";
|
||||
import { withI18n } from "../../i18n/withTranslation.js";
|
||||
|
||||
class PaymentConfirmationDialog extends Component {
|
||||
render() {
|
||||
@@ -28,24 +29,26 @@ class PaymentConfirmationDialog extends Component {
|
||||
color: paymentCompletionData.isSuccessful ? '#2e7d32' : '#d32f2f',
|
||||
fontWeight: 'bold'
|
||||
}}>
|
||||
{paymentCompletionData.isSuccessful ? 'Zahlung erfolgreich!' : 'Zahlung fehlgeschlagen'}
|
||||
{paymentCompletionData.isSuccessful ?
|
||||
(this.props.t ? this.props.t('payment.successful') : 'Zahlung erfolgreich!') :
|
||||
(this.props.t ? this.props.t('payment.failed') : 'Zahlung fehlgeschlagen')}
|
||||
</Typography>
|
||||
|
||||
{paymentCompletionData.isSuccessful ? (
|
||||
<>
|
||||
{orderCompleted ? (
|
||||
<Typography variant="body1" sx={{ mt: 2, color: '#2e7d32' }}>
|
||||
🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
{this.props.t ? this.props.t('payment.orderCompleted') : '🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.'}
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography variant="body1" sx={{ mt: 2, color: '#2e7d32' }}>
|
||||
Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
{this.props.t ? this.props.t('payment.orderProcessing') : 'Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.'}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Typography variant="body1" sx={{ mt: 2, color: '#d32f2f' }}>
|
||||
Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
{this.props.t ? this.props.t('payment.paymentError') : 'Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.'}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
@@ -75,7 +78,7 @@ class PaymentConfirmationDialog extends Component {
|
||||
}
|
||||
}}
|
||||
>
|
||||
Weiter einkaufen
|
||||
{this.props.t ? this.props.t('cart.continueShopping') : 'Weiter einkaufen'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onViewOrders}
|
||||
@@ -85,7 +88,7 @@ class PaymentConfirmationDialog extends Component {
|
||||
'&:hover': { bgcolor: '#1b5e20' }
|
||||
}}
|
||||
>
|
||||
Zu meinen Bestellungen
|
||||
{this.props.t ? this.props.t('payment.viewOrders') : 'Zu meinen Bestellungen'}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
@@ -94,4 +97,4 @@ class PaymentConfirmationDialog extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default PaymentConfirmationDialog;
|
||||
export default withI18n()(PaymentConfirmationDialog);
|
||||
@@ -24,7 +24,7 @@ const PaymentMethodSelector = ({ paymentMethod, onChange, deliveryMethod, onDeli
|
||||
// Handle delivery method changes - auto-switch to stripe when DHL/DPD is selected
|
||||
useEffect(() => {
|
||||
if ((deliveryMethod === "DHL" || deliveryMethod === "DPD") && paymentMethod === "cash") {
|
||||
handlePaymentMethodChange({ target: { value: "mollie" } });
|
||||
handlePaymentMethodChange({ target: { value: "mollie" /*stripe*/ } });
|
||||
}
|
||||
}, [deliveryMethod, paymentMethod, handlePaymentMethodChange]);
|
||||
|
||||
@@ -44,7 +44,7 @@ const PaymentMethodSelector = ({ paymentMethod, onChange, deliveryMethod, onDeli
|
||||
},
|
||||
/*{
|
||||
id: "stripe",
|
||||
name: "Karte oder Sofortüberweisung (Stripe)",
|
||||
name: "Karte oder Sofortüberweisung",
|
||||
description: totalAmount < 0.50 && totalAmount > 0
|
||||
? "Bezahlen Sie per Karte oder Sofortüberweisung (Mindestbetrag: 0,50 €)"
|
||||
: "Bezahlen Sie per Karte oder Sofortüberweisung",
|
||||
@@ -58,7 +58,7 @@ const PaymentMethodSelector = ({ paymentMethod, onChange, deliveryMethod, onDeli
|
||||
},*/
|
||||
{
|
||||
id: "mollie",
|
||||
name: "Karte oder Sofortüberweisung",
|
||||
name: "Karte, Sofortüberweisung, Apple Pay, Google Pay, PayPal",
|
||||
description: totalAmount < 0.50 && totalAmount > 0
|
||||
? "Bezahlen Sie per Karte oder Sofortüberweisung (Mindestbetrag: 0,50 €)"
|
||||
: "Bezahlen Sie per Karte oder Sofortüberweisung",
|
||||
|
||||
193
src/config.js
193
src/config.js
@@ -3,23 +3,200 @@ const config = {
|
||||
apiBaseUrl: "",
|
||||
googleClientId: "928121624463-jbgfdlgem22scs1k9c87ucg4ffvaik6o.apps.googleusercontent.com",
|
||||
stripePublishableKey: "pk_test_51R7lltRtpe3h1vwJzIrDb5bcEigTLBHrtqj9SiPX7FOEATSuD6oJmKc8xpNp49ShpGJZb2GShHIUqj4zlSIz4olj00ipOuOAnu",
|
||||
mollieProfileKey: "pfl_AtcRTimCff",
|
||||
|
||||
// SEO and Business Information
|
||||
siteName: "Growheads.de",
|
||||
brandName: "GrowHeads",
|
||||
currency: "EUR",
|
||||
language: "de-DE",
|
||||
language: "de-DE", // Will be updated dynamically based on i18n
|
||||
country: "DE",
|
||||
|
||||
// Shop Descriptions
|
||||
descriptions: {
|
||||
short: "GrowHeads - Online-Shop für Cannanis-Samen, Stecklinge und Gartenbedarf",
|
||||
long: "GrowHeads - Ihr Online-Shop für hochwertige Samen, Pflanzen und Gartenbedarf zur Cannabis Kultivierung. Entdecken Sie unser großes Sortiment an Saatgut, Pflanzen und Gartenzubehör für Ihren grünen Daumen."
|
||||
// Multilingual configurations
|
||||
languages: {
|
||||
de: {
|
||||
code: "de-DE",
|
||||
name: "Deutsch",
|
||||
shortName: "DE"
|
||||
},
|
||||
en: {
|
||||
code: "en-US",
|
||||
name: "English",
|
||||
shortName: "EN"
|
||||
},
|
||||
es: {
|
||||
code: "es-ES",
|
||||
name: "Español",
|
||||
shortName: "ES"
|
||||
},
|
||||
fr: {
|
||||
code: "fr-FR",
|
||||
name: "Français",
|
||||
shortName: "FR"
|
||||
},
|
||||
it: {
|
||||
code: "it-IT",
|
||||
name: "Italiano",
|
||||
shortName: "IT"
|
||||
},
|
||||
pl: {
|
||||
code: "pl-PL",
|
||||
name: "Polski",
|
||||
shortName: "PL"
|
||||
},
|
||||
hu: {
|
||||
code: "hu-HU",
|
||||
name: "Magyar",
|
||||
shortName: "HU"
|
||||
},
|
||||
sr: {
|
||||
code: "sr-RS",
|
||||
name: "Српски",
|
||||
shortName: "SR"
|
||||
},
|
||||
bg: {
|
||||
code: "bg-BG",
|
||||
name: "Български",
|
||||
shortName: "BG"
|
||||
},
|
||||
ru: {
|
||||
code: "ru-RU",
|
||||
name: "Русский",
|
||||
shortName: "RU"
|
||||
},
|
||||
uk: {
|
||||
code: "uk-UA",
|
||||
name: "Українська",
|
||||
shortName: "UK"
|
||||
},
|
||||
sk: {
|
||||
code: "sk-SK",
|
||||
name: "Slovenčina",
|
||||
shortName: "SK"
|
||||
},
|
||||
sl: {
|
||||
code: "sl-SI",
|
||||
name: "Slovenščina",
|
||||
shortName: "SI"
|
||||
},
|
||||
cs: {
|
||||
code: "cs-CZ",
|
||||
name: "Čeština",
|
||||
shortName: "CS"
|
||||
},
|
||||
ro: {
|
||||
code: "ro-RO",
|
||||
name: "Română",
|
||||
shortName: "RO"
|
||||
},
|
||||
hr: {
|
||||
code: "hr-HR",
|
||||
name: "Hrvatski",
|
||||
shortName: "HR"
|
||||
},
|
||||
sv: {
|
||||
code: "sv-SE",
|
||||
name: "Svenska",
|
||||
shortName: "SE"
|
||||
},
|
||||
tr: {
|
||||
code: "tr-TR",
|
||||
name: "Türkçe",
|
||||
shortName: "TR"
|
||||
},
|
||||
el: {
|
||||
code: "el-GR",
|
||||
name: "Ελληνικά",
|
||||
shortName: "GR"
|
||||
},
|
||||
ar: {
|
||||
code: "ar-EG",
|
||||
name: "العربية",
|
||||
shortName: "EG"
|
||||
},
|
||||
zh: {
|
||||
code: "zh-CN",
|
||||
name: "中文",
|
||||
shortName: "CN"
|
||||
}
|
||||
},
|
||||
|
||||
// Keywords
|
||||
keywords: "Seeds, Steckling, Cannabis, Biobizz, Growheads",
|
||||
// Shop Descriptions - Multilingual
|
||||
descriptions: {
|
||||
de: {
|
||||
short: "GrowHeads - Online-Shop für Cannabis-Samen, Stecklinge und Gartenbedarf",
|
||||
long: "GrowHeads - Ihr Online-Shop für hochwertige Samen, Pflanzen und Gartenbedarf zur Cannabis Kultivierung. Entdecken Sie unser großes Sortiment an Saatgut, Pflanzen und Gartenzubehör für Ihren grünen Daumen."
|
||||
},
|
||||
en: {
|
||||
short: "GrowHeads - Online Shop for Cannabis Seeds, Cuttings and Garden Supplies",
|
||||
long: "GrowHeads - Your online shop for high-quality seeds, plants and garden supplies for cannabis cultivation. Discover our large assortment of seeds, plants and garden accessories for your green thumb."
|
||||
},
|
||||
es: {
|
||||
short: "GrowHeads - Tienda Online de Semillas de Cannabis, Esquejes y Suministros de Jardín",
|
||||
long: "GrowHeads - Tu tienda online de semillas, plantas y suministros de jardín de alta calidad para el cultivo de cannabis. Descubre nuestro gran surtido de semillas, plantas y accesorios de jardín para tu pulgar verde."
|
||||
},
|
||||
fr: {
|
||||
short: "GrowHeads - Boutique en ligne de Graines de Cannabis, Boutures et Fournitures de Jardinage",
|
||||
long: "GrowHeads - Votre boutique en ligne pour des graines, plantes et fournitures de jardinage de haute qualité pour la culture du cannabis. Découvrez notre large assortiment de graines, plantes et accessoires de jardinage pour votre pouce vert."
|
||||
},
|
||||
it: {
|
||||
short: "GrowHeads - Negozio Online di Semi di Cannabis, Talee e Forniture da Giardino",
|
||||
long: "GrowHeads - Il tuo negozio online per semi, piante e forniture da giardino di alta qualità per la coltivazione di cannabis. Scopri il nostro vasto assortimento di semi, piante e accessori da giardino per il tuo pollice verde."
|
||||
},
|
||||
pl: {
|
||||
short: "GrowHeads - Sklep Online z Nasionami Konopi, Sadzonkami i Artykułami Ogrodniczymi",
|
||||
long: "GrowHeads - Twój sklep online z wysokiej jakości nasionami, roślinami i artykułami ogrodniczymi do uprawy konopi. Odkryj nasz duży asortyment nasion, roślin i akcesoriów ogrodniczych dla Twojego zielonego kciuka."
|
||||
},
|
||||
hu: {
|
||||
short: "GrowHeads - Online Bolt Kannabisz Magokhoz, Dugványokhoz és Kerti Kellékekhez",
|
||||
long: "GrowHeads - Az Ön online boltja minőségi magokhoz, növényekhez és kerti kellékekhez a kannabisz termesztéshez. Fedezze fel nagy választékunkat magokból, növényekből és kerti kiegészítőkből a zöld hüvelykujjához."
|
||||
},
|
||||
sr: {
|
||||
short: "GrowHeads - Онлајн продавница за семена канабиса, резнице и вртларски прибор",
|
||||
long: "GrowHeads - Ваша онлајн продавница за висококвалитетна семена, биљке и вртларски прибор за узгајање канабиса. Откријте наш велики асортиман семена, биљака и вртларских додатака за ваш зелени палац."
|
||||
},
|
||||
bg: {
|
||||
short: "GrowHeads - Онлайн магазин за семена на канабис, резници и градински принадлежности",
|
||||
long: "GrowHeads - Вашият онлайн магазин за висококачествени семена, растения и градински принадлежности за отглеждане на канабис. Открийте нашия голям асортимент от семена, растения и градински аксесоари за вашия зелен палец."
|
||||
},
|
||||
ru: {
|
||||
short: "GrowHeads - Интернет-магазин семян каннабиса, черенков и садовых принадлежностей",
|
||||
long: "GrowHeads - Ваш интернет-магазин высококачественных семян, растений и садовых принадлежностей для выращивания каннабиса. Откройте для себя наш большой ассортимент семян, растений и садовых аксессуаров для вашего зеленого пальца."
|
||||
},
|
||||
uk: {
|
||||
short: "GrowHeads - Інтернет-магазин насіння канабісу, живців та садових приладдя",
|
||||
long: "GrowHeads - Ваш інтернет-магазин високоякісного насіння, рослин та садових приладдя для вирощування канабісу. Відкрийте для себе наш великий асортимент насіння, рослин та садових аксесуарів для вашого зеленого пальця."
|
||||
},
|
||||
sk: {
|
||||
short: "GrowHeads - Online obchod so semenami konopy, sadenicami a záhradnými potrebami",
|
||||
long: "GrowHeads - Váš online obchod s vysoko kvalitnými semenami, rastlinami a záhradnými potrebami na pestovanie konopy. Objavte náš veľký sortiment semien, rastlín a záhradných doplnkov pre váš zelený palec."
|
||||
},
|
||||
cs: {
|
||||
short: "GrowHeads - Online obchod se semeny konopí, sazenicemi a zahradními potřebami",
|
||||
long: "GrowHeads - Váš online obchod s vysoce kvalitními semeny, rostlinami a zahradními potřebami pro pěstování konopí. Objevte náš velký sortiment semen, rostlin a zahradních doplňků pro váš zelený palec."
|
||||
},
|
||||
ro: {
|
||||
short: "GrowHeads - Magazin Online de Semințe de Cannabis, Butași și Articole de Grădinărit",
|
||||
long: "GrowHeads - Magazinul dumneavoastră online pentru semințe, plante și articole de grădinărit de înaltă calitate pentru cultivarea de cannabis. Descoperiți sortimentul nostru mare de semințe, plante și accesorii de grădinărit pentru degetul verde."
|
||||
}
|
||||
},
|
||||
|
||||
// Keywords - Multilingual
|
||||
keywords: {
|
||||
de: "Seeds, Steckling, Cannabis, Biobizz, Growheads",
|
||||
en: "Seeds, Cuttings, Cannabis, Biobizz, Growheads",
|
||||
es: "Semillas, Esquejes, Cannabis, Biobizz, Growheads",
|
||||
fr: "Graines, Boutures, Cannabis, Biobizz, Growheads",
|
||||
it: "Semi, Talee, Cannabis, Biobizz, Growheads",
|
||||
pl: "Nasiona, Sadzonki, Konopie, Biobizz, Growheads",
|
||||
hu: "Magok, Dugványok, Kannabisz, Biobizz, Growheads",
|
||||
sr: "Семена, Резнице, Канабис, Biobizz, Growheads",
|
||||
bg: "Семена, Резници, Канабис, Biobizz, Growheads",
|
||||
ru: "Семена, Черенки, Каннабис, Biobizz, Growheads",
|
||||
uk: "Насіння, Живці, Канабіс, Biobizz, Growheads",
|
||||
sk: "Semená, Sadenky, Konope, Biobizz, Growheads",
|
||||
cs: "Semena, Sazenice, Konopí, Biobizz, Growheads",
|
||||
ro: "Semințe, Butași, Cannabis, Biobizz, Growheads"
|
||||
},
|
||||
|
||||
// Shipping
|
||||
shipping: {
|
||||
|
||||
225
src/contexts/CarouselContext.js
Normal file
225
src/contexts/CarouselContext.js
Normal file
@@ -0,0 +1,225 @@
|
||||
import React, { createContext, useContext, useRef, useEffect, useState } from 'react';
|
||||
|
||||
const CarouselContext = createContext();
|
||||
|
||||
export const useCarousel = () => {
|
||||
const context = useContext(CarouselContext);
|
||||
if (!context) {
|
||||
throw new Error('useCarousel must be used within a CarouselProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
export const CarouselProvider = ({ children }) => {
|
||||
const carouselRef = useRef(null);
|
||||
const scrollPositionRef = useRef(0);
|
||||
const animationIdRef = useRef(null);
|
||||
const isPausedRef = useRef(false);
|
||||
const resumeTimeoutRef = useRef(null);
|
||||
|
||||
const [filteredCategories, setFilteredCategories] = useState([]);
|
||||
|
||||
// Initialize refs properly
|
||||
useEffect(() => {
|
||||
isPausedRef.current = false;
|
||||
scrollPositionRef.current = 0;
|
||||
}, []);
|
||||
|
||||
// Auto-scroll effect
|
||||
useEffect(() => {
|
||||
if (filteredCategories.length === 0) return;
|
||||
|
||||
const startAnimation = () => {
|
||||
if (!carouselRef.current) {
|
||||
return false;
|
||||
}
|
||||
|
||||
isPausedRef.current = false;
|
||||
|
||||
const itemWidth = 146; // 130px + 16px gap
|
||||
const totalWidth = filteredCategories.length * itemWidth;
|
||||
|
||||
const animate = () => {
|
||||
if (!animationIdRef.current || isPausedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollPositionRef.current += 0.5;
|
||||
|
||||
if (scrollPositionRef.current >= totalWidth) {
|
||||
scrollPositionRef.current = 0;
|
||||
}
|
||||
|
||||
if (carouselRef.current) {
|
||||
const transform = `translateX(-${scrollPositionRef.current}px)`;
|
||||
carouselRef.current.style.transform = transform;
|
||||
}
|
||||
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
if (!isPausedRef.current) {
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Try immediately, then with increasing delays
|
||||
if (!startAnimation()) {
|
||||
const timeout1 = setTimeout(() => {
|
||||
if (!startAnimation()) {
|
||||
const timeout2 = setTimeout(() => {
|
||||
if (!startAnimation()) {
|
||||
const timeout3 = setTimeout(startAnimation, 2000);
|
||||
return () => clearTimeout(timeout3);
|
||||
}
|
||||
}, 1000);
|
||||
return () => clearTimeout(timeout2);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
isPausedRef.current = true;
|
||||
clearTimeout(timeout1);
|
||||
if (animationIdRef.current) {
|
||||
cancelAnimationFrame(animationIdRef.current);
|
||||
}
|
||||
if (resumeTimeoutRef.current) {
|
||||
clearTimeout(resumeTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return () => {
|
||||
isPausedRef.current = true;
|
||||
if (animationIdRef.current) {
|
||||
cancelAnimationFrame(animationIdRef.current);
|
||||
}
|
||||
if (resumeTimeoutRef.current) {
|
||||
clearTimeout(resumeTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [filteredCategories]);
|
||||
|
||||
// Additional effect for when ref becomes available
|
||||
useEffect(() => {
|
||||
if (filteredCategories.length > 0 && carouselRef.current && !animationIdRef.current) {
|
||||
isPausedRef.current = false;
|
||||
|
||||
const itemWidth = 146;
|
||||
const totalWidth = filteredCategories.length * itemWidth;
|
||||
|
||||
const animate = () => {
|
||||
if (!animationIdRef.current || isPausedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollPositionRef.current += 0.5;
|
||||
|
||||
if (scrollPositionRef.current >= totalWidth) {
|
||||
scrollPositionRef.current = 0;
|
||||
}
|
||||
|
||||
if (carouselRef.current) {
|
||||
const transform = `translateX(-${scrollPositionRef.current}px)`;
|
||||
carouselRef.current.style.transform = transform;
|
||||
}
|
||||
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
if (!isPausedRef.current) {
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Manual navigation
|
||||
const moveCarousel = (direction) => {
|
||||
if (!carouselRef.current) return;
|
||||
|
||||
// Pause auto-scroll
|
||||
isPausedRef.current = true;
|
||||
if (animationIdRef.current) {
|
||||
cancelAnimationFrame(animationIdRef.current);
|
||||
animationIdRef.current = null;
|
||||
}
|
||||
|
||||
const itemWidth = 146;
|
||||
const moveAmount = itemWidth * 3;
|
||||
const totalWidth = filteredCategories.length * itemWidth;
|
||||
|
||||
if (direction === "left") {
|
||||
scrollPositionRef.current -= moveAmount;
|
||||
if (scrollPositionRef.current < 0) {
|
||||
scrollPositionRef.current = totalWidth + scrollPositionRef.current;
|
||||
}
|
||||
} else {
|
||||
scrollPositionRef.current += moveAmount;
|
||||
if (scrollPositionRef.current >= totalWidth) {
|
||||
scrollPositionRef.current = scrollPositionRef.current % totalWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply smooth transition
|
||||
carouselRef.current.style.transition = "transform 0.5s ease-in-out";
|
||||
carouselRef.current.style.transform = `translateX(-${scrollPositionRef.current}px)`;
|
||||
|
||||
// Remove transition after animation
|
||||
setTimeout(() => {
|
||||
if (carouselRef.current) {
|
||||
carouselRef.current.style.transition = "none";
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// Clear existing timeout
|
||||
if (resumeTimeoutRef.current) {
|
||||
clearTimeout(resumeTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Resume auto-scroll after 3 seconds
|
||||
resumeTimeoutRef.current = setTimeout(() => {
|
||||
isPausedRef.current = false;
|
||||
|
||||
const animate = () => {
|
||||
if (!animationIdRef.current || isPausedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollPositionRef.current += 0.5;
|
||||
|
||||
if (scrollPositionRef.current >= totalWidth) {
|
||||
scrollPositionRef.current = 0;
|
||||
}
|
||||
|
||||
if (carouselRef.current) {
|
||||
carouselRef.current.style.transform = `translateX(-${scrollPositionRef.current}px)`;
|
||||
}
|
||||
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const value = {
|
||||
carouselRef,
|
||||
scrollPositionRef,
|
||||
animationIdRef,
|
||||
isPausedRef,
|
||||
resumeTimeoutRef,
|
||||
filteredCategories,
|
||||
setFilteredCategories,
|
||||
moveCarousel
|
||||
};
|
||||
|
||||
return (
|
||||
<CarouselContext.Provider value={value}>
|
||||
{children}
|
||||
</CarouselContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default CarouselContext;
|
||||
129
src/i18n/index.js
Normal file
129
src/i18n/index.js
Normal file
@@ -0,0 +1,129 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
// Import all translation files
|
||||
import translationDE from './locales/de/translation.js';
|
||||
import translationEN from './locales/en/translation.js';
|
||||
import translationAR from './locales/ar/translation.js';
|
||||
import translationBG from './locales/bg/translation.js';
|
||||
import translationCS from './locales/cs/translation.js';
|
||||
import translationEL from './locales/el/translation.js';
|
||||
import translationES from './locales/es/translation.js';
|
||||
import translationFR from './locales/fr/translation.js';
|
||||
import translationHR from './locales/hr/translation.js';
|
||||
import translationHU from './locales/hu/translation.js';
|
||||
import translationIT from './locales/it/translation.js';
|
||||
import translationPL from './locales/pl/translation.js';
|
||||
import translationRO from './locales/ro/translation.js';
|
||||
import translationRU from './locales/ru/translation.js';
|
||||
import translationSK from './locales/sk/translation.js';
|
||||
import translationSL from './locales/sl/translation.js';
|
||||
import translationSR from './locales/sr/translation.js';
|
||||
import translationSV from './locales/sv/translation.js';
|
||||
import translationTR from './locales/tr/translation.js';
|
||||
import translationUK from './locales/uk/translation.js';
|
||||
import translationZH from './locales/zh/translation.js';
|
||||
|
||||
const resources = {
|
||||
de: {
|
||||
translation: translationDE
|
||||
},
|
||||
en: {
|
||||
translation: translationEN
|
||||
},
|
||||
ar: {
|
||||
translation: translationAR
|
||||
},
|
||||
bg: {
|
||||
translation: translationBG
|
||||
},
|
||||
cs: {
|
||||
translation: translationCS
|
||||
},
|
||||
el: {
|
||||
translation: translationEL
|
||||
},
|
||||
es: {
|
||||
translation: translationES
|
||||
},
|
||||
fr: {
|
||||
translation: translationFR
|
||||
},
|
||||
hr: {
|
||||
translation: translationHR
|
||||
},
|
||||
hu: {
|
||||
translation: translationHU
|
||||
},
|
||||
it: {
|
||||
translation: translationIT
|
||||
},
|
||||
pl: {
|
||||
translation: translationPL
|
||||
},
|
||||
ro: {
|
||||
translation: translationRO
|
||||
},
|
||||
ru: {
|
||||
translation: translationRU
|
||||
},
|
||||
sk: {
|
||||
translation: translationSK
|
||||
},
|
||||
sl: {
|
||||
translation: translationSL
|
||||
},
|
||||
sr: {
|
||||
translation: translationSR
|
||||
},
|
||||
sv: {
|
||||
translation: translationSV
|
||||
},
|
||||
tr: {
|
||||
translation: translationTR
|
||||
},
|
||||
uk: {
|
||||
translation: translationUK
|
||||
},
|
||||
zh: {
|
||||
translation: translationZH
|
||||
}
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: 'de', // German as fallback since it's your primary language
|
||||
lng: 'de', // Default language
|
||||
debug: process.env.NODE_ENV === 'development',
|
||||
|
||||
// Language detection options
|
||||
detection: {
|
||||
// Order of language detection methods
|
||||
order: ['localStorage', 'navigator', 'htmlTag'],
|
||||
// Cache the language selection
|
||||
caches: ['localStorage'],
|
||||
// Check for language in localStorage
|
||||
lookupLocalStorage: 'i18nextLng'
|
||||
},
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false // React already escapes values
|
||||
},
|
||||
|
||||
// Namespace configuration
|
||||
defaultNS: 'translation',
|
||||
|
||||
// React-specific options
|
||||
react: {
|
||||
useSuspense: false // Disable suspense for class components compatibility
|
||||
}
|
||||
});
|
||||
|
||||
// Export withI18n and other utilities for easy access
|
||||
export { withI18n, withTranslation, withLanguage, LanguageContext, LanguageProvider } from './withTranslation.js';
|
||||
|
||||
export default i18n;
|
||||
231
src/i18n/locales/ar/translation.js
Normal file
231
src/i18n/locales/ar/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "ar-EG" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "الرئيسية", // Startseite
|
||||
"aktionen": "العروض", // Aktionen
|
||||
"filiale": "الفرع", // Filiale
|
||||
"categories": "الفئات", // Kategorien
|
||||
"categoriesOpen": "فتح الفئات", // Kategorien öffnen
|
||||
"categoriesClose": "إغلاق الفئات", // Kategorien schließen
|
||||
"otherCategories": "فئات أخرى" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "تسجيل الدخول", // Anmelden
|
||||
"register": "تسجيل", // Registrieren
|
||||
"logout": "تسجيل خروج", // Abmelden
|
||||
"profile": "الملف الشخصي", // Profil
|
||||
"email": "البريد الإلكتروني", // E-Mail
|
||||
"password": "كلمة المرور", // Passwort
|
||||
"confirmPassword": "تأكيد كلمة المرور", // Passwort bestätigen
|
||||
"forgotPassword": "هل نسيت كلمة المرور؟", // Passwort vergessen?
|
||||
"loginWithGoogle": "تسجيل الدخول باستخدام جوجل", // Mit Google anmelden
|
||||
"or": "أو", // ODER
|
||||
"privacyAccept": "بالضغط على \"تسجيل الدخول باستخدام جوجل\" أوافق على", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "سياسة الخصوصية", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "يجب أن تكون كلمة المرور 8 أحرف على الأقل", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "يجب أن تكون كلمة المرور الجديدة 8 أحرف على الأقل", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "الملف الشخصي", // Profil
|
||||
"checkout": "إتمام الشراء", // Bestellabschluss
|
||||
"orders": "الطلبات", // Bestellungen
|
||||
"settings": "الإعدادات", // Einstellungen
|
||||
"adminDashboard": "لوحة تحكم المسؤول", // Admin Dashboard
|
||||
"adminUsers": "مستخدمو المسؤول" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "عربة التسوق", // Warenkorb
|
||||
"empty": "فارغة", // leer
|
||||
"addToCart": "أضف إلى العربة", // In den Korb
|
||||
"preorderCutting": "اطلب القطع مسبقًا", // Als Steckling vorbestellen
|
||||
"continueShopping": "متابعة التسوق", // Weiter einkaufen
|
||||
"proceedToCheckout": "المتابعة إلى الدفع", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {منتج} other {منتجات}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "إزالة من العربة", // Aus dem Warenkorb entfernen
|
||||
"openCart": "افتح العربة", // Warenkorb öffnen
|
||||
"availableFrom": "متوفر من {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← العودة إلى الطلب", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "مزامنة العربة", // Warenkorb-Synchronisierung
|
||||
"description": "لديك عربة محفوظة في حسابك. يرجى اختيار كيفية المتابعة:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "حذف عربة الخادم", // Server-Warenkorb löschen
|
||||
"useServer": "استخدام عربة الخادم", // Server-Warenkorb übernehmen
|
||||
"merge": "دمج العربات", // Warenkörbe zusammenführen
|
||||
"currentCart": "عربتك الحالية", // Ihr aktueller Warenkorb
|
||||
"serverCart": "العربة المحفوظة في ملفك الشخصي" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "جارٍ تحميل المنتج...", // Produkt wird geladen...
|
||||
"notFound": "المنتج غير موجود", // Produkt nicht gefunden
|
||||
"notFoundDescription": "المنتج الذي تبحث عنه غير موجود أو تم إزالته.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "العودة إلى الرئيسية", // Zurück zur Startseite
|
||||
"error": "خطأ", // Fehler
|
||||
"articleNumber": "رقم المنتج", // Artikelnummer
|
||||
"manufacturer": "الشركة المصنعة", // Hersteller
|
||||
"inclVat": "شامل {{vat}}% ضريبة القيمة المضافة", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "جديد", // Neu
|
||||
"arriving": "الوصول:", // Ankunft:
|
||||
"inclVatFooter": "شامل {{vat}}% ضريبة القيمة المضافة,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "التوفر", // Verfügbarkeit
|
||||
"inStock": "متوفر في المخزون", // auf Lager
|
||||
"comingSoon": "قريبًا", // Bald verfügbar
|
||||
"deliveryTime": "مدة التوصيل", // Lieferzeit
|
||||
"inclShort": "شامل", // inkl.
|
||||
"vatShort": "ضريبة القيمة المضافة", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 منتجات", // 0 Produkte
|
||||
"oneProduct": "منتج واحد", // 1 Produkt
|
||||
"multipleProducts": "{{count}} منتجات", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} من {{total}} منتجات", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} من منتج واحد" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "أزل الفلاتر لرؤية المنتجات", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "غير متوفر في المخزون", // Out of Stock
|
||||
"fromXProducts": "من {{count}} منتجات" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "يمكنك أن تسألني عن أنواع القنب...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "جارٍ التسجيل...", // Aufnahme läuft...
|
||||
"searchProducts": "ابحث عن المنتجات..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "الاسم", // Name
|
||||
"searchField": "مصطلح البحث", // Suchbegriff
|
||||
"priceLowHigh": "السعر: من الأقل للأعلى", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "السعر: من الأعلى للأقل" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "تمت القراءة والموافقة" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "بضائع ضخمة", // Sperrgut
|
||||
"pickup": "الاستلام من الفرع" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "الشحن العادي", // Standardversand
|
||||
"standardFree": "الشحن العادي - مجاني للطلبات بقيمة 100€ أو أكثر!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "غير قابل للاختيار لأن عنصرًا واحدًا أو أكثر يمكن استلامه فقط", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "للعناصر الكبيرة والثقيلة" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "مجاني", // kostenlos
|
||||
"freeFrom100": "(مجاني من 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6.99€", // 6,99 €
|
||||
"dpd": "4.90€", // 4,90 €
|
||||
"sperrgut": "28.99€" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "مدة التوصيل: 14 يومًا", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "مدة التوصيل: 2-3 أيام", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "مدة التوصيل: 7-9 أيام" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "عنوان الفاتورة", // Rechnungsadresse
|
||||
"deliveryAddress": "عنوان التوصيل", // Lieferadresse
|
||||
"saveForFuture": "حفظ للطلبات المستقبلية", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "لأي تاريخ تريد استلام القطع؟", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "ملاحظة", // Anmerkung
|
||||
"sameAddress": "عنوان التوصيل هو نفسه عنوان الفاتورة", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "لقد قرأت الشروط والأحكام، سياسة الخصوصية، ومعلومات حق الانسحاب" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "تم الدفع بنجاح!", // Zahlung erfolgreich!
|
||||
"failed": "فشل الدفع", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 تم إكمال طلبك بنجاح! يمكنك الآن عرض طلباتك.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "تمت معالجة دفعتك بنجاح. سيتم إكمال الطلب تلقائيًا.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "لم يتمكن من معالجة دفعتك. يرجى المحاولة مرة أخرى أو اختيار طريقة دفع أخرى.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "عرض طلباتي" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "الترتيب", // Sortierung
|
||||
"perPage": "لكل صفحة", // pro Seite
|
||||
"availability": "التوفر", // Verfügbarkeit
|
||||
"manufacturer": "الشركة المصنعة" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "ضريبة القيمة المضافة", // Mehrwertsteuer
|
||||
"vat7": "7% ضريبة القيمة المضافة", // 7% Mehrwertsteuer
|
||||
"vat19": "19% ضريبة القيمة المضافة", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% ضريبة القيمة المضافة (شاملة الشحن)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "إجمالي السعر الصافي", // Gesamtnettopreis
|
||||
"totalGross": "إجمالي السعر الإجمالي بدون الشحن", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "المجموع الفرعي" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "السبت 11-19", // Sa 11-19
|
||||
"address": "شارع تراشنبرجر 14 - دريسدن", // Trachenberger Straße 14 - Dresden
|
||||
"location": "بين محطة بيسشن وميدان تراشنبرجر", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* جميع الأسعار تشمل ضريبة القيمة المضافة القانونية، بالإضافة إلى الشحن", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "الخصوصية", // Datenschutz
|
||||
"agb": "الشروط والأحكام", // AGB
|
||||
"sitemap": "خريطة الموقع", // Sitemap
|
||||
"impressum": "معلومات قانونية", // Impressum
|
||||
"batteriegesetzhinweise": "ملاحظات قانون البطاريات", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "حق الانسحاب" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "بذور وقطع قنب ممتازة", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "العروض والخصومات الحالية", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "فرعنا في دريسدن" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "بذور", // Seeds
|
||||
"stecklinge": "قطع", // Stecklinge
|
||||
"oilPress": "استعارة مكبس الزيت", // Ölpresse ausleihen
|
||||
"thcTest": "اختبار THC", // THC Test
|
||||
"address1": "شارع تراشنبرجر 14", // Trachenberger Straße 14
|
||||
"address2": "01129 دريسدن" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "استعارة مكبس الزيت", // Ölpresse ausleihen
|
||||
"comingSoon": "المحتوى قادم قريبًا..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "اختبار THC", // THC Test
|
||||
"comingSoon": "المحتوى قادم قريبًا..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "قيد المعالجة", // in Bearbeitung
|
||||
"pending": "جديد", // Neu
|
||||
"processing": "قيد المعالجة", // in Bearbeitung
|
||||
"cancelled": "ملغي", // Storniert
|
||||
"shipped": "تم الشحن", // Verschickt
|
||||
"delivered": "تم التوصيل", // Geliefert
|
||||
"return": "إرجاع", // Retoure
|
||||
"partialReturn": "إرجاع جزئي", // Teil Retoure
|
||||
"partialDelivered": "تم التوصيل جزئيًا" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "جارٍ التحميل...", // Lädt...
|
||||
"error": "خطأ", // Fehler
|
||||
"close": "إغلاق", // Schließen
|
||||
"save": "حفظ", // Speichern
|
||||
"cancel": "إلغاء", // Abbrechen
|
||||
"ok": "موافق", // OK
|
||||
"yes": "نعم", // Ja
|
||||
"no": "لا", // Nein
|
||||
"next": "التالي", // Weiter
|
||||
"back": "عودة", // Zurück
|
||||
"edit": "تعديل", // Bearbeiten
|
||||
"delete": "حذف", // Löschen
|
||||
"add": "إضافة", // Hinzufügen
|
||||
"remove": "إزالة", // Entfernen
|
||||
"products": "منتجات", // Produkte
|
||||
"product": "منتج" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/bg/translation.js
Normal file
231
src/i18n/locales/bg/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "bg-BG" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Начало", // Startseite
|
||||
"aktionen": "Промоции", // Aktionen
|
||||
"filiale": "Клон", // Filiale
|
||||
"categories": "Категории", // Kategorien
|
||||
"categoriesOpen": "Отвори категории", // Kategorien öffnen
|
||||
"categoriesClose": "Затвори категории", // Kategorien schließen
|
||||
"otherCategories": "Други категории" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Вход", // Anmelden
|
||||
"register": "Регистрация", // Registrieren
|
||||
"logout": "Изход", // Abmelden
|
||||
"profile": "Профил", // Profil
|
||||
"email": "Имейл", // E-Mail
|
||||
"password": "Парола", // Passwort
|
||||
"confirmPassword": "Потвърди паролата", // Passwort bestätigen
|
||||
"forgotPassword": "Забравена парола?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Влез с Google", // Mit Google anmelden
|
||||
"or": "ИЛИ", // ODER
|
||||
"privacyAccept": "С натискането на \"Влез с Google\" приемам", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Политиката за поверителност", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Паролата трябва да е поне 8 символа", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Новата парола трябва да е поне 8 символа", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Профил", // Profil
|
||||
"checkout": "Плащане", // Bestellabschluss
|
||||
"orders": "Поръчки", // Bestellungen
|
||||
"settings": "Настройки", // Einstellungen
|
||||
"adminDashboard": "Админ табло", // Admin Dashboard
|
||||
"adminUsers": "Админ потребители" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Кошница", // Warenkorb
|
||||
"empty": "празна", // leer
|
||||
"addToCart": "Добави в кошницата", // In den Korb
|
||||
"preorderCutting": "Предварителна поръчка на резник", // Als Steckling vorbestellen
|
||||
"continueShopping": "Продължи пазаруването", // Weiter einkaufen
|
||||
"proceedToCheckout": "Продължи към плащане", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Продукт} other {Продукта}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Премахни от кошницата", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Отвори кошницата", // Warenkorb öffnen
|
||||
"availableFrom": "Наличен от {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Обратно към поръчката", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Синхронизация на кошницата", // Warenkorb-Synchronisierung
|
||||
"description": "Имате запазена кошница в профила си. Моля, изберете как да продължите:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Изтрий кошницата на сървъра", // Server-Warenkorb löschen
|
||||
"useServer": "Използвай кошницата от сървъра", // Server-Warenkorb übernehmen
|
||||
"merge": "Обедини кошниците", // Warenkörbe zusammenführen
|
||||
"currentCart": "Текущата ви кошница", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Кошница запазена в профила ви" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Зареждане на продукта...", // Produkt wird geladen...
|
||||
"notFound": "Продуктът не е намерен", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Търсеният продукт не съществува или е премахнат.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Обратно към началната страница", // Zurück zur Startseite
|
||||
"error": "Грешка", // Fehler
|
||||
"articleNumber": "Артикулен номер", // Artikelnummer
|
||||
"manufacturer": "Производител", // Hersteller
|
||||
"inclVat": "вкл. {{vat}}% ДДС", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Нов", // Neu
|
||||
"arriving": "Очаква се:", // Ankunft:
|
||||
"inclVatFooter": "вкл. {{vat}}% ДДС,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Наличност", // Verfügbarkeit
|
||||
"inStock": "налично", // auf Lager
|
||||
"comingSoon": "Очаквайте скоро", // Bald verfügbar
|
||||
"deliveryTime": "Срок на доставка", // Lieferzeit
|
||||
"inclShort": "вкл.", // inkl.
|
||||
"vatShort": "ДДС", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 продукта", // 0 Produkte
|
||||
"oneProduct": "1 продукт", // 1 Produkt
|
||||
"multipleProducts": "{{count}} продукта", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} от {{total}} продукта", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} от 1 продукт" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Премахнете филтрите, за да видите продуктите", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Изчерпано", // Out of Stock
|
||||
"fromXProducts": "от {{count}} продукта" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Можете да ме попитате за сортове канабис...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Запис...", // Aufnahme läuft...
|
||||
"searchProducts": "Търсене на продукти..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Име", // Name
|
||||
"searchField": "Търсена дума", // Suchbegriff
|
||||
"priceLowHigh": "Цена: от ниска към висока", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Цена: от висока към ниска" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Прочетено & Прието" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Обемни товари", // Sperrgut
|
||||
"pickup": "Вземане от клон" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Стандартна доставка", // Standardversand
|
||||
"standardFree": "Стандартна доставка - БЕЗПЛАТНО при поръчка над 100€!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Не може да се избере, защото един или повече артикули могат да се вземат само на място", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "За големи и тежки артикули" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "безплатно", // kostenlos
|
||||
"freeFrom100": "(безплатно от 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Срок на доставка: 14 дни", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Срок на доставка: 2-3 дни", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Срок на доставка: 7-9 дни" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Адрес за фактура", // Rechnungsadresse
|
||||
"deliveryAddress": "Адрес за доставка", // Lieferadresse
|
||||
"saveForFuture": "Запази за бъдещи поръчки", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "За коя дата желаете да вземете резниците?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Бележка", // Anmerkung
|
||||
"sameAddress": "Адресът за доставка е същият като адреса за фактура", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Прочетох Общите условия, Политиката за поверителност и информацията за правото на отказ" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Плащането е успешно!", // Zahlung erfolgreich!
|
||||
"failed": "Плащането не бе успешно", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Вашата поръчка е успешно завършена! Можете да видите вашите поръчки.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Плащането ви беше успешно обработено. Поръчката ще бъде завършена автоматично.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Плащането не можа да бъде обработено. Моля, опитайте отново или изберете друг метод на плащане.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Виж моите поръчки" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Сортиране", // Sortierung
|
||||
"perPage": "на страница", // pro Seite
|
||||
"availability": "Наличност", // Verfügbarkeit
|
||||
"manufacturer": "Производител" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Данък върху добавената стойност", // Mehrwertsteuer
|
||||
"vat7": "7% Данък върху добавената стойност", // 7% Mehrwertsteuer
|
||||
"vat19": "19% Данък върху добавената стойност", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% Данък върху добавената стойност (вкл. доставка)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Обща нетна цена", // Gesamtnettopreis
|
||||
"totalGross": "Обща брутна цена без доставка", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Междинна сума" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Съб 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Между спирка Пишен и Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Всички цени включват законен ДДС, плюс доставка", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Поверителност", // Datenschutz
|
||||
"agb": "Общи условия", // AGB
|
||||
"sitemap": "Карта на сайта", // Sitemap
|
||||
"impressum": "Отпечатък", // Impressum
|
||||
"batteriegesetzhinweise": "Бележки за закона за батериите", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Право на отказ" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Фини семена и резници от канабис", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Текущи промоции и оферти", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Нашият клон в Дрезден" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Семена", // Seeds
|
||||
"stecklinge": "Резници", // Stecklinge
|
||||
"oilPress": "Наеми преса за масло", // Ölpresse ausleihen
|
||||
"thcTest": "THC тест", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Наеми преса за масло", // Ölpresse ausleihen
|
||||
"comingSoon": "Съдържанието ще бъде налично скоро..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC тест", // THC Test
|
||||
"comingSoon": "Съдържанието ще бъде налично скоро..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "Обработка", // in Bearbeitung
|
||||
"pending": "Нова", // Neu
|
||||
"processing": "Обработка", // in Bearbeitung
|
||||
"cancelled": "Отменена", // Storniert
|
||||
"shipped": "Изпратена", // Verschickt
|
||||
"delivered": "Доставена", // Geliefert
|
||||
"return": "Връщане", // Retoure
|
||||
"partialReturn": "Частично връщане", // Teil Retoure
|
||||
"partialDelivered": "Частично доставена" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Зареждане...", // Lädt...
|
||||
"error": "Грешка", // Fehler
|
||||
"close": "Затвори", // Schließen
|
||||
"save": "Запази", // Speichern
|
||||
"cancel": "Отказ", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Да", // Ja
|
||||
"no": "Не", // Nein
|
||||
"next": "Напред", // Weiter
|
||||
"back": "Назад", // Zurück
|
||||
"edit": "Редактирай", // Bearbeiten
|
||||
"delete": "Изтрий", // Löschen
|
||||
"add": "Добави", // Hinzufügen
|
||||
"remove": "Премахни", // Entfernen
|
||||
"products": "Продукти", // Produkte
|
||||
"product": "Продукт" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/cs/translation.js
Normal file
231
src/i18n/locales/cs/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "cs-CZ" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Domů", // Startseite
|
||||
"aktionen": "Akce", // Aktionen
|
||||
"filiale": "Pobočka", // Filiale
|
||||
"categories": "Kategorie", // Kategorien
|
||||
"categoriesOpen": "Otevřít kategorie", // Kategorien öffnen
|
||||
"categoriesClose": "Zavřít kategorie", // Kategorien schließen
|
||||
"otherCategories": "Jiné kategorie" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Přihlásit se", // Anmelden
|
||||
"register": "Registrovat se", // Registrieren
|
||||
"logout": "Odhlásit se", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Heslo", // Passwort
|
||||
"confirmPassword": "Potvrdit heslo", // Passwort bestätigen
|
||||
"forgotPassword": "Zapomněli jste heslo?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Přihlásit se přes Google", // Mit Google anmelden
|
||||
"or": "NEBO", // ODER
|
||||
"privacyAccept": "Kliknutím na „Přihlásit se přes Google“ souhlasím s", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Zásadami ochrany osobních údajů", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Heslo musí mít alespoň 8 znaků", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Nové heslo musí mít alespoň 8 znaků", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Pokladna", // Bestellabschluss
|
||||
"orders": "Objednávky", // Bestellungen
|
||||
"settings": "Nastavení", // Einstellungen
|
||||
"adminDashboard": "Administrátorský panel", // Admin Dashboard
|
||||
"adminUsers": "Administrátoři" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Košík", // Warenkorb
|
||||
"empty": "prázdný", // leer
|
||||
"addToCart": "Přidat do košíku", // In den Korb
|
||||
"preorderCutting": "Předobjednat jako řízek", // Als Steckling vorbestellen
|
||||
"continueShopping": "Pokračovat v nákupu", // Weiter einkaufen
|
||||
"proceedToCheckout": "Pokračovat k pokladně", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Produkt} other {Produkty}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Odstranit z košíku", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Otevřít košík", // Warenkorb öffnen
|
||||
"availableFrom": "Dostupné od {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Zpět k objednávce", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Synchronizace košíku", // Warenkorb-Synchronisierung
|
||||
"description": "Máte uložený košík ve svém účtu. Vyberte, jak chcete pokračovat:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Smazat košík na serveru", // Server-Warenkorb löschen
|
||||
"useServer": "Použít košík ze serveru", // Server-Warenkorb übernehmen
|
||||
"merge": "Sloučit košíky", // Warenkörbe zusammenführen
|
||||
"currentCart": "Váš aktuální košík", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Košík uložený ve vašem profilu" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Načítání produktu...", // Produkt wird geladen...
|
||||
"notFound": "Produkt nenalezen", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Produkt, který hledáte, neexistuje nebo byl odstraněn.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Zpět na domovskou stránku", // Zurück zur Startseite
|
||||
"error": "Chyba", // Fehler
|
||||
"articleNumber": "Číslo artiklu", // Artikelnummer
|
||||
"manufacturer": "Výrobce", // Hersteller
|
||||
"inclVat": "včetně {{vat}}% DPH", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Nové", // Neu
|
||||
"arriving": "Přichází:", // Ankunft:
|
||||
"inclVatFooter": "včetně {{vat}}% DPH,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Dostupnost", // Verfügbarkeit
|
||||
"inStock": "skladem", // auf Lager
|
||||
"comingSoon": "Brzy k dispozici", // Bald verfügbar
|
||||
"deliveryTime": "Doba dodání", // Lieferzeit
|
||||
"inclShort": "vč.", // inkl.
|
||||
"vatShort": "DPH", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 produktů", // 0 Produkte
|
||||
"oneProduct": "1 produkt", // 1 Produkt
|
||||
"multipleProducts": "{{count}} produktů", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} z {{total}} produktů", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} z 1 produktu" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Odstraňte filtry pro zobrazení produktů", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Není skladem", // Out of Stock
|
||||
"fromXProducts": "od {{count}} produktů" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Můžete se mě zeptat na odrůdy konopí...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Nahrávání...", // Aufnahme läuft...
|
||||
"searchProducts": "Hledat produkty..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Název", // Name
|
||||
"searchField": "Hledaný výraz", // Suchbegriff
|
||||
"priceLowHigh": "Cena: od nejnižší", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Cena: od nejvyšší" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Přečteno a přijato" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Objemné zboží", // Sperrgut
|
||||
"pickup": "Osobní odběr na pobočce" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Standardní doprava", // Standardversand
|
||||
"standardFree": "Standardní doprava - ZDARMA od objednávky nad 100 €!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Nelze vybrat, protože jeden nebo více produktů lze pouze vyzvednout", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Pro velké a těžké položky" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "zdarma", // kostenlos
|
||||
"freeFrom100": "(zdarma od 100 €)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Doba dodání: 14 dní", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Doba dodání: 2-3 dny", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Doba dodání: 7-9 dní" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Fakturační adresa", // Rechnungsadresse
|
||||
"deliveryAddress": "Dodací adresa", // Lieferadresse
|
||||
"saveForFuture": "Uložit pro budoucí objednávky", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Na který den chcete vyzvednout řízky?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Poznámka", // Anmerkung
|
||||
"sameAddress": "Dodací adresa je stejná jako fakturační adresa", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Přečetl(a) jsem si Obchodní podmínky, Zásady ochrany osobních údajů a informace o právu na odstoupení od smlouvy" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Platba byla úspěšná!", // Zahlung erfolgreich!
|
||||
"failed": "Platba selhala", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Vaše objednávka byla úspěšně dokončena! Nyní můžete zobrazit své objednávky.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Vaše platba byla úspěšně zpracována. Objednávka bude automaticky dokončena.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Vaši platbu nebylo možné zpracovat. Zkuste to prosím znovu nebo zvolte jiný způsob platby.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Zobrazit mé objednávky" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Řazení", // Sortierung
|
||||
"perPage": "na stránku", // pro Seite
|
||||
"availability": "Dostupnost", // Verfügbarkeit
|
||||
"manufacturer": "Výrobce" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Daň z přidané hodnoty", // Mehrwertsteuer
|
||||
"vat7": "7% daň z přidané hodnoty", // 7% Mehrwertsteuer
|
||||
"vat19": "19% daň z přidané hodnoty", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% daň z přidané hodnoty (včetně dopravy)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Celková cena bez DPH", // Gesamtnettopreis
|
||||
"totalGross": "Celková cena včetně DPH bez dopravy", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Mezisoučet" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "So 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Drážďany", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Mezi zastávkou Pieschen a Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Všechny ceny včetně zákonné DPH, plus doprava", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Ochrana osobních údajů", // Datenschutz
|
||||
"agb": "Obchodní podmínky", // AGB
|
||||
"sitemap": "Mapa stránek", // Sitemap
|
||||
"impressum": "Impressum", // Impressum
|
||||
"batteriegesetzhinweise": "Poznámky k zákonu o bateriích", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Právo na odstoupení" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Kvalitní semena a řízky konopí", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Aktuální akce a nabídky", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Naše pobočka v Drážďanech" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Semena", // Seeds
|
||||
"stecklinge": "Řízky", // Stecklinge
|
||||
"oilPress": "Půjčit lis na olej", // Ölpresse ausleihen
|
||||
"thcTest": "THC test", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Drážďany" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Půjčit lis na olej", // Ölpresse ausleihen
|
||||
"comingSoon": "Obsah brzy k dispozici..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC test", // THC Test
|
||||
"comingSoon": "Obsah brzy k dispozici..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "Zpracovává se", // in Bearbeitung
|
||||
"pending": "Nová", // Neu
|
||||
"processing": "Zpracovává se", // in Bearbeitung
|
||||
"cancelled": "Zrušeno", // Storniert
|
||||
"shipped": "Odesláno", // Verschickt
|
||||
"delivered": "Doručeno", // Geliefert
|
||||
"return": "Vrácení", // Retoure
|
||||
"partialReturn": "Částečné vrácení", // Teil Retoure
|
||||
"partialDelivered": "Částečně doručeno" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Načítání...", // Lädt...
|
||||
"error": "Chyba", // Fehler
|
||||
"close": "Zavřít", // Schließen
|
||||
"save": "Uložit", // Speichern
|
||||
"cancel": "Zrušit", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Ano", // Ja
|
||||
"no": "Ne", // Nein
|
||||
"next": "Další", // Weiter
|
||||
"back": "Zpět", // Zurück
|
||||
"edit": "Upravit", // Bearbeiten
|
||||
"delete": "Smazat", // Löschen
|
||||
"add": "Přidat", // Hinzufügen
|
||||
"remove": "Odstranit", // Entfernen
|
||||
"products": "Produkty", // Produkte
|
||||
"product": "Produkt" // Produkt
|
||||
}
|
||||
};
|
||||
289
src/i18n/locales/de/translation.js
Normal file
289
src/i18n/locales/de/translation.js
Normal file
@@ -0,0 +1,289 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "de-DE"
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Startseite",
|
||||
"aktionen": "Aktionen",
|
||||
"filiale": "Filiale",
|
||||
"categories": "Kategorien",
|
||||
"categoriesOpen": "Kategorien öffnen",
|
||||
"categoriesClose": "Kategorien schließen",
|
||||
"otherCategories": "Andere Kategorien"
|
||||
},
|
||||
"auth": {
|
||||
"login": "Anmelden",
|
||||
"register": "Registrieren",
|
||||
"logout": "Abmelden",
|
||||
"profile": "Profil",
|
||||
"email": "E-Mail",
|
||||
"password": "Passwort",
|
||||
"confirmPassword": "Passwort bestätigen",
|
||||
"forgotPassword": "Passwort vergessen?",
|
||||
"loginWithGoogle": "Mit Google anmelden",
|
||||
"or": "ODER",
|
||||
"privacyAccept": "Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die",
|
||||
"privacyPolicy": "Datenschutzbestimmungen",
|
||||
"passwordMinLength": "Das Passwort muss mindestens 8 Zeichen lang sein",
|
||||
"newPasswordMinLength": "Das neue Passwort muss mindestens 8 Zeichen lang sein",
|
||||
"menu": {
|
||||
"profile": "Profil",
|
||||
"myProfile": "Mein Profil",
|
||||
"checkout": "Bestellabschluss",
|
||||
"orders": "Bestellungen",
|
||||
"settings": "Einstellungen",
|
||||
"adminDashboard": "Admin Dashboard",
|
||||
"adminUsers": "Admin Users"
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Warenkorb",
|
||||
"empty": "leer",
|
||||
"addToCart": "In den Korb",
|
||||
"preorderCutting": "Als Steckling vorbestellen",
|
||||
"continueShopping": "Weiter einkaufen",
|
||||
"proceedToCheckout": "Weiter zur Kasse",
|
||||
"productCount": "{{count}} {{count, plural, one {Produkt} other {Produkte}}}",
|
||||
"productSingular": "Produkt",
|
||||
"productPlural": "Produkte",
|
||||
"removeFromCart": "Aus dem Warenkorb entfernen",
|
||||
"openCart": "Warenkorb öffnen",
|
||||
"availableFrom": "Ab {{date}}",
|
||||
"backToOrder": "← Zurück zur Bestellung",
|
||||
"sync": {
|
||||
"title": "Warenkorb-Synchronisierung",
|
||||
"description": "Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:",
|
||||
"deleteServer": "Server-Warenkorb löschen",
|
||||
"useServer": "Server-Warenkorb übernehmen",
|
||||
"merge": "Warenkörbe zusammenführen",
|
||||
"currentCart": "Ihr aktueller Warenkorb",
|
||||
"serverCart": "In Ihrem Profil gespeicherter Warenkorb"
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Produkt wird geladen...",
|
||||
"notFound": "Produkt nicht gefunden",
|
||||
"notFoundDescription": "Das gesuchte Produkt existiert nicht oder wurde entfernt.",
|
||||
"backToHome": "Zurück zur Startseite",
|
||||
"error": "Fehler",
|
||||
"articleNumber": "Artikelnummer",
|
||||
"manufacturer": "Hersteller",
|
||||
"inclVat": "inkl. {{vat}}% MwSt.",
|
||||
"priceUnit": "{{price}}/{{unit}}",
|
||||
"new": "Neu",
|
||||
"arriving": "Ankunft:",
|
||||
"inclVatFooter": "inkl. {{vat}}% MwSt.,*",
|
||||
"availability": "Verfügbarkeit",
|
||||
"inStock": "auf Lager",
|
||||
"comingSoon": "Bald verfügbar",
|
||||
"deliveryTime": "Lieferzeit",
|
||||
"inclShort": "inkl.",
|
||||
"vatShort": "MwSt.",
|
||||
"countDisplay": {
|
||||
"noProducts": "0 Produkte",
|
||||
"oneProduct": "1 Produkt",
|
||||
"multipleProducts": "{{count}} Produkte",
|
||||
"filteredProducts": "{{filtered}} von {{total}} Produkten",
|
||||
"filteredOneProduct": "{{filtered}} von 1 Produkt",
|
||||
"xOfYProducts": "{{x}} von {{y}} Produkten"
|
||||
},
|
||||
"removeFiltersToSee": "Entferne Filter um Produkte zu sehen",
|
||||
"outOfStock": "Out of Stock",
|
||||
"fromXProducts": "ab {{count}} Produkten"
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Du kannst mich nach Cannabissorten fragen...",
|
||||
"recording": "Aufnahme läuft...",
|
||||
"searchProducts": "Produkte suchen..."
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Name",
|
||||
"searchField": "Suchbegriff",
|
||||
"priceLowHigh": "Preis: Niedrig zu Hoch",
|
||||
"priceHighLow": "Preis: Hoch zu Niedrig"
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Gelesen & Akzeptiert"
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL",
|
||||
"dpd": "DPD",
|
||||
"sperrgut": "Sperrgut",
|
||||
"pickup": "Abholung in der Filiale"
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Standardversand",
|
||||
"standardFree": "Standardversand - KOSTENLOS ab 100€ Warenwert!",
|
||||
"notAvailable": "nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können",
|
||||
"bulky": "Für große und schwere Artikel",
|
||||
"pickupOnly": "nur Abholung"
|
||||
},
|
||||
"prices": {
|
||||
"free": "kostenlos",
|
||||
"freeFrom100": "(kostenlos ab 100€)",
|
||||
"dhl": "6,99 €",
|
||||
"dpd": "4,90 €",
|
||||
"sperrgut": "28,99 €"
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Lieferzeit: 14 Tage",
|
||||
"standard2to3Days": "Lieferzeit: 2-3 Tage",
|
||||
"supplier7to9Days": "Lieferzeit: 7-9 Tage"
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Rechnungsadresse",
|
||||
"deliveryAddress": "Lieferadresse",
|
||||
"saveForFuture": "Für zukünftige Bestellungen speichern",
|
||||
"pickupDate": "Für welchen Termin ist die Abholung der Stecklinge gewünscht?",
|
||||
"note": "Anmerkung",
|
||||
"sameAddress": "Lieferadresse ist identisch mit Rechnungsadresse",
|
||||
"termsAccept": "Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen",
|
||||
"selectDeliveryMethod": "Versandart wählen",
|
||||
"selectPaymentMethod": "Zahlungsart wählen",
|
||||
"orderSummary": "Bestellübersicht",
|
||||
"addressValidationError": "Bitte überprüfen Sie Ihre Eingaben in den Adressfeldern."
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Zahlung erfolgreich!",
|
||||
"failed": "Zahlung fehlgeschlagen",
|
||||
"orderCompleted": "🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.",
|
||||
"orderProcessing": "Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.",
|
||||
"paymentError": "Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.",
|
||||
"viewOrders": "Zu meinen Bestellungen"
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Sortierung",
|
||||
"perPage": "pro Seite",
|
||||
"availability": "Verfügbarkeit",
|
||||
"manufacturer": "Hersteller",
|
||||
"all": "Alle"
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Mehrwertsteuer",
|
||||
"vat7": "7% Mehrwertsteuer",
|
||||
"vat19": "19% Mehrwertsteuer",
|
||||
"vat19WithShipping": "19% Mehrwertsteuer (inkl. Versand)",
|
||||
"totalNet": "Gesamtnettopreis",
|
||||
"totalGross": "Gesamtbruttopreis ohne Versand",
|
||||
"subtotal": "Zwischensumme",
|
||||
"incl7Vat": "inkl. 7% MwSt.",
|
||||
"inclVatWithFooter": "(incl. {{vat}}% USt.,*)",
|
||||
"inclVatAmount": "nkl. {{amount}} € MwSt. ({{rate}}%)"
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Sa 11-19",
|
||||
"address": "Trachenberger Straße 14 - Dresden",
|
||||
"location": "Zwischen Haltepunkt Pieschen und Trachenberger Platz",
|
||||
"allPricesIncl": "* Alle Preise inkl. gesetzlicher USt., zzgl. Versand",
|
||||
"copyright": "© {{year}} GrowHeads.de",
|
||||
"legal": {
|
||||
"datenschutz": "Datenschutz",
|
||||
"agb": "AGB",
|
||||
"sitemap": "Sitemap",
|
||||
"impressum": "Impressum",
|
||||
"batteriegesetzhinweise": "Batteriegesetzhinweise",
|
||||
"widerrufsrecht": "Widerrufsrecht"
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "ine annabis eeds & uttings", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "tuelle ktionen & gebote", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "nsere iliale in resden" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Seeds",
|
||||
"stecklinge": "Stecklinge",
|
||||
"oilPress": "Ölpresse ausleihen",
|
||||
"thcTest": "THC Test",
|
||||
"address1": "Trachenberger Straße 14",
|
||||
"address2": "01129 Dresden"
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Ölpresse ausleihen",
|
||||
"comingSoon": "Inhalt kommt bald..."
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC Test",
|
||||
"comingSoon": "Inhalt kommt bald..."
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "in Bearbeitung",
|
||||
"pending": "Neu",
|
||||
"processing": "in Bearbeitung",
|
||||
"cancelled": "Storniert",
|
||||
"shipped": "Verschickt",
|
||||
"delivered": "Geliefert",
|
||||
"return": "Retoure",
|
||||
"partialReturn": "Teil Retoure",
|
||||
"partialDelivered": "Teil geliefert"
|
||||
},
|
||||
"table": {
|
||||
"orderNumber": "Bestellnummer",
|
||||
"date": "Datum",
|
||||
"status": "Status",
|
||||
"items": "Artikel",
|
||||
"total": "Summe",
|
||||
"actions": "Aktionen",
|
||||
"viewDetails": "Details anzeigen"
|
||||
},
|
||||
"noOrders": "Sie haben noch keine Bestellungen aufgegeben."
|
||||
},
|
||||
"settings": {
|
||||
"changePassword": "Passwort ändern",
|
||||
"currentPassword": "Aktuelles Passwort",
|
||||
"newPassword": "Neues Passwort",
|
||||
"confirmNewPassword": "Neues Passwort bestätigen",
|
||||
"updatePassword": "Passwort aktualisieren",
|
||||
"changeEmail": "E-Mail-Adresse ändern",
|
||||
"password": "Passwort",
|
||||
"newEmail": "Neue E-Mail-Adresse",
|
||||
"updateEmail": "E-Mail aktualisieren",
|
||||
"apiKey": "API-Schlüssel",
|
||||
"apiKeyDescription": "Verwenden Sie Ihren API-Schlüssel für die Integration mit externen Anwendungen.",
|
||||
"apiDocumentation": "API-Dokumentation:",
|
||||
"copyToClipboard": "In Zwischenablage kopieren",
|
||||
"generate": "Generieren",
|
||||
"regenerate": "Regenerieren",
|
||||
"apiKeyCopied": "API-Schlüssel in Zwischenablage kopiert",
|
||||
"errors": {
|
||||
"fillAllFields": "Bitte füllen Sie alle Felder aus",
|
||||
"passwordsNotMatch": "Die neuen Passwörter stimmen nicht überein",
|
||||
"passwordTooShort": "Das neue Passwort muss mindestens 8 Zeichen lang sein",
|
||||
"passwordUpdateError": "Fehler beim Aktualisieren des Passworts",
|
||||
"invalidEmail": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
|
||||
"emailUpdateError": "Fehler beim Aktualisieren der E-Mail-Adresse",
|
||||
"userNotFound": "Benutzer nicht gefunden",
|
||||
"apiKeyGenerationError": "Fehler beim Generieren des API-Schlüssels"
|
||||
},
|
||||
"success": {
|
||||
"passwordUpdated": "Passwort erfolgreich aktualisiert",
|
||||
"emailUpdated": "E-Mail-Adresse erfolgreich aktualisiert",
|
||||
"apiKeyGenerated": "API-Schlüssel erfolgreich generiert",
|
||||
"apiKeyWarning": "Speichern Sie diesen Schlüssel sicher. Er wird aus Sicherheitsgründen in 10 Sekunden ausgeblendet."
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Lädt...",
|
||||
"error": "Fehler",
|
||||
"close": "Schließen",
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"ok": "OK",
|
||||
"yes": "Ja",
|
||||
"no": "Nein",
|
||||
"next": "Weiter",
|
||||
"back": "Zurück",
|
||||
"edit": "Bearbeiten",
|
||||
"delete": "Löschen",
|
||||
"add": "Hinzufügen",
|
||||
"remove": "Entfernen",
|
||||
"products": "Produkte",
|
||||
"product": "Produkt",
|
||||
"days": "Tage"
|
||||
}
|
||||
}
|
||||
231
src/i18n/locales/el/translation.js
Normal file
231
src/i18n/locales/el/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "el-GR" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Αρχική", // Startseite
|
||||
"aktionen": "Προσφορές", // Aktionen
|
||||
"filiale": "Κατάστημα", // Filiale
|
||||
"categories": "Κατηγορίες", // Kategorien
|
||||
"categoriesOpen": "Άνοιγμα κατηγοριών", // Kategorien öffnen
|
||||
"categoriesClose": "Κλείσιμο κατηγοριών", // Kategorien schließen
|
||||
"otherCategories": "Άλλες κατηγορίες" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Σύνδεση", // Anmelden
|
||||
"register": "Εγγραφή", // Registrieren
|
||||
"logout": "Αποσύνδεση", // Abmelden
|
||||
"profile": "Προφίλ", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Κωδικός", // Passwort
|
||||
"confirmPassword": "Επιβεβαίωση κωδικού", // Passwort bestätigen
|
||||
"forgotPassword": "Ξεχάσατε τον κωδικό;", // Passwort vergessen?
|
||||
"loginWithGoogle": "Σύνδεση με Google", // Mit Google anmelden
|
||||
"or": "Ή", // ODER
|
||||
"privacyAccept": "Πατώντας \"Σύνδεση με Google\" αποδέχομαι την", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Πολιτική Απορρήτου", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Ο κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Ο νέος κωδικός πρέπει να έχει τουλάχιστον 8 χαρακτήρες", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Προφίλ", // Profil
|
||||
"checkout": "Ολοκλήρωση αγοράς", // Bestellabschluss
|
||||
"orders": "Παραγγελίες", // Bestellungen
|
||||
"settings": "Ρυθμίσεις", // Einstellungen
|
||||
"adminDashboard": "Πίνακας διαχείρισης", // Admin Dashboard
|
||||
"adminUsers": "Διαχειριστές" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Καλάθι", // Warenkorb
|
||||
"empty": "κενό", // leer
|
||||
"addToCart": "Προσθήκη στο καλάθι", // In den Korb
|
||||
"preorderCutting": "Προπαραγγελία ως μοσχεύματα", // Als Steckling vorbestellen
|
||||
"continueShopping": "Συνέχεια αγορών", // Weiter einkaufen
|
||||
"proceedToCheckout": "Προχωρήστε στην ολοκλήρωση αγοράς", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Προϊόν} other {Προϊόντα}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Αφαίρεση από το καλάθι", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Άνοιγμα καλαθιού", // Warenkorb öffnen
|
||||
"availableFrom": "Διαθέσιμο από {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Επιστροφή στην παραγγελία", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Συγχρονισμός καλαθιού", // Warenkorb-Synchronisierung
|
||||
"description": "Έχετε αποθηκευμένο καλάθι στο λογαριασμό σας. Παρακαλώ επιλέξτε πώς θέλετε να προχωρήσετε:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Διαγραφή καλαθιού από διακομιστή", // Server-Warenkorb löschen
|
||||
"useServer": "Χρήση καλαθιού από διακομιστή", // Server-Warenkorb übernehmen
|
||||
"merge": "Συγχώνευση καλαθιών", // Warenkörbe zusammenführen
|
||||
"currentCart": "Το τρέχον καλάθι σας", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Καλάθι αποθηκευμένο στο προφίλ σας" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Φόρτωση προϊόντος...", // Produkt wird geladen...
|
||||
"notFound": "Το προϊόν δεν βρέθηκε", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Το προϊόν που αναζητάτε δεν υπάρχει ή έχει αφαιρεθεί.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Επιστροφή στην αρχική", // Zurück zur Startseite
|
||||
"error": "Σφάλμα", // Fehler
|
||||
"articleNumber": "Αριθμός άρθρου", // Artikelnummer
|
||||
"manufacturer": "Κατασκευαστής", // Hersteller
|
||||
"inclVat": "συμπ. {{vat}}% ΦΠΑ", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Νέο", // Neu
|
||||
"arriving": "Άφιξη:", // Ankunft:
|
||||
"inclVatFooter": "συμπ. {{vat}}% ΦΠΑ,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Διαθεσιμότητα", // Verfügbarkeit
|
||||
"inStock": "σε απόθεμα", // auf Lager
|
||||
"comingSoon": "Έρχεται σύντομα", // Bald verfügbar
|
||||
"deliveryTime": "Χρόνος παράδοσης", // Lieferzeit
|
||||
"inclShort": "συμπ.", // inkl.
|
||||
"vatShort": "ΦΠΑ", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 προϊόντα", // 0 Produkte
|
||||
"oneProduct": "1 προϊόν", // 1 Produkt
|
||||
"multipleProducts": "{{count}} προϊόντα", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} από {{total}} προϊόντα", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} από 1 προϊόν" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Αφαιρέστε τα φίλτρα για να δείτε προϊόντα", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Εξαντλημένο", // Out of Stock
|
||||
"fromXProducts": "από {{count}} προϊόντα" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Μπορείτε να με ρωτήσετε για ποικιλίες κάνναβης...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Ηχογράφηση...", // Aufnahme läuft...
|
||||
"searchProducts": "Αναζήτηση προϊόντων..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Όνομα", // Name
|
||||
"searchField": "Όρος αναζήτησης", // Suchbegriff
|
||||
"priceLowHigh": "Τιμή: Από χαμηλή σε υψηλή", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Τιμή: Από υψηλή σε χαμηλή" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Διαβασμένο & Αποδεκτό" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Ογκώδη είδη", // Sperrgut
|
||||
"pickup": "Παραλαβή από κατάστημα" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Τυπική αποστολή", // Standardversand
|
||||
"standardFree": "Τυπική αποστολή - ΔΩΡΕΑΝ από παραγγελίες άνω των 100€!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Μη διαθέσιμο γιατί ένα ή περισσότερα είδη μπορούν να παραληφθούν μόνο από το κατάστημα", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Για μεγάλα και βαριά αντικείμενα" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "δωρεάν", // kostenlos
|
||||
"freeFrom100": "(δωρεάν από 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Χρόνος παράδοσης: 14 ημέρες", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Χρόνος παράδοσης: 2-3 ημέρες", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Χρόνος παράδοσης: 7-9 ημέρες" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Διεύθυνση τιμολογίου", // Rechnungsadresse
|
||||
"deliveryAddress": "Διεύθυνση παράδοσης", // Lieferadresse
|
||||
"saveForFuture": "Αποθήκευση για μελλοντικές παραγγελίες", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Για ποια ημερομηνία θέλετε να παραλάβετε τα μοσχεύματα;", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Σημείωση", // Anmerkung
|
||||
"sameAddress": "Η διεύθυνση παράδοσης είναι ίδια με τη διεύθυνση τιμολογίου", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Έχω διαβάσει τους Όρους και Προϋποθέσεις, την Πολιτική Απορρήτου και τις πληροφορίες για το Δικαίωμα Ανάκλησης" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Η πληρωμή ήταν επιτυχής!", // Zahlung erfolgreich!
|
||||
"failed": "Η πληρωμή απέτυχε", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Η παραγγελία σας ολοκληρώθηκε με επιτυχία! Μπορείτε τώρα να δείτε τις παραγγελίες σας.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Η πληρωμή σας επεξεργάστηκε με επιτυχία. Η παραγγελία θα ολοκληρωθεί αυτόματα.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Η πληρωμή σας δεν μπόρεσε να επεξεργαστεί. Παρακαλώ δοκιμάστε ξανά ή επιλέξτε άλλη μέθοδο πληρωμής.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Δείτε τις παραγγελίες μου" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Ταξινόμηση", // Sortierung
|
||||
"perPage": "ανά σελίδα", // pro Seite
|
||||
"availability": "Διαθεσιμότητα", // Verfügbarkeit
|
||||
"manufacturer": "Κατασκευαστής" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Φόρος Προστιθέμενης Αξίας", // Mehrwertsteuer
|
||||
"vat7": "7% Φόρος Προστιθέμενης Αξίας", // 7% Mehrwertsteuer
|
||||
"vat19": "19% Φόρος Προστιθέμενης Αξίας", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% Φόρος Προστιθέμενης Αξίας (συμπ. μεταφορικά)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Συνολική καθαρή τιμή", // Gesamtnettopreis
|
||||
"totalGross": "Συνολική ακαθάριστη τιμή χωρίς μεταφορικά", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Μερικό σύνολο" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Σαβ 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Μεταξύ στάσης Pieschen και Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Όλες οι τιμές περιλαμβάνουν νόμιμο ΦΠΑ, συν τα μεταφορικά", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Απόρρητο", // Datenschutz
|
||||
"agb": "Όροι & Προϋποθέσεις", // AGB
|
||||
"sitemap": "Χάρτης ιστοσελίδας", // Sitemap
|
||||
"impressum": "Εντυπο", // Impressum
|
||||
"batteriegesetzhinweise": "Σημειώσεις νόμου για μπαταρίες", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Δικαίωμα ανάκλησης" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Εξαιρετικοί σπόροι και μοσχεύματα κάνναβης", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Τρέχουσες προσφορές & εκπτώσεις", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Το κατάστημά μας στο Ντρέσντεν" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Σπόροι", // Seeds
|
||||
"stecklinge": "Μοσχεύματα", // Stecklinge
|
||||
"oilPress": "Δανεισμός πρέσας λαδιού", // Ölpresse ausleihen
|
||||
"thcTest": "Έλεγχος THC", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Δανεισμός πρέσας λαδιού", // Ölpresse ausleihen
|
||||
"comingSoon": "Περιεχόμενο σύντομα διαθέσιμο..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "Έλεγχος THC", // THC Test
|
||||
"comingSoon": "Περιεχόμενο σύντομα διαθέσιμο..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "Σε επεξεργασία", // in Bearbeitung
|
||||
"pending": "Νέα", // Neu
|
||||
"processing": "Σε επεξεργασία", // in Bearbeitung
|
||||
"cancelled": "Ακυρώθηκε", // Storniert
|
||||
"shipped": "Απεσταλμένο", // Verschickt
|
||||
"delivered": "Παραδομένο", // Geliefert
|
||||
"return": "Επιστροφή", // Retoure
|
||||
"partialReturn": "Μερική επιστροφή", // Teil Retoure
|
||||
"partialDelivered": "Μερικώς παραδομένο" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Φόρτωση...", // Lädt...
|
||||
"error": "Σφάλμα", // Fehler
|
||||
"close": "Κλείσιμο", // Schließen
|
||||
"save": "Αποθήκευση", // Speichern
|
||||
"cancel": "Ακύρωση", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Ναι", // Ja
|
||||
"no": "Όχι", // Nein
|
||||
"next": "Επόμενο", // Weiter
|
||||
"back": "Πίσω", // Zurück
|
||||
"edit": "Επεξεργασία", // Bearbeiten
|
||||
"delete": "Διαγραφή", // Löschen
|
||||
"add": "Προσθήκη", // Hinzufügen
|
||||
"remove": "Αφαίρεση", // Entfernen
|
||||
"products": "Προϊόντα", // Produkte
|
||||
"product": "Προϊόν" // Produkt
|
||||
}
|
||||
};
|
||||
269
src/i18n/locales/en/translation.js
Normal file
269
src/i18n/locales/en/translation.js
Normal file
@@ -0,0 +1,269 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "en-US" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Home", // Startseite
|
||||
"aktionen": "Promotions", // Aktionen
|
||||
"filiale": "Branch", // Filiale
|
||||
"categories": "Categories", // Kategorien
|
||||
"categoriesOpen": "Open categories", // Kategorien öffnen
|
||||
"categoriesClose": "Close categories", // Kategorien schließen
|
||||
"otherCategories": "Other categories" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Login", // Anmelden
|
||||
"register": "Register", // Registrieren
|
||||
"logout": "Logout", // Abmelden
|
||||
"profile": "Profile", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Password", // Passwort
|
||||
"confirmPassword": "Confirm password", // Passwort bestätigen
|
||||
"forgotPassword": "Forgot password?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Sign in with Google", // Mit Google anmelden
|
||||
"or": "OR", // ODER
|
||||
"privacyAccept": "By clicking \"Sign in with Google\" I accept the", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Privacy Policy", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "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
|
||||
"menu": {
|
||||
"profile": "Profile", // Profil
|
||||
"myProfile": "My profile", // Mein Profil
|
||||
"checkout": "Checkout", // Bestellabschluss
|
||||
"orders": "Orders", // Bestellungen
|
||||
"settings": "Settings", // Einstellungen
|
||||
"adminDashboard": "Admin Dashboard", // Admin Dashboard
|
||||
"adminUsers": "Admin Users" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Cart", // Warenkorb
|
||||
"empty": "empty", // leer
|
||||
"addToCart": "Add to cart", // In den Korb
|
||||
"preorderCutting": "Preorder as cutting", // Als Steckling vorbestellen
|
||||
"continueShopping": "Continue shopping", // Weiter einkaufen
|
||||
"proceedToCheckout": "Proceed to checkout", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {product} other {products}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"productSingular": "Product", // Produkt
|
||||
"productPlural": "Products", // Produkte
|
||||
"removeFromCart": "Remove from cart", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Open cart", // Warenkorb öffnen
|
||||
"availableFrom": "Available from {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Back to order", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Cart synchronization", // Warenkorb-Synchronisierung
|
||||
"description": "You have a saved cart in your account. Please choose how you want to proceed:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Delete server cart", // Server-Warenkorb löschen
|
||||
"useServer": "Use server cart", // Server-Warenkorb übernehmen
|
||||
"merge": "Merge carts", // Warenkörbe zusammenführen
|
||||
"currentCart": "Your current cart", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Cart saved in your profile" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Loading product...", // Produkt wird geladen...
|
||||
"notFound": "Product not found", // Produkt nicht gefunden
|
||||
"notFoundDescription": "The product you are looking for does not exist or has been removed.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Back to home", // Zurück zur Startseite
|
||||
"error": "Error", // Fehler
|
||||
"articleNumber": "Article number", // Artikelnummer
|
||||
"manufacturer": "Manufacturer", // Hersteller
|
||||
"inclVat": "incl. {{vat}}% VAT", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "New", // Neu
|
||||
"arriving": "Arriving:", // Ankunft:
|
||||
"inclVatFooter": "incl. {{vat}}% VAT,*", // inkl. {{vat}}% MwSt.,*
|
||||
"availability": "Availability", // Verfügbarkeit
|
||||
"inStock": "In stock", // auf Lager
|
||||
"comingSoon": "Coming soon", // Bald verfügbar
|
||||
"deliveryTime": "Delivery time", // Lieferzeit
|
||||
"inclShort": "incl.", // inkl.
|
||||
"vatShort": "VAT", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 products", // 0 Produkte
|
||||
"oneProduct": "1 product", // 1 Produkt
|
||||
"multipleProducts": "{{count}} products", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} of {{total}} products", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} of 1 product", // {{filtered}} von 1 Produkt
|
||||
"xOfYProducts": "{{x}} of {{y}} products" // {{x}} von {{y}} Produkten
|
||||
},
|
||||
"removeFiltersToSee": "Remove filters to see products", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Out of stock", // Out of Stock
|
||||
"fromXProducts": "from {{count}} products" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "You can ask me about cannabis strains...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Recording...", // Aufnahme läuft...
|
||||
"searchProducts": "Search products..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Name", // Name
|
||||
"searchField": "Search term", // Suchbegriff
|
||||
"priceLowHigh": "Price: Low to High", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Price: High to Low" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Read & Accepted" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Bulky goods", // Sperrgut
|
||||
"pickup": "Pickup at branch" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Standard shipping", // Standardversand
|
||||
"standardFree": "Standard shipping - FREE from €100 order value!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Not selectable because one or more items can only be picked up", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "For large and heavy items", // Für große und schwere Artikel
|
||||
"pickupOnly": "Pickup only" // nur Abholung
|
||||
},
|
||||
"prices": {
|
||||
"free": "free", // kostenlos
|
||||
"freeFrom100": "(free from €100)", // (kostenlos ab 100€)
|
||||
"dhl": "€6.99", // 6,99 €
|
||||
"dpd": "€4.90", // 4,90 €
|
||||
"sperrgut": "€28.99" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Delivery time: 14 days", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Delivery time: 2-3 days", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Delivery time: 7-9 days" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Invoice address", // Rechnungsadresse
|
||||
"deliveryAddress": "Delivery address", // Lieferadresse
|
||||
"saveForFuture": "Save for future orders", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "For which date do you want to pick up the cuttings?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Note", // Anmerkung
|
||||
"sameAddress": "Delivery address is the same as invoice address", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "I have read the Terms & Conditions, Privacy Policy, and the Right of Withdrawal information", // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
"selectDeliveryMethod": "Select shipping method", // Versandart wählen
|
||||
"selectPaymentMethod": "Select payment method", // Zahlungsart wählen
|
||||
"orderSummary": "Order summary", // Bestellübersicht
|
||||
"addressValidationError": "Please check your entries in the address fields." // Bitte überprüfen Sie Ihre Eingaben in den Adressfeldern.
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Payment successful!", // Zahlung erfolgreich!
|
||||
"failed": "Payment failed", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Your order was completed successfully! You can now view your orders.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Your payment was processed successfully. The order will be completed automatically.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Your payment could not be processed. Please try again or choose another payment method.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "View my orders" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Sorting", // Sortierung
|
||||
"perPage": "per page", // pro Seite
|
||||
"availability": "Availability", // Verfügbarkeit
|
||||
"manufacturer": "Manufacturer", // Hersteller
|
||||
"all": "All" // Alle
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Value added tax", // Mehrwertsteuer
|
||||
"vat7": "7% value added tax", // 7% Mehrwertsteuer
|
||||
"vat19": "19% value added tax", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% value added tax (incl. shipping)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Total net price", // Gesamtnettopreis
|
||||
"totalGross": "Total gross price excluding shipping", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Subtotal", // Zwischensumme
|
||||
"incl7Vat": "incl. 7% VAT", // inkl. 7% MwSt.
|
||||
"inclVatWithFooter": "(incl. {{vat}}% VAT,*)", // (incl. {{vat}}% USt.,*)
|
||||
"inclVatAmount": "incl. {{amount}} € VAT ({{rate}}%)" // nkl. {{amount}} € MwSt. ({{rate}}%)
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Sat 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Between Pieschen stop and Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* All prices incl. statutory VAT, plus shipping", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Privacy", // Datenschutz
|
||||
"agb": "Terms & Conditions", // AGB
|
||||
"sitemap": "Sitemap", // Sitemap
|
||||
"impressum": "Imprint", // Impressum
|
||||
"batteriegesetzhinweise": "Battery law notes", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Right of withdrawal" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Fine Cannabis Seeds & Cuttings", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Current Promotions & Offers", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Our Branch in Dresden" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Seeds", // Seeds
|
||||
"stecklinge": "Cuttings", // Stecklinge
|
||||
"oilPress": "Borrow oil press", // Ölpresse ausleihen
|
||||
"thcTest": "THC test", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Borrow oil press", // Ölpresse ausleihen
|
||||
"comingSoon": "Content coming soon..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC test", // THC Test
|
||||
"comingSoon": "Content coming soon..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "Processing", // in Bearbeitung
|
||||
"pending": "New", // Neu
|
||||
"processing": "Processing", // in Bearbeitung
|
||||
"cancelled": "Cancelled", // Storniert
|
||||
"shipped": "Shipped", // Verschickt
|
||||
"delivered": "Delivered", // Geliefert
|
||||
"return": "Return", // Retoure
|
||||
"partialReturn": "Partial return", // Teil Retoure
|
||||
"partialDelivered": "Partially delivered" // Teil geliefert
|
||||
},
|
||||
"table": {
|
||||
"orderNumber": "Order number", // Bestellnummer
|
||||
"date": "Date", // Datum
|
||||
"status": "Status", // Status
|
||||
"items": "Items", // Artikel
|
||||
"total": "Total", // Summe
|
||||
"actions": "Actions", // Aktionen
|
||||
"viewDetails": "View details" // Details anzeigen
|
||||
},
|
||||
"noOrders": "You have not placed any orders yet." // Sie haben noch keine Bestellungen aufgegeben.
|
||||
},
|
||||
"settings": {
|
||||
"changePassword": "Change password", // Passwort ändern
|
||||
"currentPassword": "Current password", // Aktuelles Passwort
|
||||
"newPassword": "New password", // Neues Passwort
|
||||
"confirmNewPassword": "Confirm new password", // Neues Passwort bestätigen
|
||||
"updatePassword": "Update password", // Passwort aktualisieren
|
||||
"changeEmail": "Change email address", // E-Mail-Adresse ändern
|
||||
"password": "Password", // Passwort
|
||||
"newEmail": "New email address", // Neue E-Mail-Adresse
|
||||
"updateEmail": "Update email", // E-Mail aktualisieren
|
||||
"apiKey": "API key", // API-Schlüssel
|
||||
"apiKeyDescription": "Use your API key for integration with external applications.", // Verwenden Sie Ihren API-Schlüssel für die Integration mit externen Anwendungen.
|
||||
"apiDocumentation": "API documentation:", // API-Dokumentation:
|
||||
"copyToClipboard": "Copy to clipboard", // In Zwischenablage kopieren
|
||||
"generate": "Generate", // Generieren
|
||||
"regenerate": "Regenerate", // Regenerieren
|
||||
"apiKeyCopied": "API key copied to clipboard", // API-Schlüssel in Zwischenablage kopiert
|
||||
"errors": {
|
||||
"fillAllFields": "Please fill in all fields", // Bitte füllen Sie alle Felder aus
|
||||
"passwordsNotMatch": "The new passwords do not match", // Die neuen Passwörter stimmen nicht überein
|
||||
"passwordTooShort": "The new password must be at least 8 characters long", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"passwordUpdateError": "Error updating password", // Fehler beim Aktualisieren des Passworts
|
||||
"invalidEmail": "Please enter a valid email address", // Bitte geben Sie eine gültige E-Mail-Adresse ein
|
||||
"emailUpdateError": "Error updating email address", // Fehler beim Aktualisieren der E-Mail-Adresse
|
||||
"userNotFound": "User not found", // Benutzer nicht gefunden
|
||||
"apiKeyGenerationError": "Error generating API key" // Fehler beim Generieren des API-Schlüssels
|
||||
},
|
||||
"success": {
|
||||
"passwordUpdated": "Password updated successfully", // Passwort erfolgreich aktualisiert
|
||||
"emailUpdated": "Email address updated successfully", // E-Mail-Adresse erfolgreich aktualisiert
|
||||
"apiKeyGenerated": "API key generated successfully", // API-Schlüssel erfolgreich generiert
|
||||
"apiKeyWarning": "Store this key securely. For security reasons, it will be hidden in 10 seconds." // Speichern Sie diesen Schlüssel sicher. Er wird aus Sicherheitsgründen in 10 Sekunden ausgeblendet.
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/es/translation.js
Normal file
231
src/i18n/locales/es/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "es-ES" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Inicio", // Startseite
|
||||
"aktionen": "Promociones", // Aktionen
|
||||
"filiale": "Sucursal", // Filiale
|
||||
"categories": "Categorías", // Kategorien
|
||||
"categoriesOpen": "Abrir categorías", // Kategorien öffnen
|
||||
"categoriesClose": "Cerrar categorías", // Kategorien schließen
|
||||
"otherCategories": "Otras categorías" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Iniciar sesión", // Anmelden
|
||||
"register": "Registrarse", // Registrieren
|
||||
"logout": "Cerrar sesión", // Abmelden
|
||||
"profile": "Perfil", // Profil
|
||||
"email": "Correo electrónico", // E-Mail
|
||||
"password": "Contraseña", // Passwort
|
||||
"confirmPassword": "Confirmar contraseña", // Passwort bestätigen
|
||||
"forgotPassword": "¿Olvidaste tu contraseña?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Iniciar sesión con Google", // Mit Google anmelden
|
||||
"or": "O", // ODER
|
||||
"privacyAccept": "Al hacer clic en \"Iniciar sesión con Google\" acepto la", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Política de privacidad", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "La contraseña debe tener al menos 8 caracteres", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "La nueva contraseña debe tener al menos 8 caracteres", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Perfil", // Profil
|
||||
"checkout": "Finalizar compra", // Bestellabschluss
|
||||
"orders": "Pedidos", // Bestellungen
|
||||
"settings": "Configuración", // Einstellungen
|
||||
"adminDashboard": "Panel de administración", // Admin Dashboard
|
||||
"adminUsers": "Usuarios admin" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Carrito", // Warenkorb
|
||||
"empty": "vacío", // leer
|
||||
"addToCart": "Añadir al carrito", // In den Korb
|
||||
"preorderCutting": "Preordenar como esqueje", // Als Steckling vorbestellen
|
||||
"continueShopping": "Continuar comprando", // Weiter einkaufen
|
||||
"proceedToCheckout": "Proceder al pago", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Producto} other {Productos}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Eliminar del carrito", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Abrir carrito", // Warenkorb öffnen
|
||||
"availableFrom": "Disponible desde {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Volver al pedido", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Sincronización del carrito", // Warenkorb-Synchronisierung
|
||||
"description": "Tienes un carrito guardado en tu cuenta. Por favor elige cómo quieres proceder:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Eliminar carrito del servidor", // Server-Warenkorb löschen
|
||||
"useServer": "Usar carrito del servidor", // Server-Warenkorb übernehmen
|
||||
"merge": "Combinar carritos", // Warenkörbe zusammenführen
|
||||
"currentCart": "Tu carrito actual", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Carrito guardado en tu perfil" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Cargando producto...", // Produkt wird geladen...
|
||||
"notFound": "Producto no encontrado", // Produkt nicht gefunden
|
||||
"notFoundDescription": "El producto que buscas no existe o ha sido eliminado.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Volver al inicio", // Zurück zur Startseite
|
||||
"error": "Error", // Fehler
|
||||
"articleNumber": "Número de artículo", // Artikelnummer
|
||||
"manufacturer": "Fabricante", // Hersteller
|
||||
"inclVat": "incl. {{vat}}% IVA", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Nuevo", // Neu
|
||||
"arriving": "Llegando:", // Ankunft:
|
||||
"inclVatFooter": "incl. {{vat}}% IVA,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Disponibilidad", // Verfügbarkeit
|
||||
"inStock": "en stock", // auf Lager
|
||||
"comingSoon": "Próximamente", // Bald verfügbar
|
||||
"deliveryTime": "Tiempo de entrega", // Lieferzeit
|
||||
"inclShort": "incl.", // inkl.
|
||||
"vatShort": "IVA", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 productos", // 0 Produkte
|
||||
"oneProduct": "1 producto", // 1 Produkt
|
||||
"multipleProducts": "{{count}} productos", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} de {{total}} productos", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} de 1 producto" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Elimina filtros para ver productos", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Agotado", // Out of Stock
|
||||
"fromXProducts": "desde {{count}} productos" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Puedes preguntarme sobre variedades de cannabis...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Grabando...", // Aufnahme läuft...
|
||||
"searchProducts": "Buscar productos..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Nombre", // Name
|
||||
"searchField": "Término de búsqueda", // Suchbegriff
|
||||
"priceLowHigh": "Precio: de menor a mayor", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Precio: de mayor a menor" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Leído y aceptado" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Mercancía voluminosa", // Sperrgut
|
||||
"pickup": "Recoger en sucursal" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Envío estándar", // Standardversand
|
||||
"standardFree": "Envío estándar - ¡GRATIS a partir de 100€ de pedido!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "No seleccionable porque uno o más artículos solo pueden recogerse", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Para artículos grandes y pesados" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "gratis", // kostenlos
|
||||
"freeFrom100": "(gratis desde 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "€6.99", // 6,99 €
|
||||
"dpd": "€4.90", // 4,90 €
|
||||
"sperrgut": "€28.99" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Tiempo de entrega: 14 días", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Tiempo de entrega: 2-3 días", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Tiempo de entrega: 7-9 días" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Dirección de facturación", // Rechnungsadresse
|
||||
"deliveryAddress": "Dirección de entrega", // Lieferadresse
|
||||
"saveForFuture": "Guardar para futuros pedidos", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "¿Para qué fecha quieres recoger los esquejes?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Nota", // Anmerkung
|
||||
"sameAddress": "La dirección de entrega es igual a la de facturación", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "He leído los Términos y Condiciones, la Política de Privacidad y la información sobre el Derecho de desistimiento" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "¡Pago exitoso!", // Zahlung erfolgreich!
|
||||
"failed": "Pago fallido", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 ¡Tu pedido se ha completado con éxito! Ahora puedes ver tus pedidos.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Tu pago fue procesado con éxito. El pedido se completará automáticamente.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "No se pudo procesar tu pago. Por favor, inténtalo de nuevo o elige otro método de pago.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Ver mis pedidos" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Ordenar", // Sortierung
|
||||
"perPage": "por página", // pro Seite
|
||||
"availability": "Disponibilidad", // Verfügbarkeit
|
||||
"manufacturer": "Fabricante" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Impuesto al Valor Agregado", // Mehrwertsteuer
|
||||
"vat7": "7% Impuesto al Valor Agregado", // 7% Mehrwertsteuer
|
||||
"vat19": "19% Impuesto al Valor Agregado", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% Impuesto al Valor Agregado (incl. envío)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Precio neto total", // Gesamtnettopreis
|
||||
"totalGross": "Precio bruto total sin incluir envío", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Subtotal" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Sáb 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Entre la parada Pieschen y Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Todos los precios incluyen IVA legal, más envío", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Privacidad", // Datenschutz
|
||||
"agb": "Términos y condiciones", // AGB
|
||||
"sitemap": "Mapa del sitio", // Sitemap
|
||||
"impressum": "Imprint", // Impressum
|
||||
"batteriegesetzhinweise": "Notas sobre la ley de baterías", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Derecho de desistimiento" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Semillas y esquejes finos de cannabis", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Promociones y ofertas actuales", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Nuestra sucursal en Dresden" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Semillas", // Seeds
|
||||
"stecklinge": "Esquejes", // Stecklinge
|
||||
"oilPress": "Prestar prensa de aceite", // Ölpresse ausleihen
|
||||
"thcTest": "Test de THC", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Prestar prensa de aceite", // Ölpresse ausleihen
|
||||
"comingSoon": "Contenido próximamente..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "Test de THC", // THC Test
|
||||
"comingSoon": "Contenido próximamente..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "En proceso", // in Bearbeitung
|
||||
"pending": "Nuevo", // Neu
|
||||
"processing": "En proceso", // in Bearbeitung
|
||||
"cancelled": "Cancelado", // Storniert
|
||||
"shipped": "Enviado", // Verschickt
|
||||
"delivered": "Entregado", // Geliefert
|
||||
"return": "Devolución", // Retoure
|
||||
"partialReturn": "Devolución parcial", // Teil Retoure
|
||||
"partialDelivered": "Entregado parcialmente" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Cargando...", // Lädt...
|
||||
"error": "Error", // Fehler
|
||||
"close": "Cerrar", // Schließen
|
||||
"save": "Guardar", // Speichern
|
||||
"cancel": "Cancelar", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Sí", // Ja
|
||||
"no": "No", // Nein
|
||||
"next": "Siguiente", // Weiter
|
||||
"back": "Atrás", // Zurück
|
||||
"edit": "Editar", // Bearbeiten
|
||||
"delete": "Eliminar", // Löschen
|
||||
"add": "Añadir", // Hinzufügen
|
||||
"remove": "Quitar", // Entfernen
|
||||
"products": "Productos", // Produkte
|
||||
"product": "Producto" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/fr/translation.js
Normal file
231
src/i18n/locales/fr/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "fr-FR" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Accueil", // Startseite
|
||||
"aktionen": "Promotions", // Aktionen
|
||||
"filiale": "Agence", // Filiale
|
||||
"categories": "Catégories", // Kategorien
|
||||
"categoriesOpen": "Ouvrir les catégories", // Kategorien öffnen
|
||||
"categoriesClose": "Fermer les catégories", // Kategorien schließen
|
||||
"otherCategories": "Autres catégories" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Connexion", // Anmelden
|
||||
"register": "Inscription", // Registrieren
|
||||
"logout": "Déconnexion", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Mot de passe", // Passwort
|
||||
"confirmPassword": "Confirmer le mot de passe", // Passwort bestätigen
|
||||
"forgotPassword": "Mot de passe oublié ?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Se connecter avec Google", // Mit Google anmelden
|
||||
"or": "OU", // ODER
|
||||
"privacyAccept": "En cliquant sur \"Se connecter avec Google\", j'accepte la", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Politique de confidentialité", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Le mot de passe doit contenir au moins 8 caractères", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Le nouveau mot de passe doit contenir au moins 8 caractères", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Paiement", // Bestellabschluss
|
||||
"orders": "Commandes", // Bestellungen
|
||||
"settings": "Paramètres", // Einstellungen
|
||||
"adminDashboard": "Tableau de bord Admin", // Admin Dashboard
|
||||
"adminUsers": "Utilisateurs Admin" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Panier", // Warenkorb
|
||||
"empty": "vide", // leer
|
||||
"addToCart": "Ajouter au panier", // In den Korb
|
||||
"preorderCutting": "Précommander en tant que bouture", // Als Steckling vorbestellen
|
||||
"continueShopping": "Continuer vos achats", // Weiter einkaufen
|
||||
"proceedToCheckout": "Passer à la caisse", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Produit} other {Produits}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Retirer du panier", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Ouvrir le panier", // Warenkorb öffnen
|
||||
"availableFrom": "Disponible à partir du {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Retour à la commande", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Synchronisation du panier", // Warenkorb-Synchronisierung
|
||||
"description": "Vous avez un panier enregistré dans votre compte. Veuillez choisir comment vous souhaitez procéder :", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Supprimer le panier serveur", // Server-Warenkorb löschen
|
||||
"useServer": "Utiliser le panier serveur", // Server-Warenkorb übernehmen
|
||||
"merge": "Fusionner les paniers", // Warenkörbe zusammenführen
|
||||
"currentCart": "Votre panier actuel", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Panier enregistré dans votre profil" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Chargement du produit...", // Produkt wird geladen...
|
||||
"notFound": "Produit non trouvé", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Le produit que vous recherchez n'existe pas ou a été supprimé.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Retour à l'accueil", // Zurück zur Startseite
|
||||
"error": "Erreur", // Fehler
|
||||
"articleNumber": "Numéro d'article", // Artikelnummer
|
||||
"manufacturer": "Fabricant", // Hersteller
|
||||
"inclVat": "TTC {{vat}}%", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Nouveau", // Neu
|
||||
"arriving": "Arrivée :", // Ankunft:
|
||||
"inclVatFooter": "TTC {{vat}}%,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Disponibilité", // Verfügbarkeit
|
||||
"inStock": "en stock", // auf Lager
|
||||
"comingSoon": "Bientôt disponible", // Bald verfügbar
|
||||
"deliveryTime": "Délai de livraison", // Lieferzeit
|
||||
"inclShort": "TTC", // inkl.
|
||||
"vatShort": "TVA", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 produit", // 0 Produkte
|
||||
"oneProduct": "1 produit", // 1 Produkt
|
||||
"multipleProducts": "{{count}} produits", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} sur {{total}} produits", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} sur 1 produit" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Supprimez les filtres pour voir les produits", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Rupture de stock", // Out of Stock
|
||||
"fromXProducts": "à partir de {{count}} produits" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Vous pouvez me demander des variétés de cannabis...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Enregistrement...", // Aufnahme läuft...
|
||||
"searchProducts": "Rechercher des produits..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Nom", // Name
|
||||
"searchField": "Terme de recherche", // Suchbegriff
|
||||
"priceLowHigh": "Prix : du plus bas au plus élevé", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Prix : du plus élevé au plus bas" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Lu & Accepté" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Marchandises volumineuses", // Sperrgut
|
||||
"pickup": "Retrait en agence" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Livraison standard", // Standardversand
|
||||
"standardFree": "Livraison standard - GRATUITE à partir de 100€ d'achat !", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Non sélectionnable car un ou plusieurs articles ne peuvent être que retirés", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Pour les articles volumineux et lourds" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "gratuit", // kostenlos
|
||||
"freeFrom100": "(gratuit à partir de 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Délai de livraison : 14 jours", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Délai de livraison : 2-3 jours", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Délai de livraison : 7-9 jours" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Adresse de facturation", // Rechnungsadresse
|
||||
"deliveryAddress": "Adresse de livraison", // Lieferadresse
|
||||
"saveForFuture": "Enregistrer pour les commandes futures", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Pour quelle date souhaitez-vous retirer les boutures ?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Note", // Anmerkung
|
||||
"sameAddress": "L'adresse de livraison est identique à l'adresse de facturation", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "J'ai lu les Conditions Générales, la Politique de Confidentialité et les informations sur le droit de rétractation" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Paiement réussi !", // Zahlung erfolgreich!
|
||||
"failed": "Paiement échoué", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Votre commande a été complétée avec succès ! Vous pouvez maintenant consulter vos commandes.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Votre paiement a été traité avec succès. La commande sera automatiquement finalisée.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Votre paiement n'a pas pu être traité. Veuillez réessayer ou choisir un autre mode de paiement.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Voir mes commandes" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Tri", // Sortierung
|
||||
"perPage": "par page", // pro Seite
|
||||
"availability": "Disponibilité", // Verfügbarkeit
|
||||
"manufacturer": "Fabricant" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Taxe sur la valeur ajoutée", // Mehrwertsteuer
|
||||
"vat7": "Taxe sur la valeur ajoutée de 7%", // 7% Mehrwertsteuer
|
||||
"vat19": "Taxe sur la valeur ajoutée de 19%", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "Taxe sur la valeur ajoutée de 19% (frais de port inclus)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Prix total net", // Gesamtnettopreis
|
||||
"totalGross": "Prix total brut hors frais de port", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Sous-total" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Sam 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresde", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Entre l'arrêt Pieschen et Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Tous les prix incluent la TVA légale, frais de port en sus", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Confidentialité", // Datenschutz
|
||||
"agb": "Conditions générales", // AGB
|
||||
"sitemap": "Plan du site", // Sitemap
|
||||
"impressum": "Mentions légales", // Impressum
|
||||
"batteriegesetzhinweise": "Notes sur la loi sur les batteries", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Droit de rétractation" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Graines & Boutures de Cannabis de Qualité", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Promotions & Offres en cours", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Notre agence à Dresde" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Graines", // Seeds
|
||||
"stecklinge": "Boutures", // Stecklinge
|
||||
"oilPress": "Emprunter une presse à huile", // Ölpresse ausleihen
|
||||
"thcTest": "Test THC", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresde" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Emprunter une presse à huile", // Ölpresse ausleihen
|
||||
"comingSoon": "Contenu à venir..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "Test THC", // THC Test
|
||||
"comingSoon": "Contenu à venir..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "En traitement", // in Bearbeitung
|
||||
"pending": "Nouveau", // Neu
|
||||
"processing": "En traitement", // in Bearbeitung
|
||||
"cancelled": "Annulé", // Storniert
|
||||
"shipped": "Expédié", // Verschickt
|
||||
"delivered": "Livré", // Geliefert
|
||||
"return": "Retour", // Retoure
|
||||
"partialReturn": "Retour partiel", // Teil Retoure
|
||||
"partialDelivered": "Partiellement livré" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Chargement...", // Lädt...
|
||||
"error": "Erreur", // Fehler
|
||||
"close": "Fermer", // Schließen
|
||||
"save": "Enregistrer", // Speichern
|
||||
"cancel": "Annuler", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Oui", // Ja
|
||||
"no": "Non", // Nein
|
||||
"next": "Suivant", // Weiter
|
||||
"back": "Retour", // Zurück
|
||||
"edit": "Modifier", // Bearbeiten
|
||||
"delete": "Supprimer", // Löschen
|
||||
"add": "Ajouter", // Hinzufügen
|
||||
"remove": "Retirer", // Entfernen
|
||||
"products": "Produits", // Produkte
|
||||
"product": "Produit" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/hr/translation.js
Normal file
231
src/i18n/locales/hr/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "hr-HR" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Početna", // Startseite
|
||||
"aktionen": "Promocije", // Aktionen
|
||||
"filiale": "Podružnica", // Filiale
|
||||
"categories": "Kategorije", // Kategorien
|
||||
"categoriesOpen": "Otvori kategorije", // Kategorien öffnen
|
||||
"categoriesClose": "Zatvori kategorije", // Kategorien schließen
|
||||
"otherCategories": "Ostale kategorije" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Prijava", // Anmelden
|
||||
"register": "Registracija", // Registrieren
|
||||
"logout": "Odjava", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Lozinka", // Passwort
|
||||
"confirmPassword": "Potvrdi lozinku", // Passwort bestätigen
|
||||
"forgotPassword": "Zaboravili ste lozinku?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Prijavi se putem Googlea", // Mit Google anmelden
|
||||
"or": "ILI", // ODER
|
||||
"privacyAccept": "Klikom na \"Prijavi se putem Googlea\" prihvaćam", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Politiku privatnosti", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Lozinka mora imati najmanje 8 znakova", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Nova lozinka mora imati najmanje 8 znakova", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Blagajna", // Bestellabschluss
|
||||
"orders": "Narudžbe", // Bestellungen
|
||||
"settings": "Postavke", // Einstellungen
|
||||
"adminDashboard": "Administratorska ploča", // Admin Dashboard
|
||||
"adminUsers": "Administratori" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Košarica", // Warenkorb
|
||||
"empty": "prazno", // leer
|
||||
"addToCart": "Dodaj u košaricu", // In den Korb
|
||||
"preorderCutting": "Prednaruči kao reznicu", // Als Steckling vorbestellen
|
||||
"continueShopping": "Nastavi kupovati", // Weiter einkaufen
|
||||
"proceedToCheckout": "Nastavi na blagajnu", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Proizvod} other {Proizvoda}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Ukloni iz košarice", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Otvori košaricu", // Warenkorb öffnen
|
||||
"availableFrom": "Dostupno od {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Nazad na narudžbu", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Sinkronizacija košarice", // Warenkorb-Synchronisierung
|
||||
"description": "Imate spremljenu košaricu na svom računu. Molimo odaberite kako želite nastaviti:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Izbriši košaricu na serveru", // Server-Warenkorb löschen
|
||||
"useServer": "Koristi košaricu sa servera", // Server-Warenkorb übernehmen
|
||||
"merge": "Spoji košarice", // Warenkörbe zusammenführen
|
||||
"currentCart": "Vaša trenutna košarica", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Košarica spremljena u vašem profilu" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Učitavanje proizvoda...", // Produkt wird geladen...
|
||||
"notFound": "Proizvod nije pronađen", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Proizvod koji tražite ne postoji ili je uklonjen.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Natrag na početnu", // Zurück zur Startseite
|
||||
"error": "Greška", // Fehler
|
||||
"articleNumber": "Broj artikla", // Artikelnummer
|
||||
"manufacturer": "Proizvođač", // Hersteller
|
||||
"inclVat": "uključujući {{vat}}% PDV-a", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Novo", // Neu
|
||||
"arriving": "Dolazi:", // Ankunft:
|
||||
"inclVatFooter": "uključujući {{vat}}% PDV-a,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Dostupnost", // Verfügbarkeit
|
||||
"inStock": "na skladištu", // auf Lager
|
||||
"comingSoon": "Uskoro dostupno", // Bald verfügbar
|
||||
"deliveryTime": "Vrijeme isporuke", // Lieferzeit
|
||||
"inclShort": "uklj.", // inkl.
|
||||
"vatShort": "PDV", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 proizvoda", // 0 Produkte
|
||||
"oneProduct": "1 proizvod", // 1 Produkt
|
||||
"multipleProducts": "{{count}} proizvoda", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} od {{total}} proizvoda", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} od 1 proizvoda" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Uklonite filtere da vidite proizvode", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Nema na skladištu", // Out of Stock
|
||||
"fromXProducts": "od {{count}} proizvoda" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Možete me pitati o sortama kanabisa...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Snimanje...", // Aufnahme läuft...
|
||||
"searchProducts": "Pretraži proizvode..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Naziv", // Name
|
||||
"searchField": "Pojam za pretraživanje", // Suchbegriff
|
||||
"priceLowHigh": "Cijena: od najniže do najviše", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Cijena: od najviše do najniže" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Pročitano i prihvaćeno" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Glomazna roba", // Sperrgut
|
||||
"pickup": "Preuzimanje u podružnici" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Standardna dostava", // Standardversand
|
||||
"standardFree": "Standardna dostava - BESPLATNO za narudžbe iznad 100€!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Nije moguće odabrati jer se jedan ili više artikala mogu samo preuzeti", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Za velike i teške artikle" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "besplatno", // kostenlos
|
||||
"freeFrom100": "(besplatno od 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Vrijeme isporuke: 14 dana", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Vrijeme isporuke: 2-3 dana", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Vrijeme isporuke: 7-9 dana" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Adresa za račun", // Rechnungsadresse
|
||||
"deliveryAddress": "Adresa za dostavu", // Lieferadresse
|
||||
"saveForFuture": "Spremi za buduće narudžbe", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Za koji datum želite preuzeti reznice?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Napomena", // Anmerkung
|
||||
"sameAddress": "Adresa za dostavu je ista kao adresa za račun", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Pročitao/la sam Uvjete korištenja, Politiku privatnosti i informacije o pravu na odustajanje" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Plaćanje uspješno!", // Zahlung erfolgreich!
|
||||
"failed": "Plaćanje nije uspjelo", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Vaša narudžba je uspješno dovršena! Sada možete pregledati svoje narudžbe.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Vaša uplata je uspješno obrađena. Narudžba će biti automatski dovršena.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Vaša uplata nije mogla biti obrađena. Molimo pokušajte ponovno ili odaberite drugi način plaćanja.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Pogledaj moje narudžbe" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Sortiranje", // Sortierung
|
||||
"perPage": "po stranici", // pro Seite
|
||||
"availability": "Dostupnost", // Verfügbarkeit
|
||||
"manufacturer": "Proizvođač" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Porez na dodanu vrijednost", // Mehrwertsteuer
|
||||
"vat7": "7% poreza na dodanu vrijednost", // 7% Mehrwertsteuer
|
||||
"vat19": "19% poreza na dodanu vrijednost", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% poreza na dodanu vrijednost (uključujući dostavu)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Ukupna neto cijena", // Gesamtnettopreis
|
||||
"totalGross": "Ukupna bruto cijena bez dostave", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Međuzbroj" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Sub 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Između stanice Pieschen i Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Sve cijene uključuju zakonski PDV, plus dostava", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Privatnost", // Datenschutz
|
||||
"agb": "Uvjeti i odredbe", // AGB
|
||||
"sitemap": "Karta weba", // Sitemap
|
||||
"impressum": "Impressum", // Impressum
|
||||
"batteriegesetzhinweise": "Napomene o zakonu o baterijama", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Pravo na odustajanje" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Kvalitetne sjemenke i reznice kanabisa", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Trenutne promocije i ponude", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Naša podružnica u Dresdenu" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Sjemenke", // Seeds
|
||||
"stecklinge": "Reznice", // Stecklinge
|
||||
"oilPress": "Posudi prešu za ulje", // Ölpresse ausleihen
|
||||
"thcTest": "THC test", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Posudi prešu za ulje", // Ölpresse ausleihen
|
||||
"comingSoon": "Sadržaj uskoro dolazi..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC test", // THC Test
|
||||
"comingSoon": "Sadržaj uskoro dolazi..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "U obradi", // in Bearbeitung
|
||||
"pending": "Novo", // Neu
|
||||
"processing": "U obradi", // in Bearbeitung
|
||||
"cancelled": "Otkazano", // Storniert
|
||||
"shipped": "Poslano", // Verschickt
|
||||
"delivered": "Isporučeno", // Geliefert
|
||||
"return": "Povrat", // Retoure
|
||||
"partialReturn": "Djelomični povrat", // Teil Retoure
|
||||
"partialDelivered": "Djelomično isporučeno" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Učitavanje...", // Lädt...
|
||||
"error": "Greška", // Fehler
|
||||
"close": "Zatvori", // Schließen
|
||||
"save": "Spremi", // Speichern
|
||||
"cancel": "Odustani", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Da", // Ja
|
||||
"no": "Ne", // Nein
|
||||
"next": "Dalje", // Weiter
|
||||
"back": "Nazad", // Zurück
|
||||
"edit": "Uredi", // Bearbeiten
|
||||
"delete": "Izbriši", // Löschen
|
||||
"add": "Dodaj", // Hinzufügen
|
||||
"remove": "Ukloni", // Entfernen
|
||||
"products": "Proizvodi", // Produkte
|
||||
"product": "Proizvod" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/hu/translation.js
Normal file
231
src/i18n/locales/hu/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "hu-HU" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Kezdőlap", // Startseite
|
||||
"aktionen": "Akciók", // Aktionen
|
||||
"filiale": "Fiók", // Filiale
|
||||
"categories": "Kategóriák", // Kategorien
|
||||
"categoriesOpen": "Kategóriák megnyitása", // Kategorien öffnen
|
||||
"categoriesClose": "Kategóriák bezárása", // Kategorien schließen
|
||||
"otherCategories": "Egyéb kategóriák" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Bejelentkezés", // Anmelden
|
||||
"register": "Regisztráció", // Registrieren
|
||||
"logout": "Kijelentkezés", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Jelszó", // Passwort
|
||||
"confirmPassword": "Jelszó megerősítése", // Passwort bestätigen
|
||||
"forgotPassword": "Elfelejtett jelszó?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Bejelentkezés Google-lal", // Mit Google anmelden
|
||||
"or": "VAGY", // ODER
|
||||
"privacyAccept": "A \"Bejelentkezés Google-lal\" gombra kattintva elfogadom a", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Adatvédelmi szabályzatot", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "A jelszónak legalább 8 karakter hosszúnak kell lennie", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Az új jelszónak legalább 8 karakter hosszúnak kell lennie", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Pénztár", // Bestellabschluss
|
||||
"orders": "Rendelések", // Bestellungen
|
||||
"settings": "Beállítások", // Einstellungen
|
||||
"adminDashboard": "Admin Vezérlőpult", // Admin Dashboard
|
||||
"adminUsers": "Admin Felhasználók" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Kosár", // Warenkorb
|
||||
"empty": "üres", // leer
|
||||
"addToCart": "Kosárba tesz", // In den Korb
|
||||
"preorderCutting": "Előrendelés dugványként", // Als Steckling vorbestellen
|
||||
"continueShopping": "Vásárlás folytatása", // Weiter einkaufen
|
||||
"proceedToCheckout": "Tovább a pénztárhoz", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Termék} other {Termékek}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Eltávolítás a kosárból", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Kosár megnyitása", // Warenkorb öffnen
|
||||
"availableFrom": "{{date}}-tól elérhető", // Ab {{date}}
|
||||
"backToOrder": "← Vissza a rendeléshez", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Kosár szinkronizálás", // Warenkorb-Synchronisierung
|
||||
"description": "Fiókodban van egy mentett kosár. Kérjük, válaszd ki, hogyan szeretnél továbbhaladni:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Szerver kosár törlése", // Server-Warenkorb löschen
|
||||
"useServer": "Szerver kosár használata", // Server-Warenkorb übernehmen
|
||||
"merge": "Kosarak egyesítése", // Warenkörbe zusammenführen
|
||||
"currentCart": "Jelenlegi kosarad", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Profilodban mentett kosár" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Termék betöltése...", // Produkt wird geladen...
|
||||
"notFound": "Termék nem található", // Produkt nicht gefunden
|
||||
"notFoundDescription": "A keresett termék nem létezik vagy eltávolították.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Vissza a kezdőlapra", // Zurück zur Startseite
|
||||
"error": "Hiba", // Fehler
|
||||
"articleNumber": "Cikkszám", // Artikelnummer
|
||||
"manufacturer": "Gyártó", // Hersteller
|
||||
"inclVat": "Áfával együtt {{vat}}%", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Új", // Neu
|
||||
"arriving": "Érkezik:", // Ankunft:
|
||||
"inclVatFooter": "Áfával együtt {{vat}}%*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Elérhetőség", // Verfügbarkeit
|
||||
"inStock": "készleten", // auf Lager
|
||||
"comingSoon": "Hamarosan elérhető", // Bald verfügbar
|
||||
"deliveryTime": "Szállítási idő", // Lieferzeit
|
||||
"inclShort": "áfával", // inkl.
|
||||
"vatShort": "ÁFA", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 termék", // 0 Produkte
|
||||
"oneProduct": "1 termék", // 1 Produkt
|
||||
"multipleProducts": "{{count}} termék", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} / {{total}} termék", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} / 1 termék" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Szűrők eltávolítása a termékek megtekintéséhez", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Nincs készleten", // Out of Stock
|
||||
"fromXProducts": "{{count}} terméktől" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Kérdezhetsz tőlem kannabisz fajtákról...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Felvétel...", // Aufnahme läuft...
|
||||
"searchProducts": "Termékek keresése..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Név", // Name
|
||||
"searchField": "Keresési kifejezés", // Suchbegriff
|
||||
"priceLowHigh": "Ár: alacsonytól a magasig", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Ár: magasról az alacsonyra" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Elolvasva & Elfogadva" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Nagy méretű csomag", // Sperrgut
|
||||
"pickup": "Átvétel a fiókban" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Normál szállítás", // Standardversand
|
||||
"standardFree": "Normál szállítás - INGYENES 100€ rendelési érték felett!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Nem választható, mert egy vagy több termék csak átvétellel elérhető", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Nagy és nehéz termékekhez" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "ingyenes", // kostenlos
|
||||
"freeFrom100": "(ingyenes 100€-tól)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Szállítási idő: 14 nap", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Szállítási idő: 2-3 nap", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Szállítási idő: 7-9 nap" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Számlázási cím", // Rechnungsadresse
|
||||
"deliveryAddress": "Szállítási cím", // Lieferadresse
|
||||
"saveForFuture": "Mentés a jövőbeni rendelésekhez", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Melyik napra szeretnéd átvenni a dugványokat?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Megjegyzés", // Anmerkung
|
||||
"sameAddress": "A szállítási cím megegyezik a számlázási címmel", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Elolvastam az ÁSZF-et, az Adatvédelmi szabályzatot és a Visszavonási tájékoztatót" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Fizetés sikeres!", // Zahlung erfolgreich!
|
||||
"failed": "Fizetés sikertelen", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 A rendelésed sikeresen befejeződött! Most már megtekintheted a rendeléseidet.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "A fizetésed sikeresen feldolgozásra került. A rendelés automatikusan befejeződik.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "A fizetés nem sikerült. Kérjük, próbáld újra vagy válassz másik fizetési módot.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Rendeléseim megtekintése" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Rendezés", // Sortierung
|
||||
"perPage": "oldalanként", // pro Seite
|
||||
"availability": "Elérhetőség", // Verfügbarkeit
|
||||
"manufacturer": "Gyártó" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Általános forgalmi adó", // Mehrwertsteuer
|
||||
"vat7": "7% Általános forgalmi adó", // 7% Mehrwertsteuer
|
||||
"vat19": "19% Általános forgalmi adó", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% Általános forgalmi adó (szállítással együtt)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Nettó összeg", // Gesamtnettopreis
|
||||
"totalGross": "Bruttó összeg szállítás nélkül", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Részösszeg" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Szo 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "A Pieschen megálló és a Trachenberger Platz között", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Minden ár tartalmazza a törvényes ÁFÁ-t, plusz szállítási költség", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Adatvédelem", // Datenschutz
|
||||
"agb": "Általános Szerződési Feltételek", // AGB
|
||||
"sitemap": "Oldaltérkép", // Sitemap
|
||||
"impressum": "Impresszum", // Impressum
|
||||
"batteriegesetzhinweise": "Akkumulátor törvényi tájékoztatók", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Elállási jog" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Minőségi kannabisz magok és dugványok", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Aktuális akciók és ajánlatok", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Fiókunk Dresdenben" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Magok", // Seeds
|
||||
"stecklinge": "Dugványok", // Stecklinge
|
||||
"oilPress": "Olajprés kölcsönzés", // Ölpresse ausleihen
|
||||
"thcTest": "THC teszt", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Olajprés kölcsönzés", // Ölpresse ausleihen
|
||||
"comingSoon": "Tartalom hamarosan..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC teszt", // THC Test
|
||||
"comingSoon": "Tartalom hamarosan..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "Feldolgozás alatt", // in Bearbeitung
|
||||
"pending": "Új", // Neu
|
||||
"processing": "Feldolgozás alatt", // in Bearbeitung
|
||||
"cancelled": "Törölve", // Storniert
|
||||
"shipped": "Kiszállítva", // Verschickt
|
||||
"delivered": "Kézbesítve", // Geliefert
|
||||
"return": "Visszaküldés", // Retoure
|
||||
"partialReturn": "Részleges visszaküldés", // Teil Retoure
|
||||
"partialDelivered": "Részben kézbesítve" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Betöltés...", // Lädt...
|
||||
"error": "Hiba", // Fehler
|
||||
"close": "Bezárás", // Schließen
|
||||
"save": "Mentés", // Speichern
|
||||
"cancel": "Mégse", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Igen", // Ja
|
||||
"no": "Nem", // Nein
|
||||
"next": "Következő", // Weiter
|
||||
"back": "Vissza", // Zurück
|
||||
"edit": "Szerkesztés", // Bearbeiten
|
||||
"delete": "Törlés", // Löschen
|
||||
"add": "Hozzáadás", // Hinzufügen
|
||||
"remove": "Eltávolítás", // Entfernen
|
||||
"products": "Termékek", // Produkte
|
||||
"product": "Termék" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/it/translation.js
Normal file
231
src/i18n/locales/it/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "it-IT" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Home", // Startseite
|
||||
"aktionen": "Promozioni", // Aktionen
|
||||
"filiale": "Filiale", // Filiale
|
||||
"categories": "Categorie", // Kategorien
|
||||
"categoriesOpen": "Apri categorie", // Kategorien öffnen
|
||||
"categoriesClose": "Chiudi categorie", // Kategorien schließen
|
||||
"otherCategories": "Altre categorie" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Accedi", // Anmelden
|
||||
"register": "Registrati", // Registrieren
|
||||
"logout": "Esci", // Abmelden
|
||||
"profile": "Profilo", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Password", // Passwort
|
||||
"confirmPassword": "Conferma password", // Passwort bestätigen
|
||||
"forgotPassword": "Password dimenticata?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Accedi con Google", // Mit Google anmelden
|
||||
"or": "O", // ODER
|
||||
"privacyAccept": "Cliccando su \"Accedi con Google\" accetto la", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Privacy Policy", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "La password deve essere lunga almeno 8 caratteri", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "La nuova password deve essere lunga almeno 8 caratteri", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profilo", // Profil
|
||||
"checkout": "Checkout", // Bestellabschluss
|
||||
"orders": "Ordini", // Bestellungen
|
||||
"settings": "Impostazioni", // Einstellungen
|
||||
"adminDashboard": "Admin Dashboard", // Admin Dashboard
|
||||
"adminUsers": "Admin Users" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Carrello", // Warenkorb
|
||||
"empty": "vuoto", // leer
|
||||
"addToCart": "Aggiungi al carrello", // In den Korb
|
||||
"preorderCutting": "Preordina come talea", // Als Steckling vorbestellen
|
||||
"continueShopping": "Continua lo shopping", // Weiter einkaufen
|
||||
"proceedToCheckout": "Procedi al checkout", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Prodotto} other {Prodotti}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Rimuovi dal carrello", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Apri carrello", // Warenkorb öffnen
|
||||
"availableFrom": "Disponibile da {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Torna all'ordine", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Sincronizzazione carrello", // Warenkorb-Synchronisierung
|
||||
"description": "Hai un carrello salvato nel tuo account. Per favore scegli come procedere:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Elimina carrello server", // Server-Warenkorb löschen
|
||||
"useServer": "Usa carrello server", // Server-Warenkorb übernehmen
|
||||
"merge": "Unisci carrelli", // Warenkörbe zusammenführen
|
||||
"currentCart": "Il tuo carrello attuale", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Carrello salvato nel tuo profilo" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Caricamento prodotto...", // Produkt wird geladen...
|
||||
"notFound": "Prodotto non trovato", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Il prodotto che stai cercando non esiste o è stato rimosso.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Torna alla home", // Zurück zur Startseite
|
||||
"error": "Errore", // Fehler
|
||||
"articleNumber": "Numero articolo", // Artikelnummer
|
||||
"manufacturer": "Produttore", // Hersteller
|
||||
"inclVat": "incl. {{vat}}% IVA", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Nuovo", // Neu
|
||||
"arriving": "In arrivo:", // Ankunft:
|
||||
"inclVatFooter": "incl. {{vat}}% IVA,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Disponibilità", // Verfügbarkeit
|
||||
"inStock": "in stock", // auf Lager
|
||||
"comingSoon": "Prossimamente", // Bald verfügbar
|
||||
"deliveryTime": "Tempi di consegna", // Lieferzeit
|
||||
"inclShort": "incl.", // inkl.
|
||||
"vatShort": "IVA", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 prodotti", // 0 Produkte
|
||||
"oneProduct": "1 prodotto", // 1 Produkt
|
||||
"multipleProducts": "{{count}} prodotti", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} di {{total}} prodotti", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} di 1 prodotto" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Rimuovi i filtri per vedere i prodotti", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Esaurito", // Out of Stock
|
||||
"fromXProducts": "da {{count}} prodotti" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Puoi chiedermi delle varietà di cannabis...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Registrazione in corso...", // Aufnahme läuft...
|
||||
"searchProducts": "Cerca prodotti..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Nome", // Name
|
||||
"searchField": "Termine di ricerca", // Suchbegriff
|
||||
"priceLowHigh": "Prezzo: dal più basso al più alto", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Prezzo: dal più alto al più basso" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Letto & Accettato" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Merci ingombranti", // Sperrgut
|
||||
"pickup": "Ritiro in filiale" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Spedizione standard", // Standardversand
|
||||
"standardFree": "Spedizione standard - GRATIS per ordini superiori a €100!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Non selezionabile perché uno o più articoli possono essere solo ritirati", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Per articoli grandi e pesanti" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "gratis", // kostenlos
|
||||
"freeFrom100": "(gratis da €100)", // (kostenlos ab 100€)
|
||||
"dhl": "€6.99", // 6,99 €
|
||||
"dpd": "€4.90", // 4,90 €
|
||||
"sperrgut": "€28.99" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Tempi di consegna: 14 giorni", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Tempi di consegna: 2-3 giorni", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Tempi di consegna: 7-9 giorni" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Indirizzo di fatturazione", // Rechnungsadresse
|
||||
"deliveryAddress": "Indirizzo di consegna", // Lieferadresse
|
||||
"saveForFuture": "Salva per ordini futuri", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Per quale data desideri ritirare le talee?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Nota", // Anmerkung
|
||||
"sameAddress": "L'indirizzo di consegna è lo stesso dell'indirizzo di fatturazione", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Ho letto i Termini e Condizioni, la Privacy Policy e le informazioni sul Diritto di Recesso" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Pagamento effettuato con successo!", // Zahlung erfolgreich!
|
||||
"failed": "Pagamento fallito", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Il tuo ordine è stato completato con successo! Ora puoi visualizzare i tuoi ordini.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Il tuo pagamento è stato elaborato con successo. L'ordine sarà completato automaticamente.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Il tuo pagamento non è stato elaborato. Per favore riprova o scegli un altro metodo di pagamento.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Visualizza i miei ordini" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Ordinamento", // Sortierung
|
||||
"perPage": "per pagina", // pro Seite
|
||||
"availability": "Disponibilità", // Verfügbarkeit
|
||||
"manufacturer": "Produttore" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Imposta sul valore aggiunto", // Mehrwertsteuer
|
||||
"vat7": "7% Imposta sul valore aggiunto", // 7% Mehrwertsteuer
|
||||
"vat19": "19% Imposta sul valore aggiunto", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% Imposta sul valore aggiunto (incl. spedizione)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Prezzo netto totale", // Gesamtnettopreis
|
||||
"totalGross": "Prezzo lordo totale esclusa spedizione", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Totale parziale" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Sab 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Tra la fermata Pieschen e Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Tutti i prezzi includono IVA legale, più spedizione", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Privacy", // Datenschutz
|
||||
"agb": "Termini & Condizioni", // AGB
|
||||
"sitemap": "Mappa del sito", // Sitemap
|
||||
"impressum": "Impressum", // Impressum
|
||||
"batteriegesetzhinweise": "Note sulla legge sulle batterie", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Diritto di recesso" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Semi e talee di cannabis di alta qualità", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Promozioni e offerte attuali", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "La nostra filiale a Dresda" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Semi", // Seeds
|
||||
"stecklinge": "Talee", // Stecklinge
|
||||
"oilPress": "Noleggia pressa per olio", // Ölpresse ausleihen
|
||||
"thcTest": "Test THC", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Noleggia pressa per olio", // Ölpresse ausleihen
|
||||
"comingSoon": "Contenuto in arrivo..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "Test THC", // THC Test
|
||||
"comingSoon": "Contenuto in arrivo..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "In lavorazione", // in Bearbeitung
|
||||
"pending": "Nuovo", // Neu
|
||||
"processing": "In lavorazione", // in Bearbeitung
|
||||
"cancelled": "Annullato", // Storniert
|
||||
"shipped": "Spedito", // Verschickt
|
||||
"delivered": "Consegnato", // Geliefert
|
||||
"return": "Reso", // Retoure
|
||||
"partialReturn": "Reso parziale", // Teil Retoure
|
||||
"partialDelivered": "Consegnato parzialmente" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Caricamento...", // Lädt...
|
||||
"error": "Errore", // Fehler
|
||||
"close": "Chiudi", // Schließen
|
||||
"save": "Salva", // Speichern
|
||||
"cancel": "Annulla", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Sì", // Ja
|
||||
"no": "No", // Nein
|
||||
"next": "Avanti", // Weiter
|
||||
"back": "Indietro", // Zurück
|
||||
"edit": "Modifica", // Bearbeiten
|
||||
"delete": "Elimina", // Löschen
|
||||
"add": "Aggiungi", // Hinzufügen
|
||||
"remove": "Rimuovi", // Entfernen
|
||||
"products": "Prodotti", // Produkte
|
||||
"product": "Prodotto" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/pl/translation.js
Normal file
231
src/i18n/locales/pl/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "pl-PL" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Strona główna", // Startseite
|
||||
"aktionen": "Promocje", // Aktionen
|
||||
"filiale": "Oddział", // Filiale
|
||||
"categories": "Kategorie", // Kategorien
|
||||
"categoriesOpen": "Otwórz kategorie", // Kategorien öffnen
|
||||
"categoriesClose": "Zamknij kategorie", // Kategorien schließen
|
||||
"otherCategories": "Inne kategorie" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Zaloguj się", // Anmelden
|
||||
"register": "Zarejestruj się", // Registrieren
|
||||
"logout": "Wyloguj się", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Hasło", // Passwort
|
||||
"confirmPassword": "Potwierdź hasło", // Passwort bestätigen
|
||||
"forgotPassword": "Zapomniałeś hasła?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Zaloguj się przez Google", // Mit Google anmelden
|
||||
"or": "LUB", // ODER
|
||||
"privacyAccept": "Klikając „Zaloguj się przez Google” akceptuję", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Politykę prywatności", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Hasło musi mieć co najmniej 8 znaków", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Nowe hasło musi mieć co najmniej 8 znaków", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Zamówienie", // Bestellabschluss
|
||||
"orders": "Zamówienia", // Bestellungen
|
||||
"settings": "Ustawienia", // Einstellungen
|
||||
"adminDashboard": "Panel administratora", // Admin Dashboard
|
||||
"adminUsers": "Użytkownicy administratora" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Koszyk", // Warenkorb
|
||||
"empty": "pusty", // leer
|
||||
"addToCart": "Dodaj do koszyka", // In den Korb
|
||||
"preorderCutting": "Zamów jako sadzonkę", // Als Steckling vorbestellen
|
||||
"continueShopping": "Kontynuuj zakupy", // Weiter einkaufen
|
||||
"proceedToCheckout": "Przejdź do kasy", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Produkt} other {Produkty}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Usuń z koszyka", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Otwórz koszyk", // Warenkorb öffnen
|
||||
"availableFrom": "Dostępne od {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Powrót do zamówienia", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Synchronizacja koszyka", // Warenkorb-Synchronisierung
|
||||
"description": "Masz zapisany koszyk na swoim koncie. Wybierz, jak chcesz kontynuować:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Usuń koszyk z serwera", // Server-Warenkorb löschen
|
||||
"useServer": "Użyj koszyka z serwera", // Server-Warenkorb übernehmen
|
||||
"merge": "Połącz koszyki", // Warenkörbe zusammenführen
|
||||
"currentCart": "Twój aktualny koszyk", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Koszyk zapisany w Twoim profilu" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Ładowanie produktu...", // Produkt wird geladen...
|
||||
"notFound": "Produkt nie znaleziony", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Produkt, którego szukasz, nie istnieje lub został usunięty.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Powrót do strony głównej", // Zurück zur Startseite
|
||||
"error": "Błąd", // Fehler
|
||||
"articleNumber": "Numer artykułu", // Artikelnummer
|
||||
"manufacturer": "Producent", // Hersteller
|
||||
"inclVat": "w tym {{vat}}% VAT", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Nowość", // Neu
|
||||
"arriving": "Nadchodzi:", // Ankunft:
|
||||
"inclVatFooter": "w tym {{vat}}% VAT,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Dostępność", // Verfügbarkeit
|
||||
"inStock": "na stanie", // auf Lager
|
||||
"comingSoon": "Wkrótce dostępne", // Bald verfügbar
|
||||
"deliveryTime": "Czas dostawy", // Lieferzeit
|
||||
"inclShort": "w tym", // inkl.
|
||||
"vatShort": "VAT", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 produktów", // 0 Produkte
|
||||
"oneProduct": "1 produkt", // 1 Produkt
|
||||
"multipleProducts": "{{count}} produktów", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} z {{total}} produktów", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} z 1 produktu" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Usuń filtry, aby zobaczyć produkty", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Brak na stanie", // Out of Stock
|
||||
"fromXProducts": "od {{count}} produktów" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Możesz zapytać mnie o odmiany konopi...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Nagrywanie...", // Aufnahme läuft...
|
||||
"searchProducts": "Szukaj produktów..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Nazwa", // Name
|
||||
"searchField": "Fraza wyszukiwania", // Suchbegriff
|
||||
"priceLowHigh": "Cena: od najniższej do najwyższej", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Cena: od najwyższej do najniższej" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Przeczytano i zaakceptowano" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Towary wielkogabarytowe", // Sperrgut
|
||||
"pickup": "Odbiór w oddziale" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Standardowa wysyłka", // Standardversand
|
||||
"standardFree": "Standardowa wysyłka - GRATIS od zamówienia za 100€!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Nie można wybrać, ponieważ jeden lub więcej produktów można odebrać tylko osobiście", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Dla dużych i ciężkich przedmiotów" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "bezpłatnie", // kostenlos
|
||||
"freeFrom100": "(bezpłatnie od 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Czas dostawy: 14 dni", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Czas dostawy: 2-3 dni", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Czas dostawy: 7-9 dni" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Adres do faktury", // Rechnungsadresse
|
||||
"deliveryAddress": "Adres dostawy", // Lieferadresse
|
||||
"saveForFuture": "Zapisz na przyszłe zamówienia", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Na jaki termin chcesz odebrać sadzonki?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Notatka", // Anmerkung
|
||||
"sameAddress": "Adres dostawy jest taki sam jak adres do faktury", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Przeczytałem Warunki, Politykę prywatności oraz informacje o prawie do odstąpienia od umowy" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Płatność zakończona sukcesem!", // Zahlung erfolgreich!
|
||||
"failed": "Płatność nie powiodła się", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Twoje zamówienie zostało pomyślnie zrealizowane! Możesz teraz przeglądać swoje zamówienia.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Twoja płatność została pomyślnie przetworzona. Zamówienie zostanie automatycznie zakończone.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Twoja płatność nie mogła zostać przetworzona. Spróbuj ponownie lub wybierz inną metodę płatności.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Zobacz moje zamówienia" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Sortowanie", // Sortierung
|
||||
"perPage": "na stronę", // pro Seite
|
||||
"availability": "Dostępność", // Verfügbarkeit
|
||||
"manufacturer": "Producent" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Podatek VAT", // Mehrwertsteuer
|
||||
"vat7": "7% podatek VAT", // 7% Mehrwertsteuer
|
||||
"vat19": "19% podatek VAT", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% podatek VAT (w tym wysyłka)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Łączna cena netto", // Gesamtnettopreis
|
||||
"totalGross": "Łączna cena brutto bez wysyłki", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Suma częściowa" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Sob 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Drezno", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Między przystankiem Pieschen a Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Wszystkie ceny zawierają ustawowy VAT, plus koszty wysyłki", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Prywatność", // Datenschutz
|
||||
"agb": "Regulamin", // AGB
|
||||
"sitemap": "Mapa strony", // Sitemap
|
||||
"impressum": "Impressum", // Impressum
|
||||
"batteriegesetzhinweise": "Informacje o ustawie o bateriach", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Prawo do odstąpienia" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Wysokiej jakości nasiona i sadzonki konopi", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Aktualne promocje i oferty", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Nasz oddział w Dreźnie" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Nasiona", // Seeds
|
||||
"stecklinge": "Sadzonki", // Stecklinge
|
||||
"oilPress": "Wypożycz praskę do oleju", // Ölpresse ausleihen
|
||||
"thcTest": "Test THC", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Drezno" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Wypożycz praskę do oleju", // Ölpresse ausleihen
|
||||
"comingSoon": "Zawartość wkrótce..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "Test THC", // THC Test
|
||||
"comingSoon": "Zawartość wkrótce..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "W trakcie realizacji", // in Bearbeitung
|
||||
"pending": "Nowe", // Neu
|
||||
"processing": "W trakcie realizacji", // in Bearbeitung
|
||||
"cancelled": "Anulowane", // Storniert
|
||||
"shipped": "Wysłane", // Verschickt
|
||||
"delivered": "Dostarczone", // Geliefert
|
||||
"return": "Zwrot", // Retoure
|
||||
"partialReturn": "Częściowy zwrot", // Teil Retoure
|
||||
"partialDelivered": "Częściowo dostarczone" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Ładowanie...", // Lädt...
|
||||
"error": "Błąd", // Fehler
|
||||
"close": "Zamknij", // Schließen
|
||||
"save": "Zapisz", // Speichern
|
||||
"cancel": "Anuluj", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Tak", // Ja
|
||||
"no": "Nie", // Nein
|
||||
"next": "Dalej", // Weiter
|
||||
"back": "Wstecz", // Zurück
|
||||
"edit": "Edytuj", // Bearbeiten
|
||||
"delete": "Usuń", // Löschen
|
||||
"add": "Dodaj", // Hinzufügen
|
||||
"remove": "Usuń", // Entfernen
|
||||
"products": "Produkty", // Produkte
|
||||
"product": "Produkt" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/ro/translation.js
Normal file
231
src/i18n/locales/ro/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "ro-RO" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Acasă", // Startseite
|
||||
"aktionen": "Promoții", // Aktionen
|
||||
"filiale": "Sucursală", // Filiale
|
||||
"categories": "Categorii", // Kategorien
|
||||
"categoriesOpen": "Deschide categoriile", // Kategorien öffnen
|
||||
"categoriesClose": "Închide categoriile", // Kategorien schließen
|
||||
"otherCategories": "Alte categorii" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Autentificare", // Anmelden
|
||||
"register": "Înregistrare", // Registrieren
|
||||
"logout": "Deconectare", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Parolă", // Passwort
|
||||
"confirmPassword": "Confirmă parola", // Passwort bestätigen
|
||||
"forgotPassword": "Ai uitat parola?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Autentifică-te cu Google", // Mit Google anmelden
|
||||
"or": "SAU", // ODER
|
||||
"privacyAccept": "Prin clic pe „Autentifică-te cu Google” accept", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Politica de confidențialitate", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Parola trebuie să aibă cel puțin 8 caractere", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Noua parolă trebuie să aibă cel puțin 8 caractere", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Finalizare comandă", // Bestellabschluss
|
||||
"orders": "Comenzi", // Bestellungen
|
||||
"settings": "Setări", // Einstellungen
|
||||
"adminDashboard": "Panou Admin", // Admin Dashboard
|
||||
"adminUsers": "Utilizatori Admin" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Coș", // Warenkorb
|
||||
"empty": "gol", // leer
|
||||
"addToCart": "Adaugă în coș", // In den Korb
|
||||
"preorderCutting": "Precomandă ca butași", // Als Steckling vorbestellen
|
||||
"continueShopping": "Continuă cumpărăturile", // Weiter einkaufen
|
||||
"proceedToCheckout": "Continuă către plată", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Produs} other {Produse}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Elimină din coș", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Deschide coșul", // Warenkorb öffnen
|
||||
"availableFrom": "Disponibil din {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Înapoi la comandă", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Sincronizare coș", // Warenkorb-Synchronisierung
|
||||
"description": "Ai un coș salvat în contul tău. Te rugăm să alegi cum dorești să continui:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Șterge coșul de pe server", // Server-Warenkorb löschen
|
||||
"useServer": "Folosește coșul de pe server", // Server-Warenkorb übernehmen
|
||||
"merge": "Combină coșurile", // Warenkörbe zusammenführen
|
||||
"currentCart": "Coșul tău curent", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Coș salvat în profilul tău" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Se încarcă produsul...", // Produkt wird geladen...
|
||||
"notFound": "Produsul nu a fost găsit", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Produsul căutat nu există sau a fost eliminat.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Înapoi la pagina principală", // Zurück zur Startseite
|
||||
"error": "Eroare", // Fehler
|
||||
"articleNumber": "Număr articol", // Artikelnummer
|
||||
"manufacturer": "Producător", // Hersteller
|
||||
"inclVat": "incl. {{vat}}% TVA", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Nou", // Neu
|
||||
"arriving": "Sosire:", // Ankunft:
|
||||
"inclVatFooter": "incl. {{vat}}% TVA,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Disponibilitate", // Verfügbarkeit
|
||||
"inStock": "în stoc", // auf Lager
|
||||
"comingSoon": "În curând", // Bald verfügbar
|
||||
"deliveryTime": "Timp de livrare", // Lieferzeit
|
||||
"inclShort": "incl.", // inkl.
|
||||
"vatShort": "TVA", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 produse", // 0 Produkte
|
||||
"oneProduct": "1 produs", // 1 Produkt
|
||||
"multipleProducts": "{{count}} produse", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} din {{total}} produse", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} din 1 produs" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Elimină filtrele pentru a vedea produsele", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Stoc epuizat", // Out of Stock
|
||||
"fromXProducts": "de la {{count}} produse" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Mă poți întreba despre soiuri de cannabis...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Înregistrare...", // Aufnahme läuft...
|
||||
"searchProducts": "Caută produse..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Nume", // Name
|
||||
"searchField": "Termen de căutare", // Suchbegriff
|
||||
"priceLowHigh": "Preț: Crescător", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Preț: Descrescător" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Citit & Acceptat" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Mărfuri voluminoase", // Sperrgut
|
||||
"pickup": "Ridicare din sucursală" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Livrare standard", // Standardversand
|
||||
"standardFree": "Livrare standard - GRATUITĂ pentru comenzi de peste 100€!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Nu poate fi selectat deoarece unul sau mai multe articole pot fi ridicate doar personal", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Pentru articole mari și grele" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "gratuit", // kostenlos
|
||||
"freeFrom100": "(gratuit de la 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Timp de livrare: 14 zile", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Timp de livrare: 2-3 zile", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Timp de livrare: 7-9 zile" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Adresă facturare", // Rechnungsadresse
|
||||
"deliveryAddress": "Adresă livrare", // Lieferadresse
|
||||
"saveForFuture": "Salvează pentru comenzile viitoare", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Pentru ce dată dorești să ridici butașii?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Notă", // Anmerkung
|
||||
"sameAddress": "Adresa de livrare este aceeași cu adresa de facturare", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Am citit Termenii și Condițiile, Politica de Confidențialitate și informațiile privind Dreptul de Retragere" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Plată reușită!", // Zahlung erfolgreich!
|
||||
"failed": "Plata a eșuat", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Comanda ta a fost finalizată cu succes! Acum poți vizualiza comenzile tale.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Plata ta a fost procesată cu succes. Comanda va fi finalizată automat.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Plata nu a putut fi procesată. Te rugăm să încerci din nou sau să alegi o altă metodă de plată.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Vezi comenzile mele" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Sortare", // Sortierung
|
||||
"perPage": "pe pagină", // pro Seite
|
||||
"availability": "Disponibilitate", // Verfügbarkeit
|
||||
"manufacturer": "Producător" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Taxa pe valoarea adăugată", // Mehrwertsteuer
|
||||
"vat7": "Taxa pe valoarea adăugată 7%", // 7% Mehrwertsteuer
|
||||
"vat19": "Taxa pe valoarea adăugată 19%", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "Taxa pe valoarea adăugată 19% (inclusiv transport)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Preț total net", // Gesamtnettopreis
|
||||
"totalGross": "Preț total brut fără transport", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Subtotal" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Sâ 11-19", // Sa 11-19
|
||||
"address": "Strada Trachenberger 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Între stația Pieschen și Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Toate prețurile includ TVA-ul legal, plus transport", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Confidențialitate", // Datenschutz
|
||||
"agb": "Termeni & Condiții", // AGB
|
||||
"sitemap": "Hartă site", // Sitemap
|
||||
"impressum": "Impressum", // Impressum
|
||||
"batteriegesetzhinweise": "Note privind legea bateriilor", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Drept de retragere" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Semințe și butași de cannabis de calitate", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Promoții și oferte actuale", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Sucursala noastră din Dresden" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Semințe", // Seeds
|
||||
"stecklinge": "Butași", // Stecklinge
|
||||
"oilPress": "Împrumută presa de ulei", // Ölpresse ausleihen
|
||||
"thcTest": "Test THC", // THC Test
|
||||
"address1": "Strada Trachenberger 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Împrumută presa de ulei", // Ölpresse ausleihen
|
||||
"comingSoon": "Conținut în curând..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "Test THC", // THC Test
|
||||
"comingSoon": "Conținut în curând..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "În procesare", // in Bearbeitung
|
||||
"pending": "Nou", // Neu
|
||||
"processing": "În procesare", // in Bearbeitung
|
||||
"cancelled": "Anulat", // Storniert
|
||||
"shipped": "Expediat", // Verschickt
|
||||
"delivered": "Livrat", // Geliefert
|
||||
"return": "Returnare", // Retoure
|
||||
"partialReturn": "Returnare parțială", // Teil Retoure
|
||||
"partialDelivered": "Livrat parțial" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Se încarcă...", // Lädt...
|
||||
"error": "Eroare", // Fehler
|
||||
"close": "Închide", // Schließen
|
||||
"save": "Salvează", // Speichern
|
||||
"cancel": "Anulează", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Da", // Ja
|
||||
"no": "Nu", // Nein
|
||||
"next": "Următorul", // Weiter
|
||||
"back": "Înapoi", // Zurück
|
||||
"edit": "Editează", // Bearbeiten
|
||||
"delete": "Șterge", // Löschen
|
||||
"add": "Adaugă", // Hinzufügen
|
||||
"remove": "Elimină", // Entfernen
|
||||
"products": "Produse", // Produkte
|
||||
"product": "Produs" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/ru/translation.js
Normal file
231
src/i18n/locales/ru/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "ru-RU" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Главная", // Startseite
|
||||
"aktionen": "Акции", // Aktionen
|
||||
"filiale": "Филиал", // Filiale
|
||||
"categories": "Категории", // Kategorien
|
||||
"categoriesOpen": "Открыть категории", // Kategorien öffnen
|
||||
"categoriesClose": "Закрыть категории", // Kategorien schließen
|
||||
"otherCategories": "Другие категории" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Войти", // Anmelden
|
||||
"register": "Регистрация", // Registrieren
|
||||
"logout": "Выйти", // Abmelden
|
||||
"profile": "Профиль", // Profil
|
||||
"email": "Электронная почта", // E-Mail
|
||||
"password": "Пароль", // Passwort
|
||||
"confirmPassword": "Подтвердите пароль", // Passwort bestätigen
|
||||
"forgotPassword": "Забыли пароль?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Войти через Google", // Mit Google anmelden
|
||||
"or": "ИЛИ", // ODER
|
||||
"privacyAccept": "Нажимая «Войти через Google», я принимаю", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Политику конфиденциальности", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Пароль должен содержать не менее 8 символов", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Новый пароль должен содержать не менее 8 символов", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Профиль", // Profil
|
||||
"checkout": "Оформление заказа", // Bestellabschluss
|
||||
"orders": "Заказы", // Bestellungen
|
||||
"settings": "Настройки", // Einstellungen
|
||||
"adminDashboard": "Панель администратора", // Admin Dashboard
|
||||
"adminUsers": "Пользователи администратора" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Корзина", // Warenkorb
|
||||
"empty": "пусто", // leer
|
||||
"addToCart": "Добавить в корзину", // In den Korb
|
||||
"preorderCutting": "Предзаказ черенка", // Als Steckling vorbestellen
|
||||
"continueShopping": "Продолжить покупки", // Weiter einkaufen
|
||||
"proceedToCheckout": "Перейти к оформлению", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {товар} other {товаров}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Удалить из корзины", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Открыть корзину", // Warenkorb öffnen
|
||||
"availableFrom": "Доступно с {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Вернуться к заказу", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Синхронизация корзины", // Warenkorb-Synchronisierung
|
||||
"description": "У вас есть сохранённая корзина в аккаунте. Пожалуйста, выберите, как продолжить:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Удалить корзину на сервере", // Server-Warenkorb löschen
|
||||
"useServer": "Использовать корзину с сервера", // Server-Warenkorb übernehmen
|
||||
"merge": "Объединить корзины", // Warenkörbe zusammenführen
|
||||
"currentCart": "Ваша текущая корзина", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Корзина, сохранённая в вашем профиле" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Загрузка продукта...", // Produkt wird geladen...
|
||||
"notFound": "Продукт не найден", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Искомый продукт не существует или был удалён.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Вернуться на главную", // Zurück zur Startseite
|
||||
"error": "Ошибка", // Fehler
|
||||
"articleNumber": "Артикул", // Artikelnummer
|
||||
"manufacturer": "Производитель", // Hersteller
|
||||
"inclVat": "вкл. {{vat}}% НДС", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Новый", // Neu
|
||||
"arriving": "Прибытие:", // Ankunft:
|
||||
"inclVatFooter": "вкл. {{vat}}% НДС,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Наличие", // Verfügbarkeit
|
||||
"inStock": "в наличии", // auf Lager
|
||||
"comingSoon": "Скоро в продаже", // Bald verfügbar
|
||||
"deliveryTime": "Срок доставки", // Lieferzeit
|
||||
"inclShort": "вкл.", // inkl.
|
||||
"vatShort": "НДС", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 товаров", // 0 Produkte
|
||||
"oneProduct": "1 товар", // 1 Produkt
|
||||
"multipleProducts": "{{count}} товаров", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} из {{total}} товаров", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} из 1 товара" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Уберите фильтры, чтобы увидеть товары", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Нет в наличии", // Out of Stock
|
||||
"fromXProducts": "от {{count}} товаров" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Вы можете спросить меня о сортах каннабиса...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Запись...", // Aufnahme läuft...
|
||||
"searchProducts": "Поиск товаров..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Название", // Name
|
||||
"searchField": "Поисковый запрос", // Suchbegriff
|
||||
"priceLowHigh": "Цена: от низкой к высокой", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Цена: от высокой к низкой" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Прочитано и принято" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Крупногабаритный груз", // Sperrgut
|
||||
"pickup": "Самовывоз из филиала" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Стандартная доставка", // Standardversand
|
||||
"standardFree": "Стандартная доставка - БЕСПЛАТНО при заказе от 100€!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Недоступно, так как один или несколько товаров можно только забрать", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Для крупных и тяжёлых товаров" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "бесплатно", // kostenlos
|
||||
"freeFrom100": "(бесплатно от 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Срок доставки: 14 дней", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Срок доставки: 2-3 дня", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Срок доставки: 7-9 дней" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Адрес для выставления счета", // Rechnungsadresse
|
||||
"deliveryAddress": "Адрес доставки", // Lieferadresse
|
||||
"saveForFuture": "Сохранить для будущих заказов", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "На какую дату вы хотите забрать черенки?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Примечание", // Anmerkung
|
||||
"sameAddress": "Адрес доставки совпадает с адресом для выставления счета", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Я прочитал(а) Условия, Политику конфиденциальности и информацию о праве на отказ" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Платеж прошёл успешно!", // Zahlung erfolgreich!
|
||||
"failed": "Платеж не прошёл", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Ваш заказ успешно завершён! Теперь вы можете просмотреть свои заказы.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Ваш платеж успешно обработан. Заказ будет автоматически завершён.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Ваш платеж не удалось обработать. Пожалуйста, попробуйте снова или выберите другой способ оплаты.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Просмотреть мои заказы" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Сортировка", // Sortierung
|
||||
"perPage": "на странице", // pro Seite
|
||||
"availability": "Наличие", // Verfügbarkeit
|
||||
"manufacturer": "Производитель" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Налог на добавленную стоимость", // Mehrwertsteuer
|
||||
"vat7": "7% Налог на добавленную стоимость", // 7% Mehrwertsteuer
|
||||
"vat19": "19% Налог на добавленную стоимость", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% НДС (включая доставку)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Общая сумма без НДС", // Gesamtnettopreis
|
||||
"totalGross": "Общая сумма с НДС без учета доставки", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Промежуточный итог" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Сб 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Между остановкой Пишен и площадью Трахенберг", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Все цены включают законный НДС, плюс доставка", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Конфиденциальность", // Datenschutz
|
||||
"agb": "Условия и положения", // AGB
|
||||
"sitemap": "Карта сайта", // Sitemap
|
||||
"impressum": "Импрессум", // Impressum
|
||||
"batteriegesetzhinweise": "Информация о законе о батареях", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Право на отказ" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Качественные семена и черенки каннабиса", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Текущие акции и предложения", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Наш филиал в Дрездене" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Семена", // Seeds
|
||||
"stecklinge": "Черенки", // Stecklinge
|
||||
"oilPress": "Аренда маслопрессa", // Ölpresse ausleihen
|
||||
"thcTest": "Тест на THC", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Аренда маслопрессa", // Ölpresse ausleihen
|
||||
"comingSoon": "Скоро появится контент..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "Тест на THC", // THC Test
|
||||
"comingSoon": "Скоро появится контент..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "В обработке", // in Bearbeitung
|
||||
"pending": "Новый", // Neu
|
||||
"processing": "В обработке", // in Bearbeitung
|
||||
"cancelled": "Отменён", // Storniert
|
||||
"shipped": "Отправлен", // Verschickt
|
||||
"delivered": "Доставлен", // Geliefert
|
||||
"return": "Возврат", // Retoure
|
||||
"partialReturn": "Частичный возврат", // Teil Retoure
|
||||
"partialDelivered": "Частично доставлен" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Загрузка...", // Lädt...
|
||||
"error": "Ошибка", // Fehler
|
||||
"close": "Закрыть", // Schließen
|
||||
"save": "Сохранить", // Speichern
|
||||
"cancel": "Отмена", // Abbrechen
|
||||
"ok": "ОК", // OK
|
||||
"yes": "Да", // Ja
|
||||
"no": "Нет", // Nein
|
||||
"next": "Далее", // Weiter
|
||||
"back": "Назад", // Zurück
|
||||
"edit": "Редактировать", // Bearbeiten
|
||||
"delete": "Удалить", // Löschen
|
||||
"add": "Добавить", // Hinzufügen
|
||||
"remove": "Удалить", // Entfernen
|
||||
"products": "Товары", // Produkte
|
||||
"product": "Товар" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/sk/translation.js
Normal file
231
src/i18n/locales/sk/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "sk-SK" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Domov", // Startseite
|
||||
"aktionen": "Akcie", // Aktionen
|
||||
"filiale": "Pobočka", // Filiale
|
||||
"categories": "Kategórie", // Kategorien
|
||||
"categoriesOpen": "Otvoriť kategórie", // Kategorien öffnen
|
||||
"categoriesClose": "Zatvoriť kategórie", // Kategorien schließen
|
||||
"otherCategories": "Iné kategórie" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Prihlásiť sa", // Anmelden
|
||||
"register": "Registrovať sa", // Registrieren
|
||||
"logout": "Odhlásiť sa", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Heslo", // Passwort
|
||||
"confirmPassword": "Potvrdiť heslo", // Passwort bestätigen
|
||||
"forgotPassword": "Zabudli ste heslo?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Prihlásiť sa cez Google", // Mit Google anmelden
|
||||
"or": "ALEBO", // ODER
|
||||
"privacyAccept": "Kliknutím na \"Prihlásiť sa cez Google\" súhlasím s", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Zásadami ochrany osobných údajov", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Heslo musí mať aspoň 8 znakov", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Nové heslo musí mať aspoň 8 znakov", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Pokladňa", // Bestellabschluss
|
||||
"orders": "Objednávky", // Bestellungen
|
||||
"settings": "Nastavenia", // Einstellungen
|
||||
"adminDashboard": "Admin Dashboard", // Admin Dashboard
|
||||
"adminUsers": "Admin Users" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Košík", // Warenkorb
|
||||
"empty": "prázdny", // leer
|
||||
"addToCart": "Pridať do košíka", // In den Korb
|
||||
"preorderCutting": "Predobjednať ako odrezok", // Als Steckling vorbestellen
|
||||
"continueShopping": "Pokračovať v nákupe", // Weiter einkaufen
|
||||
"proceedToCheckout": "Prejsť k pokladni", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Produkt} other {Produkty}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Odstrániť z košíka", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Otvoriť košík", // Warenkorb öffnen
|
||||
"availableFrom": "Dostupné od {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Späť k objednávke", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Synchronizácia košíka", // Warenkorb-Synchronisierung
|
||||
"description": "Máte uložený košík vo svojom účte. Vyberte, ako chcete pokračovať:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Vymazať košík na serveri", // Server-Warenkorb löschen
|
||||
"useServer": "Použiť košík zo servera", // Server-Warenkorb übernehmen
|
||||
"merge": "Zlúčiť košíky", // Warenkörbe zusammenführen
|
||||
"currentCart": "Váš aktuálny košík", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Košík uložený vo vašom profile" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Načítavam produkt...", // Produkt wird geladen...
|
||||
"notFound": "Produkt nenájdený", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Produkt, ktorý hľadáte, neexistuje alebo bol odstránený.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Späť na domovskú stránku", // Zurück zur Startseite
|
||||
"error": "Chyba", // Fehler
|
||||
"articleNumber": "Číslo produktu", // Artikelnummer
|
||||
"manufacturer": "Výrobca", // Hersteller
|
||||
"inclVat": "vrátane {{vat}}% DPH", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Nové", // Neu
|
||||
"arriving": "Prichádza:", // Ankunft:
|
||||
"inclVatFooter": "vrátane {{vat}}% DPH,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Dostupnosť", // Verfügbarkeit
|
||||
"inStock": "na sklade", // auf Lager
|
||||
"comingSoon": "Čoskoro dostupné", // Bald verfügbar
|
||||
"deliveryTime": "Doba dodania", // Lieferzeit
|
||||
"inclShort": "vrátane", // inkl.
|
||||
"vatShort": "DPH", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 produktov", // 0 Produkte
|
||||
"oneProduct": "1 produkt", // 1 Produkt
|
||||
"multipleProducts": "{{count}} produktov", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} z {{total}} produktov", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} z 1 produktu" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Odstráňte filtre, aby ste videli produkty", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Nedostupné", // Out of Stock
|
||||
"fromXProducts": "od {{count}} produktov" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Môžete sa ma opýtať na odrody konope...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Nahrávanie...", // Aufnahme läuft...
|
||||
"searchProducts": "Hľadať produkty..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Názov", // Name
|
||||
"searchField": "Hľadaný výraz", // Suchbegriff
|
||||
"priceLowHigh": "Cena: od najnižšej po najvyššiu", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Cena: od najvyššej po najnižšiu" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Prečítané a akceptované" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Objemný tovar", // Sperrgut
|
||||
"pickup": "Vyzdvihnutie na pobočke" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Štandardné doručenie", // Standardversand
|
||||
"standardFree": "Štandardné doručenie - ZDARMA pri objednávke nad 100 €!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Nie je možné vybrať, pretože jeden alebo viac produktov je možné iba vyzdvihnúť", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Pre veľké a ťažké položky" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "zdarma", // kostenlos
|
||||
"freeFrom100": "(zdarma od 100 €)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Doba dodania: 14 dní", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Doba dodania: 2-3 dni", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Doba dodania: 7-9 dní" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Fakturačná adresa", // Rechnungsadresse
|
||||
"deliveryAddress": "Dodacia adresa", // Lieferadresse
|
||||
"saveForFuture": "Uložiť pre budúce objednávky", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Na ktorý dátum chcete vyzdvihnúť odrezky?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Poznámka", // Anmerkung
|
||||
"sameAddress": "Dodacia adresa je rovnaká ako fakturačná adresa", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Prečítal(a) som si Všeobecné obchodné podmienky, Zásady ochrany osobných údajov a informácie o práve na odstúpenie od zmluvy" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Platba úspešná!", // Zahlung erfolgreich!
|
||||
"failed": "Platba zlyhala", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Vaša objednávka bola úspešne dokončená! Teraz si môžete prezrieť svoje objednávky.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Vaša platba bola úspešne spracovaná. Objednávka bude automaticky dokončená.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Vaša platba nemohla byť spracovaná. Skúste to prosím znova alebo vyberte inú platobnú metódu.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Zobraziť moje objednávky" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Zoradenie", // Sortierung
|
||||
"perPage": "na stránku", // pro Seite
|
||||
"availability": "Dostupnosť", // Verfügbarkeit
|
||||
"manufacturer": "Výrobca" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Daň z pridanej hodnoty", // Mehrwertsteuer
|
||||
"vat7": "7% daň z pridanej hodnoty", // 7% Mehrwertsteuer
|
||||
"vat19": "19% daň z pridanej hodnoty", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% daň z pridanej hodnoty (vrátane dopravy)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Celková cena bez DPH", // Gesamtnettopreis
|
||||
"totalGross": "Celková cena s DPH bez dopravy", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Medzisúčet" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "So 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Drážďany", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Medzi zastávkou Pieschen a Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Všetky ceny vrátane zákonnej DPH, plus doprava", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Ochrana osobných údajov", // Datenschutz
|
||||
"agb": "Všeobecné obchodné podmienky", // AGB
|
||||
"sitemap": "Mapa stránok", // Sitemap
|
||||
"impressum": "Impressum", // Impressum
|
||||
"batteriegesetzhinweise": "Poznámky k zákonu o batériách", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Právo na odstúpenie od zmluvy" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Kvalitné semená a odrezky konope", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Aktuálne akcie a ponuky", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Naša pobočka v Drážďanoch" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Semená", // Seeds
|
||||
"stecklinge": "Odrezky", // Stecklinge
|
||||
"oilPress": "Požičajte si lis na olej", // Ölpresse ausleihen
|
||||
"thcTest": "THC test", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Drážďany" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Požičajte si lis na olej", // Ölpresse ausleihen
|
||||
"comingSoon": "Obsah čoskoro..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC test", // THC Test
|
||||
"comingSoon": "Obsah čoskoro..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "Spracováva sa", // in Bearbeitung
|
||||
"pending": "Nová", // Neu
|
||||
"processing": "Spracováva sa", // in Bearbeitung
|
||||
"cancelled": "Zrušená", // Storniert
|
||||
"shipped": "Odoslaná", // Verschickt
|
||||
"delivered": "Doručená", // Geliefert
|
||||
"return": "Vrátenie", // Retoure
|
||||
"partialReturn": "Čiastočné vrátenie", // Teil Retoure
|
||||
"partialDelivered": "Čiastočne doručená" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Načítavam...", // Lädt...
|
||||
"error": "Chyba", // Fehler
|
||||
"close": "Zavrieť", // Schließen
|
||||
"save": "Uložiť", // Speichern
|
||||
"cancel": "Zrušiť", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Áno", // Ja
|
||||
"no": "Nie", // Nein
|
||||
"next": "Ďalej", // Weiter
|
||||
"back": "Späť", // Zurück
|
||||
"edit": "Upraviť", // Bearbeiten
|
||||
"delete": "Vymazať", // Löschen
|
||||
"add": "Pridať", // Hinzufügen
|
||||
"remove": "Odstrániť", // Entfernen
|
||||
"products": "Produkty", // Produkte
|
||||
"product": "Produkt" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/sl/translation.js
Normal file
231
src/i18n/locales/sl/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "sl-SI" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Domov", // Startseite
|
||||
"aktionen": "Promocije", // Aktionen
|
||||
"filiale": "Poslovalnica", // Filiale
|
||||
"categories": "Kategorije", // Kategorien
|
||||
"categoriesOpen": "Odpri kategorije", // Kategorien öffnen
|
||||
"categoriesClose": "Zapri kategorije", // Kategorien schließen
|
||||
"otherCategories": "Druge kategorije" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Prijava", // Anmelden
|
||||
"register": "Registracija", // Registrieren
|
||||
"logout": "Odjava", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "E-pošta", // E-Mail
|
||||
"password": "Geslo", // Passwort
|
||||
"confirmPassword": "Potrdi geslo", // Passwort bestätigen
|
||||
"forgotPassword": "Ste pozabili geslo?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Prijava z Google", // Mit Google anmelden
|
||||
"or": "ALI", // ODER
|
||||
"privacyAccept": "S klikom na \"Prijava z Google\" sprejemam", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Pravilnik o zasebnosti", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Geslo mora biti dolgo vsaj 8 znakov", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Novo geslo mora biti dolgo vsaj 8 znakov", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Zaključek nakupa", // Bestellabschluss
|
||||
"orders": "Naročila", // Bestellungen
|
||||
"settings": "Nastavitve", // Einstellungen
|
||||
"adminDashboard": "Administratorska nadzorna plošča", // Admin Dashboard
|
||||
"adminUsers": "Administratorski uporabniki" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Košarica", // Warenkorb
|
||||
"empty": "prazno", // leer
|
||||
"addToCart": "Dodaj v košarico", // In den Korb
|
||||
"preorderCutting": "Prednaročilo kot potomec", // Als Steckling vorbestellen
|
||||
"continueShopping": "Nadaljuj z nakupovanjem", // Weiter einkaufen
|
||||
"proceedToCheckout": "Nadaljuj na blagajno", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Izdelek} other {Izdelki}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Odstrani iz košarice", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Odpri košarico", // Warenkorb öffnen
|
||||
"availableFrom": "Na voljo od {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Nazaj na naročilo", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Sinhronizacija košarice", // Warenkorb-Synchronisierung
|
||||
"description": "V vašem računu imate shranjeno košarico. Prosimo, izberite, kako želite nadaljevati:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Izbriši košarico na strežniku", // Server-Warenkorb löschen
|
||||
"useServer": "Uporabi košarico s strežnika", // Server-Warenkorb übernehmen
|
||||
"merge": "Združi košarice", // Warenkörbe zusammenführen
|
||||
"currentCart": "Vaša trenutna košarica", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Košarica shranjena v vašem profilu" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Nalaganje izdelka...", // Produkt wird geladen...
|
||||
"notFound": "Izdelek ni najden", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Izdelek, ki ga iščete, ne obstaja ali je bil odstranjen.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Nazaj na domov", // Zurück zur Startseite
|
||||
"error": "Napaka", // Fehler
|
||||
"articleNumber": "Številka artikla", // Artikelnummer
|
||||
"manufacturer": "Proizvajalec", // Hersteller
|
||||
"inclVat": "vključno z {{vat}}% DDV", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Novo", // Neu
|
||||
"arriving": "Prihaja:", // Ankunft:
|
||||
"inclVatFooter": "vključno z {{vat}}% DDV,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Razpoložljivost", // Verfügbarkeit
|
||||
"inStock": "na zalogi", // auf Lager
|
||||
"comingSoon": "Kmalu na voljo", // Bald verfügbar
|
||||
"deliveryTime": "Čas dostave", // Lieferzeit
|
||||
"inclShort": "vklj.", // inkl.
|
||||
"vatShort": "DDV", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 izdelkov", // 0 Produkte
|
||||
"oneProduct": "1 izdelek", // 1 Produkt
|
||||
"multipleProducts": "{{count}} izdelki", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} od {{total}} izdelkov", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} od 1 izdelka" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Odstranite filtre, da vidite izdelke", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Ni na zalogi", // Out of Stock
|
||||
"fromXProducts": "od {{count}} izdelkov" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Vprašajte me o sortah konoplje...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Snemanje...", // Aufnahme läuft...
|
||||
"searchProducts": "Išči izdelke..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Ime", // Name
|
||||
"searchField": "Iskalni izraz", // Suchbegriff
|
||||
"priceLowHigh": "Cena: naraščajoče", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Cena: padajoče" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Prebrano & Sprejeto" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Veliki tovor", // Sperrgut
|
||||
"pickup": "Prevzem v poslovalnici" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Standardna dostava", // Standardversand
|
||||
"standardFree": "Standardna dostava - BREZPLAČNO za naročila nad 100 €!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Ni mogoče izbrati, ker je en ali več artiklov mogoče samo prevzeti", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Za velike in težke artikle" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "brezplačno", // kostenlos
|
||||
"freeFrom100": "(brezplačno od 100 €)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Čas dostave: 14 dni", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Čas dostave: 2-3 dni", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Čas dostave: 7-9 dni" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Naslov za račun", // Rechnungsadresse
|
||||
"deliveryAddress": "Naslov za dostavo", // Lieferadresse
|
||||
"saveForFuture": "Shrani za prihodnja naročila", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Za kateri datum želite prevzeti potomce?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Opomba", // Anmerkung
|
||||
"sameAddress": "Naslov za dostavo je enak naslovu za račun", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Prebral sem Splošne pogoje, Pravilnik o zasebnosti in informacije o pravici do odstopa" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Plačilo uspešno!", // Zahlung erfolgreich!
|
||||
"failed": "Plačilo ni uspelo", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Vaše naročilo je bilo uspešno zaključeno! Zdaj lahko pregledate svoja naročila.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Vaše plačilo je bilo uspešno obdelano. Naročilo bo samodejno zaključeno.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Vaše plačilo ni bilo mogoče obdelati. Prosimo, poskusite znova ali izberite drugo metodo plačila.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Poglej moja naročila" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Razvrščanje", // Sortierung
|
||||
"perPage": "na stran", // pro Seite
|
||||
"availability": "Razpoložljivost", // Verfügbarkeit
|
||||
"manufacturer": "Proizvajalec" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Davek na dodano vrednost", // Mehrwertsteuer
|
||||
"vat7": "7% davek na dodano vrednost", // 7% Mehrwertsteuer
|
||||
"vat19": "19% davek na dodano vrednost", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% davek na dodano vrednost (vključno s poštnino)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Skupna neto cena", // Gesamtnettopreis
|
||||
"totalGross": "Skupna bruto cena brez poštnine", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Vmesni seštevek" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Sob 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Med postajališčem Pieschen in Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Vse cene vključujejo zakonski DDV, plus poštnina", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Zasebnost", // Datenschutz
|
||||
"agb": "Splošni pogoji", // AGB
|
||||
"sitemap": "Zemljevid strani", // Sitemap
|
||||
"impressum": "Impressum", // Impressum
|
||||
"batteriegesetzhinweise": "Opombe o zakonu o baterijah", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Pravica do odstopa" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Kakovostna semena in potomci konoplje", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Trenutne promocije in ponudbe", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Naša poslovalnica v Dresdnu" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Semena", // Seeds
|
||||
"stecklinge": "Potomci", // Stecklinge
|
||||
"oilPress": "Izposoja oljne preše", // Ölpresse ausleihen
|
||||
"thcTest": "THC test", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Izposoja oljne preše", // Ölpresse ausleihen
|
||||
"comingSoon": "Vsebina prihaja kmalu..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC test", // THC Test
|
||||
"comingSoon": "Vsebina prihaja kmalu..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "V obdelavi", // in Bearbeitung
|
||||
"pending": "Novo", // Neu
|
||||
"processing": "V obdelavi", // in Bearbeitung
|
||||
"cancelled": "Preklicano", // Storniert
|
||||
"shipped": "Poslano", // Verschickt
|
||||
"delivered": "Dostavljeno", // Geliefert
|
||||
"return": "Vrnitev", // Retoure
|
||||
"partialReturn": "Delna vrnitev", // Teil Retoure
|
||||
"partialDelivered": "Delno dostavljeno" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Nalaganje...", // Lädt...
|
||||
"error": "Napaka", // Fehler
|
||||
"close": "Zapri", // Schließen
|
||||
"save": "Shrani", // Speichern
|
||||
"cancel": "Prekliči", // Abbrechen
|
||||
"ok": "V redu", // OK
|
||||
"yes": "Da", // Ja
|
||||
"no": "Ne", // Nein
|
||||
"next": "Naprej", // Weiter
|
||||
"back": "Nazaj", // Zurück
|
||||
"edit": "Uredi", // Bearbeiten
|
||||
"delete": "Izbriši", // Löschen
|
||||
"add": "Dodaj", // Hinzufügen
|
||||
"remove": "Odstrani", // Entfernen
|
||||
"products": "Izdelki", // Produkte
|
||||
"product": "Izdelek" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/sr/translation.js
Normal file
231
src/i18n/locales/sr/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "sr-RS" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Početna", // Startseite
|
||||
"aktionen": "Promocije", // Aktionen
|
||||
"filiale": "Filijala", // Filiale
|
||||
"categories": "Kategorije", // Kategorien
|
||||
"categoriesOpen": "Otvori kategorije", // Kategorien öffnen
|
||||
"categoriesClose": "Zatvori kategorije", // Kategorien schließen
|
||||
"otherCategories": "Ostale kategorije" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Prijava", // Anmelden
|
||||
"register": "Registracija", // Registrieren
|
||||
"logout": "Odjava", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Lozinka", // Passwort
|
||||
"confirmPassword": "Potvrdi lozinku", // Passwort bestätigen
|
||||
"forgotPassword": "Zaboravljena lozinka?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Prijavi se preko Google-a", // Mit Google anmelden
|
||||
"or": "ILI", // ODER
|
||||
"privacyAccept": "Klikom na \"Prijavi se preko Google-a\" prihvatam", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Politiku privatnosti", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Lozinka mora imati najmanje 8 karaktera", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Nova lozinka mora imati najmanje 8 karaktera", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Završi kupovinu", // Bestellabschluss
|
||||
"orders": "Porudžbine", // Bestellungen
|
||||
"settings": "Podešavanja", // Einstellungen
|
||||
"adminDashboard": "Admin kontrolna tabla", // Admin Dashboard
|
||||
"adminUsers": "Admin korisnici" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Korpa", // Warenkorb
|
||||
"empty": "prazna", // leer
|
||||
"addToCart": "Dodaj u korpu", // In den Korb
|
||||
"preorderCutting": "Poruči unapred kao reznicu", // Als Steckling vorbestellen
|
||||
"continueShopping": "Nastavi kupovinu", // Weiter einkaufen
|
||||
"proceedToCheckout": "Idi na plaćanje", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Proizvod} other {Proizvoda}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Ukloni iz korpe", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Otvori korpu", // Warenkorb öffnen
|
||||
"availableFrom": "Dostupno od {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Nazad na porudžbinu", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Sinhronizacija korpe", // Warenkorb-Synchronisierung
|
||||
"description": "Imate sačuvanu korpu na svom nalogu. Molimo izaberite kako želite da nastavite:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Obriši korpu sa servera", // Server-Warenkorb löschen
|
||||
"useServer": "Koristi korpu sa servera", // Server-Warenkorb übernehmen
|
||||
"merge": "Spoji korpe", // Warenkörbe zusammenführen
|
||||
"currentCart": "Vaša trenutna korpa", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Korpa sačuvana u vašem profilu" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Učitavanje proizvoda...", // Produkt wird geladen...
|
||||
"notFound": "Proizvod nije pronađen", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Proizvod koji tražite ne postoji ili je uklonjen.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Nazad na početnu", // Zurück zur Startseite
|
||||
"error": "Greška", // Fehler
|
||||
"articleNumber": "Broj artikla", // Artikelnummer
|
||||
"manufacturer": "Proizvođač", // Hersteller
|
||||
"inclVat": "uključujući {{vat}}% PDV", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Novo", // Neu
|
||||
"arriving": "Stiže:", // Ankunft:
|
||||
"inclVatFooter": "uključujući {{vat}}% PDV,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Dostupnost", // Verfügbarkeit
|
||||
"inStock": "na lageru", // auf Lager
|
||||
"comingSoon": "Uskoro dostupno", // Bald verfügbar
|
||||
"deliveryTime": "Vreme isporuke", // Lieferzeit
|
||||
"inclShort": "uklj.", // inkl.
|
||||
"vatShort": "PDV", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 proizvoda", // 0 Produkte
|
||||
"oneProduct": "1 proizvod", // 1 Produkt
|
||||
"multipleProducts": "{{count}} proizvoda", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} od {{total}} proizvoda", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} od 1 proizvoda" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Uklonite filtere da vidite proizvode", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Nema na lageru", // Out of Stock
|
||||
"fromXProducts": "od {{count}} proizvoda" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Možete me pitati o sortama kanabisa...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Snimanje...", // Aufnahme läuft...
|
||||
"searchProducts": "Pretraži proizvode..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Ime", // Name
|
||||
"searchField": "Pretraga", // Suchbegriff
|
||||
"priceLowHigh": "Cena: od najniže do najviše", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Cena: od najviše do najniže" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Pročitano i prihvaćeno" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Glomazna roba", // Sperrgut
|
||||
"pickup": "Preuzimanje u filijali" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Standardna dostava", // Standardversand
|
||||
"standardFree": "Standardna dostava - BESPLATNO za porudžbine preko 100€!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Nije moguće izabrati jer jedan ili više artikala mogu biti samo preuzeti", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Za velike i teške artikle" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "besplatno", // kostenlos
|
||||
"freeFrom100": "(besplatno od 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Vreme isporuke: 14 dana", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Vreme isporuke: 2-3 dana", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Vreme isporuke: 7-9 dana" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Adresa za fakturu", // Rechnungsadresse
|
||||
"deliveryAddress": "Adresa za isporuku", // Lieferadresse
|
||||
"saveForFuture": "Sačuvaj za buduće porudžbine", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Za koji datum želite preuzeti reznice?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Napomena", // Anmerkung
|
||||
"sameAddress": "Adresa za isporuku je ista kao adresa za fakturu", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Pročitao/la sam Uslove korišćenja, Politiku privatnosti i informacije o pravu na odustanak" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Plaćanje uspešno!", // Zahlung erfolgreich!
|
||||
"failed": "Plaćanje nije uspelo", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Vaša porudžbina je uspešno završena! Sada možete pregledati svoje porudžbine.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Vaša uplata je uspešno obrađena. Porudžbina će biti automatski završena.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Vaša uplata nije mogla biti obrađena. Molimo pokušajte ponovo ili izaberite drugi način plaćanja.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Pogledaj moje porudžbine" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Sortiranje", // Sortierung
|
||||
"perPage": "po strani", // pro Seite
|
||||
"availability": "Dostupnost", // Verfügbarkeit
|
||||
"manufacturer": "Proizvođač" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Porez na dodatu vrednost", // Mehrwertsteuer
|
||||
"vat7": "7% poreza na dodatu vrednost", // 7% Mehrwertsteuer
|
||||
"vat19": "19% poreza na dodatu vrednost", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% poreza na dodatu vrednost (uključujući dostavu)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Ukupna neto cena", // Gesamtnettopreis
|
||||
"totalGross": "Ukupna bruto cena bez dostave", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Međuzbir" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Sub 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Između stanice Pieschen i Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Sve cene uključuju zakonski PDV, plus dostava", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Privatnost", // Datenschutz
|
||||
"agb": "Uslovi korišćenja", // AGB
|
||||
"sitemap": "Mapa sajta", // Sitemap
|
||||
"impressum": "Impresum", // Impressum
|
||||
"batteriegesetzhinweise": "Napomene o zakonu o baterijama", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Pravo na odustanak" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Kvalitetno seme i reznice kanabisa", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Trenutne promocije i ponude", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Naša filijala u Dresdenu" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Semena", // Seeds
|
||||
"stecklinge": "Reznice", // Stecklinge
|
||||
"oilPress": "Pozajmi presa za ulje", // Ölpresse ausleihen
|
||||
"thcTest": "THC test", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Pozajmi presa za ulje", // Ölpresse ausleihen
|
||||
"comingSoon": "Sadržaj uskoro..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC test", // THC Test
|
||||
"comingSoon": "Sadržaj uskoro..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "U obradi", // in Bearbeitung
|
||||
"pending": "Novo", // Neu
|
||||
"processing": "U obradi", // in Bearbeitung
|
||||
"cancelled": "Otkazano", // Storniert
|
||||
"shipped": "Poslato", // Verschickt
|
||||
"delivered": "Isporučeno", // Geliefert
|
||||
"return": "Povratak", // Retoure
|
||||
"partialReturn": "Delimični povratak", // Teil Retoure
|
||||
"partialDelivered": "Delimično isporučeno" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Učitavanje...", // Lädt...
|
||||
"error": "Greška", // Fehler
|
||||
"close": "Zatvori", // Schließen
|
||||
"save": "Sačuvaj", // Speichern
|
||||
"cancel": "Otkaži", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Da", // Ja
|
||||
"no": "Ne", // Nein
|
||||
"next": "Dalje", // Weiter
|
||||
"back": "Nazad", // Zurück
|
||||
"edit": "Izmeni", // Bearbeiten
|
||||
"delete": "Obriši", // Löschen
|
||||
"add": "Dodaj", // Hinzufügen
|
||||
"remove": "Ukloni", // Entfernen
|
||||
"products": "Proizvodi", // Produkte
|
||||
"product": "Proizvod" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/sv/translation.js
Normal file
231
src/i18n/locales/sv/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "sv-SE" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Hem", // Startseite
|
||||
"aktionen": "Kampanjer", // Aktionen
|
||||
"filiale": "Filial", // Filiale
|
||||
"categories": "Kategorier", // Kategorien
|
||||
"categoriesOpen": "Öppna kategorier", // Kategorien öffnen
|
||||
"categoriesClose": "Stäng kategorier", // Kategorien schließen
|
||||
"otherCategories": "Andra kategorier" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Logga in", // Anmelden
|
||||
"register": "Registrera", // Registrieren
|
||||
"logout": "Logga ut", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "E-post", // E-Mail
|
||||
"password": "Lösenord", // Passwort
|
||||
"confirmPassword": "Bekräfta lösenord", // Passwort bestätigen
|
||||
"forgotPassword": "Glömt lösenord?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Logga in med Google", // Mit Google anmelden
|
||||
"or": "ELLER", // ODER
|
||||
"privacyAccept": "Genom att klicka på \"Logga in med Google\" accepterar jag", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Integritetspolicy", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Lösenordet måste vara minst 8 tecken långt", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Det nya lösenordet måste vara minst 8 tecken långt", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Kassa", // Bestellabschluss
|
||||
"orders": "Beställningar", // Bestellungen
|
||||
"settings": "Inställningar", // Einstellungen
|
||||
"adminDashboard": "Admin Dashboard", // Admin Dashboard
|
||||
"adminUsers": "Admin Users" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Kundvagn", // Warenkorb
|
||||
"empty": "tom", // leer
|
||||
"addToCart": "Lägg till i kundvagn", // In den Korb
|
||||
"preorderCutting": "Förbeställ som stickling", // Als Steckling vorbestellen
|
||||
"continueShopping": "Fortsätt handla", // Weiter einkaufen
|
||||
"proceedToCheckout": "Gå till kassan", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Produkt} other {Produkter}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Ta bort från kundvagn", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Öppna kundvagn", // Warenkorb öffnen
|
||||
"availableFrom": "Tillgänglig från {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Tillbaka till beställning", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Synkronisering av kundvagn", // Warenkorb-Synchronisierung
|
||||
"description": "Du har en sparad kundvagn på ditt konto. Vänligen välj hur du vill fortsätta:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Radera serverkundvagn", // Server-Warenkorb löschen
|
||||
"useServer": "Använd serverkundvagn", // Server-Warenkorb übernehmen
|
||||
"merge": "Slå ihop kundvagnar", // Warenkörbe zusammenführen
|
||||
"currentCart": "Din nuvarande kundvagn", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Kundvagn sparad i din profil" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Laddar produkt...", // Produkt wird geladen...
|
||||
"notFound": "Produkten hittades inte", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Produkten du söker finns inte eller har tagits bort.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Tillbaka till startsidan", // Zurück zur Startseite
|
||||
"error": "Fel", // Fehler
|
||||
"articleNumber": "Artikelnummer", // Artikelnummer
|
||||
"manufacturer": "Tillverkare", // Hersteller
|
||||
"inclVat": "inkl. {{vat}}% moms", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Ny", // Neu
|
||||
"arriving": "Ankommer:", // Ankunft:
|
||||
"inclVatFooter": "inkl. {{vat}}% moms,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Tillgänglighet", // Verfügbarkeit
|
||||
"inStock": "i lager", // auf Lager
|
||||
"comingSoon": "Kommer snart", // Bald verfügbar
|
||||
"deliveryTime": "Leveranstid", // Lieferzeit
|
||||
"inclShort": "inkl.", // inkl.
|
||||
"vatShort": "Moms", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 produkter", // 0 Produkte
|
||||
"oneProduct": "1 produkt", // 1 Produkt
|
||||
"multipleProducts": "{{count}} produkter", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} av {{total}} produkter", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} av 1 produkt" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Ta bort filter för att se produkter", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Slut i lager", // Out of Stock
|
||||
"fromXProducts": "från {{count}} produkter" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Du kan fråga mig om cannabisstammar...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Spelar in...", // Aufnahme läuft...
|
||||
"searchProducts": "Sök produkter..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Namn", // Name
|
||||
"searchField": "Sökterm", // Suchbegriff
|
||||
"priceLowHigh": "Pris: Lågt till Högt", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Pris: Högt till Lågt" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Läst & Accepterat" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Skrymmande gods", // Sperrgut
|
||||
"pickup": "Hämtning i filial" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Standardfrakt", // Standardversand
|
||||
"standardFree": "Standardfrakt - GRATIS från 100€ ordervärde!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Ej valbar eftersom en eller flera artiklar endast kan hämtas upp", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "För stora och tunga artiklar" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "gratis", // kostenlos
|
||||
"freeFrom100": "(gratis från 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Leveranstid: 14 dagar", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Leveranstid: 2-3 dagar", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Leveranstid: 7-9 dagar" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Faktureringsadress", // Rechnungsadresse
|
||||
"deliveryAddress": "Leveransadress", // Lieferadresse
|
||||
"saveForFuture": "Spara för framtida beställningar", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "För vilket datum vill du hämta sticklingarna?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Notering", // Anmerkung
|
||||
"sameAddress": "Leveransadressen är samma som faktureringsadressen", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Jag har läst villkoren, integritetspolicyn och informationen om ångerrätt" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Betalning lyckades!", // Zahlung erfolgreich!
|
||||
"failed": "Betalningen misslyckades", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Din beställning har slutförts! Du kan nu se dina beställningar.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Din betalning har behandlats framgångsrikt. Beställningen slutförs automatiskt.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Din betalning kunde inte behandlas. Vänligen försök igen eller välj en annan betalningsmetod.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Visa mina beställningar" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Sortering", // Sortierung
|
||||
"perPage": "per sida", // pro Seite
|
||||
"availability": "Tillgänglighet", // Verfügbarkeit
|
||||
"manufacturer": "Tillverkare" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Mervärdesskatt", // Mehrwertsteuer
|
||||
"vat7": "7% mervärdesskatt", // 7% Mehrwertsteuer
|
||||
"vat19": "19% mervärdesskatt", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% mervärdesskatt (inkl. frakt)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Totalt nettobelopp", // Gesamtnettopreis
|
||||
"totalGross": "Totalt bruttobelopp exklusive frakt", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Delsumma" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Lör 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Mellan Pieschen hållplats och Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Alla priser inkluderar lagstadgad moms, plus frakt", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Integritet", // Datenschutz
|
||||
"agb": "Villkor", // AGB
|
||||
"sitemap": "Sajtkarta", // Sitemap
|
||||
"impressum": "Impressum", // Impressum
|
||||
"batteriegesetzhinweise": "Batterilagstiftning", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Ångerrätt" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Fine Cannabis Seeds & Cuttings", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Aktuella kampanjer & erbjudanden", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Vår filial i Dresden" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Frön", // Seeds
|
||||
"stecklinge": "Sticklingar", // Stecklinge
|
||||
"oilPress": "Låna oljepress", // Ölpresse ausleihen
|
||||
"thcTest": "THC-test", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Låna oljepress", // Ölpresse ausleihen
|
||||
"comingSoon": "Innehåll kommer snart..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC-test", // THC Test
|
||||
"comingSoon": "Innehåll kommer snart..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "Bearbetas", // in Bearbeitung
|
||||
"pending": "Ny", // Neu
|
||||
"processing": "Bearbetas", // in Bearbeitung
|
||||
"cancelled": "Avbruten", // Storniert
|
||||
"shipped": "Skickad", // Verschickt
|
||||
"delivered": "Levererad", // Geliefert
|
||||
"return": "Retur", // Retoure
|
||||
"partialReturn": "Delvis retur", // Teil Retoure
|
||||
"partialDelivered": "Delvis levererad" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Laddar...", // Lädt...
|
||||
"error": "Fel", // Fehler
|
||||
"close": "Stäng", // Schließen
|
||||
"save": "Spara", // Speichern
|
||||
"cancel": "Avbryt", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Ja", // Ja
|
||||
"no": "Nej", // Nein
|
||||
"next": "Nästa", // Weiter
|
||||
"back": "Tillbaka", // Zurück
|
||||
"edit": "Redigera", // Bearbeiten
|
||||
"delete": "Radera", // Löschen
|
||||
"add": "Lägg till", // Hinzufügen
|
||||
"remove": "Ta bort", // Entfernen
|
||||
"products": "Produkter", // Produkte
|
||||
"product": "Produkt" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/tr/translation.js
Normal file
231
src/i18n/locales/tr/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "tr-TR" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Ana Sayfa", // Startseite
|
||||
"aktionen": "Kampanyalar", // Aktionen
|
||||
"filiale": "Şube", // Filiale
|
||||
"categories": "Kategoriler", // Kategorien
|
||||
"categoriesOpen": "Kategorileri aç", // Kategorien öffnen
|
||||
"categoriesClose": "Kategorileri kapat", // Kategorien schließen
|
||||
"otherCategories": "Diğer kategoriler" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Giriş Yap", // Anmelden
|
||||
"register": "Kayıt Ol", // Registrieren
|
||||
"logout": "Çıkış Yap", // Abmelden
|
||||
"profile": "Profil", // Profil
|
||||
"email": "E-posta", // E-Mail
|
||||
"password": "Şifre", // Passwort
|
||||
"confirmPassword": "Şifreyi Onayla", // Passwort bestätigen
|
||||
"forgotPassword": "Şifrenizi mi unuttunuz?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Google ile giriş yap", // Mit Google anmelden
|
||||
"or": "VEYA", // ODER
|
||||
"privacyAccept": "\"Google ile giriş yap\" butonuna tıklayarak", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Gizlilik Politikasını", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Şifre en az 8 karakter uzunluğunda olmalıdır", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Yeni şifre en az 8 karakter uzunluğunda olmalıdır", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Profil", // Profil
|
||||
"checkout": "Ödeme", // Bestellabschluss
|
||||
"orders": "Siparişler", // Bestellungen
|
||||
"settings": "Ayarlar", // Einstellungen
|
||||
"adminDashboard": "Yönetici Paneli", // Admin Dashboard
|
||||
"adminUsers": "Yönetici Kullanıcıları" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Sepet", // Warenkorb
|
||||
"empty": "boş", // leer
|
||||
"addToCart": "Sepete ekle", // In den Korb
|
||||
"preorderCutting": "Çelik olarak ön sipariş ver", // Als Steckling vorbestellen
|
||||
"continueShopping": "Alışverişe devam et", // Weiter einkaufen
|
||||
"proceedToCheckout": "Ödemeye geç", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Ürün} other {Ürün}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Sepetten çıkar", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Sepeti aç", // Warenkorb öffnen
|
||||
"availableFrom": "{{date}} tarihinden itibaren mevcut", // Ab {{date}}
|
||||
"backToOrder": "← Siparişe geri dön", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Sepet senkronizasyonu", // Warenkorb-Synchronisierung
|
||||
"description": "Hesabınızda kayıtlı bir sepetiniz var. Lütfen nasıl devam etmek istediğinizi seçin:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Sunucu sepetini sil", // Server-Warenkorb löschen
|
||||
"useServer": "Sunucu sepetini kullan", // Server-Warenkorb übernehmen
|
||||
"merge": "Sepetleri birleştir", // Warenkörbe zusammenführen
|
||||
"currentCart": "Mevcut sepetiniz", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Profilinizde kayıtlı sepet" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Ürün yükleniyor...", // Produkt wird geladen...
|
||||
"notFound": "Ürün bulunamadı", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Aradığınız ürün mevcut değil veya kaldırılmış.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Ana sayfaya dön", // Zurück zur Startseite
|
||||
"error": "Hata", // Fehler
|
||||
"articleNumber": "Ürün numarası", // Artikelnummer
|
||||
"manufacturer": "Üretici", // Hersteller
|
||||
"inclVat": "%{{vat}} KDV dahil", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Yeni", // Neu
|
||||
"arriving": "Geliş tarihi:", // Ankunft:
|
||||
"inclVatFooter": "%{{vat}} KDV dahil,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Mevcutluk", // Verfügbarkeit
|
||||
"inStock": "stokta", // auf Lager
|
||||
"comingSoon": "Yakında geliyor", // Bald verfügbar
|
||||
"deliveryTime": "Teslim süresi", // Lieferzeit
|
||||
"inclShort": "dahil", // inkl.
|
||||
"vatShort": "KDV", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 ürün", // 0 Produkte
|
||||
"oneProduct": "1 ürün", // 1 Produkt
|
||||
"multipleProducts": "{{count}} ürün", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} / {{total}} ürün", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} / 1 ürün" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Ürünleri görmek için filtreleri kaldırın", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Stokta yok", // Out of Stock
|
||||
"fromXProducts": "{{count}} üründen itibaren" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Bana cannabis çeşitleri hakkında sorabilirsiniz...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Kayıt yapılıyor...", // Aufnahme läuft...
|
||||
"searchProducts": "Ürünlerde ara..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "İsim", // Name
|
||||
"searchField": "Arama terimi", // Suchbegriff
|
||||
"priceLowHigh": "Fiyat: Düşükten Yükseğe", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Fiyat: Yüksekten Düşüğe" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Okundu & Kabul Edildi" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Hacimli ürünler", // Sperrgut
|
||||
"pickup": "Şubeden teslim alma" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Standart kargo", // Standardversand
|
||||
"standardFree": "Standart kargo - 100€ üzeri siparişlerde ÜCRETSİZ!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Seçilemez çünkü bir veya daha fazla ürün sadece şubeden alınabilir", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Büyük ve ağır ürünler için" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "ücretsiz", // kostenlos
|
||||
"freeFrom100": "(100€ üzeri ücretsiz)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Teslim süresi: 14 gün", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Teslim süresi: 2-3 gün", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Teslim süresi: 7-9 gün" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Fatura adresi", // Rechnungsadresse
|
||||
"deliveryAddress": "Teslimat adresi", // Lieferadresse
|
||||
"saveForFuture": "Gelecek siparişler için kaydet", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "Çelikleri hangi tarihte almak istiyorsunuz?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Not", // Anmerkung
|
||||
"sameAddress": "Teslimat adresi fatura adresi ile aynı", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Şartlar ve Koşullar, Gizlilik Politikası ve Cayma Hakkı bilgilerini okudum" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Ödeme başarılı!", // Zahlung erfolgreich!
|
||||
"failed": "Ödeme başarısız", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Siparişiniz başarıyla tamamlandı! Şimdi siparişlerinizi görüntüleyebilirsiniz.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Ödemeniz başarıyla işlendi. Sipariş otomatik olarak tamamlanacaktır.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Ödemeniz işlenemedi. Lütfen tekrar deneyin veya başka bir ödeme yöntemi seçin.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Siparişlerimi görüntüle" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Sıralama", // Sortierung
|
||||
"perPage": "sayfa başına", // pro Seite
|
||||
"availability": "Mevcutluk", // Verfügbarkeit
|
||||
"manufacturer": "Üretici" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Katma Değer Vergisi", // Mehrwertsteuer
|
||||
"vat7": "%7 Katma Değer Vergisi", // 7% Mehrwertsteuer
|
||||
"vat19": "%19 Katma Değer Vergisi", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "%19 Katma Değer Vergisi (kargo dahil)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Toplam net fiyat", // Gesamtnettopreis
|
||||
"totalGross": "Kargo hariç toplam brüt fiyat", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Ara toplam" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Cts 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Pieschen durağı ile Trachenberger Platz arasında", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Tüm fiyatlar yasal KDV dahil, kargo hariç", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Gizlilik", // Datenschutz
|
||||
"agb": "Şartlar & Koşullar", // AGB
|
||||
"sitemap": "Site Haritası", // Sitemap
|
||||
"impressum": "Yasal Bilgiler", // Impressum
|
||||
"batteriegesetzhinweise": "Pil Kanunu Notları", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Cayma Hakkı" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Kaliteli Cannabis Tohumları & Çelikleri", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Güncel Kampanyalar & Teklifler", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Dresden'deki Şubemiz" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Tohumlar", // Seeds
|
||||
"stecklinge": "Çelikler", // Stecklinge
|
||||
"oilPress": "Yağ presi ödünç al", // Ölpresse ausleihen
|
||||
"thcTest": "THC Testi", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Yağ presi ödünç al", // Ölpresse ausleihen
|
||||
"comingSoon": "İçerik yakında geliyor..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC Testi", // THC Test
|
||||
"comingSoon": "İçerik yakında geliyor..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "İşleniyor", // in Bearbeitung
|
||||
"pending": "Yeni", // Neu
|
||||
"processing": "İşleniyor", // in Bearbeitung
|
||||
"cancelled": "İptal edildi", // Storniert
|
||||
"shipped": "Gönderildi", // Verschickt
|
||||
"delivered": "Teslim edildi", // Geliefert
|
||||
"return": "İade", // Retoure
|
||||
"partialReturn": "Kısmi iade", // Teil Retoure
|
||||
"partialDelivered": "Kısmen teslim edildi" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Yükleniyor...", // Lädt...
|
||||
"error": "Hata", // Fehler
|
||||
"close": "Kapat", // Schließen
|
||||
"save": "Kaydet", // Speichern
|
||||
"cancel": "İptal", // Abbrechen
|
||||
"ok": "Tamam", // OK
|
||||
"yes": "Evet", // Ja
|
||||
"no": "Hayır", // Nein
|
||||
"next": "İleri", // Weiter
|
||||
"back": "Geri", // Zurück
|
||||
"edit": "Düzenle", // Bearbeiten
|
||||
"delete": "Sil", // Löschen
|
||||
"add": "Ekle", // Hinzufügen
|
||||
"remove": "Kaldır", // Entfernen
|
||||
"products": "Ürünler", // Produkte
|
||||
"product": "Ürün" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/uk/translation.js
Normal file
231
src/i18n/locales/uk/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "uk-UA" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "Головна", // Startseite
|
||||
"aktionen": "Акції", // Aktionen
|
||||
"filiale": "Філія", // Filiale
|
||||
"categories": "Категорії", // Kategorien
|
||||
"categoriesOpen": "Відкрити категорії", // Kategorien öffnen
|
||||
"categoriesClose": "Закрити категорії", // Kategorien schließen
|
||||
"otherCategories": "Інші категорії" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "Увійти", // Anmelden
|
||||
"register": "Реєстрація", // Registrieren
|
||||
"logout": "Вийти", // Abmelden
|
||||
"profile": "Профіль", // Profil
|
||||
"email": "Електронна пошта", // E-Mail
|
||||
"password": "Пароль", // Passwort
|
||||
"confirmPassword": "Підтвердити пароль", // Passwort bestätigen
|
||||
"forgotPassword": "Забули пароль?", // Passwort vergessen?
|
||||
"loginWithGoogle": "Увійти через Google", // Mit Google anmelden
|
||||
"or": "АБО", // ODER
|
||||
"privacyAccept": "Натискаючи \"Увійти через Google\", я приймаю", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "Політику конфіденційності", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "Пароль повинен містити щонайменше 8 символів", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "Новий пароль повинен містити щонайменше 8 символів", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "Профіль", // Profil
|
||||
"checkout": "Оформлення замовлення", // Bestellabschluss
|
||||
"orders": "Замовлення", // Bestellungen
|
||||
"settings": "Налаштування", // Einstellungen
|
||||
"adminDashboard": "Адмін панель", // Admin Dashboard
|
||||
"adminUsers": "Адміністратори" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "Кошик", // Warenkorb
|
||||
"empty": "порожній", // leer
|
||||
"addToCart": "Додати до кошика", // In den Korb
|
||||
"preorderCutting": "Передзамовлення як живець", // Als Steckling vorbestellen
|
||||
"continueShopping": "Продовжити покупки", // Weiter einkaufen
|
||||
"proceedToCheckout": "Перейти до оформлення", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {Продукт} other {Продукти}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "Видалити з кошика", // Aus dem Warenkorb entfernen
|
||||
"openCart": "Відкрити кошик", // Warenkorb öffnen
|
||||
"availableFrom": "Доступно з {{date}}", // Ab {{date}}
|
||||
"backToOrder": "← Назад до замовлення", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "Синхронізація кошика", // Warenkorb-Synchronisierung
|
||||
"description": "У вас є збережений кошик у вашому акаунті. Будь ласка, оберіть, як ви хочете продовжити:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "Видалити серверний кошик", // Server-Warenkorb löschen
|
||||
"useServer": "Використати серверний кошик", // Server-Warenkorb übernehmen
|
||||
"merge": "Об’єднати кошики", // Warenkörbe zusammenführen
|
||||
"currentCart": "Ваш поточний кошик", // Ihr aktueller Warenkorb
|
||||
"serverCart": "Кошик, збережений у вашому профілі" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "Завантаження продукту...", // Produkt wird geladen...
|
||||
"notFound": "Продукт не знайдено", // Produkt nicht gefunden
|
||||
"notFoundDescription": "Продукт, який ви шукаєте, не існує або був видалений.", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "Повернутися на головну", // Zurück zur Startseite
|
||||
"error": "Помилка", // Fehler
|
||||
"articleNumber": "Артикул", // Artikelnummer
|
||||
"manufacturer": "Виробник", // Hersteller
|
||||
"inclVat": "включно з {{vat}}% ПДВ", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "Новий", // Neu
|
||||
"arriving": "Прибуття:", // Ankunft:
|
||||
"inclVatFooter": "включно з {{vat}}% ПДВ,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "Наявність", // Verfügbarkeit
|
||||
"inStock": "в наявності", // auf Lager
|
||||
"comingSoon": "Скоро в наявності", // Bald verfügbar
|
||||
"deliveryTime": "Час доставки", // Lieferzeit
|
||||
"inclShort": "вкл.", // inkl.
|
||||
"vatShort": "ПДВ", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 продуктів", // 0 Produkte
|
||||
"oneProduct": "1 продукт", // 1 Produkt
|
||||
"multipleProducts": "{{count}} продуктів", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} з {{total}} продуктів", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} з 1 продукту" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "Приберіть фільтри, щоб побачити продукти", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "Немає в наявності", // Out of Stock
|
||||
"fromXProducts": "від {{count}} продуктів" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "Ви можете запитати мене про сорти канабісу...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "Запис...", // Aufnahme läuft...
|
||||
"searchProducts": "Пошук продуктів..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "Назва", // Name
|
||||
"searchField": "Пошуковий запит", // Suchbegriff
|
||||
"priceLowHigh": "Ціна: від низької до високої", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "Ціна: від високої до низької" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "Прочитано & Прийнято" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "Великогабаритні вантажі", // Sperrgut
|
||||
"pickup": "Самовивіз з філії" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "Стандартна доставка", // Standardversand
|
||||
"standardFree": "Стандартна доставка - БЕЗКОШТОВНО при замовленні від 100€!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "Не доступно для вибору, оскільки один або кілька товарів можна лише забрати", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "Для великих та важких товарів" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "безкоштовно", // kostenlos
|
||||
"freeFrom100": "(безкоштовно від 100€)", // (kostenlos ab 100€)
|
||||
"dhl": "6,99 €", // 6,99 €
|
||||
"dpd": "4,90 €", // 4,90 €
|
||||
"sperrgut": "28,99 €" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "Час доставки: 14 днів", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "Час доставки: 2-3 дні", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "Час доставки: 7-9 днів" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "Адреса для рахунку", // Rechnungsadresse
|
||||
"deliveryAddress": "Адреса доставки", // Lieferadresse
|
||||
"saveForFuture": "Зберегти для майбутніх замовлень", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "На яку дату ви хочете забрати живці?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "Примітка", // Anmerkung
|
||||
"sameAddress": "Адреса доставки така ж, як і адреса для рахунку", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "Я прочитав умови, політику конфіденційності та інформацію про право на відмову" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "Оплата успішна!", // Zahlung erfolgreich!
|
||||
"failed": "Оплата не вдалася", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 Ваше замовлення успішно завершено! Тепер ви можете переглянути свої замовлення.", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "Ваш платіж було успішно оброблено. Замовлення буде автоматично завершено.", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "Ваш платіж не вдалося обробити. Будь ласка, спробуйте ще раз або оберіть інший спосіб оплати.", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "Переглянути мої замовлення" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "Сортування", // Sortierung
|
||||
"perPage": "на сторінку", // pro Seite
|
||||
"availability": "Наявність", // Verfügbarkeit
|
||||
"manufacturer": "Виробник" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "Податок на додану вартість", // Mehrwertsteuer
|
||||
"vat7": "7% податку на додану вартість", // 7% Mehrwertsteuer
|
||||
"vat19": "19% податку на додану вартість", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% податку на додану вартість (включно з доставкою)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "Загальна чиста ціна", // Gesamtnettopreis
|
||||
"totalGross": "Загальна брутто ціна без доставки", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "Проміжна сума" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "Сб 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "Між зупинкою Пішен та Trachenberger Platz", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* Всі ціни включають законний ПДВ, плюс доставка", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "Конфіденційність", // Datenschutz
|
||||
"agb": "Умови та положення", // AGB
|
||||
"sitemap": "Карта сайту", // Sitemap
|
||||
"impressum": "Імпресум", // Impressum
|
||||
"batteriegesetzhinweise": "Примітки до Закону про батареї", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "Право на відмову" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "Якісне насіння та живці канабісу", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "Поточні акції та пропозиції", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "Наша філія в Дрездені" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "Насіння", // Seeds
|
||||
"stecklinge": "Живці", // Stecklinge
|
||||
"oilPress": "Оренда олійного преса", // Ölpresse ausleihen
|
||||
"thcTest": "Тест на ТГК", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "Оренда олійного преса", // Ölpresse ausleihen
|
||||
"comingSoon": "Контент скоро з’явиться..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "Тест на ТГК", // THC Test
|
||||
"comingSoon": "Контент скоро з’явиться..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "Обробка", // in Bearbeitung
|
||||
"pending": "Новий", // Neu
|
||||
"processing": "Обробка", // in Bearbeitung
|
||||
"cancelled": "Скасовано", // Storniert
|
||||
"shipped": "Відправлено", // Verschickt
|
||||
"delivered": "Доставлено", // Geliefert
|
||||
"return": "Повернення", // Retoure
|
||||
"partialReturn": "Часткове повернення", // Teil Retoure
|
||||
"partialDelivered": "Частково доставлено" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "Завантаження...", // Lädt...
|
||||
"error": "Помилка", // Fehler
|
||||
"close": "Закрити", // Schließen
|
||||
"save": "Зберегти", // Speichern
|
||||
"cancel": "Скасувати", // Abbrechen
|
||||
"ok": "OK", // OK
|
||||
"yes": "Так", // Ja
|
||||
"no": "Ні", // Nein
|
||||
"next": "Далі", // Weiter
|
||||
"back": "Назад", // Zurück
|
||||
"edit": "Редагувати", // Bearbeiten
|
||||
"delete": "Видалити", // Löschen
|
||||
"add": "Додати", // Hinzufügen
|
||||
"remove": "Видалити", // Entfernen
|
||||
"products": "Продукти", // Produkte
|
||||
"product": "Продукт" // Produkt
|
||||
}
|
||||
};
|
||||
231
src/i18n/locales/zh/translation.js
Normal file
231
src/i18n/locales/zh/translation.js
Normal file
@@ -0,0 +1,231 @@
|
||||
export default {
|
||||
"locale": {
|
||||
"code": "zh-CN" // de-DE
|
||||
},
|
||||
"navigation": {
|
||||
"home": "首页", // Startseite
|
||||
"aktionen": "促销活动", // Aktionen
|
||||
"filiale": "分店", // Filiale
|
||||
"categories": "分类", // Kategorien
|
||||
"categoriesOpen": "打开分类", // Kategorien öffnen
|
||||
"categoriesClose": "关闭分类", // Kategorien schließen
|
||||
"otherCategories": "其他分类" // Andere Kategorien
|
||||
},
|
||||
"auth": {
|
||||
"login": "登录", // Anmelden
|
||||
"register": "注册", // Registrieren
|
||||
"logout": "登出", // Abmelden
|
||||
"profile": "个人资料", // Profil
|
||||
"email": "电子邮件", // E-Mail
|
||||
"password": "密码", // Passwort
|
||||
"confirmPassword": "确认密码", // Passwort bestätigen
|
||||
"forgotPassword": "忘记密码?", // Passwort vergessen?
|
||||
"loginWithGoogle": "使用 Google 登录", // Mit Google anmelden
|
||||
"or": "或", // ODER
|
||||
"privacyAccept": "点击“使用 Google 登录”即表示我接受", // Mit dem Click auf \"Mit Google anmelden\" akzeptiere ich die
|
||||
"privacyPolicy": "隐私政策", // Datenschutzbestimmungen
|
||||
"passwordMinLength": "密码长度至少为8个字符", // Das Passwort muss mindestens 8 Zeichen lang sein
|
||||
"newPasswordMinLength": "新密码长度至少为8个字符", // Das neue Passwort muss mindestens 8 Zeichen lang sein
|
||||
"menu": {
|
||||
"profile": "个人资料", // Profil
|
||||
"checkout": "结账", // Bestellabschluss
|
||||
"orders": "订单", // Bestellungen
|
||||
"settings": "设置", // Einstellungen
|
||||
"adminDashboard": "管理员面板", // Admin Dashboard
|
||||
"adminUsers": "管理员用户" // Admin Users
|
||||
}
|
||||
},
|
||||
"cart": {
|
||||
"title": "购物车", // Warenkorb
|
||||
"empty": "空", // leer
|
||||
"addToCart": "加入购物车", // In den Korb
|
||||
"preorderCutting": "预订插枝", // Als Steckling vorbestellen
|
||||
"continueShopping": "继续购物", // Weiter einkaufen
|
||||
"proceedToCheckout": "前往结账", // Weiter zur Kasse
|
||||
"productCount": "{{count}} {{count, plural, one {产品} other {产品}}}", // {{count}} {{count, plural, one {Produkt} other {Produkte}}}
|
||||
"removeFromCart": "从购物车移除", // Aus dem Warenkorb entfernen
|
||||
"openCart": "打开购物车", // Warenkorb öffnen
|
||||
"availableFrom": "自 {{date}} 起可用", // Ab {{date}}
|
||||
"backToOrder": "← 返回订单", // ← Zurück zur Bestellung
|
||||
"sync": {
|
||||
"title": "购物车同步", // Warenkorb-Synchronisierung
|
||||
"description": "您的账户中有一个已保存的购物车。请选择您想如何继续:", // Sie haben einen gespeicherten Warenkorb in ihrem Account. Bitte wählen Sie, wie Sie verfahren möchten:
|
||||
"deleteServer": "删除服务器购物车", // Server-Warenkorb löschen
|
||||
"useServer": "使用服务器购物车", // Server-Warenkorb übernehmen
|
||||
"merge": "合并购物车", // Warenkörbe zusammenführen
|
||||
"currentCart": "您当前的购物车", // Ihr aktueller Warenkorb
|
||||
"serverCart": "保存在您个人资料中的购物车" // In Ihrem Profil gespeicherter Warenkorb
|
||||
}
|
||||
},
|
||||
"product": {
|
||||
"loading": "正在加载产品...", // Produkt wird geladen...
|
||||
"notFound": "未找到产品", // Produkt nicht gefunden
|
||||
"notFoundDescription": "您查找的产品不存在或已被移除。", // Das gesuchte Produkt existiert nicht oder wurde entfernt.
|
||||
"backToHome": "返回首页", // Zurück zur Startseite
|
||||
"error": "错误", // Fehler
|
||||
"articleNumber": "商品编号", // Artikelnummer
|
||||
"manufacturer": "制造商", // Hersteller
|
||||
"inclVat": "含 {{vat}}% 增值税", // inkl. {{vat}}% MwSt.
|
||||
"priceUnit": "{{price}}/{{unit}}", // {{price}}/{{unit}}
|
||||
"new": "新品", // Neu
|
||||
"arriving": "到货时间:", // Ankunft:
|
||||
"inclVatFooter": "含 {{vat}}% 增值税,*", // incl. {{vat}}% USt.,*
|
||||
"availability": "可用性", // Verfügbarkeit
|
||||
"inStock": "有库存", // auf Lager
|
||||
"comingSoon": "即将上市", // Bald verfügbar
|
||||
"deliveryTime": "交货时间", // Lieferzeit
|
||||
"inclShort": "含", // inkl.
|
||||
"vatShort": "增值税", // MwSt.
|
||||
"countDisplay": {
|
||||
"noProducts": "0 个产品", // 0 Produkte
|
||||
"oneProduct": "1 个产品", // 1 Produkt
|
||||
"multipleProducts": "{{count}} 个产品", // {{count}} Produkte
|
||||
"filteredProducts": "{{filtered}} 个,共 {{total}} 个产品", // {{filtered}} von {{total}} Produkten
|
||||
"filteredOneProduct": "{{filtered}} 个,共 1 个产品" // {{filtered}} von 1 Produkt
|
||||
},
|
||||
"removeFiltersToSee": "移除筛选以查看产品", // Entferne Filter um Produkte zu sehen
|
||||
"outOfStock": "缺货", // Out of Stock
|
||||
"fromXProducts": "从 {{count}} 个产品开始" // ab {{count}} Produkten
|
||||
},
|
||||
"search": {
|
||||
"placeholder": "您可以问我有关大麻品种的问题...", // Du kannst mich nach Cannabissorten fragen...
|
||||
"recording": "录音中...", // Aufnahme läuft...
|
||||
"searchProducts": "搜索产品..." // Produkte suchen...
|
||||
},
|
||||
"sorting": {
|
||||
"name": "名称", // Name
|
||||
"searchField": "搜索词", // Suchbegriff
|
||||
"priceLowHigh": "价格:从低到高", // Preis: Niedrig zu Hoch
|
||||
"priceHighLow": "价格:从高到低" // Preis: Hoch zu Niedrig
|
||||
},
|
||||
"chat": {
|
||||
"privacyRead": "已阅读并接受" // Gelesen & Akzeptiert
|
||||
},
|
||||
"delivery": {
|
||||
"methods": {
|
||||
"dhl": "DHL", // DHL
|
||||
"dpd": "DPD", // DPD
|
||||
"sperrgut": "大件物品", // Sperrgut
|
||||
"pickup": "门店自取" // Abholung in der Filiale
|
||||
},
|
||||
"descriptions": {
|
||||
"standard": "标准配送", // Standardversand
|
||||
"standardFree": "标准配送 - 订单满100欧元免运费!", // Standardversand - KOSTENLOS ab 100€ Warenwert!
|
||||
"notAvailable": "不可选择,因为一个或多个商品只能自取", // nicht auswählbar weil ein oder mehrere Artikel nur abgeholt werden können
|
||||
"bulky": "适用于大件和重物" // Für große und schwere Artikel
|
||||
},
|
||||
"prices": {
|
||||
"free": "免费", // kostenlos
|
||||
"freeFrom100": "(订单满100欧元免费)", // (kostenlos ab 100€)
|
||||
"dhl": "€6.99", // 6,99 €
|
||||
"dpd": "€4.90", // 4,90 €
|
||||
"sperrgut": "€28.99" // 28,99 €
|
||||
},
|
||||
"times": {
|
||||
"cutting14Days": "交货时间:14天", // Lieferzeit: 14 Tage
|
||||
"standard2to3Days": "交货时间:2-3天", // Lieferzeit: 2-3 Tage
|
||||
"supplier7to9Days": "交货时间:7-9天" // Lieferzeit: 7-9 Tage
|
||||
}
|
||||
},
|
||||
"checkout": {
|
||||
"invoiceAddress": "发票地址", // Rechnungsadresse
|
||||
"deliveryAddress": "收货地址", // Lieferadresse
|
||||
"saveForFuture": "保存以便未来订单使用", // Für zukünftige Bestellungen speichern
|
||||
"pickupDate": "您希望在哪一天取插枝?", // Für welchen Termin ist die Abholung der Stecklinge gewünscht?
|
||||
"note": "备注", // Anmerkung
|
||||
"sameAddress": "收货地址与发票地址相同", // Lieferadresse ist identisch mit Rechnungsadresse
|
||||
"termsAccept": "我已阅读并接受条款与条件、隐私政策及撤销权信息" // Ich habe die AGBs, die Datenschutzerklärung und die Bestimmungen zum Widerrufsrecht gelesen
|
||||
},
|
||||
"payment": {
|
||||
"successful": "支付成功!", // Zahlung erfolgreich!
|
||||
"failed": "支付失败", // Zahlung fehlgeschlagen
|
||||
"orderCompleted": "🎉 您的订单已成功完成!您现在可以查看您的订单。", // 🎉 Ihre Bestellung wurde erfolgreich abgeschlossen! Sie können jetzt Ihre Bestellungen einsehen.
|
||||
"orderProcessing": "您的付款已成功处理。订单将自动完成。", // Ihre Zahlung wurde erfolgreich verarbeitet. Die Bestellung wird automatisch abgeschlossen.
|
||||
"paymentError": "您的付款未能处理。请重试或选择其他支付方式。", // Ihre Zahlung konnte nicht verarbeitet werden. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode.
|
||||
"viewOrders": "查看我的订单" // Zu meinen Bestellungen
|
||||
},
|
||||
"filters": {
|
||||
"sorting": "排序", // Sortierung
|
||||
"perPage": "每页", // pro Seite
|
||||
"availability": "可用性", // Verfügbarkeit
|
||||
"manufacturer": "制造商" // Hersteller
|
||||
},
|
||||
"tax": {
|
||||
"vat": "增值税", // Mehrwertsteuer
|
||||
"vat7": "7% 增值税", // 7% Mehrwertsteuer
|
||||
"vat19": "19% 增值税", // 19% Mehrwertsteuer
|
||||
"vat19WithShipping": "19% 增值税(含运费)", // 19% Mehrwertsteuer (inkl. Versand)
|
||||
"totalNet": "总净价", // Gesamtnettopreis
|
||||
"totalGross": "总毛价(不含运费)", // Gesamtbruttopreis ohne Versand
|
||||
"subtotal": "小计" // Zwischensumme
|
||||
},
|
||||
"footer": {
|
||||
"hours": "周六 11-19", // Sa 11-19
|
||||
"address": "Trachenberger Straße 14 - Dresden", // Trachenberger Straße 14 - Dresden
|
||||
"location": "位于 Pieschen 站和 Trachenberger Platz 之间", // Zwischen Haltepunkt Pieschen und Trachenberger Platz
|
||||
"allPricesIncl": "* 所有价格均含法定增值税,另加运费", // * Alle Preise inkl. gesetzlicher USt., zzgl. Versand
|
||||
"copyright": "© {{year}} GrowHeads.de", // © {{year}} GrowHeads.de
|
||||
"legal": {
|
||||
"datenschutz": "隐私", // Datenschutz
|
||||
"agb": "条款与条件", // AGB
|
||||
"sitemap": "网站地图", // Sitemap
|
||||
"impressum": "版权声明", // Impressum
|
||||
"batteriegesetzhinweise": "电池法说明", // Batteriegesetzhinweise
|
||||
"widerrufsrecht": "撤销权" // Widerrufsrecht
|
||||
}
|
||||
},
|
||||
"titles": {
|
||||
"home": "优质大麻种子与插枝", // Fine Cannabis Seeds & Cuttings (the text in the string has special characters, translate using the comment)
|
||||
"aktionen": "当前促销与优惠", // Aktuelle Aktionen & Angebote (the text in the string has special characters, translate using the comment)
|
||||
"filiale": "我们在德累斯顿的分店" // Unsere Filiale in Dresden (the text in the string has special characters, translate using the comment)
|
||||
},
|
||||
"sections": {
|
||||
"seeds": "种子", // Seeds
|
||||
"stecklinge": "插枝", // Stecklinge
|
||||
"oilPress": "借用榨油机", // Ölpresse ausleihen
|
||||
"thcTest": "THC 测试", // THC Test
|
||||
"address1": "Trachenberger Straße 14", // Trachenberger Straße 14
|
||||
"address2": "01129 Dresden" // 01129 Dresden
|
||||
},
|
||||
"pages": {
|
||||
"oilPress": {
|
||||
"title": "借用榨油机", // Ölpresse ausleihen
|
||||
"comingSoon": "内容即将推出..." // Inhalt kommt bald...
|
||||
},
|
||||
"thcTest": {
|
||||
"title": "THC 测试", // THC Test
|
||||
"comingSoon": "内容即将推出..." // Inhalt kommt bald...
|
||||
}
|
||||
},
|
||||
"orders": {
|
||||
"status": {
|
||||
"new": "处理中", // in Bearbeitung
|
||||
"pending": "新订单", // Neu
|
||||
"processing": "处理中", // in Bearbeitung
|
||||
"cancelled": "已取消", // Storniert
|
||||
"shipped": "已发货", // Verschickt
|
||||
"delivered": "已送达", // Geliefert
|
||||
"return": "退货", // Retoure
|
||||
"partialReturn": "部分退货", // Teil Retoure
|
||||
"partialDelivered": "部分送达" // Teil geliefert
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"loading": "加载中...", // Lädt...
|
||||
"error": "错误", // Fehler
|
||||
"close": "关闭", // Schließen
|
||||
"save": "保存", // Speichern
|
||||
"cancel": "取消", // Abbrechen
|
||||
"ok": "确定", // OK
|
||||
"yes": "是", // Ja
|
||||
"no": "否", // Nein
|
||||
"next": "下一步", // Weiter
|
||||
"back": "返回", // Zurück
|
||||
"edit": "编辑", // Bearbeiten
|
||||
"delete": "删除", // Löschen
|
||||
"add": "添加", // Hinzufügen
|
||||
"remove": "移除", // Entfernen
|
||||
"products": "产品", // Produkte
|
||||
"product": "产品" // Produkt
|
||||
}
|
||||
};
|
||||
120
src/i18n/withTranslation.js
Normal file
120
src/i18n/withTranslation.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import React, { Component } from 'react';
|
||||
import { withTranslation as reactI18nextWithTranslation } from 'react-i18next';
|
||||
|
||||
// HOC to provide translation functions to class components
|
||||
export const withTranslation = (namespaces = 'translation') => (WrappedComponent) => {
|
||||
return reactI18nextWithTranslation(namespaces)(WrappedComponent);
|
||||
};
|
||||
|
||||
// Context for language switching
|
||||
export const LanguageContext = React.createContext({
|
||||
currentLanguage: 'de',
|
||||
changeLanguage: () => {},
|
||||
availableLanguages: ['ar', 'bg', 'cs', 'de', 'el', 'en', 'es', 'fr', 'hr', 'hu', 'it', 'pl', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'tr', 'uk', 'zh']
|
||||
});
|
||||
|
||||
// Provider component for language management
|
||||
export class LanguageProvider extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// Get initial language from i18n instance
|
||||
const currentLanguage = props.i18n?.language || 'de';
|
||||
|
||||
this.state = {
|
||||
currentLanguage,
|
||||
availableLanguages: ['ar', 'bg', 'cs', 'de', 'el', 'en', 'es', 'fr', 'hr', 'hu', 'it', 'pl', 'ro', 'ru', 'sk', 'sl', 'sr', 'sv', 'tr', 'uk', 'zh']
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Listen for language changes from i18n
|
||||
if (this.props.i18n) {
|
||||
this.props.i18n.on('languageChanged', this.handleLanguageChanged);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Clean up listener
|
||||
if (this.props.i18n) {
|
||||
this.props.i18n.off('languageChanged', this.handleLanguageChanged);
|
||||
}
|
||||
}
|
||||
|
||||
handleLanguageChanged = (lng) => {
|
||||
this.setState({ currentLanguage: lng });
|
||||
|
||||
// Update HTML lang attribute for SEO
|
||||
document.documentElement.lang = lng;
|
||||
|
||||
// Update config if available
|
||||
if (window.shopConfig) {
|
||||
// Language code mapping for all supported languages
|
||||
const languageMap = {
|
||||
'ar': 'ar-EG',
|
||||
'bg': 'bg-BG',
|
||||
'cs': 'cs-CZ',
|
||||
'de': 'de-DE',
|
||||
'el': 'el-GR',
|
||||
'en': 'en-US',
|
||||
'es': 'es-ES',
|
||||
'fr': 'fr-FR',
|
||||
'hr': 'hr-HR',
|
||||
'hu': 'hu-HU',
|
||||
'it': 'it-IT',
|
||||
'pl': 'pl-PL',
|
||||
'ro': 'ro-RO',
|
||||
'ru': 'ru-RU',
|
||||
'sk': 'sk-SK',
|
||||
'sl': 'sl-SI',
|
||||
'sr': 'sr-RS',
|
||||
'sv': 'sv-SE',
|
||||
'tr': 'tr-TR',
|
||||
'uk': 'uk-UA',
|
||||
'zh': 'zh-CN'
|
||||
};
|
||||
window.shopConfig.language = languageMap[lng] || 'de-DE';
|
||||
}
|
||||
};
|
||||
|
||||
changeLanguage = (language) => {
|
||||
if (this.props.i18n && this.state.availableLanguages.includes(language)) {
|
||||
this.props.i18n.changeLanguage(language);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const contextValue = {
|
||||
currentLanguage: this.state.currentLanguage,
|
||||
changeLanguage: this.changeLanguage,
|
||||
availableLanguages: this.state.availableLanguages
|
||||
};
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider value={contextValue}>
|
||||
{this.props.children}
|
||||
</LanguageContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// HOC to inject language context into class components
|
||||
export const withLanguage = (WrappedComponent) => {
|
||||
return class extends Component {
|
||||
render() {
|
||||
return (
|
||||
<LanguageContext.Consumer>
|
||||
{(languageContext) => (
|
||||
<WrappedComponent {...this.props} languageContext={languageContext} />
|
||||
)}
|
||||
</LanguageContext.Consumer>
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Combined HOC that provides both translation and language context
|
||||
export const withI18n = (namespaces = 'translation') => (WrappedComponent) => {
|
||||
const WithTranslationComponent = withTranslation(namespaces)(WrappedComponent);
|
||||
return withLanguage(WithTranslationComponent);
|
||||
};
|
||||
@@ -56,4 +56,5 @@ body.MuiModal-open {
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ const Datenschutz = () => {
|
||||
Für andere Zahlungsarten – Lastschrift, Abholung oder Nachnahme – werden keine zusätzlichen Cookies verwendet, sofern Sie nicht die Google-Anmeldung nutzen.
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
|
||||
{/*<Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
|
||||
Stripe (Zahlungsabwicklung)
|
||||
</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
@@ -117,7 +117,21 @@ const Datenschutz = () => {
|
||||
</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
Soweit Stripe personenbezogene Daten außerhalb der EU, insbesondere in den USA, verarbeitet, geschieht dies unter Einhaltung geeigneter Garantien. Stripe setzt hierfür die EU-Standardvertragsklauseln nach Art. 46 DSGVO ein, um ein angemessenes Datenschutzniveau sicherzustellen. Wir weisen jedoch darauf hin, dass die USA datenschutzrechtlich als Drittland mit möglicherweise unzureichendem Datenschutzniveau gelten. Weitere Informationen finden Sie in der Datenschutzerklärung von Stripe unter <a href="https://stripe.com/de/privacy" target="_blank" rel="noopener noreferrer">https://stripe.com/de/privacy</a>.
|
||||
</Typography>*/}
|
||||
|
||||
<Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
|
||||
Mollie (Zahlungsabwicklung)
|
||||
</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
Wir nutzen auf unserer Website den Zahlungsdienstleister Mollie zur Abwicklung von Zahlungen. Anbieter des Dienstes ist Mollie B.V., Keizersgracht 126, 1015 CW Amsterdam, Niederlande. In diesem Zusammenhang werden personenbezogene Daten, die für die Zahlungsabwicklung erforderlich sind, an Mollie übermittelt - insbesondere Ihr Name, Ihre E-Mail-Adresse, Rechnungsanschrift, Zahlungsinformationen (z. B. Kreditkartendaten) sowie die IP-Adresse. Die Datenverarbeitung erfolgt zum Zweck der Zahlungsabwicklung; Rechtsgrundlage ist Art. 6 Abs. 1 lit. b DSGVO, da sie der Erfüllung eines Vertrags mit Ihnen dient.
|
||||
</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
Mollie verarbeitet bestimmte Daten außerdem als eigenständig Verantwortlicher, beispielsweise zur Erfüllung gesetzlicher Pflichten (etwa Geldwäsche-Prävention) und zur Betrugsabwehr. Daneben haben wir mit Mollie einen Auftragsverarbeitungsvertrag gemäß Art. 28 DSGVO geschlossen; im Rahmen dieser Vereinbarung handelt Mollie bei der Zahlungsabwicklung ausschließlich nach unserer Weisung.
|
||||
</Typography>
|
||||
<Typography variant="body1" paragraph>
|
||||
Soweit Mollie personenbezogene Daten außerhalb der EU, insbesondere in den USA, verarbeitet, geschieht dies unter Einhaltung geeigneter Garantien. Mollie setzt hierfür die EU-Standardvertragsklauseln nach Art. 46 DSGVO ein, um ein angemessenes Datenschutzniveau sicherzustellen. Wir weisen jedoch darauf hin, dass die USA datenschutzrechtlich als Drittland mit möglicherweise unzureichendem Datenschutzniveau gelten. Weitere Informationen finden Sie in der Datenschutzerklärung von Mollie unter <a href="https://www.mollie.com/de/privacy" target="_blank" rel="noopener noreferrer">https://www.mollie.com/de/privacy</a>.
|
||||
</Typography>
|
||||
|
||||
|
||||
<Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
|
||||
Dauer der Speicherung
|
||||
|
||||
@@ -17,6 +17,51 @@ import {
|
||||
import { TentShapeSelector, ProductSelector, ExtrasSelector } from '../components/configurator/index.js';
|
||||
import { tentShapes, tentSizes, lightTypes, ventilationTypes, extras } from '../data/configuratorData.js';
|
||||
|
||||
function setCachedCategoryData(categoryId, data) {
|
||||
if (!window.productCache) {
|
||||
window.productCache = {};
|
||||
}
|
||||
if (!window.productDetailCache) {
|
||||
window.productDetailCache = {};
|
||||
}
|
||||
|
||||
try {
|
||||
const cacheKey = `categoryProducts_${categoryId}`;
|
||||
if(data.products) for(const product of data.products) {
|
||||
window.productDetailCache[product.id] = product;
|
||||
}
|
||||
window.productCache[cacheKey] = {
|
||||
...data,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error writing to cache:', err);
|
||||
}
|
||||
}
|
||||
function getCachedCategoryData(categoryId) {
|
||||
if (!window.productCache) {
|
||||
window.productCache = {};
|
||||
}
|
||||
|
||||
try {
|
||||
const cacheKey = `categoryProducts_${categoryId}`;
|
||||
const cachedData = window.productCache[cacheKey];
|
||||
|
||||
if (cachedData) {
|
||||
const { timestamp } = cachedData;
|
||||
const cacheAge = Date.now() - timestamp;
|
||||
const tenMinutes = 10 * 60 * 1000;
|
||||
if (cacheAge < tenMinutes) {
|
||||
return cachedData;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error reading from cache:', err);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
class GrowTentKonfigurator extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
@@ -40,6 +85,8 @@ class GrowTentKonfigurator extends Component {
|
||||
this.handleExtraToggle = this.handleExtraToggle.bind(this);
|
||||
this.calculateTotalPrice = this.calculateTotalPrice.bind(this);
|
||||
this.saveStateToWindow = this.saveStateToWindow.bind(this);
|
||||
|
||||
|
||||
}
|
||||
|
||||
saveStateToWindow() {
|
||||
@@ -57,7 +104,10 @@ class GrowTentKonfigurator extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
// @note Calculate initial total price with preselected products
|
||||
this.calculateTotalPrice();
|
||||
//this.calculateTotalPrice();
|
||||
this.fetchCategoryData("Zelte");
|
||||
this.fetchCategoryData("Lampen");
|
||||
this.fetchCategoryData("Abluft-sets");
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
@@ -89,6 +139,31 @@ class GrowTentKonfigurator extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
fetchCategoryData(categoryId) {
|
||||
const cachedData = getCachedCategoryData(categoryId);
|
||||
if (cachedData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.props.socket || !this.props.socket.connected) {
|
||||
console.log("Socket not connected yet, waiting for connection to fetch category data");
|
||||
return;
|
||||
}
|
||||
console.log(`productList:${categoryId}`);
|
||||
this.props.socket.off(`productList:${categoryId}`);
|
||||
|
||||
this.props.socket.on(`productList:${categoryId}`,(response) => {
|
||||
console.log("getCategoryProducts full response", response);
|
||||
setCachedCategoryData(categoryId, response);
|
||||
});
|
||||
|
||||
this.props.socket.emit("getCategoryProducts", { categoryId: categoryId },
|
||||
(response) => {
|
||||
console.log("getCategoryProducts stub response", response);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
handleTentShapeSelect(shapeId) {
|
||||
this.setState({ selectedTentShape: shapeId });
|
||||
}
|
||||
|
||||
@@ -1,652 +0,0 @@
|
||||
import React, { useState, useEffect, useContext, useRef } from "react";
|
||||
import Container from "@mui/material/Container";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import ChevronLeft from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRight from "@mui/icons-material/ChevronRight";
|
||||
import { Link } from "react-router-dom";
|
||||
import CategoryBox from "../components/CategoryBox.js";
|
||||
import SocketContext from "../contexts/SocketContext.js";
|
||||
import { getCombinedAnimatedBorderStyles } from "../utils/animatedBorderStyles.js";
|
||||
|
||||
// @note SwashingtonCP font is now loaded globally via index.css
|
||||
|
||||
// Carousel styles - Simple styles for JavaScript-based animation
|
||||
const carouselStyles = `
|
||||
.carousel-wrapper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
||||
.carousel-wrapper .carousel-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 20px 0;
|
||||
width: 100%;
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.carousel-wrapper .home-carousel-track {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
transition: none;
|
||||
align-items: flex-start;
|
||||
width: 1200px;
|
||||
max-width: 100%;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.carousel-wrapper .carousel-item {
|
||||
flex: 0 0 130px;
|
||||
width: 130px !important;
|
||||
max-width: 130px;
|
||||
min-width: 130px;
|
||||
height: 130px !important;
|
||||
max-height: 130px;
|
||||
min-height: 130px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.carousel-nav-button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 20;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.carousel-nav-button:hover {
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.carousel-nav-left {
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
.carousel-nav-right {
|
||||
right: 8px;
|
||||
}
|
||||
`;
|
||||
|
||||
// Generate combined styles for both seeds and cutlings cards
|
||||
const animatedBorderStyle = getCombinedAnimatedBorderStyles([
|
||||
"seeds",
|
||||
"cutlings",
|
||||
]);
|
||||
|
||||
const Home = () => {
|
||||
const carouselRef = useRef(null);
|
||||
const scrollPositionRef = useRef(0);
|
||||
const animationIdRef = useRef(null);
|
||||
const isPausedRef = useRef(false);
|
||||
const resumeTimeoutRef = useRef(null);
|
||||
|
||||
// @note Initialize refs properly
|
||||
useEffect(() => {
|
||||
isPausedRef.current = false;
|
||||
scrollPositionRef.current = 0;
|
||||
}, []);
|
||||
// Helper to process and set categories
|
||||
const processCategoryTree = (categoryTree) => {
|
||||
if (
|
||||
categoryTree &&
|
||||
categoryTree.id === 209 &&
|
||||
Array.isArray(categoryTree.children)
|
||||
) {
|
||||
return categoryTree.children;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// Check for cached data - handle both browser and prerender environments
|
||||
const getProductCache = () => {
|
||||
if (typeof window !== "undefined" && window.productCache) {
|
||||
return window.productCache;
|
||||
}
|
||||
if (
|
||||
typeof global !== "undefined" &&
|
||||
global.window &&
|
||||
global.window.productCache
|
||||
) {
|
||||
return global.window.productCache;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Initialize rootCategories from cache if available (for prerendering)
|
||||
const initializeCategories = () => {
|
||||
const productCache = getProductCache();
|
||||
|
||||
if (productCache && productCache["categoryTree_209"]) {
|
||||
const cached = productCache["categoryTree_209"];
|
||||
//const cacheAge = Date.now() - cached.timestamp;
|
||||
//const tenMinutes = 10 * 60 * 1000;
|
||||
if (/*cacheAge < tenMinutes &&*/ cached.categoryTree) {
|
||||
return processCategoryTree(cached.categoryTree);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const [rootCategories, setRootCategories] = useState(() =>
|
||||
initializeCategories()
|
||||
);
|
||||
const context = useContext(SocketContext);
|
||||
|
||||
useEffect(() => {
|
||||
// Only fetch from socket if we don't already have categories and we're in browser
|
||||
if (
|
||||
rootCategories.length === 0 &&
|
||||
context && context.socket && context.socket.connected &&
|
||||
typeof window !== "undefined"
|
||||
) {
|
||||
context.socket.emit("categoryList", { categoryId: 209 }, (response) => {
|
||||
if (response && response.categoryTree) {
|
||||
// Store in cache
|
||||
try {
|
||||
if (!window.productCache) window.productCache = {};
|
||||
window.productCache["categoryTree_209"] = {
|
||||
categoryTree: response.categoryTree,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
setRootCategories(response.categoryTree.children || []);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [context, context?.socket?.connected, rootCategories.length]);
|
||||
|
||||
// Filter categories (excluding specific IDs)
|
||||
const filteredCategories = rootCategories.filter(
|
||||
(cat) => cat.id !== 689 && cat.id !== 706
|
||||
);
|
||||
|
||||
// Create duplicated array for seamless scrolling
|
||||
const displayCategories = [...filteredCategories, ...filteredCategories];
|
||||
|
||||
// Auto-scroll effect
|
||||
useEffect(() => {
|
||||
if (filteredCategories.length === 0) return;
|
||||
|
||||
// @note Add a small delay to ensure DOM is ready after prerender
|
||||
const startAnimation = () => {
|
||||
if (!carouselRef.current) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// @note Reset paused state when starting animation
|
||||
isPausedRef.current = false;
|
||||
|
||||
const itemWidth = 146; // 130px + 16px gap
|
||||
const totalWidth = filteredCategories.length * itemWidth;
|
||||
|
||||
const animate = () => {
|
||||
// Check if we should be animating
|
||||
if (!animationIdRef.current || isPausedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollPositionRef.current += 0.5; // Speed of scrolling
|
||||
|
||||
// Reset position for seamless loop
|
||||
if (scrollPositionRef.current >= totalWidth) {
|
||||
scrollPositionRef.current = 0;
|
||||
}
|
||||
|
||||
if (carouselRef.current) {
|
||||
const transform = `translateX(-${scrollPositionRef.current}px)`;
|
||||
carouselRef.current.style.transform = transform;
|
||||
}
|
||||
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
// Only start animation if not paused
|
||||
if (!isPausedRef.current) {
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Try immediately, then with increasing delays to handle prerender scenarios
|
||||
if (!startAnimation()) {
|
||||
const timeout1 = setTimeout(() => {
|
||||
if (!startAnimation()) {
|
||||
const timeout2 = setTimeout(() => {
|
||||
if (!startAnimation()) {
|
||||
const timeout3 = setTimeout(startAnimation, 2000);
|
||||
return () => clearTimeout(timeout3);
|
||||
}
|
||||
}, 1000);
|
||||
return () => clearTimeout(timeout2);
|
||||
}
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
isPausedRef.current = true;
|
||||
clearTimeout(timeout1);
|
||||
if (animationIdRef.current) {
|
||||
cancelAnimationFrame(animationIdRef.current);
|
||||
}
|
||||
if (resumeTimeoutRef.current) {
|
||||
clearTimeout(resumeTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return () => {
|
||||
isPausedRef.current = true;
|
||||
if (animationIdRef.current) {
|
||||
cancelAnimationFrame(animationIdRef.current);
|
||||
}
|
||||
if (resumeTimeoutRef.current) {
|
||||
clearTimeout(resumeTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [filteredCategories]);
|
||||
|
||||
// Additional effect to handle cases where categories are available but ref wasn't ready
|
||||
useEffect(() => {
|
||||
if (filteredCategories.length > 0 && carouselRef.current && !animationIdRef.current) {
|
||||
// @note Reset paused state when starting animation
|
||||
isPausedRef.current = false;
|
||||
|
||||
const itemWidth = 146;
|
||||
const totalWidth = filteredCategories.length * itemWidth;
|
||||
|
||||
const animate = () => {
|
||||
if (!animationIdRef.current || isPausedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollPositionRef.current += 0.5;
|
||||
|
||||
if (scrollPositionRef.current >= totalWidth) {
|
||||
scrollPositionRef.current = 0;
|
||||
}
|
||||
|
||||
if (carouselRef.current) {
|
||||
const transform = `translateX(-${scrollPositionRef.current}px)`;
|
||||
carouselRef.current.style.transform = transform;
|
||||
}
|
||||
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
if (!isPausedRef.current) {
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Manual navigation
|
||||
const moveCarousel = (direction) => {
|
||||
if (!carouselRef.current) return;
|
||||
|
||||
// Pause auto-scroll
|
||||
isPausedRef.current = true;
|
||||
if (animationIdRef.current) {
|
||||
cancelAnimationFrame(animationIdRef.current);
|
||||
animationIdRef.current = null;
|
||||
}
|
||||
|
||||
const itemWidth = 146;
|
||||
const moveAmount = itemWidth * 3; // Move 3 items at a time
|
||||
const totalWidth = filteredCategories.length * itemWidth;
|
||||
|
||||
if (direction === "left") {
|
||||
scrollPositionRef.current -= moveAmount;
|
||||
// Handle wrapping for infinite scroll
|
||||
if (scrollPositionRef.current < 0) {
|
||||
scrollPositionRef.current = totalWidth + scrollPositionRef.current;
|
||||
}
|
||||
} else {
|
||||
scrollPositionRef.current += moveAmount;
|
||||
// Handle wrapping for infinite scroll
|
||||
if (scrollPositionRef.current >= totalWidth) {
|
||||
scrollPositionRef.current = scrollPositionRef.current % totalWidth;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply smooth transition for manual navigation
|
||||
carouselRef.current.style.transition = "transform 0.5s ease-in-out";
|
||||
carouselRef.current.style.transform = `translateX(-${scrollPositionRef.current}px)`;
|
||||
|
||||
// Remove transition after animation completes
|
||||
setTimeout(() => {
|
||||
if (carouselRef.current) {
|
||||
carouselRef.current.style.transition = "none";
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// Clear any existing resume timeout
|
||||
if (resumeTimeoutRef.current) {
|
||||
clearTimeout(resumeTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Resume auto-scroll after 3 seconds
|
||||
resumeTimeoutRef.current = setTimeout(() => {
|
||||
isPausedRef.current = false;
|
||||
|
||||
const animate = () => {
|
||||
if (!animationIdRef.current || isPausedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrollPositionRef.current += 0.5;
|
||||
|
||||
if (scrollPositionRef.current >= totalWidth) {
|
||||
scrollPositionRef.current = 0;
|
||||
}
|
||||
|
||||
if (carouselRef.current) {
|
||||
carouselRef.current.style.transform = `translateX(-${scrollPositionRef.current}px)`;
|
||||
}
|
||||
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
animationIdRef.current = requestAnimationFrame(animate);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ pt: 4, pb: 2, maxWidth: '1200px !important' }}>
|
||||
{/* Inject the animated border and carousel styles */}
|
||||
<style>{animatedBorderStyle}</style>
|
||||
<style>{carouselStyles}</style>
|
||||
|
||||
<Typography
|
||||
variant="h3"
|
||||
component="h1"
|
||||
sx={{
|
||||
mb: 4,
|
||||
fontFamily: "SwashingtonCP",
|
||||
color: "primary.main",
|
||||
textAlign: "center",
|
||||
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
|
||||
}}
|
||||
>
|
||||
ine annabis eeds & uttings
|
||||
</Typography>
|
||||
|
||||
<Grid container sx={{ display: "flex", flexDirection: "row" }}>
|
||||
{/* Seeds Category Box */}
|
||||
<Grid item xs={12} sm={6} sx={{ p: 2, width: "50%" }}>
|
||||
<div className="animated-border-card seeds-card">
|
||||
<Paper
|
||||
component={Link}
|
||||
to="/Kategorie/Seeds"
|
||||
sx={{
|
||||
p: 0,
|
||||
textDecoration: "none",
|
||||
color: "text.primary",
|
||||
borderRadius: 2,
|
||||
overflow: "hidden",
|
||||
height: { xs: 150, sm: 200, md: 300 },
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
transition: "all 0.3s ease",
|
||||
boxShadow: 10,
|
||||
"&:hover": {
|
||||
transform: "translateY(-5px)",
|
||||
boxShadow: 20,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Image Container - Place your seeds image here */}
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
bgcolor: "#e1f0d3",
|
||||
backgroundImage: 'url("/assets/images/seeds.jpg")',
|
||||
backgroundSize: "contain",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{/* Overlay text - optional */}
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bgcolor: "rgba(27, 94, 32, 0.8)",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "1.6rem",
|
||||
color: "white",
|
||||
fontFamily: "SwashingtonCP",
|
||||
}}
|
||||
>
|
||||
Seeds
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
{/* Cutlings Category Box */}
|
||||
<Grid item xs={12} sm={6} sx={{ p: 2, width: "50%" }}>
|
||||
<div className="animated-border-card cutlings-card">
|
||||
<Paper
|
||||
component={Link}
|
||||
to="/Kategorie/Stecklinge"
|
||||
sx={{
|
||||
p: 0,
|
||||
textDecoration: "none",
|
||||
color: "text.primary",
|
||||
borderRadius: 2,
|
||||
overflow: "hidden",
|
||||
height: { xs: 150, sm: 200, md: 300 },
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
boxShadow: 10,
|
||||
transition: "all 0.3s ease",
|
||||
"&:hover": {
|
||||
transform: "translateY(-5px)",
|
||||
boxShadow: 20,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{/* Image Container - Place your cutlings image here */}
|
||||
<Box
|
||||
sx={{
|
||||
height: "100%",
|
||||
bgcolor: "#e8f5d6",
|
||||
backgroundImage: 'url("/assets/images/cutlings.jpg")',
|
||||
backgroundSize: "contain",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{/* Overlay text - optional */}
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bgcolor: "rgba(27, 94, 32, 0.8)",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "1.6rem",
|
||||
color: "white",
|
||||
fontFamily: "SwashingtonCP",
|
||||
}}
|
||||
>
|
||||
Stecklinge
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Continuous Rotating Carousel for Categories */}
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
component="h1"
|
||||
sx={{
|
||||
mb: 2,
|
||||
fontFamily: "SwashingtonCP",
|
||||
color: "primary.main",
|
||||
textAlign: "center",
|
||||
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
|
||||
}}
|
||||
>
|
||||
Kategorien
|
||||
</Typography>
|
||||
|
||||
{filteredCategories.length > 0 && (
|
||||
<div
|
||||
className="carousel-wrapper"
|
||||
style={{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
width: '100%',
|
||||
maxWidth: '1200px',
|
||||
margin: '0 auto',
|
||||
padding: '0 20px',
|
||||
boxSizing: 'border-box'
|
||||
}}
|
||||
>
|
||||
{/* Left Arrow */}
|
||||
<IconButton
|
||||
onClick={() => moveCarousel("left")}
|
||||
aria-label="Vorherige Kategorien anzeigen"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '8px',
|
||||
transform: 'translateY(-50%)',
|
||||
zIndex: 1200,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
>
|
||||
<ChevronLeft />
|
||||
</IconButton>
|
||||
|
||||
{/* Right Arrow */}
|
||||
<IconButton
|
||||
onClick={() => moveCarousel("right")}
|
||||
aria-label="Nächste Kategorien anzeigen"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
right: '8px',
|
||||
transform: 'translateY(-50%)',
|
||||
zIndex: 1200,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
>
|
||||
<ChevronRight />
|
||||
</IconButton>
|
||||
|
||||
<div
|
||||
className="carousel-container"
|
||||
style={{
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
padding: '20px 0',
|
||||
width: '100%',
|
||||
maxWidth: '1080px',
|
||||
margin: '0 auto',
|
||||
zIndex: 1,
|
||||
boxSizing: 'border-box'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="home-carousel-track"
|
||||
ref={carouselRef}
|
||||
style={{
|
||||
display: 'flex',
|
||||
gap: '16px',
|
||||
transition: 'none',
|
||||
alignItems: 'flex-start',
|
||||
width: 'fit-content',
|
||||
overflow: 'visible',
|
||||
position: 'relative',
|
||||
transform: 'translateX(0px)',
|
||||
margin: '0 auto'
|
||||
}}
|
||||
>
|
||||
{displayCategories.map((category, index) => (
|
||||
<div
|
||||
key={`${category.id}-${index}`}
|
||||
className="carousel-item"
|
||||
style={{
|
||||
flex: '0 0 130px',
|
||||
width: '130px',
|
||||
maxWidth: '130px',
|
||||
minWidth: '130px',
|
||||
height: '130px',
|
||||
maxHeight: '130px',
|
||||
minHeight: '130px',
|
||||
boxSizing: 'border-box',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<CategoryBox
|
||||
id={category.id}
|
||||
name={category.name}
|
||||
seoName={category.seoName}
|
||||
image={category.image}
|
||||
bgcolor={category.bgcolor}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -16,19 +16,20 @@ const NotFound404 = () => {
|
||||
src="/assets/images/404.png"
|
||||
alt="404 - Page Not Found"
|
||||
style={{
|
||||
width: '300px',
|
||||
height: '300px',
|
||||
maxWidth: '100%',
|
||||
height: 'auto',
|
||||
maxHeight: '300px',
|
||||
display: 'block',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Typography variant="body1" paragraph align="center">
|
||||
This page is no longer available.
|
||||
Diese Seite scheint es nicht mehr zu geben.
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
||||
return <LegalPage title="Page Not Found" content={content} />;
|
||||
return <LegalPage content={content} />;
|
||||
};
|
||||
|
||||
export default NotFound404;
|
||||
49
src/pages/PresseverleihPage.js
Normal file
49
src/pages/PresseverleihPage.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import Container from "@mui/material/Container";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const PresseverleihPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ pt: 4, pb: 2, maxWidth: '1200px !important' }}>
|
||||
<Typography
|
||||
variant="h3"
|
||||
component="h1"
|
||||
sx={{
|
||||
mb: 4,
|
||||
fontFamily: "SwashingtonCP",
|
||||
color: "primary.main",
|
||||
textAlign: "center",
|
||||
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
|
||||
}}
|
||||
>
|
||||
{t('pages.oilPress.title')}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
minHeight: "50vh",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
fontStyle: "italic"
|
||||
}}
|
||||
>
|
||||
{t('pages.oilPress.comingSoon')}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default PresseverleihPage;
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
CircularProgress
|
||||
} from '@mui/material';
|
||||
import { useLocation, useNavigate, Navigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import SocketContext from '../contexts/SocketContext.js';
|
||||
|
||||
// Import extracted components
|
||||
@@ -21,6 +22,7 @@ import LoginComponent from '../components/LoginComponent.js';
|
||||
const ProfilePage = (props) => {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const [tabValue, setTabValue] = useState(0);
|
||||
const [user, setUser] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -28,9 +30,11 @@ const ProfilePage = (props) => {
|
||||
const [orderIdFromHash, setOrderIdFromHash] = useState(null);
|
||||
const [paymentCompletion, setPaymentCompletion] = useState(null);
|
||||
|
||||
// @note Check for payment completion parameters from Stripe redirect
|
||||
// @note Check for payment completion parameters from Stripe and Mollie redirects
|
||||
useEffect(() => {
|
||||
const urlParams = new URLSearchParams(location.search);
|
||||
|
||||
// Check for Stripe payment completion
|
||||
const isComplete = urlParams.has('complete');
|
||||
const paymentIntent = urlParams.get('payment_intent');
|
||||
const paymentIntentClientSecret = urlParams.get('payment_intent_client_secret');
|
||||
@@ -38,6 +42,7 @@ const ProfilePage = (props) => {
|
||||
|
||||
if (isComplete && paymentIntent && redirectStatus) {
|
||||
setPaymentCompletion({
|
||||
paymentType: 'stripe',
|
||||
paymentIntent,
|
||||
paymentIntentClientSecret,
|
||||
redirectStatus,
|
||||
@@ -52,6 +57,39 @@ const ProfilePage = (props) => {
|
||||
newUrl.searchParams.delete('redirect_status');
|
||||
window.history.replaceState({}, '', newUrl.toString());
|
||||
}
|
||||
|
||||
// Check for Mollie payment completion
|
||||
const isMollieComplete = urlParams.has('mollieComplete');
|
||||
const molliePaymentId = urlParams.get('mollie_payment_id');
|
||||
const mollieStatus = urlParams.get('mollie_status');
|
||||
const mollieAmount = urlParams.get('mollie_amount');
|
||||
const mollieTimestamp = urlParams.get('mollie_timestamp');
|
||||
const mollieIsPaid = urlParams.get('mollie_is_paid');
|
||||
const mollieOrderId = urlParams.get('mollie_order_id');
|
||||
|
||||
if (isMollieComplete && molliePaymentId && mollieStatus) {
|
||||
setPaymentCompletion({
|
||||
paymentType: 'mollie',
|
||||
molliePaymentId,
|
||||
mollieStatus,
|
||||
mollieAmount: mollieAmount ? parseFloat(mollieAmount) : null,
|
||||
mollieTimestamp: mollieTimestamp ? parseInt(mollieTimestamp) : null,
|
||||
mollieIsPaid: mollieIsPaid === 'true',
|
||||
mollieOrderId: mollieOrderId ? parseInt(mollieOrderId) : null,
|
||||
isSuccessful: mollieIsPaid === 'true'
|
||||
});
|
||||
|
||||
// Clean up the URL by removing the Mollie payment parameters
|
||||
const newUrl = new URL(window.location);
|
||||
newUrl.searchParams.delete('mollieComplete');
|
||||
newUrl.searchParams.delete('mollie_payment_id');
|
||||
newUrl.searchParams.delete('mollie_status');
|
||||
newUrl.searchParams.delete('mollie_amount');
|
||||
newUrl.searchParams.delete('mollie_timestamp');
|
||||
newUrl.searchParams.delete('mollie_is_paid');
|
||||
newUrl.searchParams.delete('mollie_order_id');
|
||||
window.history.replaceState({}, '', newUrl.toString());
|
||||
}
|
||||
}, [location.search]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -172,10 +210,10 @@ const ProfilePage = (props) => {
|
||||
<Box sx={{ bgcolor: '#2e7d32', p: { xs: 2, sm: 3 }, color: 'white' }}>
|
||||
<Typography variant="h5" fontWeight="bold">
|
||||
{window.innerWidth < 600 ?
|
||||
(tabValue === 0 ? 'Bestellabschluss' :
|
||||
tabValue === 1 ? 'Bestellungen' :
|
||||
tabValue === 2 ? 'Einstellungen' : 'Mein Profil')
|
||||
: 'Mein Profil'
|
||||
(tabValue === 0 ? (t ? t('auth.menu.checkout') : 'Bestellabschluss') :
|
||||
tabValue === 1 ? (t ? t('auth.menu.orders') : 'Bestellungen') :
|
||||
tabValue === 2 ? (t ? t('auth.menu.settings') : 'Einstellungen') : (t ? t('auth.profile') : 'Mein Profil'))
|
||||
: (t ? t('auth.profile') : 'Mein Profil')
|
||||
}
|
||||
</Typography>
|
||||
{user && (
|
||||
@@ -200,21 +238,21 @@ const ProfilePage = (props) => {
|
||||
}}
|
||||
>
|
||||
<Tab
|
||||
label="Bestellabschluss"
|
||||
label={t ? t('auth.menu.checkout') : 'Bestellabschluss'}
|
||||
sx={{
|
||||
color: tabValue === 0 ? '#2e7d32' : 'inherit',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Bestellungen"
|
||||
label={t ? t('auth.menu.orders') : 'Bestellungen'}
|
||||
sx={{
|
||||
color: tabValue === 1 ? '#2e7d32' : 'inherit',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
/>
|
||||
<Tab
|
||||
label="Einstellungen"
|
||||
label={t ? t('auth.menu.settings') : 'Einstellungen'}
|
||||
sx={{
|
||||
color: tabValue === 2 ? '#2e7d32' : 'inherit',
|
||||
fontWeight: 'bold'
|
||||
|
||||
49
src/pages/ThcTestPage.js
Normal file
49
src/pages/ThcTestPage.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import Container from "@mui/material/Container";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const ThcTestPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg" sx={{ pt: 4, pb: 2, maxWidth: '1200px !important' }}>
|
||||
<Typography
|
||||
variant="h3"
|
||||
component="h1"
|
||||
sx={{
|
||||
mb: 4,
|
||||
fontFamily: "SwashingtonCP",
|
||||
color: "primary.main",
|
||||
textAlign: "center",
|
||||
textShadow: "3px 3px 10px rgba(0, 0, 0, 0.4)"
|
||||
}}
|
||||
>
|
||||
{t('pages.thcTest.title')}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
minHeight: "50vh",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
color: "text.secondary",
|
||||
fontStyle: "italic"
|
||||
}}
|
||||
>
|
||||
{t('pages.thcTest.comingSoon')}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThcTestPage;
|
||||
378
translate-i18n.js
Executable file
378
translate-i18n.js
Executable file
@@ -0,0 +1,378 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import OpenAI from 'openai';
|
||||
|
||||
// Configuration
|
||||
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
||||
const LOCALES_DIR = './src/i18n/locales';
|
||||
const GERMAN_FILE = path.join(LOCALES_DIR, 'de/translation.js');
|
||||
const ENGLISH_FILE = path.join(LOCALES_DIR, 'en/translation.js');
|
||||
|
||||
// Model configuration
|
||||
const GERMAN_TO_ENGLISH_MODEL = 'gpt-4.1'; // High-quality model for German -> English (critical step)
|
||||
const ENGLISH_TO_OTHER_MODEL = 'gpt-4.1-mini'; // Faster/cheaper model for English -> Other languages
|
||||
|
||||
// Supported languages for translation
|
||||
const TARGET_LANGUAGES = {
|
||||
'bg': 'Bulgarian',
|
||||
'cs': 'Czech',
|
||||
'es': 'Spanish',
|
||||
'fr': 'French',
|
||||
'el': 'Greek',
|
||||
'hr': 'Croatian',
|
||||
'hu': 'Hungarian',
|
||||
'it': 'Italian',
|
||||
'pl': 'Polish',
|
||||
'ro': 'Romanian',
|
||||
'ru': 'Russian',
|
||||
'sk': 'Slovak',
|
||||
'sl': 'Slovenian',
|
||||
'sr': 'Serbian',
|
||||
'sv': 'Swedish',
|
||||
'tr': 'Turkish',
|
||||
'uk': 'Ukrainian',
|
||||
'ar': 'Arabic (Egyptian)',
|
||||
'zh': 'Chinese (Simplified)'
|
||||
};
|
||||
|
||||
// Initialize OpenAI client
|
||||
const openai = new OpenAI({
|
||||
apiKey: OPENAI_API_KEY,
|
||||
});
|
||||
|
||||
// System prompt for German to English translation
|
||||
const GERMAN_TO_ENGLISH_SYSTEM_PROMPT = `
|
||||
You MUST translate German strings to English AND add the original German text as a comment after EVERY translated string.
|
||||
|
||||
CRITICAL REQUIREMENT: Every translated string must have the original German text as a comment.
|
||||
|
||||
Rules:
|
||||
1. Translate all German strings to English
|
||||
2. MANDATORY: Add the original German text as a comment after EVERY translated string using // format
|
||||
3. Preserve all existing comments from the German version
|
||||
4. Maintain the exact JavaScript object structure and formatting
|
||||
5. Keep all interpolation variables like {{count}}, {{vat}}, etc. unchanged
|
||||
6. Keep locale codes appropriate for English
|
||||
7. For the locale section, use "en-US" as code
|
||||
8. Do not translate technical terms that are already in English
|
||||
9. Preserve any special formatting or HTML entities
|
||||
10. Return a valid JavaScript object (not JSON) that can be exported
|
||||
|
||||
MANDATORY FORMAT for every string:
|
||||
"englishTranslation": "English Translation", // Original German Text
|
||||
|
||||
Examples:
|
||||
"login": "Login", // Anmelden
|
||||
"email": "Email", // E-Mail
|
||||
"password": "Password", // Passwort
|
||||
"home": "Home", // Startseite
|
||||
|
||||
DO NOT output any string without its German comment. Every single translated string needs the German original as a comment.
|
||||
`;
|
||||
|
||||
// System prompt template for English to other languages (file content will be inserted)
|
||||
const ENGLISH_TO_OTHER_SYSTEM_PROMPT_TEMPLATE = `
|
||||
Translate the English strings in the following file to {{targetLanguage}}, preserving the German comments.
|
||||
|
||||
Rules:
|
||||
1. Translate only the English strings to {{targetLanguage}}
|
||||
2. Keep all German comments unchanged
|
||||
3. Maintain the exact JavaScript object structure and formatting
|
||||
4. Keep all interpolation variables like {{count}}, {{vat}}, etc. unchanged
|
||||
5. Update locale code appropriately for the target language
|
||||
6. Do not translate technical terms, API keys, or code-related strings
|
||||
7. Preserve any special formatting or HTML entities
|
||||
8. Keep existing comments from the English version
|
||||
9. Return a valid JavaScript object (not JSON) that can be exported
|
||||
|
||||
Here is the English translation file to translate:
|
||||
|
||||
{{englishFileContent}}
|
||||
`;
|
||||
|
||||
// Function to read and parse JavaScript export file
|
||||
function readTranslationFile(filePath) {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
// Remove the export default and evaluate the object
|
||||
const objectContent = content.replace(/^export default\s*/, '').replace(/;\s*$/, '');
|
||||
return eval(`(${objectContent})`);
|
||||
} catch (error) {
|
||||
console.error(`Error reading ${filePath}:`, error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to write translation file (preserving comments as string)
|
||||
function writeTranslationFile(filePath, translationString) {
|
||||
try {
|
||||
// Ensure directory exists
|
||||
const dir = path.dirname(filePath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
// Ensure the string has proper export format
|
||||
const content = translationString.startsWith('export default')
|
||||
? translationString
|
||||
: `export default ${translationString}`;
|
||||
|
||||
// Ensure it ends with semicolon and newline
|
||||
const finalContent = content.endsWith(';\n') ? content : content.replace(/;?\s*$/, ';\n');
|
||||
|
||||
fs.writeFileSync(filePath, finalContent, 'utf8');
|
||||
console.log(`✅ Successfully wrote ${filePath}`);
|
||||
} catch (error) {
|
||||
console.error(`Error writing ${filePath}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to translate content using OpenAI (for German to English)
|
||||
async function translateContent(content, systemPrompt, targetLanguage = null, model = 'gpt-4') {
|
||||
try {
|
||||
const prompt = targetLanguage
|
||||
? systemPrompt.replace(/{{targetLanguage}}/g, targetLanguage)
|
||||
: systemPrompt;
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: model,
|
||||
messages: [
|
||||
{ role: 'system', content: prompt },
|
||||
{ role: 'user', content: `Please translate this translation file content:\n\n${content}` }
|
||||
],
|
||||
temperature: 0.1,
|
||||
max_tokens: 4000
|
||||
});
|
||||
|
||||
return response.choices[0].message.content;
|
||||
} catch (error) {
|
||||
console.error('OpenAI API error:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to translate English to other languages (optimized for caching)
|
||||
async function translateToTargetLanguage(englishContent, targetLanguage, model = 'gpt-4o-mini') {
|
||||
try {
|
||||
// Create system prompt with file content (cacheable)
|
||||
const systemPrompt = ENGLISH_TO_OTHER_SYSTEM_PROMPT_TEMPLATE
|
||||
.replace(/{{targetLanguage}}/g, targetLanguage)
|
||||
.replace(/{{englishFileContent}}/g, englishContent);
|
||||
|
||||
const response = await openai.chat.completions.create({
|
||||
model: model,
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: `Please translate to ${targetLanguage}` }
|
||||
],
|
||||
temperature: 0.1,
|
||||
max_tokens: 4000
|
||||
});
|
||||
|
||||
return response.choices[0].message.content;
|
||||
} catch (error) {
|
||||
console.error('OpenAI API error:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to extract JavaScript object string from OpenAI response (preserving comments)
|
||||
function extractJSObjectString(response) {
|
||||
try {
|
||||
// Remove code block markers if present
|
||||
let cleaned = response.replace(/```javascript|```json|```/g, '').trim();
|
||||
|
||||
// Try to find the object in the response
|
||||
const objectMatch = cleaned.match(/\{[\s\S]*\}/);
|
||||
if (objectMatch) {
|
||||
return objectMatch[0];
|
||||
}
|
||||
|
||||
// If no object found, return the cleaned response
|
||||
return cleaned;
|
||||
} catch (error) {
|
||||
console.error('Error parsing OpenAI response:', error.message);
|
||||
console.log('Response was:', response);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Main translation function
|
||||
async function translateToEnglish() {
|
||||
console.log(`🔄 Step 1: Translating German to English using ${GERMAN_TO_ENGLISH_MODEL}...`);
|
||||
|
||||
// Read German translation file
|
||||
const germanContent = fs.readFileSync(GERMAN_FILE, 'utf8');
|
||||
|
||||
try {
|
||||
const translatedContent = await translateContent(germanContent, GERMAN_TO_ENGLISH_SYSTEM_PROMPT, null, GERMAN_TO_ENGLISH_MODEL);
|
||||
const englishObjectString = extractJSObjectString(translatedContent);
|
||||
|
||||
if (englishObjectString) {
|
||||
writeTranslationFile(ENGLISH_FILE, englishObjectString);
|
||||
console.log('✅ German to English translation completed');
|
||||
return englishObjectString;
|
||||
} else {
|
||||
throw new Error('Failed to parse English translation');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ Error translating to English:', error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to translate English to other languages
|
||||
async function translateToOtherLanguages(englishObjectString) {
|
||||
console.log(`🔄 Step 2: Translating English to other languages using ${ENGLISH_TO_OTHER_MODEL}...`);
|
||||
|
||||
const englishContent = `export default ${englishObjectString};`;
|
||||
|
||||
for (const [langCode, langName] of Object.entries(TARGET_LANGUAGES)) {
|
||||
try {
|
||||
console.log(`🔄 Translating to ${langName} (${langCode})...`);
|
||||
|
||||
const translatedContent = await translateToTargetLanguage(
|
||||
englishContent,
|
||||
langName,
|
||||
ENGLISH_TO_OTHER_MODEL
|
||||
);
|
||||
|
||||
const translatedObjectString = extractJSObjectString(translatedContent);
|
||||
|
||||
if (translatedObjectString) {
|
||||
// Update locale code in the string
|
||||
const updatedString = translatedObjectString.replace(
|
||||
/"code":\s*"[^"]*"/,
|
||||
`"code": "${getLocaleCode(langCode)}"`
|
||||
);
|
||||
|
||||
const targetFile = path.join(LOCALES_DIR, `${langCode}/translation.js`);
|
||||
writeTranslationFile(targetFile, updatedString);
|
||||
console.log(`✅ ${langName} translation completed`);
|
||||
} else {
|
||||
console.error(`❌ Failed to parse ${langName} translation`);
|
||||
}
|
||||
|
||||
// Add delay to avoid rate limiting
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Error translating to ${langName}:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to get locale codes
|
||||
function getLocaleCode(langCode) {
|
||||
const localeCodes = {
|
||||
'bg': 'bg-BG',
|
||||
'cs': 'cs-CZ',
|
||||
'es': 'es-ES',
|
||||
'fr': 'fr-FR',
|
||||
'el': 'el-GR',
|
||||
'hr': 'hr-HR',
|
||||
'hu': 'hu-HU',
|
||||
'it': 'it-IT',
|
||||
'pl': 'pl-PL',
|
||||
'ro': 'ro-RO',
|
||||
'ru': 'ru-RU',
|
||||
'sk': 'sk-SK',
|
||||
'sl': 'sl-SI',
|
||||
'sr': 'sr-RS',
|
||||
'sv': 'sv-SE',
|
||||
'tr': 'tr-TR',
|
||||
'uk': 'uk-UA',
|
||||
'ar': 'ar-EG',
|
||||
'zh': 'zh-CN'
|
||||
};
|
||||
return localeCodes[langCode] || `${langCode}-${langCode.toUpperCase()}`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Main execution
|
||||
async function main() {
|
||||
// Parse command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
const skipEnglish = args.includes('--skip-english') || args.includes('-s');
|
||||
const onlyEnglish = args.includes('--only-english') || args.includes('-e');
|
||||
|
||||
if (skipEnglish && onlyEnglish) {
|
||||
console.error('❌ Cannot use both --skip-english and --only-english flags');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('🚀 Starting translation process...');
|
||||
|
||||
if (skipEnglish) {
|
||||
console.log('⏭️ Skipping German → English translation (using existing English file)');
|
||||
} else if (onlyEnglish) {
|
||||
console.log('🎯 Only translating German → English (skipping other languages)');
|
||||
}
|
||||
|
||||
// Check if OpenAI API key is set (only if we're doing actual translation)
|
||||
if (!skipEnglish && !OPENAI_API_KEY) {
|
||||
console.error('❌ OPENAI_API_KEY environment variable is not set');
|
||||
console.log('Please set your OpenAI API key: export OPENAI_API_KEY="your-api-key-here"');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if German file exists (only if we're translating from German)
|
||||
if (!skipEnglish && !fs.existsSync(GERMAN_FILE)) {
|
||||
console.error(`❌ German translation file not found: ${GERMAN_FILE}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
let englishObjectString;
|
||||
|
||||
if (skipEnglish) {
|
||||
// Skip German → English, read existing English file
|
||||
if (!fs.existsSync(ENGLISH_FILE)) {
|
||||
console.error(`❌ English translation file not found: ${ENGLISH_FILE}`);
|
||||
console.log('💡 Run without --skip-english first to generate the English file');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('📖 Reading existing English translation file...');
|
||||
const englishContent = fs.readFileSync(ENGLISH_FILE, 'utf8');
|
||||
// Extract the object part (remove export default and semicolon)
|
||||
englishObjectString = englishContent.replace(/^export default\s*/, '').replace(/;\s*$/, '');
|
||||
console.log('✅ English file loaded successfully');
|
||||
} else {
|
||||
// Step 1: Translate German to English
|
||||
englishObjectString = await translateToEnglish();
|
||||
|
||||
if (!englishObjectString) {
|
||||
console.error('❌ Failed to create English translation, stopping process');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (onlyEnglish) {
|
||||
console.log('🎉 English translation completed! Skipping other languages.');
|
||||
} else {
|
||||
// Step 2: Translate English to other languages
|
||||
await translateToOtherLanguages(englishObjectString);
|
||||
console.log('🎉 All translations completed successfully!');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Translation process failed:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main();
|
||||
}
|
||||
|
||||
export {
|
||||
translateToEnglish,
|
||||
translateToOtherLanguages,
|
||||
readTranslationFile,
|
||||
writeTranslationFile
|
||||
};
|
||||
@@ -83,6 +83,21 @@ const CopyAssetsPlugin = {
|
||||
} catch (err) {
|
||||
console.error('Error copying favicon:', err);
|
||||
}
|
||||
|
||||
// Copy index.html to payment/success file for callback
|
||||
const indexSrc = path.resolve(__dirname, 'dist/index.html');
|
||||
const paymentDir = path.resolve(__dirname, 'dist/payment');
|
||||
const paymentSuccessDest = path.resolve(__dirname, 'dist/payment/success');
|
||||
try {
|
||||
// Create payment directory if it doesn't exist
|
||||
if (!fs.existsSync(paymentDir)) {
|
||||
fs.mkdirSync(paymentDir, { recursive: true });
|
||||
}
|
||||
cpSync(indexSrc, paymentSuccessDest);
|
||||
console.log('Index.html copied to payment/success file successfully');
|
||||
} catch (err) {
|
||||
console.error('Error copying index.html to payment/success file:', err);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -309,6 +324,219 @@ export default {
|
||||
next();
|
||||
});
|
||||
|
||||
// Add middleware to handle /404 route BEFORE webpack-dev-server processing
|
||||
middlewares.unshift({
|
||||
name: 'handle-404-route',
|
||||
middleware: async (req, res, next) => {
|
||||
if (req.url === '/404') {
|
||||
// Set up prerender environment
|
||||
const { createRequire } = await import('module');
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
require('@babel/register')({
|
||||
presets: [
|
||||
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||
'@babel/preset-react'
|
||||
],
|
||||
extensions: ['.js', '.jsx'],
|
||||
ignore: [/node_modules/]
|
||||
});
|
||||
|
||||
// Import React first and make it globally available
|
||||
const React = require('react');
|
||||
global.React = React; // Make React available globally for components that don't import it
|
||||
|
||||
// Set up minimal globals for prerender
|
||||
if (!global.window) {
|
||||
global.window = {};
|
||||
}
|
||||
if (!global.navigator) {
|
||||
global.navigator = { userAgent: 'node.js' };
|
||||
}
|
||||
if (!global.URL) {
|
||||
global.URL = require('url').URL;
|
||||
}
|
||||
if (!global.Blob) {
|
||||
global.Blob = class MockBlob {
|
||||
constructor(data, options) {
|
||||
this.data = data;
|
||||
this.type = options?.type || '';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Mock browser storage APIs
|
||||
const mockStorage = {
|
||||
getItem: () => null,
|
||||
setItem: () => {},
|
||||
removeItem: () => {},
|
||||
clear: () => {},
|
||||
key: () => null,
|
||||
length: 0
|
||||
};
|
||||
|
||||
if (!global.localStorage) {
|
||||
global.localStorage = mockStorage;
|
||||
}
|
||||
if (!global.sessionStorage) {
|
||||
global.sessionStorage = mockStorage;
|
||||
}
|
||||
|
||||
// Also add to window object for components that access it via window
|
||||
global.window.localStorage = mockStorage;
|
||||
global.window.sessionStorage = mockStorage;
|
||||
|
||||
// Import the dedicated prerender component
|
||||
const PrerenderNotFound = require('./src/PrerenderNotFound.js').default;
|
||||
|
||||
// Create the prerender component
|
||||
const component = React.createElement(PrerenderNotFound);
|
||||
|
||||
// Get only the essential bundles (not lazy-loaded chunks)
|
||||
let jsBundles = [];
|
||||
try {
|
||||
const outputFileSystem = devServer.compiler.outputFileSystem;
|
||||
const outputPath = devServer.compiler.outputPath;
|
||||
const jsPath = path.join(outputPath, 'js');
|
||||
|
||||
if (outputFileSystem.existsSync && outputFileSystem.existsSync(jsPath)) {
|
||||
const jsFiles = outputFileSystem.readdirSync(jsPath);
|
||||
|
||||
// Only include essential bundles in correct dependency order
|
||||
const essentialBundles = [];
|
||||
|
||||
// 1. Runtime bundle (webpack runtime - must be first)
|
||||
const runtimeFile = jsFiles.find(f => f.startsWith('runtime.') && f.endsWith('.bundle.js'));
|
||||
if (runtimeFile) essentialBundles.push('/js/' + runtimeFile);
|
||||
|
||||
// 2. Vendor bundles (libraries that main depends on)
|
||||
const reactFile = jsFiles.find(f => f.startsWith('react.') && f.endsWith('.bundle.js'));
|
||||
if (reactFile) essentialBundles.push('/js/' + reactFile);
|
||||
|
||||
const emotionFile = jsFiles.find(f => f.startsWith('emotion.') && f.endsWith('.bundle.js'));
|
||||
if (emotionFile) essentialBundles.push('/js/' + emotionFile);
|
||||
|
||||
const muiIconsCommonFile = jsFiles.find(f => f.startsWith('mui-icons-common.') && f.endsWith('.bundle.js'));
|
||||
if (muiIconsCommonFile) essentialBundles.push('/js/' + muiIconsCommonFile);
|
||||
|
||||
const muiCoreFile = jsFiles.find(f => f.startsWith('mui-core.') && f.endsWith('.bundle.js'));
|
||||
if (muiCoreFile) essentialBundles.push('/js/' + muiCoreFile);
|
||||
|
||||
const vendorFile = jsFiles.find(f => f.startsWith('vendor.') && f.endsWith('.bundle.js'));
|
||||
if (vendorFile) essentialBundles.push('/js/' + vendorFile);
|
||||
|
||||
// 3. Common shared code
|
||||
const commonFile = jsFiles.find(f => f.startsWith('common.') && f.endsWith('.chunk.js'));
|
||||
if (commonFile) essentialBundles.push('/js/' + commonFile);
|
||||
|
||||
// 4. Main bundle (your app code - must be last)
|
||||
const mainFile = jsFiles.find(f => f.startsWith('main.') && f.endsWith('.bundle.js'));
|
||||
if (mainFile) essentialBundles.push('/js/' + mainFile);
|
||||
|
||||
jsBundles = essentialBundles;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Could not read webpack output filesystem:', error.message);
|
||||
}
|
||||
|
||||
// Fallback if we can't read the filesystem
|
||||
if (jsBundles.length === 0) {
|
||||
jsBundles = ['/js/runtime.bundle.js', '/js/main.bundle.js'];
|
||||
}
|
||||
|
||||
// Render the page in memory only (no file writing in dev mode)
|
||||
const ReactDOMServer = require('react-dom/server');
|
||||
const { StaticRouter } = require('react-router');
|
||||
const { CacheProvider } = require('@emotion/react');
|
||||
const { ThemeProvider } = require('@mui/material/styles');
|
||||
const createEmotionCache = require('./createEmotionCache.js').default;
|
||||
const theme = require('./src/theme.js').default;
|
||||
const createEmotionServer = require('@emotion/server/create-instance').default;
|
||||
|
||||
// Create fresh Emotion cache for this page
|
||||
const cache = createEmotionCache();
|
||||
const { extractCriticalToChunks } = createEmotionServer(cache);
|
||||
|
||||
// Wrap with StaticRouter to provide React Router context for Logo's Link components
|
||||
const routedComponent = React.createElement(
|
||||
StaticRouter,
|
||||
{ location: '/404' },
|
||||
component
|
||||
);
|
||||
|
||||
const pageElement = React.createElement(
|
||||
CacheProvider,
|
||||
{ value: cache },
|
||||
React.createElement(ThemeProvider, { theme: theme }, routedComponent)
|
||||
);
|
||||
|
||||
// Render to string
|
||||
const renderedMarkup = ReactDOMServer.renderToString(pageElement);
|
||||
const emotionChunks = extractCriticalToChunks(renderedMarkup);
|
||||
|
||||
// Build the full HTML page
|
||||
const templatePath = path.resolve(__dirname, 'public', 'index.html');
|
||||
let template = fs.readFileSync(templatePath, 'utf8');
|
||||
|
||||
// Add JavaScript bundles
|
||||
let scriptTags = '';
|
||||
jsBundles.forEach(jsFile => {
|
||||
scriptTags += `<script src="${jsFile}"></script>`;
|
||||
});
|
||||
|
||||
// Add global CSS from src/index.css
|
||||
let globalCss = '';
|
||||
try {
|
||||
globalCss = fs.readFileSync(path.resolve(__dirname, 'src', 'index.css'), 'utf8');
|
||||
// Fix relative font paths for prerendered HTML (remove ../public to make them relative to public root)
|
||||
globalCss = globalCss.replace(/url\('\.\.\/public/g, "url('");
|
||||
} catch (error) {
|
||||
console.warn('Could not read src/index.css:', error.message);
|
||||
}
|
||||
|
||||
// Add inline CSS from emotion
|
||||
let emotionCss = '';
|
||||
if (emotionChunks.styles.length > 0) {
|
||||
emotionChunks.styles.forEach(style => {
|
||||
if (style.css) {
|
||||
emotionCss += style.css;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Combine all CSS
|
||||
const inlineCss = globalCss + emotionCss;
|
||||
|
||||
// Use the rendered markup as-is (no regex replacements)
|
||||
let processedMarkup = renderedMarkup;
|
||||
|
||||
// Replace placeholders in template
|
||||
const finalHtml = template
|
||||
.replace('<div id="root"></div>', `<div id="root">${processedMarkup}</div>`)
|
||||
.replace('</head>', `<style>${inlineCss}</style></head>`)
|
||||
.replace('</body>', `
|
||||
<script>
|
||||
window.__PRERENDER_FALLBACK__ = {path: '/404', content: ${JSON.stringify(processedMarkup)}, timestamp: ${Date.now()}};
|
||||
</script>
|
||||
${scriptTags}
|
||||
</body>`);
|
||||
|
||||
// Serve the prerendered HTML with 404 status
|
||||
res.status(404);
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||
res.setHeader('Pragma', 'no-cache');
|
||||
res.setHeader('Expires', '0');
|
||||
return res.send(finalHtml);
|
||||
|
||||
// If we get here, prerender failed - let the error bubble up
|
||||
throw new Error('404 prerender failed completely');
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return middlewares;
|
||||
},
|
||||
hot: true,
|
||||
@@ -317,7 +545,18 @@ export default {
|
||||
historyApiFallback: {
|
||||
index: '/index.html',
|
||||
disableDotRule: true,
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml']
|
||||
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
|
||||
rewrites: [
|
||||
// Exclude prerendered routes from SPA fallback
|
||||
{ from: /^\/Kategorie\//, to: function(context) {
|
||||
return context.parsedUrl.pathname;
|
||||
}},
|
||||
{ from: /^\/Artikel\//, to: function(context) {
|
||||
return context.parsedUrl.pathname;
|
||||
}},
|
||||
// All other routes should fallback to React SPA
|
||||
{ from: /^\/(?!api|socket\.io|assets|js|css|favicon\.ico).*$/, to: '/index.html' }
|
||||
]
|
||||
},
|
||||
client: {
|
||||
logging: 'verbose',
|
||||
|
||||
Reference in New Issue
Block a user