Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m7s
Vorgabe war: nur dorthin liefern, wo keine Zollabwicklung noetig ist. Die
EU-Einstellung erfuellt das auf Laenderebene — die Union ist eine
Zollunion, eine Sendung von Deutschland nach Frankreich braucht keine
Zollanmeldung. Der Laendercode allein genuegt aber nicht.
Einige Gebiete gehoeren zu einem EU-Mitgliedstaat, liegen aber ausserhalb
des Zoll- und/oder Steuergebiets der Union. Dorthin ist jede Sendung eine
Ausfuhr mit Zollinhaltserklaerung, Handelsrechnung und moeglichen
Einfuhrabgaben beim Empfaenger. Zwei davon liegen in DEUTSCHLAND:
Helgoland (27498) und Büsingen am Hochrhein (78266)
Sie waren also selbst bei der Einstellung "Nur Deutschland" betroffen.
Dazu kommen Kanarische Inseln, Ceuta, Melilla, die franzoesischen
Ueberseegebiete, Livigno, Campione d'Italia, Åland und der Berg Athos.
Erkennbar sind sie nur an der Postleitzahl. findCustomsExcludedArea
prueft sie je Land, denn dieselbe Ziffernfolge bedeutet anderswo etwas
anderes: "22" ist in Finnland Åland, in Italien die Provinz Como. Die
Checkout-Route weist solche Anschriften mit einer Meldung ab, die das
Gebiet benennt und auf die Abholung verweist.
Azoren und Madeira sind bewusst NICHT ausgenommen: sie gehoeren voll zum
Zoll- und Steuergebiet, dorthin gilt der normale Binnenversand. Ein Test
haelt das fest, damit sie nicht versehentlich mitgesperrt werden.
Die Hinweistexte auf "Versand und Zahlung" nennen die Ausnahmen jetzt
ausdruecklich — in beiden Einstellungen und beiden Sprachen.
8 neue Tests in shipping-region.test.ts, 1 in der Checkout-Route
("weist Helgoland ab, obwohl es in Deutschland liegt").
tsc: 0 Fehler. vitest: 359/359 gruen (zuvor 350). eslint: 0 Warnungen.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
1621 lines
75 KiB
TypeScript
1621 lines
75 KiB
TypeScript
import type { Dictionary } from "../dictionary-type";
|
||
|
||
export const en: Dictionary = {
|
||
common: {
|
||
siteDescription:
|
||
"Discover premium honey from our own apiary in Altenmünster. Small harvests, clear provenance, genuine beekeeping craft.",
|
||
categories: {
|
||
HONIG: "Honey",
|
||
KERZEN: "Beeswax candles",
|
||
PROPOLIS: "Propolis",
|
||
},
|
||
turnstile: {
|
||
loadError: "The security check could not be loaded.",
|
||
retry: "Try again",
|
||
},
|
||
},
|
||
nav: {
|
||
shop: "Shop",
|
||
harvests: "Our Harvests",
|
||
origin: "Origin",
|
||
apiary: "Apiary",
|
||
business: "For Businesses",
|
||
knowledge: "Knowledge",
|
||
contact: "Contact",
|
||
account: "My Account",
|
||
discoverHoney: "Discover Honey",
|
||
searchPlaceholder: "Search honey…",
|
||
openSearch: "Open search",
|
||
openMenu: "Open menu",
|
||
closeMenu: "Close menu",
|
||
openCart: (count) => (count > 0 ? `Open cart, ${count} item${count === 1 ? "" : "s"}` : "Open cart"),
|
||
skipToContent: "Skip to content",
|
||
},
|
||
footer: {
|
||
columnWabenhain: "Wabenhain",
|
||
columnShopping: "Shopping",
|
||
columnLegal: "Legal",
|
||
aboutUs: "About us",
|
||
businessCustomers: "Business customers",
|
||
trackOrder: "Track order",
|
||
tagline: "Honey from the Western Woods.",
|
||
bottomLine: "Harvested · bottled · shipped from our own apiary in Altenmünster",
|
||
socialAriaLabel: (platform) => `Wabenhain on ${platform}`,
|
||
},
|
||
legalTitles: {
|
||
impressum: "Legal Notice",
|
||
datenschutz: "Privacy Policy",
|
||
agb: "Terms & Conditions",
|
||
widerruf: "Right of Withdrawal",
|
||
versandZahlung: "Shipping & Payment",
|
||
cookieRichtlinie: "Cookie Policy",
|
||
},
|
||
cart: {
|
||
title: "Cart",
|
||
close: "Close",
|
||
empty: "Your cart is still empty.",
|
||
remove: "remove",
|
||
subtotal: "Subtotal",
|
||
checkout: "Checkout",
|
||
viewCart: "View cart",
|
||
},
|
||
orderStatusBadge: {
|
||
// Not "New": this status means the payment has not been confirmed yet —
|
||
// after an abandoned checkout it never will be.
|
||
NEU: "Awaiting payment",
|
||
BEZAHLT: "Paid",
|
||
IN_VORBEREITUNG: "Preparing",
|
||
VERSANDBEREIT: "Ready to ship",
|
||
VERSENDET: "Shipped",
|
||
ABHOLBEREIT: "Ready for pickup",
|
||
ABGEHOLT: "Picked up",
|
||
STORNIERT: "Cancelled",
|
||
},
|
||
orderStatusTimeline: {
|
||
ordered: "Ordered",
|
||
paid: "Paid",
|
||
preparing: "Preparing",
|
||
readyToShip: "Ready to ship",
|
||
shipped: "Shipped",
|
||
readyForPickup: "Ready for pickup",
|
||
pickedUp: "Picked up",
|
||
cancelled: "This order was cancelled.",
|
||
ariaLabel: "Order status",
|
||
},
|
||
ordersPage: {
|
||
backToAccount: "Back to account overview",
|
||
title: "My Orders",
|
||
emptyTitle: "You haven't placed any orders yet.",
|
||
emptyDescription: "Once you order, you'll find status updates, invoices, and a way to quickly reorder here.",
|
||
shippedOn: (date) => `Shipped on ${date}`,
|
||
shippingPending: "Shipping pending",
|
||
pickedUpOn: (date) => `Picked up on ${date}`,
|
||
pickupPending: "Pickup pending",
|
||
readySince: (date) => `Ready for pickup since ${date}`,
|
||
giftMessagePrefix: "Your gift message:",
|
||
viewInvoice: "View invoice",
|
||
requestReturn: "Request a return",
|
||
reorder: "Reorder",
|
||
returnStatusLabels: {
|
||
ANGEFRAGT: "Return requested",
|
||
ANGENOMMEN: "Return accepted",
|
||
ABGELEHNT: "Return declined",
|
||
ERSTATTET: "Refunded",
|
||
},
|
||
},
|
||
herkunftPage: {
|
||
eyebrow: "Origin",
|
||
title: "A landscape shapes its honey.",
|
||
subtitle: "The Altenmünster region and the Western Woods in the district of Augsburg are the origin of every Wabenhain harvest.",
|
||
aspects: [
|
||
{ title: "Landscape", text: "Forests, meadows, and orchard meadows shape the landscape around Altenmünster." },
|
||
{ title: "Vegetation", text: "From early bloomers to black locust to summer linden: the forage plants change throughout the year." },
|
||
{ title: "Seasons", text: "Every season offers its own nectar supply, and with it, its own honey character." },
|
||
{ title: "Bee sites", text: "Our colonies are located at selected, quiet sites throughout the region." },
|
||
],
|
||
privacyNote:
|
||
"Out of consideration for our bee colonies, we don't show exact locations, only the approximate area of our bee sites.",
|
||
},
|
||
imkereiPage: {
|
||
eyebrow: "About Wabenhain",
|
||
title: "Wabenhain grew out of a passion for bees, nature, and honest craft.",
|
||
paragraphs: [
|
||
"What began as a small passion for beekeeping is now a dedicated apiary with a clear stance: quality over volume, origin over anonymity.",
|
||
"We work with our own colonies in and around Altenmünster, watch over every season closely, and intervene only as much as healthy bee colonies and a good harvest require.",
|
||
"Small batches are a deliberate choice for us rather than a compromise: it's the only way to document every harvest fully, from hive to jar.",
|
||
"Behind it all is the conviction that honey is more than a sweetener: it's a reflection of the landscape it comes from.",
|
||
],
|
||
imageLabel: "The beekeeper at work",
|
||
},
|
||
unternehmenPage: {
|
||
eyebrow: "For Businesses",
|
||
title: "Give regionally. Leave a lasting impression.",
|
||
subtitle: "For companies, law firms, medical practices, hotels, agencies, and trade businesses.",
|
||
packages: [
|
||
{ key: "KLASSIK", title: "Classic", text: "Honey jar + company band" },
|
||
{ key: "GESCHENK", title: "Gift", text: "Honey jar + box + greeting card" },
|
||
{ key: "INDIVIDUELL", title: "Custom", text: "Fully custom design" },
|
||
],
|
||
inquiryHeading: "Send an Inquiry",
|
||
},
|
||
businessInquiryForm: {
|
||
companyLabel: "Company",
|
||
contactPersonLabel: "Contact person",
|
||
emailLabel: "Email",
|
||
phoneOptionalLabel: "Phone number (optional)",
|
||
quantityLabel: "Desired quantity (units)",
|
||
deliveryDateLabel: "Desired delivery date",
|
||
packageLabel: "Package",
|
||
messageLabel: "Message",
|
||
submitting: "Sending…",
|
||
submit: "Request corporate gifts",
|
||
successMessage: "Thank you for your inquiry! We'll get back to you shortly.",
|
||
errorMessage: "Something went wrong. Please try again.",
|
||
},
|
||
legalPage: {
|
||
reviewNotice: "",
|
||
},
|
||
notFoundPage: {
|
||
eyebrow: "404",
|
||
title: "This page doesn't exist, at least not anymore.",
|
||
text: "The link might be outdated, or the page has moved. Take a look around the shop, or head back to the homepage.",
|
||
backToShop: "Go to shop",
|
||
backToHome: "Back to homepage",
|
||
},
|
||
siteErrorPage: {
|
||
eyebrow: "Error",
|
||
title: "Something went wrong.",
|
||
text: "That was a technical error on our end, not something you did. Try again, or come back a bit later.",
|
||
retry: "Try again",
|
||
backToHome: "Back to homepage",
|
||
},
|
||
cookieConsent: {
|
||
dialogLabel: "Cookie settings",
|
||
bannerTextBeforeLink:
|
||
"We only use technically necessary cookies (e.g. for logging into your customer account). With your consent, we also use cookies for audience measurement. Details can be found in our ",
|
||
policyLinkLabel: "Cookie Policy",
|
||
bannerTextAfterLink: ".",
|
||
essentialOnly: "Necessary only",
|
||
acceptAll: "Accept all",
|
||
changeSettings: "Change cookie settings",
|
||
},
|
||
impressumPage: {
|
||
title: "Legal Notice",
|
||
contactHeading: "Contact",
|
||
phoneLabel: "Phone",
|
||
emailLabel: "Email",
|
||
sections: [
|
||
{
|
||
heading: "Information pursuant to § 5 TMG (German Telemedia Act)",
|
||
paragraphs: [
|
||
"Owner: Nick Rosenlöcher",
|
||
"Legal form: sole proprietorship (Einzelunternehmen)",
|
||
],
|
||
},
|
||
{
|
||
heading: "VAT ID",
|
||
paragraphs: [
|
||
"VAT identification number pursuant to § 27a of the German VAT Act (UStG): DE366479158",
|
||
],
|
||
},
|
||
{
|
||
heading: "Editorially Responsible",
|
||
paragraphs: [
|
||
"Responsible for editorial content pursuant to § 18(2) MStV (German Interstate Media Treaty), including the articles in our \"Honey Knowledge\" section: Nick Rosenlöcher.",
|
||
],
|
||
},
|
||
{
|
||
heading: "Liability for Content",
|
||
paragraphs: [
|
||
"As a service provider, we are responsible for our own content on these pages under the general laws pursuant to § 7(1) TMG. However, pursuant to §§ 8 to 10 TMG, we as a service provider are not obligated to monitor transmitted or stored third-party information or to investigate circumstances that indicate unlawful activity. Obligations to remove or block the use of information under general law remain unaffected. However, liability in this regard is only possible from the point in time at which we become aware of a specific infringement. Upon becoming aware of any such infringements, we will remove the relevant content immediately.",
|
||
],
|
||
},
|
||
{
|
||
heading: "Liability for Links",
|
||
paragraphs: [
|
||
"Our site contains links to external third-party websites over whose content we have no influence. We therefore cannot accept any liability for this third-party content. The respective provider or operator of the linked pages is always responsible for their content. The linked pages were checked for possible legal violations at the time of linking. No unlawful content was recognizable at that time. However, permanent monitoring of the content of linked pages is not reasonable without concrete evidence of a legal violation. We will remove such links immediately upon becoming aware of any legal violations.",
|
||
],
|
||
},
|
||
{
|
||
heading: "Copyright",
|
||
paragraphs: [
|
||
"The content and works created by the site operators on these pages are subject to German copyright law. Reproduction, editing, distribution, and any kind of use outside the limits of copyright law require the written consent of the respective author or creator. Downloads and copies of this site are only permitted for private, non-commercial use.",
|
||
],
|
||
},
|
||
{
|
||
heading: "Dispute Resolution",
|
||
paragraphs: [
|
||
"The European Commission provides a platform for online dispute resolution (ODR): https://ec.europa.eu/consumers/odr. We are not obligated, and not willing, to participate in dispute resolution proceedings before a consumer arbitration board.",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
datenschutzPage: {
|
||
title: "Privacy Policy",
|
||
sections: [
|
||
{
|
||
heading: "1. Data Controller",
|
||
paragraphs: [
|
||
"Based on the size and nature of our business, we do not believe we are required to appoint a data protection officer under § 38 BDSG. For any data protection questions, please use the contact address given in our legal notice.",
|
||
],
|
||
},
|
||
{
|
||
heading: "2. Hosting",
|
||
paragraphs: [
|
||
"This website is hosted on our own, self-operated server infrastructure, not by an external hosting provider. There is therefore no data processing agreement with an external hosting provider under Art. 28 GDPR; the server log files described in item 3 are processed exclusively by us as the data controller.",
|
||
],
|
||
},
|
||
{
|
||
heading: "3. Server Log Files When Visiting the Website",
|
||
paragraphs: [
|
||
"When you access our website, our hosting provider automatically collects technical access data (e.g. IP address, date and time of access, page accessed, browser type, referrer URL) in what are known as server log files. This processing is technically necessary to provide the website (Art. 6(1)(f) GDPR) and is deleted automatically after a short time.",
|
||
],
|
||
},
|
||
{
|
||
heading: "4. Order and Contract Processing",
|
||
paragraphs: [
|
||
"When you place an order, we collect the data necessary for contract, payment, and shipping processing (name, address, email, phone number if provided, and the items ordered). The legal basis is Art. 6(1)(b) GDPR (contract performance).",
|
||
"Payments are processed by our payment provider, Stripe Payments Europe, Ltd. The data necessary for payment processing is transmitted to Stripe for this purpose. Stripe's privacy policy also applies (stripe.com/privacy). Where this involves transferring data to Stripe-affiliated companies outside the EU/EEA (in particular Stripe, Inc. in the USA), this is done on the basis of EU Standard Contractual Clauses pursuant to Art. 46 GDPR.",
|
||
"Order and invoice data is retained for the legally required period due to commercial and tax record-keeping obligations (§ 257 HGB, § 147 AO of German law).",
|
||
],
|
||
},
|
||
{
|
||
heading: "5. Customer Account",
|
||
paragraphs: [
|
||
"If you create a customer account, we store the data you provide (name, email, password encrypted as a hash) as well as any delivery addresses you optionally save, to give you access to your order history, invoices, and a faster checkout. The legal basis is Art. 6(1)(b) GDPR. You can have your account and associated data deleted at any time via an informal request to us.",
|
||
"A technically necessary cookie is set for login; see our Cookie Policy.",
|
||
],
|
||
},
|
||
{
|
||
heading: "6. Newsletter (Harvest Mail)",
|
||
paragraphs: [
|
||
"If you sign up for our harvest mail, we use your email address exclusively to send information about new harvests. The legal basis is your consent (Art. 6(1)(a) GDPR). You can unsubscribe at any time via the link in every email or through your customer account.",
|
||
],
|
||
},
|
||
{
|
||
heading: "7. Contact and Business Customer Form",
|
||
paragraphs: [
|
||
"If you contact us via a form (e.g. a business customer inquiry), we process the data you provide to handle your request. The legal basis is Art. 6(1)(b) or (f) GDPR.",
|
||
],
|
||
},
|
||
{
|
||
heading: "8. Google Reviews",
|
||
paragraphs: [
|
||
"On our homepage, we display reviews from our Google Business Profile. The review text is retrieved server-side via the Google Places API for this purpose; your browser does not establish its own connection to Google. Provider: Google Ireland Limited, though a transfer to Google LLC (USA) cannot be ruled out. Google's privacy policy applies (policies.google.com/privacy).",
|
||
],
|
||
},
|
||
{
|
||
heading: "9. Cookies",
|
||
paragraphs: ["Details on the cookies used on this website can be found in our Cookie Policy."],
|
||
},
|
||
{
|
||
heading: "10. Your Rights",
|
||
paragraphs: [
|
||
"You have the right to access (Art. 15 GDPR), rectification (Art. 16 GDPR), erasure (Art. 17 GDPR), restriction of processing (Art. 18 GDPR), data portability (Art. 20 GDPR), and objection to processing (Art. 21 GDPR) of your personal data.",
|
||
"You also have the right to lodge a complaint with a data protection supervisory authority, e.g. the Bavarian State Office for Data Protection Supervision.",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
agbPage: {
|
||
title: "Terms & Conditions",
|
||
sections: [
|
||
{
|
||
heading: "1. Scope",
|
||
paragraphs: [
|
||
"These Terms & Conditions apply to all orders placed via wabenhain.com between Wabenhain and its customers, both consumers within the meaning of § 13 BGB and businesses within the meaning of § 14 BGB.",
|
||
],
|
||
},
|
||
{
|
||
heading: "2. Formation of Contract",
|
||
paragraphs: [
|
||
"The presentation of products in the shop does not constitute a binding offer, but rather a non-binding invitation for you to place an order. By completing an order, you submit a binding offer to purchase, which we accept by confirming it via email or by delivering the goods.",
|
||
],
|
||
},
|
||
{
|
||
heading: "3. Prices and Shipping Costs",
|
||
paragraphs: ["All prices include statutory VAT. Shipping costs are shown transparently at checkout; see also our \"Shipping & Payment\" page."],
|
||
},
|
||
{
|
||
heading: "4. Payment Methods",
|
||
paragraphs: [
|
||
"We offer PayPal, credit card, Apple Pay, and Google Pay via our payment provider Stripe. Payment by bank transfer is available on request. See our \"Shipping & Payment\" page and our Privacy Policy for details on Stripe.",
|
||
"The purchase price is generally due immediately upon ordering (prepayment via the chosen payment method), unless otherwise agreed.",
|
||
],
|
||
},
|
||
{
|
||
heading: "5. Delivery and Pickup",
|
||
paragraphs: ["We deliver within Germany. Alternatively, goods can be picked up directly from us in Altenmünster by arrangement."],
|
||
},
|
||
{
|
||
heading: "6. Right of Withdrawal",
|
||
paragraphs: [
|
||
"Consumers generally have a statutory right of withdrawal. For details, in particular exceptions for perishable goods such as honey, please see our separate Right of Withdrawal page.",
|
||
],
|
||
},
|
||
{
|
||
heading: "7. Warranty",
|
||
paragraphs: [
|
||
"Statutory warranty rights apply (§§ 434 et seq. BGB). For consumers, the limitation period for defect claims is two years from delivery of the goods.",
|
||
"For food products such as honey, the printed best-before date (BBD) indicates the date until which the product retains its specific properties when stored properly; it is not an expiry date and does not, by itself, constitute a defect.",
|
||
],
|
||
},
|
||
{
|
||
heading: "8. Retention of Title",
|
||
paragraphs: ["The delivered goods remain our property until paid for in full."],
|
||
},
|
||
{
|
||
heading: "9. Dispute Resolution",
|
||
paragraphs: [
|
||
"The European Commission provides a platform for online dispute resolution (ODR): https://ec.europa.eu/consumers/odr. We are not obligated, and not willing, to participate in dispute resolution proceedings before a consumer arbitration board.",
|
||
],
|
||
},
|
||
{
|
||
heading: "10. Final Provisions",
|
||
paragraphs: [
|
||
"The law of the Federal Republic of Germany applies, excluding the UN Convention on Contracts for the International Sale of Goods. For consumers, this only applies to the extent that it does not deprive them of the protection granted by mandatory provisions of the law of their country of habitual residence.",
|
||
"Should any provision of these Terms & Conditions be or become invalid, the validity of the remaining provisions shall remain unaffected.",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
widerrufPage: {
|
||
title: "Right of Withdrawal",
|
||
sections: [
|
||
{
|
||
heading: "Right of Withdrawal",
|
||
paragraphs: [
|
||
"You have the right to withdraw from this contract within fourteen days without giving any reason. The withdrawal period is fourteen days from the day on which you, or a third party named by you, took possession of the goods.",
|
||
"To exercise your right of withdrawal, you must inform us of your decision to withdraw from this contract by means of a clear statement (e.g. by email).",
|
||
],
|
||
},
|
||
{
|
||
heading: "Consequences of Withdrawal",
|
||
paragraphs: [
|
||
"If you withdraw from this contract, we will refund all payments we received from you without undue delay, and no later than fourteen days from the day on which we received notice of your withdrawal.",
|
||
],
|
||
},
|
||
{
|
||
heading: "Exclusion of the Right of Withdrawal",
|
||
paragraphs: [
|
||
"The right of withdrawal does not apply to the delivery of goods that can spoil quickly or whose expiry date would soon be exceeded, nor to sealed goods that are not suitable for return for health or hygiene reasons if their seal has been removed after delivery.",
|
||
],
|
||
},
|
||
],
|
||
},
|
||
versandZahlungPage: {
|
||
title: "Shipping & Payment",
|
||
shippingHeading: "Shipping",
|
||
shippingIntro:
|
||
"We ship within Germany. Shipping costs depend on the total quantity of all items in your order (multiple products are added together) and are shown to you transparently before you complete your order:",
|
||
tierRange: (min, max, price) => `${min}–${max} jars/items: ${price}`,
|
||
tierMinimum: (min, price) => `From ${min} jars/items: ${price}`,
|
||
pickupNote: "Alternatively, you can pick up your order directly from us in Altenmünster by arrangement; no shipping costs apply in that case.",
|
||
regionHeading: "Shipping region",
|
||
regionTextDe: "We ship within Germany only. Unfortunately we cannot currently accept orders with a delivery address outside Germany. Heligoland and Büsingen am Hochrhein are excluded as well: both lie outside the EU customs territory, so any shipment there would be subject to customs.",
|
||
regionTextEu: "We ship to the entire European Union — no customs apply there. Unfortunately we cannot currently accept orders with a delivery address outside the EU. The same applies to territories that belong to an EU country but lie outside the customs territory: Heligoland, Büsingen am Hochrhein, the Canary Islands, Ceuta and Melilla, the French overseas territories, Livigno, Campione d’Italia, Åland and Mount Athos.",
|
||
paymentHeading: "Payment Methods",
|
||
paymentText: "We offer credit card, SEPA direct debit, Klarna, PayPal, Apple Pay, and Google Pay via our payment provider Stripe. Payment by bank transfer is available on request.",
|
||
deliveryTimeHeading: "Delivery Time",
|
||
deliveryTimeText: "Typical delivery time within Germany is 2–4 business days after payment is received.",
|
||
},
|
||
cookieRichtliniePage: {
|
||
title: "Cookie Policy",
|
||
sections: [
|
||
{
|
||
heading: "What are cookies?",
|
||
paragraphs: [
|
||
"Cookies are small text files that are stored on your device when you visit a website. Among other things, they allow us to recognize you during a visit or save your preferences.",
|
||
],
|
||
},
|
||
{
|
||
heading: "Technically necessary cookies",
|
||
paragraphs: [
|
||
"These cookies are required for the website to function and cannot be disabled. They are only set if you actively trigger them through your actions (e.g. logging into your customer account).",
|
||
"wabenhain_session: keeps you logged into your customer account after signing in. Expires automatically after 30 days. Legal basis: Art. 6(1)(b) GDPR / § 25(2) No. 2 TTDSG.",
|
||
],
|
||
},
|
||
{
|
||
heading: "Local storage without cookies",
|
||
paragraphs: [
|
||
"Your cart is not stored via a cookie, but via your browser's local storage (localStorage) on your device, and never leaves your device. Your cookie choice itself is also stored in localStorage.",
|
||
],
|
||
},
|
||
{
|
||
heading: "Cookies requiring consent",
|
||
paragraphs: [
|
||
"We currently don't use any analytics or marketing cookies. Should we introduce something like audience measurement in the future, it will only be activated after your explicit consent via the cookie banner, and will be named here in advance.",
|
||
],
|
||
},
|
||
{
|
||
heading: "External content",
|
||
paragraphs: [
|
||
"Google reviews on our homepage are retrieved and displayed server-side via the Google Places API. No cookie is set in your browser, and your browser does not connect directly to Google.",
|
||
"Payments are processed via Stripe. During the payment process, Stripe may set its own cookies necessary for fraud prevention. Stripe's privacy policy applies.",
|
||
],
|
||
},
|
||
{
|
||
heading: "Changing your choice",
|
||
paragraphs: ["You can withdraw or change your cookie choice at any time."],
|
||
},
|
||
],
|
||
},
|
||
wissenListPage: {
|
||
eyebrow: "Honey Knowledge",
|
||
title: "Good to know about honey",
|
||
readMore: "Read more",
|
||
},
|
||
wissenArticlePage: {
|
||
backLabel: "Back to Honey Knowledge",
|
||
eyebrow: "Honey Knowledge",
|
||
otherArticlesHeading: "More Articles",
|
||
},
|
||
wissenArticles: {
|
||
"warum-kristallisiert-honig": {
|
||
title: "Why does honey crystallize?",
|
||
excerpt: "Crystallization is a natural process, not a quality defect. We explain why.",
|
||
body: [
|
||
"Honey consists mainly of the sugars glucose and fructose, plus water. Glucose tends to crystallize out over time, a completely natural, physical process, not a sign of poor quality.",
|
||
"The higher a honey variety's glucose content, the faster it crystallizes. That's why single-origin honeys can differ noticeably in how quickly they set.",
|
||
"If you prefer liquid honey, you can gently warm the jar in a water bath at low temperature. Just be careful not to overheat the honey, so its valuable compounds are preserved.",
|
||
],
|
||
},
|
||
"fluessiger-oder-cremiger-honig": {
|
||
title: "Liquid or creamy honey?",
|
||
excerpt: "Both textures are pure honey: the difference lies in how they're processed after harvest.",
|
||
body: [
|
||
"Whether a honey is liquid or creamy-firm depends on how the particular variety crystallizes and how it's processed after extraction.",
|
||
"For a fine, creamy texture, the honey is stirred in a controlled way after harvest. This produces many small crystals instead of fewer large ones, giving the honey a smooth, spreadable texture.",
|
||
"Both are equal in quality and nutritional value: it's purely a matter of taste and use.",
|
||
],
|
||
},
|
||
"was-bedeutet-fruehtracht": {
|
||
title: "What does “Frühtracht” (spring harvest) mean?",
|
||
excerpt: "The spring harvest is the year's first, and carries the character of spring.",
|
||
body: [
|
||
"“Frühtracht” refers to honey from the year's first harvest cycle. During this time, bees mainly collect nectar from early bloomers such as fruit trees, rapeseed, and dandelion.",
|
||
"Spring harvest honey is usually mild in flavor, light in color, and (due to the rapeseed content) often crystallizes quickly into a fine, creamy texture.",
|
||
"Every spring harvest tastes different, because weather, bloom timing, and location vary from year to year.",
|
||
],
|
||
},
|
||
"was-bedeutet-sommertracht": {
|
||
title: "What does “Sommertracht” (summer harvest) mean?",
|
||
excerpt: "The summer harvest arrives in high summer and produces stronger, more aromatic honeys.",
|
||
body: [
|
||
"“Sommertracht” refers to harvests from around July onward, once the spring bloom has passed and bees instead collect nectar from linden, chestnut, forest sources, and late-blooming meadow flowers.",
|
||
"Summer harvest honeys tend to be darker and more intense in flavor, and crystallize more slowly than spring harvest honeys, since they contain a higher proportion of fructose.",
|
||
"Where there's forest nearby, the summer harvest can also develop spicy, slightly malty notes typical of honeydew rather than blossom nectar.",
|
||
],
|
||
},
|
||
"wie-entsteht-honig": {
|
||
title: "How does honey come to be?",
|
||
excerpt: "From nectar in a blossom to finished honey in the jar is a remarkable process carried out by bees.",
|
||
body: [
|
||
"Forager bees visit blossoms and draw up the sugary nectar with their proboscis, carrying it in a special honey stomach. Along the way, enzymes from their own bodies already begin enriching the nectar with substances that improve its shelf life.",
|
||
"Inside the hive, house bees pass the nectar between one another repeatedly, enriching it further each time. They then spread it thinly across the honeycomb cells, where excess water evaporates.",
|
||
"Only once the water content has dropped below around 18 percent do the bees seal the cell with a wax cap. The honey is ripe and ready to be harvested.",
|
||
],
|
||
},
|
||
"warum-schmeckt-jede-honigernte-anders": {
|
||
title: "Why does every honey harvest taste different?",
|
||
excerpt: "Weather, bloom timing, and location make every batch its own flavor experience.",
|
||
body: [
|
||
"Honey is a natural product: its flavor depends directly on which plants were blooming near the hives at harvest time.",
|
||
"Weather conditions like rain, hours of sunshine, and temperature affect how much nectar a given plant species produces in a particular year, and therefore which forage source ends up dominating the honey.",
|
||
"That's why even the same variety, spring harvest for instance, tastes slightly different from year to year and from location to location. We document every batch individually, so this difference stays traceable instead of disappearing into a blended batch.",
|
||
],
|
||
},
|
||
"wie-lange-ist-honig-haltbar": {
|
||
title: "How long does honey keep?",
|
||
excerpt: "Stored correctly, honey stays enjoyable for an exceptionally long time.",
|
||
body: [
|
||
"Thanks to its low water content and high sugar content, pure honey has a very long natural shelf life. We still print a best-before date on the label, as legally required.",
|
||
"The key to long shelf life is proper storage: cool, dark, and dry, ideally at a steady temperature between 10 and 20 degrees Celsius.",
|
||
"If the jar is sealed tightly again after opening and a clean, dry spoon is used, honey generally stays enjoyable even beyond its best-before date.",
|
||
],
|
||
},
|
||
"was-macht-regionalen-honig-aus": {
|
||
title: "What defines regional honey?",
|
||
excerpt: "Short distances, known locations, and traceable harvests set regional honey apart from anonymous honey.",
|
||
body: [
|
||
"Regional honey comes from bee colonies whose location and surroundings are known, unlike many supermarket honey blends, which combine honey from several countries.",
|
||
"That means short transport distances, a harvest that fits the local landscape, and an apiary you can speak to directly about quality and origin.",
|
||
"At Wabenhain, every batch can be traced back to the hive: that's the core of what regional honey means to us.",
|
||
],
|
||
},
|
||
"wie-honig-richtig-lagern": {
|
||
title: "How should honey be stored?",
|
||
excerpt: "Cool, dark, and well sealed: that's how honey stays at its best for longest.",
|
||
body: [
|
||
"Honey should be stored at as steady a temperature as possible, between 10 and 20 degrees Celsius, ideally in a pantry rather than the fridge. Storage that's too cold speeds up crystallization without improving quality.",
|
||
"Direct sunlight and heat sources like the stove or a radiator should be avoided, since heat can break down valuable aromas and nutrients.",
|
||
"Reseal the jar tightly after every use, so no moisture from the air gets into the honey. That can otherwise encourage fermentation.",
|
||
],
|
||
},
|
||
"warum-nie-ueber-28-grad-erwaermt": {
|
||
title: "Why is our honey never heated above 28 °C?",
|
||
excerpt: "Honey naturally contains heat-sensitive enzymes: ours stay intact thanks to gentle processing.",
|
||
body: [
|
||
"Honey contains enzymes introduced by the bees themselves, including invertase and diastase. They're distinctly heat-sensitive: the more intensely and the longer honey is heated, the more of them are lost, measurable, among other things, by the so-called diastase number and by HMF content (hydroxymethylfurfural), a recognized indicator of heat exposure.",
|
||
"Beekeeping generally treats 40 °C as an upper limit to avoid damaging these compounds. Extraction itself already happens at room temperature (roughly 25–30 °C), since beeswax starts to melt at around 50 °C.",
|
||
"At Wabenhain, we go a deliberate step further: our honey is never heated above 28 °C at any point during harvesting, extraction, or bottling, noticeably below both the usual 40-degree limit and the natural hive temperature of around 35 °C. That keeps the enzymes as close as possible to how they exist in the hive itself.",
|
||
],
|
||
},
|
||
},
|
||
kontaktPage: {
|
||
eyebrow: "Contact",
|
||
title: "We'd love to hear from you.",
|
||
pickupHeading: "Pick up honey locally",
|
||
pickupText:
|
||
"By arrangement, you can pick up your order directly from us in Altenmünster. Just select “Pickup” at checkout. We'll then get in touch with a pickup time.",
|
||
},
|
||
twoFactorPage: {
|
||
eyebrow: "Security",
|
||
title: "Two-Factor Authentication",
|
||
subtitle: "Protects your account beyond just a password, with a code from an authenticator app.",
|
||
backLabel: "Back to overview",
|
||
activeStatus: (count) => `Two-factor authentication is active. ${count} backup code${count === 1 ? "" : "s"} remaining.`,
|
||
disableHeading: "Disable",
|
||
passwordConfirmLabel: "Password to confirm",
|
||
disableButton: "Disable two-factor authentication",
|
||
setupButton: "Set up",
|
||
},
|
||
wishlistPage: {
|
||
title: "My Wishlist",
|
||
emptyTitle: "No products saved yet.",
|
||
emptyDescription: "You can save products from any product page via “Add to wishlist.”",
|
||
},
|
||
twoFactorCodePage: {
|
||
eyebrow: "Customer account",
|
||
title: "Confirm sign-in",
|
||
subtitle: "Enter the 6-digit code from your authenticator app, or use one of your backup codes.",
|
||
cancelLabel: "Cancel sign-in",
|
||
codeLabel: "Code",
|
||
confirm: "Confirm",
|
||
},
|
||
twoFactorBackupCodesPage: {
|
||
eyebrow: "Security",
|
||
title: "Your backup codes",
|
||
subtitle:
|
||
"Each code works once, in case you don't have access to your authenticator app. Store them somewhere safe. They're only shown now.",
|
||
warning: "These codes won't be shown again. Don't reload the page without saving them.",
|
||
done: "Done",
|
||
},
|
||
twoFactorSetupPage: {
|
||
eyebrow: "Security",
|
||
title: "Set up two-factor authentication",
|
||
subtitle:
|
||
"Scan the QR code with an authenticator app (e.g. Google Authenticator, Authy) and confirm with the code shown.",
|
||
backLabel: "Back",
|
||
qrAlt: "QR code for the authenticator app",
|
||
manualEntryLabel: "Enter manually:",
|
||
appCodeLabel: "Code from the app",
|
||
activate: "Activate",
|
||
},
|
||
retourePage: {
|
||
backLabel: "Back to my orders",
|
||
orderEyebrow: (orderNumber) => `Order ${orderNumber}`,
|
||
title: "Request a return",
|
||
reasonLabel: "Reason for the return",
|
||
reasonPlaceholder: "What made you want to return this?",
|
||
submit: "Send request",
|
||
},
|
||
erntenPage: {
|
||
eyebrow: "Harvests",
|
||
title: "No two years taste alike.",
|
||
batchCount: (count) => `${count} batch${count === 1 ? "" : "es"}`,
|
||
emptyText: "No harvests recorded yet.",
|
||
},
|
||
erntenDetailPage: {
|
||
backLabel: "Back to all harvests",
|
||
batchesHeading: "Batches from this harvest",
|
||
productsHeading: "Products from this harvest",
|
||
glassesAvailable: (remaining, total) => `${remaining} of ${total} jars available`,
|
||
},
|
||
chargePage: {
|
||
verifiedBatch: "Verified batch",
|
||
provenanceNote: (code) => `Batch ${code} · This information comes directly from our production records.`,
|
||
stepHarvest: "Harvest",
|
||
stepFilling: "Filling",
|
||
stepBestBefore: "Best before",
|
||
regionLabel: "Region",
|
||
fromThisBatch: "From this batch",
|
||
},
|
||
newsletterBestaetigtPage: {
|
||
eyebrow: "Harvest post",
|
||
confirmedTitle: "Subscription confirmed.",
|
||
invalidTitle: "Link no longer valid.",
|
||
unsubscribedTitle: "Unsubscribed successfully.",
|
||
confirmedText: "Thank you! You'll now be the first to hear about the next harvest.",
|
||
invalidText: "This confirmation link has already been used or has expired. Feel free to sign up again.",
|
||
unsubscribedText: "You've been unsubscribed from the harvest post and won't receive any further emails.",
|
||
backHome: "Back to homepage",
|
||
discountHeading: "Your welcome discount",
|
||
discountText: (percent) => `${percent}% off your next order with code`,
|
||
discountCopyHint: "Enter the code at checkout.",
|
||
},
|
||
adminReviewsPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
title: "Reviews",
|
||
searchLabel: "Search",
|
||
searchPlaceholder: "Product or customer",
|
||
filterButton: "Filter",
|
||
resetFilter: "Reset filter",
|
||
noResultsFiltered: "No results for this search.",
|
||
colProduct: "Product",
|
||
colCustomer: "Customer",
|
||
colRating: "Rating",
|
||
colComment: "Comment",
|
||
colDate: "Date",
|
||
colStatus: "Status",
|
||
statusPublished: "Published",
|
||
statusPending: "Pending",
|
||
approve: "Approve",
|
||
deleteConfirm: "Really delete this review?",
|
||
delete: "Delete",
|
||
noReviewsYet: "No reviews yet.",
|
||
},
|
||
adminAuditLogPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
title: "Audit log",
|
||
intro: (limit) => `Log of all admin changes (products, orders, vouchers, users, …), the last ${limit} entries.`,
|
||
colTimestamp: "Time",
|
||
colAdmin: "Admin",
|
||
colAction: "Action",
|
||
colObject: "Object",
|
||
colDetails: "Details",
|
||
noEntriesYet: "No logged changes yet.",
|
||
},
|
||
adminVouchersPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
title: "Vouchers",
|
||
newVoucher: "New voucher",
|
||
colCode: "Code",
|
||
colDiscount: "Discount",
|
||
colMinOrder: "Min. order value",
|
||
colMinQuantity: "Bulk discount from",
|
||
colUsage: "Uses",
|
||
colValidity: "Validity",
|
||
colStatus: "Status",
|
||
immediately: "immediately",
|
||
unlimited: "unlimited",
|
||
statusActive: "Active",
|
||
statusInactive: "Inactive",
|
||
edit: "Edit",
|
||
deleteConfirm: (code) => `Really delete voucher "${code}"?`,
|
||
delete: "Delete",
|
||
noVouchersYet: "No vouchers created yet.",
|
||
},
|
||
adminVoucherEditPage: {
|
||
backToVouchers: "Back to vouchers",
|
||
title: "Edit voucher",
|
||
codeLabel: "Code",
|
||
typeLabel: "Type",
|
||
typePercent: "Percent",
|
||
typeFixed: "Fixed amount (cents)",
|
||
valueLabel: "Value",
|
||
minOrderLabel: "Min. order value (cents, optional)",
|
||
minQuantityLabel: "Minimum quantity for bulk discount (items, optional, applied automatically, no code needed)",
|
||
maxUsesLabel: "Max. total uses (empty = unlimited)",
|
||
validFromLabel: "Valid from (optional)",
|
||
validUntilLabel: "Valid until (optional)",
|
||
activeLabel: "Active",
|
||
redeemedSoFar: (count, max) => `Redeemed ${count}${max !== null ? ` of ${max}` : ""} times so far.`,
|
||
save: "Save",
|
||
},
|
||
adminVoucherNewPage: {
|
||
backToVouchers: "Back to vouchers",
|
||
title: "New voucher",
|
||
codeLabel: "Code",
|
||
codePlaceholder: "WABEN10",
|
||
typeLabel: "Type",
|
||
typePercent: "Percent",
|
||
typeFixed: "Fixed amount (cents)",
|
||
valueLabel: "Value",
|
||
minOrderLabel: "Min. order value (cents, optional)",
|
||
minQuantityLabel: "Minimum quantity for bulk discount (items, optional, applied automatically, no code needed)",
|
||
maxUsesLabel: "Max. total uses (empty = unlimited)",
|
||
validFromLabel: "Valid from (optional)",
|
||
validUntilLabel: "Valid until (optional)",
|
||
activeLabel: "Active",
|
||
create: "Create",
|
||
},
|
||
adminShippingPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
title: "Shipping costs",
|
||
newTier: "New tier",
|
||
intro:
|
||
"Shipping cost is based on the total quantity of all items in the cart. Multiple line items are summed into one quantity rather than priced by order value. Without a matching tier, a fixed fallback of €4.90 applies. Pickup in Altenmünster is always free.",
|
||
colQuantity: "Quantity",
|
||
colPrice: "Price",
|
||
quantityFrom: (min) => `from ${min} units`,
|
||
quantityRange: (min, max) => `${min}–${max} units`,
|
||
edit: "Edit",
|
||
deleteConfirm: "Really delete this shipping tier?",
|
||
delete: "Delete",
|
||
noTiersYet: "No tiers created yet. The €4.90 fallback applies.",
|
||
},
|
||
adminShippingEditPage: {
|
||
backToShipping: "Back to shipping costs",
|
||
title: "Edit shipping tier",
|
||
minQuantityLabel: "From (units)",
|
||
maxQuantityLabel: "To (units, empty = unlimited)",
|
||
priceLabel: "Price (cents)",
|
||
save: "Save",
|
||
},
|
||
adminShippingNewPage: {
|
||
backToShipping: "Back to shipping costs",
|
||
title: "New shipping tier",
|
||
minQuantityLabel: "From (units)",
|
||
maxQuantityLabel: "To (units, empty = unlimited)",
|
||
priceLabel: "Price (cents)",
|
||
create: "Create",
|
||
},
|
||
adminUsersPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
title: "Users",
|
||
newUser: "New user",
|
||
searchLabel: "Search",
|
||
searchPlaceholder: "Name or email",
|
||
filterButton: "Filter",
|
||
resetFilter: "Reset filter",
|
||
noResultsFiltered: "No results for this search.",
|
||
colName: "Name",
|
||
colEmail: "Email",
|
||
colRole: "Role",
|
||
colStatus: "Status",
|
||
col2fa: "2FA",
|
||
colCreated: "Created",
|
||
roleCustomer: "Customer",
|
||
roleAdmin: "Admin",
|
||
youSuffix: " (You)",
|
||
statusBlocked: "Blocked",
|
||
statusTemporarilyLocked: "Temporarily locked",
|
||
statusActive: "Active",
|
||
edit: "Edit",
|
||
unblock: "Unblock",
|
||
block: "Block",
|
||
delete: "Delete",
|
||
deleteConfirm: (name) => `Really delete "${name}"? This cannot be undone.`,
|
||
noUsersYet: "No users created yet.",
|
||
},
|
||
adminUserEditPage: {
|
||
backToUsers: "Back to users",
|
||
createdOn: (date) => `Account created on ${date}`,
|
||
firstNameLabel: "First name",
|
||
lastNameLabel: "Last name",
|
||
emailLabel: "Email",
|
||
phoneLabel: "Phone",
|
||
roleLabel: "Role",
|
||
roleCustomer: "Customer",
|
||
roleAdmin: "Admin",
|
||
cannotChangeOwnRole: "You can't change your own role here.",
|
||
save: "Save",
|
||
securityHeading: "Security",
|
||
twoFactorLabel: "Two-factor authentication",
|
||
twoFactorActive: "Active",
|
||
twoFactorInactive: "Not active",
|
||
statusLabel: "Status",
|
||
blockedSince: (date) => `Blocked by admin since ${date}`,
|
||
temporarilyLockedFailedAttempts: "Temporarily locked (failed attempts)",
|
||
statusActive: "Active",
|
||
sendPasswordReset: "Send password reset email",
|
||
unblock: "Unblock",
|
||
block: "Block",
|
||
deleteAccount: "Delete account",
|
||
deleteConfirm: (name) => `Really delete "${name}"? This cannot be undone.`,
|
||
},
|
||
adminUserNewPage: {
|
||
backToUsers: "Back to users",
|
||
title: "New user",
|
||
intro:
|
||
"No password is set: the user immediately receives an email with a link to set their own password.",
|
||
firstNameLabel: "First name",
|
||
lastNameLabel: "Last name",
|
||
emailLabel: "Email",
|
||
roleLabel: "Role",
|
||
roleCustomer: "Customer",
|
||
roleAdmin: "Admin",
|
||
createAndSend: "Create & send reset email",
|
||
},
|
||
adminBatchesPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
notDeletable: "in use",
|
||
title: "Batches",
|
||
colBatch: "Batch",
|
||
colHarvest: "Harvest",
|
||
colFillDate: "Fill date",
|
||
colBestBefore: "Best before",
|
||
colTotalGlasses: "Total jars",
|
||
colRemaining: "Remaining",
|
||
edit: "Edit",
|
||
delete: "Delete",
|
||
deleteConfirm: (code) => `Really delete batch "${code}"? This cannot be undone.`,
|
||
noBatchesYet: "No batches created yet.",
|
||
newBatchHeading: "Create new batch",
|
||
codeLabel: "Batch code",
|
||
codePlaceholder: "e.g. FH-27-01",
|
||
harvestLabel: "Harvest",
|
||
fillDateLabel: "Fill date",
|
||
bestBeforeDateLabel: "Best-before date",
|
||
glassCountLabel: "Number of jars",
|
||
remainingGlassesLabel: "Remaining stock",
|
||
create: "Create",
|
||
},
|
||
adminBatchEditPage: {
|
||
backToBatches: "Back to batches",
|
||
title: "Edit batch",
|
||
fillDateLabel: "Fill date",
|
||
bestBeforeDateLabel: "Best-before date",
|
||
glassCountLabel: "Number of jars",
|
||
remainingGlassesLabel: "Remaining stock",
|
||
imagesLabel: "Batch images",
|
||
image1Label: "Image 1 — left tile on the batch page",
|
||
image2Label: "Image 2 — right tile on the batch page (location)",
|
||
save: "Save",
|
||
},
|
||
trackOrderPage: {
|
||
eyebrow: "Track order",
|
||
title: "Where's my order?",
|
||
intro: "Enter your order number and the email address used at checkout to see the current status, no account needed.",
|
||
orderNumberLabel: "Order number",
|
||
orderNumberPlaceholder: "e.g. WH-2026-A1B2C3D4",
|
||
submit: "Show status",
|
||
notFoundError: "No order found matching this order number and email address.",
|
||
rateLimitError: "Too many requests. Please try again later.",
|
||
itemsHeading: "Items",
|
||
totalLabel: "Total",
|
||
viewInvoice: "View invoice",
|
||
},
|
||
passwordStrength: {
|
||
weak: "Weak",
|
||
fair: "Fair",
|
||
strong: "Strong",
|
||
},
|
||
shipmentInfo: {
|
||
heading: "Shipment tracking",
|
||
carrierLabel: "Carrier",
|
||
trackingNumberLabel: "Tracking number",
|
||
trackPackage: "Track package",
|
||
},
|
||
errorPage: {
|
||
eyebrow: "Error",
|
||
title: "Something went wrong.",
|
||
description: "This wasn't your fault. Please try again, or head back to the homepage.",
|
||
retry: "Try again",
|
||
backHome: "Back to homepage",
|
||
},
|
||
adminHarvestsPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
notDeletable: "in use",
|
||
title: "Harvests",
|
||
colType: "Type",
|
||
colYear: "Year",
|
||
colHarvestDate: "Harvest date",
|
||
colLocation: "Location",
|
||
colBatches: "Batches",
|
||
colProducts: "Products",
|
||
colStatus: "Status",
|
||
statusPublished: "Published",
|
||
statusDraft: "Draft",
|
||
publish: "Publish",
|
||
unpublish: "Unpublish",
|
||
edit: "Edit",
|
||
delete: "Delete",
|
||
deleteConfirm: (label) => `Really delete "${label}"? This cannot be undone.`,
|
||
noHarvestsYet: "No harvests created yet.",
|
||
newHarvestHeading: "Create new harvest",
|
||
typeLabel: "Type (e.g. spring harvest)",
|
||
yearLabel: "Year",
|
||
harvestDateLabel: "Harvest date",
|
||
locationLabel: "Location",
|
||
descriptionLabel: "Description",
|
||
isPublishedLabel: "Publish immediately",
|
||
create: "Create",
|
||
},
|
||
adminHarvestEditPage: {
|
||
backToHarvests: "Back to harvests",
|
||
title: "Edit harvest",
|
||
typeLabel: "Type",
|
||
yearLabel: "Year",
|
||
harvestDateLabel: "Harvest date",
|
||
locationLabel: "Location",
|
||
descriptionLabel: "Description",
|
||
imagesLabel: "Harvest images",
|
||
image1Label: "Image 1 — harvest overview and left tile on the batch page",
|
||
image2Label: "Image 2 — right tile on the batch page (location)",
|
||
isPublishedLabel: "Publicly visible",
|
||
isPublishedHint: "Controls whether this harvest shows up on the website (shop, /ernten).",
|
||
draftNotice: "This harvest is currently only visible in the admin area (e.g. imported from ApiCore). Please review the details, then publish it.",
|
||
publishNow: "Publish now",
|
||
save: "Save",
|
||
},
|
||
adminApiCoreSync: {
|
||
heading: "ApiCore sync",
|
||
intro:
|
||
"Fetches harvests, batches and products from ApiCore in one run — in that order, because each step builds on the previous one. Harvests and products are created as drafts: harvests unpublished, products inactive and without a price, because ApiCore does not hold sales prices. Each bottling run becomes its own vintage. Entries already released are never overwritten.",
|
||
button: "Sync everything",
|
||
resultHarvests: (created, updated, skipped, failed) =>
|
||
`Harvests: ${created} created, ${updated} updated, ${skipped} skipped${failed > 0 ? `, ${failed} failed` : ""}.`,
|
||
resultBatches: (created, skipped, failed) =>
|
||
`Batches: ${created} created, ${skipped} skipped${failed > 0 ? `, ${failed} failed (see server log)` : ""}.`,
|
||
resultProducts: (created, skipped, failed) =>
|
||
`Products: ${created} new vintage${created === 1 ? "" : "s"}, ${skipped} skipped${failed > 0 ? `, ${failed} failed (see server log)` : ""}.`,
|
||
pending: (count) =>
|
||
`${count} bottling run${count === 1 ? "" : "s"} without a matching batch — usually a sign that the corresponding harvest is still missing in ApiCore.`,
|
||
discontinued: (count) =>
|
||
`${count} bottling run${count === 1 ? "" : "s"} belong to an article ApiCore no longer carries, so no new vintage was created. Existing ones stay on sale.`,
|
||
error: "Sync with ApiCore failed.",
|
||
batchLoadError: "Batches could not be loaded — without them no products are created either",
|
||
},
|
||
adminProductsPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
delete: "Delete",
|
||
deleteConfirm: (label) => `Really delete "${label}"? This cannot be undone.`,
|
||
notDeletable: "already sold",
|
||
title: "Products",
|
||
newProduct: "New product",
|
||
colProduct: "Product",
|
||
colCategory: "Category",
|
||
colHarvest: "Harvest",
|
||
colBatch: "Batch",
|
||
colWeight: "Weight",
|
||
colPrice: "Price",
|
||
colStock: "Stock",
|
||
colStatus: "Status",
|
||
statusActive: "Active",
|
||
statusInactive: "Inactive",
|
||
edit: "Edit",
|
||
noProductsYet: "No products created yet.",
|
||
draftHint: "Draft: no price set",
|
||
},
|
||
adminProductEditPage: {
|
||
backToProducts: "Back to products",
|
||
title: "Edit product",
|
||
harvestBatchSuffix: (type, year, code) => ` · ${type} ${year} · Batch ${code}`,
|
||
nameLabel: "Name",
|
||
descriptionLabel: "Description",
|
||
tasteTagsLabel: "Characteristics/tags (comma-separated)",
|
||
consistencyLabel: "Consistency",
|
||
intensityMildLabel: "mild (1-5)",
|
||
intensityFloralLabel: "floral (1-5)",
|
||
intensityKraeftigLabel: "intense (1-5)",
|
||
weightLabel: "Weight (g)",
|
||
stockLabel: "Inventory (jars)",
|
||
priceLabel: "Price (cents)",
|
||
pricePerKgLabel: "Base price per kg (cents)",
|
||
vatRateLabel: "VAT rate (%, optional)",
|
||
vatRateHint:
|
||
"Leave empty to use the category rate (honey 7%, candles 19%). Fill in only if this product differs. 0 means exempt under § 19 German VAT Act.",
|
||
imagesLabel: "Product images",
|
||
visibleInShop: "Visible in shop",
|
||
save: "Save",
|
||
},
|
||
adminProductNewPage: {
|
||
backToProducts: "Back to products",
|
||
title: "New product",
|
||
nameLabel: "Name",
|
||
slugLabel: "URL slug (optional, generated from name otherwise)",
|
||
categoryLabel: "Category",
|
||
categoryHint:
|
||
"Only honey products are linked to a harvest/batch. For other categories this selection is automatically ignored on save, since e.g. candles aren't tied to a harvest.",
|
||
harvestLabel: "Harvest",
|
||
noneOption: "None selected",
|
||
batchLabel: "Batch",
|
||
batchOption: (code, type, year) => `${code} (${type} ${year})`,
|
||
descriptionLabel: "Description",
|
||
tasteTagsLabel: "Characteristics/tags (comma-separated)",
|
||
tasteTagsPlaceholder: "mild, floral, smooth",
|
||
consistencyLabel: "Consistency (honey only)",
|
||
consistencyPlaceholder: "smooth",
|
||
intensityMildLabel: "Mild intensity (1-5)",
|
||
intensityFloralLabel: "Floral intensity (1-5)",
|
||
intensityKraeftigLabel: "Intense intensity (1-5)",
|
||
weightLabel: "Weight (g)",
|
||
stockLabel: "Inventory (jars)",
|
||
priceLabel: "Price (cents)",
|
||
pricePerKgLabel: "Base price per kg (cents)",
|
||
vatRateLabel: "VAT rate (%, optional)",
|
||
vatRateHint:
|
||
"Leave empty to use the category rate (honey 7%, candles 19%). Fill in only if this product differs. 0 means exempt under § 19 German VAT Act.",
|
||
pricePlaceholder: "990",
|
||
pricePerKgPlaceholder: "1980",
|
||
imagesLabel: "Product images",
|
||
visibleInShop: "Visible in shop",
|
||
create: "Create",
|
||
},
|
||
adminReturnsPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
title: "Returns",
|
||
searchLabel: "Search",
|
||
searchPlaceholder: "Order number, name, or email",
|
||
filterButton: "Filter",
|
||
resetFilter: "Reset filter",
|
||
noResultsFiltered: "No results for this search.",
|
||
statusRequested: "Requested",
|
||
statusAccepted: "Accepted",
|
||
statusRejected: "Rejected",
|
||
statusRefunded: "Refunded",
|
||
accept: "Accept",
|
||
reject: "Reject",
|
||
refundNow: "Refund now via Stripe",
|
||
noRequestsYet: "No return requests yet.",
|
||
},
|
||
adminInquiriesPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
title: "Business inquiries",
|
||
searchLabel: "Search",
|
||
searchPlaceholder: "Company, contact, or email",
|
||
filterButton: "Filter",
|
||
resetFilter: "Reset filter",
|
||
noResultsFiltered: "No results for this search.",
|
||
statusNew: "New",
|
||
statusInProgress: "In progress",
|
||
statusCompleted: "Completed",
|
||
statusRejected: "Rejected",
|
||
save: "Save",
|
||
packageLabel: "Package",
|
||
quantityLabel: "Quantity",
|
||
unitsSuffix: (quantity) => `${quantity} units`,
|
||
deliveryDateLabel: "Delivery date",
|
||
noInquiriesYet: "No business inquiries yet.",
|
||
},
|
||
adminOrderDetailPage: {
|
||
postNumberLabel: "DHL Post Number",
|
||
backToOrders: "Back to orders",
|
||
saveStatus: "Save status",
|
||
giftHeading: "🎁 Gift: enclose message",
|
||
customerHeading: "Customer",
|
||
registeredAccount: "Registered customer account",
|
||
guestOrder: "Guest order",
|
||
shippingAddressHeading: "Shipping address",
|
||
pickupHeading: "Pickup",
|
||
shippedOn: (date) => `Shipped on ${date}`,
|
||
notShippedYet: "Not shipped yet",
|
||
pickedUpOn: (date) => `Picked up on ${date}`,
|
||
notPickedUpYet: "Not picked up yet",
|
||
readySince: (date) => `Ready for pickup since ${date}`,
|
||
itemsHeading: "Items",
|
||
colProduct: "Product",
|
||
colBatch: "Batch",
|
||
colQuantity: "Qty",
|
||
colTotal: "Total",
|
||
subtotalLabel: "Subtotal",
|
||
shippingLabel: "Shipping",
|
||
freeShipping: "free",
|
||
discountLabel: "Discount",
|
||
totalLabel: "Total",
|
||
paymentHeading: "Payment",
|
||
paidOn: (date) => `Paid on ${date}`,
|
||
noPaymentYet: "No payment recorded yet.",
|
||
invoiceHeading: "Invoice",
|
||
downloadPdf: "Download PDF",
|
||
noInvoiceYet: "No invoice created yet (follows upon payment receipt).",
|
||
trackingHeading: "Shipment tracking",
|
||
carrierLabel: "Carrier",
|
||
carrierPlaceholder: "e.g. DHL, DPD, Hermes",
|
||
trackingNumberLabel: "Tracking number",
|
||
saveTracking: "Save",
|
||
dhlHeading: "DHL shipping label",
|
||
dhlCreateButton: "Create shipping label",
|
||
dhlAlreadyCreated: (trackingNumber) => `DHL shipping label already created: tracking number ${trackingNumber}`,
|
||
dhlNotConfigured: "DHL isn't configured yet, see .env (DHL_API_KEY, DHL_API_SECRET, DHL_GKP_USERNAME, DHL_GKP_PASSWORD, DHL_ACCOUNT_NUMBER).",
|
||
},
|
||
adminOrdersPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
title: "Orders",
|
||
searchLabel: "Search (number, email, last name)",
|
||
searchPlaceholder: "e.g. WH-2026 or [email protected]",
|
||
statusLabel: "Status",
|
||
statusAll: "All",
|
||
filterButton: "Filter",
|
||
resetFilter: "Reset filter",
|
||
colNumber: "Number",
|
||
colCustomer: "Customer",
|
||
colDate: "Date",
|
||
colItems: "Items",
|
||
colTotal: "Total",
|
||
colStatus: "Status",
|
||
colFulfillment: "Fulfillment",
|
||
giftOrderTitle: "Gift order with message",
|
||
save: "Save",
|
||
shipping: "Shipping",
|
||
pickup: "Pickup",
|
||
noResultsFiltered: "No orders match this filter.",
|
||
noOrdersYet: "No orders yet.",
|
||
},
|
||
adminDashboardPage: {
|
||
missingUploadsTitle: (missing: number, checked: number) =>
|
||
`${missing} of ${checked} referenced image/video files are missing on the server.`,
|
||
uploadsNotPersistentTitle:
|
||
"The upload directory lives inside the container and is not persistent.",
|
||
uploadsNotPersistentHint: (uploadDir: string) =>
|
||
`All images and videos under ${uploadDir} will be lost on the next redeploy. In Kubernetes the "uploads" volume must be a PersistentVolumeClaim (kubectl apply -f k8s/04-uploads-pvc.yaml and k8s/05-app-deployment.yaml); with Docker it must be a named volume. See k8s/README.md.`,
|
||
missingUploadsHint:
|
||
"The database still holds the paths, but the files themselves are gone — the shop shows empty areas there. This is almost always a non-persistent upload directory: in Kubernetes the \"uploads\" volume must be a PersistentVolumeClaim (k8s/04-uploads-pvc.yaml), not an emptyDir, otherwise it is lost on every redeploy. After fixing it, the affected files have to be uploaded once more.",
|
||
title: "Dashboard",
|
||
statRevenueToday: "Revenue today",
|
||
statRevenueMonth: "Revenue this month",
|
||
statOpenOrders: "Open orders",
|
||
statInventory: "Inventory (jars)",
|
||
statSoldGlasses: "Jars sold",
|
||
statAvgCart: "Avg. cart value",
|
||
statRepeatCustomers: "Repeat customers",
|
||
topProductsHeading: "Top products",
|
||
noSalesYet: "No sales yet.",
|
||
unitsSuffix: (quantity) => `(${quantity} units)`,
|
||
revenueByCategoryHeading: "Revenue by category",
|
||
revenueTrendHeading: (days) => `Revenue trend (${days} days)`,
|
||
daysAgoLabel: (days) => `${days} days ago`,
|
||
todayLabel: "today",
|
||
topVouchersHeading: "Most-used vouchers",
|
||
noVouchersYet: "No vouchers redeemed yet.",
|
||
usedCountLabel: (count, maxUses) => `used ${count}×${maxUses ? ` / ${maxUses}` : ""}`,
|
||
viewAllOrders: "View all orders →",
|
||
},
|
||
adminContentPage: {
|
||
backToDashboard: "Back to dashboard",
|
||
title: "Content",
|
||
intro:
|
||
"These texts appear on the website. An empty field does not mean \"blank on the page\": the original text keeps showing. Only fill in what you want to change.",
|
||
sectionImages: "Images",
|
||
sectionImagesIntro: "Every image and the hero video you can upload, grouped by page.",
|
||
sectionText: "Text",
|
||
sectionTextIntro: "Every text field, grouped by section.",
|
||
groupCompany: "Company details",
|
||
groupHome: "Homepage",
|
||
groupPages: "Other pages",
|
||
groupLegal: "Legal pages",
|
||
groupSocial: "Social media",
|
||
deLabel: "German",
|
||
enLabel: "English",
|
||
save: "Save",
|
||
saved: "Saved.",
|
||
},
|
||
adminImageField: {
|
||
none: "No image",
|
||
chooseFile: "Choose file",
|
||
uploading: "Uploading…",
|
||
remove: "Remove image",
|
||
removeImageAt: (position) => `Remove image ${position}`,
|
||
uploadError: "The upload failed.",
|
||
uploadErrorStatus: (status) =>
|
||
status === 413
|
||
? `The file was rejected because it is too large (HTTP 413). It never reached the shop — that limit comes from the reverse proxy or CDN in front of it.`
|
||
: status === 502
|
||
? `The server aborted processing (HTTP 502). The backend dropped the connection without answering — often because it ran out of memory. Please report this number to your technical contact.`
|
||
: status === 503 || status === 504 || status === 522 || status === 524
|
||
? `The server aborted processing (HTTP ${status}). This is a timeout: processing took longer than the proxy in front of it allows.`
|
||
: `The server responded unexpectedly (HTTP ${status}) — no usable reply. Please report this number to your technical contact.`,
|
||
uploadErrorNetwork: "The connection to the server was interrupted during the upload.",
|
||
},
|
||
adminVideoField: {
|
||
none: "No video",
|
||
chooseFile: "Choose file",
|
||
uploading: "Uploading…",
|
||
remove: "Remove video",
|
||
uploadError: "The upload failed.",
|
||
uploadErrorStatus: (status) =>
|
||
status === 413
|
||
? `The video was rejected because it is too large (HTTP 413). It never reached the shop — that limit comes from the reverse proxy or CDN in front of it, not from the shop itself.`
|
||
: status === 502
|
||
? `The server aborted processing (HTTP 502). The backend dropped the connection without answering — for videos usually because it ran out of memory while processing. Please report this number to your technical contact.`
|
||
: status === 503 || status === 504 || status === 522 || status === 524
|
||
? `The server aborted processing (HTTP ${status}). This is a timeout: the upload took longer than the proxy in front of it allows. A shorter or smaller video usually gets through.`
|
||
: `The server responded unexpectedly (HTTP ${status}) — no usable reply. Please report this number to your technical contact.`,
|
||
uploadErrorNetwork: "The connection to the server was interrupted during the upload.",
|
||
},
|
||
adminShell: {
|
||
navDashboard: "Dashboard",
|
||
navOrders: "Orders",
|
||
navReturns: "Returns",
|
||
navProducts: "Products",
|
||
navHarvests: "Harvests",
|
||
navBatches: "Batches",
|
||
navBusinessInquiries: "Business inquiries",
|
||
navUsers: "Users",
|
||
navShipping: "Shipping",
|
||
navVouchers: "Vouchers",
|
||
navReviews: "Reviews",
|
||
navAuditLog: "Audit log",
|
||
navContent: "Content",
|
||
navAriaLabel: "Admin navigation",
|
||
mobileNavAriaLabel: "Mobile admin navigation",
|
||
openMenu: "Open menu",
|
||
closeMenu: "Close menu",
|
||
loggedInAs: (name) => `Signed in as ${name}`,
|
||
logout: "Sign out",
|
||
twoFactorWarning:
|
||
"This admin account doesn't have two-factor authentication enabled yet, recommended for accounts with access to orders, refunds, and customer data.",
|
||
twoFactorSetupLink: "Set up now →",
|
||
},
|
||
invoicePage: {
|
||
loginRequiredError: "Please sign in to view the invoice.",
|
||
backToOrders: "Back to my orders",
|
||
downloadPdf: "Download PDF",
|
||
invoiceLabel: (invoiceNumber) => `Invoice ${invoiceNumber}`,
|
||
orderLabel: (orderNumber) => `Order ${orderNumber}`,
|
||
dateLabel: "Date:",
|
||
itemHeader: "Item",
|
||
quantityHeader: "Qty",
|
||
priceHeader: "Price",
|
||
totalHeader: "Total",
|
||
subtotalLabel: "Subtotal",
|
||
shippingLabel: "Shipping",
|
||
freeShipping: "free",
|
||
discountLabel: "Discount",
|
||
netLabel: "Net amount",
|
||
vatLabel: (ratePercent) => `plus ${ratePercent}% VAT`,
|
||
smallBusinessNote: "No VAT is charged (small-business scheme, § 19 German VAT Act).",
|
||
totalLabel: "Total",
|
||
legalNote: "This invoice was generated automatically and is valid without a signature.",
|
||
},
|
||
addressesPage: {
|
||
title: "My Addresses",
|
||
standard: "Default",
|
||
setDefault: "Set as default",
|
||
delete: "Delete",
|
||
emptyTitle: "No address saved yet.",
|
||
emptyDescription: "Save a delivery address to check out faster next time.",
|
||
addNewHeading: "Add a New Address",
|
||
labelField: "Label (e.g. Home)",
|
||
labelDefaultValue: "Home",
|
||
useAsDefault: "Use as default address",
|
||
save: "Save address",
|
||
},
|
||
account: {
|
||
eyebrow: "Account",
|
||
loginRegisterTitle: "Sign in or create an account",
|
||
login: "Sign in",
|
||
register: "Create account",
|
||
emailLabel: "Email",
|
||
passwordLabel: "Password",
|
||
passwordMinLabel: "Password (min. 8 characters)",
|
||
passwordConfirmLabel: "Confirm password",
|
||
firstNameLabel: "First name",
|
||
lastNameLabel: "Last name",
|
||
forgotPassword: "Forgot your password?",
|
||
ssoDivider: "or",
|
||
ssoButton: "Sign in with Microsoft",
|
||
greeting: (name) => `Hello, ${name}`,
|
||
verifyEmailPrompt: "Please confirm your email address. You can't place orders with this account until you do.",
|
||
resendVerification: "Resend link",
|
||
navOrders: { title: "Orders", description: "Order history, shipping status, and invoices" },
|
||
navAddresses: { title: "Addresses", description: "Manage delivery addresses" },
|
||
navWishlist: { title: "Wishlist", description: "View saved products" },
|
||
navSecurity: {
|
||
title: "Security",
|
||
twoFactorLabel: "Two-factor authentication",
|
||
twoFactorActive: "active",
|
||
twoFactorInactive: "not active",
|
||
},
|
||
navAdmin: { title: "Admin area", description: "Manage orders, products, harvests, batches, and business inquiries" },
|
||
newsletterHeading: "Harvest mail",
|
||
newsletterSubscribed: "You're currently subscribed to the harvest mail.",
|
||
newsletterNotSubscribed: "You're not currently subscribed.",
|
||
subscribe: "Subscribe",
|
||
unsubscribe: "Unsubscribe",
|
||
logoutAllDevicesHeading: "Log out all devices",
|
||
logoutAllDevicesDescription: "Ends all active sessions, including this device. You'll need to sign in again.",
|
||
logout: "Log out",
|
||
privacy: {
|
||
heading: "Privacy",
|
||
downloadDataHeading: "Download my data",
|
||
downloadDataDescription: "Download a copy of all data we hold about your account (GDPR Art. 15).",
|
||
downloadDataButton: "Download data",
|
||
deleteAccountHeading: "Delete account",
|
||
deleteAccountDescription:
|
||
"Your account and all personal data will be permanently deleted. Already-completed orders are retained as required by law, but no longer linked to your account.",
|
||
deleteAccountPasswordLabel: "Password to confirm",
|
||
deleteAccountButton: "Permanently delete account",
|
||
deleteAccountConfirm: "Are you sure you want to permanently delete your account? This cannot be undone.",
|
||
deleteAccountAdminBlocked: "Admin accounts can't be self-deleted. Please contact another admin.",
|
||
},
|
||
},
|
||
trustBadges: {
|
||
sslEncrypted: "SSL encrypted",
|
||
securePayment: "Secure payment via Stripe",
|
||
returnPolicy: "14-day right of withdrawal",
|
||
},
|
||
passwortVergessenPage: {
|
||
backLabel: "Back to sign in",
|
||
title: "Forgot Password",
|
||
sentMessage: "If an account exists for this email address, we've just sent a password reset link to it. The link is valid for one hour.",
|
||
intro: "Enter your email address. We'll send you a link to reset your password.",
|
||
requestLink: "Request link",
|
||
},
|
||
passwortZuruecksetzenPage: {
|
||
backLabel: "Back to sign in",
|
||
title: "Set a New Password",
|
||
incompleteLinkTextBefore: "This link is incomplete. Please request a new link via ",
|
||
incompleteLinkLabel: "Forgot password",
|
||
incompleteLinkTextAfter: ".",
|
||
newPasswordLabel: "New password (min. 8 characters)",
|
||
save: "Save password",
|
||
},
|
||
checkoutSuccessPage: {
|
||
pendingEyebrow: "Order Received",
|
||
successEyebrow: "Thank You",
|
||
pendingTitle: "Your order has been received.",
|
||
successTitle: "Your order has been confirmed.",
|
||
orderNumberLabel: "Order number",
|
||
pendingText: "Payment processing was briefly unreachable. We'll email you to complete the payment.",
|
||
successText: "We're carefully preparing your jar of Wabenhain honey. As soon as your payment arrives, we'll send you a confirmation by email (this can take a few business days for SEPA direct debit).",
|
||
continueShopping: "Keep browsing",
|
||
trackOrder: "Track order",
|
||
},
|
||
checkoutCancelPage: {
|
||
eyebrow: "Cancelled",
|
||
title: "Your order was not completed.",
|
||
text: "No problem, your cart is still full. You can restart the checkout process at any time.",
|
||
released: "The items reserved for you are available in the shop again.",
|
||
backToCart: "Back to cart",
|
||
},
|
||
checkoutPage: {
|
||
eyebrow: "Checkout",
|
||
emptyCart: "Your cart is empty",
|
||
yourDetails: "Your Details",
|
||
firstName: "First name",
|
||
lastName: "Last name",
|
||
email: "Email",
|
||
phoneOptional: "Phone number (optional)",
|
||
fulfillmentLabel: "Delivery method",
|
||
shippingOption: "Shipping",
|
||
pickupOption: "Pickup in Altenmünster",
|
||
deliveryTimeNote: "Typical delivery time: 2–4 business days after payment.",
|
||
deliveryTargetAddress: "To my address",
|
||
deliveryTargetPackstation: "To a DHL Packstation",
|
||
packstationIntro:
|
||
"Enter the postal code the Packstation should be near. Locations come directly from DHL.",
|
||
packstationZipLabel: "Postal code",
|
||
packstationSearchButton: "Search Packstations",
|
||
packstationSearching: "Searching …",
|
||
packstationNoResults: "No Packstation was found for this postal code.",
|
||
packstationChooseHint: "Please pick a Packstation from the list.",
|
||
packstationSelectedLabel: "Selected Packstation",
|
||
packstationChange: "Choose a different Packstation",
|
||
packstationDistance: (meters: number) =>
|
||
meters < 1000 ? `approx. ${meters} m away` : `approx. ${(meters / 1000).toFixed(1)} km away`,
|
||
postNumberLabel: "DHL Post Number",
|
||
postNumberHint:
|
||
"Your 6 to 10 digit DHL Post Number. DHL cannot deliver to a Packstation without it.",
|
||
streetLabel: "Street and house number",
|
||
zipLabel: "Postal code",
|
||
cityLabel: "City",
|
||
countryLabel: "Country",
|
||
shippingRegionDeNote: "We currently ship within Germany only.",
|
||
discountCodeLabel: "Discount code (optional)",
|
||
discountCodePlaceholder: "e.g. WABEN10",
|
||
isGiftLabel: "This is a gift",
|
||
giftMessageLabel: "Personal message (optional)",
|
||
giftMessagePlaceholder: "Will be handwritten on a card and included with your order.",
|
||
genericError: "Something went wrong. Please try again.",
|
||
submitting: "Processing…",
|
||
placeOrder: "Place order",
|
||
paymentMethodsNote:
|
||
"Payment methods: PayPal, credit card, Apple Pay, Google Pay, SEPA direct debit, Klarna, provided via Stripe.",
|
||
yourOrderHeading: "Your Order",
|
||
shippingWithCount: (count) => `Shipping (${count} item${count === 1 ? "" : "s"})`,
|
||
shippingPickup: "Shipping (pickup)",
|
||
},
|
||
cartPage: {
|
||
title: "Your Cart",
|
||
goToShop: "Go to shop",
|
||
shippingLabel: (count) => `Shipping (${count} item${count === 1 ? "" : "s"})`,
|
||
free: "free",
|
||
total: "Total",
|
||
nextTierHint: (unitsUntilNext, nextPrice) =>
|
||
unitsUntilNext === 1
|
||
? `1 more item until the next shipping tier (${nextPrice}).`
|
||
: `${unitsUntilNext} more items until the next shipping tier (${nextPrice}).`,
|
||
pickupHint: "Pickup in Altenmünster is always free.",
|
||
bulkDiscountProgress: (unitsUntilNext, percent) =>
|
||
unitsUntilNext === 1
|
||
? `1 more item until ${percent}% bulk discount.`
|
||
: `${unitsUntilNext} more items until ${percent}% bulk discount.`,
|
||
discountLabel: "Bulk discount",
|
||
},
|
||
product: {
|
||
tasteMild: "mild",
|
||
tasteFloral: "floral",
|
||
tasteIntense: "intense",
|
||
addToCart: "Add to cart",
|
||
added: "Added ✓",
|
||
outOfStock: "Sold out",
|
||
onlyLeft: (count) => `Only ${count} left`,
|
||
toHoney: "View honey",
|
||
toProduct: "View product",
|
||
viewProduct: "View product →",
|
||
},
|
||
productPage: {
|
||
reviewsLink: (count) => `${count} review${count === 1 ? "" : "s"}`,
|
||
noReviews: "No reviews yet",
|
||
shippingHint: (price) => `Shipping from ${price} · Delivery in 2–4 business days`,
|
||
onlyLeftInStock: (count) => `Only ${count} in stock`,
|
||
soldOut: "Sold out",
|
||
currentlyUnavailable: "Currently unavailable, check back soon.",
|
||
notifyMeLabel: "Notify me when it's back in stock",
|
||
emailPlaceholder: "Email address",
|
||
notifySubmit: "Sign up",
|
||
removeFromWishlist: "★ Remove from wishlist",
|
||
addToWishlist: "☆ Add to wishlist",
|
||
loginToSave: "to save this product.",
|
||
loginLinkLabel: "Sign in",
|
||
checklist: {
|
||
HONIG: [
|
||
"from our own apiary",
|
||
"honey from Germany",
|
||
"bottled in Altenmünster",
|
||
"traceable batch",
|
||
"never heated above 28 °C, enzymes stay intact",
|
||
],
|
||
KERZEN: [
|
||
"hand-poured from pure beeswax",
|
||
"from our own apiary in Altenmünster",
|
||
"low-soot, long burn time",
|
||
],
|
||
PROPOLIS: [
|
||
"from our own apiary in Altenmünster",
|
||
"gently processed",
|
||
"no synthetic additives",
|
||
],
|
||
},
|
||
detailLabels: {
|
||
description: "Description",
|
||
origin: "Origin",
|
||
harvest: "Harvest",
|
||
batch: "Batch",
|
||
storage: "Storage",
|
||
note: "Note",
|
||
},
|
||
storageText: "Store in a dry place away from heat. Crystallization is a natural quality process.",
|
||
infantWarningText: "Due to its natural spore content, honey is not suitable for infants under 12 months.",
|
||
nonHoneyOriginText: "From our own apiary and production in Altenmünster.",
|
||
viewBatchOrigin: "View this jar's batch origin →",
|
||
giftHint: "Giving this as a gift? You can add a personal message at checkout.",
|
||
learnMoreLabel: "Learn more:",
|
||
reviewsHeading: "Reviews",
|
||
noReviewsForProduct: "There are no reviews for this product yet.",
|
||
loginToReview: "to review this product.",
|
||
alreadyReviewed: "You've already reviewed this product.",
|
||
alreadyReviewedPending: "You've already reviewed this product. Your review is still being checked.",
|
||
onlyBuyersCanReview: "Only customers who purchased this product can review it.",
|
||
yourRating: "Your rating",
|
||
commentPlaceholder: "What did you like? (optional)",
|
||
submitReview: "Submit review",
|
||
relatedProductsHeading: "You might also like",
|
||
recentlyViewedHeading: "Recently viewed",
|
||
starsOutOf5: (rating) => `${rating} out of 5 stars`,
|
||
starLabel: (value) => `${value} star${value === 1 ? "" : "s"}`,
|
||
},
|
||
shop: {
|
||
eyebrow: "Shop",
|
||
title: "Our Range",
|
||
subtitle: "Honey, beeswax candles, and propolis from our own apiary, while stocks last.",
|
||
allCategories: "All",
|
||
sortLabel: "Sort by:",
|
||
sortNewest: "Newest arrivals",
|
||
sortPriceAsc: "Price ascending",
|
||
sortPriceDesc: "Price descending",
|
||
sortAvailability: "Availability",
|
||
apply: "Apply",
|
||
onlyAvailable: "In stock only",
|
||
searchInputLabel: "Search the shop",
|
||
clearSearch: "Clear search",
|
||
searchResults: (query, count) => `Search results for "${query}" (${count})`,
|
||
noSearchResults: "No results for your search.",
|
||
noProducts: "No products available here right now. Check back soon.",
|
||
productsHeading: "Products",
|
||
},
|
||
home: {
|
||
hero: {
|
||
intro: "Small harvests, clear origin, and honest beekeeping craft from Altenmünster.",
|
||
ctaPrimary: "Discover the current harvest",
|
||
ctaSecondary: "Our apiary",
|
||
footnote: "Harvested · bottled · shipped from our own apiary",
|
||
},
|
||
currentHarvest: {
|
||
eyebrow: "Currently available",
|
||
title: "The current harvest",
|
||
subtitle: "Small, documented batches, while stocks last.",
|
||
},
|
||
originStory: {
|
||
eyebrow: "Origin",
|
||
title: "You can taste where honey comes from.",
|
||
paragraph1:
|
||
"Our bee colonies are located in and around Altenmünster in the Western Woods. Forests, meadows, gardens, and farmland shape every harvest in its own way.",
|
||
paragraph2: "That's why we don't see honey as a standardized product. Every harvest has its own character.",
|
||
facts: ["Location: Altenmünster", "Origin: Germany", "small harvest quantities", "our own bee colonies", "our own bottling"],
|
||
cta: "More about our origin",
|
||
},
|
||
philosophy: {
|
||
eyebrow: "Philosophy",
|
||
title: "The Wabenhain Philosophy",
|
||
items: [
|
||
{ title: "Our own harvest", text: "Our honey comes from our own bee colonies." },
|
||
{ title: "Small batches", text: "Every harvest is documented and bottled separately." },
|
||
{ title: "Clear origin", text: "Traceable from the hive to the jar." },
|
||
{ title: "Honest craft", text: "Not anonymous mass-produced goods, but honey from our own apiary." },
|
||
],
|
||
},
|
||
productSpotlight: {
|
||
description: "The first harvest of the year.",
|
||
consistency: "Consistency",
|
||
harvest: "Harvest",
|
||
batch: "Batch",
|
||
weight: "Weight",
|
||
price: "Price",
|
||
cta: "Discover this harvest",
|
||
},
|
||
processTimeline: {
|
||
eyebrow: "From bee colony to jar",
|
||
title: "Six steps to your honey",
|
||
steps: [
|
||
{ number: "01", title: "Blossom", text: "The bees collect nectar from the surrounding area." },
|
||
{ number: "02", title: "Honey", text: "The bee colony turns it into honey." },
|
||
{ number: "03", title: "Harvest", text: "The ripe honeycombs are harvested." },
|
||
{ number: "04", title: "Processing", text: "The honey is extracted and carefully processed." },
|
||
{ number: "05", title: "Bottling", text: "Every batch is documented and bottled in Altenmünster." },
|
||
{ number: "06", title: "Your jar", text: "Straight from the apiary to you." },
|
||
],
|
||
},
|
||
regionalSection: {
|
||
eyebrow: "Regional apiary",
|
||
title: "From Altenmünster. For the region. And beyond.",
|
||
paragraph1: "Wabenhain is a small apiary from Altenmünster in the district of Augsburg.",
|
||
paragraph2:
|
||
"Our production is deliberately small in scale. That way we know our bee colonies, locations, and harvests, and can document every batch traceably.",
|
||
ctaAbout: "About Wabenhain",
|
||
ctaPickup: "Pick up honey locally",
|
||
},
|
||
sustainability: {
|
||
eyebrow: "Responsibility",
|
||
title: "Responsibility starts small.",
|
||
items: [
|
||
{ title: "Reusable jars", text: "Clean Wabenhain jars can be returned to us." },
|
||
{ title: "Short distances", text: "Pickup directly at the apiary is possible." },
|
||
{ title: "Our own production", text: "We handle harvesting and bottling ourselves." },
|
||
{ title: "Beekeeping", text: "Our bee colonies are part of the regional landscape and agriculture." },
|
||
],
|
||
},
|
||
businessTeaser: {
|
||
eyebrow: "For businesses",
|
||
title: "A regional gift people remember.",
|
||
paragraph: "Wabenhain honey makes a high-quality gift for customers, employees, and business partners.",
|
||
cta: "Request corporate gifts",
|
||
},
|
||
reviews: {
|
||
eyebrow: "Voices",
|
||
title: "What our customers say",
|
||
googleReviewsCount: (rating, count) => `${rating} ★ · ${count} Google reviews`,
|
||
seeAllOnGoogle: "See all reviews on Google →",
|
||
starsOutOf5: (rating) => `${rating} out of 5 stars`,
|
||
testimonials: [
|
||
{
|
||
quote: "Extremely creamy honey with a noticeably more intense flavor than what you get from the supermarket.",
|
||
author: "Julia, Augsburg",
|
||
},
|
||
{ quote: "You can really taste that every batch is made with care.", author: "Markus, Altenmünster" },
|
||
{
|
||
quote: "A lovely gift for clients. The feedback was consistently enthusiastic.",
|
||
author: "Business customer, Augsburg region",
|
||
},
|
||
],
|
||
},
|
||
newsletter: {
|
||
eyebrow: "Harvest mail",
|
||
title: "Be the first to know about the next harvest.",
|
||
description:
|
||
"New harvests only happen a few times a year. Our harvest mail lets you know as soon as new honey is available.",
|
||
successMessage: "Thank you! Please confirm your subscription via the link in your email.",
|
||
emailPlaceholder: "[email protected]",
|
||
subscribe: "Subscribe",
|
||
subscribing: "Sending…",
|
||
errorMessage: "Something went wrong. Please try again.",
|
||
consentBeforeLink: "By signing up you agree to our ",
|
||
consentAfterLink: ". You can unsubscribe at any time.",
|
||
privacyLinkLabel: "Privacy Policy",
|
||
},
|
||
},
|
||
};
|