i18n(es): translate SEO titles & descriptions across 40 pages (seoUi)

This commit is contained in:
SkateSpots 2026-10-07 17:17:26 +07:00
parent 94a6628faa
commit ae02f51d20
43 changed files with 234 additions and 75 deletions

View file

@ -6864,6 +6864,7 @@ walletUi: {
membersLabel: 'Members',
dismissReport: 'Dismiss report',
reportedBy: 'reported by',
completingLoginAmber: 'Completing login with Amber...',
entityMembersHint: 'Add the Nostr accounts of your crew. Each member confirms their own spot with their key — until then they show as “pending”.',
entityVerifiedHint: 'Membership is verified per member — nobody can be added to your crew without confirming from their own account.',
entityInvalidPubkey: 'Enter a valid npub, nprofile, or 64-char hex pubkey.',
@ -6882,6 +6883,83 @@ walletUi: {
offlineViewDetails: 'View cached spot details',
offlineKeepActing: 'Keep acting — posts, RSVPs, listings and messages are queued and send when you reconnect',
},
seoUi: {
becomeBuilderTitle: 'Become a Builder - SkateSpots',
becomeBuilderDesc: 'List your ramp, park and DIY building services — what you build, materials and scale.',
tradesTitle: 'My Trades — SkateSpots',
tradesDesc: 'Manage your marketplace trades, confirmations and reputation.',
eventsTitle: 'Skate Events & Tours - SkateSpots',
eventsDesc: 'Discover skateboarding events, competitions, tours, and sessions near you.',
editShopDesc: 'Edit your skate shop.',
createTourDesc: 'Organize skate tours - local hidden gem tours, national road trips, international adventures, or KOTR-style destruction competitions.',
deckDesignerTitle: 'Deck Designer — SkateSpots DIY',
deckDesignerDesc: 'Design a skateboard or longboard outline, then export a printable 1:1 template, SVG or DXF — concave, kicks, wheelbase and truck holes included.',
createEventTitle: 'Create a Skate Event - SkateSpots',
createEventDesc: 'Create a new skateboarding event for the community.',
editTourDesc: 'Edit your skate tour — update stops, dates and details.',
walletTitle: 'Wallet - SkateSpots',
walletDesc: 'Connect your Lightning wallet to send zaps to skaters and spot creators.',
becomePhotographerTitle: 'Become a Photographer - SkateSpots',
becomePhotographerDesc: 'Create your skate photography profile — cameras, lenses, lighting and services.',
newsletterTitle: 'Newsletter - SkateSpots',
newsletterDesc: 'Weekly drops from the SkateSpots community. App updates, new spots, events and the people keeping skateboarding alive.',
homeTitle: 'SkateSpots - Discover Skateboarding Locations Worldwide',
zapLeaderboardTitle: 'Zap Leaderboards - SkateSpots',
zapLeaderboardDesc: 'Top zapped skate spots and the community members supporting them.',
adminReviewTitle: 'Review queue — SkateSpots',
gearSwapTitle: 'Gear Swap - SkateSpots',
gearSwapDesc: 'Buy & sell used skate gear with other skaters. Decks, trucks, wheels, shoes, and more.',
donateTitle: 'Donate — SkateSpots',
donateDesc: 'Support SkateSpots — Lightning, private on-chain (silent payments), or fund the build on Angor.',
verifyCardTitle: 'Verify — SkateSpots',
verifyCardDesc: 'Verify a SkateSpots card or access pass.',
mapTitle: 'Skate Spots Map - SkateSpots',
mapDesc: 'Explore skateboarding spots around the world on our interactive map.',
diyTitle: 'DIY — Make Your Own | SkateSpots',
diyDesc: 'Guides, open tools and plans to make your own skateboards, ramps and concrete spots — and the people and places to do it with.',
appShopTitle: 'App Shop — SkateSpots',
becomeSkaterTitle: 'Join the Skater Directory - SkateSpots',
becomeSkaterDesc: 'Create your skater profile so the community can find you.',
marketplaceTitle: 'Marketplace - SkateSpots',
marketplaceDesc: 'Buy and sell skateboarding gear on the decentralized Plebeian Market.',
becomeEditorTitle: 'Become a Video Editor - SkateSpots',
becomeEditorDesc: 'Create your skateboarding video editor profile on SkateSpots.',
amberCallbackTitle: 'Signing in with Amber - SkateSpots',
amberCallbackDesc: 'Completing your Amber (NIP-55) login.',
supportersTitle: 'Supporters - SkateSpots',
supportersDesc: 'The community keeping SkateSpots alive — supporters, collaborators, partners and friends.',
becomeCoachTitle: 'Become a Coach - SkateSpots',
becomeCoachDesc: 'Create your skateboarding coach profile.',
riderCardTitle: 'Skater Card — SkateSpots',
riderCardDesc: 'Create your printable SkateSpots Skater License or Park Access Card.',
editEventDesc: 'Edit your skateboarding event.',
createShopTitle: 'Add a Skate Shop - SkateSpots',
createShopDesc: 'Add your skate shop to the community.',
deckCostTitle: 'Deck Cost Calculator — SkateSpots DIY',
deckCostDesc: 'Work out what a deck costs to make — labour, equipment, materials, process time, packaging and margin — and a fair selling price.',
pyramidTitle: 'Pyramid Rankings - SkateSpots',
pyramidDesc: 'Top contributors and community leaders.',
foodTitle: 'Food Near Spots - SkateSpots',
foodDesc: 'Find restaurants, cafés, and food options near skateboarding spots.',
createListingTitle: 'List Gear - SkateSpots',
createListingDesc: 'List skateboarding gear for sale or find gear you want to buy.',
becomeFilmerTitle: 'Become a Filmer - SkateSpots',
becomeFilmerDesc: 'Create your skateboarding filming profile.',
skatersTitle: 'Skaters - SkateSpots',
skatersDesc: 'Discover skaters in the SkateSpots community. Browse by style, stance, level and location.',
communityStatsTitle: 'Community Stats — SkateSpots',
communityStatsDesc: 'Live community metrics for SkateSpots — spots, events, coaches, filmers, editors, tours and growth over time. Pulled directly from Nostr.',
labTitle: 'The Lab - SkateSpots',
labDesc: 'Find skateboarding coaches, filmers, photographers, and video editors.',
stashTitle: 'My Stash - SkateSpots',
stashDesc: 'Manage your private gear inventory. Track what you own, what you want to sell or trade.',
bountyLeaderboardTitle: 'Map & Earn - SkateSpots',
bountyLeaderboardDesc: 'Map skate spots and shops, earn zaps from the community. See the top contributors.',
spotCollectionsTitle: 'My Spot Collections - SkateSpots',
spotCollectionsDesc: 'Organize your favorite skate spots into personalized collections',
mintsTitle: 'Cashu Mints — SkateSpots',
mintsDesc: 'Browse Cashu mints, check their health, and add one to your wallet.',
},
statusUi: {
quickStatus: 'Quick Status',
skating: 'Skating',

View file

@ -6864,6 +6864,7 @@ walletUi: {
membersLabel: 'Miembros',
dismissReport: 'Descartar reporte',
reportedBy: 'reportado por',
completingLoginAmber: 'Completando el inicio de sesión con Amber...',
entityMembersHint: 'Añade las cuentas Nostr de tu crew. Cada miembro confirma su propio spot con su clave — hasta entonces aparecen como “pendientes”.',
entityVerifiedHint: 'La membresía se verifica por miembro — nadie puede añadirse a tu crew sin confirmarlo desde su propia cuenta.',
entityInvalidPubkey: 'Introduce un npub, nprofile o clave hex de 64 caracteres válidos.',
@ -6882,6 +6883,83 @@ walletUi: {
offlineViewDetails: 'Ver detalles de spots en caché',
offlineKeepActing: 'Sigue actuando — publicaciones, RSVPs, anuncios y mensajes se ponen en cola y se envían al reconectar',
},
seoUi: {
becomeBuilderTitle: 'Ser Constructor - SkateSpots',
becomeBuilderDesc: 'Publica tus servicios de construcción de rampas, parques y DIY — qué construyes, materiales y escala.',
tradesTitle: 'Mis Tratos — SkateSpots',
tradesDesc: 'Gestiona tus tratos del marketplace, confirmaciones y reputación.',
eventsTitle: 'Eventos y Rutas de Skate - SkateSpots',
eventsDesc: 'Descubre eventos, competiciones, rutas y sesiones de skate cerca de ti.',
editShopDesc: 'Edita tu tienda de skate.',
createTourDesc: 'Organiza rutas de skate: rutas locales a spots ocultos, viajes nacionales, aventuras internacionales o competiciones de destrucción estilo KOTR.',
deckDesignerTitle: 'Diseñador de Tablas — SkateSpots DIY',
deckDesignerDesc: 'Diseña el contorno de una tabla de skate o longboard y exporta una plantilla imprimible 1:1, SVG o DXF — con concave, colas, distancia entre ejes y agujeros de ejes.',
createEventTitle: 'Crear un Evento de Skate - SkateSpots',
createEventDesc: 'Crea un nuevo evento de skate para la comunidad.',
editTourDesc: 'Edita tu ruta de skate — actualiza paradas, fechas y detalles.',
walletTitle: 'Cartera - SkateSpots',
walletDesc: 'Conecta tu cartera Lightning para enviar zaps a skaters y creadores de spots.',
becomePhotographerTitle: 'Ser Fotógrafo - SkateSpots',
becomePhotographerDesc: 'Crea tu perfil de fotografía de skate — cámaras, objetivos, iluminación y servicios.',
newsletterTitle: 'Boletín - SkateSpots',
newsletterDesc: 'Entregas semanales de la comunidad SkateSpots. Novedades de la app, nuevos spots, eventos y las personas que mantienen vivo el skate.',
homeTitle: 'SkateSpots - Descubre Lugares de Skate en Todo el Mundo',
zapLeaderboardTitle: 'Clasificación de Zaps - SkateSpots',
zapLeaderboardDesc: 'Los spots más zapeados y los miembros de la comunidad que los apoyan.',
adminReviewTitle: 'Cola de revisión — SkateSpots',
gearSwapTitle: 'Intercambio de Equipo - SkateSpots',
gearSwapDesc: 'Compra y vende equipo de skate usado con otros skaters. Tablas, ejes, ruedas, zapatillas y más.',
donateTitle: 'Donar — SkateSpots',
donateDesc: 'Apoya a SkateSpots — Lightning, on-chain privado (silent payments) o financia el desarrollo en Angor.',
verifyCardTitle: 'Verificar — SkateSpots',
verifyCardDesc: 'Verifica un carné de SkateSpots o un pase de acceso.',
mapTitle: 'Mapa de Spots de Skate - SkateSpots',
mapDesc: 'Explora spots de skate de todo el mundo en nuestro mapa interactivo.',
diyTitle: 'DIY — Hazlo Tú Mismo | SkateSpots',
diyDesc: 'Guías, herramientas abiertas y planos para hacer tus propias tablas, rampas y spots de hormigón — y la gente y los lugares para hacerlo.',
appShopTitle: 'Tienda de la App — SkateSpots',
becomeSkaterTitle: 'Únete al Directorio de Skaters - SkateSpots',
becomeSkaterDesc: 'Crea tu perfil de skater para que la comunidad te encuentre.',
marketplaceTitle: 'Marketplace - SkateSpots',
marketplaceDesc: 'Compra y vende equipo de skate en el mercado descentralizado Plebeian.',
becomeEditorTitle: 'Ser Editor de Vídeo - SkateSpots',
becomeEditorDesc: 'Crea tu perfil de editor de vídeo de skate en SkateSpots.',
amberCallbackTitle: 'Iniciando sesión con Amber - SkateSpots',
amberCallbackDesc: 'Completando tu inicio de sesión con Amber (NIP-55).',
supportersTitle: 'Colaboradores - SkateSpots',
supportersDesc: 'La comunidad que mantiene vivo SkateSpots — colaboradores, socios y amigos.',
becomeCoachTitle: 'Ser Coach - SkateSpots',
becomeCoachDesc: 'Crea tu perfil de coach de skate.',
riderCardTitle: 'Carné de Skater — SkateSpots',
riderCardDesc: 'Crea tu Licencia de Skater o Carné de Acceso a Parque imprimible de SkateSpots.',
editEventDesc: 'Edita tu evento de skate.',
createShopTitle: 'Añadir una Tienda de Skate - SkateSpots',
createShopDesc: 'Añade tu tienda de skate a la comunidad.',
deckCostTitle: 'Calculadora de Coste de Tablas — SkateSpots DIY',
deckCostDesc: 'Calcula cuánto cuesta hacer una tabla — mano de obra, equipo, materiales, tiempo de proceso, embalaje y margen — y un precio de venta justo.',
pyramidTitle: 'Clasificación Pirámide - SkateSpots',
pyramidDesc: 'Principales contribuidores y líderes de la comunidad.',
foodTitle: 'Comida Cerca de los Spots - SkateSpots',
foodDesc: 'Encuentra restaurantes, cafeterías y opciones de comida cerca de los spots de skate.',
createListingTitle: 'Publicar Equipo - SkateSpots',
createListingDesc: 'Publica equipo de skate en venta o encuentra el equipo que quieres comprar.',
becomeFilmerTitle: 'Ser Filmer - SkateSpots',
becomeFilmerDesc: 'Crea tu perfil de filmación de skate.',
skatersTitle: 'Skaters - SkateSpots',
skatersDesc: 'Descubre skaters de la comunidad SkateSpots. Explora por estilo, postura, nivel y ubicación.',
communityStatsTitle: 'Estadísticas de la Comunidad — SkateSpots',
communityStatsDesc: 'Métricas en vivo de la comunidad SkateSpots — spots, eventos, coaches, filmers, editores, rutas y crecimiento. Directo desde Nostr.',
labTitle: 'The Lab - SkateSpots',
labDesc: 'Encuentra coaches, filmers, fotógrafos y editores de vídeo de skate.',
stashTitle: 'Mi Stash - SkateSpots',
stashDesc: 'Gestiona tu inventario privado de equipo. Controla lo que tienes, lo que quieres vender o intercambiar.',
bountyLeaderboardTitle: 'Mapea y Gana - SkateSpots',
bountyLeaderboardDesc: 'Mapea spots y tiendas de skate, gana zaps de la comunidad. Mira los principales contribuidores.',
spotCollectionsTitle: 'Mis Colecciones de Spots - SkateSpots',
spotCollectionsDesc: 'Organiza tus spots de skate favoritos en colecciones personalizadas',
mintsTitle: 'Mints de Cashu — SkateSpots',
mintsDesc: 'Explora mints de Cashu, revisa su estado y añade uno a tu cartera.',
},
statusUi: {
quickStatus: 'Estado rápido',
skating: 'Patinando',

View file

@ -718,7 +718,7 @@ export default function AdminReviewPage() { const { t } = useTranslation();
const { user } = useCurrentUser();
const [kind, setKind] = useState<QueueKind>('reports');
useSeoMeta({ title: 'Review queue — SkateSpots' });
useSeoMeta({ title: t('seoUi.adminReviewTitle') });
// Admins only. Everyone else sees a 404 rather than a hint that this exists.
if (!isAdminPubkey(user?.pubkey)) return <NotFound />;

View file

@ -14,6 +14,7 @@ import { useNavigate, Link } from 'react-router-dom';
import { useSeoMeta } from '@unhead/react';
import { useLoginActions } from '@/hooks/useLoginActions';
import { useToast } from '@/hooks/useToast';
import { useTranslation } from '@/contexts/language';
import {
getAmberCallbackResult,
getAmberPendingOperationAsync,
@ -28,16 +29,17 @@ import { Button } from '@/components/ui/button';
type Status = 'processing' | 'success' | 'error';
export default function AmberCallbackPage() {
const { t } = useTranslation();
const navigate = useNavigate();
const login = useLoginActions();
const { toast } = useToast();
const hasProcessed = useRef(false);
const [status, setStatus] = useState<Status>('processing');
const [message, setMessage] = useState('Completing login with Amber...');
const [message, setMessage] = useState(t('miscUi.completingLoginAmber'));
useSeoMeta({
title: 'Signing in with Amber - SkateSpots',
description: 'Completing your Amber (NIP-55) login.',
title: t('seoUi.amberCallbackTitle'),
description: t('seoUi.amberCallbackDesc'),
});
useEffect(() => {

View file

@ -73,7 +73,7 @@ const PAY_CHIPS = [
export default function AppShopPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'App Shop — SkateSpots',
title: t('seoUi.appShopTitle'),
description:
'SkateSpots merch, paid in sats over Lightning or Cashu. Nothing in stock yet — here is what is coming.',
});

View file

@ -94,8 +94,8 @@ function ChipGrid({
export default function BecomeBuilderPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Become a Builder - SkateSpots',
description: 'List your ramp, park and DIY building services — what you build, materials and scale.',
title: t('seoUi.becomeBuilderTitle'),
description: t('seoUi.becomeBuilderDesc'),
});
const navigate = useNavigate();

View file

@ -102,8 +102,8 @@ function OptionChips({
export default function BecomeCoachPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Become a Coach - SkateSpots',
description: 'Create your skateboarding coach profile.',
title: t('seoUi.becomeCoachTitle'),
description: t('seoUi.becomeCoachDesc'),
});
const navigate = useNavigate();

View file

@ -51,8 +51,8 @@ const SKILL_ENTRIES = Object.entries(EDITOR_SKILL_LABELS);
export default function BecomeEditorPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Become a Video Editor - SkateSpots',
description: 'Create your skateboarding video editor profile on SkateSpots.',
title: t('seoUi.becomeEditorTitle'),
description: t('seoUi.becomeEditorDesc'),
});
const navigate = useNavigate();

View file

@ -45,8 +45,8 @@ const SKILL_ENTRIES = Object.entries(FILMING_SKILL_LABELS);
export default function BecomeFilmerPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Become a Filmer - SkateSpots',
description: 'Create your skateboarding filming profile.',
title: t('seoUi.becomeFilmerTitle'),
description: t('seoUi.becomeFilmerDesc'),
});
const navigate = useNavigate();

View file

@ -103,8 +103,8 @@ function ChipGrid({
export default function BecomePhotographerPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Become a Photographer - SkateSpots',
description: 'Create your skate photography profile — cameras, lenses, lighting and services.',
title: t('seoUi.becomePhotographerTitle'),
description: t('seoUi.becomePhotographerDesc'),
});
const navigate = useNavigate();

View file

@ -37,8 +37,8 @@ import { X, Loader2, ArrowLeft, MapPin, PersonStanding, Sparkles, UserRound, Shi
export default function BecomeSkaterPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Join the Skater Directory - SkateSpots',
description: 'Create your skater profile so the community can find you.',
title: t('seoUi.becomeSkaterTitle'),
description: t('seoUi.becomeSkaterDesc'),
});
const navigate = useNavigate();

View file

@ -124,8 +124,8 @@ function LeaderboardSkeleton() {
export default function BountyLeaderboardPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Map & Earn - SkateSpots',
description: 'Map skate spots and shops, earn zaps from the community. See the top contributors.',
title: t('seoUi.bountyLeaderboardTitle'),
description: t('seoUi.bountyLeaderboardDesc'),
});
const { user } = useCurrentUser();

View file

@ -179,8 +179,8 @@ function ChartSkeleton() {
export default function CommunityStatsPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Community Stats — SkateSpots',
description: 'Live community metrics for SkateSpots — spots, events, coaches, filmers, editors, tours and growth over time. Pulled directly from Nostr.',
title: t('seoUi.communityStatsTitle'),
description: t('seoUi.communityStatsDesc'),
});
const { data: stats, isLoading, isError, isFetching, refetch, dataUpdatedAt } = useGrowthStats();

View file

@ -59,8 +59,8 @@ export default function CreateEventPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Create a Skate Event - SkateSpots',
description: 'Create a new skateboarding event for the community.',
title: t('seoUi.createEventTitle'),
description: t('seoUi.createEventDesc'),
});
const navigate = useNavigate();

