Skip to content
Closed
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,7 @@ bun.lock
pnpm-lock.yaml

# wrangler
.wrangler/
.wrangler/

# pnpm workspace
pnpm-workspace.yaml
Binary file added public/404.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 2 additions & 9 deletions src/components/editor/CodeEditor.astro
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@ const { slug, initialCode = 'fn main() {\n println!("Hola, mundo!");\n}\n' }
import { basicSetup } from "codemirror";
import { indentWithTab } from "@codemirror/commands";
import { gruvbox } from "~/components/editor/theme";
import { db, editorCode } from "~/lib/stores/editor-store";
import { editorCode } from "~/lib/stores/editor-store";
import { rust } from "~/components/editor/config";

let editorView: EditorView | null = null;
let saveTimeout: number | undefined;

async function initEditor() {
const editorElement = document.getElementById("editor");
Expand All @@ -44,7 +43,7 @@ const { slug, initialCode = 'fn main() {\n println!("Hola, mundo!");\n}\n' }
initialCode: string;
};

const initialDoc = (await db.getCode(slug)) ?? initialCode;
const initialDoc = initialCode;

editorView = new EditorView({
state: EditorState.create({
Expand All @@ -58,11 +57,6 @@ const { slug, initialCode = 'fn main() {\n println!("Hola, mundo!");\n}\n' }
if (update.docChanged) {
const value = update.state.doc.toString();
editorCode.set(value);
clearTimeout(saveTimeout);
saveTimeout = window.setTimeout(
() => db.saveCode(slug, value),
500,
);
}
}),
],
Expand All @@ -89,7 +83,6 @@ const { slug, initialCode = 'fn main() {\n println!("Hola, mundo!");\n}\n' }
document.addEventListener("astro:page-load", initEditor);

document.addEventListener("astro:before-swap", () => {
clearTimeout(saveTimeout);
window.removeEventListener("editor:reset", handleReset);

if (editorView) {
Expand Down
23 changes: 13 additions & 10 deletions src/components/editor/output/Terminal.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
import { getLangFromUrl, useTranslations } from "~/i18n/utils"
import IconLoader2 from "~icons/tabler/loader-2"
import IconPlayerPlay from "~icons/tabler/player-play"
import IconRestore from "~icons/tabler/restore"
Expand All @@ -11,6 +12,8 @@ interface Props {

const { expectedOutput, testCode } = Astro.props
const isTestMode = !!testCode
const lang = getLangFromUrl(Astro.url)
const t = useTranslations(lang)
---

<terminal-output
Expand Down Expand Up @@ -41,22 +44,22 @@ const isTestMode = !!testCode
id="btn-reset"
class="btn-control hover:text-yellow"
type="button"
aria-label="Reset code"
aria-label={t("editor.reset")}
>
<IconRestore font-size={14} />
<span>Reiniciar</span>
<span>{t("editor.reset")}</span>
</button>

<button
id="btn-run"
class="btn-control hover:text-green-500"
type="button"
aria-label="Run code"
aria-label={t("editor.run")}
>
<IconPlayerPlay class="group-data-running:hidden" font-size={14} />
<IconLoader2 class="hidden animate-spin group-data-running:block" font-size={14} />
<span class="group-data-running:hidden">Ejecutar</span>
<span class="hidden group-data-running:inline">Ejecutando...</span>
<span class="group-data-running:hidden">{t("editor.run")}</span>
<span class="hidden group-data-running:inline">{t("editor.running")}</span>
</button>
</div>
</header>
Expand All @@ -78,22 +81,22 @@ const isTestMode = !!testCode
<button
id="btn-reset-mobile"
type="button"
aria-label="Reset code"
aria-label={t("editor.reset")}
class="flex-1 flex items-center justify-center gap-2 py-3 text-sm text-secondary/60 border-r border-stroke-color hover:bg-stroke-color/20 active:bg-stroke-color/40 transition-colors cursor-pointer touch-manipulation"
>
<IconRestore font-size={18} />
<span>Reiniciar</span>
<span>{t("editor.reset")}</span>
</button>
<button
id="btn-run-mobile"
type="button"
aria-label="Run code"
aria-label={t("editor.run")}
class="flex-1 flex items-center justify-center gap-2 py-3 text-sm text-green-400 hover:bg-green-500/10 active:bg-green-500/20 transition-colors cursor-pointer touch-manipulation"
>
<IconPlayerPlay class="group-data-running:hidden" font-size={18} />
<IconLoader2 class="hidden animate-spin group-data-running:block" font-size={18} />
<span class="group-data-running:hidden">Ejecutar</span>
<span class="hidden group-data-running:inline">Ejecutando...</span>
<span class="group-data-running:hidden">{t("editor.run")}</span>
<span class="hidden group-data-running:inline">{t("editor.running")}</span>
</button>
</div>
<div class="hidden items-center gap-2 border-t border-stroke-color px-3 py-2 group-data-running:flex">
Expand Down
29 changes: 28 additions & 1 deletion src/const/tracks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
import type { Track } from "~/entities/track"
import type { Track, TrackI18n } from "~/entities/track"

export const tracksI18n: Record<string, TrackI18n> = {
"fundamentos-de-rust": {
es: {
title: "Fundamentos de Rust",
description:
"Aprende Rust desde los fundamentos: sintaxis, ownership, borrowing y más. Ideal si nunca has programado en Rust.",
},
en: {
title: "Rust Fundamentals",
description:
"Learn Rust from the ground up: syntax, ownership, borrowing and more. Ideal if you've never programmed in Rust.",
},
},
"rust-avanzado-diseno-abstraccion": {
es: {
title: "Rust Avanzado: Diseño y Abstracción",
description:
"Profundiza en Rust con temas avanzados como lifetimes, traits, macros y programación asíncrona. Ideal para quienes ya conocen los fundamentos.",
},
en: {
title: "Advanced Rust: Design and Abstraction",
description:
"Deep dive into Rust with advanced topics like lifetimes, traits, macros and async programming. Ideal for those who already know the fundamentals.",
},
},
}

export const tracks: Track[] = [
{
Expand Down
11 changes: 11 additions & 0 deletions src/entities/track.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
export type TrackBadgeKey = "track.badge.beginner" | "track.badge.advanced"

export type TrackI18n = {
es: {
title: string
description: string
}
en: {
title: string
description: string
}
}

export type Track = {
id: string
title: string
Expand Down
17 changes: 11 additions & 6 deletions src/features/content/components/NavButtons.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
---
import { getLangFromUrl, useTranslations } from "~/i18n/utils"
import IconLeft from "~icons/tabler/arrow-big-left-lines-filled"
import IconRight from "~icons/tabler/arrow-big-right-lines-filled"

Expand All @@ -9,6 +10,7 @@ interface Props {
}

const { lang, previous, next } = Astro.props
const t = useTranslations(lang as "es" | "en")
const isNextDisabled = true

const btnBaseClass =
Expand All @@ -21,7 +23,7 @@ const iconBaseClass = "block h-6 w-6 rounded-lg transition-all duration-200 tran
<nav class="not-prose mt-auto flex w-full items-center justify-between pb-2" aria-label="Navegación de lecciones">
<a
href={previous ? `/${lang}/${previous.slug}` : undefined}
aria-label={previous ? `Anterior: ${previous.title}` : undefined}
aria-label={previous ? `${t("nav.previous")}: ${previous.title}` : undefined}
aria-disabled={!previous}
class={btnBaseClass}
>
Expand All @@ -30,21 +32,21 @@ const iconBaseClass = "block h-6 w-6 rounded-lg transition-all duration-200 tran
</span>
<div class="flex flex-col text-left">
<span class:list={["text-md font-medium", previous ? "text-yellow" : "text-[#ccc]"]}>
Anterior
{t("nav.previous")}
</span>
<span class="text-xs text-[#ccc]">{previous?.title || "-"}</span>
</div>
</a>
<a
id="next-lesson-btn"
href={next ? `/${lang}/${next.slug}` : undefined}
aria-label={next ? `Siguiente: ${next.title}` : undefined}
aria-label={next ? `${t("nav.next")}: ${next.title}` : undefined}
aria-disabled={isNextDisabled}
class={nextBtnClass}
>
<div class="flex flex-col text-right">
<span class:list={["text-md font-medium", next ? "text-yellow" : "text-[#ccc]"]}>
Siguiente
{t("nav.next")}
</span>
<span class="text-xs text-[#ccc]">{next?.title || "-"}</span>
</div>
Expand All @@ -57,6 +59,7 @@ const iconBaseClass = "block h-6 w-6 rounded-lg transition-all duration-200 tran
<script>
import { db } from "~/lib/stores/editor-store"
import { toast } from "~/utils/toasts"
import { useTranslations, getLangFromUrl } from "~/i18n/utils"

const initNextButton = async () => {
const btn = document.getElementById("next-lesson-btn") as HTMLAnchorElement | null
Expand All @@ -65,9 +68,11 @@ const iconBaseClass = "block h-6 w-6 rounded-lg transition-all duration-200 tran
btn.addEventListener("click", (e) => {
if (btn.ariaDisabled === "true") {
e.preventDefault()
const currentLang = getLangFromUrl(new URL(window.location.href))
const t = useTranslations(currentLang)
toast.info(
"Ejecuta el código",
"El código debe ser ejecutado al menos una vez para poder avanzar a la siguiente sección",
t("toast.run_required_title"),
t("toast.run_required_desc"),
)
}
})
Expand Down
4 changes: 2 additions & 2 deletions src/features/content/components/Navbar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ const sidebarItems = [...groupMap.values()].map(({ parent, children }) => ({
// Setup reactive navbar (this handles all user state changes automatically)
setupNavbar({
profileLinkId: "nav-profile-link",
loginLinkId: null, // This navbar doesn't have a login link
logoutLinkId: null // This navbar doesn't have a logout link directly
loginLinkId: undefined,
logoutLinkId: undefined
})

// Update username in profile link dynamically by targeting the span
Expand Down
2 changes: 1 addition & 1 deletion src/features/home/components/Community.astro
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface Props {
}

const { lang: langProp } = Astro.props
const lang = langProp ?? getLangFromUrl(Astro.url)
const lang = (langProp ?? getLangFromUrl(Astro.url)) as "es" | "en"
const t = useTranslations(lang)
---

Expand Down
2 changes: 1 addition & 1 deletion src/features/home/components/FAQ.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface Props {
}

const { lang: langProp } = Astro.props
const lang = langProp ?? getLangFromUrl(Astro.url)
const lang = (langProp ?? getLangFromUrl(Astro.url)) as "es" | "en"
const t = useTranslations(lang)

const faqs = [
Expand Down
2 changes: 1 addition & 1 deletion src/features/home/components/Features.astro
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface Props {
}

const { lang: langProp } = Astro.props
const lang = langProp ?? getLangFromUrl(Astro.url)
const lang = (langProp ?? getLangFromUrl(Astro.url)) as "es" | "en"
const t = useTranslations(lang)
---

Expand Down
2 changes: 1 addition & 1 deletion src/features/home/components/Hero.astro
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface Props {

const { lang: langProp } = Astro.props
const user = Astro.locals.user
const lang = langProp ?? getLangFromUrl(Astro.url)
const lang = (langProp ?? getLangFromUrl(Astro.url)) as "es" | "en"
const t = useTranslations(lang)
---

Expand Down
2 changes: 1 addition & 1 deletion src/features/home/components/HowItWorks.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ interface Props {
}

const { lang: langProp } = Astro.props
const lang = langProp ?? getLangFromUrl(Astro.url)
const lang = (langProp ?? getLangFromUrl(Astro.url)) as "es" | "en"
const t = useTranslations(lang)

const topics = [
Expand Down
2 changes: 1 addition & 1 deletion src/features/home/components/Playground.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface Props {
}

const { lang: langProp } = Astro.props
const lang = langProp ?? getLangFromUrl(Astro.url)
const lang = (langProp ?? getLangFromUrl(Astro.url)) as "es" | "en"
const t = useTranslations(lang)
---

Expand Down
28 changes: 28 additions & 0 deletions src/i18n/keys/en.keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,4 +364,32 @@ export const en_keys = {
"learn.lessons": "lessons",
"learn.progress_of": "of",
"learn.continue_label": "Continue:",

// Editor/Terminal buttons
"editor.reset": "Reset",
"editor.run": "Run",
"editor.running": "Running...",
"editor.output_label": "Output",

// Navigation buttons
"nav.previous": "Previous",
"nav.next": "Next",
"nav.content": "Content",
"nav.code": "Code",
"nav.terminal": "Terminal",

// Lesson page
"lesson.tab_content": "Content",
"lesson.tab_editor": "Editor",
"lesson.tab_terminal": "Terminal",

// Toasts
"toast.run_required_title": "Run the code",
"toast.run_required_desc": "Code must be run at least once before moving to the next section",
"toast.completed_title": "You did it! 🦀",
"toast.completed_desc": "You completed {trackTitle}. Find your certificate in your profile.",

// Auth
"auth.login_required_title": "Login required",
"auth.login_required_desc": "You must log in to access the course",
}
28 changes: 28 additions & 0 deletions src/i18n/keys/es.keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,4 +371,32 @@ export const es_keys = {
"learn.lessons": "lecciones",
"learn.progress_of": "de",
"learn.continue_label": "Continuar:",

// Editor/Terminal buttons
"editor.reset": "Reiniciar",
"editor.run": "Ejecutar",
"editor.running": "Ejecutando...",
"editor.output_label": "Salida",

// Navigation buttons
"nav.previous": "Anterior",
"nav.next": "Siguiente",
"nav.content": "Contenido",
"nav.code": "Código",
"nav.terminal": "Terminal",

// Lesson page
"lesson.tab_content": "Contenido",
"lesson.tab_editor": "Editor",
"lesson.tab_terminal": "Terminal",

// Toasts
"toast.run_required_title": "Ejecuta el código",
"toast.run_required_desc": "El código debe ser ejecutado al menos una vez para poder avanzar a la siguiente sección",
"toast.completed_title": "¡Lo lograste! 🦀",
"toast.completed_desc": "Completaste {trackTitle}. Encuentra tu certificado en tu perfil.",

// Auth
"auth.login_required_title": "Inicia sesión requerida",
"auth.login_required_desc": "Debes iniciar sesión para acceder al curso",
}
Loading
Loading