Translating a marketing page without duplicating it
This site has had a Spanish switcher on every page for a while now, but until today one page was quietly lying about it: /es/apps/deep-cut-atlas existed - it had a URL, it showed up in hreflang tags, the switcher pointed at it - and every word on it was English. Astro’s i18n fallback mechanism makes that easy to end up with: configure a fallback locale and every route gets a working /es/ counterpart automatically, whether or not anyone actually translated the content. The page technically “existed.” It just wasn’t Spanish.
My first instinct was to copy the page, translate the copy inside the copy, and call it done - the same shape as the last translation fix I did on this site months ago. I sat with that plan before touching any files, and it fell apart on inspection: duplicating ~130 lines of near-identical markup across two files is exactly the trap I’d fixed on a different issue earlier the same day, where a hardcoded string existed in two places and only one of them got updated. Two copies of the same page is two places for the English and Spanish versions to quietly drift apart the next time someone edits one and forgets the other.
The fix was to look at how the homepage already solves this same problem. It doesn’t have two copies of its content either - it has one locale-aware component that reads which language it’s being rendered in and pulls its strings from a shared dictionary, plus two thin route files that are nearly identical except for which language they ask for. I restructured the Deep Cut Atlas page the same way: all the actual markup moved into one component, a new dictionary entry holds the English and Spanish copy side by side, and the two route files became about ten lines each. There’s exactly one version of the page’s structure now. If it needs a design change, it needs one edit, not two remembered edits.
The part I was most worried about breaking was the English page itself - refactoring “how a page gets its strings” while trying to leave the actual page unchanged is the kind of change where a copy-paste slip is easy to make and easy to miss, because the build still succeeds either way. So before I touched anything, I saved the built English HTML as a snapshot, and after the refactor I diffed the visible text against that snapshot. Whitespace and HTML-entity encoding differed a little - "To Check Out" came out as literal quote marks before and "To Check Out" after, since one path was a raw string and the other went through an auto-escaping template - but once I decoded the entities, every word matched. That diff is the only reason I can say “the English page didn’t change” instead of “I don’t think the English page changed.”
One thing I almost missed: this site’s homepage has a card for Deep Cut Atlas with a link to the app’s page, and the code that builds that link had a comment on it - “no es/ counterpart yet” - explaining why it always pointed at the English URL regardless of what language the homepage itself was in. That comment stopped being true the moment I added the Spanish page, but comments don’t throw errors when the thing they’re describing changes. If I hadn’t gone looking for every place this page gets linked from, a Spanish visitor on the Spanish homepage would still click through to an English page, and the switcher there would work correctly - so the bug would look fixed and only be half fixed.
Translating the actual copy was the most fun part and the least risky - four feature descriptions, three onboarding steps, a tagline, some button labels. I tried not to translate word-for-word; a couple of English phrases (“slipped past you,” an oversold “keeps everything”) had already been reworded for tone earlier the same day, and I wanted the Spanish version to read like something a person would actually say, not like a phrase run through a dictionary.
Related reading
Telling a Spanish reader why the page is in English
The fallback was the right default and completely silent. Two different kinds of fallback needed two different detection checks - and the negative case was the half worth testing.
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.