View file

@ -30,8 +30,8 @@ import { useTranslation } from '@/contexts/language';
export default function CreateListingPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'List Gear - SkateSpots',
description: 'List skateboarding gear for sale or find gear you want to buy.',
title: t('seoUi.createListingTitle'),
description: t('seoUi.createListingDesc'),
});
const navigate = useNavigate();

View file

@ -31,8 +31,8 @@ import { useTranslation } from '@/contexts/language';
export default function CreateShopPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Add a Skate Shop - SkateSpots',
description: 'Add your skate shop to the community.',
title: t('seoUi.createShopTitle'),
description: t('seoUi.createShopDesc'),
});
const navigate = useNavigate();

View file

@ -169,7 +169,7 @@ export default function CreateTourPage({ existing }: { existing?: SkateTour } =
useSeoMeta({
title: isEditing ? 'Edit Tour - SkateSpots' : 'Create a Tour - SkateSpots',
description: 'Organize skate tours - local hidden gem tours, national road trips, international adventures, or KOTR-style destruction competitions.',
description: t('seoUi.createTourDesc'),
});
const navigate = useNavigate();

View file

@ -9,8 +9,8 @@ export default function DeckCostPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Deck Cost Calculator — SkateSpots DIY',
description: 'Work out what a deck costs to make — labour, equipment, materials, process time, packaging and margin — and a fair selling price.',
title: t('seoUi.deckCostTitle'),
description: t('seoUi.deckCostDesc'),
});
return (

View file

@ -19,8 +19,8 @@ export default function DeckDesignerPage() {
const { user } = useCurrentUser();
useSeoMeta({
title: 'Deck Designer — SkateSpots DIY',
description: 'Design a skateboard or longboard outline, then export a printable 1:1 template, SVG or DXF — concave, kicks, wheelbase and truck holes included.',
title: t('seoUi.deckDesignerTitle'),
description: t('seoUi.deckDesignerDesc'),
});
if (!isAdminPubkey(user?.pubkey)) return <Navigate to="/diy" replace />;

View file

@ -60,8 +60,8 @@ export default function DiyPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'DIY — Make Your Own | SkateSpots',
description: 'Guides, open tools and plans to make your own skateboards, ramps and concrete spots — and the people and places to do it with.',
title: t('seoUi.diyTitle'),
description: t('seoUi.diyDesc'),
});
return (

View file

@ -12,8 +12,8 @@ import { useTranslation } from '@/contexts/language';
export default function DonatePage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Donate — SkateSpots',
description: 'Support SkateSpots — Lightning, private on-chain (silent payments), or fund the build on Angor.',
title: t('seoUi.donateTitle'),
description: t('seoUi.donateDesc'),
ogTitle: 'Support SkateSpots',
ogDescription: 'Keep the map free, ad-free and open-source.',
ogImage: 'https://skate-spots.org/images/icon-1024.png',

View file

@ -109,7 +109,7 @@ export default function EditEventPage() {
useSeoMeta({
title: event ? `Edit ${event.title} - SkateSpots` : 'Edit Event - SkateSpots',
description: 'Edit your skateboarding event.',
description: t('seoUi.editEventDesc'),
});
// Initialize form with existing data

View file

@ -88,7 +88,7 @@ export default function EditShopPage() {
useSeoMeta({
title: shop ? `Edit ${shop.name} - SkateSpots` : 'Edit Shop - SkateSpots',
description: 'Edit your skate shop.',
description: t('seoUi.editShopDesc'),
});
// Initialize form with existing data

View file

@ -24,7 +24,7 @@ export default function EditTourPage() {
useSeoMeta({
title: tour?.title ? `Edit ${tour.title} - SkateSpots` : 'Edit Tour - SkateSpots',
description: 'Edit your skate tour — update stops, dates and details.',
description: t('seoUi.editTourDesc'),
});
if (isLoading) {

View file

@ -814,8 +814,8 @@ export default function EventsPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Skate Events & Tours - SkateSpots',
description: 'Discover skateboarding events, competitions, tours, and sessions near you.',
title: t('seoUi.eventsTitle'),
description: t('seoUi.eventsDesc'),
});
const { user } = useCurrentUser();

View file

@ -170,8 +170,8 @@ function FoodSpotSkeleton() {
export default function FoodPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Food Near Spots - SkateSpots',
description: 'Find restaurants, cafés, and food options near skateboarding spots.',
title: t('seoUi.foodTitle'),
description: t('seoUi.foodDesc'),
});
const { data: spots, isLoading: spotsLoading } = useSkateSpots(50);

View file

@ -29,8 +29,8 @@ type TrustFilter = 'all' | 'verified' | 'trusted' | 'following';
export default function GearSwapPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Gear Swap - SkateSpots',
description: 'Buy & sell used skate gear with other skaters. Decks, trucks, wheels, shoes, and more.',
title: t('seoUi.gearSwapTitle'),
description: t('seoUi.gearSwapDesc'),
});
const { user } = useCurrentUser();

