Blog
7 phút đọc
Thucde.dev giờ nói được hai thứ tiếng rồi đó
Thật ra...có thể nhiều hơn nếu muốn! Vì được nâng cấp theo lối i18n.

Có những lúc mình định làm một việc nghe rất nhỏ, xong nó nở ra thành nguyên một cuộc ❝ đại tu ❞.
Lần này là Thucde.dev.
Ban đầu mình chỉ nghĩ: nếu một bài viết có bản tiếng Việt và tiếng Anh, mình thêm một cái nút để người đọc chuyển qua lại được không?
Nghe đơn giản đúng không? Một cái nút.
VI. EN. Bấm qua bấm lại. Xong.Nhưng càng làm mình càng nhận ra: nếu muốn làm song ngữ cho đàng hoàng, thì nó không chỉ nằm ở phần chữ. Nó nằm ở cách app hiểu nội dung, cách URL được tạo ra, cách search/listing tránh trùng lặp, cách metadata nói chuyện với Google, cách RSS hoạt động, và cả cách production được deploy.
Nói cách khác, Thucde.dev không chỉ được “dịch sang tiếng Anh”. Nó được dạy lại cách hiểu ngôn ngữ.
Từ bên ngoài, thay đổi dễ thấy nhất là
/vi và /en. Từ bên trong, gần như cả app được chạm vào: post, dashboard, editor, search, feedback, sitemap, RSS, release notes, backend indexes, tests, deploy flow.Ban đầu là một cái nút đổi ngôn ngữ. Cuối cùng là một đợt nâng nền.
Song ngữ không chỉ là dịch chữSong ngữ không chỉ là dịch chữ
Song ngữ không chỉ là dịch chữSong ngữ không chỉ là dịch chữ
Điều mình thích nhất ở đợt này là nó ép mình nghĩ lại “một bài viết” thật ra là gì.
Nếu một bài có bản tiếng Việt và tiếng Anh, đó là một bài hay hai bài? Nếu để chung trong một object thì dễ quản lý hơn một chút, nhưng lại khó cho URL, SEO và metadata. Nếu tách ra thành hai bài riêng thì web hiểu tốt hơn, nhưng phải có cách nối chúng lại với nhau.
Cuối cùng Thucde.dev chọn hướng: mỗi bản dịch là một post riêng, nhưng cùng thuộc một nhóm dịch.
Vậy một bài có thể có:
txt
/vi/{slug} /en/{slug}
Hai URL, hai bản ngôn ngữ, hai bộ
metadata riêng. Nhưng về mặt ý nghĩa, chúng vẫn là cùng một bài viết.Cách này giúp search và listing không bị trùng nội dung, nhưng mỗi bản vẫn có URL sạch để share, index, preview và đọc đúng ngôn ngữ.
Làm tới đây mình mới thấy
i18n không phải một lớp giao diện. Nó là một phần của data model.Website cũng cần biết mình đang nói ngôn ngữ nàoWebsite cũng cần biết mình đang nói ngôn ngữ nào
Website cũng cần biết mình đang nói ngôn ngữ nàoWebsite cũng cần biết mình đang nói ngôn ngữ nào
Khi thêm
/vi và /en, mình không muốn chúng là hai prefix gắn thêm cho có. Locale phải trở thành một phần thật sự của app.Nếu người đọc đang ở tiếng Anh, họ nên tiếp tục được giữ trong không gian tiếng Anh khi bấm qua category, dashboard, post, search hay release notes. Nếu họ đổi sang tiếng Việt, app nên giữ lại context hiện tại thay vì làm họ rơi khỏi luồng.
Những thứ như giữ query string khi đổi ngôn ngữ, render đúng
html lang, canonical đúng locale, hay không để một link phụ đá người dùng về route cũ nghe rất nhỏ. Nhưng chính mấy thứ nhỏ đó quyết định app có cảm giác “thật” hay không.Một app song ngữ mà chỉ trang chủ song ngữ thì vẫn chưa đủ. Cái khó là làm sao để toàn bộ trải nghiệm không bị lẫn lộn.
SEO cũng phải hiểu song ngữSEO cũng phải hiểu song ngữ
SEO cũng phải hiểu song ngữSEO cũng phải hiểu song ngữ
Người đọc thấy nội dung. Nhưng web thì còn có Google, crawler, RSS reader, social preview bot, browser và đủ thứ parser khác.
Nên khi Thucde.dev có bản Việt và bản Anh, app cũng phải nói rõ với những hệ thống đó rằng: đây là hai bản ngôn ngữ của cùng một nội dung, không phải hai bài trùng nhau.
Đó là lý do đợt này mình phải đụng tới
canonical, hreflang, Open Graph locale, JSON-LD, sitemap, RSS và robots.txt.Đây là phần không hào nhoáng. Người dùng bình thường có thể chẳng bao giờ thấy. Nhưng mình nghĩ nó là một dấu hiệu app đang trưởng thành hơn: không chỉ hiển thị đúng cho người đọc, mà còn tự mô tả đúng với phần còn lại của web.
Kiểu như không chỉ nhà đẹp hơn, mà giấy tờ cũng rõ ràng hơn.
Nhân tiện nâng luôn phần nềnNhân tiện nâng luôn phần nền
Nhân tiện nâng luôn phần nềnNhân tiện nâng luôn phần nền
Trong lúc làm
i18n, mình cũng nâng khá nhiều thứ bên dưới.Frontend hiện đã lên
Next.js 16.3 (preview), React 19 và TypeScript 6. Một số phần UI cũ được dọn lại, nhiều chỗ được type chặt hơn, build được tối ưu hơn, navigation nhanh hơn, và một số lỗi production lắt nhắt cũng được xử lý.Backend cũng phải đi theo: model bài viết hiểu bản dịch, draft dịch, source locale, grouping, index chống trùng, feed và sitemap theo locale. Có thêm các phần liên quan security, cache, release notes và deploy flow.
Nói đời thường thì: ban đầu định sửa cái cửa, xong mở ra thấy nên đi lại luôn vài "đường" điện nước.
Mệt hơn, nhưng đáng. 😋
Có những thứ chỉ production mới nói thậtCó những thứ chỉ production mới nói thật
Có những thứ chỉ production mới nói thậtCó những thứ chỉ production mới nói thật
Một đoạn khá đáng nhớ là lúc mọi thứ ở local nhìn đã ổn, nhưng vẫn chưa thể gọi là xong.
Tests pass. Build pass. Local smoke ổn. Nhưng production lại có lúc
/vi và /en trả 200 mà render nội dung 404. Có post thì canonical đúng nhưng html lang hoặc og:locale vẫn sai.Lúc đó mới thấy lại bài học quen thuộc: local đúng chưa có nghĩa production đúng.
Release này chỉ được xem là hoàn tất sau khi database indexes được sync, frontend/backend được deploy, production routes được smoke, metadata được kiểm tra lại, sitemap/feed/robots chạy đúng, và cả những lỗi nhỏ như OG image cũng được fix.
Nghe mệt, nhưng đoạn này làm bài thật hơn. Build app không phải lúc nào cũng là mấy khoảnh khắc “wow”. Rất nhiều khi nó là nhìn log, dọn cache, kiểm tra port, chạy lại test, chờ deploy, rồi tự hỏi “ủa sao local được mà prod chưa được ta”.
Build cùng AI, nhưng vẫn có người cầm láiBuild cùng AI, nhưng vẫn có người cầm lái
Build cùng AI, nhưng vẫn có người cầm láiBuild cùng AI, nhưng vẫn có người cầm lái
Đợt này mình cũng dùng AI khá nhiều trong quá trình build.
Không phải kiểu “AI làm giùm hết”. Cảm giác đúng hơn là có một nhóm phụ tá ngồi cạnh: giúp mình rà lại checklist, soi những chỗ dễ sót, gom context sau nhiều ngày làm việc, và nhắc mình kiểm tra lại những thứ không nên tin bằng cảm giác.
Mình vẫn là người quyết định Thucde.dev nên đi hướng nào, cái gì đáng làm, trải nghiệm nên ra sao. AI chỉ giúp mình đi sâu hơn vào những lớp mà một thay đổi nhỏ có thể chạm tới: từ routing, metadata, sitemap, feed, đến deploy và production smoke.
Điều mình thích ở cách làm này là nó không thay mình cầm lái. Nó giống một nhóm ngồi ghế phụ: mở bản đồ, soi gương chiếu hậu, nhắc checklist, và đôi khi la lên “lưu ý là có xyz...” (steering mode!!)..
txt
Window: Jun 11 → Jun 30, 2026 Commits: 319 across root/frontend/backend App commits: 219 in frontend + backend Files touched: at least 874 Code churn: at least +74,600 / -41,194 lines Active commit days: 11
Thucde.dev đang lớn lênThucde.dev đang lớn lên
Thucde.dev đang lớn lênThucde.dev đang lớn lên
Sau đợt này, Thucde.dev có thể làm một việc mà trước đây nó chưa làm được tử tế: sống bằng hai ngôn ngữ.
Mình có thể viết một bài tiếng Việt, rồi tạo bản tiếng Anh sau. Hai bản có thể liên kết với nhau, nhưng vẫn có URL, metadata và preview riêng. Người đọc có thể chuyển ngôn ngữ trong bài. Search và listing không bị trùng. Sitemap và RSS hiểu locale. Release notes cũng bắt đầu giống một changelog public hơn.
Nhưng điều mình thích nhất không phải là một feature cụ thể.
Mình thích cảm giác Thucde.dev đang dần trở thành một nơi có thể lớn lên cùng mình.
Nó vẫn là personal site. Vẫn là nơi mình viết, thử, phá, sửa, học. Nhưng bên dưới, nó đang có nhiều phẩm chất của một product nghiêm túc hơn: route rõ hơn, deploy cẩn thận hơn, tests nhiều hơn, metadata đúng hơn, và workflow build cùng AI agents có kỷ luật hơn.
Thucde.dev giờ nói được hai thứ tiếng rồi đó.
Nhưng quan trọng hơn, nó đang học cách trở thành một app
đáng tin hơn.
