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', membersLabel: 'Members',
dismissReport: 'Dismiss report', dismissReport: 'Dismiss report',
reportedBy: 'reported by', 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”.', 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.', 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.', entityInvalidPubkey: 'Enter a valid npub, nprofile, or 64-char hex pubkey.',
@ -6882,6 +6883,83 @@ walletUi: {
offlineViewDetails: 'View cached spot details', offlineViewDetails: 'View cached spot details',
offlineKeepActing: 'Keep acting — posts, RSVPs, listings and messages are queued and send when you reconnect', 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: { statusUi: {
quickStatus: 'Quick Status', quickStatus: 'Quick Status',
skating: 'Skating', skating: 'Skating',

View file

@ -6864,6 +6864,7 @@ walletUi: {
membersLabel: 'Miembros', membersLabel: 'Miembros',
dismissReport: 'Descartar reporte', dismissReport: 'Descartar reporte',
reportedBy: 'reportado por', 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”.', 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.', 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.', 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é', 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', 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: { statusUi: {
quickStatus: 'Estado rápido', quickStatus: 'Estado rápido',
skating: 'Patinando', skating: 'Patinando',

View file

@ -718,7 +718,7 @@ export default function AdminReviewPage() { const { t } = useTranslation();
const { user } = useCurrentUser(); const { user } = useCurrentUser();
const [kind, setKind] = useState<QueueKind>('reports'); 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. // Admins only. Everyone else sees a 404 rather than a hint that this exists.
if (!isAdminPubkey(user?.pubkey)) return <NotFound />; 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 { useSeoMeta } from '@unhead/react';
import { useLoginActions } from '@/hooks/useLoginActions'; import { useLoginActions } from '@/hooks/useLoginActions';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { useTranslation } from '@/contexts/language';
import { import {
getAmberCallbackResult, getAmberCallbackResult,
getAmberPendingOperationAsync, getAmberPendingOperationAsync,
@ -28,16 +29,17 @@ import { Button } from '@/components/ui/button';
type Status = 'processing' | 'success' | 'error'; type Status = 'processing' | 'success' | 'error';
export default function AmberCallbackPage() { export default function AmberCallbackPage() {
const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const login = useLoginActions(); const login = useLoginActions();
const { toast } = useToast(); const { toast } = useToast();
const hasProcessed = useRef(false); const hasProcessed = useRef(false);
const [status, setStatus] = useState<Status>('processing'); const [status, setStatus] = useState<Status>('processing');
const [message, setMessage] = useState('Completing login with Amber...'); const [message, setMessage] = useState(t('miscUi.completingLoginAmber'));
useSeoMeta({ useSeoMeta({
title: 'Signing in with Amber - SkateSpots', title: t('seoUi.amberCallbackTitle'),
description: 'Completing your Amber (NIP-55) login.', description: t('seoUi.amberCallbackDesc'),
}); });
useEffect(() => { useEffect(() => {

View file

@ -73,7 +73,7 @@ const PAY_CHIPS = [
export default function AppShopPage() { export default function AppShopPage() {
const { t } = useTranslation(); const { t } = useTranslation();
useSeoMeta({ useSeoMeta({
title: 'App Shop — SkateSpots', title: t('seoUi.appShopTitle'),
description: description:
'SkateSpots merch, paid in sats over Lightning or Cashu. Nothing in stock yet — here is what is coming.', '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() { export default function BecomeBuilderPage() {
const { t } = useTranslation(); const { t } = useTranslation();
useSeoMeta({ useSeoMeta({
title: 'Become a Builder - SkateSpots', title: t('seoUi.becomeBuilderTitle'),
description: 'List your ramp, park and DIY building services — what you build, materials and scale.', description: t('seoUi.becomeBuilderDesc'),
}); });
const navigate = useNavigate(); const navigate = useNavigate();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -169,7 +169,7 @@ export default function CreateTourPage({ existing }: { existing?: SkateTour } =
useSeoMeta({ useSeoMeta({
title: isEditing ? 'Edit Tour - SkateSpots' : 'Create a Tour - SkateSpots', 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(); const navigate = useNavigate();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -24,7 +24,7 @@ export default function EditTourPage() {
useSeoMeta({ useSeoMeta({
title: tour?.title ? `Edit ${tour.title} - SkateSpots` : 'Edit Tour - SkateSpots', 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) { if (isLoading) {

View file

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

View file

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

View file

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

View file

@ -621,8 +621,9 @@ function Footer() {
} }
export default function Index() { export default function Index() {
const { t } = useTranslation();
useSeoMeta({ useSeoMeta({
title: 'SkateSpots - Discover Skateboarding Locations Worldwide', title: t('seoUi.homeTitle'),
description: description:
'Find the best skateboarding spots, connect with fellow skaters, and discover events near you. Built on Nostr for true decentralization.', '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(); const { t } = useTranslation();
useSeoMeta({ useSeoMeta({
title: 'The Lab - SkateSpots', title: t('seoUi.labTitle'),
description: 'Find skateboarding coaches, filmers, photographers, and video editors.', description: t('seoUi.labDesc'),
}); });
const { user } = useCurrentUser(); const { user } = useCurrentUser();

View file

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

View file

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

View file

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

View file

@ -629,8 +629,8 @@ function LiveIssuesSection() {
export default function NewsletterPage() { export default function NewsletterPage() {
const { t } = useTranslation(); const { t } = useTranslation();
useSeoMeta({ useSeoMeta({
title: 'Newsletter - SkateSpots', title: t('seoUi.newsletterTitle'),
description: 'Weekly drops from the SkateSpots community. App updates, new spots, events and the people keeping skateboarding alive.', description: t('seoUi.newsletterDesc'),
}); });
// Opening the newsletter clears the "News" unread badge in the bell — once // 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() { export default function PyramidPage() {
const { t } = useTranslation(); const { t } = useTranslation();
useSeoMeta({ useSeoMeta({
title: 'Pyramid Rankings - SkateSpots', title: t('seoUi.pyramidTitle'),
description: 'Top contributors and community leaders.', description: t('seoUi.pyramidDesc'),
}); });
const { user } = useCurrentUser(); const { user } = useCurrentUser();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -59,7 +59,7 @@ export default function VerifyCardPage() {
const { nostr } = useNostr(); const { nostr } = useNostr();
const parsed = parseNaddr(naddr); 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({ const result = useQuery({
queryKey: ['verify-card', naddr], queryKey: ['verify-card', naddr],

View file

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

View file

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