diff --git a/frontend/app/components/IntlProvider.tsx b/frontend/app/components/IntlProvider.tsx index aea34e5..1306622 100644 --- a/frontend/app/components/IntlProvider.tsx +++ b/frontend/app/components/IntlProvider.tsx @@ -17,11 +17,44 @@ import caExchange from "../../locales/ca/exchange.json"; import esAuth from "../../locales/es/auth.json"; import enAuth from "../../locales/en/auth.json"; import caAuth from "../../locales/ca/auth.json"; +import esTrades from "../../locales/es/trades.json"; +import enTrades from "../../locales/en/trades.json"; +import caTrades from "../../locales/ca/trades.json"; +import esInvites from "../../locales/es/invites.json"; +import enInvites from "../../locales/en/invites.json"; +import caInvites from "../../locales/ca/invites.json"; +import esProfile from "../../locales/es/profile.json"; +import enProfile from "../../locales/en/profile.json"; +import caProfile from "../../locales/ca/profile.json"; const messages = { - es: { common: esCommon, navigation: esNavigation, exchange: esExchange, auth: esAuth }, - en: { common: enCommon, navigation: enNavigation, exchange: enExchange, auth: enAuth }, - ca: { common: caCommon, navigation: caNavigation, exchange: caExchange, auth: caAuth }, + es: { + common: esCommon, + navigation: esNavigation, + exchange: esExchange, + auth: esAuth, + trades: esTrades, + invites: esInvites, + profile: esProfile, + }, + en: { + common: enCommon, + navigation: enNavigation, + exchange: enExchange, + auth: enAuth, + trades: enTrades, + invites: enInvites, + profile: enProfile, + }, + ca: { + common: caCommon, + navigation: caNavigation, + exchange: caExchange, + auth: caAuth, + trades: caTrades, + invites: caInvites, + profile: caProfile, + }, }; interface IntlProviderProps { diff --git a/frontend/app/exchange/components/BookingStep.tsx b/frontend/app/exchange/components/BookingStep.tsx index e84f076..c540b61 100644 --- a/frontend/app/exchange/components/BookingStep.tsx +++ b/frontend/app/exchange/components/BookingStep.tsx @@ -6,6 +6,7 @@ import { components } from "../../generated/api"; import { formatDate, formatTime } from "../../utils/date"; import { formatEur } from "../../utils/exchange"; import { bannerStyles } from "../../styles/shared"; +import { useTranslation } from "../../hooks/useTranslation"; type BookableSlot = components["schemas"]["BookableSlot"]; type ExchangeResponse = components["schemas"]["ExchangeResponse"]; @@ -215,14 +216,15 @@ export function BookingStep({ onSlotSelect, onBackToDetails, }: BookingStepProps) { + const t = useTranslation("exchange"); return ( <> {/* Trade Summary Card */}
- Your Exchange + {t("bookingStep.yourExchange")}
@@ -232,7 +234,7 @@ export function BookingStep({ color: direction === "buy" ? "#4ade80" : "#f87171", }} > - {direction === "buy" ? "Buy" : "Sell"} BTC + {direction === "buy" ? t("bookingStep.buy") : t("bookingStep.sell")} BTC {formatEur(eurAmount)} @@ -242,15 +244,17 @@ export function BookingStep({ - {direction === "buy" ? "Receive via " : "Send via "} - {bitcoinTransferMethod === "onchain" ? "Onchain" : "Lightning"} + {direction === "buy" ? t("bookingStep.receiveVia") : t("bookingStep.sendVia")}{" "} + {bitcoinTransferMethod === "onchain" + ? t("transferMethod.onchain") + : t("transferMethod.lightning")}
{/* Date Selection */}
-

Select a Date

+

{t("bookingStep.selectDate")}

{dates.map((date) => { const dateStr = formatDate(date); @@ -291,15 +295,13 @@ export function BookingStep({ {/* Warning for existing trade on selected date */} {existingTradeOnSelectedDate && (
-
- You already have a trade booked on this day. You can only book one trade per day. -
+
{t("bookingStep.existingTradeWarning")}
- View your existing trade → + {t("bookingStep.viewExistingTrade")}
@@ -309,7 +311,7 @@ export function BookingStep({ {selectedDate && !existingTradeOnSelectedDate && (

- Available Slots for{" "} + {t("bookingStep.availableSlots")}{" "} {selectedDate.toLocaleDateString("en-US", { weekday: "long", month: "long", @@ -318,9 +320,9 @@ export function BookingStep({

{isLoadingSlots ? ( -
Loading slots...
+
{t("bookingStep.loadingSlots")}
) : availableSlots.length === 0 ? ( -
No available slots for this date
+
{t("bookingStep.noSlots")}
) : (
{availableSlots.map((slot) => { diff --git a/frontend/app/exchange/components/ConfirmationStep.tsx b/frontend/app/exchange/components/ConfirmationStep.tsx index 1cb0197..e113527 100644 --- a/frontend/app/exchange/components/ConfirmationStep.tsx +++ b/frontend/app/exchange/components/ConfirmationStep.tsx @@ -6,6 +6,7 @@ import { components } from "../../generated/api"; import { formatTime } from "../../utils/date"; import { formatEur } from "../../utils/exchange"; import { buttonStyles } from "../../styles/shared"; +import { useTranslation } from "../../hooks/useTranslation"; type BookableSlot = components["schemas"]["BookableSlot"]; type Direction = "buy" | "sell"; @@ -154,14 +155,15 @@ export function ConfirmationStep({ onConfirm, onBack, }: ConfirmationStepProps) { + const t = useTranslation("exchange"); return ( <> {/* Compressed Booking Summary */}
- Appointment + {t("confirmationStep.appointment")}
@@ -181,44 +183,48 @@ export function ConfirmationStep({ {/* Confirmation Card */}
-

Confirm Trade

+

{t("confirmationStep.confirmTrade")}

- Time: + {t("confirmationStep.time")} {formatTime(selectedSlot.start_time)} - {formatTime(selectedSlot.end_time)}
- Direction: + {t("confirmationStep.direction")} - {direction === "buy" ? "Buy BTC" : "Sell BTC"} + {direction === "buy" ? t("direction.buyShort") : t("direction.sellShort")}
- EUR: + {t("confirmationStep.eur")} {formatEur(eurAmount)}
- BTC: + {t("confirmationStep.btc")}
- Rate: + {t("confirmationStep.rate")} {formatPrice(agreedPrice)}/BTC
- Payment: + {t("confirmationStep.payment")} - {direction === "buy" ? "Receive via " : "Send via "} - {bitcoinTransferMethod === "onchain" ? "Onchain" : "Lightning"} + {direction === "buy" + ? t("confirmationStep.receiveVia") + : t("confirmationStep.sendVia")}{" "} + {bitcoinTransferMethod === "onchain" + ? t("transferMethod.onchain") + : t("transferMethod.lightning")}
@@ -237,13 +243,15 @@ export function ConfirmationStep({ }} > {isBooking - ? "Booking..." + ? t("confirmationStep.booking") : isPriceStale - ? "Price Stale" - : `Confirm ${direction === "buy" ? "Buy" : "Sell"}`} + ? t("confirmationStep.priceStale") + : direction === "buy" + ? t("confirmationStep.confirmBuy") + : t("confirmationStep.confirmSell")}
diff --git a/frontend/app/exchange/components/ExchangeDetailsStep.tsx b/frontend/app/exchange/components/ExchangeDetailsStep.tsx index 3219baf..0f2d7de 100644 --- a/frontend/app/exchange/components/ExchangeDetailsStep.tsx +++ b/frontend/app/exchange/components/ExchangeDetailsStep.tsx @@ -5,6 +5,7 @@ import { SatsDisplay } from "../../components/SatsDisplay"; import { formatEur } from "../../utils/exchange"; import { buttonStyles } from "../../styles/shared"; import constants from "../../../../shared/constants.json"; +import { useTranslation } from "../../hooks/useTranslation"; const { lightningMaxEur: LIGHTNING_MAX_EUR } = constants.exchange; @@ -225,6 +226,7 @@ export function ExchangeDetailsStep({ hasPrice, onContinue, }: ExchangeDetailsStepProps) { + const t = useTranslation("exchange"); const isLightningDisabled = eurAmount > LIGHTNING_MAX_EUR * 100; const handleAmountChange = (value: number) => { @@ -263,7 +265,7 @@ export function ExchangeDetailsStep({ ...(direction === "buy" ? styles.directionBtnBuyActive : {}), }} > - Buy BTC + {t("direction.buyShort")}
{/* Payment Method Selector */}
- Payment Method * + {t("detailsStep.paymentMethod")}{" "} + {t("detailsStep.required")}
{isLightningDisabled && (
- Lightning payments are only available for amounts up to €{LIGHTNING_MAX_EUR} + {t("detailsStep.lightningThreshold", { max: LIGHTNING_MAX_EUR })}
)}
@@ -315,7 +318,7 @@ export function ExchangeDetailsStep({ {/* Amount Section */}
- Amount (EUR) + {t("detailsStep.amount")}
{direction === "buy" ? (

- You buy{" "} + {t("detailsStep.summaryBuy").split("{sats}")[0].trim()}{" "} - , you sell {formatEur(eurAmount)} + {", "} + {t("detailsStep.summaryBuy").split("{sats}")[1]?.split("{eur}")[0]?.trim()}{" "} + {formatEur(eurAmount)}

) : (

- You buy {formatEur(eurAmount)}, you sell{" "} + {t("detailsStep.summarySell").split("{sats}")[0]?.split("{eur}")[0]?.trim()}{" "} + {formatEur(eurAmount)} + {", "} + {t("detailsStep.summarySell").split("{sats}")[0]?.split("{eur}")[1]?.trim()}{" "} + {t("detailsStep.summarySell").split("{sats}")[1]?.trim()}

)}
@@ -370,7 +379,7 @@ export function ExchangeDetailsStep({ ...(isPriceStale || !hasPrice ? buttonStyles.buttonDisabled : {}), }} > - Continue to Booking + {t("detailsStep.continueToBooking")}
); diff --git a/frontend/app/exchange/components/PriceDisplay.tsx b/frontend/app/exchange/components/PriceDisplay.tsx index e9e03a5..8cdf5a8 100644 --- a/frontend/app/exchange/components/PriceDisplay.tsx +++ b/frontend/app/exchange/components/PriceDisplay.tsx @@ -2,6 +2,7 @@ import { CSSProperties } from "react"; import { components } from "../../generated/api"; +import { useTranslation } from "../../hooks/useTranslation"; type ExchangePriceResponse = components["schemas"]["ExchangePriceResponse"]; @@ -94,6 +95,7 @@ export function PriceDisplay({ direction, agreedPrice, }: PriceDisplayProps) { + const t = useTranslation("exchange"); const marketPrice = priceData?.price?.market_price ?? 0; const premiumPercent = priceData?.price?.premium_percentage ?? 5; const isPriceStale = priceData?.price?.is_stale ?? false; @@ -101,16 +103,16 @@ export function PriceDisplay({ return (
{isLoading && !priceData ? ( -
Loading price...
+
{t("priceDisplay.loading")}
) : error && !priceData?.price ? (
{error}
) : ( <>
- Market: + {t("priceDisplay.market")} {formatPrice(marketPrice)} - Our price: + {t("priceDisplay.ourPrice")} {formatPrice(agreedPrice)} {direction === "buy" ? "+" : "-"} @@ -119,8 +121,8 @@ export function PriceDisplay({
{lastUpdate && (
- Updated {lastUpdate.toLocaleTimeString()} - {isPriceStale && (stale)} + {t("priceDisplay.updated")} {lastUpdate.toLocaleTimeString()} + {isPriceStale && {t("priceDisplay.stale")}}
)} diff --git a/frontend/app/exchange/components/StepIndicator.tsx b/frontend/app/exchange/components/StepIndicator.tsx index d082990..5a4f58e 100644 --- a/frontend/app/exchange/components/StepIndicator.tsx +++ b/frontend/app/exchange/components/StepIndicator.tsx @@ -1,6 +1,7 @@ "use client"; import { CSSProperties } from "react"; +import { useTranslation } from "../../hooks/useTranslation"; type WizardStep = "details" | "booking" | "confirmation"; @@ -58,6 +59,7 @@ const styles: Record = { * Shows which step the user is currently on and which steps are completed. */ export function StepIndicator({ currentStep }: StepIndicatorProps) { + const t = useTranslation("exchange"); return (
1 - Exchange Details + {t("steps.details")}
2 - Book Appointment + {t("steps.booking")}
3 - Confirm + {t("steps.confirm")}
); diff --git a/frontend/app/exchange/page.tsx b/frontend/app/exchange/page.tsx index b8cac6a..00f8c72 100644 --- a/frontend/app/exchange/page.tsx +++ b/frontend/app/exchange/page.tsx @@ -19,6 +19,7 @@ import { StepIndicator } from "./components/StepIndicator"; import { ExchangeDetailsStep } from "./components/ExchangeDetailsStep"; import { BookingStep } from "./components/BookingStep"; import { ConfirmationStep } from "./components/ConfirmationStep"; +import { useTranslation } from "../hooks/useTranslation"; type ExchangeResponse = components["schemas"]["ExchangeResponse"]; @@ -57,6 +58,7 @@ const styles = { export default function ExchangePage() { const router = useRouter(); + const t = useTranslation("exchange"); const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.CREATE_EXCHANGE, fallbackRedirect: "/", @@ -281,8 +283,8 @@ export default function ExchangePage() {
-

Exchange Bitcoin

-

Buy or sell Bitcoin with a 5% premium

+

{t("page.title")}

+

{t("page.subtitle")}

{error && (
@@ -290,7 +292,7 @@ export default function ExchangePage() { {existingTradeId && ( )} diff --git a/frontend/app/invites/page.tsx b/frontend/app/invites/page.tsx index 11d8d17..e7d5bc6 100644 --- a/frontend/app/invites/page.tsx +++ b/frontend/app/invites/page.tsx @@ -11,11 +11,13 @@ import { components } from "../generated/api"; import constants from "../../../shared/constants.json"; import { Permission } from "../auth-context"; import { cardStyles, typographyStyles, buttonStyles } from "../styles/shared"; +import { useTranslation } from "../hooks/useTranslation"; // Use generated type from OpenAPI schema type Invite = components["schemas"]["UserInviteResponse"]; export default function InvitesPage() { + const t = useTranslation("invites"); const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.VIEW_OWN_INVITES, fallbackRedirect: "/admin/trades", @@ -62,26 +64,21 @@ export default function InvitesPage() { >
-

My Invites

-

- Share your invite codes with friends to let them join -

+

{t("page.title")}

+

{t("page.subtitle")}

{(invites?.length ?? 0) === 0 ? ( - + ) : (
{/* Ready Invites */} {readyInvites.length > 0 && (
-

Available ({readyInvites.length})

-

- Share these links with people you want to invite -

+

+ {t("page.available", { count: readyInvites.length })} +

+

{t("page.availableHint")}

{readyInvites.map((invite) => (
@@ -91,7 +88,7 @@ export default function InvitesPage() { onClick={() => copyToClipboard(invite)} style={buttonStyles.accentButton} > - {copiedId === invite.id ? "Copied!" : "Copy Link"} + {copiedId === invite.id ? t("page.copied") : t("page.copyLink")}
@@ -103,14 +100,20 @@ export default function InvitesPage() { {/* Spent Invites */} {spentInvites.length > 0 && (
-

Used ({spentInvites.length})

+

+ {t("page.used", { count: spentInvites.length })} +

{spentInvites.map((invite) => (
{invite.identifier}
- Used - by {invite.used_by_email} + {t("page.usedStatus")} + {invite.used_by_email && ( + + {t("page.usedBy", { email: invite.used_by_email })} + + )}
))} @@ -121,12 +124,14 @@ export default function InvitesPage() { {/* Revoked Invites */} {revokedInvites.length > 0 && (
-

Revoked ({revokedInvites.length})

+

+ {t("page.revoked", { count: revokedInvites.length })} +

{revokedInvites.map((invite) => (
{invite.identifier}
- Revoked + {t("page.revokedStatus")}
))}
diff --git a/frontend/app/profile/page.tsx b/frontend/app/profile/page.tsx index 5d8c1da..296fb00 100644 --- a/frontend/app/profile/page.tsx +++ b/frontend/app/profile/page.tsx @@ -19,6 +19,7 @@ import { utilityStyles, } from "../styles/shared"; import { validateProfileFields } from "../utils/validation"; +import { useTranslation } from "../hooks/useTranslation"; // Use generated type from OpenAPI schema type ProfileData = components["schemas"]["ProfileResponse"]; @@ -41,6 +42,7 @@ function toFormData(data: ProfileData): FormData { } export default function ProfilePage() { + const t = useTranslation("profile"); const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.MANAGE_OWN_PROFILE, fallbackRedirect: "/admin/trades", @@ -88,7 +90,7 @@ export default function ProfilePage() { setGodfatherEmail(data.godfather_email ?? null); } catch (err) { console.error("Profile load error:", err); - setToast({ message: "Failed to load profile", type: "error" }); + setToast({ message: t("messages.loadError"), type: "error" }); } finally { setIsLoadingProfile(false); } @@ -141,16 +143,16 @@ export default function ProfilePage() { const formValues = toFormData(data); setFormData(formValues); setOriginalData(formValues); - setToast({ message: "Profile saved successfully!", type: "success" }); + setToast({ message: t("messages.saveSuccess"), type: "success" }); } catch (err) { console.error("Profile save error:", err); const fieldErrors = extractFieldErrors(err); if (fieldErrors?.detail?.field_errors) { setErrors(fieldErrors.detail.field_errors); - setToast({ message: "Please fix the errors below", type: "error" }); + setToast({ message: t("messages.fixErrors"), type: "error" }); } else { setToast({ - message: extractApiErrorMessage(err, "Network error. Please try again."), + message: extractApiErrorMessage(err, t("messages.networkError")), type: "error", }); } @@ -181,16 +183,16 @@ export default function ProfilePage() {
-

My Profile

-

Manage your contact information

+

{t("page.title")}

+

{t("page.subtitle")}

{/* Login email - read only */}
- - This is your login email and cannot be changed here. - + {t("form.emailHint")}
{/* Godfather - shown if user was invited */} {godfatherEmail && (
{godfatherEmail}
- The user who invited you to join. + {t("form.invitedByHint")}
)}
-

Contact Details

-

- These are for communication purposes only — they won't affect your login. -

+

{t("form.contactDetails")}

+

{t("form.contactDetailsHint")}

{/* Contact email */}
{errors.contact_email && ( {errors.contact_email} @@ -248,7 +246,7 @@ export default function ProfilePage() { {/* Telegram */}
{errors.telegram && {errors.telegram}}
@@ -267,7 +265,7 @@ export default function ProfilePage() { {/* Signal */}
{errors.signal && {errors.signal}}
@@ -286,7 +284,7 @@ export default function ProfilePage() { {/* Nostr npub */}
{errors.nostr_npub && {errors.nostr_npub}}
@@ -311,7 +309,7 @@ export default function ProfilePage() { }} disabled={!canSubmit} > - {isSubmitting ? "Saving..." : "Save Changes"} + {isSubmitting ? t("form.saving") : t("form.saveChanges")}
diff --git a/frontend/app/trades/page.tsx b/frontend/app/trades/page.tsx index cda4720..376c960 100644 --- a/frontend/app/trades/page.tsx +++ b/frontend/app/trades/page.tsx @@ -15,9 +15,12 @@ import { useMutation } from "../hooks/useMutation"; import { formatDateTime } from "../utils/date"; import { formatEur } from "../utils/exchange"; import { typographyStyles, tradeCardStyles } from "../styles/shared"; +import { useTranslation } from "../hooks/useTranslation"; export default function TradesPage() { const router = useRouter(); + const t = useTranslation("trades"); + const tExchange = useTranslation("exchange"); const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.VIEW_OWN_EXCHANGES, fallbackRedirect: "/", @@ -70,17 +73,17 @@ export default function TradesPage() { error={error} contentStyle={styles.content} > -

My Trades

-

View and manage your Bitcoin trades

+

{t("page.title")}

+

{t("page.subtitle")}

{isLoadingTrades ? ( - + ) : (trades?.length ?? 0) === 0 ? ( - Start trading + {t("page.startTrading")} } /> @@ -89,7 +92,9 @@ export default function TradesPage() { {/* Upcoming Trades */} {upcomingTrades.length > 0 && (
-

Upcoming ({upcomingTrades.length})

+

+ {t("page.upcoming", { count: upcomingTrades.length })} +

{upcomingTrades.map((trade) => { const isBuy = trade.direction === "buy"; @@ -110,7 +115,7 @@ export default function TradesPage() { color: isBuy ? "#4ade80" : "#f87171", }} > - {isBuy ? "BUY BTC" : "SELL BTC"} + {isBuy ? tExchange("direction.buy") : tExchange("direction.sell")} {isBuy - ? `Receive via ${trade.bitcoin_transfer_method === "onchain" ? "Onchain" : "Lightning"}` - : `Send via ${trade.bitcoin_transfer_method === "onchain" ? "Onchain" : "Lightning"}`} + ? `${tExchange("bookingStep.receiveVia")} ${trade.bitcoin_transfer_method === "onchain" ? tExchange("transferMethod.onchain") : tExchange("transferMethod.lightning")}` + : `${tExchange("bookingStep.sendVia")} ${trade.bitcoin_transfer_method === "onchain" ? tExchange("transferMethod.onchain") : tExchange("transferMethod.lightning")}`} {formatEur(trade.eur_amount)} @@ -132,7 +137,7 @@ export default function TradesPage() {
- Rate: + {t("trade.rate")} € {trade.agreed_price_eur.toLocaleString("de-DE", { @@ -153,7 +158,7 @@ export default function TradesPage() { onConfirm={() => handleCancel(trade.public_id)} onCancel={() => setConfirmCancelId(null)} onActionClick={() => setConfirmCancelId(trade.public_id)} - actionLabel="Cancel" + actionLabel={t("trade.cancel")} isLoading={cancellingId === trade.public_id} confirmVariant="danger" confirmButtonStyle={styles.confirmButton} @@ -166,7 +171,7 @@ export default function TradesPage() { }} style={styles.viewDetailsButton} > - View Details + {t("trade.viewDetails")}
@@ -181,7 +186,7 @@ export default function TradesPage() { {pastOrFinalTrades.length > 0 && (

- History ({pastOrFinalTrades.length}) + {t("page.history", { count: pastOrFinalTrades.length })}

{pastOrFinalTrades.map((trade) => { diff --git a/frontend/locales/ca/exchange.json b/frontend/locales/ca/exchange.json index 2362763..0fa6c43 100644 --- a/frontend/locales/ca/exchange.json +++ b/frontend/locales/ca/exchange.json @@ -8,10 +8,70 @@ }, "direction": { "buy": "COMPRAR BTC", - "sell": "VENDRE BTC" + "sell": "VENDRE BTC", + "buyShort": "Comprar BTC", + "sellShort": "Vendre BTC" }, "transferMethod": { "onchain": "Onchain", "lightning": "Lightning" + }, + "page": { + "title": "Intercanviar Bitcoin", + "subtitle": "Compra o ven Bitcoin amb una prima del 5%", + "viewExistingTrade": "Veure la teva operació existent →" + }, + "steps": { + "details": "Detalls de l'Intercanvi", + "booking": "Reservar Cita", + "confirm": "Confirmar" + }, + "detailsStep": { + "paymentMethod": "Mètode de Pagament", + "required": "*", + "lightningThreshold": "Els pagaments Lightning només estan disponibles per importants fins a €{max}", + "amount": "Quantitat (EUR)", + "summaryBuy": "Compres {sats}, vens {eur}", + "summarySell": "Compres {eur}, vens {sats}", + "continueToBooking": "Continuar a Reserva" + }, + "bookingStep": { + "yourExchange": "El Teu Intercanvi", + "edit": "Editar", + "buy": "Comprar", + "sell": "Vendre", + "receiveVia": "Rebre via", + "sendVia": "Enviar via", + "selectDate": "Seleccionar una Data", + "existingTradeWarning": "Ja tens una operació reservada en aquest dia. Només pots reservar una operació per dia.", + "viewExistingTrade": "Veure la teva operació existent →", + "availableSlots": "Espais Disponibles per a", + "loadingSlots": "Carregant espais...", + "noSlots": "No hi ha espais disponibles per a aquesta data" + }, + "confirmationStep": { + "appointment": "Cita", + "edit": "Editar", + "confirmTrade": "Confirmar Operació", + "time": "Hora:", + "direction": "Direcció:", + "eur": "EUR:", + "btc": "BTC:", + "rate": "Taxa:", + "payment": "Pagament:", + "receiveVia": "Rebre via", + "sendVia": "Enviar via", + "booking": "Reservant...", + "priceStale": "Preu Desactualitzat", + "confirmBuy": "Confirmar Compra", + "confirmSell": "Confirmar Venda", + "back": "Enrere" + }, + "priceDisplay": { + "loading": "Carregant preu...", + "market": "Mercat:", + "ourPrice": "El nostre preu:", + "updated": "Actualitzat", + "stale": "(desactualitzat)" } } diff --git a/frontend/locales/ca/invites.json b/frontend/locales/ca/invites.json new file mode 100644 index 0000000..dea790b --- /dev/null +++ b/frontend/locales/ca/invites.json @@ -0,0 +1,17 @@ +{ + "page": { + "title": "Les Meves Invitacions", + "subtitle": "Comparteix els teus codis d'invitació amb amics perquè s'uneixin", + "noInvites": "Encara no tens invitacions.", + "noInvitesHint": "Contacta amb un administrador si necessites codis d'invitació per compartir.", + "available": "Disponibles ({count})", + "availableHint": "Comparteix aquests enllaços amb les persones que vulguis convidar", + "used": "Usades ({count})", + "revoked": "Revocades ({count})", + "copyLink": "Copiar Enllaç", + "copied": "Copiat!", + "usedBy": "per {email}", + "usedStatus": "Usada", + "revokedStatus": "Revocada" + } +} diff --git a/frontend/locales/ca/profile.json b/frontend/locales/ca/profile.json new file mode 100644 index 0000000..30edb9c --- /dev/null +++ b/frontend/locales/ca/profile.json @@ -0,0 +1,33 @@ +{ + "page": { + "title": "El Meu Perfil", + "subtitle": "Gestiona la teva informació de contacte" + }, + "form": { + "email": "Correu Electrònic", + "emailHint": "Aquest és el teu correu d'inici de sessió i no es pot canviar aquí.", + "invitedBy": "Convidat Per", + "invitedByHint": "L'usuari que et va convidar a unir-te.", + "readOnly": "Només lectura", + "contactDetails": "Detalls de Contacte", + "contactDetailsHint": "Aquests són només per a fins de comunicació — no afectaran el teu inici de sessió.", + "contactEmail": "Correu de Contacte", + "telegram": "Telegram", + "signal": "Signal", + "nostrNpub": "Nostr (npub)", + "saving": "Desant...", + "saveChanges": "Desar Canvis" + }, + "messages": { + "loadError": "Error en carregar el perfil", + "saveSuccess": "Perfil desat amb èxit!", + "fixErrors": "Si us plau, corregeix els errors a continuació", + "networkError": "Error de xarxa. Si us plau, intenta-ho de nou." + }, + "placeholders": { + "contactEmail": "alternate@example.com", + "telegram": "@username", + "signal": "username.01", + "nostrNpub": "npub1..." + } +} diff --git a/frontend/locales/ca/trades.json b/frontend/locales/ca/trades.json new file mode 100644 index 0000000..547f9af --- /dev/null +++ b/frontend/locales/ca/trades.json @@ -0,0 +1,16 @@ +{ + "page": { + "title": "Les Meves Operacions", + "subtitle": "Veure i gestionar les teves operacions de Bitcoin", + "loadingTrades": "Carregant operacions...", + "noTrades": "Encara no tens operacions.", + "startTrading": "Començar a operar", + "upcoming": "Properes ({count})", + "history": "Historial ({count})" + }, + "trade": { + "rate": "Taxa:", + "cancel": "Cancel·lar", + "viewDetails": "Veure Detalls" + } +} diff --git a/frontend/locales/en/exchange.json b/frontend/locales/en/exchange.json index 13297c6..7e1d4ee 100644 --- a/frontend/locales/en/exchange.json +++ b/frontend/locales/en/exchange.json @@ -8,10 +8,70 @@ }, "direction": { "buy": "BUY BTC", - "sell": "SELL BTC" + "sell": "SELL BTC", + "buyShort": "Buy BTC", + "sellShort": "Sell BTC" }, "transferMethod": { "onchain": "Onchain", "lightning": "Lightning" + }, + "page": { + "title": "Exchange Bitcoin", + "subtitle": "Buy or sell Bitcoin with a 5% premium", + "viewExistingTrade": "View your existing trade →" + }, + "steps": { + "details": "Exchange Details", + "booking": "Book Appointment", + "confirm": "Confirm" + }, + "detailsStep": { + "paymentMethod": "Payment Method", + "required": "*", + "lightningThreshold": "Lightning payments are only available for amounts up to €{max}", + "amount": "Amount (EUR)", + "summaryBuy": "You buy {sats}, you sell {eur}", + "summarySell": "You buy {eur}, you sell {sats}", + "continueToBooking": "Continue to Booking" + }, + "bookingStep": { + "yourExchange": "Your Exchange", + "edit": "Edit", + "buy": "Buy", + "sell": "Sell", + "receiveVia": "Receive via", + "sendVia": "Send via", + "selectDate": "Select a Date", + "existingTradeWarning": "You already have a trade booked on this day. You can only book one trade per day.", + "viewExistingTrade": "View your existing trade →", + "availableSlots": "Available Slots for", + "loadingSlots": "Loading slots...", + "noSlots": "No available slots for this date" + }, + "confirmationStep": { + "appointment": "Appointment", + "edit": "Edit", + "confirmTrade": "Confirm Trade", + "time": "Time:", + "direction": "Direction:", + "eur": "EUR:", + "btc": "BTC:", + "rate": "Rate:", + "payment": "Payment:", + "receiveVia": "Receive via", + "sendVia": "Send via", + "booking": "Booking...", + "priceStale": "Price Stale", + "confirmBuy": "Confirm Buy", + "confirmSell": "Confirm Sell", + "back": "Back" + }, + "priceDisplay": { + "loading": "Loading price...", + "market": "Market:", + "ourPrice": "Our price:", + "updated": "Updated", + "stale": "(stale)" } } diff --git a/frontend/locales/en/invites.json b/frontend/locales/en/invites.json new file mode 100644 index 0000000..0961e8e --- /dev/null +++ b/frontend/locales/en/invites.json @@ -0,0 +1,17 @@ +{ + "page": { + "title": "My Invites", + "subtitle": "Share your invite codes with friends to let them join", + "noInvites": "You don't have any invites yet.", + "noInvitesHint": "Contact an admin if you need invite codes to share.", + "available": "Available ({count})", + "availableHint": "Share these links with people you want to invite", + "used": "Used ({count})", + "revoked": "Revoked ({count})", + "copyLink": "Copy Link", + "copied": "Copied!", + "usedBy": "by {email}", + "usedStatus": "Used", + "revokedStatus": "Revoked" + } +} diff --git a/frontend/locales/en/profile.json b/frontend/locales/en/profile.json new file mode 100644 index 0000000..4e0cf03 --- /dev/null +++ b/frontend/locales/en/profile.json @@ -0,0 +1,33 @@ +{ + "page": { + "title": "My Profile", + "subtitle": "Manage your contact information" + }, + "form": { + "email": "Email", + "emailHint": "This is your login email and cannot be changed here.", + "invitedBy": "Invited By", + "invitedByHint": "The user who invited you to join.", + "readOnly": "Read only", + "contactDetails": "Contact Details", + "contactDetailsHint": "These are for communication purposes only — they won't affect your login.", + "contactEmail": "Contact Email", + "telegram": "Telegram", + "signal": "Signal", + "nostrNpub": "Nostr (npub)", + "saving": "Saving...", + "saveChanges": "Save Changes" + }, + "messages": { + "loadError": "Failed to load profile", + "saveSuccess": "Profile saved successfully!", + "fixErrors": "Please fix the errors below", + "networkError": "Network error. Please try again." + }, + "placeholders": { + "contactEmail": "alternate@example.com", + "telegram": "@username", + "signal": "username.01", + "nostrNpub": "npub1..." + } +} diff --git a/frontend/locales/en/trades.json b/frontend/locales/en/trades.json new file mode 100644 index 0000000..a4917a7 --- /dev/null +++ b/frontend/locales/en/trades.json @@ -0,0 +1,16 @@ +{ + "page": { + "title": "My Trades", + "subtitle": "View and manage your Bitcoin trades", + "loadingTrades": "Loading trades...", + "noTrades": "You don't have any trades yet.", + "startTrading": "Start trading", + "upcoming": "Upcoming ({count})", + "history": "History ({count})" + }, + "trade": { + "rate": "Rate:", + "cancel": "Cancel", + "viewDetails": "View Details" + } +} diff --git a/frontend/locales/es/exchange.json b/frontend/locales/es/exchange.json index f755651..78a8a6d 100644 --- a/frontend/locales/es/exchange.json +++ b/frontend/locales/es/exchange.json @@ -8,10 +8,70 @@ }, "direction": { "buy": "COMPRAR BTC", - "sell": "VENDER BTC" + "sell": "VENDER BTC", + "buyShort": "Comprar BTC", + "sellShort": "Vender BTC" }, "transferMethod": { "onchain": "Onchain", "lightning": "Lightning" + }, + "page": { + "title": "Intercambiar Bitcoin", + "subtitle": "Compra o vende Bitcoin con una prima del 5%", + "viewExistingTrade": "Ver tu operación existente →" + }, + "steps": { + "details": "Detalles del Intercambio", + "booking": "Reservar Cita", + "confirm": "Confirmar" + }, + "detailsStep": { + "paymentMethod": "Método de Pago", + "required": "*", + "lightningThreshold": "Los pagos Lightning solo están disponibles para montos de hasta €{max}", + "amount": "Cantidad (EUR)", + "summaryBuy": "Compras {sats}, vendes {eur}", + "summarySell": "Compras {eur}, vendes {sats}", + "continueToBooking": "Continuar a Reserva" + }, + "bookingStep": { + "yourExchange": "Tu Intercambio", + "edit": "Editar", + "buy": "Comprar", + "sell": "Vender", + "receiveVia": "Recibir vía", + "sendVia": "Enviar vía", + "selectDate": "Seleccionar una Fecha", + "existingTradeWarning": "Ya tienes una operación reservada en este día. Solo puedes reservar una operación por día.", + "viewExistingTrade": "Ver tu operación existente →", + "availableSlots": "Espacios Disponibles para", + "loadingSlots": "Cargando espacios...", + "noSlots": "No hay espacios disponibles para esta fecha" + }, + "confirmationStep": { + "appointment": "Cita", + "edit": "Editar", + "confirmTrade": "Confirmar Operación", + "time": "Hora:", + "direction": "Dirección:", + "eur": "EUR:", + "btc": "BTC:", + "rate": "Tasa:", + "payment": "Pago:", + "receiveVia": "Recibir vía", + "sendVia": "Enviar vía", + "booking": "Reservando...", + "priceStale": "Precio Desactualizado", + "confirmBuy": "Confirmar Compra", + "confirmSell": "Confirmar Venta", + "back": "Atrás" + }, + "priceDisplay": { + "loading": "Cargando precio...", + "market": "Mercado:", + "ourPrice": "Nuestro precio:", + "updated": "Actualizado", + "stale": "(desactualizado)" } } diff --git a/frontend/locales/es/invites.json b/frontend/locales/es/invites.json new file mode 100644 index 0000000..e8e3ca7 --- /dev/null +++ b/frontend/locales/es/invites.json @@ -0,0 +1,17 @@ +{ + "page": { + "title": "Mis Invitaciones", + "subtitle": "Comparte tus códigos de invitación con amigos para que se unan", + "noInvites": "Aún no tienes invitaciones.", + "noInvitesHint": "Contacta con un administrador si necesitas códigos de invitación para compartir.", + "available": "Disponibles ({count})", + "availableHint": "Comparte estos enlaces con las personas que quieras invitar", + "used": "Usadas ({count})", + "revoked": "Revocadas ({count})", + "copyLink": "Copiar Enlace", + "copied": "¡Copiado!", + "usedBy": "por {email}", + "usedStatus": "Usada", + "revokedStatus": "Revocada" + } +} diff --git a/frontend/locales/es/profile.json b/frontend/locales/es/profile.json new file mode 100644 index 0000000..dd99fe3 --- /dev/null +++ b/frontend/locales/es/profile.json @@ -0,0 +1,33 @@ +{ + "page": { + "title": "Mi Perfil", + "subtitle": "Gestiona tu información de contacto" + }, + "form": { + "email": "Correo Electrónico", + "emailHint": "Este es tu correo de inicio de sesión y no se puede cambiar aquí.", + "invitedBy": "Invitado Por", + "invitedByHint": "El usuario que te invitó a unirte.", + "readOnly": "Solo lectura", + "contactDetails": "Detalles de Contacto", + "contactDetailsHint": "Estos son solo para fines de comunicación — no afectarán tu inicio de sesión.", + "contactEmail": "Correo de Contacto", + "telegram": "Telegram", + "signal": "Signal", + "nostrNpub": "Nostr (npub)", + "saving": "Guardando...", + "saveChanges": "Guardar Cambios" + }, + "messages": { + "loadError": "Error al cargar el perfil", + "saveSuccess": "¡Perfil guardado exitosamente!", + "fixErrors": "Por favor corrige los errores a continuación", + "networkError": "Error de red. Por favor intenta de nuevo." + }, + "placeholders": { + "contactEmail": "alternate@example.com", + "telegram": "@username", + "signal": "username.01", + "nostrNpub": "npub1..." + } +} diff --git a/frontend/locales/es/trades.json b/frontend/locales/es/trades.json new file mode 100644 index 0000000..40c22c4 --- /dev/null +++ b/frontend/locales/es/trades.json @@ -0,0 +1,16 @@ +{ + "page": { + "title": "Mis Operaciones", + "subtitle": "Ver y gestionar tus operaciones de Bitcoin", + "loadingTrades": "Cargando operaciones...", + "noTrades": "Aún no tienes operaciones.", + "startTrading": "Empezar a operar", + "upcoming": "Próximas ({count})", + "history": "Historial ({count})" + }, + "trade": { + "rate": "Tasa:", + "cancel": "Cancelar", + "viewDetails": "Ver Detalles" + } +}