From 43c563ed6460a1c9ed221d23b78edb236147f69c Mon Sep 17 00:00:00 2001 From: Marcelo Santos <117441129+marcelo-m7@users.noreply.github.com> Date: Fri, 19 Sep 2025 17:07:33 +0100 Subject: [PATCH] Enable multilingual Hugo structure and localization --- README.md | 34 +- config/_default/hugo.toml | 2 +- config/_default/languages.toml | 26 +- config/_default/menus/menus.en.toml | 2 +- config/_default/menus/menus.es.toml | 9 + config/_default/menus/menus.fr.toml | 9 + config/_default/menus/menus.pt.toml | 2 +- config/_default/params.toml | 47 -- content/en/_index.md | 6 +- content/es/_index.md | 9 + content/fr/_index.md | 9 + content/{ => pt}/_index.md | 0 .../{ => pt}/courses/LESTI/2024-2025/index.md | 0 .../uc/LESTI-ALG1/estruturas-de-dados.md | 0 .../LESTI/2024-2025/uc/LESTI-ALG1/index.md | 0 i18n/en.toml | 116 +++++ i18n/es.toml | 116 +++++ i18n/fr.toml | 116 +++++ i18n/pt.toml | 116 +++++ layouts/_default/baseof.html | 2 +- .../footer/script-footer-custom.html | 8 +- layouts/course/single.html | 24 +- layouts/home.html | 10 +- layouts/topic/single.html | 14 +- layouts/uc/single.html | 30 +- netlify.toml | 2 +- scripts/sync-content.mjs | 238 ++++++++--- scripts/validate-frontmatter.js | 25 +- static/js/loaders.js | 403 +++++++++++++++--- 29 files changed, 1146 insertions(+), 229 deletions(-) create mode 100644 config/_default/menus/menus.es.toml create mode 100644 config/_default/menus/menus.fr.toml create mode 100644 content/es/_index.md create mode 100644 content/fr/_index.md rename content/{ => pt}/_index.md (100%) rename content/{ => pt}/courses/LESTI/2024-2025/index.md (100%) rename content/{ => pt}/courses/LESTI/2024-2025/uc/LESTI-ALG1/estruturas-de-dados.md (100%) rename content/{ => pt}/courses/LESTI/2024-2025/uc/LESTI-ALG1/index.md (100%) create mode 100644 i18n/en.toml create mode 100644 i18n/es.toml create mode 100644 i18n/fr.toml create mode 100644 i18n/pt.toml diff --git a/README.md b/README.md index 61ec4ed4..d9afee3c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ A **FACODI** é uma plataforma de aprendizagem aberta que replica planos curricu - 🎓 Unidades curriculares com ementa, resultados de aprendizagem e tópicos - 🎥 Playlists abertas do YouTube organizadas por prioridade - 📝 Conteúdo em Markdown sincronizado automaticamente com o Supabase -- 🌐 Multilíngue (PT como padrão, EN preparado como fallback) +- 🌐 Multilíngue (PT, EN, ES e FR) --- @@ -25,6 +25,16 @@ A **FACODI** é uma plataforma de aprendizagem aberta que replica planos curricu --- +## 🧭 Separação de responsabilidades + +- **Hugo (`facodi.pt`)**: mantém o catálogo estático em Markdown, gera as páginas multilíngues e apenas injeta as variáveis públicas do Supabase recebidas no ambiente de deploy. +- **Supabase**: armazena o conteúdo normalizado (cursos, UCs, tópicos, playlists) e expõe APIs autenticadas por `anon`/`service`. As chaves continuam definidas exclusivamente como _secrets_. +- **Portal Next.js (futuro)**: consumirá esses dados de forma dinâmica, com autenticação e experiências interativas sem misturar runtime React com o build do Hugo. + +Essa divisão garante que o catálogo estático permaneça simples, seguro e versionável, enquanto o portal evolui com funcionalidades ricas sobre a mesma base de dados. + +--- + ## 📂 Estrutura do repositório ```bash @@ -33,15 +43,19 @@ facodi.pt/ ├─ config/ │ └─ _default/ ├─ content/ -│ ├─ _index.md -│ └─ courses/ -│ └─ LESTI/ -│ └─ 2024-2025/ -│ ├─ index.md # Curso -│ └─ uc/ -│ └─ LESTI-ALG1/ -│ ├─ index.md # Unidade curricular -│ └─ estruturas-de-dados.md # Tópico +│ ├─ pt/ +│ │ ├─ _index.md +│ │ └─ courses/ +│ │ └─ LESTI/ +│ │ └─ 2024-2025/ +│ │ ├─ index.md # Curso +│ │ └─ uc/ +│ │ └─ LESTI-ALG1/ +│ │ ├─ index.md # Unidade curricular +│ │ └─ estruturas-de-dados.md # Tópico +│ ├─ en/_index.md +│ ├─ es/_index.md +│ └─ fr/_index.md ├─ layouts/ │ ├─ _default/baseof.html │ ├─ course/single.html diff --git a/config/_default/hugo.toml b/config/_default/hugo.toml index 72679145..09ac7205 100644 --- a/config/_default/hugo.toml +++ b/config/_default/hugo.toml @@ -10,7 +10,7 @@ rssLimit = 10 summarylength = 40 defaultContentLanguage = "pt" -defaultContentLanguageInSubdir = false +defaultContentLanguageInSubdir = true copyRight = "FACODI — Conteúdo aberto para aprendizagem comunitária" diff --git a/config/_default/languages.toml b/config/_default/languages.toml index 8846aa2e..3bdef4cf 100644 --- a/config/_default/languages.toml +++ b/config/_default/languages.toml @@ -1,7 +1,8 @@ [pt] languageName = "Português" - contentDir = "content" + contentDir = "content/pt" weight = 1 + languageCode = "pt-PT" [pt.params] languageISO = "PT" languageTag = "pt-PT" @@ -11,7 +12,28 @@ languageName = "English" contentDir = "content/en" weight = 2 + languageCode = "en-US" [en.params] languageISO = "EN" languageTag = "en-US" - footer = 'Built by the FACODI community. English content coming soon.' + footer = 'Built by the FACODI community with Hugo and Supabase.' + +[es] + languageName = "Español" + contentDir = "content/es" + weight = 3 + languageCode = "es-ES" + [es.params] + languageISO = "ES" + languageTag = "es-ES" + footer = 'Creado por la comunidad FACODI con Hugo y Supabase.' + +[fr] + languageName = "Français" + contentDir = "content/fr" + weight = 4 + languageCode = "fr-FR" + [fr.params] + languageISO = "FR" + languageTag = "fr-FR" + footer = 'Construit par la communauté FACODI avec Hugo et Supabase.' diff --git a/config/_default/menus/menus.en.toml b/config/_default/menus/menus.en.toml index 91a65ca0..3d844e4b 100644 --- a/config/_default/menus/menus.en.toml +++ b/config/_default/menus/menus.en.toml @@ -1,6 +1,6 @@ [[main]] name = "Courses" - url = "/" + pageRef = "/" weight = 10 [[footer]] diff --git a/config/_default/menus/menus.es.toml b/config/_default/menus/menus.es.toml new file mode 100644 index 00000000..0be26324 --- /dev/null +++ b/config/_default/menus/menus.es.toml @@ -0,0 +1,9 @@ +[[main]] + name = "Cursos" + pageRef = "/" + weight = 10 + +[[footer]] + name = "Código abierto" + url = "https://github.com/Monynha-Softwares/facodi.pt" + weight = 10 diff --git a/config/_default/menus/menus.fr.toml b/config/_default/menus/menus.fr.toml new file mode 100644 index 00000000..f3723f5e --- /dev/null +++ b/config/_default/menus/menus.fr.toml @@ -0,0 +1,9 @@ +[[main]] + name = "Cours" + pageRef = "/" + weight = 10 + +[[footer]] + name = "Code ouvert" + url = "https://github.com/Monynha-Softwares/facodi.pt" + weight = 10 diff --git a/config/_default/menus/menus.pt.toml b/config/_default/menus/menus.pt.toml index b3e88ccb..65163256 100644 --- a/config/_default/menus/menus.pt.toml +++ b/config/_default/menus/menus.pt.toml @@ -1,6 +1,6 @@ [[main]] name = "Cursos" - url = "/" + pageRef = "/" weight = 10 [[footer]] diff --git a/config/_default/params.toml b/config/_default/params.toml index 1ce8f688..8e4425fb 100644 --- a/config/_default/params.toml +++ b/config/_default/params.toml @@ -14,58 +14,11 @@ mainSections = ["courses"] colorModeToggler = true navbarSticky = true containerBreakpoint = "lg" - - navBarButton = false - navBarButtonUrl = "/" - navBarButtonText = "" - - flexSearch = false - searchExclKinds = [] - searchExclTypes = [] - showSearch = [] - indexSummary = false - - showDate = false - showSummary = true - searchLimit = 50 - - alert = false - alertDismissable = true - - bootstrapJavascript = true - - sectionNav = ["courses"] toTopButton = true - breadcrumbTrail = false - headlineHash = true scrollSpy = false - multilingualMode = true showMissingLanguages = false - docsVersioning = false - docsVersion = "1.0" - - headerBar = false - backgroundDots = false - - sectionFooter = false - relatedPosts = false - imageList = false - imageSingle = true - - editPage = false - lastMod = false - repoHost = "GitHub" - docsRepo = "https://github.com/Monynha-Softwares/facodi.pt" - docsRepoBranch = "main" - docsRepoSubPath = "" - - krokiURL = "https://kroki.io" - -[supabase] - url = "" - anon_key = "" [seo] [seo.title] diff --git a/content/en/_index.md b/content/en/_index.md index 41521c1f..1eb47dfe 100644 --- a/content/en/_index.md +++ b/content/en/_index.md @@ -1,7 +1,9 @@ --- title: "FACODI Catalog" +lead: "Open courses inspired by the University of Algarve curricula." layout: home -draft: true --- -FACODI content is primarily authored in Portuguese. English translations will be published as they become available. +FACODI is a community-led initiative that curates open courses based on the official curricula from UAlg. + +Explore the published courses and dive into each unit to see syllabi, learning outcomes, and curated playlists. diff --git a/content/es/_index.md b/content/es/_index.md new file mode 100644 index 00000000..2192255e --- /dev/null +++ b/content/es/_index.md @@ -0,0 +1,9 @@ +--- +title: "Catálogo FACODI" +lead: "Cursos abiertos inspirados en los planes de estudios de la Universidad del Algarve." +layout: home +--- + +FACODI es una iniciativa comunitaria que organiza cursos abiertos basados en los planes oficiales de la UAlg. + +Descubre los cursos disponibles y explora cada unidad curricular para consultar programas, resultados de aprendizaje y playlists recomendadas. diff --git a/content/fr/_index.md b/content/fr/_index.md new file mode 100644 index 00000000..e598dcb8 --- /dev/null +++ b/content/fr/_index.md @@ -0,0 +1,9 @@ +--- +title: "Catalogue FACODI" +lead: "Cours ouverts inspirés des plans d'études de l'Université de l'Algarve." +layout: home +--- + +FACODI est une initiative communautaire qui propose des cours ouverts basés sur les programmes officiels de l'UAlg. + +Parcourez les cours disponibles et explorez chaque unité d'enseignement pour consulter les plans de cours, les résultats d'apprentissage et les playlists recommandées. diff --git a/content/_index.md b/content/pt/_index.md similarity index 100% rename from content/_index.md rename to content/pt/_index.md diff --git a/content/courses/LESTI/2024-2025/index.md b/content/pt/courses/LESTI/2024-2025/index.md similarity index 100% rename from content/courses/LESTI/2024-2025/index.md rename to content/pt/courses/LESTI/2024-2025/index.md diff --git a/content/courses/LESTI/2024-2025/uc/LESTI-ALG1/estruturas-de-dados.md b/content/pt/courses/LESTI/2024-2025/uc/LESTI-ALG1/estruturas-de-dados.md similarity index 100% rename from content/courses/LESTI/2024-2025/uc/LESTI-ALG1/estruturas-de-dados.md rename to content/pt/courses/LESTI/2024-2025/uc/LESTI-ALG1/estruturas-de-dados.md diff --git a/content/courses/LESTI/2024-2025/uc/LESTI-ALG1/index.md b/content/pt/courses/LESTI/2024-2025/uc/LESTI-ALG1/index.md similarity index 100% rename from content/courses/LESTI/2024-2025/uc/LESTI-ALG1/index.md rename to content/pt/courses/LESTI/2024-2025/uc/LESTI-ALG1/index.md diff --git a/i18n/en.toml b/i18n/en.toml new file mode 100644 index 00000000..06efa657 --- /dev/null +++ b/i18n/en.toml @@ -0,0 +1,116 @@ +[course_plan_short] +other = "Plan {{ .plan }}" + +[course_ects_short] +other = "{{ .ects }} ECTS" + +[course_duration_short] +other = "{{ .count }} semesters" + +[course_summary_pending] +other = "Documentation in progress." + +[home_no_courses] +other = "No courses published yet. Check back soon!" + +[course_header_label] +other = "Course {{ .code }}" + +[course_plan_caption] +other = "Curricular plan {{ .plan }}" + +[course_synced_title] +other = "Synchronized content" + +[course_synced_loading] +other = "Loading detailed description from Supabase…" + +[course_units_title] +other = "Curricular units" + +[course_units_loading] +other = "Loading curricular units…" + +[course_details_title] +other = "Course details" + +[course_details_institution] +other = "Institution" + +[course_details_school] +other = "School" + +[course_details_ects] +other = "ECTS" + +[course_details_duration] +other = "Duration" + +[course_details_language] +other = "Language" + +[uc_header_label] +other = "Curricular unit {{ .code }}" + +[uc_official_title] +other = "Official content" + +[uc_official_loading] +other = "Syncing syllabus and materials…" + +[uc_outcomes_title] +other = "Learning outcomes" + +[uc_outcomes_loading] +other = "Loading outcomes…" + +[uc_playlists_title] +other = "Recommended playlists" + +[uc_playlists_loading] +other = "Loading playlists…" + +[uc_topics_title] +other = "Associated topics" + +[uc_topics_loading] +other = "Loading topics…" + +[uc_details_title] +other = "Information" + +[uc_details_course] +other = "Course" + +[uc_details_ects] +other = "ECTS" + +[uc_details_semester] +other = "Semester" + +[uc_details_language] +other = "Language" + +[uc_details_prerequisites] +other = "Prerequisites" + +[topic_header_label] +other = "Topic {{ .slug }}" + +[topic_synced_title] +other = "Synchronized content" + +[topic_synced_loading] +other = "Loading Markdown content…" + +[topic_playlists_title] +other = "Related playlists" + +[topic_playlists_loading] +other = "Loading playlists…" + +[topic_tags_title] +other = "Tags" + +[topic_tags_loading] +other = "Loading tags…" diff --git a/i18n/es.toml b/i18n/es.toml new file mode 100644 index 00000000..ce7963e6 --- /dev/null +++ b/i18n/es.toml @@ -0,0 +1,116 @@ +[course_plan_short] +other = "Plan {{ .plan }}" + +[course_ects_short] +other = "{{ .ects }} ECTS" + +[course_duration_short] +other = "{{ .count }} semestres" + +[course_summary_pending] +other = "Documentación en preparación." + +[home_no_courses] +other = "Todavía no hay cursos publicados. ¡Vuelve pronto!" + +[course_header_label] +other = "Curso {{ .code }}" + +[course_plan_caption] +other = "Plan curricular {{ .plan }}" + +[course_synced_title] +other = "Contenido sincronizado" + +[course_synced_loading] +other = "Cargando descripción detallada desde Supabase…" + +[course_units_title] +other = "Unidades curriculares" + +[course_units_loading] +other = "Cargando unidades curriculares…" + +[course_details_title] +other = "Detalles del curso" + +[course_details_institution] +other = "Institución" + +[course_details_school] +other = "Escuela" + +[course_details_ects] +other = "ECTS" + +[course_details_duration] +other = "Duración" + +[course_details_language] +other = "Idioma" + +[uc_header_label] +other = "Unidad curricular {{ .code }}" + +[uc_official_title] +other = "Contenido oficial" + +[uc_official_loading] +other = "Sincronizando programa y materiales…" + +[uc_outcomes_title] +other = "Resultados de aprendizaje" + +[uc_outcomes_loading] +other = "Cargando resultados…" + +[uc_playlists_title] +other = "Playlists recomendadas" + +[uc_playlists_loading] +other = "Cargando playlists…" + +[uc_topics_title] +other = "Temas asociados" + +[uc_topics_loading] +other = "Cargando temas…" + +[uc_details_title] +other = "Información" + +[uc_details_course] +other = "Curso" + +[uc_details_ects] +other = "ECTS" + +[uc_details_semester] +other = "Semestre" + +[uc_details_language] +other = "Idioma" + +[uc_details_prerequisites] +other = "Requisitos previos" + +[topic_header_label] +other = "Tema {{ .slug }}" + +[topic_synced_title] +other = "Contenido sincronizado" + +[topic_synced_loading] +other = "Cargando contenido Markdown…" + +[topic_playlists_title] +other = "Playlists relacionadas" + +[topic_playlists_loading] +other = "Cargando playlists…" + +[topic_tags_title] +other = "Etiquetas" + +[topic_tags_loading] +other = "Cargando etiquetas…" diff --git a/i18n/fr.toml b/i18n/fr.toml new file mode 100644 index 00000000..26fc6e11 --- /dev/null +++ b/i18n/fr.toml @@ -0,0 +1,116 @@ +[course_plan_short] +other = "Plan {{ .plan }}" + +[course_ects_short] +other = "{{ .ects }} ECTS" + +[course_duration_short] +other = "{{ .count }} semestres" + +[course_summary_pending] +other = "Documentation en préparation." + +[home_no_courses] +other = "Aucun cours publié pour le moment. Revenez bientôt !" + +[course_header_label] +other = "Cours {{ .code }}" + +[course_plan_caption] +other = "Plan d'études {{ .plan }}" + +[course_synced_title] +other = "Contenu synchronisé" + +[course_synced_loading] +other = "Chargement de la description détaillée depuis Supabase…" + +[course_units_title] +other = "Unités d'enseignement" + +[course_units_loading] +other = "Chargement des unités d'enseignement…" + +[course_details_title] +other = "Détails du cours" + +[course_details_institution] +other = "Institution" + +[course_details_school] +other = "École" + +[course_details_ects] +other = "ECTS" + +[course_details_duration] +other = "Durée" + +[course_details_language] +other = "Langue" + +[uc_header_label] +other = "Unité d'enseignement {{ .code }}" + +[uc_official_title] +other = "Contenu officiel" + +[uc_official_loading] +other = "Synchronisation du programme et des ressources…" + +[uc_outcomes_title] +other = "Résultats d'apprentissage" + +[uc_outcomes_loading] +other = "Chargement des résultats…" + +[uc_playlists_title] +other = "Playlists recommandées" + +[uc_playlists_loading] +other = "Chargement des playlists…" + +[uc_topics_title] +other = "Sujets associés" + +[uc_topics_loading] +other = "Chargement des sujets…" + +[uc_details_title] +other = "Informations" + +[uc_details_course] +other = "Cours" + +[uc_details_ects] +other = "ECTS" + +[uc_details_semester] +other = "Semestre" + +[uc_details_language] +other = "Langue" + +[uc_details_prerequisites] +other = "Prérequis" + +[topic_header_label] +other = "Sujet {{ .slug }}" + +[topic_synced_title] +other = "Contenu synchronisé" + +[topic_synced_loading] +other = "Chargement du contenu Markdown…" + +[topic_playlists_title] +other = "Playlists associées" + +[topic_playlists_loading] +other = "Chargement des playlists…" + +[topic_tags_title] +other = "Étiquettes" + +[topic_tags_loading] +other = "Chargement des étiquettes…" diff --git a/i18n/pt.toml b/i18n/pt.toml new file mode 100644 index 00000000..262a6b7b --- /dev/null +++ b/i18n/pt.toml @@ -0,0 +1,116 @@ +[course_plan_short] +other = "Plano {{ .plan }}" + +[course_ects_short] +other = "{{ .ects }} ECTS" + +[course_duration_short] +other = "{{ .count }} semestres" + +[course_summary_pending] +other = "Documentação em preparação." + +[home_no_courses] +other = "Nenhum curso publicado ainda. Volte em breve!" + +[course_header_label] +other = "Curso {{ .code }}" + +[course_plan_caption] +other = "Plano curricular {{ .plan }}" + +[course_synced_title] +other = "Conteúdo sincronizado" + +[course_synced_loading] +other = "Carregando descrição detalhada a partir do Supabase…" + +[course_units_title] +other = "Unidades curriculares" + +[course_units_loading] +other = "Carregando unidades curriculares…" + +[course_details_title] +other = "Detalhes do curso" + +[course_details_institution] +other = "Instituição" + +[course_details_school] +other = "Escola" + +[course_details_ects] +other = "ECTS" + +[course_details_duration] +other = "Duração" + +[course_details_language] +other = "Idioma" + +[uc_header_label] +other = "Unidade curricular {{ .code }}" + +[uc_official_title] +other = "Conteúdo oficial" + +[uc_official_loading] +other = "Sincronizando ementa e materiais…" + +[uc_outcomes_title] +other = "Resultados de aprendizagem" + +[uc_outcomes_loading] +other = "Carregando resultados…" + +[uc_playlists_title] +other = "Playlists recomendadas" + +[uc_playlists_loading] +other = "Carregando playlists…" + +[uc_topics_title] +other = "Tópicos associados" + +[uc_topics_loading] +other = "Carregando tópicos…" + +[uc_details_title] +other = "Informações" + +[uc_details_course] +other = "Curso" + +[uc_details_ects] +other = "ECTS" + +[uc_details_semester] +other = "Semestre" + +[uc_details_language] +other = "Idioma" + +[uc_details_prerequisites] +other = "Pré-requisitos" + +[topic_header_label] +other = "Tópico {{ .slug }}" + +[topic_synced_title] +other = "Conteúdo sincronizado" + +[topic_synced_loading] +other = "Carregando conteúdo Markdown…" + +[topic_playlists_title] +other = "Playlists relacionadas" + +[topic_playlists_loading] +other = "Carregando playlists…" + +[topic_tags_title] +other = "Etiquetas" + +[topic_tags_loading] +other = "Carregando tags…" diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index a98637f9..6f840a0e 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -1,5 +1,5 @@ - + {{ partial "head/head" . }} {{ partial "head/body-class" . }}
diff --git a/layouts/_partials/footer/script-footer-custom.html b/layouts/_partials/footer/script-footer-custom.html index 9a1de999..2ebd3c8e 100644 --- a/layouts/_partials/footer/script-footer-custom.html +++ b/layouts/_partials/footer/script-footer-custom.html @@ -1,8 +1,8 @@ -{{- $supabaseUrl := site.Params.supabase.url | default (getenv "SUPABASE_URL") -}} -{{- $supabaseAnon := site.Params.supabase.anon_key | default (getenv "SUPABASE_ANON_KEY") -}} +{{- $supabaseUrl := getenv "SUPABASE_URL" -}} +{{- $supabaseAnon := getenv "SUPABASE_ANON_KEY" -}} diff --git a/layouts/course/single.html b/layouts/course/single.html index b2b9844f..9ce854bd 100644 --- a/layouts/course/single.html +++ b/layouts/course/single.html @@ -3,42 +3,42 @@Curso {{ .Params.code }}
+{{ i18n "course_header_label" (dict "code" .Params.code) }}
Plano curricular {{ . }}
+{{ i18n "course_plan_caption" (dict "plan" .) }}
{{ end }}Carregando descrição detalhada a partir do Supabase…
+{{ i18n "course_synced_loading" }}
Nenhum curso publicado ainda. Volte em breve!
+{{ i18n "home_no_courses" }}
Tópico {{ .Params.slug }}
+{{ i18n "topic_header_label" (dict "slug" .Params.slug) }}
Carregando conteúdo Markdown…
+{{ i18n "topic_synced_loading" }}
Unidade curricular {{ .Params.code }}
+{{ i18n "uc_header_label" (dict "code" .Params.code) }}
Sincronizando ementa e materiais…
+{{ i18n "uc_official_loading" }}