Files
Wabenhain/src/i18n/dictionaries/en.ts
T
Nick SzaunigandClaude Opus 5.5 f71bcb2b67
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m53s
feat(inhalte): text placeholder {{USt-IdNr}} for the VAT ID
Admin texts can now contain the placeholder {{USt-IdNr}}. Wherever
content is read (lib/content.ts), it is replaced with the VAT ID
maintained under Admin > Einstellungen > Firmendaten, so every text
follows a change automatically. The placeholder is matched case- and
space-insensitively; an empty VAT ID inserts nothing, and unknown
placeholders stay visible so typos show up. Images, videos, select
fields and the source field itself are never rewritten.

The default imprint text uses the placeholder instead of a hard-coded
number, and the settings page lists the available placeholders.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-01 00:59:04 +02:00

1985 lines
107 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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",
},
},
pageOutro: {
ariaLabel: "Keep reading",
shopText: "Honey, beeswax candles and propolis from our own apiary — while each batch lasts.",
harvestsText: "Every harvest with date, location and batch, traceable down to the jar.",
originText: "Where our colonies live and what shapes the landscape around Altenmünster.",
},
theme: {
light: "Light",
dark: "Dark",
system: "Automatic",
toggleLabel: (current, next) => `Appearance: ${current}. Switch to ${next}`,
},
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",
forLabel: "For",
audiences: ["companies", "law firms", "medical practices", "hotels", "agencies", "trade businesses"],
packagesHeading: "Three ways to give honey.",
packageCta: "Ask for this package",
},
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: "",
tocLabel: "Contents",
revisionLabel: "Last updated",
},
withdrawalPage: {
title: "Withdraw from contract",
intro: "Here you can withdraw from your purchase contract online within the 14-day withdrawal period. You do not need a customer account and do not have to give a reason.",
nameLabel: "Your name",
orderNumberLabel: "Order number",
orderNumberHint: "Shown in your order confirmation, e.g. WH-2026-0042.",
emailLabel: "Email address",
emailHint: "We will send the confirmation of receipt to this address.",
messageLabel: "Message (optional)",
requiredNote: "Only name, order number and email address are required.",
startButton: "Withdraw from contract",
reviewHeading: "Please check your withdrawal",
reviewIntro: "By clicking “Confirm withdrawal” we will submit this notice:",
confirmButton: "Confirm withdrawal",
editButton: "Edit details",
submitting: "Sending …",
successHeading: "Your withdrawal has been received",
successMailSent: "We have just emailed you a confirmation of receipt containing your notice and the date and time it was received.",
successMailFailed: "Your withdrawal has been validly received. The confirmation email could not be sent just now; we will send it to you as soon as possible. Please save this page as proof.",
receivedAtLabel: "Received on",
errorMessage: "Your withdrawal could not be sent. Please try again or email us — a withdrawal by email or letter is also valid.",
policyLink: "Right of withdrawal",
footerButton: "Withdraw from contract",
},
notFoundPage: {
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: {
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 use technically necessary cookies (e.g. for logging into your customer account). With your consent, we also remember in your browser which products you viewed recently. We do not use tracking or advertising. 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 DDG (German Digital Services 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): {{USt-IdNr}}",
],
},
{
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, address as above.",
],
},
{
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) DDG. However, pursuant to §§ 8 to 10 DDG, 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: [
// Siehe Begründung in de.ts: die EU-Plattform zur Online-Streit-
// beilegung wurde eingestellt, der Verweis darauf ist entfallen.
"We are not obligated, and not willing, to participate in dispute resolution proceedings before a consumer arbitration board (Section 36(1) No. 1 VSBG).",
],
},
],
},
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 above.",
],
},
{
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, we automatically collect on our server 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) The log files are not combined with other data and are deleted automatically once they are no longer needed for security and error analysis, after 14 days at the latest.",
],
},
{
heading: "4. Order and Contract Processing",
paragraphs: [
"For shipping, we pass your name and delivery address to our shipping provider DHL (DHL Paket GmbH, Bonn, Germany). If you choose delivery to a Packstation or branch, the location number you selected is added. If you search for a Packstation during checkout, the town or postcode you enter is sent to DHL to perform that search. When you check your shipment under “Track order”, we request its status from DHL using the tracking number and the delivery postcode and show it to you directly on our site. Legal basis: Art. 6(1)(b) GDPR.",
"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.",
"If you choose PayPal or Klarna in the payment step, you are redirected to the respective provider and the order data required for payment is transmitted to them: PayPal (Europe) S.à r.l. et Cie, S.C.A., 22–24 Boulevard Royal, L-2449 Luxembourg, or Klarna Bank AB (publ), Sveavägen 46, 111 34 Stockholm, Sweden. These providers may carry out their own checks, e.g. of identity or creditworthiness; they are responsible for these themselves and their privacy notices apply. Legal basis: Art. 6(1)(b) 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).",
"We send order confirmations and other emails about your contract or customer account via Microsoft 365 (Microsoft Ireland Operations Limited, One Microsoft Place, South County Business Park, Leopardstown, Dublin 18, Ireland). Microsoft processes the data as our processor under a contract pursuant to Art. 28 GDPR. Data may be transferred to Microsoft Corporation in the USA; Microsoft is certified under the EU-US Data Privacy Framework. Legal basis: Art. 6(1)(b) GDPR.",
"If you withdraw from a contract via our online withdrawal function, we store your name, order number, email address, your optional message and the time of receipt, send you a confirmation of receipt and match the withdrawal to your order. Legal basis: Art. 6(1)(c) GDPR in conjunction with § 356a BGB. We keep the notice as proof until the limitation periods for claims under the contract have expired.",
],
},
{
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.",
"You can alternatively sign in with a Microsoft account (Microsoft Entra ID). You are redirected to Microsoft and sign in there; we never receive a password, only your email address and name in order to match your customer account. Which data Microsoft itself processes during sign-in is governed by Microsoft's privacy notice. The provider is Microsoft Ireland Operations Limited; a transfer to Microsoft Corporation in the USA may take place. Microsoft is certified under the EU-US Data Privacy Framework. Legal basis: Art. 6(1)(b) GDPR.",
"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.",
"Sign-up uses a double opt-in: you are only added once you click the link in our confirmation email; we store the time of this confirmation as proof of your consent. If you subscribe in your customer account, the click there constitutes your consent and we store its time as well. Unsubscribing withdraws your consent with effect for the future (Art. 7(3) GDPR). We then keep your address together with the time of unsubscribing so that we can demonstrate your consent and its withdrawal and send you no further harvest mail (Art. 6(1)(f) GDPR).",
],
},
{
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. Protecting Our Forms Against Abuse (Cloudflare Turnstile)",
paragraphs: [
"To protect our forms against automated bulk access, we use Cloudflare Turnstile, a service of Cloudflare, Inc., 101 Townsend Street, San Francisco, CA 94107, USA. This affects sign-in, registration, password reset, two-factor confirmation, checkout, newsletter sign-up, business enquiries and back-in-stock notifications.",
"When you open a page containing one of these forms, your browser loads a script from challenges.cloudflare.com. Your IP address and technical characteristics of your browser and device are transmitted to Cloudflare and evaluated there to distinguish whether the input comes from a human or a program. According to the provider, Turnstile sets no advertising cookies for this and builds no cross-site user profile.",
"Legal basis is Art. 6(1)(f) GDPR. Our legitimate interest lies in preventing sign-in attempts with stolen credentials, spam submitted through our forms, and fraudulent orders.",
"A transfer to the USA takes place. Cloudflare is certified under the EU-US Data Privacy Framework; standard contractual clauses apply in addition.",
],
},
{
heading: "9. 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: "10. Product Reviews",
paragraphs: [
"If you write a review while signed in, we store the star rating, text, time and the link to your customer account. Once approved by us, the review appears on the product page together with your first name and the date. Legal basis: Art. 6(1)(b) GDPR; the review is kept until you ask us to delete it or your customer account is deleted.",
],
},
{
heading: "11. Back-in-Stock Notification",
paragraphs: [
"If you ask to be notified when a sold-out product is available again, we store your email address, the product and your language in order to send you exactly one notification once it is back in stock. Legal basis: your request (Art. 6(1)(b) GDPR). You can have the request deleted at any time by contacting us.",
],
},
{
heading: "12. Cookies",
paragraphs: ["Details on the cookies used on this website can be found in our Cookie Policy."],
},
{
heading: "13. Storage Period and Obligation to Provide Data",
paragraphs: [
"We store personal data only as long as necessary for the respective purpose or as required by statutory retention periods (for order and invoice data up to ten years under § 147 AO, § 257 HGB). The data is deleted afterwards.",
"The details required for an order are needed to conclude and perform the contract; without them no order is possible. All other details (e.g. newsletter, reviews) are voluntary.",
"No automated decision-making including profiling within the meaning of Art. 22 GDPR takes place.",
],
},
{
heading: "14. 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 (BayLDA), Promenade 18, 91522 Ansbach, Germany, www.lda.bayern.de.",
"If you have given consent, you may withdraw it at any time with effect for the future (Art. 7(3) GDPR). This does not affect the lawfulness of processing carried out before the withdrawal.",
"Right to object: where we process data on the basis of legitimate interests (Art. 6(1)(f) GDPR), you may object at any time on grounds relating to your particular situation (Art. 21(1) GDPR). We will then no longer process the data unless we can demonstrate compelling legitimate grounds. You may object to processing for direct marketing at any time without giving reasons (Art. 21(2) GDPR). An informal message to us is sufficient.",
],
},
],
},
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.",
"Until payment is completed you can correct your entries in the order form at any time; from the payment step, “Back to your details” returns you to the form. The contract is concluded in German. We store the contract text; you receive the order details with the confirmation email, and with a customer account you can view your orders there at any time. You can access, print and save these terms on this page at any time.",
],
},
{
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: [
"Payment is processed by our payment provider Stripe. The payment methods available (e.g. credit card, SEPA direct debit, Klarna, PayPal, Apple Pay, Google Pay) are shown during checkout and on our \"Shipping & Payment\" page. Payment by bank transfer is available on request.",
"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 to the delivery area stated on our \"Shipping & Payment\" page. 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 the statutory exceptions, 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: [
// Siehe gleichlautende Stelle im Impressum.
"We are not obligated, and not willing, to participate in dispute resolution proceedings before a consumer arbitration board (Section 36(1) No. 1 VSBG).",
],
},
{
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: [
{
// Translation of the official German model (see de.ts); the German
// version is the legally binding one.
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 other than the carrier named by you, took possession of the goods.",
"To exercise your right of withdrawal, you must inform us ({{anbieter}}) of your decision to withdraw from this contract by means of a clear statement (e.g. a letter sent by post or an email). You may use the model withdrawal form below, but it is not obligatory.",
"You can also exercise your right of withdrawal online at {{widerrufsfunktion}}. If you use this online function, we will send you a confirmation of receipt on a durable medium (e.g. by email) without undue delay.",
"To meet the withdrawal deadline, it is sufficient for you to send your communication concerning your exercise of the right of withdrawal before the withdrawal period has expired.",
],
},
{
heading: "Consequences of Withdrawal",
paragraphs: [
"If you withdraw from this contract, we shall reimburse to you all payments received from you, including the costs of delivery (with the exception of the supplementary costs resulting from your choice of a type of delivery other than the least expensive type of standard delivery offered by us), without undue delay and in any event not later than fourteen days from the day on which we are informed about your decision to withdraw from this contract. We will carry out such reimbursement using the same means of payment as you used for the initial transaction, unless you have expressly agreed otherwise; in any event, you will not incur any fees as a result of such reimbursement.",
"We may withhold reimbursement until we have received the goods back or you have supplied evidence of having sent back the goods, whichever is the earliest.",
"You shall send back the goods or hand them over to us without undue delay and in any event not later than fourteen days from the day on which you communicate your withdrawal from this contract to us. The deadline is met if you send back the goods before the period of fourteen days has expired.",
"You bear the direct cost of returning the goods.",
"You are only liable for any diminished value of the goods resulting from handling other than what is necessary to establish the nature, characteristics and functioning of the goods.",
],
},
{
heading: "Exclusion of the Right of Withdrawal",
paragraphs: [
"The right of withdrawal does not apply to contracts for the supply of sealed goods which are not suitable for return due to health protection or hygiene reasons if they were unsealed after delivery (§ 312g(2) No. 3 BGB). This applies in particular to opened honey jars whose tamper-evident seal has been broken.",
],
},
{
heading: "Model withdrawal form",
paragraphs: [
"(Complete and return this form only if you wish to withdraw from the contract.)",
"To: {{anbieter}}",
"- I/We (*) hereby give notice that I/We (*) withdraw from my/our (*) contract of sale of the following goods (*)/for the provision of the following service (*)\n- Ordered on (*)/received on (*)\n- Name of consumer(s)\n- Address of consumer(s)\n- Signature of consumer(s) (only if this form is notified on paper)\n- Date",
"(*) Delete as appropriate.",
],
},
],
},
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.",
returnsHeading: "Returns",
returnsText:
"You bear the direct cost of returning the goods. This applies when you exercise your right of withdrawal, meaning you return goods that are in perfect condition.",
returnsDefectText:
"Defective goods are excluded from this: if a defect originates in production or is otherwise attributable to us as the seller, we cover the full cost of the return. In that case simply contact us before sending the goods back.",
},
cookieRichtliniePage: {
title: "Cookie Policy",
// Mirrors de.ts entry for entry — see the comment there. Keep both in sync.
sections: [
{
heading: "What are cookies and similar technologies?",
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.",
"Besides cookies, we use your browser's local storage (localStorage). Legally, both are treated the same: § 25 of the German Telecommunications Digital Services Data Protection Act (TDDDG) applies to any storage of information on your device and any access to it. This policy therefore lists both in full.",
"The controller is the operator of this website named in the legal notice. Further information on how we process personal data can be found in our privacy policy.",
],
},
{
heading: "Legal basis",
paragraphs: [
"Storing information on your device or reading it from there generally requires your consent (§ 25(1) TDDDG). No consent is needed where the storage is strictly necessary for us to provide a service you have explicitly requested (§ 25(2) No. 2 TDDDG), such as signing in to your customer account or completing an order.",
"Where personal data is processed in the process, we rely on Art. 6(1)(b) GDPR (contract and pre-contractual steps, e.g. customer account and order) or Art. 6(1)(f) GDPR (our legitimate interest in a secure, functioning website). For storage requiring consent (see “Cookies requiring consent”), the legal basis is your consent under § 25(1) TDDDG in conjunction with Art. 6(1)(a) GDPR.",
],
},
{
heading: "Overview",
paragraphs: [
"We use technically necessary cookies and storage entries and — only with your consent — one further storage entry for the list of recently viewed products. We do not use analytics, statistics, marketing or tracking cookies, we do not build usage profiles, and we do not pass on data to third parties for advertising purposes.",
"Most of the cookies listed below are only created when you use a specific feature (e.g. signing in, paying, switching language). Merely visiting the website sets no cookie.",
],
},
{
heading: "Technically necessary cookies",
paragraphs: [
"We set all of the following cookies ourselves (first-party cookies). They are only transmitted encrypted (HTTPS) and, unless stated otherwise, are inaccessible to scripts in the browser (HttpOnly). They cannot be deselected because the respective feature does not work without them. Legal basis: § 25(2) No. 2 TDDDG and Art. 6(1)(b) GDPR.",
"wabenhain_session – Keeps you signed in to your customer account. Content: a signed identifier of your account, no passwords. Storage period: 30 days, 7 days for administrator accounts; deleted immediately when you sign out.",
"wabenhain_2fa_challenge – When signing in with two-factor authentication, remembers that your password has already been checked until you enter the verification code. Storage period: 5 minutes; deleted after successful sign-in.",
"wabenhain_2fa_setup – Holds the setup step together while you enable two-factor authentication in your customer account. Storage period: 10 minutes; deleted on completion.",
"wabenhain_2fa_backup_display – Shows you your recovery codes exactly once. Storage period: 2 minutes at most; deleted as soon as they are displayed.",
"wabenhain_entra_oauth – Only for “Sign in with Microsoft”: protects the redirect to Microsoft and back against tampering (cross-site request forgery protection). Storage period: 10 minutes; deleted on return.",
"wh_checkout_ref – Only when paying: links the order just created to your browser so that only you can reopen or cancel the payment. Only sent to pages under /checkout. Storage period: 40 minutes.",
"wh_flash – Carries a short confirmation or error message (e.g. “Your email address has been confirmed”) over to the next page. Readable by scripts so the message can be displayed. Storage period: 60 seconds; deleted once displayed. Legal basis: § 25(2) No. 2 TDDDG and Art. 6(1)(f) GDPR.",
],
},
{
heading: "Cookies for your settings",
paragraphs: [
"These two cookies are only set when you change the respective setting yourself, and they store nothing but your choice. They are readable by scripts but contain no personal data. Legal basis: § 25(2) No. 2 TDDDG (you explicitly triggered saving the setting) and Art. 6(1)(f) GDPR.",
"theme – Stores your chosen appearance (light or dark). Storage period: 1 year. Switching back to “System” deletes the cookie.",
"locale – Stores your chosen language (German or English). Storage period: 1 year.",
],
},
{
heading: "Local storage (localStorage)",
paragraphs: [
"The following entries are kept in your browser's local storage. Unlike cookies, they are not automatically sent to us with every page request and do not expire on their own: they remain until you delete them (via your browser's “clear site data” setting) or the website removes them. Legal basis: § 25(2) No. 2 TDDDG and Art. 6(1)(b) or (f) GDPR.",
"wabenhain-cart – Your cart (items, quantities, prices). It is only transmitted to us when you submit an order, and it is emptied after a successful order. Storage period: until your next order or until you empty it yourself.",
"wabenhain-cookie-consent – Your choice in the cookie banner (“Essential only” or “Accept all”), so the banner does not reappear on every visit and we can demonstrate your decision. Storage period: until you change your choice or clear the site data. Legal basis: § 25(2) No. 2 TDDDG and Art. 6(1)(c) GDPR in conjunction with Art. 7(1) GDPR.",
],
},
{
heading: "Third-party cookies",
paragraphs: [
"Stripe (payment processing): Only in the payment step of checkout does your browser load Stripe's payment form. Stripe sets its own cookies for fraud prevention and secure payment processing, in particular __stripe_mid (storage period 1 year) and __stripe_sid (storage period 30 minutes), as well as further cookies within the payment form on Stripe domains. Stripe determines the names and lifetimes of these cookies. Provider: Stripe Payments Europe, Ltd., 1 Grand Canal Street Lower, Dublin 2, Ireland. Data may be transferred to Stripe, Inc. in the USA on the basis of EU standard contractual clauses (Art. 46 GDPR). Legal basis: § 25(2) No. 2 TDDDG and Art. 6(1)(b) GDPR. More information: stripe.com/privacy and stripe.com/legal/cookies-policy.",
"Microsoft (sign-in with a Microsoft account): If you choose “Sign in with Microsoft”, you are redirected to a Microsoft sign-in page. Microsoft is responsible for the cookies set there; Microsoft's privacy statement applies. Microsoft sets no cookies on our website. Provider: Microsoft Ireland Operations Limited, One Microsoft Place, South County Business Park, Leopardstown, Dublin 18, Ireland.",
"Cloudflare Turnstile (form protection): On pages with forms (sign-in, registration, password reset, two-factor verification, checkout, newsletter, business enquiry, back-in-stock notification), your browser loads a script from challenges.cloudflare.com. It distinguishes input made by humans from input made by automated programs. Your IP address and technical characteristics of your browser are transmitted to Cloudflare, Inc. (USA) in the process. According to the provider, Turnstile sets no advertising cookies and builds no cross-site user profile. Cloudflare is certified under the EU-US Data Privacy Framework. Provider: Cloudflare, Inc., 101 Townsend Street, San Francisco, CA 94107, USA. Legal basis: § 25(2) No. 2 TDDDG and Art. 6(1)(f) GDPR. More information: see the “Cloudflare Turnstile” section of our privacy policy.",
"Google reviews: We retrieve the reviews on our homepage server-side via the Google Places API. Your browser does not connect to Google, and Google sets no cookie.",
],
},
{
heading: "Cookies requiring consent",
paragraphs: [
"We only store the following entry if you choose “Accept all” in the cookie banner. Without consent it is not created; the list then only lasts while the tab is open. If you choose “Essential only” or withdraw your consent, we delete the entry immediately. Legal basis: § 25(1) TDDDG in conjunction with Art. 6(1)(a) GDPR.",
"wabenhain-recently-viewed – The identifiers of up to 6 recently viewed products (localStorage), so they can be shown to you again on product pages. Only these product identifiers are requested for display; we store no browsing history on our servers. Storage period: until you withdraw your consent or clear the site data in your browser.",
"We do not use any other technologies requiring consent, such as audience measurement. Should that change, we will name them here beforehand with provider, purpose and storage period, and only activate it after your explicit, voluntary consent via the cookie banner. Declining has no disadvantages for you.",
],
},
{
heading: "Managing and deleting cookies in your browser",
paragraphs: [
"In your browser settings you can view and delete stored cookies and site data at any time, and block cookies entirely or in part. Instructions can be found in your browser's help (e.g. Chrome, Firefox, Safari, Edge).",
"If you block technically necessary cookies, some features will stop working, in particular signing in to your customer account and completing an order.",
],
},
{
heading: "Your rights",
paragraphs: [
"You have the right of access, rectification, erasure, restriction of processing, data portability and to object to processing based on legitimate interests (Art. 15 to 21 GDPR). You may also lodge a complaint with a data protection supervisory authority (Art. 77 GDPR). Details and contact options are in our privacy policy.",
],
},
{
heading: "Changing your choice",
paragraphs: [
"You can withdraw or change your cookie choice at any time with effect for the future (Art. 7(3) GDPR). Withdrawing is as easy as giving consent: the button below reopens the cookie banner.",
"Last updated: September 2026",
],
},
],
},
wissenListPage: {
eyebrow: "Honey Knowledge",
title: "Good to know about honey",
},
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: {
title: "Two-Factor Authentication",
subtitle: "Protects your account beyond just a password, with a code from an authenticator app.",
backLabel: "Back to settings",
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: {
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: {
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: {
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: {
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) =>
remaining <= 0
? "Already sold out"
: remaining === 1
? "Only 1 jar left"
: `Only ${remaining} jars left`,
},
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: {
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.",
copyCode: "Copy code",
codeCopied: "Copied",
},
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",
statusExpired: "Expired",
edit: "Edit",
deleteConfirm: (code) => `Really delete voucher "${code}"?`,
delete: "Delete",
noVouchersYet: "No vouchers created yet.",
},
adminVoucherEditPage: {
sectionDiscount: "Code and discount",
sectionConditions: "Conditions",
sectionValidity: "Period and status",
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: {
sectionDiscount: "Code and discount",
sectionConditions: "Conditions",
sectionValidity: "Period and status",
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: {
sectionProfile: "Profile",
sectionDanger: "Block or delete account",
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: {
sectionDetails: "Filling and stock",
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: {
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",
historyHeading: "Shipment history",
historyAriaLabel: "DHL shipment history",
estimatedDelivery: (date) => `Estimated delivery: ${date}`,
notYetScanned:
"DHL hasn't scanned the parcel yet. This can take a few hours after dispatch – please check back later.",
historyUnavailable: "The shipment history can't be loaded right now. Please try again later.",
dataSource: "Data provided by DHL",
statuses: {
"pre-transit": "Announced",
transit: "In transit",
delivered: "Delivered",
failure: "Delivery issue",
unknown: "Status unknown",
},
},
flash: {
close: "Close message",
success: "Done",
error: "Error",
info: "Notice",
},
adminArchive: {
tabsAriaLabel: "View",
tabActive: "Active",
tabArchive: "Archive",
archive: "Archive",
restore: "Restore",
archivedOn: (date) => `Archived on ${date}`,
manualHint: "manually",
autoRuleOrders:
"Shipped, picked-up and cancelled orders without an open return move here automatically after one month without changes.",
autoRuleReturns: "Accepted, rejected and refunded returns move here automatically after one month without changes.",
autoRuleProducts:
"Products with zero stock for more than a month move here automatically and return once they are back in stock. They stay visible in the shop.",
emptyArchive: "The archive is empty.",
},
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: {
sectionDetails: "Details",
sectionVisibility: "Visibility",
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. Beekeeping facts (dates, lot number, best-before date, filled quantity) always come from ApiCore; texts, prices, images, visibility and shop stock stay as maintained here. Internal ApiCore notes are not copied.",
button: "Sync everything",
resultHarvests: (created, updated, skipped, failed) =>
`Harvests: ${created} created, ${updated} updated, ${skipped} skipped${failed > 0 ? `, ${failed} failed` : ""}.`,
resultBatches: (created, updated, skipped, failed) =>
`Batches: ${created} created, ${updated} updated, ${skipped} unchanged${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.`,
protectedDrafts: (count) =>
`${count} harvest draft${count === 1 ? " was" : "s were"} already edited in the admin: type, location and text stay, only date and year come from ApiCore.`,
stockConflicts: (count) =>
`For ${count} batch${count === 1 ? "" : "es"} the shop stock exceeds the quantity filled according to ApiCore. Please check the stock under batches — it was not changed automatically.`,
lastRun: (when) => `Last complete run: ${when}`,
neverRun: "No complete run yet.",
error: "Sync with ApiCore failed.",
},
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",
sectionText: "Text and images",
sectionTaste: "Taste",
sectionPriceStock: "Price and stock",
sectionVisibility: "Visibility",
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",
sectionText: "Text and images",
sectionCategory: "Category and origin",
sectionTaste: "Taste",
sectionPriceStock: "Price and stock",
sectionVisibility: "Visibility",
create: "Create",
},
adminEmailTestPage: {
title: "Test email delivery",
intro: "Gets a token via the “Sign in with Microsoft” app registration, signs in to Microsoft 365 as SMTP_USER and sends a test email to your own address. If something fails, you see the mail server message and what to do (the explanation is in German).",
button: "Send test email",
running: "Testing …",
success: (to) => `Delivery works. The test email is on its way to ${to}.`,
notConfigured: "Email delivery is not fully set up. Without the values listed below (Kubernetes config map or secret), emails are only written to the server log.",
failureHeading: "Delivery failed",
causeLabel: "What to do",
detailLabel: "Mail server message",
configLabel: "Settings used",
authModeOAuth: "Microsoft 365, OAuth 2.0 via the SSO app",
networkHeading: "Network (IPv4)",
networkSmtp: "smtp.office365.com, port 587",
networkEntra: "login.microsoftonline.com, port 443",
networkOk: "reachable",
networkFailed: "not reachable",
missingLabel: "Missing settings",
},
adminWithdrawalsPage: {
title: "Withdrawals",
intro: "Notices submitted via the online withdrawal function (§ 356a BGB). The refund is due no later than 14 days after receipt; you may withhold it until the goods are back or proof of return has been provided.",
backToDashboard: "Back to dashboard",
empty: "No withdrawals received yet.",
statusOpen: "Open",
statusProcessed: "Done",
matched: "Order matched",
unmatched: "No matching order — please check",
mailSent: "Confirmation of receipt sent",
mailNotSent: "Confirmation of receipt NOT sent — please send manually",
receivedAt: "Received",
processedOn: (date) => `Done on ${date}`,
markProcessed: "Mark as done",
reopen: "Reopen",
viewOrder: "View order",
},
adminReturnsPage: {
refundConfirm: (orderNumber, amount) => `Refund ${amount} for order ${orderNumber} via Stripe now? This cannot be undone.`,
rejectConfirm: (orderNumber) => `Really reject the return for order ${orderNumber}?`,
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",
statusSelectLabel: "Inquiry status",
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).",
deleteHeading: "Delete order",
deleteHint:
"This order never became a business transaction: no payment, no invoice. It can therefore be removed without a trace. Reserved goods are credited back to stock.",
deleteButton: "Delete permanently",
deleteConfirm: (orderNumber) =>
`Permanently delete order ${orderNumber}? This cannot be undone.`,
deleteBlocked:
"This order cannot be deleted — a payment or an invoice is attached to it, or it is already being processed. Invoices and payment records are subject to statutory retention. Cancel the order instead: that refunds the money, returns the goods to stock and keeps the record.",
},
adminOrdersPage: {
reopenConfirm:
"This order is cancelled: the goods were restocked and any payment refunded. Reopening deducts the goods from stock again — it does NOT collect a new payment, you have to arrange that yourself. Really reopen?",
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 →",
greeting: (part, name) =>
`${part === "morning" ? "Good morning" : part === "day" ? "Hello" : "Good evening"}, ${name}.`,
todayRevenueLabel: "Revenue today",
monthRevenueLabel: "Revenue this month",
vsYesterday: (delta, up) => `${up ? "↑" : "↓"} ${delta} vs. yesterday`,
toDoHeading: "To do",
toDoOrders: (count) => (count === 1 ? "1 order waiting to ship" : `${count} orders waiting to ship`),
toDoReturns: (count) => (count === 1 ? "1 new return" : `${count} new returns`),
toDoReviews: (count) => (count === 1 ? "1 review awaiting approval" : `${count} reviews awaiting approval`),
toDoInquiries: (count) => (count === 1 ? "1 new business inquiry" : `${count} new business inquiries`),
allDoneTitle: "All done.",
allDoneText: "Nothing is waiting for you right now. Time for the bees.",
chartTooltip: (day, value) => `${day}: ${value}`,
},
adminContentPage: {
tabsLabel: "Settings sections",
tabCompany: "Company & shipping",
tabBrand: "Brand",
tabHome: "Home page",
tabPages: "Pages",
tabSocial: "Social media",
tabLegal: "Legal texts",
tabSystem: "System & tests",
systemIntro: "Checks whether the shop can work with all connected services. Values from the Kubernetes secret are never shown here, only whether they are set.",
emailTestHeading: "Email delivery",
integrationsHeading: "Integrations",
integrationsIntro: "Which services are set up. If something is missing, the names of the missing values are listed — to be added to the Kubernetes secret or config map (see docs/EINRICHTUNG.md).",
statusConfigured: "Set up",
statusNotConfigured: "Not set up",
statusMissing: (names) => `Missing: ${names}`,
statusRequired: "Required",
statusOptional: "Optional",
warningStripeTest: "Test keys active — orders look successful, but no money is collected.",
warningStripeMixed: "Secret and publishable key come from different modes (test/live) — checkout will not work like this.",
integrationNames: {
stripe: "Stripe (payments)",
stripeWebhook: "Stripe webhook (payment confirmation)",
mail: "Email delivery (Microsoft 365)",
sso: "Sign in with Microsoft",
turnstile: "Cloudflare Turnstile (CAPTCHA)",
googleReviews: "Google reviews",
dhl: "DHL (shipping labels)",
apicore: "ApiCore sync",
},
backToDashboard: "Back to dashboard",
title: "Settings",
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.",
placeholdersHeading: "Text placeholders",
placeholdersIntro:
"Write a placeholder into any text and the website shows the current value in its place. When the value changes, every text follows automatically.",
groupBrand: "Logo and favicon",
groupCompany: "Company details",
groupShipping: "Shipping",
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: {
backToWebsite: "Back to website",
navDashboard: "Dashboard",
navOrders: "Orders",
navReturns: "Returns",
navWithdrawals: "Withdrawals",
navProducts: "Products",
navHarvests: "Harvests",
navBatches: "Batches",
navBusinessInquiries: "Business inquiries",
navUsers: "Users",
navShipping: "Shipping",
navVouchers: "Vouchers",
navReviews: "Reviews",
navAuditLog: "Audit log",
navContent: "Settings",
navAriaLabel: "Admin navigation",
mobileNavAriaLabel: "Mobile admin navigation",
openMenu: "Open menu",
closeMenu: "Close menu",
loggedInAs: (name) => `Signed in as ${name}`,
logout: "Sign out",
groupSales: "Sales",
groupRange: "Range",
groupCustomers: "Customers",
groupSite: "Site",
paletteOpen: "Quick search",
paletteShortcut: "Ctrl K",
palettePlaceholder: "Where to? Search a page or action …",
paletteNoResults: "Nothing found.",
paletteSectionPages: "Pages",
paletteSectionActions: "Actions",
actionNewProduct: "Create new product",
actionNewVoucher: "Create new voucher",
actionNewUser: "Create new account",
actionNewShippingTier: "Create new shipping tier",
actionViewShop: "View shop",
badgeWaiting: (count) => `${count} waiting`,
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",
documentTitle: "Invoice",
billedToLabel: "Billed to",
invoiceNumberLabel: "Invoice number",
invoiceDateLabel: "Invoice date",
orderNumberLabel: "Order number",
vatIdLabel: "VAT ID",
itemHeader: "Item",
vatRateHeader: "VAT",
quantityHeader: "Qty",
priceHeader: "Unit price",
totalHeader: "Total",
groupHeading: (ratePercent) => `Items at ${ratePercent}% VAT`,
groupSubtotalLabel: (ratePercent) => `Subtotal ${ratePercent}%`,
subtotalLabel: "Subtotal",
shippingLabel: "Shipping",
freeShipping: "free",
discountLabel: "Discount",
taxSummaryHeading: "VAT included",
taxRateHeader: "Rate",
netHeader: "Net",
vatAmountHeader: "VAT",
grossHeader: "Gross",
smallBusinessNote: "No VAT is charged (small-business scheme, § 19 German VAT Act).",
totalLabel: "Total amount",
pricesIncludeVat: "All prices include statutory VAT.",
thankYou: "Thank you for supporting our apiary!",
pageLabel: (page, total) => `Page ${page} of ${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: {
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" },
navSettings: {
title: "Settings",
description: "Security and privacy",
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",
settings: {
title: "Settings",
subtitle: "Your account security and your data in one place.",
backLabel: "Back to my account",
securityHeading: "Security",
twoFactorHeading: "Two-factor authentication",
twoFactorActiveDescription: "Active. When you sign in, we also ask for a code from your authenticator app.",
twoFactorInactiveDescription: "Not active. We recommend protecting your account with an additional code from an authenticator app.",
twoFactorManage: "Manage",
twoFactorSetup: "Set up",
logoutAllDevicesButton: "Sign out everywhere",
},
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: "Payment Processing",
successEyebrow: "Thank You",
pendingTitle: "Your order has been received.",
successTitle: "Your order has been confirmed.",
orderNumberLabel: "Order number",
pendingText: "Your payment is still being confirmed (this can take a few business days for SEPA direct debit). We'll email you as soon as it arrives.",
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",
openEyebrow: "Payment Not Completed",
openTitle: "That didn't work, unfortunately.",
openText: "The payment was cancelled or declined and nothing was charged. Your order is still reserved for you — just try again, with another payment method if you like.",
cancelPayment: "Cancel order",
notCompletedEyebrow: "Not Completed",
notCompletedTitle: "This order was not paid.",
notCompletedText: "The payment expired or was cancelled and nothing was charged. Your cart is still filled, so you can order again at any time.",
backToCart: "Back to cart",
},
checkoutCancelPage: {
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: {
paymentHeading: "Payment",
paymentBack: "Back to your details",
paymentCancelling: "One moment…",
paymentCancelError: "That didn't work just now. Please try again.",
paymentLoading: "Loading secure payment…",
paymentLoadError: "The payment form could not be loaded. An ad blocker can prevent this.",
paymentUnavailable: "Payment is currently unavailable. Please try again later or get in touch with us.",
eyebrow: "Checkout",
emptyCart: "Your cart is empty",
yourDetails: "Your Details",
stepPayment: "Payment",
stepsAriaLabel: "Checkout steps",
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:
"In the next step you pay securely right here on this page: PayPal, credit card, SEPA direct debit or Klarna, processed by 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: {
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: {
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: {
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: {
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: {
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: {
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: {
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",
},
},
};