[{"data":1,"prerenderedAt":216},["ShallowReactive",2],{"blog:page:\u002Fvi\u002Fblog\u002Flocale-switching":3,"blog:articles:vi":157,"blog:translations:\u002Fvi\u002Fblog\u002Flocale-switching":213},{"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":46,"seo":151,"stem":152,"tags":153,"__hash__":156},"blog\u002Fvi\u002Fblog\u002F3.locale-switching\u002Findex.md","Locale switching không bao giờ nói dối","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-không-bao-giờ-nói-dối",[15,16,17],"p",{},"Điều tệ nhất một language switcher có thể làm là offer một bản dịch không tồn\ntại. Reader bấm vào, rơi vào trang 404, và học được rằng switcher nói dối.\nNên quy tắc ở đây rất đơn giản:",[19,20,21],"blockquote",{},[15,22,23],{},"Link switcher chỉ được render khi bản dịch thực sự tồn tại.",[25,26,28],"h2",{"id":27},"availability-đến-từ-content","Availability đến từ content",[15,30,31],{},"Với mỗi trang, app hỏi content collection xem những locale nào có document ở\ncùng resource path. Switcher render từ câu trả lời đó - không phải từ registry\nđầy đủ. Nếu một trang chỉ có tiếng Anh, reader tiếng Anh không thấy link tiếng\nViệt, vì đằng sau nó không có gì cả.",[25,33,35],{"id":34},"slug-là-khoá-nối","Slug là khoá nối",[15,37,38,39,43,44,47],{},"Các bản dịch dùng chung một slug. ",[40,41,42],"code",{},"\u002Fen\u002Fblog\u002Flocale-switching"," và\n",[40,45,46],{},"\u002Fvi\u002Fblog\u002Flocale-switching"," là cùng một article, nên việc chuyển locale nghĩa\nlà thay đúng một 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],{},"Không có bảng mapping per-page, không có chuỗi redirect sau switcher - path\nđược dựng lại bởi builder của chính nền tảng, thứ từ chối output hỏng lúc\ncompile thay vì phục vụ nó.",[25,62,64],{"id":63},"metadata-theo-cùng-quy-tắc","Metadata theo cùng quy tắc",[15,66,67,68,71],{},"Các link ",[40,69,70],{},"hreflang"," alternate trong head của trang được dựng từ cùng câu trả\nlời availability. Một trang không bao giờ quảng bá với search engine một bản\ndịch mà nó không offer cho reader.",[73,74,75,88],"table",{},[76,77,78],"thead",{},[79,80,81,85],"tr",{},[82,83,84],"th",{},"Bề mặt",[82,86,87],{},"Nguồn chân lý",[89,90,91,100,109],"tbody",{},[79,92,93,97],{},[94,95,96],"td",{},"Link switcher",[94,98,99],{},"content query",[79,101,102,107],{},[94,103,104,105],{},"Link ",[40,106,70],{},[94,108,99],{},[79,110,111,116],{},[94,112,113],{},[40,114,115],{},"canonical",[94,117,118],{},"path hiện tại, production origin",[15,120,121],{},"Một query, ba bề mặt, không lệch nhau.",[123,124],"hr",{},[15,126,127],{},[128,129,130,131,136],"em",{},"Registry định nghĩa những locale nào tồn tại được nhắc đến trong bài\n",[132,133,135],"a",{"href":134},"\u002Fen\u002Fblog\u002Fbuild-time-content","content build-time",".",{"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","Minh họa bìa: hai đường dẫn ngôn ngữ hội tụ về cùng một bố cục trang","2026-09-16","Language switcher chỉ được offer thứ tồn tại. Đây là cách danh sách link được suy ra từ content thật.","md",false,{},true,{"title":5,"description":146},"vi\u002Fblog\u002F3.locale-switching\u002Findex",[154,155],"i18n","ux","81EjWwsriNiUDMajS14BFkVgS-rSOF6ub5xYbds64dU",[158,166,178,190,193,202],{"path":159,"stem":160,"title":161,"description":162,"date":163,"author":6,"tags":164,"featured":148},"\u002Fvi\u002Fblog\u002Fdemo-content-note","vi\u002Fblog\u002F6.demo-content-note\u002Findex","Blog này là gì (và không phải gì)","Đây là một vertical slice demo - layout thật, pipeline thật, content editorial một cách có chủ ý.","2026-10-07",[165],"meta",{"path":167,"stem":168,"title":169,"description":170,"date":171,"author":6,"tags":172,"featured":148,"cover":175},"\u002Fvi\u002Fblog\u002Freading-first-design","vi\u002Fblog\u002F5.reading-first-design\u002Findex","Trang đọc bài không phải trang tài liệu","Vì sao layout article của blog cố tình khác layout docs - phân cấp thông tin, bề rộng cột, và sự tiết chế.","2026-09-30",[173,174],"design","typography",{"src":176,"alt":177},"\u002Fassets\u002Fcover-reading-first.svg","Minh họa bìa: bố cục một cột hẹp được chọn thay vì bố cục nhiều cột rối rắm",{"path":179,"stem":180,"title":181,"description":182,"date":183,"author":6,"tags":184,"featured":148,"cover":187},"\u002Fvi\u002Fblog\u002Fstatic-deployment","vi\u002Fblog\u002F4.static-deployment\u002Findex","Deploy một blog như static assets","Không Worker script, không database binding - toàn bộ deployment là một thư mục file và một trang 404.","2026-09-23",[185,186],"deployment","cloudflare",{"src":188,"alt":189},"\u002Fassets\u002Fcover-static-deployment.svg","Minh họa bìa: thư mục các tệp HTML và SVG chảy ra mạng lưới edge CDN",{"path":46,"stem":152,"title":5,"description":146,"date":145,"author":6,"tags":191,"featured":148,"cover":192},[154,155],{"src":143,"alt":144},{"path":194,"stem":195,"title":196,"description":197,"date":198,"author":6,"tags":199,"featured":148},"\u002Fvi\u002Fblog\u002Fbuild-time-content","vi\u002Fblog\u002F2.build-time-content\u002Findex","Content được resolve ở build time","Vì sao blog này ship dưới dạng file prerender thay vì một content API - và điều đó mua được gì.","2026-09-09",[200,201],"architecture","static-sites",{"path":203,"stem":204,"title":205,"description":206,"date":207,"author":6,"tags":208,"featured":150,"cover":210},"\u002Fvi\u002Fblog\u002Furl-model","vi\u002Fblog\u002F1.url-model\u002Findex","Một mô hình URL cho mọi trang public","\u002Flocale\u002Fmount\u002Fpath giữ cho mọi trang public đoán được - và vì sao ta ràng buộc nó thay vì hy vọng nó được tuân thủ.","2026-09-02",[200,209],"urls",{"src":211,"alt":212},"\u002Fassets\u002Fcover-url-model.svg","Minh họa bìa: một thanh địa chỉ tách thành ba segment locale, mount và path",[214,215],"en","vi",1791809694189]