Compare commits

..
6 Commits
Author SHA1 Message Date
anthonyandClaude Sonnet 5 fd7515f8c2 feat: make Ombrora logo in header link to homepage
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 11:58:06 +02:00
anthonyandClaude Sonnet 5 f1a2639866 feat: show full supported-platforms block on each downloader page
Replace the plain "looking for another platform?" text link at the
bottom of each per-platform page with the same SupportedPlatformsSection
used on the homepage, giving stronger internal linking between all
platform pages. Drop the now-unused otherPlatforms/browseAll strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 11:55:48 +02:00
anthonyandClaude Sonnet 5 948b77cef4 feat: add per-platform SEO landing pages (/youtube-downloader, etc.)
Create a dedicated, dynamically-routed page for each of 15 curated
platforms (YouTube, TikTok, Instagram, Facebook, X, Reddit, Pinterest,
Vimeo, SoundCloud, Twitch, Dailymotion, LinkedIn, Tumblr, VK, Snapchat)
under /[locale]/[slug], with a tailored hero, an example URL matched
to the platform, a unique "about" paragraph, and a mix of
platform-specific and shared FAQ items, all translated across
en/fr/es/it. Unknown slugs 404.

Kept the scope to a curated list rather than all 1380 yt-dlp
extractors to avoid thin/duplicate "doorway page" content that search
engines penalize.

