refactor: replace hardcoded categories with Directus-powered category tree and translations
This commit is contained in:
@@ -40,10 +40,11 @@ git push origin master
|
|||||||
|
|
||||||
## Commit-Konvention
|
## Commit-Konvention
|
||||||
|
|
||||||
Wenn der User nach **"commit text (en)"** oder **"commit text (de)"** fragt:
|
Wenn der User nach **"commit text"** fragt:
|
||||||
- Erstelle eine einzeilige Commit-Message im Format: `git commit -m "..."`
|
- **Immer nur eine einzeilige** Commit-Message im Format: `git commit -m "..."`
|
||||||
|
- Keine mehrzeiligen Messages oder Body-Text
|
||||||
- Nutze [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
- Nutze [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||||
- Sprache entsprechend der Anfrage (en/de)
|
- Standardsprache: Englisch (außer User fragt explizit nach "de")
|
||||||
|
|
||||||
## Dateistruktur
|
## Dateistruktur
|
||||||
|
|
||||||
|
|||||||
255
docs/import-categories.sh
Normal file
255
docs/import-categories.sh
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Bulk-Import: All categories for dgray.io
|
||||||
|
# Usage: DIRECTUS_TOKEN=your_admin_token bash docs/import-categories.sh
|
||||||
|
|
||||||
|
API="https://api.dgray.io"
|
||||||
|
TOKEN="${DIRECTUS_TOKEN:?Set DIRECTUS_TOKEN environment variable}"
|
||||||
|
|
||||||
|
create_category() {
|
||||||
|
local slug="$1" name="$2" icon="$3" sort="$4" parent="$5"
|
||||||
|
local name_de="$6" name_en="$7" name_fr="$8"
|
||||||
|
|
||||||
|
local parent_field="null"
|
||||||
|
if [ -n "$parent" ]; then
|
||||||
|
parent_field="\"$parent\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
local response
|
||||||
|
response=$(curl -s -X POST "$API/items/categories" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"name\": \"$name\",
|
||||||
|
\"slug\": \"$slug\",
|
||||||
|
\"icon\": \"$icon\",
|
||||||
|
\"status\": \"published\",
|
||||||
|
\"sort\": $sort,
|
||||||
|
\"parent\": $parent_field,
|
||||||
|
\"translations\": {
|
||||||
|
\"create\": [
|
||||||
|
{ \"languages_code\": \"de-DE\", \"name\": \"$name_de\" },
|
||||||
|
{ \"languages_code\": \"en-US\", \"name\": \"$name_en\" },
|
||||||
|
{ \"languages_code\": \"fr-FR\", \"name\": \"$name_fr\" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}")
|
||||||
|
|
||||||
|
local id
|
||||||
|
id=$(echo "$response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||||
|
|
||||||
|
if [ -n "$id" ]; then
|
||||||
|
echo "$id"
|
||||||
|
else
|
||||||
|
echo "ERROR: $response" >&2
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "=== Importing categories into Directus ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── 1. Electronics ──
|
||||||
|
echo "1/12 Electronics..."
|
||||||
|
ID_ELECTRONICS=$(create_category "electronics" "Electronics" "💻" 1 "" \
|
||||||
|
"Elektronik" "Electronics" "Électronique")
|
||||||
|
echo " → $ID_ELECTRONICS"
|
||||||
|
|
||||||
|
create_category "phones" "Phones & Tablets" "📱" 1 "$ID_ELECTRONICS" \
|
||||||
|
"Handy & Tablets" "Phones & Tablets" "Téléphones & Tablettes" > /dev/null
|
||||||
|
create_category "computers" "Computers & Accessories" "🖥️" 2 "$ID_ELECTRONICS" \
|
||||||
|
"Computer & Zubehör" "Computers & Accessories" "Ordinateurs & Accessoires" > /dev/null
|
||||||
|
create_category "tv-audio" "TV, Audio & Video" "📺" 3 "$ID_ELECTRONICS" \
|
||||||
|
"TV, Audio & Video" "TV, Audio & Video" "TV, Audio & Vidéo" > /dev/null
|
||||||
|
create_category "gaming" "Gaming & Consoles" "🎮" 4 "$ID_ELECTRONICS" \
|
||||||
|
"Gaming & Konsolen" "Gaming & Consoles" "Jeux & Consoles" > /dev/null
|
||||||
|
create_category "appliances" "Home Appliances" "🏠" 5 "$ID_ELECTRONICS" \
|
||||||
|
"Haushaltsgeräte" "Home Appliances" "Électroménager" > /dev/null
|
||||||
|
create_category "cameras" "Cameras & Photography" "📷" 6 "$ID_ELECTRONICS" \
|
||||||
|
"Kameras & Fotografie" "Cameras & Photography" "Appareils photo" > /dev/null
|
||||||
|
echo " ✓ 6 subcategories"
|
||||||
|
|
||||||
|
# ── 2. Vehicles ──
|
||||||
|
echo "2/12 Vehicles..."
|
||||||
|
ID_VEHICLES=$(create_category "vehicles" "Vehicles" "🚗" 2 "" \
|
||||||
|
"Fahrzeuge" "Vehicles" "Véhicules")
|
||||||
|
echo " → $ID_VEHICLES"
|
||||||
|
|
||||||
|
create_category "cars" "Cars" "🚙" 1 "$ID_VEHICLES" \
|
||||||
|
"Autos" "Cars" "Voitures" > /dev/null
|
||||||
|
create_category "motorcycles" "Motorcycles" "🏍️" 2 "$ID_VEHICLES" \
|
||||||
|
"Motorräder" "Motorcycles" "Motos" > /dev/null
|
||||||
|
create_category "bikes" "Bicycles & E-Bikes" "🚲" 3 "$ID_VEHICLES" \
|
||||||
|
"Fahrräder & E-Bikes" "Bicycles & E-Bikes" "Vélos & E-Bikes" > /dev/null
|
||||||
|
create_category "vehicle-parts" "Parts & Accessories" "🔧" 4 "$ID_VEHICLES" \
|
||||||
|
"Ersatzteile & Zubehör" "Parts & Accessories" "Pièces & Accessoires" > /dev/null
|
||||||
|
create_category "boats" "Boats & Watercraft" "⛵" 5 "$ID_VEHICLES" \
|
||||||
|
"Boote & Wasserfahrzeuge" "Boats & Watercraft" "Bateaux" > /dev/null
|
||||||
|
echo " ✓ 5 subcategories"
|
||||||
|
|
||||||
|
# ── 3. Home & Garden ──
|
||||||
|
echo "3/12 Home & Garden..."
|
||||||
|
ID_HOME=$(create_category "home-garden" "Home & Garden" "🏡" 3 "" \
|
||||||
|
"Haus & Garten" "Home & Garden" "Maison & Jardin")
|
||||||
|
echo " → $ID_HOME"
|
||||||
|
|
||||||
|
create_category "furniture" "Furniture" "🪑" 1 "$ID_HOME" \
|
||||||
|
"Möbel" "Furniture" "Meubles" > /dev/null
|
||||||
|
create_category "kitchen" "Kitchen & Dining" "🍳" 2 "$ID_HOME" \
|
||||||
|
"Küche & Essen" "Kitchen & Dining" "Cuisine" > /dev/null
|
||||||
|
create_category "garden" "Garden & Outdoor" "🌿" 3 "$ID_HOME" \
|
||||||
|
"Garten & Outdoor" "Garden & Outdoor" "Jardin & Extérieur" > /dev/null
|
||||||
|
create_category "tools" "Tools & Workshop" "🔨" 4 "$ID_HOME" \
|
||||||
|
"Werkzeuge & Werkstatt" "Tools & Workshop" "Outils & Atelier" > /dev/null
|
||||||
|
create_category "decoration" "Decoration & Art" "🖼️" 5 "$ID_HOME" \
|
||||||
|
"Deko & Kunst" "Decoration & Art" "Décoration & Art" > /dev/null
|
||||||
|
create_category "bathroom" "Bathroom & Sanitary" "🚿" 6 "$ID_HOME" \
|
||||||
|
"Bad & Sanitär" "Bathroom & Sanitary" "Salle de bain" > /dev/null
|
||||||
|
echo " ✓ 6 subcategories"
|
||||||
|
|
||||||
|
# ── 4. Fashion & Accessories ──
|
||||||
|
echo "4/12 Fashion..."
|
||||||
|
ID_FASHION=$(create_category "fashion" "Fashion & Accessories" "👗" 4 "" \
|
||||||
|
"Mode & Accessoires" "Fashion & Accessories" "Mode & Accessoires")
|
||||||
|
echo " → $ID_FASHION"
|
||||||
|
|
||||||
|
create_category "women" "Women's Fashion" "👚" 1 "$ID_FASHION" \
|
||||||
|
"Damenmode" "Women's Fashion" "Mode femme" > /dev/null
|
||||||
|
create_category "men" "Men's Fashion" "👔" 2 "$ID_FASHION" \
|
||||||
|
"Herrenmode" "Men's Fashion" "Mode homme" > /dev/null
|
||||||
|
create_category "kids-fashion" "Kids' Fashion" "👶" 3 "$ID_FASHION" \
|
||||||
|
"Kindermode" "Kids' Fashion" "Mode enfant" > /dev/null
|
||||||
|
create_category "shoes" "Shoes" "👟" 4 "$ID_FASHION" \
|
||||||
|
"Schuhe" "Shoes" "Chaussures" > /dev/null
|
||||||
|
create_category "watches-jewelry" "Watches & Jewelry" "⌚" 5 "$ID_FASHION" \
|
||||||
|
"Uhren & Schmuck" "Watches & Jewelry" "Montres & Bijoux" > /dev/null
|
||||||
|
create_category "bags" "Bags & Luggage" "👜" 6 "$ID_FASHION" \
|
||||||
|
"Taschen & Gepäck" "Bags & Luggage" "Sacs & Bagages" > /dev/null
|
||||||
|
echo " ✓ 6 subcategories"
|
||||||
|
|
||||||
|
# ── 5. Sports & Leisure ──
|
||||||
|
echo "5/12 Sports..."
|
||||||
|
ID_SPORTS=$(create_category "sports" "Sports & Leisure" "⚽" 5 "" \
|
||||||
|
"Sport & Freizeit" "Sports & Leisure" "Sports & Loisirs")
|
||||||
|
echo " → $ID_SPORTS"
|
||||||
|
|
||||||
|
create_category "fitness" "Fitness & Gym" "🏋️" 1 "$ID_SPORTS" \
|
||||||
|
"Fitness & Training" "Fitness & Gym" "Fitness" > /dev/null
|
||||||
|
create_category "outdoor-sports" "Hiking & Outdoor" "🥾" 2 "$ID_SPORTS" \
|
||||||
|
"Wandern & Outdoor" "Hiking & Outdoor" "Randonnée & Outdoor" > /dev/null
|
||||||
|
create_category "winter-sports" "Winter Sports" "⛷️" 3 "$ID_SPORTS" \
|
||||||
|
"Wintersport" "Winter Sports" "Sports d'hiver" > /dev/null
|
||||||
|
create_category "water-sports" "Water Sports" "🏄" 4 "$ID_SPORTS" \
|
||||||
|
"Wassersport" "Water Sports" "Sports nautiques" > /dev/null
|
||||||
|
create_category "team-sports" "Team Sports" "🏀" 5 "$ID_SPORTS" \
|
||||||
|
"Mannschaftssport" "Team Sports" "Sports d'équipe" > /dev/null
|
||||||
|
create_category "cycling" "Cycling Gear" "🚴" 6 "$ID_SPORTS" \
|
||||||
|
"Radzubehör" "Cycling Gear" "Équipement cyclisme" > /dev/null
|
||||||
|
echo " ✓ 6 subcategories"
|
||||||
|
|
||||||
|
# ── 6. Family & Kids ──
|
||||||
|
echo "6/12 Family & Kids..."
|
||||||
|
ID_FAMILY=$(create_category "family-kids" "Family & Kids" "👨👩👧" 6 "" \
|
||||||
|
"Familie & Kinder" "Family & Kids" "Famille & Enfants")
|
||||||
|
echo " → $ID_FAMILY"
|
||||||
|
|
||||||
|
create_category "toys" "Toys & Games" "🧸" 1 "$ID_FAMILY" \
|
||||||
|
"Spielzeug & Spiele" "Toys & Games" "Jouets & Jeux" > /dev/null
|
||||||
|
create_category "baby" "Baby & Toddler" "🍼" 2 "$ID_FAMILY" \
|
||||||
|
"Baby & Kleinkind" "Baby & Toddler" "Bébé & Puériculture" > /dev/null
|
||||||
|
create_category "school" "School Supplies" "🎒" 3 "$ID_FAMILY" \
|
||||||
|
"Schulsachen" "School Supplies" "Fournitures scolaires" > /dev/null
|
||||||
|
echo " ✓ 3 subcategories"
|
||||||
|
|
||||||
|
# ── 7. Books & Media ──
|
||||||
|
echo "7/12 Books & Media..."
|
||||||
|
ID_BOOKS=$(create_category "books-media" "Books & Media" "📚" 7 "" \
|
||||||
|
"Bücher & Medien" "Books & Media" "Livres & Médias")
|
||||||
|
echo " → $ID_BOOKS"
|
||||||
|
|
||||||
|
create_category "fiction" "Fiction" "📖" 1 "$ID_BOOKS" \
|
||||||
|
"Belletristik" "Fiction" "Fiction" > /dev/null
|
||||||
|
create_category "nonfiction" "Non-Fiction & Science" "📘" 2 "$ID_BOOKS" \
|
||||||
|
"Sachbücher" "Non-Fiction & Science" "Non-fiction" > /dev/null
|
||||||
|
create_category "textbooks" "Textbooks & Courses" "🎓" 3 "$ID_BOOKS" \
|
||||||
|
"Lehrbücher & Kurse" "Textbooks & Courses" "Manuels scolaires" > /dev/null
|
||||||
|
create_category "music-movies" "Music, Movies & Games" "🎵" 4 "$ID_BOOKS" \
|
||||||
|
"Musik, Filme & Spiele" "Music, Movies & Games" "Musique, Films & Jeux" > /dev/null
|
||||||
|
echo " ✓ 4 subcategories"
|
||||||
|
|
||||||
|
# ── 8. Pets & Animals ──
|
||||||
|
echo "8/12 Pets..."
|
||||||
|
ID_PETS=$(create_category "pets" "Pets & Animals" "🐾" 8 "" \
|
||||||
|
"Haustiere & Tierzubehör" "Pets & Animals" "Animaux")
|
||||||
|
echo " → $ID_PETS"
|
||||||
|
|
||||||
|
create_category "pet-supplies" "Pet Supplies" "🦴" 1 "$ID_PETS" \
|
||||||
|
"Tierbedarf" "Pet Supplies" "Accessoires animaux" > /dev/null
|
||||||
|
create_category "pet-adoption" "Adoption & Rehoming" "🐕" 2 "$ID_PETS" \
|
||||||
|
"Adoption & Vermittlung" "Adoption & Rehoming" "Adoption" > /dev/null
|
||||||
|
echo " ✓ 2 subcategories"
|
||||||
|
|
||||||
|
# ── 9. Jobs & Services ──
|
||||||
|
echo "9/12 Jobs..."
|
||||||
|
ID_JOBS=$(create_category "jobs" "Jobs & Services" "💼" 9 "" \
|
||||||
|
"Jobs & Dienstleistungen" "Jobs & Services" "Emplois & Services")
|
||||||
|
echo " → $ID_JOBS"
|
||||||
|
|
||||||
|
create_category "full-time" "Full-Time" "🏢" 1 "$ID_JOBS" \
|
||||||
|
"Festanstellung" "Full-Time" "CDI" > /dev/null
|
||||||
|
create_category "part-time" "Part-Time & Mini Jobs" "⏰" 2 "$ID_JOBS" \
|
||||||
|
"Teilzeit & Minijobs" "Part-Time & Mini Jobs" "Temps partiel" > /dev/null
|
||||||
|
create_category "freelance" "Freelance & Remote" "💻" 3 "$ID_JOBS" \
|
||||||
|
"Freelance & Remote" "Freelance & Remote" "Freelance & Télétravail" > /dev/null
|
||||||
|
create_category "services" "Services & Crafts" "🛠️" 4 "$ID_JOBS" \
|
||||||
|
"Dienstleistungen & Handwerk" "Services & Crafts" "Services & Artisanat" > /dev/null
|
||||||
|
echo " ✓ 4 subcategories"
|
||||||
|
|
||||||
|
# ── 10. Real Estate ──
|
||||||
|
echo "10/12 Real Estate..."
|
||||||
|
ID_REALESTATE=$(create_category "real-estate" "Real Estate" "🏘️" 10 "" \
|
||||||
|
"Immobilien" "Real Estate" "Immobilier")
|
||||||
|
echo " → $ID_REALESTATE"
|
||||||
|
|
||||||
|
create_category "apartments" "Apartments" "🏢" 1 "$ID_REALESTATE" \
|
||||||
|
"Wohnungen" "Apartments" "Appartements" > /dev/null
|
||||||
|
create_category "houses" "Houses" "🏠" 2 "$ID_REALESTATE" \
|
||||||
|
"Häuser" "Houses" "Maisons" > /dev/null
|
||||||
|
create_category "rooms" "Rooms & WG" "🛏️" 3 "$ID_REALESTATE" \
|
||||||
|
"Zimmer & WG" "Rooms & WG" "Chambres & Colocation" > /dev/null
|
||||||
|
create_category "commercial" "Commercial" "🏪" 4 "$ID_REALESTATE" \
|
||||||
|
"Gewerbe" "Commercial" "Commercial" > /dev/null
|
||||||
|
echo " ✓ 4 subcategories"
|
||||||
|
|
||||||
|
# ── 11. Collectibles & Hobbies ──
|
||||||
|
echo "11/12 Collectibles..."
|
||||||
|
ID_COLLECT=$(create_category "collectibles" "Collectibles & Hobbies" "🏆" 11 "" \
|
||||||
|
"Sammeln & Hobby" "Collectibles & Hobbies" "Collections & Hobbies")
|
||||||
|
echo " → $ID_COLLECT"
|
||||||
|
|
||||||
|
create_category "antiques" "Antiques" "🏺" 1 "$ID_COLLECT" \
|
||||||
|
"Antiquitäten" "Antiques" "Antiquités" > /dev/null
|
||||||
|
create_category "coins-stamps" "Coins & Stamps" "🪙" 2 "$ID_COLLECT" \
|
||||||
|
"Münzen & Briefmarken" "Coins & Stamps" "Monnaies & Timbres" > /dev/null
|
||||||
|
create_category "models" "Models & Figures" "🚂" 3 "$ID_COLLECT" \
|
||||||
|
"Modellbau & Figuren" "Models & Figures" "Modélisme & Figurines" > /dev/null
|
||||||
|
create_category "art-crafts" "Art & Handmade" "🎨" 4 "$ID_COLLECT" \
|
||||||
|
"Kunst & Handgemacht" "Art & Handmade" "Art & Fait main" > /dev/null
|
||||||
|
echo " ✓ 4 subcategories"
|
||||||
|
|
||||||
|
# ── 12. Other ──
|
||||||
|
echo "12/12 Other..."
|
||||||
|
ID_OTHER=$(create_category "other" "Other" "📦" 12 "" \
|
||||||
|
"Sonstiges" "Other" "Autres")
|
||||||
|
echo " → $ID_OTHER"
|
||||||
|
|
||||||
|
create_category "free-stuff" "Free Stuff" "🎁" 1 "$ID_OTHER" \
|
||||||
|
"Zu verschenken" "Free Stuff" "Gratuit" > /dev/null
|
||||||
|
create_category "barter" "Barter & Trade" "🔄" 2 "$ID_OTHER" \
|
||||||
|
"Tauschen" "Barter & Trade" "Troc" > /dev/null
|
||||||
|
create_category "lost-found" "Lost & Found" "🔍" 3 "$ID_OTHER" \
|
||||||
|
"Fundsachen" "Lost & Found" "Objets trouvés" > /dev/null
|
||||||
|
echo " ✓ 3 subcategories"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Import complete! ==="
|
||||||
|
echo "12 main categories + 59 subcategories created."
|
||||||
@@ -21,7 +21,7 @@ class PageCreate extends HTMLElement {
|
|||||||
this.formData = this.loadDraft() || this.getEmptyFormData()
|
this.formData = this.loadDraft() || this.getEmptyFormData()
|
||||||
this.imageFiles = []
|
this.imageFiles = []
|
||||||
this.imagePreviews = []
|
this.imagePreviews = []
|
||||||
this.categories = []
|
this.categoryTree = []
|
||||||
this.submitting = false
|
this.submitting = false
|
||||||
this.isNewAccount = true
|
this.isNewAccount = true
|
||||||
}
|
}
|
||||||
@@ -183,10 +183,10 @@ class PageCreate extends HTMLElement {
|
|||||||
|
|
||||||
async loadCategories() {
|
async loadCategories() {
|
||||||
try {
|
try {
|
||||||
this.categories = await categoriesService.getAll()
|
this.categoryTree = await categoriesService.getTree()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to load categories:', e)
|
console.error('Failed to load categories:', e)
|
||||||
this.categories = []
|
this.categoryTree = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,10 +238,14 @@ class PageCreate extends HTMLElement {
|
|||||||
<label class="label" for="category">${t('create.category')}</label>
|
<label class="label" for="category">${t('create.category')}</label>
|
||||||
<select class="input" id="category" name="category" required>
|
<select class="input" id="category" name="category" required>
|
||||||
<option value="">${t('create.selectCategory')}</option>
|
<option value="">${t('create.selectCategory')}</option>
|
||||||
${this.categories.map(cat => `
|
${(this.categoryTree || []).map(cat => `
|
||||||
<option value="${cat.id}" ${this.formData.category === cat.id ? 'selected' : ''}>
|
<optgroup label="${categoriesService.getTranslatedName(cat)}">
|
||||||
${t(`categories.${cat.slug}`) || cat.name}
|
${(cat.children || []).map(sub => `
|
||||||
</option>
|
<option value="${sub.id}" ${this.formData.category === sub.id ? 'selected' : ''}>
|
||||||
|
${categoriesService.getTranslatedName(sub)}
|
||||||
|
</option>
|
||||||
|
`).join('')}
|
||||||
|
</optgroup>
|
||||||
`).join('')}
|
`).join('')}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class PageHome extends HTMLElement {
|
|||||||
// Filter state
|
// Filter state
|
||||||
this.query = ''
|
this.query = ''
|
||||||
this.category = ''
|
this.category = ''
|
||||||
|
this.subcategory = ''
|
||||||
this.sort = 'newest'
|
this.sort = 'newest'
|
||||||
this.minPrice = null
|
this.minPrice = null
|
||||||
this.maxPrice = null
|
this.maxPrice = null
|
||||||
@@ -39,8 +40,7 @@ class PageHome extends HTMLElement {
|
|||||||
this.setupPullToRefresh()
|
this.setupPullToRefresh()
|
||||||
this.loadListings()
|
this.loadListings()
|
||||||
this.unsubscribe = i18n.subscribe(() => {
|
this.unsubscribe = i18n.subscribe(() => {
|
||||||
this.render()
|
this.updateTextContent()
|
||||||
this.setupEventListeners()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Re-render listings on auth change to show owner badges
|
// Re-render listings on auth change to show owner badges
|
||||||
@@ -129,6 +129,7 @@ class PageHome extends HTMLElement {
|
|||||||
searchBox?.addEventListener('filter-change', (e) => {
|
searchBox?.addEventListener('filter-change', (e) => {
|
||||||
const hadLocation = this.hasUserLocation()
|
const hadLocation = this.hasUserLocation()
|
||||||
this.category = e.detail.category || ''
|
this.category = e.detail.category || ''
|
||||||
|
this.subcategory = e.detail.subcategory || ''
|
||||||
this.query = e.detail.query || ''
|
this.query = e.detail.query || ''
|
||||||
this.updateUserLocation(e.detail)
|
this.updateUserLocation(e.detail)
|
||||||
this.updateUrl()
|
this.updateUrl()
|
||||||
@@ -147,6 +148,7 @@ class PageHome extends HTMLElement {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
this.query = e.detail.query || ''
|
this.query = e.detail.query || ''
|
||||||
this.category = e.detail.category || ''
|
this.category = e.detail.category || ''
|
||||||
|
this.subcategory = e.detail.subcategory || ''
|
||||||
this.updateUserLocation(e.detail)
|
this.updateUserLocation(e.detail)
|
||||||
this.updateUrl()
|
this.updateUrl()
|
||||||
this.resetAndSearch()
|
this.resetAndSearch()
|
||||||
@@ -184,6 +186,7 @@ class PageHome extends HTMLElement {
|
|||||||
this.querySelector('#clear-filters')?.addEventListener('click', () => {
|
this.querySelector('#clear-filters')?.addEventListener('click', () => {
|
||||||
this.query = ''
|
this.query = ''
|
||||||
this.category = ''
|
this.category = ''
|
||||||
|
this.subcategory = ''
|
||||||
this.sort = 'newest'
|
this.sort = 'newest'
|
||||||
this.minPrice = null
|
this.minPrice = null
|
||||||
this.maxPrice = null
|
this.maxPrice = null
|
||||||
@@ -210,6 +213,7 @@ class PageHome extends HTMLElement {
|
|||||||
const filters = {
|
const filters = {
|
||||||
search: this.query || undefined,
|
search: this.query || undefined,
|
||||||
category: this.category || undefined,
|
category: this.category || undefined,
|
||||||
|
subcategory: this.subcategory || undefined,
|
||||||
sort: isDistanceSort ? 'newest' : this.sort, // Backend can't sort by distance
|
sort: isDistanceSort ? 'newest' : this.sort, // Backend can't sort by distance
|
||||||
minPrice: this.minPrice,
|
minPrice: this.minPrice,
|
||||||
maxPrice: this.maxPrice,
|
maxPrice: this.maxPrice,
|
||||||
@@ -348,6 +352,28 @@ class PageHome extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateTextContent() {
|
||||||
|
const titleEl = this.querySelector('.listings-title')
|
||||||
|
if (titleEl) titleEl.textContent = this.getListingsTitle()
|
||||||
|
|
||||||
|
const sortSelect = this.querySelector('#sort-select')
|
||||||
|
if (sortSelect) {
|
||||||
|
sortSelect.querySelectorAll('option').forEach(opt => {
|
||||||
|
const key = {
|
||||||
|
distance: 'search.sortDistance',
|
||||||
|
newest: 'search.sortNewest',
|
||||||
|
oldest: 'search.sortOldest',
|
||||||
|
price_asc: 'search.sortPriceAsc',
|
||||||
|
price_desc: 'search.sortPriceDesc'
|
||||||
|
}[opt.value]
|
||||||
|
if (key) opt.textContent = t(key)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchBox = this.querySelector('search-box')
|
||||||
|
if (searchBox) searchBox.loadAndRender()
|
||||||
|
}
|
||||||
|
|
||||||
getListingsTitle() {
|
getListingsTitle() {
|
||||||
if (this.hasActiveFilters()) {
|
if (this.hasActiveFilters()) {
|
||||||
const count = this.listings.length
|
const count = this.listings.length
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { escapeHTML } from '../../utils/helpers.js'
|
|||||||
import '../chat-widget.js'
|
import '../chat-widget.js'
|
||||||
import '../location-map.js'
|
import '../location-map.js'
|
||||||
import '../listing-card.js'
|
import '../listing-card.js'
|
||||||
|
import { categoriesService } from '../../services/categories.js'
|
||||||
|
|
||||||
class PageListing extends HTMLElement {
|
class PageListing extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -150,7 +151,9 @@ class PageListing extends HTMLElement {
|
|||||||
const firstImage = hasImages ? this.getImageUrl(images[0]) : null
|
const firstImage = hasImages ? this.getImageUrl(images[0]) : null
|
||||||
this.allImages = images
|
this.allImages = images
|
||||||
|
|
||||||
const categoryName = this.listing.category?.name || ''
|
const categoryName = this.listing.category
|
||||||
|
? categoriesService.getTranslatedName(this.listing.category)
|
||||||
|
: ''
|
||||||
const priceInfo = this.getFormattedPrice()
|
const priceInfo = this.getFormattedPrice()
|
||||||
const createdDate = this.listing.date_created
|
const createdDate = this.listing.date_created
|
||||||
? new Date(this.listing.date_created).toLocaleDateString()
|
? new Date(this.listing.date_created).toLocaleDateString()
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
import { t, i18n } from '../i18n.js'
|
import { t, i18n } from '../i18n.js'
|
||||||
import { escapeHTML } from '../utils/helpers.js'
|
import { escapeHTML } from '../utils/helpers.js'
|
||||||
|
import { categoriesService } from '../services/categories.js'
|
||||||
const CATEGORIES = {
|
|
||||||
electronics: ['phones', 'computers', 'tv_audio', 'gaming', 'appliances'],
|
|
||||||
vehicles: ['cars', 'motorcycles', 'bikes', 'parts'],
|
|
||||||
furniture: ['living', 'bedroom', 'office', 'outdoor_furniture'],
|
|
||||||
clothing: ['women', 'men', 'kids', 'shoes', 'accessories'],
|
|
||||||
sports: ['fitness', 'outdoor', 'winter', 'water', 'team_sports'],
|
|
||||||
books: ['fiction', 'nonfiction', 'textbooks', 'music_movies'],
|
|
||||||
garden: ['plants', 'tools', 'outdoor_living', 'decoration'],
|
|
||||||
other: ['collectibles', 'art', 'handmade', 'services']
|
|
||||||
}
|
|
||||||
|
|
||||||
const COUNTRIES = ['ch', 'de', 'at', 'fr', 'it', 'li']
|
const COUNTRIES = ['ch', 'de', 'at', 'fr', 'it', 'li']
|
||||||
const RADIUS_OPTIONS = [5, 10, 20, 50, 100, 200]
|
const RADIUS_OPTIONS = [5, 10, 20, 50, 100, 200]
|
||||||
@@ -51,6 +41,7 @@ class SearchBox extends HTMLElement {
|
|||||||
this.selectedRadius = 50
|
this.selectedRadius = 50
|
||||||
this.useCurrentLocation = false
|
this.useCurrentLocation = false
|
||||||
this.searchQuery = ''
|
this.searchQuery = ''
|
||||||
|
this.categoryTree = []
|
||||||
this.geoLoading = false
|
this.geoLoading = false
|
||||||
this.currentLat = null
|
this.currentLat = null
|
||||||
this.currentLng = null
|
this.currentLng = null
|
||||||
@@ -83,12 +74,18 @@ class SearchBox extends HTMLElement {
|
|||||||
this.searchQuery = this.getAttribute('query')
|
this.searchQuery = this.getAttribute('query')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.loadAndRender()
|
||||||
|
this.unsubscribe = i18n.subscribe(() => this.loadAndRender())
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadAndRender() {
|
||||||
|
try {
|
||||||
|
this.categoryTree = await categoriesService.getTree()
|
||||||
|
} catch (e) {
|
||||||
|
this.categoryTree = []
|
||||||
|
}
|
||||||
this.render()
|
this.render()
|
||||||
this.setupEventListeners()
|
this.setupEventListeners()
|
||||||
this.unsubscribe = i18n.subscribe(() => {
|
|
||||||
this.render()
|
|
||||||
this.setupEventListeners()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
@@ -151,6 +148,18 @@ class SearchBox extends HTMLElement {
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCategoryLabel() {
|
||||||
|
const cat = this.categoryTree.find(c => c.slug === this.selectedCategory)
|
||||||
|
if (!cat) return this.selectedCategory
|
||||||
|
|
||||||
|
const catName = categoriesService.getTranslatedName(cat)
|
||||||
|
if (this.selectedSubcategory) {
|
||||||
|
const sub = (cat.children || []).find(s => s.slug === this.selectedSubcategory)
|
||||||
|
if (sub) return `${catName} › ${categoriesService.getTranslatedName(sub)}`
|
||||||
|
}
|
||||||
|
return catName
|
||||||
|
}
|
||||||
|
|
||||||
hasActiveFilters() {
|
hasActiveFilters() {
|
||||||
return this.searchQuery || this.selectedCategory || this.useCurrentLocation
|
return this.searchQuery || this.selectedCategory || this.useCurrentLocation
|
||||||
}
|
}
|
||||||
@@ -183,9 +192,7 @@ class SearchBox extends HTMLElement {
|
|||||||
|
|
||||||
// Category badge
|
// Category badge
|
||||||
if (this.selectedCategory) {
|
if (this.selectedCategory) {
|
||||||
const categoryLabel = this.selectedSubcategory
|
const categoryLabel = this.getCategoryLabel()
|
||||||
? `${t(`categories.${this.selectedCategory}`)} › ${t(`subcategories.${this.selectedSubcategory}`)}`
|
|
||||||
: t(`categories.${this.selectedCategory}`)
|
|
||||||
|
|
||||||
badges.push(/* html */`
|
badges.push(/* html */`
|
||||||
<button type="button" class="filter-badge" data-filter="category">
|
<button type="button" class="filter-badge" data-filter="category">
|
||||||
@@ -242,9 +249,7 @@ class SearchBox extends HTMLElement {
|
|||||||
<button type="button" class="category-dropdown-trigger" id="category-trigger">
|
<button type="button" class="category-dropdown-trigger" id="category-trigger">
|
||||||
<span class="category-dropdown-label">
|
<span class="category-dropdown-label">
|
||||||
${this.selectedCategory
|
${this.selectedCategory
|
||||||
? (this.selectedSubcategory
|
? this.getCategoryLabel()
|
||||||
? `${t(`categories.${this.selectedCategory}`)} › ${t(`subcategories.${this.selectedSubcategory}`)}`
|
|
||||||
: t(`categories.${this.selectedCategory}`))
|
|
||||||
: t('search.allCategories')}
|
: t('search.allCategories')}
|
||||||
</span>
|
</span>
|
||||||
<svg class="category-dropdown-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg class="category-dropdown-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
@@ -256,21 +261,21 @@ class SearchBox extends HTMLElement {
|
|||||||
<button type="button" class="category-item category-item--all ${!this.selectedCategory ? 'active' : ''}" data-category="" data-subcategory="">
|
<button type="button" class="category-item category-item--all ${!this.selectedCategory ? 'active' : ''}" data-category="" data-subcategory="">
|
||||||
${t('search.allCategories')}
|
${t('search.allCategories')}
|
||||||
</button>
|
</button>
|
||||||
${Object.keys(CATEGORIES).map(cat => `
|
${(this.categoryTree || []).map(cat => `
|
||||||
<div class="category-accordion ${this._expandedCategory === cat ? 'expanded' : ''}">
|
<div class="category-accordion ${this._expandedCategory === cat.slug ? 'expanded' : ''}">
|
||||||
<button type="button" class="category-item ${this.selectedCategory === cat ? 'active' : ''}" data-category="${cat}">
|
<button type="button" class="category-item ${this.selectedCategory === cat.slug ? 'active' : ''}" data-category="${cat.slug}">
|
||||||
<span>${t(`categories.${cat}`)}</span>
|
<span>${categoriesService.getTranslatedName(cat)}</span>
|
||||||
<svg class="category-item-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg class="category-item-arrow" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<polyline points="6 9 12 15 18 9"></polyline>
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="subcategory-list">
|
<div class="subcategory-list">
|
||||||
<button type="button" class="subcategory-item ${this.selectedCategory === cat && !this.selectedSubcategory ? 'active' : ''}" data-category="${cat}" data-subcategory="">
|
<button type="button" class="subcategory-item ${this.selectedCategory === cat.slug && !this.selectedSubcategory ? 'active' : ''}" data-category="${cat.slug}" data-subcategory="">
|
||||||
${t('search.allIn')} ${t(`categories.${cat}`)}
|
${t('search.allIn')} ${categoriesService.getTranslatedName(cat)}
|
||||||
</button>
|
</button>
|
||||||
${CATEGORIES[cat].map(sub => `
|
${(cat.children || []).map(sub => `
|
||||||
<button type="button" class="subcategory-item ${this.selectedCategory === cat && this.selectedSubcategory === sub ? 'active' : ''}" data-category="${cat}" data-subcategory="${sub}">
|
<button type="button" class="subcategory-item ${this.selectedCategory === cat.slug && this.selectedSubcategory === sub.slug ? 'active' : ''}" data-category="${cat.slug}" data-subcategory="${sub.slug}">
|
||||||
${t(`subcategories.${sub}`)}
|
${categoriesService.getTranslatedName(sub)}
|
||||||
</button>
|
</button>
|
||||||
`).join('')}
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
@@ -696,15 +701,16 @@ class SearchBox extends HTMLElement {
|
|||||||
if (filters.useCurrentLocation !== undefined) this.useCurrentLocation = filters.useCurrentLocation
|
if (filters.useCurrentLocation !== undefined) this.useCurrentLocation = filters.useCurrentLocation
|
||||||
|
|
||||||
this.saveFiltersToStorage()
|
this.saveFiltersToStorage()
|
||||||
this.render()
|
if (this.categoryTree && this.categoryTree.length > 0) {
|
||||||
this.setupEventListeners()
|
this.render()
|
||||||
|
this.setupEventListeners()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearFilters() {
|
clearFilters() {
|
||||||
this.resetFilters()
|
this.resetFilters()
|
||||||
localStorage.removeItem('searchFilters')
|
localStorage.removeItem('searchFilters')
|
||||||
this.render()
|
this.loadAndRender()
|
||||||
this.setupEventListeners()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1174,4 +1180,4 @@ style.textContent = /* css */`
|
|||||||
`
|
`
|
||||||
document.head.appendChild(style)
|
document.head.appendChild(style)
|
||||||
|
|
||||||
export { SearchBox, CATEGORIES, COUNTRIES, RADIUS_OPTIONS }
|
export { SearchBox, COUNTRIES, RADIUS_OPTIONS }
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class CategoriesService {
|
|||||||
this.cache = null
|
this.cache = null
|
||||||
this.cacheTimestamp = 0
|
this.cacheTimestamp = 0
|
||||||
this.cacheTimeout = 10 * 60 * 1000 // 10 minutes
|
this.cacheTimeout = 10 * 60 * 1000 // 10 minutes
|
||||||
|
this._pending = null
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll() {
|
async getAll() {
|
||||||
@@ -17,10 +18,19 @@ class CategoriesService {
|
|||||||
return this.cache
|
return this.cache
|
||||||
}
|
}
|
||||||
|
|
||||||
const categories = await directus.getCategories()
|
if (this._pending) return this._pending
|
||||||
this.cache = categories
|
|
||||||
this.cacheTimestamp = Date.now()
|
this._pending = directus.getCategories().then(categories => {
|
||||||
return categories
|
this.cache = categories
|
||||||
|
this.cacheTimestamp = Date.now()
|
||||||
|
this._pending = null
|
||||||
|
return categories
|
||||||
|
}).catch(err => {
|
||||||
|
this._pending = null
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
|
return this._pending
|
||||||
}
|
}
|
||||||
|
|
||||||
async getById(id) {
|
async getById(id) {
|
||||||
@@ -32,7 +42,17 @@ class CategoriesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getTree() {
|
async getTree() {
|
||||||
return directus.getCategoryTree()
|
const all = await this.getAll()
|
||||||
|
return this.buildTree(all)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTree(categories, parentId = null) {
|
||||||
|
return categories
|
||||||
|
.filter(cat => (cat.parent?.id || cat.parent) === parentId)
|
||||||
|
.map(cat => ({
|
||||||
|
...cat,
|
||||||
|
children: this.buildTree(categories, cat.id)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSubcategories(parentId) {
|
async getSubcategories(parentId) {
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ class DirectusService {
|
|||||||
* @returns {Promise<Object|null>} Response data or null for 204
|
* @returns {Promise<Object|null>} Response data or null for 204
|
||||||
* @throws {DirectusError} On request failure
|
* @throws {DirectusError} On request failure
|
||||||
*/
|
*/
|
||||||
async request(endpoint, options = {}) {
|
async request(endpoint, options = {}, _retryCount = 0) {
|
||||||
const url = `${this.baseUrl}${endpoint}`
|
const url = `${this.baseUrl}${endpoint}`
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
@@ -163,6 +163,12 @@ class DirectusService {
|
|||||||
this.clearTokens()
|
this.clearTokens()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.status === 429 && _retryCount < 3) {
|
||||||
|
const retryAfter = parseInt(response.headers.get('Retry-After') || '3', 10)
|
||||||
|
await new Promise(r => setTimeout(r, retryAfter * 1000))
|
||||||
|
return this.request(endpoint, options, _retryCount + 1)
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json().catch(() => ({}))
|
const error = await response.json().catch(() => ({}))
|
||||||
@@ -405,6 +411,7 @@ class DirectusService {
|
|||||||
'category.id',
|
'category.id',
|
||||||
'category.name',
|
'category.name',
|
||||||
'category.slug',
|
'category.slug',
|
||||||
|
'category.translations.*',
|
||||||
'location.id',
|
'location.id',
|
||||||
'location.name',
|
'location.name',
|
||||||
'location.postal_code',
|
'location.postal_code',
|
||||||
|
|||||||
@@ -4,13 +4,27 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { directus } from './directus.js'
|
import { directus } from './directus.js'
|
||||||
|
import { categoriesService } from './categories.js'
|
||||||
|
|
||||||
class ListingsService {
|
class ListingsService {
|
||||||
async getListingsWithFilters(filters = {}) {
|
async getListingsWithFilters(filters = {}) {
|
||||||
const directusFilter = { status: { _eq: 'published' } }
|
const directusFilter = { status: { _eq: 'published' } }
|
||||||
|
|
||||||
if (filters.category) {
|
if (filters.category) {
|
||||||
directusFilter.category = { slug: { _eq: filters.category } }
|
if (filters.subcategory) {
|
||||||
|
directusFilter.category = { slug: { _eq: filters.subcategory } }
|
||||||
|
} else {
|
||||||
|
const all = await categoriesService.getAll()
|
||||||
|
const parent = all.find(c => c.slug === filters.category && !c.parent)
|
||||||
|
if (parent) {
|
||||||
|
const childSlugs = all
|
||||||
|
.filter(c => (c.parent?.id || c.parent) === parent.id)
|
||||||
|
.map(c => c.slug)
|
||||||
|
directusFilter.category = { slug: { _in: [filters.category, ...childSlugs] } }
|
||||||
|
} else {
|
||||||
|
directusFilter.category = { slug: { _eq: filters.category } }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.location) {
|
if (filters.location) {
|
||||||
|
|||||||
@@ -40,16 +40,6 @@
|
|||||||
"retry": "Erneut versuchen",
|
"retry": "Erneut versuchen",
|
||||||
"offline": "Keine Internetverbindung"
|
"offline": "Keine Internetverbindung"
|
||||||
},
|
},
|
||||||
"categories": {
|
|
||||||
"electronics": "Elektronik",
|
|
||||||
"furniture": "Möbel",
|
|
||||||
"clothing": "Kleidung",
|
|
||||||
"vehicles": "Fahrzeuge",
|
|
||||||
"sports": "Sport & Freizeit",
|
|
||||||
"books": "Bücher & Medien",
|
|
||||||
"garden": "Garten",
|
|
||||||
"other": "Sonstiges"
|
|
||||||
},
|
|
||||||
"search": {
|
"search": {
|
||||||
"title": "Suche",
|
"title": "Suche",
|
||||||
"placeholder": "Suchbegriff eingeben...",
|
"placeholder": "Suchbegriff eingeben...",
|
||||||
@@ -76,43 +66,6 @@
|
|||||||
"sortPriceDesc": "Preis absteigend",
|
"sortPriceDesc": "Preis absteigend",
|
||||||
"sortDistance": "In der Nähe"
|
"sortDistance": "In der Nähe"
|
||||||
},
|
},
|
||||||
"subcategories": {
|
|
||||||
"phones": "Handy & Telefon",
|
|
||||||
"computers": "Computer & Zubehör",
|
|
||||||
"tv_audio": "TV & Audio",
|
|
||||||
"gaming": "Gaming & Konsolen",
|
|
||||||
"appliances": "Haushaltsgeräte",
|
|
||||||
"cars": "Autos",
|
|
||||||
"motorcycles": "Motorräder",
|
|
||||||
"bikes": "Fahrräder",
|
|
||||||
"parts": "Ersatzteile & Zubehör",
|
|
||||||
"living": "Wohnzimmer",
|
|
||||||
"bedroom": "Schlafzimmer",
|
|
||||||
"office": "Büro",
|
|
||||||
"outdoor_furniture": "Gartenmöbel",
|
|
||||||
"women": "Damen",
|
|
||||||
"men": "Herren",
|
|
||||||
"kids": "Kinder",
|
|
||||||
"shoes": "Schuhe",
|
|
||||||
"accessories": "Accessoires",
|
|
||||||
"fitness": "Fitness",
|
|
||||||
"outdoor": "Outdoor",
|
|
||||||
"winter": "Wintersport",
|
|
||||||
"water": "Wassersport",
|
|
||||||
"team_sports": "Mannschaftssport",
|
|
||||||
"fiction": "Belletristik",
|
|
||||||
"nonfiction": "Sachbücher",
|
|
||||||
"textbooks": "Lehrbücher",
|
|
||||||
"music_movies": "Musik & Filme",
|
|
||||||
"plants": "Pflanzen",
|
|
||||||
"tools": "Werkzeuge",
|
|
||||||
"outdoor_living": "Outdoor-Living",
|
|
||||||
"decoration": "Dekoration",
|
|
||||||
"collectibles": "Sammlerstücke",
|
|
||||||
"art": "Kunst",
|
|
||||||
"handmade": "Handgemacht",
|
|
||||||
"services": "Dienstleistungen"
|
|
||||||
},
|
|
||||||
"countries": {
|
"countries": {
|
||||||
"ch": "Schweiz",
|
"ch": "Schweiz",
|
||||||
"de": "Deutschland",
|
"de": "Deutschland",
|
||||||
|
|||||||
@@ -40,16 +40,6 @@
|
|||||||
"retry": "Try again",
|
"retry": "Try again",
|
||||||
"offline": "No internet connection"
|
"offline": "No internet connection"
|
||||||
},
|
},
|
||||||
"categories": {
|
|
||||||
"electronics": "Electronics",
|
|
||||||
"furniture": "Furniture",
|
|
||||||
"clothing": "Clothing",
|
|
||||||
"vehicles": "Vehicles",
|
|
||||||
"sports": "Sports & Leisure",
|
|
||||||
"books": "Books & Media",
|
|
||||||
"garden": "Garden",
|
|
||||||
"other": "Other"
|
|
||||||
},
|
|
||||||
"search": {
|
"search": {
|
||||||
"title": "Search",
|
"title": "Search",
|
||||||
"placeholder": "Enter search term...",
|
"placeholder": "Enter search term...",
|
||||||
@@ -76,43 +66,6 @@
|
|||||||
"sortPriceDesc": "Price: high to low",
|
"sortPriceDesc": "Price: high to low",
|
||||||
"sortDistance": "Nearby"
|
"sortDistance": "Nearby"
|
||||||
},
|
},
|
||||||
"subcategories": {
|
|
||||||
"phones": "Phones & Tablets",
|
|
||||||
"computers": "Computers & Accessories",
|
|
||||||
"tv_audio": "TV & Audio",
|
|
||||||
"gaming": "Gaming & Consoles",
|
|
||||||
"appliances": "Appliances",
|
|
||||||
"cars": "Cars",
|
|
||||||
"motorcycles": "Motorcycles",
|
|
||||||
"bikes": "Bicycles",
|
|
||||||
"parts": "Parts & Accessories",
|
|
||||||
"living": "Living Room",
|
|
||||||
"bedroom": "Bedroom",
|
|
||||||
"office": "Office",
|
|
||||||
"outdoor_furniture": "Outdoor Furniture",
|
|
||||||
"women": "Women",
|
|
||||||
"men": "Men",
|
|
||||||
"kids": "Kids",
|
|
||||||
"shoes": "Shoes",
|
|
||||||
"accessories": "Accessories",
|
|
||||||
"fitness": "Fitness",
|
|
||||||
"outdoor": "Outdoor",
|
|
||||||
"winter": "Winter Sports",
|
|
||||||
"water": "Water Sports",
|
|
||||||
"team_sports": "Team Sports",
|
|
||||||
"fiction": "Fiction",
|
|
||||||
"nonfiction": "Non-Fiction",
|
|
||||||
"textbooks": "Textbooks",
|
|
||||||
"music_movies": "Music & Movies",
|
|
||||||
"plants": "Plants",
|
|
||||||
"tools": "Tools",
|
|
||||||
"outdoor_living": "Outdoor Living",
|
|
||||||
"decoration": "Decoration",
|
|
||||||
"collectibles": "Collectibles",
|
|
||||||
"art": "Art",
|
|
||||||
"handmade": "Handmade",
|
|
||||||
"services": "Services"
|
|
||||||
},
|
|
||||||
"countries": {
|
"countries": {
|
||||||
"ch": "Switzerland",
|
"ch": "Switzerland",
|
||||||
"de": "Germany",
|
"de": "Germany",
|
||||||
|
|||||||
@@ -40,16 +40,6 @@
|
|||||||
"retry": "Réessayer",
|
"retry": "Réessayer",
|
||||||
"offline": "Pas de connexion internet"
|
"offline": "Pas de connexion internet"
|
||||||
},
|
},
|
||||||
"categories": {
|
|
||||||
"electronics": "Électronique",
|
|
||||||
"furniture": "Meubles",
|
|
||||||
"clothing": "Vêtements",
|
|
||||||
"vehicles": "Véhicules",
|
|
||||||
"sports": "Sports & Loisirs",
|
|
||||||
"books": "Livres & Médias",
|
|
||||||
"garden": "Jardin",
|
|
||||||
"other": "Autres"
|
|
||||||
},
|
|
||||||
"search": {
|
"search": {
|
||||||
"title": "Recherche",
|
"title": "Recherche",
|
||||||
"placeholder": "Entrez un terme de recherche...",
|
"placeholder": "Entrez un terme de recherche...",
|
||||||
@@ -76,43 +66,6 @@
|
|||||||
"sortPriceDesc": "Prix décroissant",
|
"sortPriceDesc": "Prix décroissant",
|
||||||
"sortDistance": "À proximité"
|
"sortDistance": "À proximité"
|
||||||
},
|
},
|
||||||
"subcategories": {
|
|
||||||
"phones": "Téléphones & Tablettes",
|
|
||||||
"computers": "Ordinateurs & Accessoires",
|
|
||||||
"tv_audio": "TV & Audio",
|
|
||||||
"gaming": "Jeux & Consoles",
|
|
||||||
"appliances": "Électroménager",
|
|
||||||
"cars": "Voitures",
|
|
||||||
"motorcycles": "Motos",
|
|
||||||
"bikes": "Vélos",
|
|
||||||
"parts": "Pièces & Accessoires",
|
|
||||||
"living": "Salon",
|
|
||||||
"bedroom": "Chambre",
|
|
||||||
"office": "Bureau",
|
|
||||||
"outdoor_furniture": "Mobilier extérieur",
|
|
||||||
"women": "Femmes",
|
|
||||||
"men": "Hommes",
|
|
||||||
"kids": "Enfants",
|
|
||||||
"shoes": "Chaussures",
|
|
||||||
"accessories": "Accessoires",
|
|
||||||
"fitness": "Fitness",
|
|
||||||
"outdoor": "Plein air",
|
|
||||||
"winter": "Sports d'hiver",
|
|
||||||
"water": "Sports nautiques",
|
|
||||||
"team_sports": "Sports d'équipe",
|
|
||||||
"fiction": "Fiction",
|
|
||||||
"nonfiction": "Non-fiction",
|
|
||||||
"textbooks": "Manuels scolaires",
|
|
||||||
"music_movies": "Musique & Films",
|
|
||||||
"plants": "Plantes",
|
|
||||||
"tools": "Outils",
|
|
||||||
"outdoor_living": "Vie extérieure",
|
|
||||||
"decoration": "Décoration",
|
|
||||||
"collectibles": "Objets de collection",
|
|
||||||
"art": "Art",
|
|
||||||
"handmade": "Fait main",
|
|
||||||
"services": "Services"
|
|
||||||
},
|
|
||||||
"countries": {
|
"countries": {
|
||||||
"ch": "Suisse",
|
"ch": "Suisse",
|
||||||
"de": "Allemagne",
|
"de": "Allemagne",
|
||||||
|
|||||||
Reference in New Issue
Block a user