Telling a Spanish reader why the page is in English
This site’s Spanish support has a fallback built in: if a page or a blog post doesn’t have a real translation yet, Astro quietly serves the English version at the Spanish URL instead of 404ing. That’s the right default - a broken link is worse than English content - but it has a UX problem nobody flagged until a design review caught it: it’s silent. A Spanish reader clicks the language switcher, lands on /es/blog/some-post, and gets a page that’s 100% in English with no explanation. It looks like the switcher is broken, not like a deliberate fallback.
The fix is a one-line notice bar, but finding a reliable way to detect “this page is a fallback” took more digging than the UI itself. There are two genuinely different situations here, and they need two different checks.
The first is a page like the Deep Cut Atlas marketing page, which has no Spanish route file at all - so Astro’s routing fallback renders the single English component for both /apps/deep-cut-atlas and /es/apps/deep-cut-atlas. I already knew from an earlier i18n gotcha that Astro.url.pathname lies on these fallback-rendered pages - it reports the matched route, not what was actually requested. What I hadn’t confirmed until now: Astro.currentLocale does NOT lie the same way. I curled both URLs and diffed the rendered <html lang="..."> attribute, and sure enough, the Spanish URL correctly reports lang="es" even though the content itself never changes. That single boolean - currentLocale === 'es' - is enough to say “this whole page is an English fallback,” since the component itself has zero Spanish awareness to begin with.
The second situation is the blog, which does have a real /es/blog/[...slug].astro route - it’s just that only some posts have an actual Spanish translation. Here currentLocale is always 'es' regardless of whether this particular post is translated, so it’s useless as a signal. The actual answer was already sitting in the route’s own data: the route’s getStaticPaths() passes through the raw, unnormalized collection entry, and for an untranslated post that entry’s id still literally starts with en/ even though it’s being rendered by the Spanish route. Check that prefix before any of the existing helpers strip it off for URL-building, and you know exactly which posts are fallbacks without adding a single new field anywhere.
The part I almost skipped: after wiring both checks in, it would have been easy to just confirm the notice shows up on the untranslated pages and call it done. That’s the easy half. The half that actually catches a wrong condition is confirming the notice does NOT show up where a real translation exists - I curled all six fallback pages and all six real pages (the translated posts, the Spanish homepage, the Spanish privacy page) for the notice’s exact text and diffed the counts. Six ones, six zeros. That asymmetry is the whole point of the test.
One thing I looked at and deliberately didn’t do: the original finding also suggested dropping the Spanish hreflang tag on fallback pages, to stop search engines from indexing duplicate English content under two URLs. I traced through what that would actually require and it’s not a one-liner - the English page still advertises a Spanish hreflang alternate for every post regardless of translation status, so unilaterally dropping it from just the Spanish side is non-reciprocal, which most SEO tooling just ignores. And this site already deliberately self-canonicalizes Spanish pages to their own URL, so dropping the alternate while keeping that self-canonical sends a mixed signal. The real fix there is pointing the canonical tag at the English URL for fallback pages specifically, which is a more considered change than the one-line suggestion implied. Left it out rather than shipping something that looks like a fix but doesn’t actually change what gets indexed.
Related reading
Translating a marketing page without duplicating it
The Spanish page existed, had a URL, showed up in hreflang - and every word was English. The fix that almost shipped was 130 duplicated lines waiting to drift.
The homepage was the one page i18n forgot
The language switcher worked perfectly. There was just nothing on the other side of it: six components of English literals, a flat English data file, and a dev server that lied for twenty minutes.
The paragraph that skipped translation because it never asked
A footer paragraph sat hardcoded in English next to fully translated Spanish strings - because it was never in the shared i18n strings file at all.