i18n(es): batch 3 part 2 — locale-aware dates & relative times
- New src/lib/dateFnsI18n.ts re-exports date-fns with formatDistanceToNow /
formatDistanceToNowStrict / formatDistance / format defaulting to the active
locale, plus dateLocaleCode() for native toLocale{Date,Time}String calls.
- LanguageProvider keeps the locale in sync during render, so the first paint is
already in-language.
- Codemod: 11 date-fns imports now use the shim; ~39 native date/time calls pass
the app locale code instead of undefined/[]/hardcoded 'en-US'. Number
.toLocaleString() calls are deliberately untouched.
This commit is contained in:
parent
3e6354995a
commit
9357386bbd
45 changed files with 168 additions and 65 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { Link } from 'react-router-dom';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||
import { Heart, Repeat2, Zap, AtSign, MessageCircle, CalendarCheck, Megaphone } from 'lucide-react';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||
import { Lock, Send, Loader2, ShieldCheck, MessageSquare, EyeOff } from 'lucide-react';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useMemo } from 'react';
|
||||
import { Lock } from 'lucide-react';
|
||||
import { parseEscrowToken, type EscrowTokenInfo } from '@/lib/cashuEscrow';
|
||||
|
|
@ -13,7 +14,7 @@ function hostOf(url: string): string {
|
|||
}
|
||||
|
||||
function formatDate(seconds: number): string {
|
||||
return new Date(seconds * 1000).toLocaleDateString(undefined, {
|
||||
return new Date(seconds * 1000).toLocaleDateString(dateLocaleCode(), {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
|
|
@ -54,7 +55,7 @@ interface EventCardProps {
|
|||
|
||||
function formatDate(timestamp: number): string {
|
||||
const date = new Date(timestamp * 1000);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
return date.toLocaleDateString(dateLocaleCode(), {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
|
|
@ -63,7 +64,7 @@ function formatDate(timestamp: number): string {
|
|||
|
||||
function formatTime(timestamp: number): string {
|
||||
const date = new Date(timestamp * 1000);
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
return date.toLocaleTimeString(dateLocaleCode(), {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
|
|
@ -99,7 +100,7 @@ export function EventCard({ event, compact = false, goingCount: goingCountProp }
|
|||
<div className="flex-shrink-0 w-16 text-center">
|
||||
<div className="bg-[#ccff00]/10 border-2 border-[#ccff00]/50 rounded-lg p-2">
|
||||
<div className="text-xs font-bold text-[#ccff00] uppercase">
|
||||
{new Date(event.start * 1000).toLocaleDateString('en-US', { month: 'short' })}
|
||||
{new Date(event.start * 1000).toLocaleDateString(dateLocaleCode(), { month: 'short' })}
|
||||
</div>
|
||||
<div className="text-2xl font-black text-white">
|
||||
{new Date(event.start * 1000).getDate()}
|
||||
|
|
@ -169,7 +170,7 @@ export function EventCard({ event, compact = false, goingCount: goingCountProp }
|
|||
<div className="absolute top-3 left-3 flex items-center gap-2">
|
||||
<div className="bg-background/95 backdrop-blur-sm rounded-lg px-3 py-2 text-center shadow-lg">
|
||||
<div className="text-xs font-medium text-primary uppercase">
|
||||
{new Date(event.start * 1000).toLocaleDateString('en-US', { month: 'short' })}
|
||||
{new Date(event.start * 1000).toLocaleDateString(dateLocaleCode(), { month: 'short' })}
|
||||
</div>
|
||||
<div className="text-xl font-bold">
|
||||
{new Date(event.start * 1000).getDate()}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from 'react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||
import { Megaphone, Loader2, Send, CalendarClock, MapPin, XCircle, Info } from 'lucide-react';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useMemo, useState, useRef, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ChevronLeft, ChevronRight, Calendar as CalendarIcon, Route, MapPin, Clock, Users } from 'lucide-react';
|
||||
|
|
@ -132,11 +133,11 @@ function itemOnDay(item: CalendarItem, day: Date): boolean {
|
|||
}
|
||||
|
||||
function formatTime(unix: number): string {
|
||||
return new Date(unix * 1000).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
|
||||
return new Date(unix * 1000).toLocaleTimeString(dateLocaleCode(), { hour: 'numeric', minute: '2-digit' });
|
||||
}
|
||||
|
||||
function formatDayLong(d: Date): string {
|
||||
return d.toLocaleDateString([], { weekday: 'long', month: 'long', day: 'numeric' });
|
||||
return d.toLocaleDateString(dateLocaleCode(), { weekday: 'long', month: 'long', day: 'numeric' });
|
||||
}
|
||||
|
||||
// ─── Component ────────────────────────────────────────────────────────────────
|
||||
|
|
@ -376,7 +377,7 @@ export function EventsCalendar({ events, tours, goingCounts, show, anchorUnix }:
|
|||
<span className="flex items-center gap-1">
|
||||
<Clock className="h-3 w-3" />
|
||||
{multiDay
|
||||
? `${new Date(it.start * 1000).toLocaleDateString([], { month: 'short', day: 'numeric' })} – ${new Date((it.end as number) * 1000).toLocaleDateString([], { month: 'short', day: 'numeric' })}`
|
||||
? `${new Date(it.start * 1000).toLocaleDateString(dateLocaleCode(), { month: 'short', day: 'numeric' })} – ${new Date((it.end as number) * 1000).toLocaleDateString(dateLocaleCode(), { month: 'short', day: 'numeric' })}`
|
||||
: it.allDay
|
||||
? t('placesUi.calendar.allDay')
|
||||
: formatTime(it.start)}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { MessageCircle, MapPin, Search, CheckCircle, ShieldCheck, AlertTriangle,
|
|||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator } from '@/components/ui/dropdown-menu';
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog';
|
||||
import { ReportDialog } from '@/components/ReportDialog';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||
import { useTranslation } from '@/contexts/language';
|
||||
|
||||
interface GearSwapItemCardProps {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
* AI proxy both honour it.
|
||||
*/
|
||||
|
||||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState } from 'react';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { useNostr } from '@nostrify/react';
|
||||
|
|
@ -87,7 +88,7 @@ export function GrantProCard() {
|
|||
title: t('settingsUi.grantPro.grantedTitle'),
|
||||
description: t('settingsUi.grantPro.grantedDesc', {
|
||||
npub: `${nip19.npubEncode(pubkey).slice(0, 14)}…`,
|
||||
date: new Date(expiry * 1000).toLocaleDateString(),
|
||||
date: new Date(expiry * 1000).toLocaleDateString(dateLocaleCode()),
|
||||
}),
|
||||
});
|
||||
setTarget('');
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useMemo } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Calendar, Users, Pencil, Megaphone, Plus, CalendarClock } from 'lucide-react';
|
||||
|
|
@ -11,7 +12,7 @@ import { cn } from '@/lib/utils';
|
|||
import type { SkateEvent } from '@/lib/types';
|
||||
|
||||
function formatWhen(ts: number): string {
|
||||
return new Date(ts * 1000).toLocaleDateString(undefined, {
|
||||
return new Date(ts * 1000).toLocaleDateString(dateLocaleCode(), {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
|
|
@ -32,7 +33,7 @@ function HostedEventRow({ event, isPast }: { event: SkateEvent; isPast: boolean
|
|||
<div className="flex-shrink-0 w-14 text-center">
|
||||
<div className="rounded-lg border-2 border-[#ccff00]/40 bg-[#ccff00]/10 p-1.5">
|
||||
<div className="text-[10px] font-black uppercase text-[#ccff00]">
|
||||
{new Date(event.start * 1000).toLocaleDateString(undefined, { month: 'short' })}
|
||||
{new Date(event.start * 1000).toLocaleDateString(dateLocaleCode(), { month: 'short' })}
|
||||
</div>
|
||||
<div className="text-xl font-black text-white leading-none">
|
||||
{new Date(event.start * 1000).getDate()}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import { getLastLocation } from '@/lib/lastLocation';
|
|||
import { itemDistanceKm } from '@/lib/sortOptions';
|
||||
import { TrustBadge } from '@/components/TrustBadge';
|
||||
import { Clock, Search, Tag, Flag, MoreVertical, ShieldCheck, Calendar, Package, AlertTriangle, CheckCircle, RotateCcw, Trash2, Loader2 } from 'lucide-react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||
import type { MarketplaceItem } from '@/lib/types';
|
||||
import { GEAR_CONDITIONS, GEAR_CATEGORIES, PRICE_TYPES, LISTING_REPORT_REASONS, formatPrice } from '@/lib/types';
|
||||
import { useTranslation } from '@/contexts/language';
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import {
|
|||
Heart,
|
||||
Zap,
|
||||
} from 'lucide-react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||
import { useTranslation } from '@/contexts/language';
|
||||
|
||||
// Kind constants
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useMemo } from 'react';
|
||||
import { ImageWithFallback } from '@/components/ImageWithFallback';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
|
@ -152,7 +153,7 @@ export function NewsletterIssueView({ pubkey, identifier }: { pubkey: string; id
|
|||
|
||||
const authorName = author.data?.metadata?.name ?? genUserName(pubkey);
|
||||
const authorPic = author.data?.metadata?.picture;
|
||||
const date = new Date(issue.publishedAt * 1000).toLocaleDateString('en-US', {
|
||||
const date = new Date(issue.publishedAt * 1000).toLocaleDateString(dateLocaleCode(), {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
|
@ -148,7 +149,7 @@ function NewsletterItem({
|
|||
pubkey: issue.pubkey,
|
||||
identifier: issue.identifier,
|
||||
});
|
||||
const date = new Date(issue.publishedAt * 1000).toLocaleDateString(undefined, {
|
||||
const date = new Date(issue.publishedAt * 1000).toLocaleDateString(dateLocaleCode(), {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
||||
import L from 'leaflet';
|
||||
import { Loader2, MapPin, Navigation, AlertTriangle, LocateFixed, Play, Square, Crosshair, Volume2, VolumeX, Check, Search } from 'lucide-react';
|
||||
|
|
@ -117,7 +118,7 @@ function speedValue(distanceM: number, durationS: number): string | undefined {
|
|||
|
||||
/** Local clock time you'd arrive if you set off now. */
|
||||
function etaClock(durationS: number): string {
|
||||
return new Date(Date.now() + durationS * 1000).toLocaleTimeString([], {
|
||||
return new Date(Date.now() + durationS * 1000).toLocaleTimeString(dateLocaleCode(), {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
* Falls back to static FAQ if AI is unavailable.
|
||||
*/
|
||||
|
||||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
|
||||
import { Input } from '@/components/ui/input';
|
||||
|
|
@ -1656,7 +1657,7 @@ Instructions:
|
|||
{isPro ? (
|
||||
<span
|
||||
className="flex items-center gap-0.5 rounded bg-[#ccff00]/20 px-1 py-0.5 text-[8px] font-black uppercase tracking-wider text-[#ccff00]"
|
||||
title={expiresAt ? t('sasquatchUi.activeUntil', { date: new Date(expiresAt * 1000).toLocaleDateString() }) : t('sasquatchUi.platformOwner')}
|
||||
title={expiresAt ? t('sasquatchUi.activeUntil', { date: new Date(expiresAt * 1000).toLocaleDateString(dateLocaleCode()) }) : t('sasquatchUi.platformOwner')}
|
||||
>
|
||||
⚡ Pro{proDaysLeft !== null ? ` · ${proDaysLeft}d` : ''}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
* no-ads, community-funded app — Pro is the thank-you.
|
||||
*/
|
||||
|
||||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useEffect, useState } from 'react';
|
||||
import QRCode from 'qrcode';
|
||||
import {
|
||||
|
|
@ -112,7 +113,7 @@ export function SasquatchProDialog({
|
|||
<p className="font-black text-[#ccff00]">{t('sasquatchUi.proSupporter')}</p>
|
||||
{expiresAt && (
|
||||
<p className="mt-1 text-xs text-gray-300">
|
||||
{t('sasquatchUi.activeUntil', { date: new Date(expiresAt * 1000).toLocaleDateString() })}
|
||||
{t('sasquatchUi.activeUntil', { date: new Date(expiresAt * 1000).toLocaleDateString(dateLocaleCode()) })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { formatDistanceToNowStrict } from 'date-fns';
|
||||
import { formatDistanceToNowStrict } from '@/lib/dateFnsI18n';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { MessageCircle, Send, Radio, Info, Globe } from 'lucide-react';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useEffect, useRef, useMemo, useCallback } from 'react';
|
||||
import L from 'leaflet';
|
||||
import 'leaflet.markercluster';
|
||||
|
|
@ -621,7 +622,7 @@ function createPopupContent(
|
|||
const dayDiv = document.createElement('div');
|
||||
dayDiv.className = 'p-1';
|
||||
const date = new Date(day.date);
|
||||
const dayName = date.toLocaleDateString('en-US', { weekday: 'short' });
|
||||
const dayName = date.toLocaleDateString(dateLocaleCode(), { weekday: 'short' });
|
||||
dayDiv.innerHTML = `
|
||||
<div class="font-semibold text-gray-700">${escapeHtml(dayName)}</div>
|
||||
<div class="text-lg my-1">${escapeHtml(day.weatherIcon)}</div>
|
||||
|
|
@ -1372,8 +1373,8 @@ export function SpotMap({
|
|||
const details = document.createElement('div');
|
||||
details.className = 'space-y-1.5 mb-3 text-xs text-gray-300';
|
||||
const startDate = new Date(evt.start * 1000);
|
||||
const dateStr = startDate.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' });
|
||||
const timeStr = startDate.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
|
||||
const dateStr = startDate.toLocaleDateString(dateLocaleCode(), { weekday: 'short', month: 'short', day: 'numeric' });
|
||||
const timeStr = startDate.toLocaleTimeString(dateLocaleCode(), { hour: 'numeric', minute: '2-digit' });
|
||||
details.appendChild(popupRow('🗓️', `${dateStr} · ${timeStr}`));
|
||||
if (evt.location) {
|
||||
details.appendChild(popupRow('📍', evt.location));
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
|
|
@ -72,12 +73,12 @@ function formatDateRange(startDate?: number, endDate?: number, withTime = false)
|
|||
});
|
||||
}
|
||||
|
||||
const startStr = start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
const startStr = start.toLocaleDateString(dateLocaleCode(), { month: 'short', day: 'numeric' });
|
||||
|
||||
if (!endDate) return startStr;
|
||||
|
||||
const end = new Date(endDate * 1000);
|
||||
const endStr = end.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
const endStr = end.toLocaleDateString(dateLocaleCode(), { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
|
||||
return `${startStr} - ${endStr}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { Link } from 'react-router-dom';
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
import { Star, ThumbsUp, ArrowUpRight } from 'lucide-react';
|
||||
|
|
@ -28,7 +29,7 @@ function ReviewRow({ ev, selfPubkey }: { ev: NostrEvent; selfPubkey: string }) {
|
|||
const picture = author.data?.metadata?.picture;
|
||||
|
||||
const given = ev.pubkey === selfPubkey;
|
||||
const date = new Date(ev.created_at * 1000).toLocaleDateString();
|
||||
const date = new Date(ev.created_at * 1000).toLocaleDateString(dateLocaleCode());
|
||||
|
||||
return (
|
||||
<div className="flex items-start gap-3 rounded-xl border border-white/8 bg-white/[0.03] p-3">
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { Card, CardContent } from '@/components/ui/card';
|
|||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
import { MessageSquare, ChevronDown, ChevronRight, MoreHorizontal, Flag } from 'lucide-react';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
|
||||
interface CommentProps {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useCashu } from '@/hooks/wallet/useCashu';
|
||||
import { useMintManager } from '@/hooks/wallet/useMintManager';
|
||||
|
|
@ -637,7 +638,7 @@ function CashuWalletCardNative() {
|
|||
{entry.type === 'receive' && t('walletUi.cashu.receivedToken')}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{new Date(entry.timestamp).toLocaleDateString()}
|
||||
{new Date(entry.timestamp).toLocaleDateString(dateLocaleCode())}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
translations,
|
||||
type Language,
|
||||
} from './language';
|
||||
import { setDateLocale } from '@/lib/dateFnsI18n';
|
||||
|
||||
interface LanguageProviderProps {
|
||||
children: ReactNode;
|
||||
|
|
@ -73,6 +74,11 @@ export function LanguageProvider({ children }: LanguageProviderProps) {
|
|||
// Currently no RTL languages supported, but ready for future
|
||||
const isRTL = false;
|
||||
|
||||
// Keep date-fns + native toLocale* formatting in the app language. Called
|
||||
// during render (before children) so the first paint is already in-language;
|
||||
// it is an idempotent module assignment, not React state.
|
||||
setDateLocale(language);
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider value={{ language, setLanguage, t, isRTL }}>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useCVMClient } from '@/hooks/useCVMClient';
|
||||
import { callSasquatchAI, parseToolCall, type ChatTurn } from '@/lib/sasquatchBrain';
|
||||
|
|
@ -64,7 +65,7 @@ export interface CrewContext {
|
|||
|
||||
function fmtDate(unix: number): string {
|
||||
try {
|
||||
return new Date(unix * 1000).toLocaleDateString(undefined, {
|
||||
return new Date(unix * 1000).toLocaleDateString(dateLocaleCode(), {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
* Verification is handled separately by useSasquatchPro.
|
||||
*/
|
||||
|
||||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useNostr } from '@nostrify/react';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
|
|
@ -59,7 +60,7 @@ export function usePurchasePro() {
|
|||
const sendConfirmationDM = useCallback(
|
||||
async (tier: ProTier, expiry: number) => {
|
||||
if (!user?.pubkey || !user.signer?.nip44) return;
|
||||
const until = new Date(expiry * 1000).toLocaleDateString(undefined, {
|
||||
const until = new Date(expiry * 1000).toLocaleDateString(dateLocaleCode(), {
|
||||
year: 'numeric', month: 'long', day: 'numeric',
|
||||
});
|
||||
const content = [
|
||||
|
|
|
|||
64
src/lib/dateFnsI18n.ts
Normal file
64
src/lib/dateFnsI18n.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* Locale-aware date/number formatting.
|
||||
*
|
||||
* The app ships English + Spanish, but `date-fns` calls `formatDistanceToNow`
|
||||
* with the default (English) locale and native `Date.toLocaleDateString()`
|
||||
* falls back to the *runtime* locale, not the app language. This module keeps a
|
||||
* single module-level locale in sync with `LanguageProvider` and re-exports all
|
||||
* of `date-fns` with `formatDistanceToNow` / `formatDistance` / `format`
|
||||
* overridden to apply it. Import from here instead of `date-fns` directly.
|
||||
*
|
||||
* `dateLocaleCode()` feeds native `toLocaleDateString/TimeString` calls so a
|
||||
* Spanish user sees "5 ene" rather than "Jan 5".
|
||||
*/
|
||||
export * from 'date-fns';
|
||||
|
||||
import {
|
||||
formatDistanceToNow as _formatDistanceToNow,
|
||||
formatDistanceToNowStrict as _formatDistanceToNowStrict,
|
||||
formatDistance as _formatDistance,
|
||||
format as _format,
|
||||
} from 'date-fns';
|
||||
import { enUS, es as esLocale } from 'date-fns/locale';
|
||||
import type { Locale } from 'date-fns';
|
||||
import type { Language } from '@/contexts/language';
|
||||
|
||||
let activeLocale: Locale = enUS;
|
||||
let activeCode = 'en-US';
|
||||
|
||||
/** Called by LanguageProvider (during render) so children format in-language. */
|
||||
export function setDateLocale(language: Language): void {
|
||||
activeLocale = language === 'es' ? esLocale : enUS;
|
||||
activeCode = language === 'es' ? 'es-ES' : 'en-US';
|
||||
}
|
||||
|
||||
/** The active date-fns locale. */
|
||||
export function dateLocale(): Locale {
|
||||
return activeLocale;
|
||||
}
|
||||
|
||||
/** BCP-47 code for native `toLocale*` / `Intl` APIs. */
|
||||
export function dateLocaleCode(): string {
|
||||
return activeCode;
|
||||
}
|
||||
|
||||
type DistOptions = Parameters<typeof _formatDistanceToNow>[1];
|
||||
type DistStrictOptions = Parameters<typeof _formatDistanceToNowStrict>[1];
|
||||
type FormatDistanceOptions = Parameters<typeof _formatDistance>[2];
|
||||
type FormatOptions = Parameters<typeof _format>[2];
|
||||
|
||||
export function formatDistanceToNow(date: Date | number, options?: DistOptions) {
|
||||
return _formatDistanceToNow(date, { ...options, locale: options?.locale ?? activeLocale });
|
||||
}
|
||||
|
||||
export function formatDistanceToNowStrict(date: Date | number, options?: DistStrictOptions) {
|
||||
return _formatDistanceToNowStrict(date, { ...options, locale: options?.locale ?? activeLocale });
|
||||
}
|
||||
|
||||
export function formatDistance(date: Date | number, baseDate: Date | number, options?: FormatDistanceOptions) {
|
||||
return _formatDistance(date, baseDate, { ...options, locale: options?.locale ?? activeLocale });
|
||||
}
|
||||
|
||||
export function format(date: Date | number, formatStr: string, options?: FormatOptions) {
|
||||
return _format(date, formatStr, { ...options, locale: options?.locale ?? activeLocale });
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
* 35679 — Park Access Card (addressable). A venue/owner signs it for a rider.
|
||||
* 1293 — Skill badge (regular). A witness signs it for a rider.
|
||||
*/
|
||||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import type { NostrEvent } from '@nostrify/nostrify';
|
||||
|
||||
|
|
@ -193,11 +194,11 @@ export function roleMeta(role: string) {
|
|||
/** Short, locale-aware date (+ time range) for the event pass. */
|
||||
export function formatCardDate(start: number, end?: number): string {
|
||||
const s = new Date(start * 1000);
|
||||
const date = s.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
const time = s.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
||||
const date = s.toLocaleDateString(dateLocaleCode(), { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
const time = s.toLocaleTimeString(dateLocaleCode(), { hour: '2-digit', minute: '2-digit' });
|
||||
if (end && end > start) {
|
||||
const e = new Date(end * 1000);
|
||||
return `${date} · ${time}–${e.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })}`;
|
||||
return `${date} · ${time}–${e.toLocaleTimeString(dateLocaleCode(), { hour: '2-digit', minute: '2-digit' })}`;
|
||||
}
|
||||
return `${date} · ${time}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@
|
|||
* where none is published we report that honestly instead of faking a route.
|
||||
*/
|
||||
|
||||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
|
||||
/** Travel modes offered in the UI. */
|
||||
export type TravelMode = 'foot' | 'bike' | 'moto' | 'car' | 'transit';
|
||||
|
||||
|
|
@ -513,7 +515,7 @@ export async function fetchStopDepartures(
|
|||
export function formatClock(iso: string): string {
|
||||
const d = new Date(iso);
|
||||
if (Number.isNaN(d.getTime())) return '';
|
||||
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
return d.toLocaleTimeString(dateLocaleCode(), { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
// ─── Formatting helpers ──────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
* the real travel sites for the parts we can't (and shouldn't) do ourselves.
|
||||
*/
|
||||
|
||||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { SKATE_SPOT_KIND } from '@/lib/types';
|
||||
|
||||
// ─── Shared result shapes (mirror useCVMClient return items) ─────────────────
|
||||
|
|
@ -127,7 +128,7 @@ export function buildItinerary(opts: {
|
|||
const eventStops: ItineraryStop[] = opts.events.map((e) => ({
|
||||
kind: 'event' as const,
|
||||
title: e.title,
|
||||
subtitle: e.start ? new Date(e.start).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) : e.location,
|
||||
subtitle: e.start ? new Date(e.start).toLocaleDateString(dateLocaleCode(), { month: 'short', day: 'numeric' }) : e.location,
|
||||
url: e.url,
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||||
import { PageError } from '@/components/ErrorState';
|
||||
|
|
@ -288,7 +289,7 @@ export default function CoachDetailPage() {
|
|||
</div>
|
||||
<p className="text-gray-400 flex items-center gap-2">
|
||||
<Calendar className="h-4 w-4" />
|
||||
{t('labUi.detail.joined')} {new Date(profile.createdAt * 1000).toLocaleDateString()}
|
||||
{t('labUi.detail.joined')} {new Date(profile.createdAt * 1000).toLocaleDateString(dateLocaleCode())}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -550,7 +551,7 @@ export default function CoachDetailPage() {
|
|||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-bold text-white">{gig.title}</p>
|
||||
<p className="text-xs text-gray-400">
|
||||
{new Date(gig.start * 1000).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
{new Date(gig.start * 1000).toLocaleDateString(dateLocaleCode(), { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
{gig.location ? ` · ${gig.location}` : ''}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { useNostr } from '@nostrify/react';
|
||||
|
|
@ -264,7 +265,7 @@ export default function CollectionDetailPage() {
|
|||
</Badge>
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
|
||||
{t('walletUi.collection.created', { date: new Date(collection.createdAt * 1000).toLocaleDateString() })}
|
||||
{t('walletUi.collection.created', { date: new Date(collection.createdAt * 1000).toLocaleDateString(dateLocaleCode()) })}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { EscrowPanel } from '@/components/EscrowPanel';
|
|||
import { useDMRead } from '@/hooks/useDMRead';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Send,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { useDMRead } from '@/hooks/useDMRead';
|
|||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||
import {
|
||||
MessageCircle,
|
||||
Plus,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
|
@ -347,8 +348,8 @@ export default function EditEventPage() {
|
|||
if (timeChanged) {
|
||||
parts.push(
|
||||
t('placesUi.editEvent.newTime', {
|
||||
date: startDateTime.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' }),
|
||||
time: startDateTime.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' }),
|
||||
date: startDateTime.toLocaleDateString(dateLocaleCode(), { weekday: 'short', month: 'short', day: 'numeric' }),
|
||||
time: startDateTime.toLocaleTimeString(dateLocaleCode(), { hour: 'numeric', minute: '2-digit' }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
|
|
@ -51,7 +52,7 @@ function eventTypeLabel(t: (key: string) => string, type: string): string {
|
|||
|
||||
function formatTime(timestamp: number): string {
|
||||
const date = new Date(timestamp * 1000);
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
return date.toLocaleTimeString(dateLocaleCode(), {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
});
|
||||
|
|
@ -109,7 +110,7 @@ export default function EventDetailPage() {
|
|||
const bits: string[] = [];
|
||||
if (event.start) {
|
||||
bits.push(
|
||||
new Date(event.start * 1000).toLocaleDateString(undefined, {
|
||||
new Date(event.start * 1000).toLocaleDateString(dateLocaleCode(), {
|
||||
weekday: 'short',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
|
|
@ -440,7 +441,7 @@ export default function EventDetailPage() {
|
|||
<div className="flex items-center gap-3 p-4">
|
||||
<div className="flex h-11 w-11 flex-shrink-0 flex-col items-center justify-center rounded-lg border-2 border-[#ccff00]/40 bg-[#ccff00]/10">
|
||||
<span className="text-[9px] font-black uppercase leading-none text-[#ccff00]">
|
||||
{new Date(event.start * 1000).toLocaleDateString(undefined, { month: 'short' })}
|
||||
{new Date(event.start * 1000).toLocaleDateString(dateLocaleCode(), { month: 'short' })}
|
||||
</span>
|
||||
<span className="text-lg font-black leading-none text-white">
|
||||
{new Date(event.start * 1000).getDate()}
|
||||
|
|
@ -449,7 +450,7 @@ export default function EventDetailPage() {
|
|||
<div className="min-w-0">
|
||||
<p className="text-[10px] font-black uppercase tracking-widest text-gray-500">{t('placesUi.eventDetail.date')}</p>
|
||||
<p className="truncate text-sm font-bold text-white">
|
||||
{new Date(event.start * 1000).toLocaleDateString(undefined, { weekday: 'long' })}
|
||||
{new Date(event.start * 1000).toLocaleDateString(dateLocaleCode(), { weekday: 'long' })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -524,13 +525,13 @@ export default function EventDetailPage() {
|
|||
<CardContent className="pt-6">
|
||||
<div className="text-center">
|
||||
<div className="text-sm font-medium text-primary uppercase mb-1">
|
||||
{new Date(event.start * 1000).toLocaleDateString('en-US', { month: 'long' })}
|
||||
{new Date(event.start * 1000).toLocaleDateString(dateLocaleCode(), { month: 'long' })}
|
||||
</div>
|
||||
<div className="text-5xl font-bold mb-2">
|
||||
{new Date(event.start * 1000).getDate()}
|
||||
</div>
|
||||
<div className="text-muted-foreground">
|
||||
{new Date(event.start * 1000).toLocaleDateString('en-US', { weekday: 'long' })}
|
||||
{new Date(event.start * 1000).toLocaleDateString(dateLocaleCode(), { weekday: 'long' })}
|
||||
</div>
|
||||
<div className="text-lg font-medium mt-2">
|
||||
{formatTime(event.start)}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { hasNip44 as checkNip44 } from '@/lib/signerCapabilities';
|
|||
import { useGroupConversation, useSendDirectMessage } from '@/hooks/useDirectMessages';
|
||||
import { useAuthor } from '@/hooks/useAuthor';
|
||||
import { genUserName } from '@/lib/genUserName';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
import { formatDistanceToNow } from '@/lib/dateFnsI18n';
|
||||
import { ArrowLeft, Lock, Loader2, RefreshCw, Send, Users } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState } from 'react';
|
||||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
|
|
@ -243,7 +244,7 @@ export default function ListingDetailPage() {
|
|||
)}
|
||||
<div className="flex items-center gap-2 text-gray-500">
|
||||
<Calendar className="h-4 w-4" />{' '}
|
||||
{t('marketUi.listingDetail.listed', { date: new Date(item.createdAt * 1000).toLocaleDateString() })}
|
||||
{t('marketUi.listingDetail.listed', { date: new Date(item.createdAt * 1000).toLocaleDateString(dateLocaleCode()) })}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
|
|
@ -41,7 +42,7 @@ import { useTranslation } from '@/contexts/language';
|
|||
/** Format a "member since" year from a kind-0 event timestamp (seconds). */
|
||||
function profileEventCreatedAt(createdAt: number | undefined): string | undefined {
|
||||
if (!createdAt) return undefined;
|
||||
return new Date(createdAt * 1000).toLocaleDateString(undefined, { year: 'numeric', month: 'short' });
|
||||
return new Date(createdAt * 1000).toLocaleDateString(dateLocaleCode(), { year: 'numeric', month: 'short' });
|
||||
}
|
||||
|
||||
/** A stat cell for the at-a-glance grid. Clickable (jumps to its section) when
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
|
@ -65,7 +66,7 @@ function NostrPreviewModal({
|
|||
const displayName = metadata?.name ?? genUserName(user?.pubkey ?? '');
|
||||
const avatar = metadata?.picture;
|
||||
const now = new Date();
|
||||
const timeStr = now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
|
||||
const timeStr = now.toLocaleTimeString(dateLocaleCode(), { hour: 'numeric', minute: '2-digit' });
|
||||
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [editedText, setEditedText] = useState(issue.nostrText);
|
||||
|
|
@ -397,7 +398,7 @@ function SpotOfTheWeekBlock({ spot }: { spot: SpotOfTheWeek }) {
|
|||
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function formatIssueDate(iso: string): string {
|
||||
return new Date(iso).toLocaleDateString('en-US', {
|
||||
return new Date(iso).toLocaleDateString(dateLocaleCode(), {
|
||||
year: 'numeric', month: 'long', day: 'numeric',
|
||||
});
|
||||
}
|
||||
|
|
@ -547,7 +548,7 @@ function LiveIssueCard({ issue }: { issue: SkateNewsletterIssue }) {
|
|||
pubkey: issue.pubkey,
|
||||
identifier: issue.identifier,
|
||||
});
|
||||
const date = new Date(issue.publishedAt * 1000).toLocaleDateString('en-US', {
|
||||
const date = new Date(issue.publishedAt * 1000).toLocaleDateString(dateLocaleCode(), {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
* Print-ready: credit-card size (85.6×54mm), @media print isolates card
|
||||
*/
|
||||
|
||||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
|
@ -150,7 +151,7 @@ function BadgeEventRow({ event }: { event: NostrEvent }) {
|
|||
</p>
|
||||
</div>
|
||||
<div className="text-[9px] text-gray-600 flex-shrink-0">
|
||||
{new Date(event.created_at * 1000).toLocaleDateString()}
|
||||
{new Date(event.created_at * 1000).toLocaleDateString(dateLocaleCode())}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||||
|
|
@ -480,7 +481,7 @@ export default function ShopDetailPage() {
|
|||
<p className="font-medium truncate">{displayName}</p>
|
||||
<p className="text-sm text-muted-foreground flex items-center gap-1">
|
||||
<Calendar className="h-3 w-3" />
|
||||
{new Date(shop.createdAt * 1000).toLocaleDateString()}
|
||||
{new Date(shop.createdAt * 1000).toLocaleDateString(dateLocaleCode())}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
|
|
@ -100,7 +101,7 @@ function CollectionCard({
|
|||
<div className="flex items-center justify-between text-[10px] sm:text-xs text-gray-500">
|
||||
<span className="flex items-center gap-1">
|
||||
<Calendar className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
|
||||
{new Date(collection.createdAt * 1000).toLocaleDateString()}
|
||||
{new Date(collection.createdAt * 1000).toLocaleDateString(dateLocaleCode())}
|
||||
</span>
|
||||
<span className="flex items-center gap-1 text-[#ccff00] sm:opacity-0 sm:group-hover:opacity-100 transition-opacity">
|
||||
{t('mapUi.view')}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||||
|
|
@ -1047,7 +1048,7 @@ export default function SpotDetailPage() {
|
|||
<div className="grid grid-cols-3 gap-2">
|
||||
{weatherData.forecast.daily.map((day, idx) => {
|
||||
const date = new Date(day.date);
|
||||
const dayName = idx === 0 ? t('spotUi.today') : idx === 1 ? t('spotUi.tomorrow') : date.toLocaleDateString('en-US', { weekday: 'short' });
|
||||
const dayName = idx === 0 ? t('spotUi.today') : idx === 1 ? t('spotUi.tomorrow') : date.toLocaleDateString(dateLocaleCode(), { weekday: 'short' });
|
||||
return (
|
||||
<div key={day.date} className="text-center p-2 rounded-lg bg-background/50">
|
||||
<div className="text-xs font-semibold text-muted-foreground mb-1">{dayName}</div>
|
||||
|
|
@ -1149,7 +1150,7 @@ export default function SpotDetailPage() {
|
|||
</p>
|
||||
<p className="text-sm text-muted-foreground flex items-center gap-1">
|
||||
<Calendar className="h-3 w-3" />
|
||||
{new Date(spot.createdAt * 1000).toLocaleDateString()}
|
||||
{new Date(spot.createdAt * 1000).toLocaleDateString(dateLocaleCode())}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { useParams, Link, useNavigate } from 'react-router-dom';
|
||||
import { nip19 } from 'nostr-tools';
|
||||
|
|
@ -149,10 +150,10 @@ export default function TourDetailPage() {
|
|||
minute: '2-digit',
|
||||
});
|
||||
}
|
||||
const startStr = start.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
const startStr = start.toLocaleDateString(dateLocaleCode(), { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
if (tour.endDate) {
|
||||
const end = new Date(tour.endDate * 1000);
|
||||
const endStr = end.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
const endStr = end.toLocaleDateString(dateLocaleCode(), { month: 'short', day: 'numeric', year: 'numeric' });
|
||||
return `${startStr} → ${endStr}`;
|
||||
}
|
||||
return startStr;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { dateLocaleCode } from '@/lib/dateFnsI18n';
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
|
|
@ -281,7 +282,7 @@ export default function TradesPage() {
|
|||
{counterparty ? <>{t('marketUi.trades.with')} <PeerName pubkey={counterparty} /></> : t('marketUi.trades.roleTrade').toLowerCase()}
|
||||
</p>
|
||||
<p className="text-[11px] text-gray-500">
|
||||
{new Date(ev.created_at * 1000).toLocaleDateString()}
|
||||
{new Date(ev.created_at * 1000).toLocaleDateString(dateLocaleCode())}
|
||||
{state !== 'valid' && ` · ${state}`}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue