diff --git a/src/api.ts b/src/api.ts index b74157b..b77ee53 100644 --- a/src/api.ts +++ b/src/api.ts @@ -269,6 +269,8 @@ export interface VmCustomTemplateRequest { export interface VmCustomPrice { currency: string; amount: number; + interval_amount: number; + interval_type: CostPlanIntervalType; } export interface VmTemplateResponse { diff --git a/src/components/cost.tsx b/src/components/cost.tsx index 0310ecd..37921ab 100644 --- a/src/components/cost.tsx +++ b/src/components/cost.tsx @@ -1,14 +1,14 @@ -import { FormattedMessage, FormattedNumber } from "react-intl"; +import { FormattedNumber, useIntl } from "react-intl"; import useLogin from "../hooks/login"; import { taxRateFor, useTaxRates } from "../hooks/tax"; import useExchangeRates from "../hooks/useExchangeRates"; -import { convertAmount } from "../utils/currency"; +import { convertAmount, formatIntervalText } from "../utils/currency"; interface Price { currency: string; amount: number; } -type Cost = Price & { interval_type?: string }; +type Cost = Price & { interval_type?: string; interval_amount?: number }; function grossPrice(p: T, rate: number): T { return { ...p, amount: Math.round(p.amount * (1 + rate / 100)) }; @@ -51,6 +51,7 @@ export default function CostLabel({ currency: target, amount: Math.round(converted), interval_type: base.interval_type, + interval_amount: base.interval_amount, }} converted={true} />{" "} @@ -68,32 +69,8 @@ export function IntervalSuffix({ interval: string; n?: number; }) { - const count = n ?? 1; - switch (interval) { - case "day": - return ( - - ); - case "month": - return ( - - ); - case "year": - return ( - - ); - default: - return <>{interval}; - } + const intl = useIntl(); + return <>{formatIntervalText(intl, interval, n ?? 1)}; } export function CostAmount({ @@ -137,7 +114,8 @@ export function CostAmount({ {cost.currency === "BTC" && " sats"} {cost.interval_type && ( <> - / + /{(cost.interval_amount ?? 1) > 1 && <>{cost.interval_amount} } + )} diff --git a/src/components/region-landing.tsx b/src/components/region-landing.tsx index 9562c44..af09ed9 100644 --- a/src/components/region-landing.tsx +++ b/src/components/region-landing.tsx @@ -97,7 +97,7 @@ export function RegionLanding({ {from && (

, }} diff --git a/src/components/vps-custom.tsx b/src/components/vps-custom.tsx index 63b3f7f..faa0544 100644 --- a/src/components/vps-custom.tsx +++ b/src/components/vps-custom.tsx @@ -241,8 +241,8 @@ export function VpsCustomOrder({ name: "custom", amount: price?.amount ?? 0, currency: (price?.currency as "BTC" | "EUR" | "USD") ?? "USD", - interval_amount: 1, - interval_type: CostPlanIntervalType.MONTH, + interval_amount: price?.interval_amount ?? 1, + interval_type: price?.interval_type ?? CostPlanIntervalType.MONTH, }; // Reset selected template when region changes diff --git a/src/locales/ar.json b/src/locales/ar.json index 23ae06c..ef65852 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -18,7 +18,10 @@ "defaultMessage": "قم بتشغيل خادم الوسائط الخاص بك من نوع Blossom وNIP-96 لـ {price}‏/شهر. يمكن إعداد Route96 في دقائق معدودة على اسم النطاق الخاص به، مع توفير مساحة تخزين دائمة وبروتوكول أمان طبقة النقل (TLS)." }, "+JVG/q": { - "defaultMessage": "{n,plural,one{عام} other{سنوات}}" + "defaultMessage": "{n,plural,one{سنة} other{سنوات}}" + }, + "+Mo6Oy": { + "defaultMessage": "قم بتشغيل عقدة بيتكوين كاملة على خادم افتراضي خاص في {regions}يمكنك الدفع بعملة البيتكوين عبر شبكة لايتنينج أو مباشرةً على البلوك تشين. ابتداءً من: {price} باستثناء ضريبة القيمة المضافة." }, "+Qebzr": { "defaultMessage": "عنوان على السلسلة." @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "قم بتشغيل عقدة بيتكوين كاملة على خادم افتراضي خاص في {regions}. ادفع بالبيتكوين عبر شبكة \"لايتنينغ\" أو مباشرةً على سلسلة الكتل. من {price}شهريًا، باستثناء ضريبة القيمة المضافة." - }, "0gAiqM": { "defaultMessage": "استضافة خوادم افتراضية خاصة في أيرلندا." }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "تم إيقاف التجديد التلقائي. يرجى تجديد الاشتراك يدويًا قبل انتهاء صلاحيته لتجنب أي انقطاع في الخدمة." }, + "V/V4bz": { + "defaultMessage": "خدمة استضافة VPS في دبلن، أيرلندا، تبدأ من {price} باستثناء ضريبة القيمة المضافة. صمم المواصفات التي تريدها بنفسك حتى {maxCpu} وحدة معالجة مركزية افتراضية. الدفع بعملة البيتكوين عبر شبكة لايتنينغ أو مباشرة على البلوك تشين." + }, "V7fgpY": { "defaultMessage": "جارٍ إكمال تسجيل الدخول…" }, @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "معدل/تقييم" }, - "c3o/3B": { - "defaultMessage": "من {price} شهريًا، باستثناء ضريبة القيمة المضافة." - }, "c5aQL+": { "defaultMessage": "اختر تطبيقًا، وأعطه اسمًا، وادفع. سيظهر التطبيق على نطاقه الخاص مع تفعيل بروتوكول TLS تلقائيًا — لا حاجة لتحديث نظام التشغيل، ولا حاجة لتكوين Docker، ولا حاجة لتجديد الشهادة." }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "موقعنا في دبلن. صمم الآلة التي تريدها في الفترة ما بين {minCpu}–{maxCpu} وحدة معالجة مركزية افتراضية {minMemory}–{maxMemory}وادفع ثمنه بالبيتكوين." }, - "dTTuZ+": { - "defaultMessage": "خدمة استضافة VPS في دبلن، أيرلندا، ابتداءً من {price}/شهريًا، بدون ضريبة القيمة المضافة. صمم المواصفات التي تناسبك. {maxCpu} وحدة معالجة مركزية افتراضية. الدفع بعملة البيتكوين عبر شبكة لايتنينج أو مباشرة." - }, "dUQeuG": { "defaultMessage": "إنشاء حساب بمفتاح مرور" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "قم بتكوين جهازك" }, + "e55cj/": { + "defaultMessage": "من {price}، باستثناء ضريبة القيمة المضافة." + }, "eKEL/g": { "defaultMessage": "قيد الانتظار." }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "سجّل الدخول لتقديم طلبك" }, + "fNZGof": { + "defaultMessage": "خدمة استضافة VPS في لندن ابتداءً من {price} باستثناء ضريبة القيمة المضافة. {minCpu}-{maxCpu} وحدة معالجة مركزية افتراضية، بحد أقصى {maxDisk} {diskType}يمكنك الدفع بعملة البيتكوين عبر شبكة لايتنينغ أو مباشرة على البلوك تشين." + }, "fblXmJ": { "defaultMessage": "اسم المثيل" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "إنشاء حساب" }, - "i/LmLC": { - "defaultMessage": "خدمة استضافة VPS في لندن تبدأ من {price}شهريًا، باستثناء ضريبة القيمة المضافة. {minCpu}–{maxCpu} وحدة معالجة مركزية افتراضية، بحد أقصى {maxDisk} {diskType}. ادفع بالبيتكوين عبر شبكة لايتنينغ أو مباشرةً." - }, "i2/SmH": { "defaultMessage": "تعمل عملية الفوترة مثل اشتراك الخادم الافتراضي الخاص (VPS)، ولا توجد رسوم إعداد لأي تطبيق." }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "خدمات استضافة VPS في كيبيك، كندا، تبدأ من {price} باستثناء ضريبة القيمة المضافة. {minCpu}–{maxCpu} وحدة معالجة مركزية افتراضية، بحد أقصى {maxDisk} {diskType}ادفع بالبيتكوين عبر شبكة لايتنينغ أو مباشرةً على البلوك تشين." + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "استخدم مفتاح المستلم المخصص." }, - "vIv3vC": { - "defaultMessage": "خدمة استضافة VPS في كيبيك، كندا، تبدأ من {price}شهريًا، باستثناء ضريبة القيمة المضافة. {minCpu}–{maxCpu} وحدة معالجة مركزية افتراضية، بحد أقصى {maxDisk} {diskType}. ادفع بالبيتكوين عبر شبكة \"لايتنينغ\" أو مباشرةً على البلوك تشين." - }, "vONi+O": { "defaultMessage": "دولة" }, diff --git a/src/locales/de.json b/src/locales/de.json index 3cdc875..e279016 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -20,6 +20,9 @@ "+JVG/q": { "defaultMessage": "{n,plural,one{Jahr} other{Jahre}}" }, + "+Mo6Oy": { + "defaultMessage": "Betreiben Sie einen vollständigen Bitcoin-Knoten auf einem VPS in {regions}Bezahlen Sie mit Bitcoin über das Lightning-Netzwerk oder direkt in der Blockchain. Ab {price} zzgl. Mehrwertsteuer." + }, "+Qebzr": { "defaultMessage": "On-Chain-Adresse" }, @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "Betreiben Sie einen vollständigen Bitcoin-Knoten auf einem VPS in {regions}. Bezahlen Sie mit Bitcoin über das Lightning-Netzwerk oder direkt in der Bitcoin-Blockchain. Ab {price}pro Monat, zuzüglich Mehrwertsteuer." - }, "0gAiqM": { "defaultMessage": "VPS-Hosting in Irland" }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "Die automatische Verlängerung ist deaktiviert. Bitte verlängern Sie die Mitgliedschaft manuell, bevor sie ausläuft, um eine Unterbrechung zu vermeiden." }, + "V/V4bz": { + "defaultMessage": "VPS-Hosting in Dublin, Irland, ab {price} zzgl. Mehrwertsteuer. Stellen Sie Ihre eigene Konfiguration zusammen, bis zu {maxCpu} vCPU. Zahlung in Bitcoin per Lightning Network oder direkt auf der Blockchain möglich." + }, "V7fgpY": { "defaultMessage": "Anmeldung wird abgeschlossen…" }, @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "Bewertung" }, - "c3o/3B": { - "defaultMessage": "Von {price} pro Monat, zuzüglich Mehrwertsteuer." - }, "c5aQL+": { "defaultMessage": "Wählen Sie eine App aus, geben Sie ihr einen Namen und bezahlen Sie. Sie wird auf ihrem eigenen Hostnamen bereitgestellt, wobei TLS bereits aktiviert ist – es sind keine Betriebssystem-Updates erforderlich, keine Docker-Konfigurationen notwendig und keine Zertifikate müssen erneuert werden." }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "Unser Standort in Dublin. Hier können Sie die Maschine Ihrer Wahl zusammenstellen. {minCpu}–{maxCpu} vCPU und {minMemory}–{maxMemory}und bezahlen Sie es mit Bitcoin." }, - "dTTuZ+": { - "defaultMessage": "VPS-Hosting in Dublin, Irland, ab {price}pro Monat, zuzüglich Mehrwertsteuer. Stellen Sie Ihre eigene Konfiguration zusammen, bis zu {maxCpu} vCPU. Zahlung in Bitcoin über das Lightning-Netzwerk oder direkt auf der Blockchain möglich." - }, "dUQeuG": { "defaultMessage": "Passkey-Konto erstellen" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "Stelle deine Maschine zusammen" }, + "e55cj/": { + "defaultMessage": "Von {price}zzgl. Mehrwertsteuer." + }, "eKEL/g": { "defaultMessage": "Ausstehend" }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "Anmelden, um eine Bestellung aufzugeben" }, + "fNZGof": { + "defaultMessage": "VPS-Hosting in London ab {price} zzgl. Mehrwertsteuer. {minCpu}–{maxCpu} virtuelle CPU, bis zu {maxDisk} {diskType}Bezahlen Sie mit Bitcoin über das Lightning-Netzwerk oder direkt auf der Blockchain." + }, "fblXmJ": { "defaultMessage": "Instanzname" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "Konto erstellen" }, - "i/LmLC": { - "defaultMessage": "VPS-Hosting in London ab {price}pro Monat, zuzüglich Mehrwertsteuer. {minCpu}–{maxCpu} vCPU, bis zu {maxDisk} {diskType}Bezahlen Sie in Bitcoin über das Lightning-Netzwerk oder direkt in der Bitcoin-Blockchain." - }, "i2/SmH": { "defaultMessage": "Die Abrechnung funktioniert wie ein VPS-Abonnement, wobei für keine der Apps eine Einrichtungsgebühr anfällt." }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "VPS-Hosting in Québec, Kanada, ab {price} zzgl. Mehrwertsteuer. {minCpu}–{maxCpu} virtuelle CPU, bis zu {maxDisk} {diskType}Sie können mit Bitcoin über das Lightning-Netzwerk oder direkt (on-chain) bezahlen." + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "Benutzen Sie einen benutzerdefinierten Empfängerschlüssel." }, - "vIv3vC": { - "defaultMessage": "VPS-Hosting in Québec, Kanada, ab {price}pro Monat, zuzüglich Mehrwertsteuer. {minCpu}–{maxCpu} vCPU, bis zu {maxDisk} {diskType}Bezahlen Sie mit Bitcoin über das Lightning-Netzwerk oder direkt in der Bitcoin-Blockchain." - }, "vONi+O": { "defaultMessage": "Land" }, diff --git a/src/locales/en.json b/src/locales/en.json index 052bdcd..9c1fbc0 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -20,6 +20,9 @@ "+JVG/q": { "defaultMessage": "{n, plural, one {year} other {years}}" }, + "+Mo6Oy": { + "defaultMessage": "Run a Bitcoin full node on a VPS in {regions}. Pay in Bitcoin over Lightning or on-chain. From {price} ex-VAT." + }, "+Qebzr": { "defaultMessage": "On-chain Address" }, @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "Run a Bitcoin full node on a VPS in {regions}. Pay in Bitcoin over Lightning or on-chain. From {price}/month ex-VAT." - }, "0gAiqM": { "defaultMessage": "VPS hosting in Ireland" }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "Automatic renewal is off. Renew manually before expiry to avoid interruption." }, + "V/V4bz": { + "defaultMessage": "VPS hosting in Dublin, Ireland from {price} ex-VAT. Build your own spec up to {maxCpu} vCPU. Pay in Bitcoin over Lightning or on-chain." + }, "V7fgpY": { "defaultMessage": "Completing sign-in…" }, @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "Rate" }, - "c3o/3B": { - "defaultMessage": "From {price} per month, excluding VAT." - }, "c5aQL+": { "defaultMessage": "Pick an app, give it a name, pay. It comes up on its own hostname with TLS already working — no OS to patch, no Docker to configure, no certificate to renew." }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "Our Dublin site. Build the machine you want between {minCpu}–{maxCpu} vCPU and {minMemory}–{maxMemory}, and pay for it in Bitcoin." }, - "dTTuZ+": { - "defaultMessage": "VPS hosting in Dublin, Ireland from {price}/month ex-VAT. Build your own spec up to {maxCpu} vCPU. Pay in Bitcoin over Lightning or on-chain." - }, "dUQeuG": { "defaultMessage": "Create a passkey account" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "Build your machine" }, + "e55cj/": { + "defaultMessage": "From {price}, excluding VAT." + }, "eKEL/g": { "defaultMessage": "Pending" }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "Login To Order" }, + "fNZGof": { + "defaultMessage": "VPS hosting in London from {price} ex-VAT. {minCpu}–{maxCpu} vCPU, up to {maxDisk} {diskType}. Pay in Bitcoin over Lightning or on-chain." + }, "fblXmJ": { "defaultMessage": "Instance name" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "Create account" }, - "i/LmLC": { - "defaultMessage": "VPS hosting in London from {price}/month ex-VAT. {minCpu}–{maxCpu} vCPU, up to {maxDisk} {diskType}. Pay in Bitcoin over Lightning or on-chain." - }, "i2/SmH": { "defaultMessage": "Billing works like a VPS subscription, with no setup fee on any app." }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "VPS hosting in Quebec, Canada from {price} ex-VAT. {minCpu}–{maxCpu} vCPU, up to {maxDisk} {diskType}. Pay in Bitcoin over Lightning or on-chain." + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "Use custom recipient key" }, - "vIv3vC": { - "defaultMessage": "VPS hosting in Quebec, Canada from {price}/month ex-VAT. {minCpu}–{maxCpu} vCPU, up to {maxDisk} {diskType}. Pay in Bitcoin over Lightning or on-chain." - }, "vONi+O": { "defaultMessage": "Country" }, diff --git a/src/locales/es.json b/src/locales/es.json index 7f0ca6c..9ce1104 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -20,6 +20,9 @@ "+JVG/q": { "defaultMessage": "{n,plural,one{año} other{años}}" }, + "+Mo6Oy": { + "defaultMessage": "Ejecute un nodo completo de Bitcoin en un servidor virtual privado (VPS) ubicado en {regions}Pague con Bitcoin a través de Lightning o directamente en la cadena de bloques. Desde {price} sin IVA." + }, "+Qebzr": { "defaultMessage": "Dirección en la cadena de bloques" }, @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "Ejecute un nodo completo de Bitcoin en un VPS en {regions}. Pague con Bitcoin a través de Lightning o directamente en la cadena de bloques. Desde {price}/mes, sin IVA." - }, "0gAiqM": { "defaultMessage": "Alojamiento VPS en Irlanda." }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "La renovación automática está desactivada. Para evitar interrupciones, renueve manualmente antes de la fecha de vencimiento." }, + "V/V4bz": { + "defaultMessage": "Alojamiento VPS en Dublín, Irlanda, desde {price} precio sin IVA. Configure las especificaciones que desee. {maxCpu} vCPU. Pague con Bitcoin a través de Lightning o directamente en la cadena de bloques." + }, "V7fgpY": { "defaultMessage": "Completando el inicio de sesión…" }, @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "Tasa." }, - "c3o/3B": { - "defaultMessage": "Desde {price} por mes, IVA no incluido." - }, "c5aQL+": { "defaultMessage": "Elige una aplicación, dale un nombre y paga. Se implementa automáticamente en su propio nombre de dominio con TLS ya configurado: no es necesario aplicar parches al sistema operativo, ni configurar Docker, ni renovar el certificado." }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "Nuestras instalaciones en Dublín. Diseña la máquina que desees. {minCpu}–{maxCpu} vCPU y {minMemory}–{maxMemory}y páguelo con Bitcoin." }, - "dTTuZ+": { - "defaultMessage": "Alojamiento VPS en Dublín, Irlanda, a partir de {price}/mes, IVA no incluido. Configure las especificaciones que desee. {maxCpu} vCPU. Pague con Bitcoin a través de Lightning o directamente en la cadena de bloques." - }, "dUQeuG": { "defaultMessage": "Crear cuenta con clave de acceso" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "Configura tu máquina" }, + "e55cj/": { + "defaultMessage": "Desde {price}, sin incluir el IVA." + }, "eKEL/g": { "defaultMessage": "Pendiente" }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "Inicie sesión para realizar su pedido." }, + "fNZGof": { + "defaultMessage": "Alojamiento VPS en Londres desde {price} sin IVA. {minCpu}–{maxCpu} vCPU, hasta {maxDisk} {diskType}Puede pagar con Bitcoin mediante la red Lightning o directamente en la cadena de bloques." + }, "fblXmJ": { "defaultMessage": "Nombre de la instancia" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "Crear cuenta" }, - "i/LmLC": { - "defaultMessage": "Alojamiento VPS en Londres desde {price}/mes, sin IVA. {minCpu}–{maxCpu} vCPU, hasta {maxDisk} {diskType}. Pague con Bitcoin a través de Lightning o directamente en la cadena de bloques." - }, "i2/SmH": { "defaultMessage": "El sistema de facturación funciona de forma similar a una suscripción de VPS, sin costes de configuración para ninguna aplicación." }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "Alojamiento VPS en Quebec, Canadá, desde {price} sin IVA. {minCpu}–{maxCpu} vCPU, hasta {maxDisk} {diskType}Puede pagar con Bitcoin a través de Lightning o directamente en la cadena de bloques." + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "Utilice una clave de destinatario personalizada." }, - "vIv3vC": { - "defaultMessage": "Alojamiento VPS en Quebec, Canadá, a partir de {price}/mes, sin IVA. {minCpu}–{maxCpu} vCPU, hasta {maxDisk} {diskType}. Pague con Bitcoin a través de Lightning o directamente en la cadena de bloques." - }, "vONi+O": { "defaultMessage": "País" }, diff --git a/src/locales/fr.json b/src/locales/fr.json index 656d948..3812b8b 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -18,7 +18,10 @@ "defaultMessage": "Exploitez votre propre serveur multimédia Blossom et NIP-96 pour {price}/mois. Route96 peut être déployé en quelques minutes sur son propre nom de domaine, avec un stockage persistant et le protocole TLS inclus." }, "+JVG/q": { - "defaultMessage": "{n,plural,one{an} other{ans}}" + "defaultMessage": "{n,plural,one{an} other{années}}" + }, + "+Mo6Oy": { + "defaultMessage": "Exécutez un nœud complet Bitcoin sur un serveur virtuel privé (VPS) situé à {regions}Vous pouvez payer en Bitcoin via le réseau Lightning ou directement sur la chaîne de blocs. À partir de {price} hors TVA." }, "+Qebzr": { "defaultMessage": "Adresse sur la chaîne" @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "Exécutez un nœud Bitcoin complet sur un serveur virtuel privé (VPS) situé à {regions}. Payez en Bitcoin via Lightning ou directement sur la chaîne de blocs. À partir de {price}par mois, hors TVA." - }, "0gAiqM": { "defaultMessage": "Hébergement VPS en Irlande." }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "Le renouvellement automatique est désactivé. Renouvelez manuellement avant la date d’expiration pour éviter toute interruption." }, + "V/V4bz": { + "defaultMessage": "Hébergement VPS à Dublin, en Irlande, à partir de : {price} hors TVA. Configurez votre modèle personnalisé jusqu’à… {maxCpu} vCPU. Paiement accepté en Bitcoin via Lightning Network ou directement sur la chaîne de blocs." + }, "V7fgpY": { "defaultMessage": "Connexion en cours…" }, @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "Taux" }, - "c3o/3B": { - "defaultMessage": "De {price} par mois, hors TVA." - }, "c5aQL+": { "defaultMessage": "Choisissez une application, donnez-lui un nom, effectuez le paiement. Elle sera accessible via son propre nom de domaine, avec TLS déjà configuré – aucune mise à jour du système d’exploitation à effectuer, aucune configuration de Docker, aucun certificat à renouveler." }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "Notre site de Dublin. Créez la machine que vous souhaitez. {minCpu}–{maxCpu} vCPU et {minMemory}–{maxMemory}et payez-le en Bitcoin." }, - "dTTuZ+": { - "defaultMessage": "Hébergement VPS à Dublin, en Irlande, à partir de {price}/mois, hors TVA. Configurez les spécifications de votre choix. {maxCpu} vCPU. Paiement accepté en Bitcoin via Lightning Network ou directement sur la blockchain." - }, "dUQeuG": { "defaultMessage": "Créer un compte avec une clé d'accès" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "Configurez votre machine" }, + "e55cj/": { + "defaultMessage": "À partir de {price}, hors taxes sur la valeur ajoutée." + }, "eKEL/g": { "defaultMessage": "En attente" }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "Connectez-vous pour passer votre commande." }, + "fNZGof": { + "defaultMessage": "Hébergement VPS à Londres, à partir de {price} hors TVA. {minCpu}–{maxCpu} vCPU, jusqu’à {maxDisk} {diskType}Vous pouvez payer en Bitcoin, soit via le réseau Lightning, soit directement sur la chaîne principale." + }, "fblXmJ": { "defaultMessage": "Nom de l’instance" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "Créer un compte" }, - "i/LmLC": { - "defaultMessage": "Hébergement VPS à Londres à partir de {price}par mois, hors taxes. {minCpu}–{maxCpu} vCPU, jusqu’à {maxDisk} {diskType}Vous pouvez payer en Bitcoin, soit via le réseau Lightning, soit directement sur la chaîne principale." - }, "i2/SmH": { "defaultMessage": "La facturation fonctionne comme un abonnement VPS, sans frais d’installation pour aucune application." }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "Hébergement VPS au Québec, Canada, à partir de {price} hors TVA. {minCpu}–{maxCpu} vCPU, jusqu’à {maxDisk} {diskType}Vous pouvez payer en Bitcoin, soit via le réseau Lightning, soit directement sur la chaîne de blocs." + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "Utiliser une clé de destinataire personnalisée." }, - "vIv3vC": { - "defaultMessage": "Hébergement VPS au Québec, Canada, à partir de {price}par mois, hors TVA. {minCpu}–{maxCpu} vCPU, jusqu’à {maxDisk} {diskType}. Payez en Bitcoin via Lightning Network ou directement sur la chaîne principale." - }, "vONi+O": { "defaultMessage": "Pays" }, diff --git a/src/locales/ja.json b/src/locales/ja.json index 545ef1b..7c899ae 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -20,6 +20,9 @@ "+JVG/q": { "defaultMessage": "{n,plural,one{年} other{年}}" }, + "+Mo6Oy": { + "defaultMessage": "VPS上でビットコインのフルノードを稼働させる。 {regions}ビットコインで、ライトニングネットワークまたはオンチェーン経由で支払ってください。 {price} 付加価値税抜き。" + }, "+Qebzr": { "defaultMessage": "オンチェーンアドレス" }, @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "VPS上でビットコインのフルノードを稼働させる。 {regions}ビットコインで、ライトニングネットワークまたはオンチェーンで支払いを受け付けます。 {price}月額料金(税別)。" - }, "0gAiqM": { "defaultMessage": "アイルランドでのVPSホスティング" }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "自動更新はオフになっています。サービスが中断されないように、有効期限が切れる前に手動で更新してください。" }, + "V/V4bz": { + "defaultMessage": "アイルランドのダブリンにあるVPSホスティングサービス {price} 消費税抜き。お客様のニーズに合わせて仕様を自由にカスタマイズできます。 {maxCpu} 仮想CPU。ライトニングネットワークまたはオンチェーンでビットコインを使ってお支払いください。" + }, "V7fgpY": { "defaultMessage": "サインインを完了しています…" }, @@ -1374,7 +1377,7 @@ "defaultMessage": "サーバーのデプロイと管理" }, "WxTcqt": { - "defaultMessage": "{n,plural,one{月} other{月}}" + "defaultMessage": "{n,plural,one{月} other{か月}}" }, "X/4SWn": { "defaultMessage": "カナダのケベック州でVPSホスティングをご利用いただけます。CPU、メモリ、ディスクをお選びください。お支払いは、ライトニングネットワークまたはオンチェーンでのビットコイン決済でお願いします。" @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "レート、割合" }, - "c3o/3B": { - "defaultMessage": "~から {price} 月額料金で、消費税は含まれません。" - }, "c5aQL+": { "defaultMessage": "アプリを選び、名前を付け、料金を支払います。すると、TLSがすでに有効になっている状態で、独自のホスト名で自動的に起動します。OSのパッチを当てたり、Dockerを設定したり、証明書を更新したりする必要はありません。" }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "当社のダブリン拠点。お客様がご希望の機械を組み立てます。 {minCpu}{maxCpu} vCPUおよび {minMemory}–{maxMemory}そして、ビットコインで支払います。" }, - "dTTuZ+": { - "defaultMessage": "アイルランドのダブリンにあるVPSホスティング(仮想専用サーバー)サービス {price}月額料金(税別)。お客様のニーズに合わせて仕様をカスタマイズできます。 {maxCpu} vCPU。ライトニングネットワークまたはオンチェーンでビットコインで支払ってください。" - }, "dUQeuG": { "defaultMessage": "パスキーアカウントを作成" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "マシンを構成する" }, + "e55cj/": { + "defaultMessage": "から {price}消費税は含まれません。" + }, "eKEL/g": { "defaultMessage": "保留中" }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "ご注文にはログインが必要です。" }, + "fNZGof": { + "defaultMessage": "ロンドンでのVPSホスティング(サービス) {price} 付加価値税(VAT)を除く。 {minCpu}{maxCpu} 仮想CPU、最大。 {maxDisk} {diskType}ビットコインで、ライトニングネットワークまたはオンチェーンでの支払いを受け付けます。" + }, "fblXmJ": { "defaultMessage": "インスタンス名" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "アカウントを作成" }, - "i/LmLC": { - "defaultMessage": "ロンドンでのVPSホスティングは、[価格]から {price}月額、税別。 {minCpu}{maxCpu} vCPU、最大 {maxDisk} {diskType}ビットコインで、ライトニングネットワークまたはオンチェーンで支払ってください。" - }, "i2/SmH": { "defaultMessage": "課金システムはVPSのサブスクリプションと同様に機能し、どのアプリにも初期費用はかかりません。" }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "カナダのケベック州におけるVPSホスティングサービス {price} 付加価値税抜き。 {minCpu}-{maxCpu} vCPU、最大 {maxDisk} {diskType}ビットコインで、ライトニングネットワークまたはオンチェーンでの支払いが可能です。" + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "カスタム受信者キーを使用する" }, - "vIv3vC": { - "defaultMessage": "カナダのケベック州におけるVPSホスティング(提供元:) {price}月額、税別。 {minCpu}–{maxCpu} vCPU、最大 {maxDisk} {diskType}ビットコインで、ライトニングネットワークまたはオンチェーンで支払ってください。" - }, "vONi+O": { "defaultMessage": "国" }, diff --git a/src/locales/ko.json b/src/locales/ko.json index 539131d..4a9de73 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -20,6 +20,9 @@ "+JVG/q": { "defaultMessage": "{n,plural,one{년} other{년}}" }, + "+Mo6Oy": { + "defaultMessage": "[특정 지역]에 있는 VPS에서 비트코인 풀 노드를 실행합니다. {regions}비트코인으로 라이트닝 네트워크 또는 온체인 방식으로 결제하세요. {price} 부가세 제외." + }, "+Qebzr": { "defaultMessage": "온체인 주소" }, @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "VPS에 비트코인 전체 노드를 실행합니다. {regions}비트코인으로 라이트닝 네트워크 또는 온체인 방식으로 결제하세요. {price}월별, 부가가치세 제외." - }, "0gAiqM": { "defaultMessage": "아일랜드에서 VPS 호스팅" }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "자동 갱신 기능이 꺼져 있습니다. 서비스 중단을 피하려면 만료되기 전에 수동으로 갱신하세요." }, + "V/V4bz": { + "defaultMessage": "아일랜드 더블린에서 제공되는 VPS 호스팅 서비스 {price} 부가세 제외 가격입니다. 원하는 사양으로 직접 구성하세요. {maxCpu} 가상 CPU. 라이트닝 네트워크 또는 온체인 방식으로 비트코인으로 결제하세요." + }, "V7fgpY": { "defaultMessage": "로그인 완료 중…" }, @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "요금" }, - "c3o/3B": { - "defaultMessage": "~에서 {price} 부가세 제외, 월별 금액." - }, "c5aQL+": { "defaultMessage": "앱을 선택하고, 이름을 지정하고, 결제하세요. 그러면 TLS가 이미 적용된 자체 호스트 이름으로 앱이 실행됩니다. 운영체제를 패치하거나, Docker를 설정하거나, 인증서를 갱신할 필요가 없습니다." }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "저희 더블린 사업장입니다. 원하는 기계를 직접 제작해 보세요. {minCpu}–{maxCpu} 가상 CPU 및 {minMemory}–{maxMemory}그리고 비트코인으로 결제하세요." }, - "dTTuZ+": { - "defaultMessage": "아일랜드 더블린에서 제공되는 VPS 호스팅 서비스 {price}/월, 부가가치세 별도. 원하는 사양으로 직접 구성하세요. {maxCpu} 가상 CPU. 비트코인을 사용하여 라이트닝 네트워크 또는 온체인 방식으로 결제하세요." - }, "dUQeuG": { "defaultMessage": "패스키 계정 만들기" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "머신 구성하기" }, + "e55cj/": { + "defaultMessage": "부터 {price}부가세는 제외합니다." + }, "eKEL/g": { "defaultMessage": "대기 중" }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "주문하려면 로그인하세요." }, + "fNZGof": { + "defaultMessage": "런던에서 제공되는 VPS 호스팅 서비스 {price} 부가가치세 제외. {minCpu}–{maxCpu} 가상 CPU, 최대 {maxDisk} {diskType}비트코인으로 라이트닝 네트워크 또는 온체인 방식으로 결제하세요." + }, "fblXmJ": { "defaultMessage": "인스턴스 이름" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "계정 만들기" }, - "i/LmLC": { - "defaultMessage": "런던에서 이용 가능한 VPS 호스팅, 가격은 다음과 같습니다. {price}월별, 부가가치세 제외. {minCpu}–{maxCpu} vCPU, 최대 {maxDisk} {diskType}비트코인을 사용하여 라이트닝 네트워크 또는 온체인 방식으로 결제하세요." - }, "i2/SmH": { "defaultMessage": "요금 부과는 VPS 구독 방식과 동일하며, 모든 앱에 대해 초기 설정 비용이 부과되지 않습니다." }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "캐나다 퀘벡에서 제공하는 VPS 호스팅 서비스 {price} 부가가치세 제외. {minCpu}–{maxCpu} 가상 CPU, 최대 {maxDisk} {diskType}비트코인을 라이트닝 네트워크 또는 온체인 방식으로 결제하세요." + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "사용자 지정 수신자 키를 사용하세요." }, - "vIv3vC": { - "defaultMessage": "캐나다 퀘벡에서 이용 가능한 VPS 호스팅 서비스 {price}월별, 부가가치세 제외. {minCpu}–{maxCpu} vCPU, 최대 {maxDisk} {diskType}비트코인을 사용하여 라이트닝 네트워크 또는 온체인 방식으로 결제하세요." - }, "vONi+O": { "defaultMessage": "국가" }, diff --git a/src/locales/pt.json b/src/locales/pt.json index fa9299b..74c3c19 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -20,6 +20,9 @@ "+JVG/q": { "defaultMessage": "{n,plural,one{ano} other{anos}}" }, + "+Mo6Oy": { + "defaultMessage": "Execute um nó completo de Bitcoin num servidor virtual privado (VPS) em {regions}Pague em Bitcoin através da rede Lightning ou diretamente na blockchain. A partir de {price} preço sem IVA." + }, "+Qebzr": { "defaultMessage": "Endereço na blockchain" }, @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "Execute um nó completo de Bitcoin num VPS em {regions}. Pague em Bitcoin através da Lightning Network ou diretamente na blockchain. De {price}por mês, sem IVA." - }, "0gAiqM": { "defaultMessage": "Serviço de hospedagem VPS na Irlanda" }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "A renovação automática está desativada. Renove manualmente antes do prazo para evitar interrupções." }, + "V/V4bz": { + "defaultMessage": "Serviço de hospedagem VPS em Dublin, Irlanda, a partir de {price} preço sem IVA. Configure o seu modelo de acordo com as suas preferências até um máximo de: {maxCpu} vCPU. Pague em Bitcoin através da Lightning Network ou diretamente na blockchain." + }, "V7fgpY": { "defaultMessage": "A concluir o início de sessão…" }, @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "Taxa/Avaliação" }, - "c3o/3B": { - "defaultMessage": "De {price} por mês, excluindo o IVA." - }, "c5aQL+": { "defaultMessage": "Escolha um aplicativo, dê-lhe um nome e efetue o pagamento. Ele será disponibilizado no seu próprio nome de domínio, com o TLS já configurado — não é necessário aplicar patches no sistema operativo, configurar o Docker ou renovar certificados." }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "A nossa unidade em Dublin. Crie a máquina que deseja. {minCpu}–{maxCpu} vCPU e {minMemory}–{maxMemory}e pague usando Bitcoin." }, - "dTTuZ+": { - "defaultMessage": "Serviço de hospedagem VPS em Dublin, Irlanda, a partir de {price}/mês, excluindo o IVA. Configure as especificações que deseja. {maxCpu} vCPU. Pague em Bitcoin através da Lightning Network ou diretamente na blockchain." - }, "dUQeuG": { "defaultMessage": "Criar conta com chave de acesso" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "Monte a sua máquina" }, + "e55cj/": { + "defaultMessage": "De {price}, IVA não incluído." + }, "eKEL/g": { "defaultMessage": "Pendente" }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "Inicie sessão para fazer o seu pedido." }, + "fNZGof": { + "defaultMessage": "Serviço de hospedagem VPS em Londres, a partir de {price} preço sem IVA. {minCpu}–{maxCpu} vCPU, até {maxDisk} {diskType}Pague em Bitcoin através da rede Lightning ou diretamente na blockchain." + }, "fblXmJ": { "defaultMessage": "Nome da instância" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "Criar conta" }, - "i/LmLC": { - "defaultMessage": "Serviço de hospedagem VPS em Londres a partir de {price}por mês, sem IVA. {minCpu}–{maxCpu} vCPU, até {maxDisk} {diskType}. Pague em Bitcoin através da Lightning Network ou diretamente na blockchain." - }, "i2/SmH": { "defaultMessage": "A cobrança funciona como uma assinatura de VPS, sem taxas de configuração para nenhum aplicativo." }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "Serviço de hospedagem VPS em Quebec, Canadá, a partir de: {price} preço sem IVA. {minCpu}–{maxCpu} vCPU, até {maxDisk} {diskType}Pague em Bitcoin através da Lightning Network ou diretamente na blockchain." + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "Use uma chave de destinatário personalizada." }, - "vIv3vC": { - "defaultMessage": "Serviço de hospedagem VPS em Quebec, Canadá, a partir de {price}por mês, sem IVA. {minCpu}–{maxCpu} vCPU, até {maxDisk} {diskType}. Pague em Bitcoin através da Lightning Network ou diretamente na blockchain." - }, "vONi+O": { "defaultMessage": "País" }, diff --git a/src/locales/ru.json b/src/locales/ru.json index 7df6356..414902a 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -20,6 +20,9 @@ "+JVG/q": { "defaultMessage": "{n,plural,one{год} other{лет}}" }, + "+Mo6Oy": { + "defaultMessage": "Запустите полную ноду Bitcoin на виртуальном частном сервере в… {regions}Оплачивайте в биткоинах через Lightning Network или напрямую, в основной сети. С {price} без НДС." + }, "+Qebzr": { "defaultMessage": "Адрес в блокчейне" }, @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "Запустите полную ноду Bitcoin на VPS в {regions}Оплачивайте в биткоинах через Lightning Network или напрямую в основной сети. От {price}в месяц, без НДС." - }, "0gAiqM": { "defaultMessage": "Виртуальный выделенный сервер в Ирландии." }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "Автоматическое продление отключено. Продлите подписку вручную до истечения срока действия, чтобы избежать перерыва в обслуживании." }, + "V/V4bz": { + "defaultMessage": "Размещение VPS-серверов в Дублине, Ирландия, от {price} без учета НДС. Соберите свою конфигурацию по индивидуальным параметрам до {maxCpu} Виртуальный процессор. Оплата в биткоинах через Lightning Network или напрямую, в основной сети." + }, "V7fgpY": { "defaultMessage": "Завершение входа…" }, @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "Цена/тариф/ставка" }, - "c3o/3B": { - "defaultMessage": "От {price} в месяц, без учета НДС." - }, "c5aQL+": { "defaultMessage": "Выберите приложение, дайте ему название и оплатите. Оно автоматически развернется на собственном домене с уже настроенным протоколом TLS — не нужно обновлять операционную систему, настраивать Docker или продлевать срок действия сертификата." }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "Наш завод в Дублине. Создавайте машины по своему желанию. {minCpu}–{maxCpu} виртуальный процессор и {minMemory}–{maxMemory}и оплатите это в биткоинах." }, - "dTTuZ+": { - "defaultMessage": "Размещение VPS-серверов в Дублине, Ирландия, от {price}в месяц, без учета НДС. Создайте собственную конфигурацию на сумму до {maxCpu} Виртуальный процессор. Оплачивайте в биткоинах через Lightning Network или непосредственно в блокчейне." - }, "dUQeuG": { "defaultMessage": "Создать аккаунт с ключом доступа" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "Соберите свою машину" }, + "e55cj/": { + "defaultMessage": "От {price}без учета НДС." + }, "eKEL/g": { "defaultMessage": "В ожидании" }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "Войдите, чтобы сделать заказ." }, + "fNZGof": { + "defaultMessage": "Виртуальный выделенный сервер в Лондоне от {price} без НДС. {minCpu}–{maxCpu} виртуальный процессор, до {maxDisk} {diskType}Оплачивайте в биткоинах через Lightning Network или напрямую, используя основной блокчейн." + }, "fblXmJ": { "defaultMessage": "Имя экземпляра" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "Создать аккаунт" }, - "i/LmLC": { - "defaultMessage": "Виртуальный выделенный сервер в Лондоне от {price}в месяц, без учета НДС. {minCpu}–{maxCpu} виртуальный процессор, до {maxDisk} {diskType}Оплачивайте в биткоинах через Lightning Network или напрямую в основной сети." - }, "i2/SmH": { "defaultMessage": "Оплата производится по принципу подписки на VPS, при этом за настройку любого приложения плата не взимается." }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "Размещение VPS-серверов в Квебеке, Канада, от {price} без НДС. {minCpu}–{maxCpu} виртуальный процессор, до {maxDisk} {diskType}Оплачивайте в биткоинах через сеть Lightning или напрямую, используя основной блокчейн." + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "Используйте пользовательский ключ получателя." }, - "vIv3vC": { - "defaultMessage": "Размещение VPS-серверов в Квебеке, Канада, от {price}в месяц, без учета НДС. {minCpu}–{maxCpu} виртуальный процессор, до {maxDisk} {diskType}Оплачивайте в биткоинах через Lightning Network или напрямую, в основной сети." - }, "vONi+O": { "defaultMessage": "Страна" }, diff --git a/src/locales/tr.json b/src/locales/tr.json index d0be9ae..237d194 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -20,6 +20,9 @@ "+JVG/q": { "defaultMessage": "{n,plural,one{yıl} other{yıl}}" }, + "+Mo6Oy": { + "defaultMessage": "bir VPS üzerinde çalışan bir Bitcoin tam düğümü çalıştırın. {regions}Bitcoin ile Lightning veya doğrudan blok zinciri üzerinden ödeme yapın. Şuradan: {price} KDV hariç." + }, "+Qebzr": { "defaultMessage": "Zincir Üzerindeki Adres" }, @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "şu konumda bir VPS üzerinde çalışan bir Bitcoin tam düğümü çalıştırın: {regions}Bitcoin ile Lightning veya doğrudan ödeme yapın. {price}ayda, KDV hariç." - }, "0gAiqM": { "defaultMessage": "İrlanda'da VPS hosting" }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "Otomatik yenileme özelliği devre dışı bırakıldı. Hizmetin kesintisiz devam etmesi için son kullanma tarihinden önce manuel olarak yenileyin." }, + "V/V4bz": { + "defaultMessage": "İrlanda'nın Dublin şehrinde VPS sunucu hizmeti. {price} KDV dahil değil. İstediğiniz özelliklere göre özelleştirin. {maxCpu} sanal CPU. Ödemenizi Bitcoin ile Lightning ağı üzerinden veya doğrudan blok zinciri üzerinde yapın." + }, "V7fgpY": { "defaultMessage": "Giriş tamamlanıyor…" }, @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "Oran" }, - "c3o/3B": { - "defaultMessage": "-den {price} ayda, KDV hariç." - }, "c5aQL+": { "defaultMessage": "Bir uygulama seçin, ona bir ad verin ve ödeme yapın. Uygulama, TLS'nin zaten etkin olduğu kendi alan adı altında otomatik olarak çalışmaya başlar; herhangi bir işletim sistemi güncellemesi yapmanıza, Docker yapılandırması yapmanıza veya sertifika yenilemenize gerek kalmaz." }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "Dublin'deki tesisimiz. İstediğiniz makineyi burada oluşturun. {minCpu}–{maxCpu} sanal CPU ve {minMemory}–{maxMemory}ve bunun için Bitcoin ile ödeme yapın." }, - "dTTuZ+": { - "defaultMessage": "Dublin, İrlanda'da VPS hosting hizmeti, şu tarihten itibaren: {price}/ay, KDV hariç. İstediğiniz özelliklere göre özelleştirin. {maxCpu} vCPU. Bitcoin ile Lightning veya doğrudan ödeme yapın." - }, "dUQeuG": { "defaultMessage": "Geçiş anahtarı hesabı oluştur" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "Makinenizi oluşturun" }, + "e55cj/": { + "defaultMessage": "-den itibaren / -dan başlayarak {price}KDV hariç." + }, "eKEL/g": { "defaultMessage": "Beklemede" }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "Sipariş Vermek İçin Giriş Yapın" }, + "fNZGof": { + "defaultMessage": "Londra'da VPS sunucu hizmeti. {price} KDV hariç. {minCpu}–{maxCpu} sanal CPU, en fazla {maxDisk} {diskType}Bitcoin ile Lightning veya zincir üzerinde ödeme yapın." + }, "fblXmJ": { "defaultMessage": "Örnek adı" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "Hesap oluştur" }, - "i/LmLC": { - "defaultMessage": "Londra'da VPS hosting hizmeti, şu fiyatlardan itibaren: {price}aylık, KDV hariç. {minCpu}–{maxCpu} sanal CPU, en fazla {maxDisk} {diskType}Bitcoin ile Lightning veya doğrudan blok zinciri üzerinden ödeme yapın." - }, "i2/SmH": { "defaultMessage": "Fatura sistemi, bir VPS aboneliği gibi çalışır ve hiçbir uygulamada kurulum ücreti alınmaz." }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "Kanada, Québec'te VPS sunucu hizmeti. {price} KDV hariç. {minCpu}–{maxCpu} sanal CPU, en fazla {maxDisk} {diskType}Bitcoin ile Lightning ağı veya doğrudan zincir üzerinden ödeme yapın." + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "Özel alıcı anahtarını kullanın" }, - "vIv3vC": { - "defaultMessage": "Kanada, Quebec'te VPS hosting hizmeti. {price}aylık, KDV hariç. {minCpu}–{maxCpu} vCPU, en fazla {maxDisk} {diskType}Bitcoin ile Lightning veya doğrudan zincir üzerinden ödeme yapın." - }, "vONi+O": { "defaultMessage": "Ülke" }, diff --git a/src/locales/zh.json b/src/locales/zh.json index 719c009..ad46791 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -20,6 +20,9 @@ "+JVG/q": { "defaultMessage": "{n,plural,one{年} other{年}}" }, + "+Mo6Oy": { + "defaultMessage": "在某个虚拟专用服务器(VPS)上运行一个比特币完整节点。 {regions}可通过闪电网络或链上方式使用比特币支付。从…… {price} 不含增值税。" + }, "+Qebzr": { "defaultMessage": "链上地址" }, @@ -80,9 +83,6 @@ "0RxidJ": { "defaultMessage": "LNURL" }, - "0eyb8X": { - "defaultMessage": "在某个地区的虚拟专用服务器(VPS)上运行一个比特币完整节点。 {regions}可通过闪电网络或链上方式使用比特币支付。来自 {price}每月,不含增值税。" - }, "0gAiqM": { "defaultMessage": "在爱尔兰提供 VPS 虚拟主机服务。" }, @@ -1316,6 +1316,9 @@ "Uvhzx4": { "defaultMessage": "已关闭自动续订功能。请在到期前手动续订,以避免服务中断。" }, + "V/V4bz": { + "defaultMessage": "在爱尔兰都柏林提供 VPS 主机服务,起价为: {price} 不含增值税。您可以根据自己的需求配置。 {maxCpu} 虚拟 CPU。可通过闪电网络或直接链上支付比特币。" + }, "V7fgpY": { "defaultMessage": "正在完成登录…" }, @@ -1374,7 +1377,7 @@ "defaultMessage": "部署和管理服务器" }, "WxTcqt": { - "defaultMessage": "{n,plural,one{月} other{月}}" + "defaultMessage": "{n,plural,one{月} other{月份}}" }, "X/4SWn": { "defaultMessage": "在加拿大魁北克省提供 VPS 虚拟主机服务。您可以选择所需的 CPU、内存和硬盘。支持通过闪电网络或链上方式使用比特币支付。" @@ -1541,9 +1544,6 @@ "c0b5XV": { "defaultMessage": "费率" }, - "c3o/3B": { - "defaultMessage": "来自 {price} 每月,不含增值税。" - }, "c5aQL+": { "defaultMessage": "选择一个应用程序,为其命名,然后付费。它会自动在自己的主机名上运行,并且已启用 TLS 加密,无需修复操作系统、配置 Docker 或更新证书。" }, @@ -1613,9 +1613,6 @@ "dSDJft": { "defaultMessage": "我们在都柏林的工厂。您可以根据自己的需求定制机器。 {minCpu}——{maxCpu} 虚拟 CPU 和 {minMemory}——{maxMemory}并使用比特币支付。" }, - "dTTuZ+": { - "defaultMessage": "在爱尔兰都柏林提供 VPS 虚拟主机服务,起价为 {price}不含增值税,每月费用。您可以根据自己的需求定制配置。 {maxCpu} 虚拟 CPU。您可以使用比特币通过闪电网络或链上方式进行支付。" - }, "dUQeuG": { "defaultMessage": "创建通行密钥账户" }, @@ -1649,6 +1646,9 @@ "e4ehrh": { "defaultMessage": "配置您的机器" }, + "e55cj/": { + "defaultMessage": "从…… {price},不含增值税。" + }, "eKEL/g": { "defaultMessage": "待处理" }, @@ -1694,6 +1694,9 @@ "fNSGnl": { "defaultMessage": "登录以进行订购。" }, + "fNZGof": { + "defaultMessage": "伦敦的 VPS 主机,起价为 {price} 不含增值税。 {minCpu}——{maxCpu} 虚拟 CPU,最多可达 {maxDisk} {diskType}可以使用比特币通过闪电网络或主链进行支付。" + }, "fblXmJ": { "defaultMessage": "实例名称" }, @@ -1778,9 +1781,6 @@ "huqKGl": { "defaultMessage": "创建账户" }, - "i/LmLC": { - "defaultMessage": "伦敦的 VPS 虚拟主机,起价为 {price}每月,不含增值税。 {minCpu}——{maxCpu} vCPU,最多 {maxDisk} {diskType}可以使用比特币通过闪电网络或主链进行支付。" - }, "i2/SmH": { "defaultMessage": "计费方式类似于虚拟专用服务器(VPS)的订阅模式,任何应用程序都不收取设置费用。" }, @@ -2105,6 +2105,9 @@ "pgl1gp": { "defaultMessage": "Resize to {size}× base" }, + "phIEA8": { + "defaultMessage": "位于加拿大魁北克的 VPS 服务器托管服务,提供商为: {price} 不含增值税。 {minCpu}——{maxCpu} 虚拟 CPU,最多可达 {maxDisk} {diskType}可以使用比特币通过闪电网络或主链进行支付。" + }, "ps4K9r": { "defaultMessage": "What is Blossom?" }, @@ -2312,9 +2315,6 @@ "v4Sg0C": { "defaultMessage": "使用自定义收件人密钥。" }, - "vIv3vC": { - "defaultMessage": "位于加拿大魁北克的 VPS 虚拟主机,起价为 {price}每月,不含增值税。 {minCpu}——{maxCpu} vCPU,最多可达 {maxDisk} {diskType}可以使用比特币通过闪电网络或主链进行支付。" - }, "vONi+O": { "defaultMessage": "国家" }, diff --git a/src/pages/bitcoin-node-hosting.tsx b/src/pages/bitcoin-node-hosting.tsx index 694a858..f9d93c0 100644 --- a/src/pages/bitcoin-node-hosting.tsx +++ b/src/pages/bitcoin-node-hosting.tsx @@ -7,7 +7,7 @@ import { OrderCta, RegionSpecs, Section } from "../components/landing"; import { DiskType } from "../api"; import { faqJsonLd, type FaqItem } from "../utils/faq-seo"; import { formatBytesText } from "../utils/bytes"; -import { formatPriceText } from "../utils/currency"; +import { formatPriceWithInterval } from "../utils/currency"; import { regionCustomTemplate, regionDisk, @@ -40,7 +40,7 @@ export function BitcoinNodeHostingPage() { const hdd = regionDisk(dublin, DiskType.HDD); const ssd = regionDisk(dublin, DiskType.SSD); const maxDisk = regionMaxDisk(dublin); - const fromText = from ? formatPriceText(intl, from) : undefined; + const fromText = from ? formatPriceWithInterval(intl, from) : undefined; // The regions we actually offer a build in, named as the catalog names them // and sorted, since the endpoint's order is not stable and a sentence should @@ -58,7 +58,7 @@ export function BitcoinNodeHostingPage() { ? formatMessage( { defaultMessage: - "Run a Bitcoin full node on a VPS in {regions}. Pay in Bitcoin over Lightning or on-chain. From {price}/month ex-VAT.", + "Run a Bitcoin full node on a VPS in {regions}. Pay in Bitcoin over Lightning or on-chain. From {price} ex-VAT.", }, { regions: regionList, price: fromText }, ) @@ -139,7 +139,7 @@ export function BitcoinNodeHostingPage() { {from && (

, }} diff --git a/src/pages/vps-canada.tsx b/src/pages/vps-canada.tsx index eff848a..d324b0e 100644 --- a/src/pages/vps-canada.tsx +++ b/src/pages/vps-canada.tsx @@ -2,7 +2,7 @@ import { useLoaderData } from "react-router-dom"; import { FormattedMessage, useIntl } from "react-intl"; import { RegionLanding } from "../components/region-landing"; import { formatBytesText } from "../utils/bytes"; -import { formatPriceText } from "../utils/currency"; +import { formatPriceWithInterval } from "../utils/currency"; import { regionCustomTemplate, regionMaxDisk } from "../utils/regions"; import { QuebecRegionId } from "../const"; import type { RegionLoaderData } from "../loaders"; @@ -19,14 +19,14 @@ export function VpsCanadaPage() { const { offers, from } = useLoaderData(); const template = regionCustomTemplate(offers, QuebecRegionId); const disk = regionMaxDisk(template); - const fromText = from ? formatPriceText(intl, from) : undefined; + const fromText = from ? formatPriceWithInterval(intl, from) : undefined; const description = fromText && template && disk ? intl.formatMessage( { defaultMessage: - "VPS hosting in Quebec, Canada from {price}/month ex-VAT. {minCpu}–{maxCpu} vCPU, up to {maxDisk} {diskType}. Pay in Bitcoin over Lightning or on-chain.", + "VPS hosting in Quebec, Canada from {price} ex-VAT. {minCpu}–{maxCpu} vCPU, up to {maxDisk} {diskType}. Pay in Bitcoin over Lightning or on-chain.", }, { price: fromText, diff --git a/src/pages/vps-ireland.tsx b/src/pages/vps-ireland.tsx index af18030..a2f935e 100644 --- a/src/pages/vps-ireland.tsx +++ b/src/pages/vps-ireland.tsx @@ -5,7 +5,7 @@ import { CostAmount } from "../components/cost"; import BytesSize from "../components/bytes"; import { Section } from "../components/landing"; import { RegionLanding } from "../components/region-landing"; -import { formatPriceText } from "../utils/currency"; +import { formatPriceWithInterval } from "../utils/currency"; import { catalogPriceFrom } from "../utils/relay-catalog"; import { regionCustomTemplate } from "../utils/regions"; import { DublinRegionId } from "../const"; @@ -28,7 +28,7 @@ export function VpsIrelandPage() { const intl = useIntl(); const { offers, from, apps } = useLoaderData(); const template = regionCustomTemplate(offers, DublinRegionId); - const fromText = from ? formatPriceText(intl, from) : undefined; + const fromText = from ? formatPriceWithInterval(intl, from) : undefined; // "From" across the whole catalog, since the sentence names the relays and // the Blossom servers together. Undefined when the catalog is unreachable, // and the sentence then drops the figure. @@ -58,7 +58,7 @@ export function VpsIrelandPage() { ? intl.formatMessage( { defaultMessage: - "VPS hosting in Dublin, Ireland from {price}/month ex-VAT. Build your own spec up to {maxCpu} vCPU. Pay in Bitcoin over Lightning or on-chain.", + "VPS hosting in Dublin, Ireland from {price} ex-VAT. Build your own spec up to {maxCpu} vCPU. Pay in Bitcoin over Lightning or on-chain.", }, { price: fromText, maxCpu: template.max_cpu }, ) diff --git a/src/pages/vps-london.tsx b/src/pages/vps-london.tsx index cf0264d..7358351 100644 --- a/src/pages/vps-london.tsx +++ b/src/pages/vps-london.tsx @@ -2,7 +2,7 @@ import { useLoaderData } from "react-router-dom"; import { FormattedMessage, useIntl } from "react-intl"; import { RegionLanding } from "../components/region-landing"; import { formatBytesText } from "../utils/bytes"; -import { formatPriceText } from "../utils/currency"; +import { formatPriceWithInterval } from "../utils/currency"; import { regionCustomTemplate, regionMaxDisk } from "../utils/regions"; import { LondonRegionId } from "../const"; import type { RegionLoaderData } from "../loaders"; @@ -19,14 +19,14 @@ export function VpsLondonPage() { const { offers, from } = useLoaderData(); const template = regionCustomTemplate(offers, LondonRegionId); const disk = regionMaxDisk(template); - const fromText = from ? formatPriceText(intl, from) : undefined; + const fromText = from ? formatPriceWithInterval(intl, from) : undefined; const description = fromText && template && disk ? intl.formatMessage( { defaultMessage: - "VPS hosting in London from {price}/month ex-VAT. {minCpu}–{maxCpu} vCPU, up to {maxDisk} {diskType}. Pay in Bitcoin over Lightning or on-chain.", + "VPS hosting in London from {price} ex-VAT. {minCpu}–{maxCpu} vCPU, up to {maxDisk} {diskType}. Pay in Bitcoin over Lightning or on-chain.", }, { price: fromText, diff --git a/src/utils/currency.test.ts b/src/utils/currency.test.ts new file mode 100644 index 0000000..fdda2a0 --- /dev/null +++ b/src/utils/currency.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, test } from "bun:test"; +import { createIntl } from "react-intl"; +import { formatIntervalText, formatPriceText, formatPriceWithInterval } from "./currency"; + +const intl = createIntl({ locale: "en", messages: {} }); + +describe("formatIntervalText", () => { + test("the word only — no count, callers that already print their own count depend on this", () => { + expect(formatIntervalText(intl, "month", 1)).toBe("month"); + expect(formatIntervalText(intl, "month", 3)).toBe("months"); + expect(formatIntervalText(intl, "day", 1)).toBe("day"); + expect(formatIntervalText(intl, "year", 2)).toBe("years"); + }); + + test("defaults to one when n is omitted", () => { + expect(formatIntervalText(intl, "month")).toBe("month"); + }); + + test("an interval the switch doesn't know passes through unchanged", () => { + expect(formatIntervalText(intl, "fortnight")).toBe("fortnight"); + }); +}); + +describe("formatPriceWithInterval", () => { + test("appends the interval to formatPriceText's own figure", () => { + const cost = { currency: "EUR", amount: 500, interval_type: "month" }; + expect(formatPriceWithInterval(intl, cost)).toBe( + `${formatPriceText(intl, cost)}/month`, + ); + }); + + test("a BTC price still floors to sats before the interval", () => { + const cost = { currency: "BTC", amount: 1791986, interval_type: "year" }; + expect(formatPriceWithInterval(intl, cost)).toBe( + `${formatPriceText(intl, cost)}/year`, + ); + }); + + test("falls back to a monthly default instead of printing 'undefined'", () => { + const cost = { currency: "EUR", amount: 500 }; + expect(formatPriceWithInterval(intl, cost)).toBe( + `${formatPriceText(intl, cost)}/month`, + ); + }); + + test("carries interval_amount into both the count and the word", () => { + const cost = { + currency: "EUR", + amount: 1500, + interval_type: "month", + interval_amount: 3, + }; + expect(formatPriceWithInterval(intl, cost)).toBe( + `${formatPriceText(intl, cost)}/3 months`, + ); + }); +}); diff --git a/src/utils/currency.ts b/src/utils/currency.ts index 178e666..62c9854 100644 --- a/src/utils/currency.ts +++ b/src/utils/currency.ts @@ -41,6 +41,63 @@ export function formatPriceText( }); } +/** + * The word for a billing interval ("month", "months", ...) — the one place + * that maps an interval to text, so `IntervalSuffix` and a plain-text price + * sentence read the same translation instead of two that could drift apart. + */ +export function formatIntervalText( + intl: IntlShape, + interval: string, + n = 1, +): string { + switch (interval) { + case "day": + return intl.formatMessage( + { defaultMessage: "{n, plural, one {day} other {days}}" }, + { n }, + ); + case "month": + return intl.formatMessage( + { defaultMessage: "{n, plural, one {month} other {months}}" }, + { n }, + ); + case "year": + return intl.formatMessage( + { defaultMessage: "{n, plural, one {year} other {years}}" }, + { n }, + ); + default: + return interval; + } +} + +/** + * `formatPriceText` with "/" appended, for a recurring price — + * the custom-VM entry price on the region pages, not a one-off figure. + * + * `interval_type` falls back to month when absent, so a price from an API + * that hasn't shipped the field yet still reads right instead of printing + * the literal word "undefined". The count is printed here, not inside + * `formatIntervalText` — `IntervalSuffix` callers that already show their + * own count (`payment-flow.tsx`, `vm-billing.tsx`) only want the word from + * that helper, so the digit belongs at the one call site that needs it. + */ +export function formatPriceWithInterval( + intl: IntlShape, + cost: { + currency: string; + amount: number; + interval_type?: string; + interval_amount?: number; + }, +): string { + const n = cost.interval_amount ?? 1; + const interval = formatIntervalText(intl, cost.interval_type ?? "month", n); + const count = n > 1 ? `${n} ` : ""; + return `${formatPriceText(intl, cost)}/${count}${interval}`; +} + /** A currency's rate relative to the snapshot base (the base itself is 1). */ function rateOf(currency: string, rates: ExchangeRates): number | undefined { if (currency === rates.base) return 1; diff --git a/src/utils/vps-seo.test.ts b/src/utils/vps-seo.test.ts index 5f11e9d..fafad8b 100644 --- a/src/utils/vps-seo.test.ts +++ b/src/utils/vps-seo.test.ts @@ -4,10 +4,11 @@ import { CostPlanIntervalType, DiskInterface, DiskType, + type VmCustomPrice, type VmTemplate, } from "../api"; import { GiB } from "../const"; -import { vpsTemplateJsonLd } from "./vps-seo"; +import { regionOfferJsonLd, vpsTemplateJsonLd } from "./vps-seo"; const intl = createIntl({ locale: "en", messages: {} }); @@ -45,6 +46,16 @@ function offerOf(schema: object): Offer | undefined { return (schema as { offers?: Offer }).offers; } +function customPrice(over: Partial = {}): VmCustomPrice { + return { + currency: "EUR", + amount: 500, + interval_amount: 1, + interval_type: CostPlanIntervalType.MONTH, + ...over, + }; +} + describe("vpsTemplateJsonLd", () => { test("scales the amount out of minor units and keeps the billing period", () => { const offer = offerOf(vpsTemplateJsonLd(template(), intl)); @@ -79,3 +90,62 @@ describe("vpsTemplateJsonLd", () => { ); }); }); + +describe("regionOfferJsonLd", () => { + test("reads the billing period off the price, not a hardcoded month", () => { + const offer = offerOf( + regionOfferJsonLd({ + name: "VPS in Dublin", + description: "A VPS built to order in Dublin.", + path: "/vps-ireland", + regionName: "Dublin (IE)", + price: customPrice({ interval_type: CostPlanIntervalType.YEAR }), + })!, + ); + expect(offer?.priceSpecification.unitCode).toBe("ANN"); + expect(offer?.priceSpecification.billingDuration).toBe(1); + }); + + test("a longer interval_amount carries through as the billing duration", () => { + const offer = offerOf( + regionOfferJsonLd({ + name: "VPS in Dublin", + description: "A VPS built to order in Dublin.", + path: "/vps-ireland", + regionName: "Dublin (IE)", + price: customPrice({ interval_amount: 3 }), + })!, + ); + expect(offer?.priceSpecification.unitCode).toBe("MON"); + expect(offer?.priceSpecification.billingDuration).toBe(3); + }); + + test("undefined for a BTC price, whose amount is millisats", () => { + expect( + regionOfferJsonLd({ + name: "VPS in Dublin", + description: "A VPS built to order in Dublin.", + path: "/vps-ireland", + regionName: "Dublin (IE)", + price: customPrice({ currency: "BTC", amount: 1791986 }), + }), + ).toBeUndefined(); + }); + + test("degrades to a monthly default instead of throwing against a pre-api#347 payload", () => { + // The type says interval_amount/interval_type are always there; a server + // that hasn't deployed api#347 yet still won't send them. + const price = { currency: "EUR", amount: 500 } as VmCustomPrice; + const offer = offerOf( + regionOfferJsonLd({ + name: "VPS in Dublin", + description: "A VPS built to order in Dublin.", + path: "/vps-ireland", + regionName: "Dublin (IE)", + price, + })!, + ); + expect(offer?.priceSpecification.unitCode).toBe("MON"); + expect(offer?.priceSpecification.billingDuration).toBe(1); + }); +}); diff --git a/src/utils/vps-seo.ts b/src/utils/vps-seo.ts index 8133288..c95f26a 100644 --- a/src/utils/vps-seo.ts +++ b/src/utils/vps-seo.ts @@ -31,11 +31,11 @@ import { BILLING_UNIT, SITE_URL, standardUnitPrice } from "./schema-org"; * the price endpoint; a region with no price gets no markup rather than a * remembered figure. * - * The billing period is monthly and is *not* in the response: custom templates - * are always billed on a one-month interval - * (`lnvps_api_common/src/pricing.rs:882`), which is why the payload has no - * interval field to read. `LNVPS/api#302` asks for it on the wire; until then - * this is the one figure here the API does not state. + * The billing period comes off the price response itself + * (`interval_amount`/`interval_type`), the same fields `vpsTemplateJsonLd` + * reads off a standard plan's `cost_plan` below. Falls back to a monthly + * default when they're absent, so this degrades instead of throwing against + * an API that hasn't shipped them yet. * * Ex-VAT, like every other price on the site logged out: tax is applied at * payment against the buyer's country, so it cannot be in a crawled figure. @@ -69,10 +69,10 @@ export function regionOfferJsonLd(opts: { price: offerPrice, priceCurrency: opts.price.currency, valueAddedTaxIncluded: false, - billingDuration: 1, + billingDuration: opts.price.interval_amount ?? 1, billingIncrement: 1, - unitCode: BILLING_UNIT[CostPlanIntervalType.MONTH].code, - unitText: BILLING_UNIT[CostPlanIntervalType.MONTH].text, + unitCode: BILLING_UNIT[opts.price.interval_type ?? CostPlanIntervalType.MONTH].code, + unitText: BILLING_UNIT[opts.price.interval_type ?? CostPlanIntervalType.MONTH].text, }, }, };