The homepage's platform badges and the "+1000 sites" links now point
to these pages, extracted the FAQ accordion markup into a shared
FaqAccordion component reused by both the homepage FAQ and the new
platform pages.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 11:53:36 +02:00
anthonyandClaude Sonnet 5 bd863056a3 feat: add supported sites page, remove yt-dlp mentions from public copy
yt-dlp is an internal implementation detail and should not be exposed
on the public site. Replace direct mentions in the platforms and FAQ
sections with a link to a new /supported-sites page listing every
extractor from yt-dlp's supported-sites doc (searchable, grouped
alphabetically), translated across en/fr/es/it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 11:36:29 +02:00
anthonyandClaude Sonnet 5 c8e991a8f7 feat: add how-it-works, supported platforms and FAQ sections to homepage
Enrich the landing page with reassurance/trust content inspired by
competitor downloader sites: a 3-step "how it works" guide, a
supported-platforms grid with a non-affiliation disclaimer, and an
8-question FAQ (with FAQPage JSON-LD) -- all translated across en/fr/es/it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 11:24:00 +02:00
anthony 1963521818 feat: redesign language switcher as dropdown, add cursor pointers
Replace flag-row selector with a dropdown to fix distorted flag icons
and remove the colored ring border. Add cursor-pointer to the language
menu and download submit button.
2026-08-11 10:51:53 +02:00
17 changed files with 2613 additions and 27 deletions
+161
View File
@@ -41,7 +41,168 @@
"formats": "Multi-format", "formats": "Multi-format",
"formatsDesc": "mp4, mp3, webm, mkv -- up to 1080p" "formatsDesc": "mp4, mp3, webm, mkv -- up to 1080p"
}, },
"howItWorks": {
"heading": "How it works",
"subheading": "Three steps, no software to install",
"step1Title": "Paste the link",
"step1Desc": "Copy the video URL from YouTube, TikTok, Instagram or any supported site",
"step2Title": "Choose your format",
"step2Desc": "Pick MP4, MP3, WebM or MKV and the quality you want, up to 1080p",
"step3Title": "Download",
"step3Desc": "Your file is ready in seconds, via a secure link valid for 24 hours"
},
"platforms": {
"heading": "Supported platforms",
"subheading": "Works with your favorite sites",
"more": "+ 1000 more sites supported. <link>See the full list</link>.",
"disclaimer": "Ombrora is not affiliated with the platforms listed above. Please respect copyright and only download content you have the right to use."
},
"faq": {
"heading": "Frequently asked questions",
"q1": "Is Ombrora really free?",
"a1": "Yes. Ombrora is 100% free, with no account, no subscription and no hidden fees.",
"q2": "Is it legal to download videos?",
"a2": "Downloading is legal for content you own the rights to, that is in the public domain, or that you have permission to save for personal, offline use. You are responsible for respecting the copyright and terms of service of the source platform.",
"q3": "Which sites are supported?",
"a3": "YouTube, TikTok, Instagram, Facebook, X (Twitter), Vimeo, Twitch, SoundCloud, Dailymotion and over 1000 other sites. <link>See the full list</link>.",
"q4": "What formats and quality can I get?",
"a4": "Video in MP4, WebM or MKV, or audio-only in MP3, up to 1080p depending on the source video.",
"q5": "Do you keep a copy of my videos?",
"a5": "No. Files are stored temporarily and automatically deleted after 24 hours. We never share or reuse the content you download.",
"q6": "Do I need to create an account?",
"a6": "No account, no email, no sign-up. Just paste your link and download.",
"q7": "Is there a limit on how many videos I can download?",
"a7": "Yes, a light rate limit protects the service from abuse: a few submissions per hour per visitor.",
"q8": "Why did my download fail?",
"a8": "Some videos are private, age-restricted, geo-blocked or region-locked by the source platform and cannot be downloaded. Live streams in progress and unreleased premieres are not supported either."
},
"supportedSitesPage": {
"title": "Supported sites -- Ombrora",
"description": "Full list of sites supported for video and audio downloads on Ombrora.",
"back": "← Back to home",
"heading": "Supported sites",
"subheading": "Browse or search the full list of sites Ombrora can download from.",
"searchPlaceholder": "Search a site...",
"resultCount": "{count} of {total} sites",
"noResults": "No site matches your search.",
"note": "This list is indicative and evolves over time: some entries may stop working if a source platform changes its restrictions, and new sites are added regularly. Don't see your site? Try pasting the link anyway, it may still work."
},
"footer": { "footer": {
"copyright": "© 2026 Ombrora" "copyright": "© 2026 Ombrora"
},
"downloaderPage": {
"metaTitle": "{platform} Downloader — Free Video & Audio Download",
"metaDescription": "Download {platform} videos for free in MP4, MP3, WebM or MKV. No sign-up, no watermark, fast and secure.",
"heading": "{platform} Downloader",
"subheading": "Paste a {platform} link below and get your file in seconds — free, no account required.",
"aboutHeading": "About downloading from {platform}",
"faqHeading": "{platform} downloader FAQ"
},
"downloaderPages": {
"youtube": {
"about": "YouTube is the world's largest video platform, hosting everything from music videos to full documentaries. Ombrora lets you save any public YouTube video as MP4 or extract the audio as MP3, in the resolution available on the source video.",
"faqQ1": "Can I download a full YouTube playlist?",
"faqA1": "No, only single videos are supported at the moment. Paste the direct link to the video you want.",
"faqQ2": "What's the maximum quality I can get?",
"faqA2": "Up to 1080p, depending on the resolutions available for that specific video."
},
"tiktok": {
"about": "TikTok videos are only playable in the app or with the platform's watermark baked in when shared elsewhere. Ombrora downloads the original TikTok video file so you can save it to your device without the in-app player.",
"faqQ1": "Does the downloaded video still have the TikTok watermark?",
"faqA1": "Ombrora saves the original video file provided by TikTok; whether it includes a watermark depends on how the creator published it.",
"faqQ2": "Can I download private TikTok videos?",
"faqA2": "No, only public videos with a shareable link can be downloaded."
},
"instagram": {
"about": "Whether it's a Reel, an IGTV video or a video shared in a post, Ombrora can fetch the underlying file directly from a public Instagram link.",
"faqQ1": "Can I download Instagram Stories?",
"faqA1": "Only Stories that are still publicly available at the time of download can be fetched; expired Stories cannot be recovered.",
"faqQ2": "Does it work with Instagram Reels?",
"faqA2": "Yes, paste the Reel's link the same way you would for any other Instagram video."
},
"facebook": {
"about": "Facebook videos shared publicly, on pages or in groups, can be downloaded directly with Ombrora, without needing to log into Facebook.",
"faqQ1": "Can I download videos from private Facebook groups?",
"faqA1": "No, only videos that are publicly accessible without logging in can be downloaded.",
"faqQ2": "Does it support Facebook Watch videos?",
"faqA2": "Yes, paste the link to the Facebook Watch video the same way as any other Facebook video link."
},
"twitter": {
"about": "Videos and GIFs posted on X (formerly Twitter) can be saved directly from a public post link, in the best quality available.",
"faqQ1": "Can I download a video from a protected (private) account?",
"faqA1": "No, only videos from public posts that anyone can view are supported.",
"faqQ2": "Does it work with X Spaces recordings?",
"faqA2": "No, only video posts are currently supported, not audio Spaces."
},
"reddit": {
"about": "Reddit hosts videos with the audio track stored separately from the video file. Ombrora automatically merges both into a single downloadable file.",
"faqQ1": "Will the downloaded Reddit video include sound?",
"faqA1": "Yes, Ombrora automatically merges the video and audio tracks that Reddit stores separately.",
"faqQ2": "Can I download an entire gallery post?",
"faqA2": "Only the video portion of a post is downloaded; image-only galleries are not supported."
},
"pinterest": {
"about": "Many Pinterest pins are actually short videos. Ombrora extracts the video file behind a public pin so you can save it outside the app.",
"faqQ1": "Can I download image pins?",
"faqA1": "No, Ombrora only downloads video pins, not static images.",
"faqQ2": "Do I need a Pinterest account?",
"faqA2": "No, you just need the public link to the pin."
},
"vimeo": {
"about": "Vimeo is popular with filmmakers and creative professionals for its high-quality playback. Ombrora downloads public Vimeo videos in the best format available.",
"faqQ1": "Can I download password-protected Vimeo videos?",
"faqA1": "No, only videos that are publicly viewable without a password can be downloaded.",
"faqQ2": "What quality can I expect?",
"faqA2": "It depends on what the uploader made available, up to 1080p in most cases."
},
"soundcloud": {
"about": "SoundCloud is built for audio: tracks, remixes, podcasts and DJ sets. Ombrora downloads SoundCloud tracks directly as MP3 files.",
"faqQ1": "Can I download private or followers-only tracks?",
"faqA1": "No, only tracks that are publicly playable can be downloaded.",
"faqQ2": "Will I get the full track or just a preview?",
"faqA2": "Ombrora downloads the full track as made available by the uploader on the public page."
},
"twitch": {
"about": "Ombrora can download past Twitch broadcasts and clips, letting you keep a local copy of a stream after it disappears from the platform.",
"faqQ1": "Can I download a live stream while it's happening?",
"faqA1": "No, only videos on demand (VODs) and clips that have finished processing can be downloaded.",
"faqQ2": "Do Twitch VODs expire before I can download them?",
"faqA2": "Twitch VODs are only kept online for a limited time by Twitch itself, so download them before they expire."
},
"dailymotion": {
"about": "Dailymotion is a video platform popular in Europe for news clips, entertainment and sports highlights. Ombrora downloads public Dailymotion videos directly.",
"faqQ1": "Can I download Dailymotion playlists?",
"faqA1": "No, only individual video links are supported.",
"faqQ2": "Is there a quality limit?",
"faqA2": "You'll get the best quality the uploader made available for that video."
},
"linkedin": {
"about": "Videos shared in LinkedIn posts, such as talks, product demos or interviews, can be downloaded directly from their public post link.",
"faqQ1": "Can I download videos from a private LinkedIn group?",
"faqA1": "No, only videos from posts visible without logging in can be downloaded.",
"faqQ2": "Does it work with LinkedIn Learning videos?",
"faqA2": "No, LinkedIn Learning courses are behind a paywall and are not supported."
},
"tumblr": {
"about": "Tumblr posts often embed videos and GIFs from external sources as well as natively uploaded clips. Ombrora downloads the video file behind a public Tumblr post.",
"faqQ1": "Does it work with GIFs?",
"faqA1": "Ombrora focuses on video files; animated GIFs are not converted or downloaded.",
"faqQ2": "Can I download from a private/password-protected blog?",
"faqA2": "No, only posts on publicly accessible Tumblr blogs can be downloaded."
},
"vk": {
"about": "VK (VKontakte) is one of the largest social platforms in Eastern Europe, with a large native video library. Ombrora downloads public VK videos directly.",
"faqQ1": "Can I download videos that require a VK account to view?",
"faqA1": "No, only videos accessible without logging in can be downloaded.",
"faqQ2": "What formats are available?",
"faqA2": "The same formats as other platforms: MP4, WebM, MKV or MP3 for audio only."
},
"snapchat": {
"about": "Ombrora supports downloading public Snapchat Spotlight videos — the short, TikTok-style clips shared publicly on the platform. Private Snaps and Stories from friends are not accessible.",
"faqQ1": "Can I download a friend's private Snap or Story?",
"faqA1": "No, only public Spotlight videos with a shareable link can be downloaded.",
"faqQ2": "Is Snapchat Memories supported?",
"faqA2": "No, only public Spotlight content is supported, not personal Memories."
}
} }
} }
+161
View File
@@ -41,7 +41,168 @@
"formats": "Multi-formato", "formats": "Multi-formato",
"formatsDesc": "mp4, mp3, webm, mkv — hasta 1080p" "formatsDesc": "mp4, mp3, webm, mkv — hasta 1080p"
}, },
"howItWorks": {
"heading": "Cómo funciona",
"subheading": "Tres pasos, sin instalar nada",
"step1Title": "Pega el enlace",
"step1Desc": "Copia la URL del vídeo desde YouTube, TikTok, Instagram o cualquier sitio compatible",
"step2Title": "Elige tu formato",
"step2Desc": "Selecciona MP4, MP3, WebM o MKV y la calidad deseada, hasta 1080p",
"step3Title": "Descarga",
"step3Desc": "Tu archivo está listo en segundos, mediante un enlace seguro válido durante 24 horas"
},
"platforms": {
"heading": "Plataformas compatibles",
"subheading": "Funciona con tus sitios favoritos",
"more": "+ 1000 sitios adicionales compatibles. <link>Ver la lista completa</link>.",
"disclaimer": "Ombrora no está afiliado a ninguna de las plataformas mencionadas arriba. Por favor, respeta los derechos de autor y descarga únicamente contenido que tengas derecho a usar."
},
"faq": {
"heading": "Preguntas frecuentes",
"q1": "¿Ombrora es realmente gratis?",
"a1": "Sí. Ombrora es 100% gratuito, sin cuenta, sin suscripción y sin costes ocultos.",
"q2": "¿Es legal descargar vídeos?",
"a2": "Descargar es legal para contenido del que posees los derechos, que es de dominio público, o que tienes permiso para guardar para uso personal sin conexión. Eres responsable de respetar los derechos de autor y las condiciones de uso de la plataforma de origen.",
"q3": "¿Qué sitios son compatibles?",
"a3": "YouTube, TikTok, Instagram, Facebook, X (Twitter), Vimeo, Twitch, SoundCloud, Dailymotion y más de 1000 sitios adicionales. <link>Ver la lista completa</link>.",
"q4": "¿Qué formatos y calidad puedo obtener?",
"a4": "Vídeo en MP4, WebM o MKV, o solo audio en MP3, hasta 1080p según el vídeo de origen.",
"q5": "¿Guardáis una copia de mis vídeos?",
"a5": "No. Los archivos se almacenan temporalmente y se eliminan automáticamente después de 24 horas. Nunca compartimos ni reutilizamos el contenido descargado.",
"q6": "¿Necesito crear una cuenta?",
"a6": "Sin cuenta, sin correo, sin registro. Solo pega tu enlace y descarga.",
"q7": "¿Hay un límite en la cantidad de vídeos que puedo descargar?",
"a7": "Sí, un límite razonable protege el servicio contra abusos: unas pocas solicitudes por hora y por visitante.",
"q8": "¿Por qué falló mi descarga?",
"a8": "Algunos vídeos son privados, tienen restricción de edad, están bloqueados geográficamente o limitados por la plataforma de origen y no se pueden descargar. Las transmisiones en directo en curso y los estrenos no publicados tampoco son compatibles."
},
"supportedSitesPage": {
"title": "Sitios compatibles -- Ombrora",
"description": "Lista completa de sitios compatibles para descargar vídeos y audio en Ombrora.",
"back": "← Volver al inicio",
"heading": "Sitios compatibles",
"subheading": "Consulta o busca en la lista completa de sitios desde los que Ombrora puede descargar.",
"searchPlaceholder": "Buscar un sitio...",
"resultCount": "{count} de {total} sitios",
"noResults": "Ningún sitio coincide con tu búsqueda.",
"note": "Esta lista es orientativa y evoluciona con el tiempo: algunos sitios pueden dejar de funcionar si la plataforma de origen cambia sus restricciones, y se añaden nuevos sitios con frecuencia. ¿No encuentras tu sitio? Prueba a pegar el enlace de todos modos, puede que funcione."
},
"footer": { "footer": {
"copyright": "© 2026 Ombrora" "copyright": "© 2026 Ombrora"
},
"downloaderPage": {
"metaTitle": "Descargador de {platform} — Vídeo y audio gratis",
"metaDescription": "Descarga vídeos de {platform} gratis en MP4, MP3, WebM o MKV. Sin registro, sin marca de agua, rápido y seguro.",
"heading": "Descargador de {platform}",
"subheading": "Pega un enlace de {platform} abajo y obtén tu archivo en segundos — gratis, sin cuenta.",
"aboutHeading": "Sobre la descarga desde {platform}",
"faqHeading": "FAQ del descargador de {platform}"
},
"downloaderPages": {
"youtube": {
"about": "YouTube es la mayor plataforma de vídeo del mundo, con contenidos que van desde videoclips musicales hasta documentales completos. Ombrora te permite guardar cualquier vídeo público de YouTube en MP4 o extraer el audio en MP3, en la resolución disponible en el vídeo original.",
"faqQ1": "¿Puedo descargar una lista de reproducción completa de YouTube?",
"faqA1": "No, por ahora solo se admiten vídeos individuales. Pega el enlace directo del vídeo que quieres.",
"faqQ2": "¿Cuál es la calidad máxima que puedo obtener?",
"faqA2": "Hasta 1080p, según las resoluciones disponibles para ese vídeo en concreto."
},
"tiktok": {
"about": "Los vídeos de TikTok solo se pueden reproducir en la app o con la marca de agua de la plataforma incrustada al compartirlos fuera de ella. Ombrora descarga el archivo de vídeo original de TikTok para que puedas guardarlo en tu dispositivo sin pasar por el reproductor de la app.",
"faqQ1": "¿El vídeo descargado conserva la marca de agua de TikTok?",
"faqA1": "Ombrora guarda el archivo de vídeo original proporcionado por TikTok; que incluya marca de agua depende de cómo lo publicó el creador.",
"faqQ2": "¿Puedo descargar vídeos privados de TikTok?",
"faqA2": "No, solo se pueden descargar vídeos públicos con un enlace para compartir."
},
"instagram": {
"about": "Ya sea un Reel, un vídeo de IGTV o un vídeo compartido en una publicación, Ombrora puede obtener el archivo correspondiente directamente desde un enlace público de Instagram.",
"faqQ1": "¿Puedo descargar Historias de Instagram?",
"faqA1": "Solo se pueden obtener las Historias que sigan disponibles públicamente en el momento de la descarga; las Historias caducadas no se pueden recuperar.",
"faqQ2": "¿Funciona con los Reels de Instagram?",
"faqA2": "Sí, pega el enlace del Reel igual que harías con cualquier otro vídeo de Instagram."
},
"facebook": {
"about": "Los vídeos de Facebook compartidos públicamente, en páginas o en grupos, se pueden descargar directamente con Ombrora, sin necesidad de iniciar sesión en Facebook.",
"faqQ1": "¿Puedo descargar vídeos de grupos privados de Facebook?",
"faqA1": "No, solo se pueden descargar vídeos accesibles públicamente sin iniciar sesión.",
"faqQ2": "¿Es compatible con los vídeos de Facebook Watch?",
"faqA2": "Sí, pega el enlace del vídeo de Facebook Watch igual que con cualquier otro enlace de vídeo de Facebook."
},
"twitter": {
"about": "Los vídeos y GIF publicados en X (antes Twitter) se pueden guardar directamente desde el enlace público de una publicación, en la mejor calidad disponible.",
"faqQ1": "¿Puedo descargar un vídeo de una cuenta protegida (privada)?",
"faqA1": "No, solo se admiten vídeos de publicaciones públicas que cualquiera puede ver.",
"faqQ2": "¿Funciona con las grabaciones de X Spaces?",
"faqA2": "No, actualmente solo se admiten publicaciones de vídeo, no los Spaces de audio."
},
"reddit": {
"about": "Reddit almacena la pista de audio por separado del archivo de vídeo. Ombrora combina automáticamente ambas en un único archivo descargable.",
"faqQ1": "¿El vídeo de Reddit descargado tendrá sonido?",
"faqA1": "Sí, Ombrora combina automáticamente las pistas de vídeo y audio que Reddit almacena por separado.",
"faqQ2": "¿Puedo descargar una publicación de galería completa?",
"faqA2": "Solo se descarga la parte de vídeo de una publicación; las galerías de solo imágenes no son compatibles."
},
"pinterest": {
"about": "Muchos pines de Pinterest son en realidad vídeos cortos. Ombrora extrae el archivo de vídeo detrás de un pin público para que puedas guardarlo fuera de la app.",
"faqQ1": "¿Puedo descargar pines de imagen?",
"faqA1": "No, Ombrora solo descarga pines de vídeo, no imágenes estáticas.",
"faqQ2": "¿Necesito una cuenta de Pinterest?",
"faqA2": "No, solo necesitas el enlace público del pin."
},
"vimeo": {
"about": "Vimeo es popular entre cineastas y profesionales creativos por su reproducción de alta calidad. Ombrora descarga vídeos públicos de Vimeo en el mejor formato disponible.",
"faqQ1": "¿Puedo descargar vídeos de Vimeo protegidos con contraseña?",
"faqA1": "No, solo se pueden descargar vídeos visibles públicamente sin contraseña.",
"faqQ2": "¿Qué calidad puedo esperar?",
"faqA2": "Depende de lo que haya puesto a disposición quien lo subió, hasta 1080p en la mayoría de los casos."
},
"soundcloud": {
"about": "SoundCloud está pensado para el audio: temas, remixes, podcasts y sets de DJ. Ombrora descarga los temas de SoundCloud directamente como archivos MP3.",
"faqQ1": "¿Puedo descargar temas privados o solo para seguidores?",
"faqA1": "No, solo se pueden descargar temas reproducibles públicamente.",
"faqQ2": "¿Obtendré el tema completo o solo una vista previa?",
"faqA2": "Ombrora descarga el tema completo tal como lo puso a disposición quien lo subió en la página pública."
},
"twitch": {
"about": "Ombrora puede descargar retransmisiones y clips pasados de Twitch, para que conserves una copia local de un stream después de que desaparezca de la plataforma.",
"faqQ1": "¿Puedo descargar una transmisión en directo mientras ocurre?",
"faqA1": "No, solo se pueden descargar vídeos bajo demanda (VOD) y clips que ya hayan terminado de procesarse.",
"faqQ2": "¿Los VOD de Twitch caducan antes de que pueda descargarlos?",
"faqA2": "Los VOD de Twitch solo permanecen disponibles en línea durante un tiempo limitado establecido por la propia Twitch, así que descárgalos antes de que caduquen."
},
"dailymotion": {
"about": "Dailymotion es una plataforma de vídeo popular en Europa para clips de noticias, entretenimiento y resúmenes deportivos. Ombrora descarga directamente los vídeos públicos de Dailymotion.",
"faqQ1": "¿Puedo descargar listas de reproducción de Dailymotion?",
"faqA1": "No, solo se admiten enlaces de vídeos individuales.",
"faqQ2": "¿Hay un límite de calidad?",
"faqA2": "Obtendrás la mejor calidad que quien lo subió puso a disposición para ese vídeo."
},
"linkedin": {
"about": "Los vídeos compartidos en publicaciones de LinkedIn, como charlas, demostraciones de productos o entrevistas, se pueden descargar directamente desde su enlace público.",
"faqQ1": "¿Puedo descargar vídeos de un grupo privado de LinkedIn?",
"faqA1": "No, solo se pueden descargar vídeos de publicaciones visibles sin iniciar sesión.",
"faqQ2": "¿Funciona con los vídeos de LinkedIn Learning?",
"faqA2": "No, los cursos de LinkedIn Learning son de pago y no son compatibles."
},
"tumblr": {
"about": "Las publicaciones de Tumblr suelen incluir vídeos y GIF de fuentes externas, además de clips subidos de forma nativa. Ombrora descarga el archivo de vídeo detrás de una publicación pública de Tumblr.",
"faqQ1": "¿Funciona con los GIF?",
"faqA1": "Ombrora se centra en archivos de vídeo; los GIF animados no se convierten ni se descargan.",
"faqQ2": "¿Puedo descargar desde un blog privado o protegido con contraseña?",
"faqA2": "No, solo se pueden descargar publicaciones de blogs de Tumblr de acceso público."
},
"vk": {
"about": "VK (VKontakte) es una de las mayores redes sociales de Europa del Este, con una amplia biblioteca de vídeo nativa. Ombrora descarga directamente los vídeos públicos de VK.",
"faqQ1": "¿Puedo descargar vídeos que requieren una cuenta de VK para verse?",
"faqA1": "No, solo se pueden descargar vídeos accesibles sin iniciar sesión.",
"faqQ2": "¿Qué formatos están disponibles?",
"faqA2": "Los mismos formatos que en otras plataformas: MP4, WebM, MKV o MP3 solo para audio."
},
"snapchat": {
"about": "Ombrora permite descargar vídeos públicos de Snapchat Spotlight, esos clips cortos al estilo TikTok que se comparten públicamente en la plataforma. Los Snaps y las Historias privadas de amigos no son accesibles.",
"faqQ1": "¿Puedo descargar el Snap o la Historia privada de un amigo?",
"faqA1": "No, solo se pueden descargar vídeos públicos de Spotlight con un enlace para compartir.",
"faqQ2": "¿Se admiten los Recuerdos (Memories) de Snapchat?",
"faqA2": "No, solo se admite el contenido público de Spotlight, no los Recuerdos personales."
}
} }
} }
+161
View File
@@ -41,7 +41,168 @@
"formats": "Multi-format", "formats": "Multi-format",
"formatsDesc": "mp4, mp3, webm, mkv — jusqu'en 1080p" "formatsDesc": "mp4, mp3, webm, mkv — jusqu'en 1080p"
}, },
"howItWorks": {
"heading": "Comment ça marche",
"subheading": "Trois étapes, aucun logiciel à installer",
"step1Title": "Collez le lien",
"step1Desc": "Copiez l'URL de la vidéo depuis YouTube, TikTok, Instagram ou tout site pris en charge",
"step2Title": "Choisissez votre format",
"step2Desc": "Sélectionnez MP4, MP3, WebM ou MKV et la qualité souhaitée, jusqu'en 1080p",
"step3Title": "Téléchargez",
"step3Desc": "Votre fichier est prêt en quelques secondes, via un lien sécurisé valable 24 heures"
},
"platforms": {
"heading": "Plateformes prises en charge",
"subheading": "Fonctionne avec vos sites préférés",
"more": "+ 1000 autres sites pris en charge. <link>Voir la liste complète</link>.",
"disclaimer": "Ombrora n'est affilié à aucune des plateformes listées ci-dessus. Merci de respecter le droit d'auteur et de ne télécharger que du contenu que vous avez le droit d'utiliser."
},
"faq": {
"heading": "Questions fréquentes",
"q1": "Ombrora est-il vraiment gratuit ?",
"a1": "Oui. Ombrora est 100% gratuit, sans compte, sans abonnement et sans frais cachés.",
"q2": "Est-il légal de télécharger des vidéos ?",
"a2": "Le téléchargement est légal pour un contenu dont vous détenez les droits, qui appartient au domaine public, ou que vous êtes autorisé à conserver pour un usage personnel hors ligne. Vous êtes responsable du respect du droit d'auteur et des conditions d'utilisation de la plateforme source.",
"q3": "Quels sites sont pris en charge ?",
"a3": "YouTube, TikTok, Instagram, Facebook, X (Twitter), Vimeo, Twitch, SoundCloud, Dailymotion et plus de 1000 autres sites. <link>Voir la liste complète</link>.",
"q4": "Quels formats et quelle qualité puis-je obtenir ?",
"a4": "Vidéo en MP4, WebM ou MKV, ou audio seul en MP3, jusqu'en 1080p selon la vidéo source.",
"q5": "Conservez-vous une copie de mes vidéos ?",
"a5": "Non. Les fichiers sont stockés temporairement et supprimés automatiquement après 24 heures. Nous ne partageons ni ne réutilisons jamais le contenu téléchargé.",
"q6": "Dois-je créer un compte ?",
"a6": "Aucun compte, aucun e-mail, aucune inscription. Collez simplement votre lien et téléchargez.",
"q7": "Y a-t-il une limite au nombre de vidéos que je peux télécharger ?",
"a7": "Oui, une limite raisonnable protège le service contre les abus : quelques soumissions par heure et par visiteur.",
"q8": "Pourquoi mon téléchargement a-t-il échoué ?",
"a8": "Certaines vidéos sont privées, soumises à une restriction d'âge, bloquées géographiquement ou limitées par la plateforme source et ne peuvent pas être téléchargées. Les lives en cours et les premières non sorties ne sont pas non plus pris en charge."
},
"supportedSitesPage": {
"title": "Sites pris en charge -- Ombrora",
"description": "Liste complète des sites pris en charge pour le téléchargement de vidéos et d'audio sur Ombrora.",
"back": "← Retour à l'accueil",
"heading": "Sites pris en charge",
"subheading": "Parcourez ou recherchez la liste complète des sites depuis lesquels Ombrora peut télécharger.",
"searchPlaceholder": "Rechercher un site...",
"resultCount": "{count} sur {total} sites",
"noResults": "Aucun site ne correspond à votre recherche.",
"note": "Cette liste est indicative et évolue régulièrement : certains sites peuvent cesser de fonctionner si la plateforme source change ses restrictions, et de nouveaux sites sont ajoutés fréquemment. Vous ne trouvez pas votre site ? Essayez tout de même de coller le lien, il fonctionnera peut-être."
},
"footer": { "footer": {
"copyright": "© 2026 Ombrora" "copyright": "© 2026 Ombrora"
},
"downloaderPage": {
"metaTitle": "Téléchargeur {platform} — Vidéo et audio gratuits",
"metaDescription": "Téléchargez vos vidéos {platform} gratuitement en MP4, MP3, WebM ou MKV. Sans inscription, sans watermark, rapide et sécurisé.",
"heading": "Téléchargeur {platform}",
"subheading": "Collez un lien {platform} ci-dessous et récupérez votre fichier en quelques secondes — gratuit, sans compte.",
"aboutHeading": "À propos du téléchargement depuis {platform}",
"faqHeading": "FAQ téléchargeur {platform}"
},
"downloaderPages": {
"youtube": {
"about": "YouTube est la plus grande plateforme vidéo au monde, avec des contenus allant des clips musicaux aux documentaires complets. Ombrora vous permet d'enregistrer n'importe quelle vidéo YouTube publique en MP4 ou d'en extraire l'audio en MP3, dans la résolution disponible sur la vidéo source.",
"faqQ1": "Puis-je télécharger une playlist YouTube entière ?",
"faqA1": "Non, seules les vidéos individuelles sont prises en charge pour le moment. Collez le lien direct de la vidéo souhaitée.",
"faqQ2": "Quelle est la qualité maximale disponible ?",
"faqA2": "Jusqu'à 1080p, selon les résolutions disponibles pour cette vidéo en particulier."
},
"tiktok": {
"about": "Les vidéos TikTok ne sont lisibles que dans l'application ou avec le filigrane de la plateforme incrusté lorsqu'elles sont partagées ailleurs. Ombrora télécharge le fichier vidéo TikTok d'origine pour que vous puissiez l'enregistrer sur votre appareil sans passer par le lecteur intégré.",
"faqQ1": "La vidéo téléchargée conserve-t-elle le filigrane TikTok ?",
"faqA1": "Ombrora enregistre le fichier vidéo d'origine fourni par TikTok ; la présence d'un filigrane dépend de la façon dont le créateur l'a publiée.",
"faqQ2": "Puis-je télécharger des vidéos TikTok privées ?",
"faqA2": "Non, seules les vidéos publiques disposant dun lien partageable peuvent être téléchargées."
},
"instagram": {
"about": "Qu'il s'agisse d'un Reel, d'une vidéo IGTV ou d'une vidéo partagée dans une publication, Ombrora peut récupérer le fichier correspondant directement depuis un lien Instagram public.",
"faqQ1": "Puis-je télécharger des Stories Instagram ?",
"faqA1": "Seules les Stories encore publiquement disponibles au moment du téléchargement peuvent être récupérées ; les Stories expirées ne peuvent pas être récupérées.",
"faqQ2": "Est-ce que ça fonctionne avec les Reels Instagram ?",
"faqA2": "Oui, collez le lien du Reel de la même façon que pour n'importe quelle autre vidéo Instagram."
},
"facebook": {
"about": "Les vidéos Facebook partagées publiquement, sur des pages ou dans des groupes, peuvent être téléchargées directement avec Ombrora, sans avoir besoin de se connecter à Facebook.",
"faqQ1": "Puis-je télécharger des vidéos de groupes Facebook privés ?",
"faqA1": "Non, seules les vidéos accessibles publiquement sans connexion peuvent être téléchargées.",
"faqQ2": "Est-ce que les vidéos Facebook Watch sont prises en charge ?",
"faqA2": "Oui, collez le lien de la vidéo Facebook Watch de la même façon que pour tout autre lien vidéo Facebook."
},
"twitter": {
"about": "Les vidéos et GIF publiés sur X (anciennement Twitter) peuvent être enregistrés directement depuis le lien public d'une publication, dans la meilleure qualité disponible.",
"faqQ1": "Puis-je télécharger une vidéo depuis un compte protégé (privé) ?",
"faqA1": "Non, seules les vidéos de publications publiques visibles par tous sont prises en charge.",
"faqQ2": "Est-ce que ça fonctionne avec les enregistrements de X Spaces ?",
"faqA2": "Non, seules les publications vidéo sont prises en charge actuellement, pas les Spaces audio."
},
"reddit": {
"about": "Reddit stocke la piste audio séparément du fichier vidéo. Ombrora fusionne automatiquement les deux en un seul fichier téléchargeable.",
"faqQ1": "La vidéo Reddit téléchargée aura-t-elle du son ?",
"faqA1": "Oui, Ombrora fusionne automatiquement les pistes vidéo et audio que Reddit stocke séparément.",
"faqQ2": "Puis-je télécharger une publication de type galerie entière ?",
"faqA2": "Seule la partie vidéo dune publication est téléchargée ; les galeries composées uniquement dimages ne sont pas prises en charge."
},
"pinterest": {
"about": "De nombreuses épingles Pinterest sont en réalité de courtes vidéos. Ombrora extrait le fichier vidéo derrière une épingle publique pour que vous puissiez l'enregistrer en dehors de l'application.",
"faqQ1": "Puis-je télécharger des épingles image ?",
"faqA1": "Non, Ombrora ne télécharge que les épingles vidéo, pas les images statiques.",
"faqQ2": "Ai-je besoin d'un compte Pinterest ?",
"faqA2": "Non, il vous suffit d'avoir le lien public de l'épingle."
},
"vimeo": {
"about": "Vimeo est apprécié des cinéastes et des professionnels de la création pour la qualité de sa lecture vidéo. Ombrora télécharge les vidéos Vimeo publiques dans le meilleur format disponible.",
"faqQ1": "Puis-je télécharger des vidéos Vimeo protégées par mot de passe ?",
"faqA1": "Non, seules les vidéos visibles publiquement sans mot de passe peuvent être téléchargées.",
"faqQ2": "Quelle qualité puis-je obtenir ?",
"faqA2": "Cela dépend de ce que l'auteur a mis à disposition, jusqu'à 1080p dans la plupart des cas."
},
"soundcloud": {
"about": "SoundCloud est conçu pour laudio : morceaux, remixes, podcasts et sets DJ. Ombrora télécharge les morceaux SoundCloud directement en fichiers MP3.",
"faqQ1": "Puis-je télécharger des morceaux privés ou réservés aux abonnés ?",
"faqA1": "Non, seuls les morceaux lisibles publiquement peuvent être téléchargés.",
"faqQ2": "Vais-je obtenir le morceau complet ou juste un extrait ?",
"faqA2": "Ombrora télécharge le morceau complet tel que mis à disposition par l'auteur sur la page publique."
},
"twitch": {
"about": "Ombrora permet de télécharger les rediffusions et clips Twitch passés, pour garder une copie locale d'un stream après sa disparition de la plateforme.",
"faqQ1": "Puis-je télécharger un live pendant quil se déroule ?",
"faqA1": "Non, seules les vidéos à la demande (VOD) et les clips déjà traités peuvent être téléchargés.",
"faqQ2": "Les VOD Twitch expirent-elles avant que je puisse les télécharger ?",
"faqA2": "Les VOD Twitch ne sont conservées en ligne que pendant une durée limitée par Twitch lui-même, pensez donc à les télécharger avant leur expiration."
},
"dailymotion": {
"about": "Dailymotion est une plateforme vidéo populaire en Europe pour les clips d'actualité, le divertissement et les temps forts sportifs. Ombrora télécharge directement les vidéos Dailymotion publiques.",
"faqQ1": "Puis-je télécharger des playlists Dailymotion ?",
"faqA1": "Non, seuls les liens de vidéos individuelles sont pris en charge.",
"faqQ2": "Y a-t-il une limite de qualité ?",
"faqA2": "Vous obtiendrez la meilleure qualité mise à disposition par l'auteur pour cette vidéo."
},
"linkedin": {
"about": "Les vidéos partagées dans des publications LinkedIn, comme des conférences, des démonstrations de produits ou des interviews, peuvent être téléchargées directement depuis leur lien public.",
"faqQ1": "Puis-je télécharger des vidéos dun groupe LinkedIn privé ?",
"faqA1": "Non, seules les vidéos de publications visibles sans connexion peuvent être téléchargées.",
"faqQ2": "Est-ce que ça fonctionne avec les vidéos LinkedIn Learning ?",
"faqA2": "Non, les formations LinkedIn Learning sont payantes et ne sont pas prises en charge."
},
"tumblr": {
"about": "Les publications Tumblr intègrent souvent des vidéos et GIF provenant de sources externes, ainsi que des clips uploadés nativement. Ombrora télécharge le fichier vidéo derrière une publication Tumblr publique.",
"faqQ1": "Est-ce que ça fonctionne avec les GIF ?",
"faqA1": "Ombrora se concentre sur les fichiers vidéo ; les GIF animés ne sont ni convertis ni téléchargés.",
"faqQ2": "Puis-je télécharger depuis un blog privé/protégé par mot de passe ?",
"faqA2": "Non, seules les publications de blogs Tumblr accessibles publiquement peuvent être téléchargées."
},
"vk": {
"about": "VK (VKontakte) est l'une des plus grandes plateformes sociales d'Europe de l'Est, avec une importante bibliothèque vidéo native. Ombrora télécharge directement les vidéos VK publiques.",
"faqQ1": "Puis-je télécharger des vidéos nécessitant un compte VK pour être visionnées ?",
"faqA1": "Non, seules les vidéos accessibles sans connexion peuvent être téléchargées.",
"faqQ2": "Quels formats sont disponibles ?",
"faqA2": "Les mêmes formats que pour les autres plateformes : MP4, WebM, MKV ou MP3 pour l'audio seul."
},
"snapchat": {
"about": "Ombrora prend en charge le téléchargement des vidéos Snapchat Spotlight publiques, ces courts clips façon TikTok partagés publiquement sur la plateforme. Les Snaps et Stories privés d'amis ne sont pas accessibles.",
"faqQ1": "Puis-je télécharger le Snap ou la Story privée d'un ami ?",
"faqA1": "Non, seules les vidéos Spotlight publiques disposant dun lien partageable peuvent être téléchargées.",
"faqQ2": "Les Souvenirs (Memories) Snapchat sont-ils pris en charge ?",
"faqA2": "Non, seul le contenu Spotlight public est pris en charge, pas les Souvenirs personnels."
}
} }
} }
+161
View File
@@ -41,7 +41,168 @@
"formats": "Multi-formato", "formats": "Multi-formato",
"formatsDesc": "mp4, mp3, webm, mkv — fino a 1080p" "formatsDesc": "mp4, mp3, webm, mkv — fino a 1080p"
}, },
"howItWorks": {
"heading": "Come funziona",
"subheading": "Tre passaggi, nessun software da installare",
"step1Title": "Incolla il link",
"step1Desc": "Copia l'URL del video da YouTube, TikTok, Instagram o qualsiasi sito supportato",
"step2Title": "Scegli il formato",
"step2Desc": "Seleziona MP4, MP3, WebM o MKV e la qualità desiderata, fino a 1080p",
"step3Title": "Scarica",
"step3Desc": "Il tuo file è pronto in pochi secondi, tramite un link sicuro valido 24 ore"
},
"platforms": {
"heading": "Piattaforme supportate",
"subheading": "Funziona con i tuoi siti preferiti",
"more": "+ 1000 altri siti supportati. <link>Vedi l'elenco completo</link>.",
"disclaimer": "Ombrora non è affiliato a nessuna delle piattaforme elencate sopra. Rispetta il copyright e scarica solo contenuti che hai il diritto di utilizzare."
},
"faq": {
"heading": "Domande frequenti",
"q1": "Ombrora è davvero gratuito?",
"a1": "Sì. Ombrora è gratuito al 100%, senza account, senza abbonamento e senza costi nascosti.",
"q2": "È legale scaricare video?",
"a2": "Scaricare è legale per contenuti di cui possiedi i diritti, che sono di dominio pubblico, o che sei autorizzato a conservare per uso personale offline. Sei responsabile del rispetto del copyright e dei termini di servizio della piattaforma di origine.",
"q3": "Quali siti sono supportati?",
"a3": "YouTube, TikTok, Instagram, Facebook, X (Twitter), Vimeo, Twitch, SoundCloud, Dailymotion e oltre 1000 altri siti. <link>Vedi l'elenco completo</link>.",
"q4": "Quali formati e qualità posso ottenere?",
"a4": "Video in MP4, WebM o MKV, oppure solo audio in MP3, fino a 1080p a seconda del video originale.",
"q5": "Conservate una copia dei miei video?",
"a5": "No. I file vengono archiviati temporaneamente ed eliminati automaticamente dopo 24 ore. Non condividiamo né riutilizziamo mai i contenuti scaricati.",
"q6": "Devo creare un account?",
"a6": "Nessun account, nessuna email, nessuna registrazione. Incolla semplicemente il tuo link e scarica.",
"q7": "C'è un limite al numero di video che posso scaricare?",
"a7": "Sì, un limite ragionevole protegge il servizio dagli abusi: poche richieste all'ora per visitatore.",
"q8": "Perché il mio download è fallito?",
"a8": "Alcuni video sono privati, soggetti a restrizioni di età, bloccati geograficamente o limitati dalla piattaforma di origine e non possono essere scaricati. Anche le dirette in corso e le anteprime non ancora pubblicate non sono supportate."
},
"supportedSitesPage": {
"title": "Siti supportati -- Ombrora",
"description": "Elenco completo dei siti supportati per scaricare video e audio su Ombrora.",
"back": "← Torna alla home",
"heading": "Siti supportati",
"subheading": "Sfoglia o cerca nell'elenco completo dei siti da cui Ombrora può scaricare.",
"searchPlaceholder": "Cerca un sito...",
"resultCount": "{count} di {total} siti",
"noResults": "Nessun sito corrisponde alla tua ricerca.",
"note": "Questo elenco è indicativo ed evolve nel tempo: alcuni siti potrebbero smettere di funzionare se la piattaforma di origine modifica le proprie restrizioni, e nuovi siti vengono aggiunti regolarmente. Non trovi il tuo sito? Prova comunque a incollare il link: potrebbe funzionare lo stesso."
},
"footer": { "footer": {
"copyright": "© 2026 Ombrora" "copyright": "© 2026 Ombrora"
},
"downloaderPage": {
"metaTitle": "Downloader {platform} — Video e audio gratis",
"metaDescription": "Scarica video da {platform} gratis in MP4, MP3, WebM o MKV. Senza registrazione, senza watermark, veloce e sicuro.",
"heading": "Downloader {platform}",
"subheading": "Incolla un link {platform} qui sotto e ottieni il tuo file in pochi secondi — gratis, senza account.",
"aboutHeading": "Informazioni sullo scaricamento da {platform}",
"faqHeading": "FAQ downloader {platform}"
},
"downloaderPages": {
"youtube": {
"about": "YouTube è la più grande piattaforma video al mondo, con contenuti che vanno dai video musicali ai documentari completi. Ombrora ti permette di salvare qualsiasi video pubblico di YouTube in MP4 o di estrarne laudio in MP3, nella risoluzione disponibile per il video originale.",
"faqQ1": "Posso scaricare un'intera playlist di YouTube?",
"faqA1": "No, al momento sono supportati solo i singoli video. Incolla il link diretto del video desiderato.",
"faqQ2": "Qual è la qualità massima disponibile?",
"faqA2": "Fino a 1080p, in base alle risoluzioni disponibili per quel video specifico."
},
"tiktok": {
"about": "I video di TikTok sono riproducibili solo nell'app o con la filigrana della piattaforma incorporata quando vengono condivisi altrove. Ombrora scarica il file video originale di TikTok, così puoi salvarlo sul tuo dispositivo senza passare dal player dell'app.",
"faqQ1": "Il video scaricato mantiene la filigrana di TikTok?",
"faqA1": "Ombrora salva il file video originale fornito da TikTok; la presenza della filigrana dipende da come il creatore lo ha pubblicato.",
"faqQ2": "Posso scaricare video privati di TikTok?",
"faqA2": "No, si possono scaricare solo i video pubblici con un link condivisibile."
},
"instagram": {
"about": "Che si tratti di un Reel, di un video IGTV o di un video condiviso in un post, Ombrora può recuperare il file corrispondente direttamente da un link pubblico di Instagram.",
"faqQ1": "Posso scaricare le Storie di Instagram?",
"faqA1": "Possono essere recuperate solo le Storie ancora pubblicamente disponibili al momento del download; le Storie scadute non possono essere recuperate.",
"faqQ2": "Funziona con i Reels di Instagram?",
"faqA2": "Sì, incolla il link del Reel come faresti con qualsiasi altro video di Instagram."
},
"facebook": {
"about": "I video di Facebook condivisi pubblicamente, su pagine o in gruppi, possono essere scaricati direttamente con Ombrora, senza bisogno di accedere a Facebook.",
"faqQ1": "Posso scaricare video da gruppi Facebook privati?",
"faqA1": "No, si possono scaricare solo video accessibili pubblicamente senza accedere.",
"faqQ2": "Sono supportati i video di Facebook Watch?",
"faqA2": "Sì, incolla il link del video di Facebook Watch come faresti con qualsiasi altro link video di Facebook."
},
"twitter": {
"about": "I video e le GIF pubblicati su X (ex Twitter) possono essere salvati direttamente dal link pubblico di un post, nella migliore qualità disponibile.",
"faqQ1": "Posso scaricare un video da un account protetto (privato)?",
"faqA1": "No, sono supportati solo i video di post pubblici visibili a chiunque.",
"faqQ2": "Funziona con le registrazioni degli X Spaces?",
"faqA2": "No, al momento sono supportati solo i post video, non gli Spaces audio."
},
"reddit": {
"about": "Reddit memorizza la traccia audio separatamente dal file video. Ombrora unisce automaticamente le due tracce in un unico file scaricabile.",
"faqQ1": "Il video di Reddit scaricato avrà l'audio?",
"faqA1": "Sì, Ombrora unisce automaticamente le tracce video e audio che Reddit memorizza separatamente.",
"faqQ2": "Posso scaricare un intero post di tipo galleria?",
"faqA2": "Viene scaricata solo la parte video di un post; le gallerie di sole immagini non sono supportate."
},
"pinterest": {
"about": "Molti pin di Pinterest sono in realtà brevi video. Ombrora estrae il file video dietro un pin pubblico, così puoi salvarlo al di fuori dell'app.",
"faqQ1": "Posso scaricare i pin immagine?",
"faqA1": "No, Ombrora scarica solo i pin video, non le immagini statiche.",
"faqQ2": "Mi serve un account Pinterest?",
"faqA2": "No, ti serve solo il link pubblico del pin."
},
"vimeo": {
"about": "Vimeo è apprezzato da registi e professionisti creativi per la qualità di riproduzione elevata. Ombrora scarica i video pubblici di Vimeo nel miglior formato disponibile.",
"faqQ1": "Posso scaricare video di Vimeo protetti da password?",
"faqA1": "No, si possono scaricare solo i video visibili pubblicamente senza password.",
"faqQ2": "Che qualità posso aspettarmi?",
"faqA2": "Dipende da cosa ha reso disponibile chi ha caricato il video, fino a 1080p nella maggior parte dei casi."
},
"soundcloud": {
"about": "SoundCloud è pensato per laudio: brani, remix, podcast e set DJ. Ombrora scarica i brani di SoundCloud direttamente come file MP3.",
"faqQ1": "Posso scaricare brani privati o riservati ai follower?",
"faqA1": "No, si possono scaricare solo i brani riproducibili pubblicamente.",
"faqQ2": "Otterrò il brano completo o solo un'anteprima?",
"faqA2": "Ombrora scarica il brano completo così come reso disponibile da chi lo ha caricato sulla pagina pubblica."
},
"twitch": {
"about": "Ombrora può scaricare le trasmissioni passate e le clip di Twitch, permettendoti di conservare una copia locale di uno stream dopo che scompare dalla piattaforma.",
"faqQ1": "Posso scaricare una diretta mentre è in corso?",
"faqA1": "No, si possono scaricare solo i video on demand (VOD) e le clip già elaborate.",
"faqQ2": "I VOD di Twitch scadono prima che io possa scaricarli?",
"faqA2": "I VOD di Twitch restano online solo per un periodo limitato stabilito da Twitch stessa, quindi scaricali prima che scadano."
},
"dailymotion": {
"about": "Dailymotion è una piattaforma video popolare in Europa per clip di notizie, intrattenimento ed highlights sportivi. Ombrora scarica direttamente i video pubblici di Dailymotion.",
"faqQ1": "Posso scaricare le playlist di Dailymotion?",
"faqA1": "No, sono supportati solo i link ai singoli video.",
"faqQ2": "C'è un limite di qualità?",
"faqA2": "Otterrai la migliore qualità resa disponibile da chi ha caricato quel video."
},
"linkedin": {
"about": "I video condivisi nei post di LinkedIn, come talk, demo di prodotto o interviste, possono essere scaricati direttamente dal loro link pubblico.",
"faqQ1": "Posso scaricare video da un gruppo LinkedIn privato?",
"faqA1": "No, si possono scaricare solo video da post visibili senza accedere.",
"faqQ2": "Funziona con i video di LinkedIn Learning?",
"faqA2": "No, i corsi LinkedIn Learning sono a pagamento e non sono supportati."
},
"tumblr": {
"about": "I post di Tumblr spesso incorporano video e GIF da fonti esterne, oltre a clip caricate nativamente. Ombrora scarica il file video dietro un post pubblico di Tumblr.",
"faqQ1": "Funziona con le GIF?",
"faqA1": "Ombrora si concentra sui file video; le GIF animate non vengono convertite né scaricate.",
"faqQ2": "Posso scaricare da un blog privato o protetto da password?",
"faqA2": "No, si possono scaricare solo i post di blog Tumblr accessibili pubblicamente."
},
"vk": {
"about": "VK (VKontakte) è uno dei più grandi social network dell'Europa dell'Est, con un'ampia libreria video nativa. Ombrora scarica direttamente i video pubblici di VK.",
"faqQ1": "Posso scaricare video che richiedono un account VK per essere visualizzati?",
"faqA1": "No, si possono scaricare solo i video accessibili senza accedere.",
"faqQ2": "Quali formati sono disponibili?",
"faqA2": "Gli stessi formati delle altre piattaforme: MP4, WebM, MKV o MP3 per il solo audio."
},
"snapchat": {
"about": "Ombrora supporta lo scaricamento dei video pubblici di Snapchat Spotlight, le clip brevi in stile TikTok condivise pubblicamente sulla piattaforma. Snap e Storie private degli amici non sono accessibili.",
"faqQ1": "Posso scaricare lo Snap o la Storia privata di un amico?",
"faqA1": "No, si possono scaricare solo i video pubblici di Spotlight con un link condivisibile.",
"faqQ2": "I Ricordi (Memories) di Snapchat sono supportati?",
"faqA2": "No, è supportato solo il contenuto pubblico di Spotlight, non i Ricordi personali."
}
} }
} }
+114
View File
@@ -0,0 +1,114 @@
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { getTranslations } from 'next-intl/server'
import { routing } from '@/i18n/routing'
import { getDownloaderPlatformBySlug } from '@/lib/downloader-platforms'
import { SubmitForm } from '@/components/SubmitForm'
import { ReassuranceSection } from '@/components/ReassuranceSection'
import { HowItWorksSection } from '@/components/HowItWorksSection'
import { SupportedPlatformsSection } from '@/components/SupportedPlatformsSection'
import { FaqAccordion } from '@/components/FaqAccordion'
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string; slug: string }>
}): Promise<Metadata> {
const { locale, slug } = await params
const platform = getDownloaderPlatformBySlug(slug)
if (!platform) return {}
const t = await getTranslations({ locale, namespace: 'downloaderPage' })
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? ''
return {
title: t('metaTitle', { platform: platform.name }),
description: t('metaDescription', { platform: platform.name }),
alternates: {
canonical: `${baseUrl}/${locale}/${slug}`,
languages: Object.fromEntries(
routing.locales.map((l) => [l, `/${l}/${slug}`])
),
},
}
}
export default async function DownloaderPage({
params,
}: {
params: Promise<{ locale: string; slug: string }>
}) {
const { locale, slug } = await params
const platform = getDownloaderPlatformBySlug(slug)
if (!platform) notFound()
const t = await getTranslations({ locale, namespace: 'downloaderPage' })
const tPlatform = await getTranslations({ locale, namespace: `downloaderPages.${platform.id}` })
const tFaq = await getTranslations({ locale, namespace: 'faq' })
const faqItems = [
{ q: tPlatform('faqQ1'), a: tPlatform('faqA1') },
{ q: tPlatform('faqQ2'), a: tPlatform('faqA2') },
{ q: tFaq('q1'), a: tFaq('a1') },
{ q: tFaq('q2'), a: tFaq('a2') },
]
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: faqItems.map(({ q, a }) => ({
'@type': 'Question',
name: q,
acceptedAnswer: { '@type': 'Answer', text: a },
})),
}
return (
<main>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<section
className="flex flex-col items-center justify-center px-6 pt-20 pb-16 text-center"
aria-labelledby="hero-heading"
>
<h1
id="hero-heading"
className="text-4xl md:text-5xl font-bold tracking-tight text-gray-900 dark:text-gray-50 mb-4 max-w-2xl"
>
{t('heading', { platform: platform.name })}
</h1>
<p className="text-lg text-gray-500 dark:text-gray-400 mb-10">
{t('subheading', { platform: platform.name })}
</p>
<SubmitForm examplePlaceholder={platform.exampleUrl} />
</section>
<ReassuranceSection locale={locale} />
<HowItWorksSection locale={locale} />
<section className="py-16 px-6" aria-labelledby="about-heading">
<div className="max-w-3xl mx-auto">
<h2 id="about-heading" className="text-2xl md:text-3xl font-bold tracking-tight text-gray-900 dark:text-gray-50 text-center mb-6">
{t('aboutHeading', { platform: platform.name })}
</h2>
<p className="text-gray-600 dark:text-gray-400 text-center leading-relaxed">
{tPlatform('about')}
</p>
</div>
</section>
<section className="py-16 px-6 bg-gray-50 dark:bg-slate-900/50" aria-labelledby="faq-heading">
<div className="max-w-3xl mx-auto">
<h2 id="faq-heading" className="text-2xl md:text-3xl font-bold tracking-tight text-gray-900 dark:text-gray-50 text-center mb-10">
{t('faqHeading', { platform: platform.name })}
</h2>
<FaqAccordion items={faqItems} />
</div>
</section>
<SupportedPlatformsSection locale={locale} />
</main>
)
}
+6
View File
@@ -1,6 +1,9 @@
import { getTranslations } from 'next-intl/server' import { getTranslations } from 'next-intl/server'
import { SubmitForm } from '@/components/SubmitForm' import { SubmitForm } from '@/components/SubmitForm'
import { ReassuranceSection } from '@/components/ReassuranceSection' import { ReassuranceSection } from '@/components/ReassuranceSection'
import { HowItWorksSection } from '@/components/HowItWorksSection'
import { SupportedPlatformsSection } from '@/components/SupportedPlatformsSection'
import { FaqSection } from '@/components/FaqSection'
export default async function Home({ export default async function Home({
params, params,
@@ -32,6 +35,9 @@ export default async function Home({
</section> </section>
<ReassuranceSection locale={locale} /> <ReassuranceSection locale={locale} />
<HowItWorksSection locale={locale} />
<SupportedPlatformsSection locale={locale} />
<FaqSection locale={locale} />
</main> </main>
) )
} }
+61
View File
@@ -0,0 +1,61 @@
import type { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
import { Link } from '@/navigation'
import { SUPPORTED_SITES } from '@/lib/supported-sites'
import { SupportedSitesBrowser } from '@/components/SupportedSitesBrowser'
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>
}): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: 'supportedSitesPage' })
return {
title: t('title'),
description: t('description'),
}
}
export default async function SupportedSitesPage({
params,
}: {
params: Promise<{ locale: string }>
}) {
const { locale } = await params
const t = await getTranslations({ locale, namespace: 'supportedSitesPage' })
return (
<main className="px-6 py-16">
<div className="max-w-4xl mx-auto">
<Link
href="/"
className="inline-block text-sm text-violet-600 dark:text-violet-400 hover:underline"
>
{t('back')}
</Link>
<h1 className="mt-4 text-3xl md:text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-50 text-center">
{t('heading')}
</h1>
<p className="mt-3 text-gray-500 dark:text-gray-400 text-center max-w-2xl mx-auto">
{t('subheading')}
</p>
<div className="mt-10">
<SupportedSitesBrowser
sites={SUPPORTED_SITES}
searchPlaceholder={t('searchPlaceholder')}
resultCountTemplate={t.raw('resultCount')}
noResultsLabel={t('noResults')}
/>
</div>
<p className="mt-12 text-xs text-gray-400 dark:text-slate-500 text-center max-w-2xl mx-auto">
{t('note')}
</p>
</div>
</main>
)
}
+39
View File
@@ -0,0 +1,39 @@
import type { ReactNode } from 'react'
function ChevronIcon() {
return (
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden
className="shrink-0 transition-transform duration-200 group-open:rotate-180"
>
<path d="M6 9l6 6 6-6" />
</svg>
)
}
export function FaqAccordion({ items }: { items: { q: string; a: ReactNode }[] }) {
return (
<div className="space-y-3">
{items.map(({ q, a }) => (
<details
key={q}
className="group rounded-xl border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-5 py-4"
>
<summary className="flex items-center justify-between gap-4 cursor-pointer select-none font-medium text-gray-900 dark:text-gray-50 list-none [&::-webkit-details-marker]:hidden">
{q}
<ChevronIcon />
</summary>
<p className="mt-3 text-sm text-gray-500 dark:text-gray-400">{a}</p>
</details>
))}
</div>
)
}
+53
View File
@@ -0,0 +1,53 @@
import { getTranslations } from 'next-intl/server'
import { Link } from '@/navigation'
import { FaqAccordion } from '@/components/FaqAccordion'
const QUESTION_COUNT = 8
function stripRichTags(text: string) {
return text.replace(/<link>(.*?)<\/link>/g, '$1')
}
export async function FaqSection({ locale }: { locale: string }) {
const t = await getTranslations({ locale, namespace: 'faq' })
const items = Array.from({ length: QUESTION_COUNT }, (_, i) => {
const key = `a${i + 1}` as const
return {
q: t(`q${i + 1}`),
aText: stripRichTags(t.raw(key) as string),
a: t.rich(key, {
link: (chunks) => (
<Link href="/supported-sites" className="text-violet-600 dark:text-violet-400 hover:underline">
{chunks}
</Link>
),
}),
}
})
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: items.map(({ q, aText }) => ({
'@type': 'Question',
name: q,
acceptedAnswer: { '@type': 'Answer', text: aText },
})),
}
return (
<section className="py-16 px-6 bg-gray-50 dark:bg-slate-900/50" aria-labelledby="faq-heading">
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<div className="max-w-3xl mx-auto">
<h2 id="faq-heading" className="text-2xl md:text-3xl font-bold tracking-tight text-gray-900 dark:text-gray-50 text-center mb-10">
{t('heading')}
</h2>
<FaqAccordion items={items} />
</div>
</section>
)
}
+3 -2
View File
@@ -1,3 +1,4 @@
import { Link } from '@/navigation'
import { ThemeToggle } from '@/components/ThemeToggle' import { ThemeToggle } from '@/components/ThemeToggle'
import { LanguageSwitcher } from '@/components/LanguageSwitcher' import { LanguageSwitcher } from '@/components/LanguageSwitcher'
@@ -5,9 +6,9 @@ export function Header() {
return ( return (
<header className="sticky top-0 z-50 border-b border-gray-100 dark:border-slate-800 bg-white/80 dark:bg-slate-950/80 backdrop-blur-sm"> <header className="sticky top-0 z-50 border-b border-gray-100 dark:border-slate-800 bg-white/80 dark:bg-slate-950/80 backdrop-blur-sm">
<div className="max-w-5xl mx-auto px-6 py-3 flex items-center justify-between"> <div className="max-w-5xl mx-auto px-6 py-3 flex items-center justify-between">
<span className="text-xl font-bold tracking-tight select-none"> <Link href="/" className="text-xl font-bold tracking-tight select-none">
<span className="text-violet-600 dark:text-violet-400">O</span>mbrora <span className="text-violet-600 dark:text-violet-400">O</span>mbrora
</span> </Link>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<ThemeToggle /> <ThemeToggle />
<LanguageSwitcher /> <LanguageSwitcher />
+68
View File
@@ -0,0 +1,68 @@
import { getTranslations } from 'next-intl/server'
function LinkIcon() {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M10 13a5 5 0 007.07 0l2.83-2.83a5 5 0 00-7.07-7.07l-1.5 1.5"/>
<path d="M14 11a5 5 0 00-7.07 0l-2.83 2.83a5 5 0 007.07 7.07l1.5-1.5"/>
</svg>
)
}
function SlidersIcon() {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<line x1="4" y1="6" x2="20" y2="6"/>
<line x1="4" y1="12" x2="20" y2="12"/>
<line x1="4" y1="18" x2="20" y2="18"/>
<circle cx="9" cy="6" r="2" fill="currentColor" stroke="none"/>
<circle cx="16" cy="12" r="2" fill="currentColor" stroke="none"/>
<circle cx="11" cy="18" r="2" fill="currentColor" stroke="none"/>
</svg>
)
}
function DownloadIcon() {
return (
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<path d="M12 3v12"/>
<path d="M7 10l5 5 5-5"/>
<path d="M4 21h16"/>
</svg>
)
}
export async function HowItWorksSection({ locale }: { locale: string }) {
const t = await getTranslations({ locale, namespace: 'howItWorks' })
const steps = [
{ title: t('step1Title'), desc: t('step1Desc'), Icon: LinkIcon },
{ title: t('step2Title'), desc: t('step2Desc'), Icon: SlidersIcon },
{ title: t('step3Title'), desc: t('step3Desc'), Icon: DownloadIcon },
]
return (
<section className="py-16 px-6 bg-gray-50 dark:bg-slate-900/50" aria-labelledby="how-it-works-heading">
<div className="max-w-5xl mx-auto">
<div className="text-center mb-12">
<h2 id="how-it-works-heading" className="text-2xl md:text-3xl font-bold tracking-tight text-gray-900 dark:text-gray-50">
{t('heading')}
</h2>
<p className="mt-2 text-gray-500 dark:text-gray-400">{t('subheading')}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-10">
{steps.map(({ title, desc, Icon }, i) => (
<div key={title} className="text-center">
<div className="mx-auto inline-flex items-center justify-center w-14 h-14 rounded-full bg-violet-100 dark:bg-violet-900/30 text-violet-600 dark:text-violet-400 mb-4">
<Icon />
</div>
<p className="text-xs font-semibold text-violet-600 dark:text-violet-400 mb-1">0{i + 1}</p>
<p className="font-semibold text-gray-900 dark:text-gray-50">{title}</p>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1 max-w-xs mx-auto">{desc}</p>
</div>
))}
</div>
</div>
</section>
)
}
+83 -22
View File
@@ -1,44 +1,105 @@
'use client' 'use client'
import { useEffect, useRef, useState } from 'react'
import { useLocale } from 'next-intl' import { useLocale } from 'next-intl'
import { useRouter, usePathname } from '@/navigation' import { useRouter, usePathname } from '@/navigation'
import { routing } from '@/i18n/routing' import { routing } from '@/i18n/routing'
const FLAG_LABELS: Record<string, string> = { const LANGUAGE_LABELS: Record<string, string> = {
en: 'English', en: 'English',
fr: 'Français', fr: 'Français',
es: 'Español', es: 'Español',
it: 'Italiano', it: 'Italiano',
} }
function Flag({ locale, className }: { locale: string; className?: string }) {
return (
<img
src={`/flags/${locale}.svg`}
alt=""
className={['w-6 h-4 rounded-sm object-cover shrink-0', className].filter(Boolean).join(' ')}
/>
)
}
function ChevronIcon() {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<polyline points="6 9 12 15 18 9" />
</svg>
)
}
export function LanguageSwitcher() { export function LanguageSwitcher() {
const locale = useLocale() const locale = useLocale()
const router = useRouter() const router = useRouter()
const pathname = usePathname() const pathname = usePathname()
const [open, setOpen] = useState(false)
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!open) return
function handlePointerDown(event: MouseEvent) {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setOpen(false)
}
}
function handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') setOpen(false)
}
document.addEventListener('mousedown', handlePointerDown)
document.addEventListener('keydown', handleKeyDown)
return () => {
document.removeEventListener('mousedown', handlePointerDown)
document.removeEventListener('keydown', handleKeyDown)
}
}, [open])
function selectLocale(loc: string) {
setOpen(false)
router.replace(pathname, { locale: loc })
}
return ( return (
<div className="flex items-center gap-1"> <div ref={containerRef} className="relative">
{routing.locales.map((loc) => ( <button
<button type="button"
key={loc} onClick={() => setOpen((value) => !value)}
onClick={() => router.replace(pathname, { locale: loc })} aria-haspopup="listbox"
aria-label={FLAG_LABELS[loc]} aria-expanded={open}
className={[ className="flex items-center gap-2 rounded-lg py-1.5 pl-2 pr-2.5 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-slate-800 transition-colors cursor-pointer"
'rounded transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500', >
loc === locale <Flag locale={locale} />
? 'opacity-100 ring-2 ring-violet-500 ring-offset-1 ring-offset-white dark:ring-offset-slate-950' <ChevronIcon />
: 'opacity-40 hover:opacity-70', </button>
].join(' ')}
{open && (
<ul
role="listbox"
className="absolute right-0 top-full mt-2 min-w-[9rem] rounded-lg border border-gray-200 dark:border-slate-800 bg-white dark:bg-slate-900 shadow-lg py-1 z-50"
> >
<img {routing.locales.map((loc) => (
src={`/flags/${loc}.svg`} <li key={loc} role="option" aria-selected={loc === locale}>
alt={FLAG_LABELS[loc]} <button
width={28} type="button"
height={20} onClick={() => selectLocale(loc)}
className="rounded-sm block" className={[
/> 'flex w-full items-center gap-2.5 px-3 py-2 text-sm text-left transition-colors cursor-pointer',
</button> loc === locale
))} ? 'bg-violet-50 dark:bg-violet-500/10 text-violet-600 dark:text-violet-400 font-medium'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-slate-800',
].join(' ')}
>
<Flag locale={loc} />
{LANGUAGE_LABELS[loc]}
</button>
</li>
))}
</ul>
)}
</div> </div>
) )
} }
+3 -3
View File
@@ -19,7 +19,7 @@ function Spinner() {
const selectClass = const selectClass =
'rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-2 py-1 text-gray-900 dark:text-gray-50 text-sm outline-none focus:ring-2 focus:ring-violet-500' 'rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-900 px-2 py-1 text-gray-900 dark:text-gray-50 text-sm outline-none focus:ring-2 focus:ring-violet-500'
export function SubmitForm() { export function SubmitForm({ examplePlaceholder }: { examplePlaceholder?: string } = {}) {
const t = useTranslations('home') const t = useTranslations('home')
const router = useRouter() const router = useRouter()
const [url, setUrl] = useState('') const [url, setUrl] = useState('')
@@ -75,13 +75,13 @@ export function SubmitForm() {
value={url} value={url}
onChange={(e) => setUrl(e.target.value)} onChange={(e) => setUrl(e.target.value)}
required required
placeholder="https://www.youtube.com/watch?v=..." placeholder={examplePlaceholder ?? 'https://www.youtube.com/watch?v=...'}
className="flex-1 px-4 py-3 bg-transparent text-sm outline-none placeholder:text-gray-400 dark:placeholder:text-slate-500" className="flex-1 px-4 py-3 bg-transparent text-sm outline-none placeholder:text-gray-400 dark:placeholder:text-slate-500"
/> />
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="flex items-center gap-2 px-5 py-3 bg-violet-600 hover:bg-violet-700 dark:bg-violet-500 dark:hover:bg-violet-600 text-white text-sm font-medium transition-colors disabled:opacity-60" className="flex items-center gap-2 px-5 py-3 bg-violet-600 hover:bg-violet-700 dark:bg-violet-500 dark:hover:bg-violet-600 text-white text-sm font-medium transition-colors disabled:opacity-60 cursor-pointer disabled:cursor-not-allowed"
> >
{loading && <Spinner />} {loading && <Spinner />}
{loading ? t('submitting') : t('submit')} {loading ? t('submitting') : t('submit')}
@@ -0,0 +1,40 @@
import { getTranslations } from 'next-intl/server'
import { Link } from '@/navigation'
import { DOWNLOADER_PLATFORMS } from '@/lib/downloader-platforms'
export async function SupportedPlatformsSection({ locale }: { locale: string }) {
const t = await getTranslations({ locale, namespace: 'platforms' })
return (
<section className="py-16 px-6" aria-labelledby="platforms-heading">
<div className="max-w-5xl mx-auto text-center">
<h2 id="platforms-heading" className="text-2xl md:text-3xl font-bold tracking-tight text-gray-900 dark:text-gray-50">
{t('heading')}
</h2>
<p className="mt-2 text-gray-500 dark:text-gray-400">{t('subheading')}</p>
<div className="mt-8 flex flex-wrap justify-center gap-3">
{DOWNLOADER_PLATFORMS.map(({ id, slug, name }) => (
<Link
key={id}
href={`/${slug}`}
className="px-4 py-2 rounded-full border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-sm font-medium text-gray-700 dark:text-gray-300 hover:border-violet-300 dark:hover:border-violet-700 hover:text-violet-600 dark:hover:text-violet-400 transition-colors"
>
{name}
</Link>
))}
</div>
<p className="mt-4 text-sm text-gray-400 dark:text-slate-500">
{t.rich('more', {
link: (chunks) => (
<Link href="/supported-sites" className="text-violet-600 dark:text-violet-400 hover:underline">
{chunks}
</Link>
),
})}
</p>
<p className="mt-6 text-xs text-gray-400 dark:text-slate-500 max-w-2xl mx-auto">{t('disclaimer')}</p>
</div>
</section>
)
}
+88
View File
@@ -0,0 +1,88 @@
'use client'
import { useMemo, useState } from 'react'
function SearchIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
<circle cx="11" cy="11" r="7" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
)
}
export function SupportedSitesBrowser({
sites,
searchPlaceholder,
resultCountTemplate,
noResultsLabel,
}: {
sites: string[]
searchPlaceholder: string
resultCountTemplate: string
noResultsLabel: string
}) {
const [query, setQuery] = useState('')
const filtered = useMemo(() => {
const q = query.trim().toLowerCase()
if (!q) return sites
return sites.filter((site) => site.toLowerCase().includes(q))
}, [sites, query])
const grouped = useMemo(() => {
const map = new Map<string, string[]>()
for (const site of filtered) {
const first = site[0]?.toUpperCase() ?? '#'
const letter = /[A-Z]/.test(first) ? first : '#'
if (!map.has(letter)) map.set(letter, [])
map.get(letter)!.push(site)
}
return Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b))
}, [filtered])
return (
<div>
<div className="relative max-w-md mx-auto">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-slate-500">
<SearchIcon />
</span>
<input
type="search"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={searchPlaceholder}
className="w-full rounded-lg border border-gray-200 dark:border-slate-700 bg-white dark:bg-slate-900 pl-9 pr-4 py-2.5 text-sm text-gray-900 dark:text-gray-50 outline-none focus:ring-2 focus:ring-violet-500"
/>
</div>
<p className="mt-4 text-center text-sm text-gray-400 dark:text-slate-500">
{resultCountTemplate
.replace('{count}', String(filtered.length))
.replace('{total}', String(sites.length))}
</p>
{filtered.length === 0 ? (
<p className="mt-10 text-center text-gray-500 dark:text-gray-400">{noResultsLabel}</p>
) : (
<div className="mt-8 space-y-8">
{grouped.map(([letter, names]) => (
<div key={letter}>
<h2 className="text-sm font-bold text-violet-600 dark:text-violet-400 mb-3">{letter}</h2>
<div className="flex flex-wrap gap-2">
{names.map((name) => (
<span
key={name}
className="px-3 py-1.5 rounded-full border border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900 text-sm text-gray-700 dark:text-gray-300"
>
{name}
</span>
))}
</div>
</div>
))}
</div>
)}
</div>
)
}
+28
View File
@@ -0,0 +1,28 @@
export type DownloaderPlatform = {
id: string
slug: string
name: string
exampleUrl: string
}
export const DOWNLOADER_PLATFORMS: DownloaderPlatform[] = [
{ id: 'youtube', slug: 'youtube-downloader', name: 'YouTube', exampleUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ' },
{ id: 'tiktok', slug: 'tiktok-downloader', name: 'TikTok', exampleUrl: 'https://www.tiktok.com/@username/video/1234567890123456789' },
{ id: 'instagram', slug: 'instagram-downloader', name: 'Instagram', exampleUrl: 'https://www.instagram.com/reel/Cxxxxxxxxxx/' },
{ id: 'facebook', slug: 'facebook-downloader', name: 'Facebook', exampleUrl: 'https://www.facebook.com/watch/?v=1234567890123456' },
{ id: 'twitter', slug: 'twitter-downloader', name: 'X (Twitter)', exampleUrl: 'https://x.com/username/status/1234567890123456789' },
{ id: 'reddit', slug: 'reddit-downloader', name: 'Reddit', exampleUrl: 'https://www.reddit.com/r/videos/comments/abc123/example_post/' },
{ id: 'pinterest', slug: 'pinterest-downloader', name: 'Pinterest', exampleUrl: 'https://www.pinterest.com/pin/1234567890123456789/' },
{ id: 'vimeo', slug: 'vimeo-downloader', name: 'Vimeo', exampleUrl: 'https://vimeo.com/123456789' },
{ id: 'soundcloud', slug: 'soundcloud-downloader', name: 'SoundCloud', exampleUrl: 'https://soundcloud.com/artist/track-name' },
{ id: 'twitch', slug: 'twitch-downloader', name: 'Twitch', exampleUrl: 'https://www.twitch.tv/videos/1234567890' },
{ id: 'dailymotion', slug: 'dailymotion-downloader', name: 'Dailymotion', exampleUrl: 'https://www.dailymotion.com/video/x8abcde' },
{ id: 'linkedin', slug: 'linkedin-downloader', name: 'LinkedIn', exampleUrl: 'https://www.linkedin.com/posts/username_activity-1234567890123456789' },
{ id: 'tumblr', slug: 'tumblr-downloader', name: 'Tumblr', exampleUrl: 'https://username.tumblr.com/post/1234567890123' },
{ id: 'vk', slug: 'vk-downloader', name: 'VK', exampleUrl: 'https://vk.com/video-12345678_123456789' },
{ id: 'snapchat', slug: 'snapchat-downloader', name: 'Snapchat', exampleUrl: 'https://www.snapchat.com/spotlight/Xxxxxxxxxxxxxxxxxxxxxx' },
]
export function getDownloaderPlatformBySlug(slug: string): DownloaderPlatform | undefined {
return DOWNLOADER_PLATFORMS.find((platform) => platform.slug === slug)
}
File diff suppressed because it is too large Load Diff