Phase 6: Translate User Pages - exchange, trades, invites, profile

- Expand exchange.json with all exchange page strings (page, steps, detailsStep, bookingStep, confirmationStep, priceDisplay)
- Create trades.json translation files for es, en, ca
- Create invites.json translation files for es, en, ca
- Create profile.json translation files for es, en, ca
- Translate exchange page and all components (ExchangeDetailsStep, BookingStep, ConfirmationStep, StepIndicator, PriceDisplay)
- Translate trades page (titles, sections, buttons, status labels)
- Translate invites page (titles, sections, status badges, copy button)
- Translate profile page (form labels, hints, placeholders, messages)
- Update IntlProvider to load all new namespaces
- All frontend tests passing
This commit is contained in:
counterweight 2025-12-25 22:19:13 +01:00
parent 7dd13292a0
commit 246553c402
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
22 changed files with 559 additions and 115 deletions

View file

@ -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}
>
<h1 style={typographyStyles.pageTitle}>My Trades</h1>
<p style={typographyStyles.pageSubtitle}>View and manage your Bitcoin trades</p>
<h1 style={typographyStyles.pageTitle}>{t("page.title")}</h1>
<p style={typographyStyles.pageSubtitle}>{t("page.subtitle")}</p>
{isLoadingTrades ? (
<EmptyState message="Loading trades..." isLoading={true} />
<EmptyState message={t("page.loadingTrades")} isLoading={true} />
) : (trades?.length ?? 0) === 0 ? (
<EmptyState
message="You don't have any trades yet."
message={t("page.noTrades")}
action={
<a href="/exchange" style={styles.emptyStateLink}>
Start trading
{t("page.startTrading")}
</a>
}
/>
@ -89,7 +92,9 @@ export default function TradesPage() {
{/* Upcoming Trades */}
{upcomingTrades.length > 0 && (
<div style={styles.section}>
<h2 style={styles.sectionTitle}>Upcoming ({upcomingTrades.length})</h2>
<h2 style={styles.sectionTitle}>
{t("page.upcoming", { count: upcomingTrades.length })}
</h2>
<div style={tradeCardStyles.tradeList}>
{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")}
</span>
<span
style={{
@ -120,8 +125,8 @@ export default function TradesPage() {
}}
>
{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")}`}
</span>
<span style={tradeCardStyles.amount}>
{formatEur(trade.eur_amount)}
@ -132,7 +137,7 @@ export default function TradesPage() {
</span>
</div>
<div style={tradeCardStyles.rateRow}>
<span style={tradeCardStyles.rateLabel}>Rate:</span>
<span style={tradeCardStyles.rateLabel}>{t("trade.rate")}</span>
<span style={tradeCardStyles.rateValue}>
{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")}
</button>
</div>
</div>
@ -181,7 +186,7 @@ export default function TradesPage() {
{pastOrFinalTrades.length > 0 && (
<div style={styles.section}>
<h2 style={typographyStyles.sectionTitleMuted}>
History ({pastOrFinalTrades.length})
{t("page.history", { count: pastOrFinalTrades.length })}
</h2>
<div style={tradeCardStyles.tradeList}>
{pastOrFinalTrades.map((trade) => {