diff --git a/.woodpecker.yml b/.woodpecker.yml index eca2f89..f0a0447 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -42,7 +42,13 @@ steps: commands: # Bundle Decap locally so the /admin page makes zero third-party requests. - wget -q -O static/admin/decap-cms.js https://unpkg.com/decap-cms@^3.0.0/dist/decap-cms.js - - hugo --minify + # --printPathWarnings makes Hugo report two pages resolving to one output + # path; --panicOnWarning turns that report into a failed build. Without + # both, Hugo writes both documents into the same index.html and the site + # ships a page that renders as the article followed by a second copy of + # the whole site — with nothing in the CMS, the pipeline or the logs + # saying a word about it. Better a red build than a corrupted page. + - hugo --minify --printPathWarnings --panicOnWarning deploy: image: alpine:3.20 diff --git a/config.yaml b/config.yaml index e180dac..f2bf9e5 100644 --- a/config.yaml +++ b/config.yaml @@ -9,9 +9,14 @@ theme: "vienalatina" defaultContentLanguage: "es" defaultContentLanguageInSubdir: false -# Preserve WordPress slugs so external inbound links keep working. +# Date-scoped so two posts can share a title without fighting over one URL. +# `:slug` falls back to the title when front matter sets none, so a bare +# "/:slug/" mapped every post called "Hola mundo" onto /hola-mundo/; Hugo wrote +# both documents into that one index.html and the page rendered as two stacked +# copies of the site, with no error anywhere. Community blogs repeat titles +# constantly ("Resumen del mes"), so the year and month carry the uniqueness. permalinks: - post: "/:slug/" + post: "/:year/:month/:slug/" taxonomies: category: "categories" diff --git a/scripts/translate.py b/scripts/translate.py index 76d7a24..724fbb5 100644 --- a/scripts/translate.py +++ b/scripts/translate.py @@ -78,6 +78,14 @@ def split_lang(path: Path) -> tuple[str, str] | None: return None +# Decap resolves a filename clash by appending a counter, turning +# `hola-mundo.es.md` into `hola-mundo.es-1.md`. That name no longer ends in a +# language, so Hugo stops pairing it with its siblings and split_lang() returns +# None: the post publishes in Spanish and is never translated, with nothing +# anywhere reporting it. Recognising the shape lets us fail loudly instead. +CLASH_SUFFIX = re.compile(r"\.(?:%s)-\d+$" % "|".join(re.escape(l) for l in SITE_LANGS)) + + def split_frontmatter(text: str) -> tuple[dict, str]: match = re.match(r"\A---\n(.*?)\n---\n?(.*)\Z", text, re.DOTALL) if not match: @@ -130,6 +138,14 @@ def authored_sources(paths: list[Path]) -> list[tuple[Path, str, str]]: for path in paths: parsed = split_lang(path) if not parsed: + rel = path.relative_to(REPO_ROOT) + if CLASH_SUFFIX.search(path.name[: -len(".md")] if path.suffix == ".md" else ""): + raise SystemExit( + f"{rel}: filename ends in a clash counter, so it is neither a " + f"translation source nor a sibling — it would publish untranslated.\n" + f"Rename it to ..md (the CMS produced this " + f"because another post already claimed the name)." + ) continue basename, lang = parsed fm = read_frontmatter(path) diff --git a/static/admin/config.yml b/static/admin/config.yml index 95d0215..58c970c 100644 --- a/static/admin/config.yml +++ b/static/admin/config.yml @@ -22,7 +22,12 @@ collections: create: true extension: md format: yaml-frontmatter - slug: "{{slug}}.es" + # Date-prefixed so a repeated title does not collide on the filename. When + # it does collide, Decap appends "-1" and writes `hola-mundo.es-1.md` — a + # name whose suffix is no longer `.es`, so Hugo stops seeing a Spanish + # sibling and translate.py's split_lang() skips the file entirely: the post + # publishes but is never translated, with nothing reported anywhere. + slug: "{{year}}-{{month}}-{{day}}-{{slug}}.es" filter: { field: lang, value: es } fields: - { name: title, label: "Título", widget: string }