Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions src/components/AlgoViz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
getAlgorithmDescription,
getAlgorithmMetaTitle,
getAlgorithmMetaDescription,
defaultLocale,
locales,
} from '@i18n/translations'
import { algorithms, categories } from '@lib/algorithms'
import { usePlayback } from '@hooks/usePlayback'
Expand All @@ -26,13 +28,16 @@ const COLLAPSE_THRESHOLD = 100
const MOBILE_BREAKPOINT = 768

function getAlgorithmUrl(locale: string, algoId: string): string {
return locale === 'es' ? `/es/${algoId}` : `/${algoId}`
return locale === defaultLocale ? `/${algoId}` : `/${locale}/${algoId}`
}

function getAlgorithmIdFromPath(pathname: string): string | null {
const cleaned = pathname.replace(/\/$/, '')
if (cleaned === '' || cleaned === '/es') return null
if (cleaned.startsWith('/es/')) return cleaned.slice(4)
if (cleaned === '') return null
for (const locale of locales) {
if (cleaned === `/${locale}`) return null
if (cleaned.startsWith(`/${locale}/`)) return cleaned.slice(locale.length + 2)
}
return cleaned.slice(1)
}

Expand Down Expand Up @@ -249,7 +254,7 @@ export default function AlgoViz({ locale = 'en', initialAlgorithmId }: AlgoVizPr
style={{
width: sidebar.isDragging ? sidebar.width : sidebar.collapsed ? 0 : SIDEBAR_MAX,
}}
aria-label={locale === 'es' ? 'Categorías de algoritmos' : 'Algorithm categories'}
aria-label={t.sidebarAriaLabel}
aria-hidden={sidebar.collapsed}
inert={sidebar.collapsed || undefined}
>
Expand Down Expand Up @@ -314,19 +319,19 @@ export default function AlgoViz({ locale = 'en', initialAlgorithmId }: AlgoVizPr
className={`fixed top-0 left-0 bottom-0 w-[280px] bg-black z-50 border-r border-white/8 transition-transform duration-300 ease-in-out ${
mobileSidebarOpen ? 'translate-x-0' : '-translate-x-full'
}`}
aria-label={locale === 'es' ? 'Categorías de algoritmos' : 'Algorithm categories'}
aria-label={t.sidebarAriaLabel}
aria-hidden={!mobileSidebarOpen}
inert={!mobileSidebarOpen || undefined}
>
<div className="h-full flex flex-col">
<div className="flex items-center justify-between px-4 py-3 border-b border-white/8">
<span className="text-sm font-semibold text-white font-heading">
{locale === 'es' ? 'Algoritmos' : 'Algorithms'}
{t.mobileMenuTitle}
</span>
<button
onClick={() => setMobileSidebarOpen(false)}
className="w-7 h-7 flex items-center justify-center rounded-md hover:bg-white/6 text-neutral-400 hover:text-white transition-colors"
aria-label={locale === 'es' ? 'Cerrar menú' : 'Close menu'}
aria-label={t.closeMenu}
>
<svg
className="w-4 h-4"
Expand Down Expand Up @@ -412,7 +417,7 @@ export default function AlgoViz({ locale = 'en', initialAlgorithmId }: AlgoVizPr
? 0
: CODEPANEL_MAX,
}}
aria-label={locale === 'es' ? 'Panel de código y detalles' : 'Code and details panel'}
aria-label={t.codePanelAriaLabel}
aria-hidden={codePanel.collapsed}
inert={codePanel.collapsed || undefined}
>
Expand Down Expand Up @@ -464,19 +469,17 @@ export default function AlgoViz({ locale = 'en', initialAlgorithmId }: AlgoVizPr
className={`fixed top-0 right-0 bottom-0 w-[min(360px,90vw)] bg-black z-50 border-l border-white/8 transition-transform duration-300 ease-in-out ${
mobileCodePanelOpen ? 'translate-x-0' : 'translate-x-full'
}`}
aria-label={locale === 'es' ? 'Panel de código y detalles' : 'Code and details panel'}
aria-label={t.codePanelAriaLabel}
aria-hidden={!mobileCodePanelOpen}
inert={!mobileCodePanelOpen || undefined}
>
<div className="h-full flex flex-col">
<div className="flex items-center justify-between px-4 py-3 border-b border-white/8">
<span className="text-sm font-semibold text-white font-heading">
{locale === 'es' ? 'Código' : 'Code'}
</span>
<span className="text-sm font-semibold text-white font-heading">{t.tabCode}</span>
<button
onClick={() => setMobileCodePanelOpen(false)}
className="w-7 h-7 flex items-center justify-center rounded-md hover:bg-white/6 text-neutral-400 hover:text-white transition-colors"
aria-label={locale === 'es' ? 'Cerrar panel' : 'Close panel'}
aria-label={t.closePanel}
>
<svg
className="w-4 h-4"
Expand Down Expand Up @@ -519,7 +522,7 @@ export default function AlgoViz({ locale = 'en', initialAlgorithmId }: AlgoVizPr
<button
onClick={() => setMobileSidebarOpen(true)}
className="w-8 h-8 flex items-center justify-center rounded-md hover:bg-white/6 text-neutral-400 hover:text-white transition-colors shrink-0"
aria-label={locale === 'es' ? 'Abrir menú' : 'Open menu'}
aria-label={t.openMenu}
>
<svg
className="w-4 h-4"
Expand Down Expand Up @@ -607,7 +610,7 @@ export default function AlgoViz({ locale = 'en', initialAlgorithmId }: AlgoVizPr
<button
onClick={() => setMobileCodePanelOpen(true)}
className="w-8 h-8 flex items-center justify-center rounded-md hover:bg-white/6 text-neutral-400 hover:text-white transition-colors shrink-0"
aria-label={locale === 'es' ? 'Ver código' : 'View code'}
aria-label={t.viewCode}
>
<svg
className="w-4 h-4"
Expand Down
44 changes: 36 additions & 8 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,21 @@ export default function Header({
<button
onClick={onToggleMobileSidebar}
className="flex items-center justify-center w-7 h-7 rounded-md hover:bg-white/6 transition-colors text-neutral-400 hover:text-white shrink-0"
aria-label={locale === 'es' ? 'Abrir menú' : 'Open menu'}
aria-label={t.openMenu}
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2} aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
/>
</svg>
</button>
) : sidebarCollapsed ? (
Expand Down Expand Up @@ -129,7 +140,10 @@ export default function Header({
)}
</a>
{selectedAlgorithm && (
<nav aria-label="Breadcrumb" className="flex items-center gap-1.5 min-w-0 overflow-hidden">
<nav
aria-label="Breadcrumb"
className="flex items-center gap-1.5 min-w-0 overflow-hidden"
>
<span className="text-neutral-600 shrink-0">/</span>
<span className="text-xs text-neutral-500 hidden md:inline shrink-0">
{getCategoryName(locale, selectedAlgorithm.category)}
Expand Down Expand Up @@ -167,10 +181,21 @@ export default function Header({
<button
onClick={onToggleMobileCodePanel}
className="flex items-center justify-center w-7 h-7 rounded-md hover:bg-white/6 transition-colors text-neutral-400 hover:text-white"
aria-label={locale === 'es' ? 'Ver código' : 'View code'}
aria-label={t.viewCode}
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2} aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5" />
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M17.25 6.75L22.5 12l-5.25 5.25m-10.5 0L1.5 12l5.25-5.25m7.5-3l-4.5 16.5"
/>
</svg>
</button>
)}
Expand All @@ -196,7 +221,10 @@ export default function Header({
</svg>
</button>
)}
<nav aria-label={locale === 'es' ? 'Idioma' : 'Language'} className="flex items-center gap-0.5 bg-white/6 rounded-lg p-0.5 border border-white/8">
<nav
aria-label={t.languageLabel}
className="flex items-center gap-0.5 bg-white/6 rounded-lg p-0.5 border border-white/8"
>
{locales.map((l) => (
<a
key={l}
Expand Down
54 changes: 43 additions & 11 deletions src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ export interface Translations {
resizeSidebar: string
resizeCodePanel: string

// Mobile / aria labels
sidebarAriaLabel: string
codePanelAriaLabel: string
mobileMenuTitle: string
openMenu: string
closeMenu: string
closePanel: string
viewCode: string
languageLabel: string

// Graph visualizer
queue: string
stack: string
Expand Down Expand Up @@ -102,6 +112,15 @@ export const translations: Record<Locale, Translations> = {
resizeSidebar: 'Resize sidebar',
resizeCodePanel: 'Resize code panel',

sidebarAriaLabel: 'Algorithm categories',
codePanelAriaLabel: 'Code and details panel',
mobileMenuTitle: 'Algorithms',
openMenu: 'Open menu',
closeMenu: 'Close menu',
closePanel: 'Close panel',
viewCode: 'View code',
languageLabel: 'Language',

queue: 'Queue',
stack: 'Stack',
empty: 'empty',
Expand Down Expand Up @@ -141,7 +160,7 @@ Rules of Big O:
2. Drop lower-order terms: O(n² + n) → O(n²)
3. Focus on the dominant term as n grows large`,

'recursion': `Recursion
recursion: `Recursion

Recursion is a programming technique where a function calls itself to solve smaller instances of the same problem. It's one of the most powerful concepts in computer science.

Expand All @@ -168,7 +187,7 @@ Pitfalls:
Recursive algorithms in this visualizer:
Quick Sort, Merge Sort, DFS, N-Queens, Sudoku Solver, Tower of Hanoi`,

'stack': `Stack
stack: `Stack

A Stack is a linear data structure that follows the LIFO principle — Last In, First Out. Like a stack of plates: you add and remove from the top only.

Expand All @@ -188,7 +207,7 @@ Applications:

Space Complexity: O(n) for n elements`,

'queue': `Queue
queue: `Queue

A Queue is a linear data structure that follows the FIFO principle — First In, First Out. Like a line at a store: the first person in line is served first.

Expand Down Expand Up @@ -263,7 +282,7 @@ Examples:
O(n): Merge Sort (temporary arrays), hash tables
O(n²): DP tables, adjacency matrices`,

'memoization': `Memoization
memoization: `Memoization

Memoization is an optimization technique that stores the results of expensive function calls and returns the cached result when the same inputs occur again.

Expand Down Expand Up @@ -364,7 +383,7 @@ Where h = height of the tree:

Applications: ordered data storage, range queries, priority queues (with balancing)`,

'heap': `Heap (Min Heap)
heap: `Heap (Min Heap)

A Heap is a complete binary tree where every parent is smaller (min-heap) or larger (max-heap) than its children. It's stored as an array.

Expand Down Expand Up @@ -1034,6 +1053,15 @@ The puzzle was invented by mathematician Édouard Lucas in 1883. Legend says mon
resizeSidebar: 'Redimensionar barra lateral',
resizeCodePanel: 'Redimensionar panel de código',

sidebarAriaLabel: 'Categorías de algoritmos',
codePanelAriaLabel: 'Panel de código y detalles',
mobileMenuTitle: 'Algoritmos',
openMenu: 'Abrir menú',
closeMenu: 'Cerrar menú',
closePanel: 'Cerrar panel',
viewCode: 'Ver código',
languageLabel: 'Idioma',

queue: 'Cola',
stack: 'Pila',
empty: 'vacía',
Expand Down Expand Up @@ -1073,7 +1101,7 @@ Reglas de Big O:
2. Eliminar términos de menor orden: O(n² + n) → O(n²)
3. Enfocarse en el término dominante cuando n crece`,

'recursion': `Recursión
recursion: `Recursión

La recursión es una técnica de programación donde una función se llama a sí misma para resolver instancias más pequeñas del mismo problema. Es uno de los conceptos más poderosos en las ciencias de la computación.

Expand All @@ -1100,7 +1128,7 @@ Errores comunes:
Algoritmos recursivos en este visualizador:
Quick Sort, Merge Sort, DFS, N-Queens, Sudoku Solver, Torre de Hanoi`,

'stack': `Pila (Stack)
stack: `Pila (Stack)

Una Pila es una estructura de datos lineal que sigue el principio LIFO — Último en Entrar, Primero en Salir. Como una pila de platos: solo puedes añadir y quitar del tope.

Expand All @@ -1120,7 +1148,7 @@ Aplicaciones:

Complejidad Espacial: O(n) para n elementos`,

'queue': `Cola (Queue)
queue: `Cola (Queue)

Una Cola es una estructura de datos lineal que sigue el principio FIFO — Primero en Entrar, Primero en Salir. Como una fila en una tienda: el primero en llegar es atendido primero.

Expand Down Expand Up @@ -1195,7 +1223,7 @@ Ejemplos:
O(n): Merge Sort (arreglos temporales), tablas hash
O(n²): tablas de DP, matrices de adyacencia`,

'memoization': `Memoización
memoization: `Memoización

La memoización es una técnica de optimización que almacena los resultados de llamadas a funciones costosas y devuelve el resultado cacheado cuando se repiten las mismas entradas.

Expand Down Expand Up @@ -1296,7 +1324,7 @@ Donde h = altura del árbol:

Aplicaciones: almacenamiento de datos ordenados, consultas por rango, colas de prioridad (con balanceo)`,

'heap': `Montículo (Heap)
heap: `Montículo (Heap)

Un Heap es un árbol binario completo donde cada padre es menor (min-heap) o mayor (max-heap) que sus hijos. Se almacena como un arreglo.

Expand Down Expand Up @@ -1941,7 +1969,11 @@ export function getCategoryName(locale: Locale, categoryKey: string): string {
return translations[locale].categories[categoryKey] || categoryKey
}

export function getAlgorithmMetaTitle(locale: Locale, algorithmId: string, fallbackName: string): string {
export function getAlgorithmMetaTitle(
locale: Locale,
algorithmId: string,
fallbackName: string,
): string {
const desc = translations[locale].algorithmDescriptions[algorithmId]
if (!desc) return `${fallbackName} | alg0.dev`
const firstLine = desc.split('\n')[0].trim()
Expand Down
10 changes: 5 additions & 5 deletions src/pages/[algorithm].astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import Layout from "@layouts/Layout.astro"
import AlgoViz from "@components/AlgoViz"
import { algorithms } from "@lib/algorithms"
import { getAlgorithmMetaTitle, getAlgorithmMetaDescription } from "@i18n/translations"
import { getAlgorithmMetaTitle, getAlgorithmMetaDescription, defaultLocale } from "@i18n/translations"
import type { GetStaticPaths } from "astro"

export const getStaticPaths: GetStaticPaths = () => {
Expand All @@ -14,10 +14,10 @@ export const getStaticPaths: GetStaticPaths = () => {
const { algorithm } = Astro.params as { algorithm: string }
const algo = algorithms.find((a) => a.id === algorithm)!

const title = getAlgorithmMetaTitle("en", algo.id, algo.name)
const description = getAlgorithmMetaDescription("en", algo.id)
const title = getAlgorithmMetaTitle(defaultLocale, algo.id, algo.name)
const description = getAlgorithmMetaDescription(defaultLocale, algo.id)
---

<Layout locale="en" title={title} description={description}>
<AlgoViz locale="en" initialAlgorithmId={algorithm} client:load />
<Layout locale={defaultLocale} title={title} description={description}>
<AlgoViz locale={defaultLocale} initialAlgorithmId={algo.id} client:load />
</Layout>
27 changes: 27 additions & 0 deletions src/pages/[lang]/[algorithm].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
import Layout from "@layouts/Layout.astro"
import AlgoViz from "@components/AlgoViz"
import { algorithms } from "@lib/algorithms"
import { getAlgorithmMetaTitle, getAlgorithmMetaDescription, type Locale, locales, defaultLocale } from "@i18n/translations"
import type { GetStaticPaths } from "astro"

export const getStaticPaths: GetStaticPaths = () => {
return locales
.filter((lang) => lang !== defaultLocale)
.flatMap((lang) =>
algorithms.map((algo) => ({
params: { lang, algorithm: algo.id },
}))
)
}

const { algorithm, lang } = Astro.params as { algorithm: string, lang: Locale }
const algo = algorithms.find((a) => a.id === algorithm)!

const title = getAlgorithmMetaTitle(lang, algo.id, algo.name)
const description = getAlgorithmMetaDescription(lang, algo.id)
---

<Layout locale={lang} title={title} description={description}>
<AlgoViz locale={lang} initialAlgorithmId={algo.id} client:load />
</Layout>
Loading