From a15c5efa63d803190873c0a4cce0ec06b2cdd585 Mon Sep 17 00:00:00 2001 From: v0l Date: Thu, 30 Jul 2026 19:57:02 +0100 Subject: [PATCH 1/5] fix(vps): read the region-price billing period off the API Signed-off-by: v0l --- src/api.ts | 2 ++ src/components/cost.tsx | 32 +++-------------- src/components/region-landing.tsx | 2 +- src/locales/ar.json | 30 ++++++++-------- src/locales/de.json | 30 ++++++++-------- src/locales/en.json | 30 ++++++++-------- src/locales/es.json | 30 ++++++++-------- src/locales/fr.json | 30 ++++++++-------- src/locales/ja.json | 30 ++++++++-------- src/locales/ko.json | 30 ++++++++-------- src/locales/pt.json | 30 ++++++++-------- src/locales/ru.json | 30 ++++++++-------- src/locales/tr.json | 30 ++++++++-------- src/locales/zh.json | 30 ++++++++-------- src/pages/bitcoin-node-hosting.tsx | 8 ++--- src/pages/vps-canada.tsx | 6 ++-- src/pages/vps-ireland.tsx | 6 ++-- src/pages/vps-london.tsx | 6 ++-- src/utils/currency.test.ts | 38 +++++++++++++++++++++ src/utils/currency.ts | 42 +++++++++++++++++++++++ src/utils/vps-seo.test.ts | 55 +++++++++++++++++++++++++++++- src/utils/vps-seo.ts | 16 ++++----- 22 files changed, 326 insertions(+), 217 deletions(-) create mode 100644 src/utils/currency.test.ts 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..5aec628 100644 --- a/src/components/cost.tsx +++ b/src/components/cost.tsx @@ -1,8 +1,8 @@ -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; @@ -68,32 +68,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({ 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/locales/ar.json b/src/locales/ar.json index 23ae06c..ae10f64 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -20,6 +20,9 @@ "+JVG/q": { "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..236cee0 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -20,6 +20,9 @@ "+JVG/q": { "defaultMessage": "{n,plural,one{an} other{ans}}" }, + "+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..d465c8d 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": "サインインを完了しています…" }, @@ -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..bc71332 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": "正在完成登录…" }, @@ -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..9db161c --- /dev/null +++ b/src/utils/currency.test.ts @@ -0,0 +1,38 @@ +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("singular at one, plural above it", () => { + 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`, + ); + }); +}); diff --git a/src/utils/currency.ts b/src/utils/currency.ts index 178e666..4851101 100644 --- a/src/utils/currency.ts +++ b/src/utils/currency.ts @@ -41,6 +41,48 @@ 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. + */ +export function formatPriceWithInterval( + intl: IntlShape, + cost: { currency: string; amount: number; interval_type: string }, +): string { + return `${formatPriceText(intl, cost)}/${formatIntervalText(intl, cost.interval_type)}`; +} + /** 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..d747e65 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,45 @@ 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(); + }); +}); diff --git a/src/utils/vps-seo.ts b/src/utils/vps-seo.ts index 8133288..6136708 100644 --- a/src/utils/vps-seo.ts +++ b/src/utils/vps-seo.ts @@ -1,5 +1,5 @@ import type { IntlShape } from "react-intl"; -import { CostPlanIntervalType, type VmCustomPrice, type VmTemplate } from "../api"; +import { type VmCustomPrice, type VmTemplate } from "../api"; import { formatBytesText } from "./bytes"; import { BILLING_UNIT, SITE_URL, standardUnitPrice } from "./schema-org"; @@ -31,11 +31,9 @@ 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. * * 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 +67,10 @@ export function regionOfferJsonLd(opts: { price: offerPrice, priceCurrency: opts.price.currency, valueAddedTaxIncluded: false, - billingDuration: 1, + billingDuration: opts.price.interval_amount, billingIncrement: 1, - unitCode: BILLING_UNIT[CostPlanIntervalType.MONTH].code, - unitText: BILLING_UNIT[CostPlanIntervalType.MONTH].text, + unitCode: BILLING_UNIT[opts.price.interval_type].code, + unitText: BILLING_UNIT[opts.price.interval_type].text, }, }, }; From a1f20ab43e178ea5f42b393676a8e59421ce89bb Mon Sep 17 00:00:00 2001 From: v0l Date: Thu, 30 Jul 2026 20:14:54 +0100 Subject: [PATCH 2/5] fix(vps): guard interval fallbacks, carry interval_amount through Signed-off-by: v0l --- src/components/cost.tsx | 5 +++-- src/components/vps-custom.tsx | 4 ++-- src/utils/currency.test.ts | 19 +++++++++++++++++++ src/utils/currency.ts | 18 ++++++++++++++++-- src/utils/vps-seo.test.ts | 17 +++++++++++++++++ src/utils/vps-seo.ts | 12 +++++++----- 6 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/components/cost.tsx b/src/components/cost.tsx index 5aec628..90515c4 100644 --- a/src/components/cost.tsx +++ b/src/components/cost.tsx @@ -8,7 +8,7 @@ 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} />{" "} @@ -113,7 +114,7 @@ export function CostAmount({ {cost.currency === "BTC" && " sats"} {cost.interval_type && ( <> - / + / )} 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/utils/currency.test.ts b/src/utils/currency.test.ts index 9db161c..eb3ffeb 100644 --- a/src/utils/currency.test.ts +++ b/src/utils/currency.test.ts @@ -35,4 +35,23 @@ describe("formatPriceWithInterval", () => { `${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 the pluralised word", () => { + const cost = { + currency: "EUR", + amount: 1500, + interval_type: "month", + interval_amount: 3, + }; + expect(formatPriceWithInterval(intl, cost)).toBe( + `${formatPriceText(intl, cost)}/months`, + ); + }); }); diff --git a/src/utils/currency.ts b/src/utils/currency.ts index 4851101..e15ec48 100644 --- a/src/utils/currency.ts +++ b/src/utils/currency.ts @@ -75,12 +75,26 @@ export function formatIntervalText( /** * `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". */ export function formatPriceWithInterval( intl: IntlShape, - cost: { currency: string; amount: number; interval_type: string }, + cost: { + currency: string; + amount: number; + interval_type?: string; + interval_amount?: number; + }, ): string { - return `${formatPriceText(intl, cost)}/${formatIntervalText(intl, cost.interval_type)}`; + const interval = formatIntervalText( + intl, + cost.interval_type ?? "month", + cost.interval_amount, + ); + return `${formatPriceText(intl, cost)}/${interval}`; } /** A currency's rate relative to the snapshot base (the base itself is 1). */ diff --git a/src/utils/vps-seo.test.ts b/src/utils/vps-seo.test.ts index d747e65..fafad8b 100644 --- a/src/utils/vps-seo.test.ts +++ b/src/utils/vps-seo.test.ts @@ -131,4 +131,21 @@ describe("regionOfferJsonLd", () => { }), ).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 6136708..c95f26a 100644 --- a/src/utils/vps-seo.ts +++ b/src/utils/vps-seo.ts @@ -1,5 +1,5 @@ import type { IntlShape } from "react-intl"; -import { type VmCustomPrice, type VmTemplate } from "../api"; +import { CostPlanIntervalType, type VmCustomPrice, type VmTemplate } from "../api"; import { formatBytesText } from "./bytes"; import { BILLING_UNIT, SITE_URL, standardUnitPrice } from "./schema-org"; @@ -33,7 +33,9 @@ import { BILLING_UNIT, SITE_URL, standardUnitPrice } from "./schema-org"; * * 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. + * 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. @@ -67,10 +69,10 @@ export function regionOfferJsonLd(opts: { price: offerPrice, priceCurrency: opts.price.currency, valueAddedTaxIncluded: false, - billingDuration: opts.price.interval_amount, + billingDuration: opts.price.interval_amount ?? 1, billingIncrement: 1, - unitCode: BILLING_UNIT[opts.price.interval_type].code, - unitText: BILLING_UNIT[opts.price.interval_type].text, + unitCode: BILLING_UNIT[opts.price.interval_type ?? CostPlanIntervalType.MONTH].code, + unitText: BILLING_UNIT[opts.price.interval_type ?? CostPlanIntervalType.MONTH].text, }, }, }; From c1444ca5f6a8ebd947d6600a82c935c3b2c9961f Mon Sep 17 00:00:00 2001 From: v0l Date: Thu, 30 Jul 2026 20:19:47 +0100 Subject: [PATCH 3/5] fix(vps): print the count, not just its plural, above one Signed-off-by: v0l --- src/locales/ar.json | 18 +++++++++--------- src/locales/de.json | 18 +++++++++--------- src/locales/en.json | 18 +++++++++--------- src/locales/es.json | 18 +++++++++--------- src/locales/fr.json | 18 +++++++++--------- src/locales/ja.json | 18 +++++++++--------- src/locales/ko.json | 18 +++++++++--------- src/locales/pt.json | 18 +++++++++--------- src/locales/ru.json | 18 +++++++++--------- src/locales/tr.json | 18 +++++++++--------- src/locales/zh.json | 18 +++++++++--------- src/utils/currency.test.ts | 10 +++++----- src/utils/currency.ts | 6 +++--- 13 files changed, 107 insertions(+), 107 deletions(-) diff --git a/src/locales/ar.json b/src/locales/ar.json index ae10f64..fbc1c5d 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -17,9 +17,6 @@ "+H6Rvj": { "defaultMessage": "قم بتشغيل خادم الوسائط الخاص بك من نوع Blossom وNIP-96 لـ {price}‏/شهر. يمكن إعداد Route96 في دقائق معدودة على اسم النطاق الخاص به، مع توفير مساحة تخزين دائمة وبروتوكول أمان طبقة النقل (TLS)." }, - "+JVG/q": { - "defaultMessage": "{n,plural,one{عام} other{سنوات}}" - }, "+Mo6Oy": { "defaultMessage": "قم بتشغيل عقدة بيتكوين كاملة على خادم افتراضي خاص في {regions}يمكنك الدفع بعملة البيتكوين عبر شبكة لايتنينج أو مباشرةً على البلوك تشين. ابتداءً من: {price} باستثناء ضريبة القيمة المضافة." }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "ادفع من أي محفظة تدعم شبكة لايتنينغ لتجديد الاشتراك." }, + "1bHQJa": { + "defaultMessage": "{n,plural,one{شهر} other{# أشهر}}" + }, "1kZ3H0": { "defaultMessage": "عرض المعاملة" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "المنتجات" }, - "7Xz8h2": { - "defaultMessage": "{n,plural,one{يوم} other{أيام}}" - }, "7agV2X": { "defaultMessage": "يُمرِّر/ينقل" }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "اطلب المساعدة من الفريق." }, + "Cbg9PU": { + "defaultMessage": "{n,plural,one{يوم} other{# أيام}}" + }, "Cououe": { "defaultMessage": "سجّل الدخول لتفعيل هذا التطبيق." }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "انشر الخوادم وأدِرها" }, - "WxTcqt": { - "defaultMessage": "{n,plural,one{شهر} other{أشهر}}" - }, "X/4SWn": { "defaultMessage": "خدمة استضافة VPS في كيبيك، كندا. اختر وحدة المعالجة المركزية والذاكرة والتخزين الذي يناسبك. ادفع باستخدام عملة البيتكوين عبر شبكة Lightning أو مباشرة." }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "الاستخدام" }, + "wcUmWu": { + "defaultMessage": "{n,plural,one{سنة} other{# سنوات}}" + }, "wgMz3h": { "defaultMessage": "{count,plural,zero{# تنفيذ لآلية الترحيل} one{تنفيذ واحد لآلية الترحيل} two{تنفيذان لآلية الترحيل} few{# تنفيذات لآلية الترحيل} many{# تنفيذًا لآلية الترحيل} other{# تنفيذ لآلية الترحيل}}، من {price} شهريًا، بدون رسوم إعداد أولية." }, diff --git a/src/locales/de.json b/src/locales/de.json index e279016..a75cc69 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -17,9 +17,6 @@ "+H6Rvj": { "defaultMessage": "Betreiben Sie Ihren eigenen Blossom- und NIP-96-Medienserver für {price}/Monat. Route96 lässt sich innerhalb von Minuten auf seinem eigenen Hostnamen installieren und bietet persistente Datenspeicherung sowie TLS-Unterstützung." }, - "+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." }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "Zahlen Sie mit einer beliebigen Lightning-Wallet, um dieses Abonnement aufzuladen." }, + "1bHQJa": { + "defaultMessage": "{n,plural,one{Monat} other{# Monate}}" + }, "1kZ3H0": { "defaultMessage": "Transaktion ansehen" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "Produkte" }, - "7Xz8h2": { - "defaultMessage": "{n,plural,one{Tag} other{Tage}}" - }, "7agV2X": { "defaultMessage": "Weiterleiten / Relais" }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "Lassen Sie sich vom Team unterstützen." }, + "Cbg9PU": { + "defaultMessage": "{n,plural,one{Tag} other{# Tage}}" + }, "Cououe": { "defaultMessage": "Melden Sie sich an, um diese App zu installieren." }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "Server bereitstellen & verwalten" }, - "WxTcqt": { - "defaultMessage": "{n,plural,one{Monat} other{Monate}}" - }, "X/4SWn": { "defaultMessage": "VPS-Hosting in Québec, Kanada. Wählen Sie Ihre CPU, Ihren Speicher und Ihre Festplatte aus. Bezahlen Sie in Bitcoin über Lightning oder direkt in der Bitcoin-Blockchain." }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "Anwendung" }, + "wcUmWu": { + "defaultMessage": "{n,plural,one{Jahr} other{# Jahre}}" + }, "wgMz3h": { "defaultMessage": "{count,plural,one{# Relais-Implementierung} other{# Relais-Implementierungen}}, von {price} pro Monat, ohne Einrichtungskosten." }, diff --git a/src/locales/en.json b/src/locales/en.json index 9c1fbc0..7fbc2f5 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -17,9 +17,6 @@ "+H6Rvj": { "defaultMessage": "Run your own Blossom and NIP-96 media server for {price}/month. Route96, up in minutes on its own hostname with persistent storage and TLS included." }, - "+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." }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "Pay from any Lightning wallet to top up this subscription." }, + "1bHQJa": { + "defaultMessage": "{n, plural, one {month} other {# months}}" + }, "1kZ3H0": { "defaultMessage": "View transaction" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "Products" }, - "7Xz8h2": { - "defaultMessage": "{n, plural, one {day} other {days}}" - }, "7agV2X": { "defaultMessage": "Relay" }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "Get help from the team" }, + "Cbg9PU": { + "defaultMessage": "{n, plural, one {day} other {# days}}" + }, "Cououe": { "defaultMessage": "Log in to deploy this app." }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "Deploy & manage servers" }, - "WxTcqt": { - "defaultMessage": "{n, plural, one {month} other {months}}" - }, "X/4SWn": { "defaultMessage": "VPS hosting in Quebec, Canada. Pick your CPU, memory and disk. Pay in Bitcoin over Lightning or on-chain." }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "Usage" }, + "wcUmWu": { + "defaultMessage": "{n, plural, one {year} other {# years}}" + }, "wgMz3h": { "defaultMessage": "{count, plural, one {# relay implementation} other {# relay implementations}}, from {price} per month, no setup fee." }, diff --git a/src/locales/es.json b/src/locales/es.json index 9ce1104..5b4717d 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -17,9 +17,6 @@ "+H6Rvj": { "defaultMessage": "Ejecute su propio servidor de medios Blossom y NIP-96. {price}/mes. Route96 se puede implementar en cuestión de minutos en su propio servidor, con almacenamiento persistente y TLS incluido." }, - "+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." }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "Pague desde cualquier billetera Lightning para recargar esta suscripción." }, + "1bHQJa": { + "defaultMessage": "{n,plural,one{mes} other{# meses}}" + }, "1kZ3H0": { "defaultMessage": "Ver transacción" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "Productos" }, - "7Xz8h2": { - "defaultMessage": "{n,plural,one{día} other{días}}" - }, "7agV2X": { "defaultMessage": "Relevo." }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "Pide ayuda al equipo." }, + "Cbg9PU": { + "defaultMessage": "{n,plural,one{día} other{# días}}" + }, "Cououe": { "defaultMessage": "Inicie sesión para implementar esta aplicación." }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "Despliega y gestiona servidores" }, - "WxTcqt": { - "defaultMessage": "{n,plural,one{mes} other{meses}}" - }, "X/4SWn": { "defaultMessage": "Alojamiento VPS en Quebec, Canadá. Elija su CPU, memoria y disco. Pague con Bitcoin a través de Lightning o directamente en la cadena de bloques." }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "Uso." }, + "wcUmWu": { + "defaultMessage": "{n,plural,one{año} other{# años}}" + }, "wgMz3h": { "defaultMessage": "{count,plural,one{# implementación del relé} other{# implementaciones de relés}}, desde {price} por mes, sin coste de instalación." }, diff --git a/src/locales/fr.json b/src/locales/fr.json index 236cee0..2eb246e 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -17,9 +17,6 @@ "+H6Rvj": { "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}}" - }, "+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." }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "Effectuez un paiement depuis n’importe quel portefeuille Lightning pour renouveler cet abonnement." }, + "1bHQJa": { + "defaultMessage": "{n,plural,one{mois} other{# mois}}" + }, "1kZ3H0": { "defaultMessage": "Voir la transaction" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "Produits" }, - "7Xz8h2": { - "defaultMessage": "{n,plural,one{jour} other{jours}}" - }, "7agV2X": { "defaultMessage": "Relais" }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "Demandez de l’aide à l’équipe." }, + "Cbg9PU": { + "defaultMessage": "{n,plural,one{jour} other{# jours}}" + }, "Cououe": { "defaultMessage": "Connectez-vous pour déployer cette application." }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "Déployer et gérer des serveurs" }, - "WxTcqt": { - "defaultMessage": "{n,plural,one{mois} other{mois}}" - }, "X/4SWn": { "defaultMessage": "Hébergement VPS au Québec, au Canada. Choisissez votre processeur, votre mémoire et votre disque dur. Payez en Bitcoin via Lightning ou directement sur la chaîne de blocs." }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "Utilisation" }, + "wcUmWu": { + "defaultMessage": "{n,plural,one{an} other{# années}}" + }, "wgMz3h": { "defaultMessage": "{count,plural,one{# mise en œuvre du relais} other{# implémentations de relais}}, à partir de {price} par mois, sans frais d’installation." }, diff --git a/src/locales/ja.json b/src/locales/ja.json index d465c8d..1fbe2bd 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -17,9 +17,6 @@ "+H6Rvj": { "defaultMessage": "BlossomおよびNIP-96メディアサーバーを自分で運用して、 {price}1か月に1回。Route96は、独自のホスト名で数分以内に起動し、永続ストレージとTLSが組み込まれています。" }, - "+JVG/q": { - "defaultMessage": "{n,plural,one{年} other{年}}" - }, "+Mo6Oy": { "defaultMessage": "VPS上でビットコインのフルノードを稼働させる。 {regions}ビットコインで、ライトニングネットワークまたはオンチェーン経由で支払ってください。 {price} 付加価値税抜き。" }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "このサブスクリプションにチャージするには、任意のLightningウォレットからお支払いください。" }, + "1bHQJa": { + "defaultMessage": "{n,plural,one{月} other{# か月}}" + }, "1kZ3H0": { "defaultMessage": "取引を表示" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "製品" }, - "7Xz8h2": { - "defaultMessage": "{n,plural,one{日} other{日}}" - }, "7agV2X": { "defaultMessage": "リレー" }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "チームに協力を求めましょう。" }, + "Cbg9PU": { + "defaultMessage": "{n,plural,one{日} other{# 日}}" + }, "Cououe": { "defaultMessage": "このアプリを公開するには、ログインしてください。" }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "サーバーのデプロイと管理" }, - "WxTcqt": { - "defaultMessage": "{n,plural,one{月} other{月}}" - }, "X/4SWn": { "defaultMessage": "カナダのケベック州でVPSホスティングをご利用いただけます。CPU、メモリ、ディスクをお選びください。お支払いは、ライトニングネットワークまたはオンチェーンでのビットコイン決済でお願いします。" }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "使用方法" }, + "wcUmWu": { + "defaultMessage": "{n,plural,one{年} other{# 年}}" + }, "wgMz3h": { "defaultMessage": "{count,plural,one{# リレーの実装} other{# リレーの実装}}〜から {price} 月額料金制で、初期費用はかかりません。" }, diff --git a/src/locales/ko.json b/src/locales/ko.json index 4a9de73..95cb6d8 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -17,9 +17,6 @@ "+H6Rvj": { "defaultMessage": "Blossom과 NIP-96 미디어 서버를 직접 운영하여 {price}/월. Route96은 자체 호스트 이름으로 몇 분 안에 실행되며, 영구 저장 공간과 TLS가 포함되어 있습니다." }, - "+JVG/q": { - "defaultMessage": "{n,plural,one{년} other{년}}" - }, "+Mo6Oy": { "defaultMessage": "[특정 지역]에 있는 VPS에서 비트코인 풀 노드를 실행합니다. {regions}비트코인으로 라이트닝 네트워크 또는 온체인 방식으로 결제하세요. {price} 부가세 제외." }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "어떤 라이트닝 지갑에서든 결제하여 이 구독 서비스를 충전할 수 있습니다." }, + "1bHQJa": { + "defaultMessage": "{n,plural,one{달} other{# 개월}}" + }, "1kZ3H0": { "defaultMessage": "거래 보기" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "제품" }, - "7Xz8h2": { - "defaultMessage": "{n,plural,one{일} other{일}}" - }, "7agV2X": { "defaultMessage": "릴레이" }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "팀의 도움을 받으세요." }, + "Cbg9PU": { + "defaultMessage": "{n,plural,one{일} other{# 일}}" + }, "Cououe": { "defaultMessage": "이 앱을 배포하려면 로그인하세요." }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "서버 배포 및 관리" }, - "WxTcqt": { - "defaultMessage": "{n,plural,one{달} other{개월}}" - }, "X/4SWn": { "defaultMessage": "캐나다 퀘벡에서 VPS 호스팅을 이용하세요. 원하는 CPU, 메모리, 디스크를 선택하고, 라이트닝 네트워크 또는 온체인 방식으로 비트코인으로 결제하세요." }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "사용법" }, + "wcUmWu": { + "defaultMessage": "{n,plural,one{년} other{# 년}}" + }, "wgMz3h": { "defaultMessage": "{count,plural,one{# 릴레이 구현} other{# 릴레이 구현}}~부터 {price} 매월 부과되며, 초기 설정 비용은 없습니다." }, diff --git a/src/locales/pt.json b/src/locales/pt.json index 74c3c19..2ef1c2e 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -17,9 +17,6 @@ "+H6Rvj": { "defaultMessage": "Configure o seu próprio servidor de mídia Blossom e NIP-96 para {price}/mês. Route96, disponível em minutos no seu próprio nome de domínio, com armazenamento persistente e TLS incluídos." }, - "+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." }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "Pague através de qualquer carteira Lightning para renovar esta assinatura." }, + "1bHQJa": { + "defaultMessage": "{n,plural,one{mês} other{# meses}}" + }, "1kZ3H0": { "defaultMessage": "Ver transação" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "Produtos" }, - "7Xz8h2": { - "defaultMessage": "{n,plural,one{dia} other{dias}}" - }, "7agV2X": { "defaultMessage": "Revezamento / Transmitir / Relé" }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "Peça ajuda à equipa." }, + "Cbg9PU": { + "defaultMessage": "{n,plural,one{dia} other{# dias}}" + }, "Cououe": { "defaultMessage": "Faça o login para implementar este aplicativo." }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "Implante e gerencie servidores" }, - "WxTcqt": { - "defaultMessage": "{n,plural,one{mês} other{meses}}" - }, "X/4SWn": { "defaultMessage": "Serviço de hospedagem VPS em Quebec, Canadá. Escolha o seu processador, memória e disco. Pague com Bitcoin através da Lightning Network ou diretamente na blockchain." }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "Utilização" }, + "wcUmWu": { + "defaultMessage": "{n,plural,one{ano} other{# anos}}" + }, "wgMz3h": { "defaultMessage": "{count,plural,one{# implementação do relé} other{# implementações de retransmissão}}, de {price} por mês, sem taxa de configuração." }, diff --git a/src/locales/ru.json b/src/locales/ru.json index 414902a..30eb307 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -17,9 +17,6 @@ "+H6Rvj": { "defaultMessage": "Запустите собственный медиасервер Blossom и NIP-96 для {price}В месяц. Route96 — развертывание занимает всего несколько минут на собственном сервере с использованием постоянного хранилища и поддержкой TLS." }, - "+JVG/q": { - "defaultMessage": "{n,plural,one{год} other{лет}}" - }, "+Mo6Oy": { "defaultMessage": "Запустите полную ноду Bitcoin на виртуальном частном сервере в… {regions}Оплачивайте в биткоинах через Lightning Network или напрямую, в основной сети. С {price} без НДС." }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "Оплатите подписку с любого кошелька Lightning." }, + "1bHQJa": { + "defaultMessage": "{n,plural,one{месяц} other{# месяцы}}" + }, "1kZ3H0": { "defaultMessage": "Посмотреть транзакцию" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "Продукты" }, - "7Xz8h2": { - "defaultMessage": "{n,plural,one{день} other{дней}}" - }, "7agV2X": { "defaultMessage": "Эстафета" }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "Обратитесь за помощью к команде." }, + "Cbg9PU": { + "defaultMessage": "{n,plural,one{день} other{# дней}}" + }, "Cououe": { "defaultMessage": "Войдите в систему, чтобы установить это приложение." }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "Разворачивайте и управляйте серверами" }, - "WxTcqt": { - "defaultMessage": "{n,plural,one{месяц} other{месяцы}}" - }, "X/4SWn": { "defaultMessage": "Виртуальный выделенный сервер (VPS) в Квебеке, Канада. Выберите процессор, объем памяти и дисковое пространство. Оплачивайте в биткоинах через Lightning Network или напрямую." }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "Применение/Использование" }, + "wcUmWu": { + "defaultMessage": "{n,plural,one{год} other{# лет}}" + }, "wgMz3h": { "defaultMessage": "{count,plural,one{# реализация ретранслятора} few{# реализации ретранслятора} many{# реализаций ретранслятора} other{# реализации ретранслятора}}, начиная с {price} в месяц, без платы за установку." }, diff --git a/src/locales/tr.json b/src/locales/tr.json index 237d194..4c3bcba 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -17,9 +17,6 @@ "+H6Rvj": { "defaultMessage": "Kendi Blossom ve NIP-96 medya sunucunuzu çalıştırın. {price}/ay. Route96, kendi alan adında birkaç dakika içinde kalıcı depolama ve TLS ile birlikte kullanıma hazır hale getirilebilir." }, - "+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ç." }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "Bu aboneliği yenilemek için herhangi bir Lightning cüzdanından ödeme yapın." }, + "1bHQJa": { + "defaultMessage": "{n,plural,one{ay} other{# aylar}}" + }, "1kZ3H0": { "defaultMessage": "İşlemi görüntüle" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "Ürünler" }, - "7Xz8h2": { - "defaultMessage": "{n,plural,one{gün} other{gün}}" - }, "7agV2X": { "defaultMessage": "Bayrak yarışı / Röle" }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "Ekip üyelerinden yardım alın." }, + "Cbg9PU": { + "defaultMessage": "{n,plural,one{gün} other{# gün}}" + }, "Cououe": { "defaultMessage": "Bu uygulamayı kullanıma sunmak için oturum açın." }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "Sunucuları dağıtın ve yönetin" }, - "WxTcqt": { - "defaultMessage": "{n,plural,one{ay} other{aylar}}" - }, "X/4SWn": { "defaultMessage": "Kanada, Quebec'te VPS hosting hizmeti. İstediğiniz işlemciyi, belleği ve diski seçin. Ödemenizi Lightning veya doğrudan Bitcoin ile yapın." }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "Kullanım" }, + "wcUmWu": { + "defaultMessage": "{n,plural,one{yıl} other{# yıl}}" + }, "wgMz3h": { "defaultMessage": "{count,plural,one{# röle uygulaması} other{# röle uygulamaları}}, itibaren {price} aylık olarak, kurulum ücreti yok." }, diff --git a/src/locales/zh.json b/src/locales/zh.json index bc71332..fc152fe 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -17,9 +17,6 @@ "+H6Rvj": { "defaultMessage": "运行您自己的 Blossom 和 NIP-96 媒体服务器,用于…… {price}每月。Route96,可在几分钟内通过其自身的域名进行部署,并包含持久性存储和 TLS 加密。" }, - "+JVG/q": { - "defaultMessage": "{n,plural,one{年} other{年}}" - }, "+Mo6Oy": { "defaultMessage": "在某个虚拟专用服务器(VPS)上运行一个比特币完整节点。 {regions}可通过闪电网络或链上方式使用比特币支付。从…… {price} 不含增值税。" }, @@ -131,6 +128,9 @@ "1aBLDj": { "defaultMessage": "使用任意闪电网络钱包支付,即可充值此订阅。" }, + "1bHQJa": { + "defaultMessage": "{n,plural,one{月} other{# 几个月}}" + }, "1kZ3H0": { "defaultMessage": "查看交易" }, @@ -359,9 +359,6 @@ "7NFfmz": { "defaultMessage": "产品" }, - "7Xz8h2": { - "defaultMessage": "{n,plural,one{天} other{天}}" - }, "7agV2X": { "defaultMessage": "中继;传递" }, @@ -569,6 +566,9 @@ "CYP/Jx": { "defaultMessage": "寻求团队的帮助。" }, + "Cbg9PU": { + "defaultMessage": "{n,plural,one{天} other{# 天}}" + }, "Cououe": { "defaultMessage": "登录以部署此应用程序。" }, @@ -1376,9 +1376,6 @@ "WwXOJ8": { "defaultMessage": "部署和管理服务器" }, - "WxTcqt": { - "defaultMessage": "{n,plural,one{月} other{月}}" - }, "X/4SWn": { "defaultMessage": "在加拿大魁北克省提供 VPS 虚拟主机服务。您可以选择所需的 CPU、内存和硬盘。支持通过闪电网络或链上方式使用比特币支付。" }, @@ -2363,6 +2360,9 @@ "wbsq7O": { "defaultMessage": "使用方法" }, + "wcUmWu": { + "defaultMessage": "{n,plural,one{年} other{# 年}}" + }, "wgMz3h": { "defaultMessage": "{count,plural,one{# 个中继实现} other{# 个中继实现}},从 {price} 每月起,无需支付设置费用。" }, diff --git a/src/utils/currency.test.ts b/src/utils/currency.test.ts index eb3ffeb..8c94b2d 100644 --- a/src/utils/currency.test.ts +++ b/src/utils/currency.test.ts @@ -5,11 +5,11 @@ import { formatIntervalText, formatPriceText, formatPriceWithInterval } from "./ const intl = createIntl({ locale: "en", messages: {} }); describe("formatIntervalText", () => { - test("singular at one, plural above it", () => { + test("bare word at one, count prefixed above it", () => { expect(formatIntervalText(intl, "month", 1)).toBe("month"); - expect(formatIntervalText(intl, "month", 3)).toBe("months"); + expect(formatIntervalText(intl, "month", 3)).toBe("3 months"); expect(formatIntervalText(intl, "day", 1)).toBe("day"); - expect(formatIntervalText(intl, "year", 2)).toBe("years"); + expect(formatIntervalText(intl, "year", 2)).toBe("2 years"); }); test("defaults to one when n is omitted", () => { @@ -43,7 +43,7 @@ describe("formatPriceWithInterval", () => { ); }); - test("carries interval_amount into the pluralised word", () => { + test("carries interval_amount into both the count and the word", () => { const cost = { currency: "EUR", amount: 1500, @@ -51,7 +51,7 @@ describe("formatPriceWithInterval", () => { interval_amount: 3, }; expect(formatPriceWithInterval(intl, cost)).toBe( - `${formatPriceText(intl, cost)}/months`, + `${formatPriceText(intl, cost)}/3 months`, ); }); }); diff --git a/src/utils/currency.ts b/src/utils/currency.ts index e15ec48..0b78963 100644 --- a/src/utils/currency.ts +++ b/src/utils/currency.ts @@ -54,17 +54,17 @@ export function formatIntervalText( switch (interval) { case "day": return intl.formatMessage( - { defaultMessage: "{n, plural, one {day} other {days}}" }, + { defaultMessage: "{n, plural, one {day} other {# days}}" }, { n }, ); case "month": return intl.formatMessage( - { defaultMessage: "{n, plural, one {month} other {months}}" }, + { defaultMessage: "{n, plural, one {month} other {# months}}" }, { n }, ); case "year": return intl.formatMessage( - { defaultMessage: "{n, plural, one {year} other {years}}" }, + { defaultMessage: "{n, plural, one {year} other {# years}}" }, { n }, ); default: From 63570e5d1513469dd6200c8067e1f2c634b924af Mon Sep 17 00:00:00 2001 From: v0l Date: Thu, 30 Jul 2026 20:25:32 +0100 Subject: [PATCH 4/5] fix(vps): move the count out of the shared interval word Signed-off-by: v0l --- src/components/cost.tsx | 5 ++++- src/locales/ar.json | 18 +++++++++--------- src/locales/de.json | 18 +++++++++--------- src/locales/en.json | 18 +++++++++--------- src/locales/es.json | 18 +++++++++--------- src/locales/fr.json | 18 +++++++++--------- src/locales/ja.json | 18 +++++++++--------- src/locales/ko.json | 18 +++++++++--------- src/locales/pt.json | 18 +++++++++--------- src/locales/ru.json | 18 +++++++++--------- src/locales/tr.json | 18 +++++++++--------- src/locales/zh.json | 18 +++++++++--------- src/utils/currency.test.ts | 6 +++--- src/utils/currency.ts | 21 +++++++++++---------- 14 files changed, 117 insertions(+), 113 deletions(-) diff --git a/src/components/cost.tsx b/src/components/cost.tsx index 90515c4..7078a48 100644 --- a/src/components/cost.tsx +++ b/src/components/cost.tsx @@ -114,7 +114,10 @@ export function CostAmount({ {cost.currency === "BTC" && " sats"} {cost.interval_type && ( <> - / + /{cost.interval_amount && cost.interval_amount > 1 && ( + <>{cost.interval_amount} + )} + )} diff --git a/src/locales/ar.json b/src/locales/ar.json index fbc1c5d..ef65852 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -17,6 +17,9 @@ "+H6Rvj": { "defaultMessage": "قم بتشغيل خادم الوسائط الخاص بك من نوع Blossom وNIP-96 لـ {price}‏/شهر. يمكن إعداد Route96 في دقائق معدودة على اسم النطاق الخاص به، مع توفير مساحة تخزين دائمة وبروتوكول أمان طبقة النقل (TLS)." }, + "+JVG/q": { + "defaultMessage": "{n,plural,one{سنة} other{سنوات}}" + }, "+Mo6Oy": { "defaultMessage": "قم بتشغيل عقدة بيتكوين كاملة على خادم افتراضي خاص في {regions}يمكنك الدفع بعملة البيتكوين عبر شبكة لايتنينج أو مباشرةً على البلوك تشين. ابتداءً من: {price} باستثناء ضريبة القيمة المضافة." }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "ادفع من أي محفظة تدعم شبكة لايتنينغ لتجديد الاشتراك." }, - "1bHQJa": { - "defaultMessage": "{n,plural,one{شهر} other{# أشهر}}" - }, "1kZ3H0": { "defaultMessage": "عرض المعاملة" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "المنتجات" }, + "7Xz8h2": { + "defaultMessage": "{n,plural,one{يوم} other{أيام}}" + }, "7agV2X": { "defaultMessage": "يُمرِّر/ينقل" }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "اطلب المساعدة من الفريق." }, - "Cbg9PU": { - "defaultMessage": "{n,plural,one{يوم} other{# أيام}}" - }, "Cououe": { "defaultMessage": "سجّل الدخول لتفعيل هذا التطبيق." }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "انشر الخوادم وأدِرها" }, + "WxTcqt": { + "defaultMessage": "{n,plural,one{شهر} other{أشهر}}" + }, "X/4SWn": { "defaultMessage": "خدمة استضافة VPS في كيبيك، كندا. اختر وحدة المعالجة المركزية والذاكرة والتخزين الذي يناسبك. ادفع باستخدام عملة البيتكوين عبر شبكة Lightning أو مباشرة." }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "الاستخدام" }, - "wcUmWu": { - "defaultMessage": "{n,plural,one{سنة} other{# سنوات}}" - }, "wgMz3h": { "defaultMessage": "{count,plural,zero{# تنفيذ لآلية الترحيل} one{تنفيذ واحد لآلية الترحيل} two{تنفيذان لآلية الترحيل} few{# تنفيذات لآلية الترحيل} many{# تنفيذًا لآلية الترحيل} other{# تنفيذ لآلية الترحيل}}، من {price} شهريًا، بدون رسوم إعداد أولية." }, diff --git a/src/locales/de.json b/src/locales/de.json index a75cc69..e279016 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -17,6 +17,9 @@ "+H6Rvj": { "defaultMessage": "Betreiben Sie Ihren eigenen Blossom- und NIP-96-Medienserver für {price}/Monat. Route96 lässt sich innerhalb von Minuten auf seinem eigenen Hostnamen installieren und bietet persistente Datenspeicherung sowie TLS-Unterstützung." }, + "+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." }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "Zahlen Sie mit einer beliebigen Lightning-Wallet, um dieses Abonnement aufzuladen." }, - "1bHQJa": { - "defaultMessage": "{n,plural,one{Monat} other{# Monate}}" - }, "1kZ3H0": { "defaultMessage": "Transaktion ansehen" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "Produkte" }, + "7Xz8h2": { + "defaultMessage": "{n,plural,one{Tag} other{Tage}}" + }, "7agV2X": { "defaultMessage": "Weiterleiten / Relais" }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "Lassen Sie sich vom Team unterstützen." }, - "Cbg9PU": { - "defaultMessage": "{n,plural,one{Tag} other{# Tage}}" - }, "Cououe": { "defaultMessage": "Melden Sie sich an, um diese App zu installieren." }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "Server bereitstellen & verwalten" }, + "WxTcqt": { + "defaultMessage": "{n,plural,one{Monat} other{Monate}}" + }, "X/4SWn": { "defaultMessage": "VPS-Hosting in Québec, Kanada. Wählen Sie Ihre CPU, Ihren Speicher und Ihre Festplatte aus. Bezahlen Sie in Bitcoin über Lightning oder direkt in der Bitcoin-Blockchain." }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "Anwendung" }, - "wcUmWu": { - "defaultMessage": "{n,plural,one{Jahr} other{# Jahre}}" - }, "wgMz3h": { "defaultMessage": "{count,plural,one{# Relais-Implementierung} other{# Relais-Implementierungen}}, von {price} pro Monat, ohne Einrichtungskosten." }, diff --git a/src/locales/en.json b/src/locales/en.json index 7fbc2f5..9c1fbc0 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -17,6 +17,9 @@ "+H6Rvj": { "defaultMessage": "Run your own Blossom and NIP-96 media server for {price}/month. Route96, up in minutes on its own hostname with persistent storage and TLS included." }, + "+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." }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "Pay from any Lightning wallet to top up this subscription." }, - "1bHQJa": { - "defaultMessage": "{n, plural, one {month} other {# months}}" - }, "1kZ3H0": { "defaultMessage": "View transaction" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "Products" }, + "7Xz8h2": { + "defaultMessage": "{n, plural, one {day} other {days}}" + }, "7agV2X": { "defaultMessage": "Relay" }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "Get help from the team" }, - "Cbg9PU": { - "defaultMessage": "{n, plural, one {day} other {# days}}" - }, "Cououe": { "defaultMessage": "Log in to deploy this app." }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "Deploy & manage servers" }, + "WxTcqt": { + "defaultMessage": "{n, plural, one {month} other {months}}" + }, "X/4SWn": { "defaultMessage": "VPS hosting in Quebec, Canada. Pick your CPU, memory and disk. Pay in Bitcoin over Lightning or on-chain." }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "Usage" }, - "wcUmWu": { - "defaultMessage": "{n, plural, one {year} other {# years}}" - }, "wgMz3h": { "defaultMessage": "{count, plural, one {# relay implementation} other {# relay implementations}}, from {price} per month, no setup fee." }, diff --git a/src/locales/es.json b/src/locales/es.json index 5b4717d..9ce1104 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -17,6 +17,9 @@ "+H6Rvj": { "defaultMessage": "Ejecute su propio servidor de medios Blossom y NIP-96. {price}/mes. Route96 se puede implementar en cuestión de minutos en su propio servidor, con almacenamiento persistente y TLS incluido." }, + "+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." }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "Pague desde cualquier billetera Lightning para recargar esta suscripción." }, - "1bHQJa": { - "defaultMessage": "{n,plural,one{mes} other{# meses}}" - }, "1kZ3H0": { "defaultMessage": "Ver transacción" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "Productos" }, + "7Xz8h2": { + "defaultMessage": "{n,plural,one{día} other{días}}" + }, "7agV2X": { "defaultMessage": "Relevo." }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "Pide ayuda al equipo." }, - "Cbg9PU": { - "defaultMessage": "{n,plural,one{día} other{# días}}" - }, "Cououe": { "defaultMessage": "Inicie sesión para implementar esta aplicación." }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "Despliega y gestiona servidores" }, + "WxTcqt": { + "defaultMessage": "{n,plural,one{mes} other{meses}}" + }, "X/4SWn": { "defaultMessage": "Alojamiento VPS en Quebec, Canadá. Elija su CPU, memoria y disco. Pague con Bitcoin a través de Lightning o directamente en la cadena de bloques." }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "Uso." }, - "wcUmWu": { - "defaultMessage": "{n,plural,one{año} other{# años}}" - }, "wgMz3h": { "defaultMessage": "{count,plural,one{# implementación del relé} other{# implementaciones de relés}}, desde {price} por mes, sin coste de instalación." }, diff --git a/src/locales/fr.json b/src/locales/fr.json index 2eb246e..3812b8b 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -17,6 +17,9 @@ "+H6Rvj": { "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{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." }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "Effectuez un paiement depuis n’importe quel portefeuille Lightning pour renouveler cet abonnement." }, - "1bHQJa": { - "defaultMessage": "{n,plural,one{mois} other{# mois}}" - }, "1kZ3H0": { "defaultMessage": "Voir la transaction" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "Produits" }, + "7Xz8h2": { + "defaultMessage": "{n,plural,one{jour} other{jours}}" + }, "7agV2X": { "defaultMessage": "Relais" }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "Demandez de l’aide à l’équipe." }, - "Cbg9PU": { - "defaultMessage": "{n,plural,one{jour} other{# jours}}" - }, "Cououe": { "defaultMessage": "Connectez-vous pour déployer cette application." }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "Déployer et gérer des serveurs" }, + "WxTcqt": { + "defaultMessage": "{n,plural,one{mois} other{mois}}" + }, "X/4SWn": { "defaultMessage": "Hébergement VPS au Québec, au Canada. Choisissez votre processeur, votre mémoire et votre disque dur. Payez en Bitcoin via Lightning ou directement sur la chaîne de blocs." }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "Utilisation" }, - "wcUmWu": { - "defaultMessage": "{n,plural,one{an} other{# années}}" - }, "wgMz3h": { "defaultMessage": "{count,plural,one{# mise en œuvre du relais} other{# implémentations de relais}}, à partir de {price} par mois, sans frais d’installation." }, diff --git a/src/locales/ja.json b/src/locales/ja.json index 1fbe2bd..7c899ae 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -17,6 +17,9 @@ "+H6Rvj": { "defaultMessage": "BlossomおよびNIP-96メディアサーバーを自分で運用して、 {price}1か月に1回。Route96は、独自のホスト名で数分以内に起動し、永続ストレージとTLSが組み込まれています。" }, + "+JVG/q": { + "defaultMessage": "{n,plural,one{年} other{年}}" + }, "+Mo6Oy": { "defaultMessage": "VPS上でビットコインのフルノードを稼働させる。 {regions}ビットコインで、ライトニングネットワークまたはオンチェーン経由で支払ってください。 {price} 付加価値税抜き。" }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "このサブスクリプションにチャージするには、任意のLightningウォレットからお支払いください。" }, - "1bHQJa": { - "defaultMessage": "{n,plural,one{月} other{# か月}}" - }, "1kZ3H0": { "defaultMessage": "取引を表示" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "製品" }, + "7Xz8h2": { + "defaultMessage": "{n,plural,one{日} other{日}}" + }, "7agV2X": { "defaultMessage": "リレー" }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "チームに協力を求めましょう。" }, - "Cbg9PU": { - "defaultMessage": "{n,plural,one{日} other{# 日}}" - }, "Cououe": { "defaultMessage": "このアプリを公開するには、ログインしてください。" }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "サーバーのデプロイと管理" }, + "WxTcqt": { + "defaultMessage": "{n,plural,one{月} other{か月}}" + }, "X/4SWn": { "defaultMessage": "カナダのケベック州でVPSホスティングをご利用いただけます。CPU、メモリ、ディスクをお選びください。お支払いは、ライトニングネットワークまたはオンチェーンでのビットコイン決済でお願いします。" }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "使用方法" }, - "wcUmWu": { - "defaultMessage": "{n,plural,one{年} other{# 年}}" - }, "wgMz3h": { "defaultMessage": "{count,plural,one{# リレーの実装} other{# リレーの実装}}〜から {price} 月額料金制で、初期費用はかかりません。" }, diff --git a/src/locales/ko.json b/src/locales/ko.json index 95cb6d8..4a9de73 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -17,6 +17,9 @@ "+H6Rvj": { "defaultMessage": "Blossom과 NIP-96 미디어 서버를 직접 운영하여 {price}/월. Route96은 자체 호스트 이름으로 몇 분 안에 실행되며, 영구 저장 공간과 TLS가 포함되어 있습니다." }, + "+JVG/q": { + "defaultMessage": "{n,plural,one{년} other{년}}" + }, "+Mo6Oy": { "defaultMessage": "[특정 지역]에 있는 VPS에서 비트코인 풀 노드를 실행합니다. {regions}비트코인으로 라이트닝 네트워크 또는 온체인 방식으로 결제하세요. {price} 부가세 제외." }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "어떤 라이트닝 지갑에서든 결제하여 이 구독 서비스를 충전할 수 있습니다." }, - "1bHQJa": { - "defaultMessage": "{n,plural,one{달} other{# 개월}}" - }, "1kZ3H0": { "defaultMessage": "거래 보기" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "제품" }, + "7Xz8h2": { + "defaultMessage": "{n,plural,one{일} other{일}}" + }, "7agV2X": { "defaultMessage": "릴레이" }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "팀의 도움을 받으세요." }, - "Cbg9PU": { - "defaultMessage": "{n,plural,one{일} other{# 일}}" - }, "Cououe": { "defaultMessage": "이 앱을 배포하려면 로그인하세요." }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "서버 배포 및 관리" }, + "WxTcqt": { + "defaultMessage": "{n,plural,one{달} other{개월}}" + }, "X/4SWn": { "defaultMessage": "캐나다 퀘벡에서 VPS 호스팅을 이용하세요. 원하는 CPU, 메모리, 디스크를 선택하고, 라이트닝 네트워크 또는 온체인 방식으로 비트코인으로 결제하세요." }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "사용법" }, - "wcUmWu": { - "defaultMessage": "{n,plural,one{년} other{# 년}}" - }, "wgMz3h": { "defaultMessage": "{count,plural,one{# 릴레이 구현} other{# 릴레이 구현}}~부터 {price} 매월 부과되며, 초기 설정 비용은 없습니다." }, diff --git a/src/locales/pt.json b/src/locales/pt.json index 2ef1c2e..74c3c19 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -17,6 +17,9 @@ "+H6Rvj": { "defaultMessage": "Configure o seu próprio servidor de mídia Blossom e NIP-96 para {price}/mês. Route96, disponível em minutos no seu próprio nome de domínio, com armazenamento persistente e TLS incluídos." }, + "+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." }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "Pague através de qualquer carteira Lightning para renovar esta assinatura." }, - "1bHQJa": { - "defaultMessage": "{n,plural,one{mês} other{# meses}}" - }, "1kZ3H0": { "defaultMessage": "Ver transação" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "Produtos" }, + "7Xz8h2": { + "defaultMessage": "{n,plural,one{dia} other{dias}}" + }, "7agV2X": { "defaultMessage": "Revezamento / Transmitir / Relé" }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "Peça ajuda à equipa." }, - "Cbg9PU": { - "defaultMessage": "{n,plural,one{dia} other{# dias}}" - }, "Cououe": { "defaultMessage": "Faça o login para implementar este aplicativo." }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "Implante e gerencie servidores" }, + "WxTcqt": { + "defaultMessage": "{n,plural,one{mês} other{meses}}" + }, "X/4SWn": { "defaultMessage": "Serviço de hospedagem VPS em Quebec, Canadá. Escolha o seu processador, memória e disco. Pague com Bitcoin através da Lightning Network ou diretamente na blockchain." }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "Utilização" }, - "wcUmWu": { - "defaultMessage": "{n,plural,one{ano} other{# anos}}" - }, "wgMz3h": { "defaultMessage": "{count,plural,one{# implementação do relé} other{# implementações de retransmissão}}, de {price} por mês, sem taxa de configuração." }, diff --git a/src/locales/ru.json b/src/locales/ru.json index 30eb307..414902a 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -17,6 +17,9 @@ "+H6Rvj": { "defaultMessage": "Запустите собственный медиасервер Blossom и NIP-96 для {price}В месяц. Route96 — развертывание занимает всего несколько минут на собственном сервере с использованием постоянного хранилища и поддержкой TLS." }, + "+JVG/q": { + "defaultMessage": "{n,plural,one{год} other{лет}}" + }, "+Mo6Oy": { "defaultMessage": "Запустите полную ноду Bitcoin на виртуальном частном сервере в… {regions}Оплачивайте в биткоинах через Lightning Network или напрямую, в основной сети. С {price} без НДС." }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "Оплатите подписку с любого кошелька Lightning." }, - "1bHQJa": { - "defaultMessage": "{n,plural,one{месяц} other{# месяцы}}" - }, "1kZ3H0": { "defaultMessage": "Посмотреть транзакцию" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "Продукты" }, + "7Xz8h2": { + "defaultMessage": "{n,plural,one{день} other{дней}}" + }, "7agV2X": { "defaultMessage": "Эстафета" }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "Обратитесь за помощью к команде." }, - "Cbg9PU": { - "defaultMessage": "{n,plural,one{день} other{# дней}}" - }, "Cououe": { "defaultMessage": "Войдите в систему, чтобы установить это приложение." }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "Разворачивайте и управляйте серверами" }, + "WxTcqt": { + "defaultMessage": "{n,plural,one{месяц} other{месяцы}}" + }, "X/4SWn": { "defaultMessage": "Виртуальный выделенный сервер (VPS) в Квебеке, Канада. Выберите процессор, объем памяти и дисковое пространство. Оплачивайте в биткоинах через Lightning Network или напрямую." }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "Применение/Использование" }, - "wcUmWu": { - "defaultMessage": "{n,plural,one{год} other{# лет}}" - }, "wgMz3h": { "defaultMessage": "{count,plural,one{# реализация ретранслятора} few{# реализации ретранслятора} many{# реализаций ретранслятора} other{# реализации ретранслятора}}, начиная с {price} в месяц, без платы за установку." }, diff --git a/src/locales/tr.json b/src/locales/tr.json index 4c3bcba..237d194 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -17,6 +17,9 @@ "+H6Rvj": { "defaultMessage": "Kendi Blossom ve NIP-96 medya sunucunuzu çalıştırın. {price}/ay. Route96, kendi alan adında birkaç dakika içinde kalıcı depolama ve TLS ile birlikte kullanıma hazır hale getirilebilir." }, + "+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ç." }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "Bu aboneliği yenilemek için herhangi bir Lightning cüzdanından ödeme yapın." }, - "1bHQJa": { - "defaultMessage": "{n,plural,one{ay} other{# aylar}}" - }, "1kZ3H0": { "defaultMessage": "İşlemi görüntüle" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "Ürünler" }, + "7Xz8h2": { + "defaultMessage": "{n,plural,one{gün} other{gün}}" + }, "7agV2X": { "defaultMessage": "Bayrak yarışı / Röle" }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "Ekip üyelerinden yardım alın." }, - "Cbg9PU": { - "defaultMessage": "{n,plural,one{gün} other{# gün}}" - }, "Cououe": { "defaultMessage": "Bu uygulamayı kullanıma sunmak için oturum açın." }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "Sunucuları dağıtın ve yönetin" }, + "WxTcqt": { + "defaultMessage": "{n,plural,one{ay} other{aylar}}" + }, "X/4SWn": { "defaultMessage": "Kanada, Quebec'te VPS hosting hizmeti. İstediğiniz işlemciyi, belleği ve diski seçin. Ödemenizi Lightning veya doğrudan Bitcoin ile yapın." }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "Kullanım" }, - "wcUmWu": { - "defaultMessage": "{n,plural,one{yıl} other{# yıl}}" - }, "wgMz3h": { "defaultMessage": "{count,plural,one{# röle uygulaması} other{# röle uygulamaları}}, itibaren {price} aylık olarak, kurulum ücreti yok." }, diff --git a/src/locales/zh.json b/src/locales/zh.json index fc152fe..ad46791 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -17,6 +17,9 @@ "+H6Rvj": { "defaultMessage": "运行您自己的 Blossom 和 NIP-96 媒体服务器,用于…… {price}每月。Route96,可在几分钟内通过其自身的域名进行部署,并包含持久性存储和 TLS 加密。" }, + "+JVG/q": { + "defaultMessage": "{n,plural,one{年} other{年}}" + }, "+Mo6Oy": { "defaultMessage": "在某个虚拟专用服务器(VPS)上运行一个比特币完整节点。 {regions}可通过闪电网络或链上方式使用比特币支付。从…… {price} 不含增值税。" }, @@ -128,9 +131,6 @@ "1aBLDj": { "defaultMessage": "使用任意闪电网络钱包支付,即可充值此订阅。" }, - "1bHQJa": { - "defaultMessage": "{n,plural,one{月} other{# 几个月}}" - }, "1kZ3H0": { "defaultMessage": "查看交易" }, @@ -359,6 +359,9 @@ "7NFfmz": { "defaultMessage": "产品" }, + "7Xz8h2": { + "defaultMessage": "{n,plural,one{天} other{天}}" + }, "7agV2X": { "defaultMessage": "中继;传递" }, @@ -566,9 +569,6 @@ "CYP/Jx": { "defaultMessage": "寻求团队的帮助。" }, - "Cbg9PU": { - "defaultMessage": "{n,plural,one{天} other{# 天}}" - }, "Cououe": { "defaultMessage": "登录以部署此应用程序。" }, @@ -1376,6 +1376,9 @@ "WwXOJ8": { "defaultMessage": "部署和管理服务器" }, + "WxTcqt": { + "defaultMessage": "{n,plural,one{月} other{月份}}" + }, "X/4SWn": { "defaultMessage": "在加拿大魁北克省提供 VPS 虚拟主机服务。您可以选择所需的 CPU、内存和硬盘。支持通过闪电网络或链上方式使用比特币支付。" }, @@ -2360,9 +2363,6 @@ "wbsq7O": { "defaultMessage": "使用方法" }, - "wcUmWu": { - "defaultMessage": "{n,plural,one{年} other{# 年}}" - }, "wgMz3h": { "defaultMessage": "{count,plural,one{# 个中继实现} other{# 个中继实现}},从 {price} 每月起,无需支付设置费用。" }, diff --git a/src/utils/currency.test.ts b/src/utils/currency.test.ts index 8c94b2d..fdda2a0 100644 --- a/src/utils/currency.test.ts +++ b/src/utils/currency.test.ts @@ -5,11 +5,11 @@ import { formatIntervalText, formatPriceText, formatPriceWithInterval } from "./ const intl = createIntl({ locale: "en", messages: {} }); describe("formatIntervalText", () => { - test("bare word at one, count prefixed above it", () => { + 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("3 months"); + expect(formatIntervalText(intl, "month", 3)).toBe("months"); expect(formatIntervalText(intl, "day", 1)).toBe("day"); - expect(formatIntervalText(intl, "year", 2)).toBe("2 years"); + expect(formatIntervalText(intl, "year", 2)).toBe("years"); }); test("defaults to one when n is omitted", () => { diff --git a/src/utils/currency.ts b/src/utils/currency.ts index 0b78963..62c9854 100644 --- a/src/utils/currency.ts +++ b/src/utils/currency.ts @@ -54,17 +54,17 @@ export function formatIntervalText( switch (interval) { case "day": return intl.formatMessage( - { defaultMessage: "{n, plural, one {day} other {# days}}" }, + { defaultMessage: "{n, plural, one {day} other {days}}" }, { n }, ); case "month": return intl.formatMessage( - { defaultMessage: "{n, plural, one {month} other {# months}}" }, + { defaultMessage: "{n, plural, one {month} other {months}}" }, { n }, ); case "year": return intl.formatMessage( - { defaultMessage: "{n, plural, one {year} other {# years}}" }, + { defaultMessage: "{n, plural, one {year} other {years}}" }, { n }, ); default: @@ -78,7 +78,10 @@ export function formatIntervalText( * * `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 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, @@ -89,12 +92,10 @@ export function formatPriceWithInterval( interval_amount?: number; }, ): string { - const interval = formatIntervalText( - intl, - cost.interval_type ?? "month", - cost.interval_amount, - ); - return `${formatPriceText(intl, cost)}/${interval}`; + 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). */ From 1af7dca959c7e032f47f21050102ade552d73646 Mon Sep 17 00:00:00 2001 From: v0l Date: Thu, 30 Jul 2026 20:27:18 +0100 Subject: [PATCH 5/5] fix(vps): a zero interval_amount must not render itself Signed-off-by: v0l --- src/components/cost.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/cost.tsx b/src/components/cost.tsx index 7078a48..37921ab 100644 --- a/src/components/cost.tsx +++ b/src/components/cost.tsx @@ -114,9 +114,7 @@ export function CostAmount({ {cost.currency === "BTC" && " sats"} {cost.interval_type && ( <> - /{cost.interval_amount && cost.interval_amount > 1 && ( - <>{cost.interval_amount} - )} + /{(cost.interval_amount ?? 1) > 1 && <>{cost.interval_amount} } )}