[{"data":1,"prerenderedAt":215},["ShallowReactive",2],{"blog:page:\u002Fen\u002Fblog\u002Flocale-switching":3,"blog:articles:en":157,"blog:translations:\u002Fen\u002Fblog\u002Flocale-switching":212},{"id":4,"title":5,"author":6,"body":7,"cover":143,"coverAlt":144,"date":145,"description":146,"extension":147,"featured":148,"meta":149,"navigation":150,"path":42,"seo":151,"stem":152,"tags":153,"__hash__":156},"blog\u002Fen\u002Fblog\u002F3.locale-switching\u002Findex.md","Locale switching that never lies","John Martin",{"type":8,"value":9,"toc":137},"minimark",[10,14,18,24,29,32,36,48,58,61,65,72,119,122,125],[11,12,5],"h1",{"id":13},"locale-switching-that-never-lies",[15,16,17],"p",{},"The worst thing a language switcher can do is offer a translation that does\nnot exist. The reader clicks, lands on a 404, and learns the switcher lies.\nSo the rule here is simple:",[19,20,21],"blockquote",{},[15,22,23],{},"A switcher link is only rendered when the translation actually exists.",[25,26,28],"h2",{"id":27},"availability-comes-from-content","Availability comes from content",[15,30,31],{},"For every page, the app asks the content collection which locales have a\ndocument at the same resource path. The switcher renders from that answer -\nnot from the full registry. If a page exists in English only, English readers\nsee no Vietnamese link, because there is nothing behind it.",[25,33,35],{"id":34},"the-slug-is-the-join-key","The slug is the join key",[15,37,38,39,43,44,47],{},"Translations share one slug. ",[40,41,42],"code",{},"\u002Fen\u002Fblog\u002Flocale-switching"," and\n",[40,45,46],{},"\u002Fvi\u002Fblog\u002Flocale-switching"," are the same article, so switching means replacing\nexactly one segment:",[49,50,56],"pre",{"className":51,"code":53,"language":54,"meta":55},[52],"language-text","\u002Fen\u002Fblog\u002Flocale-switching\n       |\n       v\n\u002Fvi\u002Fblog\u002Flocale-switching\n","text","",[40,57,53],{"__ignoreMap":55},[15,59,60],{},"No per-page mapping table, no redirects chained behind the switcher - the path\nis rebuilt by the platform's own builder, which rejects malformed output at\ncompile time rather than serving it.",[25,62,64],{"id":63},"metadata-follows-the-same-rule","Metadata follows the same rule",[15,66,67,68,71],{},"The ",[40,69,70],{},"hreflang"," alternates in the page head are built from the same\navailability answer. A page never advertises a translation to search engines\nthat it would not offer to a reader.",[73,74,75,88],"table",{},[76,77,78],"thead",{},[79,80,81,85],"tr",{},[82,83,84],"th",{},"Surface",[82,86,87],{},"Source of truth",[89,90,91,100,109],"tbody",{},[79,92,93,97],{},[94,95,96],"td",{},"Switcher links",[94,98,99],{},"content query",[79,101,102,107],{},[94,103,104,106],{},[40,105,70],{}," links",[94,108,99],{},[79,110,111,116],{},[94,112,113],{},[40,114,115],{},"canonical",[94,117,118],{},"current path, production origin",[15,120,121],{},"One query, three surfaces, no divergence.",[123,124],"hr",{},[15,126,127],{},[128,129,130,131,136],"em",{},"The registry that defines which locales exist is documented in the\n",[132,133,135],"a",{"href":134},"\u002Fen\u002Fblog\u002Fbuild-time-content","build-time content"," post.",{"title":55,"searchDepth":138,"depth":138,"links":139},2,[140,141,142],{"id":27,"depth":138,"text":28},{"id":34,"depth":138,"text":35},{"id":63,"depth":138,"text":64},"\u002Fassets\u002Ftwo-locales-one-page.svg","Cover illustration: two locale paths joining into one shared page layout","2026-09-16","A language switcher should only offer what exists. Here is how the link list is derived from real content.","md",false,{},true,{"title":5,"description":146},"en\u002Fblog\u002F3.locale-switching\u002Findex",[154,155],"i18n","ux","4lNIk3Yr2rNsIdONYW6fGtNbFnMxfp_8Q9IJ_370tyA",[158,166,178,190,193,201],{"path":159,"stem":160,"title":161,"description":162,"date":163,"author":6,"tags":164,"featured":148},"\u002Fen\u002Fblog\u002Fdemo-content-note","en\u002Fblog\u002F6.demo-content-note\u002Findex","What this blog is (and is not)","This is a demo vertical slice - real layout, real pipelines, deliberately editorial content.","2026-10-07",[165],"meta",{"path":167,"stem":168,"title":169,"description":170,"date":171,"author":6,"tags":172,"featured":148,"cover":175},"\u002Fen\u002Fblog\u002Freading-first-design","en\u002Fblog\u002F5.reading-first-design\u002Findex","A reading page is not a documentation page","Why the blog article layout deliberately differs from the docs layout - hierarchy, width, and restraint.","2026-09-30",[173,174],"design","typography",{"src":176,"alt":177},"\u002Fassets\u002Fcover-reading-first.svg","Cover illustration: a narrow single-column article layout chosen over a cluttered multi-column layout",{"path":179,"stem":180,"title":181,"description":182,"date":183,"author":6,"tags":184,"featured":148,"cover":187},"\u002Fen\u002Fblog\u002Fstatic-deployment","en\u002Fblog\u002F4.static-deployment\u002Findex","Deploying a blog as static assets","No Worker script, no database binding - the whole deployment is a directory of files and a 404 page.","2026-09-23",[185,186],"deployment","cloudflare",{"src":188,"alt":189},"\u002Fassets\u002Fcover-static-deployment.svg","Cover illustration: a folder of HTML and SVG files flowing onto a globe of CDN edge nodes",{"path":42,"stem":152,"title":5,"description":146,"date":145,"author":6,"tags":191,"featured":148,"cover":192},[154,155],{"src":143,"alt":144},{"path":134,"stem":194,"title":195,"description":196,"date":197,"author":6,"tags":198,"featured":148},"en\u002Fblog\u002F2.build-time-content\u002Findex","Content is resolved at build time","Why this blog ships as prerendered files instead of a content API - and what that buys.","2026-09-09",[199,200],"architecture","static-sites",{"path":202,"stem":203,"title":204,"description":205,"date":206,"author":6,"tags":207,"featured":150,"cover":209},"\u002Fen\u002Fblog\u002Furl-model","en\u002Fblog\u002F1.url-model\u002Findex","One URL model for every public page","How \u002Flocale\u002Fmount\u002Fpath keeps every public page predictable - and why we enforce it instead of hoping for it.","2026-09-02",[199,208],"urls",{"src":210,"alt":211},"\u002Fassets\u002Fcover-url-model.svg","Cover illustration: a single address bar splitting into locale, mount and path segments",[213,214],"en","vi",1791809694154]