Commits
Sections that speak all languages, custom fields and the parser that was stopping translations
The night of sections with name and slug per language across all sites, ACF-style custom fields, the chat that left quotes as 404s, and the JSON parser that was breaking all translations of articles with code.
I picked up mid-session one that had run out of credits: the data layer for section translations was already sketched out (JSONB model, schemas, migration) and all of its real usage was still missing. The night ended with sections that speak all languages across all sites, WordPress-style custom fields, and, in the evening, the automatic translation flow repaired.
Sections with name and slug per language
Until now, every section always showed the name and slug of the language in which it had been created, no matter which languages the site had. Now each section stores translations {language: {name, slug}} editable from the panel; the API validates language, patterns, and collisions between all the slugs of all sections, and the public site serves the slug of the requested language with a 301 from the original, filters articles by language, declares hreflang, and publishes a sitemap with one URL per language and their alternates. I applied it to the template, to BeCoach, and to the presentation site, and then brought it to escandell.cat with its own style, which was already consuming the sections from the panel but without name or slug per language. Core release 0.1.45 shipped with all of this, and the becoach and web-base-astro builds went green; on escandell.cat, a push, build, and image are enough because it was already filtering by language on the client.
ACF-style custom fields
The request was clear: the WordPress ACF pattern. Each section defines its fields (portfolio → client, year, gallery...) and the articles in the section fill them in, so a custom design can extract them from the public API. We agreed on the scope before touching anything: values on the articles, a complete set of types —text, long text, number, date, URL, image, and select, to which I later added text list and yes/no— and for now only exposing it to the API, with no rendering in the template. From the technical details, I'll keep one Python gotcha: SectionService defines a list method, so annotating a later method with list[dict] resolved to the method and blew up the import. Fixed with from __future__ import annotations, which the project already uses.
The panel returns home and the chat stops throwing 404
Two quick usability frictions: when editing a section article, all the back buttons always led to Artículos, the blog listing. Now each section goes back to its own listing, and with the general blog turned off, the entry disappears from the menu and the search. More serious was the public chat: quotes ended up as 404s because the API always built /blog/{slug}/ — a route that doesn't exist on escandell.cat — and it also mixed in articles from other languages in the replies and answered in Spanish by default. Now the endpoint accepts language and section, filters the context to published articles in the requested language, and builds the quotes with the translated slug of each language, with no prefix on the main language.
An issue that remains open
While diagnosing the chat, the one on web-base-astro.escandell.cat returns 504: its "chat" usage points to Groq and the quota is exhausted, with the retries exceeding the gateway timeout. It's not new code, it's configuration: quota needs to be reloaded or the provider changed in Settings → AI of the panel. Pending.
The JSON parser that was stopping translations
In the evening, the report came in of translations failing systematically, especially in English. The root cause wasn't the one in the report: the parser did split("```") to strip the closing fence of the response's code block, but if the article contained code blocks, the model's correct JSON had fences inside the value and the parser always cut it → 502. Every technical article was untranslatable, even if the model responded perfectly. I fixed the parser so it only removes the enclosing fence, rewrote the language prompt (which arrived literally, without interpolation), added retries with response_format json, detection of truncated and empty translations, and in the scheduler a cooldown per article and language with growing wait, instead of burning each cycle's budget on the same articles stuck forever.