diff --git a/frontend/app/components/LanguageSelector.tsx b/frontend/app/components/LanguageSelector.tsx index d8d420e..836e211 100644 --- a/frontend/app/components/LanguageSelector.tsx +++ b/frontend/app/components/LanguageSelector.tsx @@ -4,10 +4,10 @@ import { useState, useRef, useEffect } from "react"; import { useLanguage, type Locale } from "../hooks/useLanguage"; import { sharedStyles } from "../styles/shared"; -const LANGUAGES: Array<{ code: Locale; name: string; flag: string }> = [ - { code: "es", name: "Español", flag: "🇪🇸" }, - { code: "en", name: "English", flag: "🇬🇧" }, - { code: "ca", name: "Català", flag: "🇪🇸" }, +const LANGUAGES: Array<{ code: Locale; name: string }> = [ + { code: "es", name: "Español" }, + { code: "en", name: "English" }, + { code: "ca", name: "Català" }, ]; export function LanguageSelector() { @@ -45,7 +45,6 @@ export function LanguageSelector() { gap: "0.5rem", }} > - {currentLanguage?.flag} {currentLanguage?.name} @@ -97,7 +96,6 @@ export function LanguageSelector() { } }} > - {lang.flag} {lang.name} ))} diff --git a/frontend/app/exchange/components/BookingStep.tsx b/frontend/app/exchange/components/BookingStep.tsx index ccba969..f50287f 100644 --- a/frontend/app/exchange/components/BookingStep.tsx +++ b/frontend/app/exchange/components/BookingStep.tsx @@ -7,6 +7,7 @@ import { formatDate, formatTime } from "../../utils/date"; import { formatEur } from "../../utils/exchange"; import { bannerStyles } from "../../styles/shared"; import { useTranslation } from "../../hooks/useTranslation"; +import { useLanguage } from "../../hooks/useLanguage"; type BookableSlot = components["schemas"]["BookableSlot"]; type ExchangeResponse = components["schemas"]["ExchangeResponse"]; @@ -217,6 +218,10 @@ export function BookingStep({ onBackToDetails, }: BookingStepProps) { const t = useTranslation("exchange"); + const { locale } = useLanguage(); + + // Map locale codes to Intl locale strings + const intlLocale = locale === "es" ? "es-ES" : locale === "ca" ? "ca-ES" : "en-US"; return ( <> {/* Trade Summary Card */} @@ -277,10 +282,10 @@ export function BookingStep({ }} >
- {date.toLocaleDateString("es-ES", { weekday: "short" })} + {date.toLocaleDateString(intlLocale, { weekday: "short" })}
- {date.toLocaleDateString("es-ES", { + {date.toLocaleDateString(intlLocale, { month: "short", day: "numeric", })} @@ -312,7 +317,7 @@ export function BookingStep({

{t("bookingStep.availableSlots")}{" "} - {selectedDate.toLocaleDateString("es-ES", { + {selectedDate.toLocaleDateString(intlLocale, { weekday: "long", month: "long", day: "numeric", diff --git a/frontend/app/exchange/components/ConfirmationStep.tsx b/frontend/app/exchange/components/ConfirmationStep.tsx index def0940..9d613a4 100644 --- a/frontend/app/exchange/components/ConfirmationStep.tsx +++ b/frontend/app/exchange/components/ConfirmationStep.tsx @@ -7,6 +7,7 @@ import { formatTime } from "../../utils/date"; import { formatEur } from "../../utils/exchange"; import { buttonStyles } from "../../styles/shared"; import { useTranslation } from "../../hooks/useTranslation"; +import { useLanguage } from "../../hooks/useLanguage"; type BookableSlot = components["schemas"]["BookableSlot"]; type Direction = "buy" | "sell"; @@ -156,6 +157,10 @@ export function ConfirmationStep({ onBack, }: ConfirmationStepProps) { const t = useTranslation("exchange"); + const { locale } = useLanguage(); + + // Map locale codes to Intl locale strings + const intlLocale = locale === "es" ? "es-ES" : locale === "ca" ? "ca-ES" : "en-US"; return ( <> {/* Compressed Booking Summary */} @@ -168,7 +173,7 @@ export function ConfirmationStep({

- {selectedDate?.toLocaleDateString("es-ES", { + {selectedDate?.toLocaleDateString(intlLocale, { weekday: "short", month: "short", day: "numeric", diff --git a/frontend/app/trades/[id]/page.tsx b/frontend/app/trades/[id]/page.tsx index 5086169..ea621dd 100644 --- a/frontend/app/trades/[id]/page.tsx +++ b/frontend/app/trades/[id]/page.tsx @@ -18,12 +18,20 @@ import { buttonStyles, tradeCardStyles, } from "../../styles/shared"; +import { useTranslation } from "../../hooks/useTranslation"; +import { useLanguage } from "../../hooks/useLanguage"; export default function TradeDetailPage() { const router = useRouter(); const params = useParams(); const publicId = params?.id as string | undefined; const [cancelError, setCancelError] = useState(null); + const t = useTranslation("trades"); + const tExchange = useTranslation("exchange"); + const { locale } = useLanguage(); + + // Map locale codes to Intl locale strings + const intlLocale = locale === "es" ? "es-ES" : locale === "ca" ? "ca-ES" : "en-US"; const { user, isLoading, isAuthorized } = useRequireAuth({ requiredPermission: Permission.VIEW_OWN_EXCHANGES, @@ -50,7 +58,7 @@ export default function TradeDetailPage() { if (isLoading || isLoadingTrade) { return (
-
Loading...
+
{t("details.loading")}
); } @@ -64,15 +72,10 @@ export default function TradeDetailPage() {
-

Trade Details

- {error && ( -
- {error || - "Failed to load trade. It may not exist or you may not have permission to view it."} -
- )} +

{t("details.title")}

+ {error &&
{error || t("details.error")}
}
@@ -90,26 +93,28 @@ export default function TradeDetailPage() {
-

Trade Details

+

{t("details.title")}

-

Trade Information

+

{t("details.tradeInformation")}

- Status: + {t("details.status")} {""}
- Time: - {formatDateTime(trade.slot_start)} + {t("details.time")} + + {formatDateTime(trade.slot_start, intlLocale)} +
- Direction: + {t("details.direction")} - {isBuy ? "BUY BTC" : "SELL BTC"} + {isBuy ? t("details.buyBtc") : t("details.sellBtc")}
- Payment Method: + {t("details.paymentMethod")} {isBuy - ? `Receive via ${trade.bitcoin_transfer_method === "onchain" ? "Onchain" : "Lightning"}` - : `Send via ${trade.bitcoin_transfer_method === "onchain" ? "Onchain" : "Lightning"}`} + ? `${t("details.receiveVia")} ${trade.bitcoin_transfer_method === "onchain" ? tExchange("transferMethod.onchain") : tExchange("transferMethod.lightning")}` + : `${t("details.sendVia")} ${trade.bitcoin_transfer_method === "onchain" ? tExchange("transferMethod.onchain") : tExchange("transferMethod.lightning")}`}
-

Amounts

+

{t("details.amounts")}

- EUR Amount: + {t("details.eurAmount")} {formatEur(trade.eur_amount)}
- Bitcoin Amount: + {t("details.bitcoinAmount")} @@ -148,52 +153,58 @@ export default function TradeDetailPage() {
-

Pricing

+

{t("details.pricing")}

- Market Price: + {t("details.marketPrice")} € - {trade.market_price_eur.toLocaleString("es-ES", { + {trade.market_price_eur.toLocaleString(intlLocale, { maximumFractionDigits: 0, })} /BTC
- Agreed Price: + {t("details.agreedPrice")} € - {trade.agreed_price_eur.toLocaleString("es-ES", { + {trade.agreed_price_eur.toLocaleString(intlLocale, { maximumFractionDigits: 0, })} /BTC
- Premium: + {t("details.premium")} {trade.premium_percentage}%
-

Timestamps

+

{t("details.timestamps")}

- Created: - {formatDateTime(trade.created_at)} + {t("details.created")} + + {formatDateTime(trade.created_at, intlLocale)} +
{trade.cancelled_at && (
- Cancelled: - {formatDateTime(trade.cancelled_at)} + {t("details.cancelled")} + + {formatDateTime(trade.cancelled_at, intlLocale)} +
)} {trade.completed_at && (
- Completed: - {formatDateTime(trade.completed_at)} + {t("details.completed")} + + {formatDateTime(trade.completed_at, intlLocale)} +
)}
@@ -204,23 +215,19 @@ export default function TradeDetailPage() {
)} diff --git a/frontend/app/utils/date.ts b/frontend/app/utils/date.ts index 1a821fc..0fb15ac 100644 --- a/frontend/app/utils/date.ts +++ b/frontend/app/utils/date.ts @@ -27,9 +27,9 @@ export function formatTime(isoString: string): string { /** * Format datetime from ISO string to a readable format in local timezone. */ -export function formatDateTime(isoString: string): string { +export function formatDateTime(isoString: string, locale: string = "es-ES"): string { const d = new Date(isoString); - return d.toLocaleString("es-ES", { + return d.toLocaleString(locale, { weekday: "short", month: "short", day: "numeric", diff --git a/frontend/locales/ca/trades.json b/frontend/locales/ca/trades.json index 547f9af..8b05e64 100644 --- a/frontend/locales/ca/trades.json +++ b/frontend/locales/ca/trades.json @@ -12,5 +12,34 @@ "rate": "Taxa:", "cancel": "Cancel·lar", "viewDetails": "Veure Detalls" + }, + "details": { + "title": "Detalls de l'Operació", + "loading": "Carregant...", + "backToTrades": "← Tornar a Operacions", + "backToTradesShort": "Tornar a Operacions", + "error": "Error en carregar l'operació. Pot ser que no existeixi o no tenguis permís per veure-la.", + "tradeInformation": "Informació de l'Operació", + "amounts": "Quantitats", + "pricing": "Preus", + "timestamps": "Dates", + "status": "Estat:", + "time": "Hora:", + "direction": "Direcció:", + "paymentMethod": "Mètode de Pagament:", + "eurAmount": "Quantitat EUR:", + "bitcoinAmount": "Quantitat Bitcoin:", + "marketPrice": "Preu de Mercat:", + "agreedPrice": "Preu Acordat:", + "premium": "Prima:", + "created": "Creada:", + "cancelled": "Cancel·lada:", + "completed": "Completada:", + "buyBtc": "COMPRAR BTC", + "sellBtc": "VENDRE BTC", + "receiveVia": "Rebre via", + "sendVia": "Enviar via", + "cancelTrade": "Cancel·lar Operació", + "cancelConfirm": "Estàs segur que vols cancel·lar aquesta operació? Aquesta acció no es pot desfer." } } diff --git a/frontend/locales/en/trades.json b/frontend/locales/en/trades.json index a4917a7..dba3893 100644 --- a/frontend/locales/en/trades.json +++ b/frontend/locales/en/trades.json @@ -12,5 +12,34 @@ "rate": "Rate:", "cancel": "Cancel", "viewDetails": "View Details" + }, + "details": { + "title": "Trade Details", + "loading": "Loading...", + "backToTrades": "← Back to Trades", + "backToTradesShort": "Back to Trades", + "error": "Failed to load trade. It may not exist or you may not have permission to view it.", + "tradeInformation": "Trade Information", + "amounts": "Amounts", + "pricing": "Pricing", + "timestamps": "Timestamps", + "status": "Status:", + "time": "Time:", + "direction": "Direction:", + "paymentMethod": "Payment Method:", + "eurAmount": "EUR Amount:", + "bitcoinAmount": "Bitcoin Amount:", + "marketPrice": "Market Price:", + "agreedPrice": "Agreed Price:", + "premium": "Premium:", + "created": "Created:", + "cancelled": "Cancelled:", + "completed": "Completed:", + "buyBtc": "BUY BTC", + "sellBtc": "SELL BTC", + "receiveVia": "Receive via", + "sendVia": "Send via", + "cancelTrade": "Cancel Trade", + "cancelConfirm": "Are you sure you want to cancel this trade? This action cannot be undone." } } diff --git a/frontend/locales/es/trades.json b/frontend/locales/es/trades.json index 40c22c4..30998ee 100644 --- a/frontend/locales/es/trades.json +++ b/frontend/locales/es/trades.json @@ -12,5 +12,34 @@ "rate": "Tasa:", "cancel": "Cancelar", "viewDetails": "Ver Detalles" + }, + "details": { + "title": "Detalles de la Operación", + "loading": "Cargando...", + "backToTrades": "← Volver a Operaciones", + "backToTradesShort": "Volver a Operaciones", + "error": "Error al cargar la operación. Puede que no exista o no tengas permiso para verla.", + "tradeInformation": "Información de la Operación", + "amounts": "Cantidades", + "pricing": "Precios", + "timestamps": "Fechas", + "status": "Estado:", + "time": "Hora:", + "direction": "Dirección:", + "paymentMethod": "Método de Pago:", + "eurAmount": "Cantidad EUR:", + "bitcoinAmount": "Cantidad Bitcoin:", + "marketPrice": "Precio de Mercado:", + "agreedPrice": "Precio Acordado:", + "premium": "Prima:", + "created": "Creada:", + "cancelled": "Cancelada:", + "completed": "Completada:", + "buyBtc": "COMPRAR BTC", + "sellBtc": "VENDER BTC", + "receiveVia": "Recibir vía", + "sendVia": "Enviar vía", + "cancelTrade": "Cancelar Operación", + "cancelConfirm": "¿Estás seguro de que quieres cancelar esta operación? Esta acción no se puede deshacer." } }