// Translatable strings for invoice rendering. Two languages: de (default), en. export type InvoiceLanguage = "de" | "en"; export interface InvoiceStrings { invoice: string; stornoInvoice: string; offer: string; offerNumber: string; offerDate: string; offerValidUntil: (until: string) => string; stornoReference: (originalNumber: string) => string; invoiceNumber: string; invoiceDate: string; deliveryDate: string; customerVatId: string; position: string; description: string; quantity: string; unitPrice: string; totalPrice: string; netTotal: string; vatLine: (ratePct: string) => string; grossTotal: string; salutationGeneric: string; thankYouLine: string; closing: string; paymentTerms: (days: number, dueDate: string) => string; paymentTermsImmediate: string; giroCodeCaption: string; reverseChargeNotice: string; exportNotice: string; kleinunternehmerNotice: string; pieceUnit: string; page: (current: number, total: number) => string; legalCourtLabel: string; fnLabel: string; vatIdLabel: string; taxNumberLabel: string; ownerLabel: string; ibanLabel: string; bicLabel: string; bankLabel: string; recipientLabel: string; amountLabel: string; referenceLabel: string; addressHeading: string; contactHeading: string; legalHeading: string; bankHeading: string; emailLabel: string; webLabel: string; phoneLabel: string; paidStamp: string; paymentMethodLabel: string; paymentStatusLabel: string; paymentStatusPaid: string; paymentStatusUnpaid: string; paymentStatusPartial: string; paymentStatusRefunded: string; } /** Status displayed for the order's payment, derived from Shopify's * `displayFinancialStatus`. */ export type PaymentStatus = "paid" | "unpaid" | "partial" | "refunded"; export function paymentStatusLabel( status: PaymentStatus, strings: InvoiceStrings, ): string { switch (status) { case "paid": return strings.paymentStatusPaid; case "partial": return strings.paymentStatusPartial; case "refunded": return strings.paymentStatusRefunded; default: return strings.paymentStatusUnpaid; } } /** Maps Shopify's `displayFinancialStatus` to our condensed enum. Values not * signalling actual receipt of money map to "unpaid". */ export function derivePaymentStatus( displayFinancialStatus: string | null | undefined, ): PaymentStatus { const v = (displayFinancialStatus || "").toUpperCase(); if (v === "PAID") return "paid"; if (v === "PARTIALLY_PAID") return "partial"; if (v === "REFUNDED" || v === "PARTIALLY_REFUNDED") return "refunded"; return "unpaid"; } const de: InvoiceStrings = { invoice: "Rechnung", stornoInvoice: "Stornorechnung", offer: "Angebot", offerNumber: "Angebots-Nr.", offerDate: "Angebotsdatum", offerValidUntil: (d) => `Dieses Angebot ist gültig bis ${d}.`, stornoReference: (n) => `Storno zu Rechnung Nr. ${n}`, invoiceNumber: "Rechnungs-Nr.", invoiceDate: "Rechnungsdatum", deliveryDate: "Lieferdatum", customerVatId: "Deine USt-Id.", position: "Pos.", description: "Beschreibung", quantity: "Menge", unitPrice: "Einzelpreis", totalPrice: "Gesamtpreis", netTotal: "Gesamtbetrag netto", vatLine: (r) => `zzgl. Umsatzsteuer ${r}`, grossTotal: "Gesamtbetrag brutto", salutationGeneric: "Hallo,", thankYouLine: "vielen Dank für deine Bestellung. Wir berechnen dir folgende Leistungen:", closing: "Danke für deinen Einkauf", paymentTerms: (days, due) => `Bitte überweise den Rechnungsbetrag innerhalb von ${days} Tagen, spätestens bis zum ${due}, auf das unten angegebene Konto. Bei Fragen zur Rechnung sind wir gerne für dich da.`, paymentTermsImmediate: "Der Rechnungsbetrag ist sofort nach Erhalt zur Zahlung fällig.", giroCodeCaption: "GiroCode", reverseChargeNotice: "Steuerschuldnerschaft des Leistungsempfängers gemäß Art. 196 MwStSystRL (Reverse Charge).", exportNotice: "Steuerfreie Ausfuhrlieferung gemäß § 7 UStG.", kleinunternehmerNotice: "Gemäß § 6 Abs. 1 Z 27 UStG wird keine Umsatzsteuer ausgewiesen (Kleinunternehmer).", pieceUnit: "Stk", page: (c, t) => `${c}/${t}`, legalCourtLabel: "Amtsgericht", fnLabel: "FN", vatIdLabel: "UID", taxNumberLabel: "St.Nr.", ownerLabel: "Inhaber", ibanLabel: "IBAN", bicLabel: "BIC", bankLabel: "Bank", recipientLabel: "Empfänger", amountLabel: "Betrag", referenceLabel: "Referenz", addressHeading: "Adresse", contactHeading: "Kontakt", legalHeading: "Rechtliches", bankHeading: "Bankverbindung", emailLabel: "E-Mail", webLabel: "Web", phoneLabel: "Tel.", paidStamp: "BEZAHLT", paymentMethodLabel: "Zahlart", paymentStatusLabel: "Zahlstatus", paymentStatusPaid: "Bezahlt", paymentStatusUnpaid: "Offen", paymentStatusPartial: "Teilweise bezahlt", paymentStatusRefunded: "Erstattet", }; const en: InvoiceStrings = { invoice: "Invoice", stornoInvoice: "Cancellation invoice", offer: "Offer", offerNumber: "Offer no.", offerDate: "Offer date", offerValidUntil: (d) => `This offer is valid until ${d}.`, stornoReference: (n) => `Cancels invoice no. ${n}`, invoiceNumber: "Invoice no.", invoiceDate: "Invoice date", deliveryDate: "Delivery date", customerVatId: "Your VAT ID", position: "Pos.", description: "Description", quantity: "Qty", unitPrice: "Unit price", totalPrice: "Total", netTotal: "Net total", vatLine: (r) => `plus VAT ${r}`, grossTotal: "Gross total", salutationGeneric: "Dear Sir or Madam,", thankYouLine: "Thank you for your order. We hereby invoice you for the following:", closing: "Thank you for your purchase.", paymentTerms: (days, due) => `Please transfer the invoice amount within ${days} days, no later than ${due}, to the bank account shown below.`, paymentTermsImmediate: "The invoice amount is due immediately upon receipt.", giroCodeCaption: "GiroCode", reverseChargeNotice: "Reverse charge: VAT to be accounted for by the recipient pursuant to Art. 196 of Council Directive 2006/112/EC.", exportNotice: "Tax-exempt export delivery pursuant to § 7 UStG.", kleinunternehmerNotice: "VAT is not charged pursuant to § 6 (1) 27 UStG (small-business exemption).", pieceUnit: "pcs", page: (c, t) => `${c}/${t}`, legalCourtLabel: "Commercial court", fnLabel: "FN", vatIdLabel: "VAT ID", taxNumberLabel: "Tax no.", ownerLabel: "Owner", ibanLabel: "IBAN", bicLabel: "BIC", bankLabel: "Bank", recipientLabel: "Recipient", amountLabel: "Amount", referenceLabel: "Reference", addressHeading: "Address", contactHeading: "Contact", legalHeading: "Legal", bankHeading: "Bank details", emailLabel: "E-mail", webLabel: "Web", phoneLabel: "Tel.", paidStamp: "PAID", paymentMethodLabel: "Payment method", paymentStatusLabel: "Payment status", paymentStatusPaid: "Paid", paymentStatusUnpaid: "Outstanding", paymentStatusPartial: "Partially paid", paymentStatusRefunded: "Refunded", }; // Locale → invoice language. We only render in German (`de`) when the // caller is explicitly German-speaking (de, de-AT, de-DE, de_CH, …). // Everything else (it, fr, es, en, …) falls back to English so that // non-German-speaking customers don't receive a German invoice. Callers // that have a per-shop default fall back to it via // `pickLanguage(customerLocale ?? settings.defaultLanguage)`, which is why // `null`/`undefined` still maps to `de` (the legacy default for the // Austrian shops this app was built for). export function pickLanguage(input: string | null | undefined): InvoiceLanguage { if (!input) return "de"; const v = input.toLowerCase(); if (v.startsWith("de")) return "de"; return "en"; } export function getStrings(language: InvoiceLanguage): InvoiceStrings { return language === "en" ? en : de; }