View file

@ -621,8 +621,9 @@ function Footer() {
}
export default function Index() {
const { t } = useTranslation();
useSeoMeta({
title: 'SkateSpots - Discover Skateboarding Locations Worldwide',
title: t('seoUi.homeTitle'),
description:
'Find the best skateboarding spots, connect with fellow skaters, and discover events near you. Built on Nostr for true decentralization.',
});

View file

@ -1354,8 +1354,8 @@ export default function LabPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'The Lab - SkateSpots',
description: 'Find skateboarding coaches, filmers, photographers, and video editors.',
title: t('seoUi.labTitle'),
description: t('seoUi.labDesc'),
});
const { user } = useCurrentUser();

View file

@ -536,8 +536,8 @@ export default function MapPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Skate Spots Map - SkateSpots',
description: 'Explore skateboarding spots around the world on our interactive map.',
title: t('seoUi.mapTitle'),
description: t('seoUi.mapDesc'),
});
const navigate = useNavigate();

View file

@ -9,8 +9,8 @@ import { useTranslation } from '@/contexts/language';
export default function MarketplacePage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Marketplace - SkateSpots',
description: 'Buy and sell skateboarding gear on the decentralized Plebeian Market.',
title: t('seoUi.marketplaceTitle'),
description: t('seoUi.marketplaceDesc'),
});
return (

View file

@ -187,8 +187,8 @@ export default function MintsPage() {
};
useSeoMeta({
title: 'Cashu Mints — SkateSpots',
description: 'Browse Cashu mints, check their health, and add one to your wallet.',
title: t('seoUi.mintsTitle'),
description: t('seoUi.mintsDesc'),
});
const canAdd = isNativePlatform() && isInitialized && !!coco;

View file

@ -629,8 +629,8 @@ function LiveIssuesSection() {
export default function NewsletterPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Newsletter - SkateSpots',
description: 'Weekly drops from the SkateSpots community. App updates, new spots, events and the people keeping skateboarding alive.',
title: t('seoUi.newsletterTitle'),
description: t('seoUi.newsletterDesc'),
});
// Opening the newsletter clears the "News" unread badge in the bell — once

