i18n(web): full landing page TR↔EN — language switcher actually works now
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Root cause: landing was almost entirely hard-coded Turkish (5 t() calls in
1889 lines), and initLocale() — which reads the persisted locale from
localStorage on boot — was never called from main.tsx, so a reload always
reverted to the TR default.
This commit fixes both.
apps/web/src/main.tsx
- Call initLocale() before first render so the persisted choice survives reload.
apps/web/src/routes/__root.tsx
- Translate the skip-to-content link via a new `a11y.skipToContent` key.
apps/web/src/routes/index.tsx
- Internationalize ~145 user-visible strings across hero, brand strip, features,
comparison table, bento grid, use cases, stats, dashboard showcase, ecommerce
block, testimonials, pricing tiers, FAQ, final CTA, referral banner and ARIA
labels. Module-scope data arrays moved inside the component so re-renders
pick up the active locale.
- Wrap the three Remotion players (Schema / Dashboard / Ecommerce) so they read
the current locale and pass translated labels into their inputProps.
Remotion demos (DashboardDemo, EcommerceDemo, SchemaDemo)
- Each now accepts a `labels` prop (DashboardDemoLabels / EcommerceDemoLabels /
partLabel). All hard-coded TR sidebar items, placeholders, category names,
product names, "Add to Cart" buttons, the cart-added toast, overlays and
stat labels are now driven by labels with a TR fallback (keeps Remotion
Studio preview working standalone).
messages/{tr,en}.json
- New blocks: `a11y`, `landing.*` (huge tree covering every section above),
`landing.dashboardDemo`, `landing.ecommerceDemo`.
Intentionally preserved as data, not translated: brand names (Volkswagen,
Mitsubishi …), VIN literals, OEM codes (1J0 820 803F …), numeric stat values
(1.2sn, 27, 1M+, %99.9 …), prices (200/350/500/999), and the testimonial
person name "Özge D." (proper noun).
Verified: typecheck clean, 17/17 test files / 57/57 tests pass, biome clean,
Playwright TR↔EN round-trip succeeds, only the testimonial name remains as
Turkish on the EN page.
This commit is contained in:
@@ -4,11 +4,16 @@ import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { initChatwoot } from "./lib/chatwoot";
|
||||
import { initFaro } from "./lib/faro";
|
||||
import { initLocale } from "./lib/i18n";
|
||||
import { initMetaPixel } from "./lib/meta-pixel";
|
||||
import { initPostHog } from "./lib/posthog";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
import "./globals.css";
|
||||
|
||||
// Read persisted locale from localStorage before first render — without this
|
||||
// a reload always falls back to the TR default even if the user picked EN.
|
||||
initLocale();
|
||||
|
||||
// Initialize frontend observability (async, non-blocking)
|
||||
initFaro();
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
"register": "Sign Up",
|
||||
"reload": "Reload page"
|
||||
},
|
||||
"a11y": {
|
||||
"skipToContent": "Skip to content"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Dashboard",
|
||||
"search": "Search",
|
||||
@@ -657,5 +660,411 @@
|
||||
"partsHeading": "Parts (OEM)",
|
||||
"noPartMatch": "No matching parts. (Only parts in opened categories are searched.)",
|
||||
"partsCount": "{count} part(s)"
|
||||
},
|
||||
"landing": {
|
||||
"meta": {
|
||||
"title": "VIN Lookup & OEM Parts Catalog | Sase.tr",
|
||||
"description": "Enter a vehicle VIN and get OEM part numbers in seconds. 27 brands, 1M+ parts. Order the right part the first time.",
|
||||
"schemaOrgDescription": "Turkey's VIN lookup and OEM spare parts catalog platform.",
|
||||
"schemaSoftwareDescription": "OEM spare parts lookup platform powered by VIN."
|
||||
},
|
||||
"nav": {
|
||||
"features": "Features",
|
||||
"howItWorks": "How It Works",
|
||||
"pricing": "Pricing",
|
||||
"blog": "Blog",
|
||||
"contact": "Contact",
|
||||
"login": "Log In",
|
||||
"register": "Try Free for 30 Days",
|
||||
"dashboard": "Go to Dashboard"
|
||||
},
|
||||
"aria": {
|
||||
"themeToggle": "Toggle theme",
|
||||
"showBrand": "Show {brand}"
|
||||
},
|
||||
"hero": {
|
||||
"lossPill": "Every wrong-part order costs 200-500 TL",
|
||||
"ratingLabel": "4.9/5",
|
||||
"socialProofLabel": "500+ Businesses Trust Us",
|
||||
"title": "Find the Right Part Instantly",
|
||||
"subtitle": "Cross-referenced OEM codes from multiple catalogs, always up to date. Enter a VIN and ship the right part the first time.",
|
||||
"vinPlaceholder": "Example: WVWZZZ1JZ3W597935",
|
||||
"searchCta": "Search",
|
||||
"decoding": "Decoding...",
|
||||
"loadingVehicle": "Fetching vehicle info...",
|
||||
"ctaOpenCatalog": "Open parts catalog",
|
||||
"ctaAccessFullCatalog": "Access the full parts catalog",
|
||||
"vinNotFound": "VIN not found. Please check and try again.",
|
||||
"noVinPrompt": "Don't have a VIN?",
|
||||
"tryExample": "Try an example search →",
|
||||
"metricSpeed": "1.2s Average Result",
|
||||
"metricBrands": "27 Brands Supported",
|
||||
"metricParts": "1M+ OEM Parts"
|
||||
},
|
||||
"brandStrip": {
|
||||
"supported": "27 Supported Car Brands"
|
||||
},
|
||||
"testimonialStrip": {
|
||||
"title": "What our users say",
|
||||
"items": {
|
||||
"0": {
|
||||
"text": "With Sase.tr our wrong-part orders dropped 80%. We order with confidence now.",
|
||||
"name": "Ahmet Y.",
|
||||
"role": "Spare Parts Store"
|
||||
},
|
||||
"1": {
|
||||
"text": "We save over 40 hours a month. Entering a VIN and getting the right code instantly is incredible.",
|
||||
"name": "Mehmet K.",
|
||||
"role": "Auto Service Owner"
|
||||
},
|
||||
"2": {
|
||||
"text": "We integrated it into our online store and returns fell by half. Customer satisfaction is visibly higher.",
|
||||
"name": "Özge D.",
|
||||
"role": "E-Commerce Manager"
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"badge": "Our Solutions",
|
||||
"title": "VIN Decoding & Parts Catalog",
|
||||
"subtitle": "From vehicle identification to part ordering — the whole flow on one platform.",
|
||||
"loading": "Loading…",
|
||||
"ctaTry": "Try It Now",
|
||||
"vin": {
|
||||
"title": "VIN Decoding",
|
||||
"description": "Customer not sure about the vehicle? Enter the VIN — brand, model, engine and trim show up in seconds.",
|
||||
"bullet1": "Brand, model and year detection",
|
||||
"bullet2": "Engine and transmission details",
|
||||
"bullet3": "Trim package information",
|
||||
"mockupTitle": "VIN Decoding — Vehicle Details",
|
||||
"labelMake": "Brand",
|
||||
"labelModel": "Model",
|
||||
"labelYear": "Year",
|
||||
"labelEngine": "Engine",
|
||||
"labelBody": "Body",
|
||||
"valueBody": "Hatchback"
|
||||
},
|
||||
"schema": {
|
||||
"title": "Interactive Diagrams",
|
||||
"description": "Instead of getting lost in parts catalogs, find the part visually on clickable diagrams.",
|
||||
"bullet1": "Clickable part diagrams",
|
||||
"bullet2": "Zoom & pan navigation",
|
||||
"bullet3": "Sub-category depth",
|
||||
"mockupTitle": "Interactive Diagram — Engine Bay"
|
||||
},
|
||||
"oem": {
|
||||
"title": "OEM Part Numbers",
|
||||
"description": "Cross-references multiple catalogs so OEM codes are always current and correct — no more wrong parts, no more returns.",
|
||||
"bullet1": "Multi-catalog cross-referencing",
|
||||
"bullet2": "Always-current OEM codes",
|
||||
"bullet3": "Price comparison",
|
||||
"mockupTitle": "OEM Catalog — Parts List",
|
||||
"part1": "AC Compressor",
|
||||
"part2": "Heater Blower Motor",
|
||||
"part3": "Front Brake Pad"
|
||||
}
|
||||
},
|
||||
"compare": {
|
||||
"badge": "Comparison",
|
||||
"title": "Manual Workflow vs Sase.tr",
|
||||
"manualHeader": "Manual",
|
||||
"saseHeader": "Sase.tr",
|
||||
"rows": {
|
||||
"0": {
|
||||
"label": "Part lookup time",
|
||||
"manual": "~15 minutes",
|
||||
"sase": "30 seconds"
|
||||
},
|
||||
"1": {
|
||||
"label": "Wrong-part risk",
|
||||
"manual": "High",
|
||||
"sase": "Near zero"
|
||||
},
|
||||
"2": {
|
||||
"label": "Finding the OEM code",
|
||||
"manual": "Single catalog, may be outdated",
|
||||
"sase": "Multi-catalog cross-query"
|
||||
},
|
||||
"3": {
|
||||
"label": "Price comparison",
|
||||
"manual": "Impossible",
|
||||
"sase": "On a single screen"
|
||||
},
|
||||
"4": {
|
||||
"label": "Monthly time saved",
|
||||
"manual": "—",
|
||||
"sase": "~40 hours"
|
||||
}
|
||||
}
|
||||
},
|
||||
"howItWorks": {
|
||||
"badge": "How It Works",
|
||||
"title": "Get to the Right OEM Part in 3 Steps",
|
||||
"items": {
|
||||
"0": {
|
||||
"title": "Enter VIN",
|
||||
"description": "Enter the 17-character VIN and the vehicle is identified instantly."
|
||||
},
|
||||
"1": {
|
||||
"title": "27 Brands",
|
||||
"description": "European, Asian and American brands under one roof."
|
||||
},
|
||||
"2": {
|
||||
"title": "Pick a Category",
|
||||
"description": "Engine, chassis, electrical… drill into the category you need."
|
||||
},
|
||||
"3": {
|
||||
"title": "Inspect the Diagram",
|
||||
"description": "Find part positions visually on interactive technical diagrams."
|
||||
},
|
||||
"4": {
|
||||
"title": "Get the OEM Code",
|
||||
"description": "Copy the original manufacturer part number, ready to order."
|
||||
},
|
||||
"5": {
|
||||
"title": "Place the Order",
|
||||
"description": "Send suppliers an order with the exact part number."
|
||||
}
|
||||
}
|
||||
},
|
||||
"useCases": {
|
||||
"badge": "Use Cases",
|
||||
"title": "Who It's For",
|
||||
"items": {
|
||||
"0": {
|
||||
"title": "Spare Parts Lookup",
|
||||
"description": "Find genuine parts that fit the vehicle from its VIN, instantly."
|
||||
},
|
||||
"1": {
|
||||
"title": "Original Part Numbers",
|
||||
"description": "Use OEM codes to order the right part the first time."
|
||||
},
|
||||
"2": {
|
||||
"title": "Vehicle Identification",
|
||||
"description": "Decode the VIN to get full technical and trim details for any car."
|
||||
},
|
||||
"3": {
|
||||
"title": "Used-Part Verification",
|
||||
"description": "Verify authenticity and fitment of salvage parts via OEM codes."
|
||||
},
|
||||
"4": {
|
||||
"title": "Service & Maintenance",
|
||||
"description": "Speed up service jobs with parts lists tailored to maintenance schedules."
|
||||
}
|
||||
},
|
||||
"ctaCard": {
|
||||
"title": "Have a different need?",
|
||||
"description": "Reach out if you need a custom integration or bulk lookup.",
|
||||
"cta": "Contact Us"
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"badge": "Platform",
|
||||
"titleLine1": "Sase.tr",
|
||||
"titleLine2": "in Numbers",
|
||||
"items": {
|
||||
"0": { "value": "1.2sn", "label": "Average lookup time" },
|
||||
"1": { "value": "27", "label": "Supported brands" },
|
||||
"2": { "value": "1M+", "label": "OEM part numbers" },
|
||||
"3": { "value": "%99.9", "label": "Platform uptime" }
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "All Your Data, One Platform",
|
||||
"subtitle": "VIN decoding, parts catalog and interactive diagrams in a single interface.",
|
||||
"frameTitle": "sase.tr/dashboard",
|
||||
"loading": "Loading…",
|
||||
"floatVin": "VIN Search",
|
||||
"floatSchema": "Interactive Diagram",
|
||||
"floatOem": "OEM Codes"
|
||||
},
|
||||
"dashboardDemo": {
|
||||
"sidebar": {
|
||||
"dashboard": "Dashboard",
|
||||
"vinSearch": "VIN Search",
|
||||
"catalog": "Catalog",
|
||||
"diagrams": "Diagrams",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"categories": {
|
||||
"engine": "Engine",
|
||||
"chassis": "Chassis",
|
||||
"electrical": "Electrical",
|
||||
"body": "Body",
|
||||
"brakes": "Brakes"
|
||||
},
|
||||
"parts": {
|
||||
"acCompressor": "A/C Compressor",
|
||||
"brakePads": "Brake Pads",
|
||||
"heaterBlower": "Heater Blower Motor"
|
||||
},
|
||||
"vinPlaceholder": "Enter VIN...",
|
||||
"categorySelectTitle": "Select Category",
|
||||
"schemaViewTitle": "Engine — Schematic View",
|
||||
"oemListTitle": "OEM Parts List"
|
||||
},
|
||||
"ecommerceDemo": {
|
||||
"products": {
|
||||
"brakePads": "Brake Pads",
|
||||
"acCompressor": "A/C Compressor",
|
||||
"shockAbsorber": "Shock Absorber",
|
||||
"oilFilter": "Oil Filter",
|
||||
"wiperMotor": "Wiper Motor",
|
||||
"headlight": "Headlight"
|
||||
},
|
||||
"searchPlaceholder": "Search part...",
|
||||
"preReturnBadge": "23% return rate",
|
||||
"addToCart": "Add to Cart",
|
||||
"cartAddedToast": "Brake Pads added to cart",
|
||||
"conversionOverlay": "Conversion: 94%",
|
||||
"statReturnRate": "Return rate",
|
||||
"statConversionRate": "Conversion rate",
|
||||
"statSatisfaction": "Customer satisfaction"
|
||||
},
|
||||
"ecommerce": {
|
||||
"badge": "E-Commerce Integration",
|
||||
"title": "Add VIN Decoding to Your Own Site",
|
||||
"subtitle": "Customers ordering wrong parts and opening returns? Add VIN-based filtering with Sase.tr — returns drop, conversion rises.",
|
||||
"frameTitle": "otoyedekparca.co — Sase.tr Integration",
|
||||
"loading": "Loading…",
|
||||
"easyBadge": "Easy Integration",
|
||||
"cardTitle": "A Few Lines of Code, Big Difference",
|
||||
"cardBody": "Embed Sase.tr's VIN decoding engine into your e-commerce site. Customers enter the VIN and see only parts that fit their vehicle.",
|
||||
"bullet1": "API & Widget — embed in a few lines of code",
|
||||
"bullet2": "VIN-based filtering — only compatible parts appear",
|
||||
"bullet3": "Lower returns — the right part, the first time",
|
||||
"bullet4": "White-label — the widget matches your site's design",
|
||||
"cta": "Get in Touch",
|
||||
"stats": {
|
||||
"0": { "value": "%42", "label": "Fewer Returns" },
|
||||
"1": { "value": "%35", "label": "Higher Conversion" },
|
||||
"2": { "value": "<30dk", "label": "Integration Time" }
|
||||
}
|
||||
},
|
||||
"testimonials": {
|
||||
"badge": "Customer Stories",
|
||||
"title": "What Business Owners Say",
|
||||
"items": {
|
||||
"0": {
|
||||
"name": "Ahmet Y.",
|
||||
"role": "Spare Parts Store Owner",
|
||||
"text": "I used to open 3-4 catalogs for one part. With Sase.tr I enter the VIN and find the right OEM code in 30 seconds. Our return rate dropped 80%."
|
||||
},
|
||||
"1": {
|
||||
"name": "Mehmet K.",
|
||||
"role": "Auto Service Owner",
|
||||
"text": "Wrong-part orders almost went to zero since we switched to Sase.tr. We save over 40 hours a month."
|
||||
},
|
||||
"2": {
|
||||
"name": "Özge D.",
|
||||
"role": "E-Commerce Manager",
|
||||
"text": "We added VIN integration to our online store. Customers enter the VIN and only compatible parts show up. Returns fell 42%."
|
||||
},
|
||||
"3": {
|
||||
"name": "Burak T.",
|
||||
"role": "Fleet Manager",
|
||||
"text": "Parts management for our 200+ vehicle fleet is now effortless. The interactive diagrams speed up installs a lot."
|
||||
}
|
||||
}
|
||||
},
|
||||
"pricing": {
|
||||
"badge": "Pricing",
|
||||
"titleLine1": "From 7 TL a day",
|
||||
"titleLine2": "and up.",
|
||||
"subtitle": "Less than the cost of a single wrong-part return. Every plan comes with a 30-day free trial, no card required.",
|
||||
"perMonth": "/mo",
|
||||
"currency": "TL",
|
||||
"yearlyPrice": "{amount} TL/year",
|
||||
"tryFree": "Try free for 30 days",
|
||||
"plans": {
|
||||
"brand1": {
|
||||
"name": "1 Brand",
|
||||
"description": "Access for a single brand",
|
||||
"feature1": "1 brand selection",
|
||||
"feature2": "Unlimited VIN search",
|
||||
"feature3": "Parts catalog",
|
||||
"feature4": "Diagram viewer"
|
||||
},
|
||||
"brand2": {
|
||||
"name": "2 Brands",
|
||||
"description": "Two different brands",
|
||||
"feature1": "2 brand selections",
|
||||
"feature2": "Unlimited VIN search",
|
||||
"feature3": "Parts catalog",
|
||||
"feature4": "Priority support"
|
||||
},
|
||||
"brand3": {
|
||||
"name": "3 Brands",
|
||||
"description": "Three brands, broad coverage",
|
||||
"feature1": "3 brand selections",
|
||||
"feature2": "Unlimited VIN search",
|
||||
"feature3": "Parts catalog",
|
||||
"feature4": "Priority support"
|
||||
}
|
||||
},
|
||||
"full": {
|
||||
"badge": "Most popular",
|
||||
"name": "Full Package",
|
||||
"description": "Unlimited access to every brand. All in a single subscription.",
|
||||
"priceUnit": "TL/mo",
|
||||
"yearlyNote": "9,990 TL/year — works out to 832 TL/mo",
|
||||
"ctaTry": "Try free for 30 days",
|
||||
"ctaFineprint": "No card required. Cancel anytime.",
|
||||
"included": "Included",
|
||||
"feature1": "Access to all 27 brands",
|
||||
"feature2": "Unlimited VIN search",
|
||||
"feature3": "OEM parts catalog",
|
||||
"feature4": "Interactive diagram viewer",
|
||||
"feature5": "Search history & favorites",
|
||||
"feature6": "Priority email support",
|
||||
"feature7": "API access (on request)",
|
||||
"feature8": "Bulk lookup feature"
|
||||
},
|
||||
"detailedComparison": "See the detailed comparison"
|
||||
},
|
||||
"faq": {
|
||||
"badge": "FAQ",
|
||||
"title": "Frequently Asked Questions",
|
||||
"items": {
|
||||
"0": {
|
||||
"q": "Which brands do you support?",
|
||||
"a": "We currently support 27 automotive brands and the list keeps growing. Enter your VIN in the search box to see right away whether your vehicle is covered."
|
||||
},
|
||||
"1": {
|
||||
"q": "Is my data safe?",
|
||||
"a": "All traffic is encrypted with SSL and your data is processed in line with KVKK. Your search history is private to you."
|
||||
},
|
||||
"2": {
|
||||
"q": "Do you need card details?",
|
||||
"a": "No. We don't ask for a credit card for the 30-day free trial; you can stop anytime."
|
||||
},
|
||||
"3": {
|
||||
"q": "Can I cancel anytime?",
|
||||
"a": "Yes, no commitment. You can cancel your subscription from the dashboard in one click."
|
||||
},
|
||||
"4": {
|
||||
"q": "Does it integrate with my existing e-commerce or service system?",
|
||||
"a": "Full Package includes API access and bulk lookup so you can plug it into your systems (white-glove setup on request)."
|
||||
},
|
||||
"5": {
|
||||
"q": "What if I don't know the VIN?",
|
||||
"a": "You can browse the catalog by brand and model, or try the example search on the home page to test the platform."
|
||||
}
|
||||
}
|
||||
},
|
||||
"finalCta": {
|
||||
"title": "Every Wrong Part Costs You 200-500 TL",
|
||||
"subtitle": "Return shipping, lost customers, lost time. Decode the VIN and find the right part the first time.",
|
||||
"cta": "Start 30 Days Free — No Card Required",
|
||||
"trust": "KVKK compliant · SSL encrypted · No card required"
|
||||
},
|
||||
"referral": {
|
||||
"title": "Invite a Friend",
|
||||
"description": "3 invites = +7 days free, 5 invites = +1 month free",
|
||||
"cta": "Get Your Invite Code"
|
||||
},
|
||||
"errors": {
|
||||
"decodeGeneric": "Something went wrong. Please try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
"register": "Kayıt Ol",
|
||||
"reload": "Sayfayı yenile"
|
||||
},
|
||||
"a11y": {
|
||||
"skipToContent": "İçeriğe atla"
|
||||
},
|
||||
"nav": {
|
||||
"dashboard": "Gösterge Paneli",
|
||||
"search": "Arama",
|
||||
@@ -657,5 +660,411 @@
|
||||
"partsHeading": "Parçalar (OEM)",
|
||||
"noPartMatch": "Eşleşen parça yok. (Yalnızca açılmış kategorilerin parçaları aranır.)",
|
||||
"partsCount": "{count} parça"
|
||||
},
|
||||
"landing": {
|
||||
"meta": {
|
||||
"title": "Şase Numarası Sorgulama & OEM Parça Kataloğu | Sase.tr",
|
||||
"description": "Araç şase numarasını (VIN) girin, saniyeler içinde OEM parça kodlarına ulaşın. 27 marka, 1M+ parça. Yanlış sipariş yapmadan doğru parçayı bulun.",
|
||||
"schemaOrgDescription": "Türkiye'nin VIN/şase numarası sorgulama ve OEM yedek parça kataloğu platformu.",
|
||||
"schemaSoftwareDescription": "Araç şase numarası ile OEM yedek parça sorgulama platformu."
|
||||
},
|
||||
"nav": {
|
||||
"features": "Özellikler",
|
||||
"howItWorks": "Nasıl Çalışır",
|
||||
"pricing": "Fiyatlar",
|
||||
"blog": "Blog",
|
||||
"contact": "İletişim",
|
||||
"login": "Giriş Yap",
|
||||
"register": "30 Gün Ücretsiz Deneyin",
|
||||
"dashboard": "Panele Git"
|
||||
},
|
||||
"aria": {
|
||||
"themeToggle": "Tema değiştir",
|
||||
"showBrand": "{brand} markasını göster"
|
||||
},
|
||||
"hero": {
|
||||
"lossPill": "Her yanlış parça siparişi 200-500 TL'ye mal olur",
|
||||
"ratingLabel": "4.9/5",
|
||||
"socialProofLabel": "500+ İşletme Güveniyor",
|
||||
"title": "Doğru Parçayı Anında Bulun",
|
||||
"subtitle": "Birden fazla katalogda çapraz sorgulama ile her zaman en güncel OEM kodları. Şase numarasını girin, doğru parçayı ilk seferde gönderin.",
|
||||
"vinPlaceholder": "Örnek: WVWZZZ1JZ3W597935",
|
||||
"searchCta": "Ara",
|
||||
"decoding": "Çözülüyor...",
|
||||
"loadingVehicle": "Araç bilgileri alınıyor...",
|
||||
"ctaOpenCatalog": "Parça kataloğunu aç",
|
||||
"ctaAccessFullCatalog": "Tam parça kataloğuna eriş",
|
||||
"vinNotFound": "Şase bilgisi bulunamadı. Lütfen kontrol edip tekrar deneyin.",
|
||||
"noVinPrompt": "Şase numaranız yok mu?",
|
||||
"tryExample": "Örnek aramayı deneyin →",
|
||||
"metricSpeed": "Ortalama 1.2sn Sonuç",
|
||||
"metricBrands": "27 Marka Desteği",
|
||||
"metricParts": "1M+ OEM Parça"
|
||||
},
|
||||
"brandStrip": {
|
||||
"supported": "Desteklenen 27 Otomobil Markası"
|
||||
},
|
||||
"testimonialStrip": {
|
||||
"title": "Kullanıcılarımız ne diyor?",
|
||||
"items": {
|
||||
"0": {
|
||||
"text": "Sase.tr ile yanlış parça siparişlerimiz %80 azaldı. Artık gönül rahatlığıyla sipariş veriyoruz.",
|
||||
"name": "Ahmet Y.",
|
||||
"role": "Yedek Parça Mağazası"
|
||||
},
|
||||
"1": {
|
||||
"text": "Ayda 40 saatten fazla zaman kazanıyoruz. Şase numarasını girip anında doğru koda ulaşmak inanılmaz.",
|
||||
"name": "Mehmet K.",
|
||||
"role": "Oto Servis İşletmecisi"
|
||||
},
|
||||
"2": {
|
||||
"text": "Online mağazamıza entegre ettik, iade oranımız yarıya düştü. Müşteri memnuniyeti gözle görülür arttı.",
|
||||
"name": "Özge D.",
|
||||
"role": "E-Ticaret Yöneticisi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"badge": "Çözümlerimiz",
|
||||
"title": "Şase Çözme & Parça Kataloğu",
|
||||
"subtitle": "Araç tanımlamadan parça siparişine kadar tüm süreç tek platformda.",
|
||||
"loading": "Yükleniyor…",
|
||||
"ctaTry": "Hemen Deneyin",
|
||||
"vin": {
|
||||
"title": "Şase Çözme",
|
||||
"description": "Müşteri araç bilgisini tam bilmiyor mu? Şase numarasını girin — marka, model, motor, donanım saniyeler içinde karşınızda.",
|
||||
"bullet1": "Marka, model ve yıl tespiti",
|
||||
"bullet2": "Motor ve şanzıman detayları",
|
||||
"bullet3": "Donanım paketi bilgileri",
|
||||
"mockupTitle": "Şase Çözme — Araç Bilgileri",
|
||||
"labelMake": "Marka",
|
||||
"labelModel": "Model",
|
||||
"labelYear": "Yıl",
|
||||
"labelEngine": "Motor",
|
||||
"labelBody": "Kasa",
|
||||
"valueBody": "Hatchback"
|
||||
},
|
||||
"schema": {
|
||||
"title": "İnteraktif Şema",
|
||||
"description": "Parça kataloglarında kaybolmak yerine, tıklanabilir diyagramlarda parçayı görsel olarak bulun.",
|
||||
"bullet1": "Tıklanabilir parça diyagramları",
|
||||
"bullet2": "Zoom & pan navigasyon",
|
||||
"bullet3": "Alt kategori derinliği",
|
||||
"mockupTitle": "İnteraktif Şema — Motor Bölgesi"
|
||||
},
|
||||
"oem": {
|
||||
"title": "OEM Parça Numaraları",
|
||||
"description": "Birden fazla katalogda çapraz sorgulama yaparak her zaman en güncel ve doğru OEM kodlarını sunar — yanlış parça, iade derdi tarih olur.",
|
||||
"bullet1": "Çoklu katalog çapraz sorgulama",
|
||||
"bullet2": "Her zaman güncel OEM kodları",
|
||||
"bullet3": "Fiyat karşılaştırma",
|
||||
"mockupTitle": "OEM Katalog — Parça Listesi",
|
||||
"part1": "Klima Kompresörü",
|
||||
"part2": "Kalorifer Motoru",
|
||||
"part3": "Ön Fren Balatası"
|
||||
}
|
||||
},
|
||||
"compare": {
|
||||
"badge": "Karşılaştırma",
|
||||
"title": "Manuel Yöntem vs Sase.tr",
|
||||
"manualHeader": "Manuel",
|
||||
"saseHeader": "Sase.tr",
|
||||
"rows": {
|
||||
"0": {
|
||||
"label": "Parça arama süresi",
|
||||
"manual": "~15 dakika",
|
||||
"sase": "30 saniye"
|
||||
},
|
||||
"1": {
|
||||
"label": "Yanlış parça riski",
|
||||
"manual": "Yüksek",
|
||||
"sase": "Sıfıra yakın"
|
||||
},
|
||||
"2": {
|
||||
"label": "OEM kodu bulma",
|
||||
"manual": "Tek katalog, güncel olmayabilir",
|
||||
"sase": "Çoklu katalog çapraz sorgu"
|
||||
},
|
||||
"3": {
|
||||
"label": "Fiyat karşılaştırma",
|
||||
"manual": "İmkansız",
|
||||
"sase": "Tek ekranda"
|
||||
},
|
||||
"4": {
|
||||
"label": "Aylık zaman tasarrufu",
|
||||
"manual": "—",
|
||||
"sase": "~40 saat"
|
||||
}
|
||||
}
|
||||
},
|
||||
"howItWorks": {
|
||||
"badge": "Nasıl Çalışır",
|
||||
"title": "3 Adımda Orijinal Parçaya Ulaşın",
|
||||
"items": {
|
||||
"0": {
|
||||
"title": "Şase Girin",
|
||||
"description": "17 haneli şase numarasını girin, araç anında tanımlansın."
|
||||
},
|
||||
"1": {
|
||||
"title": "27 Marka",
|
||||
"description": "Avrupa, Asya ve Amerikan markaları tek çatı altında."
|
||||
},
|
||||
"2": {
|
||||
"title": "Kategorileri Seçin",
|
||||
"description": "Motor, şasi, elektrik... İstediğiniz parça kategorisine dalın."
|
||||
},
|
||||
"3": {
|
||||
"title": "Şema İnceleyin",
|
||||
"description": "İnteraktif teknik diyagramlarda parça konumlarını görsel olarak keşfedin."
|
||||
},
|
||||
"4": {
|
||||
"title": "OEM Kod Alın",
|
||||
"description": "Orijinal üretici parça numarasını kopyalayın, siparişe hazır olun."
|
||||
},
|
||||
"5": {
|
||||
"title": "Sipariş Verin",
|
||||
"description": "Doğru parça numarasıyla tedarikçilere hızlıca sipariş oluşturun."
|
||||
}
|
||||
}
|
||||
},
|
||||
"useCases": {
|
||||
"badge": "Kullanım Alanları",
|
||||
"title": "Kimler İçin?",
|
||||
"items": {
|
||||
"0": {
|
||||
"title": "Yedek Parça Arama",
|
||||
"description": "Şase numarası ile araç modelinize uygun orijinal parçaları anında bulun."
|
||||
},
|
||||
"1": {
|
||||
"title": "Orijinal Parça Numarası",
|
||||
"description": "OEM parça kodlarına erişerek doğru parçayı ilk seferde sipariş edin."
|
||||
},
|
||||
"2": {
|
||||
"title": "Araç Tanımlama",
|
||||
"description": "Şase çözme ile aracın tüm teknik detaylarını ve donanım bilgilerini öğrenin."
|
||||
},
|
||||
"3": {
|
||||
"title": "Çıkma Parça Doğrulama",
|
||||
"description": "OEM kodlarıyla çıkma parçaların orijinalliğini ve uyumluluğunu kontrol edin."
|
||||
},
|
||||
"4": {
|
||||
"title": "Servis & Bakım",
|
||||
"description": "Bakım planlarına uygun parça listeleriyle servis süreçlerini hızlandırın."
|
||||
}
|
||||
},
|
||||
"ctaCard": {
|
||||
"title": "Farklı bir ihtiyacınız mı var?",
|
||||
"description": "Özel entegrasyon veya toplu sorgulama ihtiyacınız varsa bize ulaşın.",
|
||||
"cta": "Bize Ulaşın"
|
||||
}
|
||||
},
|
||||
"stats": {
|
||||
"badge": "Platform",
|
||||
"titleLine1": "Rakamlarla",
|
||||
"titleLine2": "Sase.tr",
|
||||
"items": {
|
||||
"0": { "value": "1.2sn", "label": "Ortalama sorgu süresi" },
|
||||
"1": { "value": "27", "label": "Desteklenen marka" },
|
||||
"2": { "value": "1M+", "label": "OEM parça numarası" },
|
||||
"3": { "value": "%99.9", "label": "Platform erişilebilirlik" }
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Tüm Veriler, Tek Platform",
|
||||
"subtitle": "Şase çözme, parça kataloğu ve interaktif şemalar tek bir arayüzde.",
|
||||
"frameTitle": "sase.tr/dashboard",
|
||||
"loading": "Yükleniyor…",
|
||||
"floatVin": "Şase Arama",
|
||||
"floatSchema": "İnteraktif Şema",
|
||||
"floatOem": "OEM Kodları"
|
||||
},
|
||||
"dashboardDemo": {
|
||||
"sidebar": {
|
||||
"dashboard": "Dashboard",
|
||||
"vinSearch": "Şase Arama",
|
||||
"catalog": "Katalog",
|
||||
"diagrams": "Şemalar",
|
||||
"settings": "Ayarlar"
|
||||
},
|
||||
"categories": {
|
||||
"engine": "Motor",
|
||||
"chassis": "Şasi",
|
||||
"electrical": "Elektrik",
|
||||
"body": "Karoseri",
|
||||
"brakes": "Fren"
|
||||
},
|
||||
"parts": {
|
||||
"acCompressor": "Klima Kompresörü",
|
||||
"brakePads": "Fren Balatası",
|
||||
"heaterBlower": "Kalorifer Motoru"
|
||||
},
|
||||
"vinPlaceholder": "Şase numarası girin...",
|
||||
"categorySelectTitle": "Kategori Seçin",
|
||||
"schemaViewTitle": "Motor — Şema Görünümü",
|
||||
"oemListTitle": "OEM Parça Listesi"
|
||||
},
|
||||
"ecommerceDemo": {
|
||||
"products": {
|
||||
"brakePads": "Fren Balatası",
|
||||
"acCompressor": "Klima Kompresörü",
|
||||
"shockAbsorber": "Amortisör",
|
||||
"oilFilter": "Yağ Filtresi",
|
||||
"wiperMotor": "Silecek Motoru",
|
||||
"headlight": "Ön Far"
|
||||
},
|
||||
"searchPlaceholder": "Parça ara...",
|
||||
"preReturnBadge": "%23 iade oranı",
|
||||
"addToCart": "Sepete Ekle",
|
||||
"cartAddedToast": "Fren Balatası sepete eklendi",
|
||||
"conversionOverlay": "Dönüşüm: %94",
|
||||
"statReturnRate": "İade oranı",
|
||||
"statConversionRate": "Dönüşüm oranı",
|
||||
"statSatisfaction": "Müşteri memnuniyeti"
|
||||
},
|
||||
"ecommerce": {
|
||||
"badge": "E-Ticaret Entegrasyonu",
|
||||
"title": "Kendi Sitenize Şase Çözme Gücü Katın",
|
||||
"subtitle": "Müşterileriniz yanlış parça sipariş edip iade mi açıyor? Sase.tr entegrasyonu ile VIN bazlı filtreleme ekleyin — iadeler düşsün, dönüşüm artsın.",
|
||||
"frameTitle": "otoyedekparca.co — Sase.tr Entegrasyonu",
|
||||
"loading": "Yükleniyor…",
|
||||
"easyBadge": "Kolay Entegrasyon",
|
||||
"cardTitle": "Birkaç Satır Kod, Büyük Fark",
|
||||
"cardBody": "E-ticaret sitenize Sase.tr'nin VIN çözme motorunu entegre edin. Müşterileriniz şase numarasını girsin — sadece araçlarına uygun parçalar listelensin.",
|
||||
"bullet1": "API & Widget — Birkaç satır kodla sitenize entegre edin",
|
||||
"bullet2": "VIN Bazlı Filtreleme — Sadece uyumlu parçalar görünsün",
|
||||
"bullet3": "İade Oranını Düşürün — Doğru parça, ilk seferde",
|
||||
"bullet4": "White-Label — Widget sitenizin tasarımına uyum sağlar",
|
||||
"cta": "İletişime Geçin",
|
||||
"stats": {
|
||||
"0": { "value": "%42", "label": "Daha Az İade" },
|
||||
"1": { "value": "%35", "label": "Daha Yüksek Dönüşüm" },
|
||||
"2": { "value": "<30dk", "label": "Entegrasyon Süresi" }
|
||||
}
|
||||
},
|
||||
"testimonials": {
|
||||
"badge": "Müşteri Yorumları",
|
||||
"title": "İşletme Sahipleri Ne Diyor?",
|
||||
"items": {
|
||||
"0": {
|
||||
"name": "Ahmet Y.",
|
||||
"role": "Yedek Parça Mağazası Sahibi",
|
||||
"text": "Eskiden bir parça aramak için 3-4 katalog açıyordum. Sase.tr ile şase numarasını giriyorum, 30 saniyede doğru OEM kodunu buluyorum. İade oranımız %80 düştü."
|
||||
},
|
||||
"1": {
|
||||
"name": "Mehmet K.",
|
||||
"role": "Oto Servis İşletmecisi",
|
||||
"text": "Sase.tr'ye geçtiğimizden beri yanlış parça siparişlerimiz neredeyse sıfıra indi. Aylık 40 saatin üzerinde zaman tasarrufu sağlıyoruz."
|
||||
},
|
||||
"2": {
|
||||
"name": "Özge D.",
|
||||
"role": "E-Ticaret Yöneticisi",
|
||||
"text": "Online mağazamıza VIN entegrasyonu ekledik. Müşteriler şase numarasını giriyor, sadece uyumlu parçalar listeleniyor. İade oranımız %42 düştü."
|
||||
},
|
||||
"3": {
|
||||
"name": "Burak T.",
|
||||
"role": "Filo Yöneticisi",
|
||||
"text": "200+ araçlık filomuz için parça yönetimi çok kolay hale geldi. Özellikle interaktif şema özelliği montaj sürecini çok hızlandırıyor."
|
||||
}
|
||||
}
|
||||
},
|
||||
"pricing": {
|
||||
"badge": "Fiyatlandırma",
|
||||
"titleLine1": "Günde 7 TL'den",
|
||||
"titleLine2": "başlayan fiyatlar.",
|
||||
"subtitle": "Tek bir yanlış parça iadesinin maliyetinden daha az. Tüm planlarda 30 gün ücretsiz deneme var, kart bilgisi gerekmez.",
|
||||
"perMonth": "/ay",
|
||||
"currency": "TL",
|
||||
"yearlyPrice": "yıllık {amount} TL",
|
||||
"tryFree": "30 gün ücretsiz dene",
|
||||
"plans": {
|
||||
"brand1": {
|
||||
"name": "1 Marka",
|
||||
"description": "Tek marka için erişim",
|
||||
"feature1": "1 marka seçimi",
|
||||
"feature2": "Sınırsız VIN arama",
|
||||
"feature3": "Parça kataloğu",
|
||||
"feature4": "Şema görüntüleyici"
|
||||
},
|
||||
"brand2": {
|
||||
"name": "2 Marka",
|
||||
"description": "İki farklı marka",
|
||||
"feature1": "2 marka seçimi",
|
||||
"feature2": "Sınırsız VIN arama",
|
||||
"feature3": "Parça kataloğu",
|
||||
"feature4": "Öncelikli destek"
|
||||
},
|
||||
"brand3": {
|
||||
"name": "3 Marka",
|
||||
"description": "Üç marka kapsamlı",
|
||||
"feature1": "3 marka seçimi",
|
||||
"feature2": "Sınırsız VIN arama",
|
||||
"feature3": "Parça kataloğu",
|
||||
"feature4": "Öncelikli destek"
|
||||
}
|
||||
},
|
||||
"full": {
|
||||
"badge": "En çok tercih edilen",
|
||||
"name": "Full Paket",
|
||||
"description": "Tüm markalara sınırsız erişim. Tek bir aboneliğe sığdırdık.",
|
||||
"priceUnit": "TL/ay",
|
||||
"yearlyNote": "yıllık 9.990 TL — ayda 832 TL'ye denk gelir",
|
||||
"ctaTry": "30 gün ücretsiz dene",
|
||||
"ctaFineprint": "Kart bilgisi gerekmez. İstediğin zaman iptal et.",
|
||||
"included": "Pakete dahil",
|
||||
"feature1": "Tüm 27 markaya erişim",
|
||||
"feature2": "Sınırsız VIN arama",
|
||||
"feature3": "OEM parça kataloğu",
|
||||
"feature4": "İnteraktif şema görüntüleyici",
|
||||
"feature5": "Geçmiş sorgular & favoriler",
|
||||
"feature6": "Öncelikli e-posta desteği",
|
||||
"feature7": "API erişimi (talep üzerine)",
|
||||
"feature8": "Toplu sorgu özelliği"
|
||||
},
|
||||
"detailedComparison": "Detaylı karşılaştırmayı gör"
|
||||
},
|
||||
"faq": {
|
||||
"badge": "SSS",
|
||||
"title": "Sık Sorulan Sorular",
|
||||
"items": {
|
||||
"0": {
|
||||
"q": "Hangi markaları destekliyorsunuz?",
|
||||
"a": "Şu an 27 otomobil markası destekleniyor ve liste sürekli genişliyor. Şase numaranızı arama kutusuna girerek aracınızın desteklenip desteklenmediğini hemen görebilirsiniz."
|
||||
},
|
||||
"1": {
|
||||
"q": "Verilerim güvende mi?",
|
||||
"a": "Tüm trafik SSL ile şifrelenir ve verileriniz KVKK'ya uygun şekilde işlenir. Sorgu geçmişiniz yalnızca size özeldir."
|
||||
},
|
||||
"2": {
|
||||
"q": "Kart bilgisi gerekiyor mu?",
|
||||
"a": "Hayır. 30 günlük ücretsiz deneme için kredi kartı bilgisi istemiyoruz; dilediğiniz an vazgeçebilirsiniz."
|
||||
},
|
||||
"3": {
|
||||
"q": "İstediğim zaman iptal edebilir miyim?",
|
||||
"a": "Evet, taahhüt yok. Aboneliğinizi panelden tek tıkla iptal edebilirsiniz."
|
||||
},
|
||||
"4": {
|
||||
"q": "Mevcut e-ticaret veya servis sistemime entegre olur mu?",
|
||||
"a": "Full Paket'te API erişimi ve toplu sorgu ile sistemlerinize entegre edebilirsiniz (talep üzerine kurulum desteği)."
|
||||
},
|
||||
"5": {
|
||||
"q": "Şase numarasını bilmiyorsam ne olur?",
|
||||
"a": "Marka ve model üzerinden de katalogda gezebilir, ya da ana sayfadaki örnek aramayı deneyerek platformu test edebilirsiniz."
|
||||
}
|
||||
}
|
||||
},
|
||||
"finalCta": {
|
||||
"title": "Her Yanlış Parça Size 200-500 TL'ye Mal Oluyor",
|
||||
"subtitle": "İade kargosu, müşteri kaybı, zaman kaybı. Şase çözme ile ilk seferde doğru parçayı bulun.",
|
||||
"cta": "30 Gün Ücretsiz Başla — Kart Gerekmez",
|
||||
"trust": "KVKK uyumlu · SSL şifreli · Kart bilgisi gerekmez"
|
||||
},
|
||||
"referral": {
|
||||
"title": "Arkadaşını Davet Et",
|
||||
"description": "3 davet = +7 gün ücretsiz, 5 davet = +1 ay ücretsiz",
|
||||
"cta": "Davet Kodunuzu Alın"
|
||||
},
|
||||
"errors": {
|
||||
"decodeGeneric": "Bir hata oluştu. Lütfen tekrar deneyin."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,17 +21,38 @@ function getColors(isDark: boolean) {
|
||||
}
|
||||
|
||||
const VIN_TEXT = "WVWZZZ1JZ3W...";
|
||||
const SIDEBAR_ITEMS = ["Dashboard", "Şase Arama", "Katalog", "Şemalar", "Ayarlar"];
|
||||
const CATEGORIES = ["Motor", "Şasi", "Elektrik", "Karoseri", "Fren"];
|
||||
const PARTS = [
|
||||
{ code: "1J0 820 803F", name: "Klima Kompresörü" },
|
||||
{ code: "1J0 698 151G", name: "Fren Balatası" },
|
||||
{ code: "1J0 819 031A", name: "Kalorifer Motoru" },
|
||||
];
|
||||
|
||||
// All translatable copy threaded through the Remotion composition. Parent
|
||||
// supplies labels from useTranslation(); falls back to TR if nothing is passed
|
||||
// (keeps the standalone Remotion Studio preview working).
|
||||
export interface DashboardDemoLabels {
|
||||
sidebar: [string, string, string, string, string];
|
||||
categories: [string, string, string, string, string];
|
||||
parts: [string, string, string];
|
||||
vinPlaceholder: string;
|
||||
categorySelectTitle: string;
|
||||
schemaViewTitle: string;
|
||||
oemListTitle: string;
|
||||
}
|
||||
|
||||
const DEFAULT_LABELS: DashboardDemoLabels = {
|
||||
sidebar: ["Dashboard", "Şase Arama", "Katalog", "Şemalar", "Ayarlar"],
|
||||
categories: ["Motor", "Şasi", "Elektrik", "Karoseri", "Fren"],
|
||||
parts: ["Klima Kompresörü", "Fren Balatası", "Kalorifer Motoru"],
|
||||
vinPlaceholder: "Şase numarası girin...",
|
||||
categorySelectTitle: "Kategori Seçin",
|
||||
schemaViewTitle: "Motor — Şema Görünümü",
|
||||
oemListTitle: "OEM Parça Listesi",
|
||||
};
|
||||
|
||||
const PART_CODES = ["1J0 820 803F", "1J0 698 151G", "1J0 819 031A"];
|
||||
|
||||
// ─── Scene 1: VIN Input ─────────────────────────────────────────────────────
|
||||
|
||||
const VinInputScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
const VinInputScene: React.FC<{ isDark: boolean; labels: DashboardDemoLabels }> = ({
|
||||
isDark,
|
||||
labels,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
@@ -61,7 +82,7 @@ const VinInputScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
transform: `translateX(${sidebarX}px)`,
|
||||
}}
|
||||
>
|
||||
{SIDEBAR_ITEMS.map((item, i) => (
|
||||
{labels.sidebar.map((item, i) => (
|
||||
<div
|
||||
key={item}
|
||||
style={{
|
||||
@@ -114,7 +135,7 @@ const VinInputScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
color: typedText ? c.fg : c.mutedFg,
|
||||
}}
|
||||
>
|
||||
{typedText || "Şase numarası girin..."}
|
||||
{typedText || labels.vinPlaceholder}
|
||||
</span>
|
||||
{/* Blinking cursor */}
|
||||
{frame > typeStart && charsToShow < VIN_TEXT.length && (
|
||||
@@ -135,7 +156,10 @@ const VinInputScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
|
||||
// ─── Scene 2: Vehicle Info ──────────────────────────────────────────────────
|
||||
|
||||
const VehicleInfoScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
const VehicleInfoScene: React.FC<{ isDark: boolean; labels: DashboardDemoLabels }> = ({
|
||||
isDark,
|
||||
labels,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
@@ -159,7 +183,7 @@ const VehicleInfoScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
padding: 12,
|
||||
}}
|
||||
>
|
||||
{SIDEBAR_ITEMS.map((item, i) => (
|
||||
{labels.sidebar.map((item, i) => (
|
||||
<div
|
||||
key={item}
|
||||
style={{
|
||||
@@ -269,7 +293,10 @@ const VehicleInfoScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
|
||||
// ─── Scene 3: Category Selection ────────────────────────────────────────────
|
||||
|
||||
const CategoryScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
const CategoryScene: React.FC<{ isDark: boolean; labels: DashboardDemoLabels }> = ({
|
||||
isDark,
|
||||
labels,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
@@ -285,7 +312,7 @@ const CategoryScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
padding: 12,
|
||||
}}
|
||||
>
|
||||
{SIDEBAR_ITEMS.map((item, i) => (
|
||||
{labels.sidebar.map((item, i) => (
|
||||
<div
|
||||
key={item}
|
||||
style={{
|
||||
@@ -314,10 +341,10 @@ const CategoryScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
fontFamily: "system-ui, sans-serif",
|
||||
}}
|
||||
>
|
||||
Kategori Seçin
|
||||
{labels.categorySelectTitle}
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
|
||||
{CATEGORIES.map((cat, i) => {
|
||||
{labels.categories.map((cat, i) => {
|
||||
const delay = i * 5;
|
||||
const itemSpring = spring({
|
||||
frame: frame - delay,
|
||||
@@ -364,7 +391,10 @@ const CategoryScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
|
||||
// ─── Scene 4: Schema View ───────────────────────────────────────────────────
|
||||
|
||||
const SchemaViewScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
const SchemaViewScene: React.FC<{ isDark: boolean; labels: DashboardDemoLabels }> = ({
|
||||
isDark,
|
||||
labels,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
@@ -387,7 +417,7 @@ const SchemaViewScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
padding: 12,
|
||||
}}
|
||||
>
|
||||
{SIDEBAR_ITEMS.map((item, i) => (
|
||||
{labels.sidebar.map((item, i) => (
|
||||
<div
|
||||
key={item}
|
||||
style={{
|
||||
@@ -416,7 +446,7 @@ const SchemaViewScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
fontFamily: "system-ui, sans-serif",
|
||||
}}
|
||||
>
|
||||
Motor — Şema Görünümü
|
||||
{labels.schemaViewTitle}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
@@ -515,7 +545,10 @@ const SchemaViewScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
|
||||
// ─── Scene 5: OEM Codes ─────────────────────────────────────────────────────
|
||||
|
||||
const OemCodesScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
const OemCodesScene: React.FC<{ isDark: boolean; labels: DashboardDemoLabels }> = ({
|
||||
isDark,
|
||||
labels,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
@@ -537,7 +570,7 @@ const OemCodesScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
padding: 12,
|
||||
}}
|
||||
>
|
||||
{SIDEBAR_ITEMS.map((item, i) => (
|
||||
{labels.sidebar.map((item, i) => (
|
||||
<div
|
||||
key={item}
|
||||
style={{
|
||||
@@ -566,10 +599,12 @@ const OemCodesScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
fontFamily: "system-ui, sans-serif",
|
||||
}}
|
||||
>
|
||||
OEM Parça Listesi
|
||||
{labels.oemListTitle}
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
|
||||
{PARTS.map((part, i) => {
|
||||
{PART_CODES.map((code, i) => {
|
||||
const partName = labels.parts[i] ?? "";
|
||||
const part = { code, name: partName };
|
||||
const delay = i * 8;
|
||||
const slideSpring = spring({
|
||||
frame: frame - delay,
|
||||
@@ -634,25 +669,28 @@ const OemCodesScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
|
||||
// ─── Main Composition ───────────────────────────────────────────────────────
|
||||
|
||||
export const DashboardDemo: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
export const DashboardDemo: React.FC<{ isDark: boolean; labels?: DashboardDemoLabels }> = ({
|
||||
isDark,
|
||||
labels = DEFAULT_LABELS,
|
||||
}) => {
|
||||
const c = getColors(isDark);
|
||||
|
||||
return (
|
||||
<AbsoluteFill style={{ backgroundColor: c.bg }}>
|
||||
<Sequence from={0} durationInFrames={90}>
|
||||
<VinInputScene isDark={isDark} />
|
||||
<VinInputScene isDark={isDark} labels={labels} />
|
||||
</Sequence>
|
||||
<Sequence from={90} durationInFrames={90}>
|
||||
<VehicleInfoScene isDark={isDark} />
|
||||
<VehicleInfoScene isDark={isDark} labels={labels} />
|
||||
</Sequence>
|
||||
<Sequence from={180} durationInFrames={90}>
|
||||
<CategoryScene isDark={isDark} />
|
||||
<CategoryScene isDark={isDark} labels={labels} />
|
||||
</Sequence>
|
||||
<Sequence from={270} durationInFrames={90}>
|
||||
<SchemaViewScene isDark={isDark} />
|
||||
<SchemaViewScene isDark={isDark} labels={labels} />
|
||||
</Sequence>
|
||||
<Sequence from={360} durationInFrames={90}>
|
||||
<OemCodesScene isDark={isDark} />
|
||||
<OemCodesScene isDark={isDark} labels={labels} />
|
||||
</Sequence>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
|
||||
@@ -23,20 +23,57 @@ function getColors(isDark: boolean) {
|
||||
|
||||
const STORE_NAME = "OtoYedekParca.co";
|
||||
const VIN_DISPLAY = "WVWZZZ1JZ3W...";
|
||||
const PRODUCTS = [
|
||||
{ name: "Fren Balatası", price: "450 TL", compatible: true },
|
||||
{ name: "Klima Kompresörü", price: "1.200 TL", compatible: true },
|
||||
{ name: "Amortisör", price: "680 TL", compatible: false },
|
||||
{ name: "Yağ Filtresi", price: "120 TL", compatible: true },
|
||||
{ name: "Silecek Motoru", price: "350 TL", compatible: false },
|
||||
{ name: "Ön Far", price: "890 TL", compatible: false },
|
||||
];
|
||||
|
||||
export interface EcommerceDemoLabels {
|
||||
productNames: [string, string, string, string, string, string];
|
||||
searchPlaceholder: string;
|
||||
preReturnBadge: string;
|
||||
addToCart: string;
|
||||
cartAddedToast: string;
|
||||
conversionOverlay: string;
|
||||
statReturnRate: string;
|
||||
statConversionRate: string;
|
||||
statSatisfaction: string;
|
||||
}
|
||||
|
||||
const DEFAULT_LABELS: EcommerceDemoLabels = {
|
||||
productNames: [
|
||||
"Fren Balatası",
|
||||
"Klima Kompresörü",
|
||||
"Amortisör",
|
||||
"Yağ Filtresi",
|
||||
"Silecek Motoru",
|
||||
"Ön Far",
|
||||
],
|
||||
searchPlaceholder: "Parça ara...",
|
||||
preReturnBadge: "%23 iade oranı",
|
||||
addToCart: "Sepete Ekle",
|
||||
cartAddedToast: "Fren Balatası sepete eklendi",
|
||||
conversionOverlay: "Dönüşüm: %94",
|
||||
statReturnRate: "İade oranı",
|
||||
statConversionRate: "Dönüşüm oranı",
|
||||
statSatisfaction: "Müşteri memnuniyeti",
|
||||
};
|
||||
|
||||
const PRODUCT_PRICES = ["450 TL", "1.200 TL", "680 TL", "120 TL", "350 TL", "890 TL"];
|
||||
const PRODUCT_COMPAT = [true, true, false, true, false, false];
|
||||
|
||||
function buildProducts(labels: EcommerceDemoLabels) {
|
||||
return labels.productNames.map((name, i) => ({
|
||||
name,
|
||||
price: PRODUCT_PRICES[i],
|
||||
compatible: PRODUCT_COMPAT[i],
|
||||
}));
|
||||
}
|
||||
|
||||
const FONT = "system-ui, sans-serif";
|
||||
|
||||
// ─── Scene 1: Storefront ─────────────────────────────────────────────────────
|
||||
|
||||
const StorefrontScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
const StorefrontScene: React.FC<{ isDark: boolean; labels: EcommerceDemoLabels }> = ({
|
||||
isDark,
|
||||
labels,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
@@ -106,7 +143,7 @@ const StorefrontScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
fontFamily: FONT,
|
||||
}}
|
||||
>
|
||||
Parça ara...
|
||||
{labels.searchPlaceholder}
|
||||
</span>
|
||||
</div>
|
||||
{/* Cart icon */}
|
||||
@@ -169,7 +206,7 @@ const StorefrontScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
{PRODUCTS.map((product, i) => {
|
||||
{buildProducts(labels).map((product, i) => {
|
||||
const delay = i * 5;
|
||||
const itemSpring = spring({
|
||||
frame: frame - delay,
|
||||
@@ -253,7 +290,7 @@ const StorefrontScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
%23 iade oranı
|
||||
{labels.preReturnBadge}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -263,7 +300,10 @@ const StorefrontScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
|
||||
// ─── Scene 2: Widget Integration ────────────────────────────────────────────
|
||||
|
||||
const WidgetIntegrationScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
const WidgetIntegrationScene: React.FC<{ isDark: boolean; labels: EcommerceDemoLabels }> = ({
|
||||
isDark,
|
||||
labels,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
@@ -357,7 +397,7 @@ const WidgetIntegrationScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
Parça ara...
|
||||
{labels.searchPlaceholder}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -466,7 +506,7 @@ const WidgetIntegrationScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
{PRODUCTS.map((product) => (
|
||||
{buildProducts(labels).map((product) => (
|
||||
<div
|
||||
key={product.name}
|
||||
style={{
|
||||
@@ -516,7 +556,10 @@ const WidgetIntegrationScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
|
||||
// ─── Scene 3: VIN Filter ────────────────────────────────────────────────────
|
||||
|
||||
const VinFilterScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
const VinFilterScene: React.FC<{ isDark: boolean; labels: EcommerceDemoLabels }> = ({
|
||||
isDark,
|
||||
labels,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
@@ -605,7 +648,7 @@ const VinFilterScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
Parça ara...
|
||||
{labels.searchPlaceholder}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -722,7 +765,7 @@ const VinFilterScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
{PRODUCTS.map((product, i) => {
|
||||
{buildProducts(labels).map((product, i) => {
|
||||
const isIncompatible = incompatibleIndices.includes(i);
|
||||
const cardOpacity = isIncompatible ? interpolate(filterProgress, [0, 1], [1, 0.15]) : 1;
|
||||
const borderColor = !isIncompatible && filterProgress > 0.5 ? c.emerald : c.border;
|
||||
@@ -809,7 +852,10 @@ const VinFilterScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
|
||||
// ─── Scene 4: Add To Cart ───────────────────────────────────────────────────
|
||||
|
||||
const AddToCartScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
const AddToCartScene: React.FC<{ isDark: boolean; labels: EcommerceDemoLabels }> = ({
|
||||
isDark,
|
||||
labels,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
@@ -988,7 +1034,7 @@ const AddToCartScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
Fren Balatası sepete eklendi
|
||||
{labels.cartAddedToast}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1022,7 +1068,7 @@ const AddToCartScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
{PRODUCTS.map((product, i) => {
|
||||
{buildProducts(labels).map((product, i) => {
|
||||
const isIncompatible = incompatibleIndices.includes(i);
|
||||
return (
|
||||
<div
|
||||
@@ -1115,7 +1161,7 @@ const AddToCartScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Sepete Ekle
|
||||
{labels.addToCart}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1140,7 +1186,7 @@ const AddToCartScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Dönüşüm: %94
|
||||
{labels.conversionOverlay}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1150,15 +1196,18 @@ const AddToCartScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
|
||||
// ─── Scene 5: Stats Overlay ─────────────────────────────────────────────────
|
||||
|
||||
const StatsOverlayScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
const StatsOverlayScene: React.FC<{ isDark: boolean; labels: EcommerceDemoLabels }> = ({
|
||||
isDark,
|
||||
labels,
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
|
||||
const stats = [
|
||||
{ arrow: "↓", value: "%42", label: "İade oranı" },
|
||||
{ arrow: "↑", value: "%35", label: "Dönüşüm oranı" },
|
||||
{ arrow: "↑", value: "%98", label: "Müşteri memnuniyeti" },
|
||||
{ arrow: "↓", value: "%42", label: labels.statReturnRate },
|
||||
{ arrow: "↑", value: "%35", label: labels.statConversionRate },
|
||||
{ arrow: "↑", value: "%98", label: labels.statSatisfaction },
|
||||
];
|
||||
|
||||
// Fade out at end
|
||||
@@ -1231,25 +1280,28 @@ const StatsOverlayScene: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
|
||||
// ─── Main Composition ───────────────────────────────────────────────────────
|
||||
|
||||
export const EcommerceDemo: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
export const EcommerceDemo: React.FC<{ isDark: boolean; labels?: EcommerceDemoLabels }> = ({
|
||||
isDark,
|
||||
labels = DEFAULT_LABELS,
|
||||
}) => {
|
||||
const c = getColors(isDark);
|
||||
|
||||
return (
|
||||
<AbsoluteFill style={{ backgroundColor: c.bg }}>
|
||||
<Sequence from={0} durationInFrames={90}>
|
||||
<StorefrontScene isDark={isDark} />
|
||||
<StorefrontScene isDark={isDark} labels={labels} />
|
||||
</Sequence>
|
||||
<Sequence from={90} durationInFrames={90}>
|
||||
<WidgetIntegrationScene isDark={isDark} />
|
||||
<WidgetIntegrationScene isDark={isDark} labels={labels} />
|
||||
</Sequence>
|
||||
<Sequence from={180} durationInFrames={90}>
|
||||
<VinFilterScene isDark={isDark} />
|
||||
<VinFilterScene isDark={isDark} labels={labels} />
|
||||
</Sequence>
|
||||
<Sequence from={270} durationInFrames={90}>
|
||||
<AddToCartScene isDark={isDark} />
|
||||
<AddToCartScene isDark={isDark} labels={labels} />
|
||||
</Sequence>
|
||||
<Sequence from={360} durationInFrames={60}>
|
||||
<StatsOverlayScene isDark={isDark} />
|
||||
<StatsOverlayScene isDark={isDark} labels={labels} />
|
||||
</Sequence>
|
||||
</AbsoluteFill>
|
||||
);
|
||||
|
||||
@@ -21,7 +21,10 @@ function getColors(isDark: boolean) {
|
||||
};
|
||||
}
|
||||
|
||||
export const SchemaDemo: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
export const SchemaDemo: React.FC<{ isDark: boolean; partLabel?: string }> = ({
|
||||
isDark,
|
||||
partLabel = "Klima Kompresörü",
|
||||
}) => {
|
||||
const frame = useCurrentFrame();
|
||||
const { fps } = useVideoConfig();
|
||||
const c = getColors(isDark);
|
||||
@@ -175,7 +178,7 @@ export const SchemaDemo: React.FC<{ isDark: boolean }> = ({ isDark }) => {
|
||||
marginTop: 2,
|
||||
}}
|
||||
>
|
||||
Klima Kompresörü
|
||||
{partLabel}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { api } from "@/lib/api-client";
|
||||
import { resetChatwootUser, setChatwootUser } from "@/lib/chatwoot";
|
||||
import { useTranslation } from "@/lib/i18n";
|
||||
import { trackPageView as trackMetaPageView } from "@/lib/meta-pixel";
|
||||
import { capturePageView, identifyUser, resetUser } from "@/lib/posthog";
|
||||
import { Toaster } from "@/lib/toast";
|
||||
@@ -92,6 +93,7 @@ function applyTheme(theme: "light" | "dark" | "system") {
|
||||
function RootComponent() {
|
||||
const location = useLocation();
|
||||
const { user } = useAuth();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Pageview tracking
|
||||
useEffect(() => {
|
||||
@@ -135,7 +137,7 @@ function RootComponent() {
|
||||
return (
|
||||
<>
|
||||
<a href="#main-content" className="skip-link">
|
||||
İçeriğe atla
|
||||
{t("a11y.skipToContent")}
|
||||
</a>
|
||||
<Outlet />
|
||||
<Toaster position="top-center" />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user