View file

@ -77,8 +77,8 @@ function ContributorRow({ pubkey, spots, events, rank }: { pubkey: string; spots
export default function PyramidPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Pyramid Rankings - SkateSpots',
description: 'Top contributors and community leaders.',
title: t('seoUi.pyramidTitle'),
description: t('seoUi.pyramidDesc'),
});
const { user } = useCurrentUser();

View file

@ -161,8 +161,8 @@ function BadgeEventRow({ event }: { event: NostrEvent }) {
export default function RiderCardPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Skater Card — SkateSpots',
description: 'Create your printable SkateSpots Skater License or Park Access Card.',
title: t('seoUi.riderCardTitle'),
description: t('seoUi.riderCardDesc'),
});
const { user } = useCurrentUser();

View file

@ -135,8 +135,8 @@ function SkaterCardSkeleton() {
export default function SkatersPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Skaters - SkateSpots',
description: 'Discover skaters in the SkateSpots community. Browse by style, stance, level and location.',
title: t('seoUi.skatersTitle'),
description: t('seoUi.skatersDesc'),
});
const { data: skaters, isLoading } = useSkaters();

View file

@ -149,8 +149,8 @@ export default function SpotCollectionsPage() {
const [editDescription, setEditDescription] = useState('');
useSeoMeta({
title: 'My Spot Collections - SkateSpots',
description: 'Organize your favorite skate spots into personalized collections',
title: t('seoUi.spotCollectionsTitle'),
description: t('seoUi.spotCollectionsDesc'),
});
const handleCreate = async (e: React.FormEvent) => {

View file

@ -650,8 +650,8 @@ function ListAtSpotDialog({ item, open, onOpenChange, onSelectSpot }: {
export default function StashPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'My Stash - SkateSpots',
description: 'Manage your private gear inventory. Track what you own, what you want to sell or trade.',
title: t('seoUi.stashTitle'),
description: t('seoUi.stashDesc'),
});
const navigate = useNavigate();

View file

@ -519,8 +519,8 @@ export default function SupportersPage() {
: [];
useSeoMeta({
title: 'Supporters - SkateSpots',
description: 'The community keeping SkateSpots alive — supporters, collaborators, partners and friends.',
title: t('seoUi.supportersTitle'),
description: t('seoUi.supportersDesc'),
ogTitle: 'Supporters — SkateSpots',
ogDescription: 'Built by skaters, funded by skaters. Back the map on Angor or zap it.',
ogImage: 'https://skate-spots.org/images/icon-1024.png',

View file

@ -69,8 +69,8 @@ export default function TradesPage() {
const [revoking, setRevoking] = useState(false);
useSeoMeta({
title: 'My Trades — SkateSpots',
description: 'Manage your marketplace trades, confirmations and reputation.',
title: t('seoUi.tradesTitle'),
description: t('seoUi.tradesDesc'),
});
const handleConfirmSale = async (

View file

@ -59,7 +59,7 @@ export default function VerifyCardPage() {
const { nostr } = useNostr();
const parsed = parseNaddr(naddr);
useSeoMeta({ title: 'Verify — SkateSpots', description: 'Verify a SkateSpots card or access pass.' });
useSeoMeta({ title: t('seoUi.verifyCardTitle'), description: t('seoUi.verifyCardDesc') });
const result = useQuery({
queryKey: ['verify-card', naddr],

View file

@ -236,8 +236,8 @@ function NotLoggedIn() {
export default function WalletPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Wallet - SkateSpots',
description: 'Connect your Lightning wallet to send zaps to skaters and spot creators.',
title: t('seoUi.walletTitle'),
description: t('seoUi.walletDesc'),
});
const { user } = useCurrentUser();

View file

@ -150,8 +150,8 @@ function RowsSkeleton() {
export default function ZapLeaderboardPage() {
const { t } = useTranslation();
useSeoMeta({
title: 'Zap Leaderboards - SkateSpots',
description: 'Top zapped skate spots and the community members supporting them.',
title: t('seoUi.zapLeaderboardTitle'),
description: t('seoUi.zapLeaderboardDesc'),
});
const [range, setRange] = useState<RangeKey>('month');