Mở đầu — vì sao bài này quan trọng
Hãy thử tưởng tượng một tình huống rất đời thường. Bạn xây một website tiếng Việt cho một shop mỹ phẩm, giao diện đẹp, màu sắc chuẩn, đọc bằng mắt thì hoàn hảo. Nhưng một khách hàng khiếm thị dùng phần mềm đọc màn hình (screen reader) truy cập vào. Thay vì nghe giọng tiếng Việt mượt mà đọc "Kem dưỡng ẩm ban đêm", họ nghe thấy một giọng máy đọc tiếng Anh cố phát âm từng chữ cái Việt theo kiểu Anh — "Kem doong am ban dem" — méo mó, khó hiểu, gần như vô nghĩa. Toàn bộ nội dung tuyệt vời của bạn trở thành một mớ âm thanh hỗn độn.
Nguyên nhân? Bạn quên khai báo ngôn ngữ của trang. Chỉ thiếu một thuộc tính duy nhất — lang — mà cả một nhóm người dùng bị đẩy ra ngoài.
Đây chính là chủ đề của bài học hôm nay: thuộc tính ngôn ngữ (language attributes). Nghe có vẻ nhỏ, chỉ là vài dòng code, nhưng nó là một trong những tiêu chí cơ bản nhất của WCAG (Web Content Accessibility Guidelines) và là một trong những lỗi phổ biến nhất mà các trình kiểm tra tự động phát hiện ra trên hàng triệu website. Điều đáng nói là nó cực kỳ dễ sửa — chi phí gần như bằng không — nhưng lại tạo ra khác biệt lớn cho người dùng screen reader, cho công cụ dịch tự động, và cho cả SEO. Trong bài này, chúng ta sẽ đi sâu vào từng lớp: khai báo ngôn ngữ cho toàn trang, cho từng đoạn nội dung, và cả những sắc thái mà nhiều lập trình viên bỏ qua.
Khái niệm cốt lõi
Ngôn ngữ trong HTML được khai báo bằng thuộc tính lang. Có hai cấp độ khai báo mà WCAG quy định, tương ứng với hai tiêu chí riêng biệt.
WCAG 3.1.1 — Language of Page (Level A)
Đây là tiêu chí bắt buộc ở mức thấp nhất (Level A), nghĩa là mọi website muốn tuân thủ accessibility đều phải đạt được. Nội dung của nó rất đơn giản: ngôn ngữ mặc định của mỗi trang web phải được xác định bằng code (programmatically determined).
Cách thực hiện là thêm thuộc tính lang vào thẻ <html> gốc:
<html lang="vi">
Khi bạn khai báo lang="vi", bạn đang nói với trình duyệt và các công nghệ hỗ trợ: "Nội dung mặc định của trang này là tiếng Việt." Từ đó:
- Screen reader (như NVDA, JAWS, VoiceOver, TalkBack) sẽ chọn đúng giọng đọc và bộ quy tắc phát âm tiếng Việt. Người dùng nghe được nội dung một cách tự nhiên, đúng thanh điệu.
- Trình duyệt biết cách chọn font phù hợp, áp dụng đúng quy tắc gạch nối từ (hyphenation), quy tắc dấu ngoặc kép, và xử lý các đặc thù chính tả.
- Công cụ dịch tự động (Google Translate, tính năng dịch tích hợp của Chrome/Edge) nhận diện đúng ngôn ngữ nguồn để dịch chính xác.
- Công cụ tìm kiếm dùng thông tin này để phục vụ đúng đối tượng người dùng.
lang không phải bịa ra tùy ý. Nó tuân theo chuẩn BCP 47 (còn gọi là language tags). Một số mã thông dụng: vi (tiếng Việt), en (tiếng Anh), en-US (tiếng Anh Mỹ), en-GB (tiếng Anh Anh), ja (tiếng Nhật), ko (tiếng Hàn), zh-Hans (tiếng Trung giản thể), zh-Hant (tiếng Trung phồn thể), fr (tiếng Pháp). Phần đầu là mã ngôn ngữ (ISO 639), phần sau (tùy chọn) là mã khu vực hoặc hệ chữ viết.WCAG 3.1.2 — Language of Parts (Level AA)
Trong thực tế, một trang không phải lúc nào cũng chỉ có một ngôn ngữ. Một bài blog tiếng Việt có thể trích một câu tiếng Anh, một trang giới thiệu công ty có tên sản phẩm tiếng Nhật, một trang tin có đoạn phỏng vấn tiếng Pháp. Tiêu chí 3.1.2 (Level AA) yêu cầu: mỗi đoạn nội dung dùng ngôn ngữ khác với ngôn ngữ mặc định của trang cũng phải được khai báo ngôn ngữ riêng.
Cách làm là thêm lang vào phần tử bao quanh đoạn đó:
<p>Triết lý thương hiệu của chúng tôi gói gọn trong câu
<span lang="en">less is more</span>.</p>
<blockquote lang="fr">
<p>La simplicité est la sophistication suprême.</p>
</blockquote>
Nhờ đó, khi screen reader gặp đoạn lang="en", nó tạm chuyển sang giọng đọc tiếng Anh để phát âm "less is more" cho đúng, rồi quay lại tiếng Việt cho phần còn lại. Nếu không khai báo, giọng tiếng Việt sẽ cố đọc "less is more" theo phiên âm tiếng Việt — nghe rất kỳ.
Ngoại lệ quan trọng: đừng đánh dấu quá tay
WCAG 3.1.2 có một lưu ý tinh tế mà nhiều người bỏ qua: các từ mượn đã trở thành một phần của ngôn ngữ bản địa thì KHÔNG cần đánh dấu. Ví dụ, trong tiếng Việt ngày nay các từ như "marketing", "internet", "email", "laptop", "menu", "logo" đã được dùng phổ biến, hầu như là một phần của ngôn ngữ giao tiếp hằng ngày. Bạn không cần bọc từng từ này trong <span lang="en">. Việc đánh dấu quá tay không những vô ích mà còn có thể làm screen reader liên tục chuyển đổi giọng đọc, gây khó chịu. Nguyên tắc: chỉ khai báo ngôn ngữ khác khi đó thực sự là một đoạn văn/cụm từ thuộc ngôn ngữ khác, không phải từ mượn đã hòa nhập.
Phân biệt lang và hreflang và xml:lang
Có ba thuộc tính dễ gây nhầm lẫn:
lang(trên bất kỳ phần tử HTML nào): khai báo ngôn ngữ của nội dung phần tử đó. Đây là thứ chính chúng ta bàn hôm nay.hreflang(trên thẻ<a>hoặc<link>): cho biết ngôn ngữ của tài liệu mà liên kết trỏ đến, không phải ngôn ngữ của trang hiện tại. Ví dụ<a href="/en/" hreflang="en">English</a>.xml:lang: phiên bản dành cho tài liệu XHTML/XML. Với HTML5 thông thường, bạn chỉ cầnlang.
hreflang không thay thế được lang.Tình huống thực tế
Ví dụ 1 — Shop mỹ phẩm Việt và cú "vấp" của screen reader
Một shop mỹ phẩm online tại TP.HCM (giả định tên "Bloom Cosmetics") thuê team dev dựng lại website trên nền một theme mua sẵn từ nước ngoài. Theme này để mặc định <html lang="en"> vì tác giả là người Mỹ. Đội dev chỉ thay nội dung tiếng Việt vào mà không đụng tới thẻ <html>.
Ba tháng sau, một khách hàng khiếm thị phản hồi qua fanpage rằng cô "không nghe được gì rõ ràng" khi dùng TalkBack trên điện thoại Android để duyệt trang sản phẩm. Team kiểm tra: máy đọc màn hình đang dùng giọng tiếng Anh để đọc toàn bộ mô tả tiếng Việt. "Kem chống nắng nâng tông" bị đọc thành một chuỗi âm rời rạc, sai thanh điệu, nghe như tiếng nước ngoài. Kết quả: khách bỏ giỏ hàng, và shop mất một khách hàng trung thành tiềm năng.
Cách sửa? Đúng một dòng: đổi <html lang="en"> thành <html lang="vi">. Sau khi deploy, cùng khách hàng đó thử lại và xác nhận giọng đọc đã chuyển sang tiếng Việt tự nhiên.
Bài học: Đừng bao giờ tin tưởng giá trị lang mặc định của theme/template mua sẵn. Kiểm tra thẻ <html> là việc đầu tiên nên làm khi Việt hóa một giao diện có sẵn. Một dòng code, nhưng là ranh giới giữa "dùng được" và "hoàn toàn không dùng được" với một nhóm người dùng.
Ví dụ 2 — Trang tin song ngữ và câu trích dẫn tiếng Anh
Một trang báo công nghệ ở Việt Nam (giả định "TechViet") có bài viết về CEO một startup. Trong bài có một đoạn trích nguyên văn phát biểu tiếng Anh của vị CEO: "We build for the next billion users." Đội biên tập không đánh dấu ngôn ngữ cho đoạn này.
Khi một độc giả khiếm thị nghe bài, screen reader (đang ở chế độ tiếng Việt vì trang khai báo lang="vi" — điều này đã đúng) cố đọc câu tiếng Anh bằng bộ phát âm tiếng Việt. "We build for the next billion users" bị đọc thành "quê bát pho tờ nếch bin li ần u sờ" — hoàn toàn không hiểu được. Ý nghĩa của câu trích dẫn quan trọng nhất bài viết bị mất trắng với người dùng đó.
Team sửa bằng cách bọc câu trích trong thẻ có khai báo ngôn ngữ:
<blockquote lang="en">
<p>We build for the next billion users.</p>
</blockquote>
Từ đó, screen reader tạm chuyển sang giọng tiếng Anh để đọc đúng câu này, rồi quay lại tiếng Việt cho phần bình luận.
Bài học: Với nội dung song ngữ — vốn rất phổ biến ở Việt Nam do lượng thuật ngữ và trích dẫn tiếng Anh lớn — hãy có quy trình biên tập: bất kỳ đoạn nào là câu/cụm trọn vẹn thuộc ngôn ngữ khác thì bọc lang tương ứng. Nhưng nhớ ngoại lệ: từ mượn đơn lẻ như "startup", "CEO", "app" thì không cần.
Ví dụ 3 — App ngân hàng đa quốc gia và chuyện lang động
Một app ngân hàng số hoạt động ở nhiều nước Đông Nam Á (giả định "SEABank Digital") cho phép người dùng đổi ngôn ngữ giao diện giữa tiếng Việt, tiếng Anh và tiếng Thái ngay trong app. Lỗi họ mắc phải: khi người dùng đổi sang tiếng Thái, toàn bộ chữ hiển thị đổi sang tiếng Thái đúng, nhưng thẻ <html lang="vi"> không được cập nhật — nó vẫn giữ nguyên vi từ lần tải trang đầu.
Hậu quả: screen reader vẫn cố đọc chữ Thái bằng giọng tiếng Việt, và người dùng khiếm thị người Thái không dùng được. Đội kiểm thử accessibility phát hiện ra trong đợt audit tuân thủ WCAG trước khi ra mắt phiên bản khu vực.
Giải pháp là cập nhật thuộc tính lang động mỗi khi đổi ngôn ngữ giao diện:
function setLanguage(langCode) {
document.documentElement.lang = langCode; // ví dụ 'th', 'vi', 'en'
// ... phần load bản dịch giao diện
}
Bài học: Trong các ứng dụng đa ngôn ngữ hoặc SPA (single-page application), lang không phải thứ "đặt một lần rồi quên". Nó là trạng thái phải đồng bộ với ngôn ngữ nội dung đang hiển thị. Bất cứ khi nào bạn thay đổi ngôn ngữ giao diện, hãy cập nhật document.documentElement.lang tương ứng.
Hướng dẫn từng bước
Dưới đây là quy trình thực tế để đảm bảo trang của bạn khai báo ngôn ngữ đúng chuẩn.
Bước 1 — Xác định ngôn ngữ mặc định của trang. Ngôn ngữ mà phần lớn nội dung sử dụng là ngôn ngữ mặc định. Với website tiếng Việt, đó là vi.
Bước 2 — Đặt lang trên thẻ <html>. Đây là việc đầu tiên và quan trọng nhất:
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="utf-8">
<title>Trang chủ — Bloom Cosmetics</title>
</head>
<body>
...
</body>
</html>
Bước 3 — Chọn đúng mã BCP 47. Với tiếng Việt dùng vi. Nếu cần cụ thể hơn có thể dùng vi-VN, nhưng vi là đủ và phổ biến nhất. Tránh những giá trị sai như lang="vietnamese" hay lang="Vietnam" — chúng không hợp lệ và bị bỏ qua.
Bước 4 — Rà soát nội dung đa ngôn ngữ. Đọc qua trang và tìm các đoạn là câu/cụm trọn vẹn thuộc ngôn ngữ khác (trích dẫn, đoạn tiếng Anh, tên riêng dài bằng tiếng khác). Bọc chúng bằng phần tử có lang tương ứng:
<p>Khẩu hiệu của họ là <span lang="en">Think different</span>.</p>
Bước 5 — Bỏ qua các từ mượn đã hòa nhập. Đừng đánh dấu "email", "marketing", "menu"... Chỉ đánh dấu khi thật sự cần.
Bước 6 — Xử lý ngôn ngữ động (nếu có). Nếu app cho đổi ngôn ngữ runtime, viết code cập nhật document.documentElement.lang mỗi lần đổi. Với các thành phần tải nội dung ngôn ngữ khác qua AJAX, đặt lang trên container tương ứng.
Bước 7 — Kiểm tra lại. Mở DevTools, xem thẻ <html> có đúng lang chưa. Chạy một công cụ tự động (axe DevTools, WAVE, Lighthouse) — chúng đều có kiểm tra riêng cho "html element has lang attribute" và "lang attribute has valid value". Cuối cùng, kiểm tra thủ công bằng screen reader để nghe giọng đọc có đúng ngôn ngữ không — đây mới là phép thử thực sự.
Lỗi thường gặp & mẹo
Lỗi 1 — Thiếu hẳn thuộc tính lang. Đây là lỗi phổ biến nhất. Nhiều trang không có lang nào cả, screen reader phải "đoán" ngôn ngữ hoặc dùng ngôn ngữ mặc định của hệ điều hành người dùng — thường sai. Luôn có lang trên <html>.
Lỗi 2 — Tin theo lang mặc định của template. Theme/framework nước ngoài thường để lang="en". Khi Việt hóa, người ta quên đổi. Hãy coi việc kiểm tra <html lang> là bước bắt buộc mỗi khi dựng site từ template.
Lỗi 3 — Dùng mã ngôn ngữ sai định dạng. lang="VI" (viết hoa vẫn chạy nhưng quy ước là chữ thường cho phần ngôn ngữ), lang="vn" (sai — mã tiếng Việt là vi, không phải vn; vn chỉ là mã quốc gia dạng khác và không hợp lệ ở đây), lang="vietnam" (sai hoàn toàn). Hãy dùng đúng vi.
Lỗi 4 — Đánh dấu ngôn ngữ quá tay. Bọc từng từ mượn tiếng Anh trong <span lang="en"> khiến screen reader chuyển giọng liên tục, gây rối. Chỉ đánh dấu câu/cụm trọn vẹn thực sự thuộc ngôn ngữ khác.
Lỗi 5 — Quên cập nhật lang khi đổi ngôn ngữ runtime. Trong SPA và app đa ngôn ngữ, lang phải đổi theo. Nếu quên, nội dung đổi ngôn ngữ nhưng screen reader vẫn đọc bằng giọng cũ.
Lỗi 6 — Nhầm hreflang với lang. hreflang mô tả ngôn ngữ của trang đích của liên kết, không phải trang hiện tại. Nó không thay thế cho lang trên <html>.
Mẹo: Với các liên kết chuyển đổi ngôn ngữ (ví dụ nút "English" trên site tiếng Việt), hãy đánh dấu chính chữ đó bằng lang để screen reader đọc đúng: <a href="/en/" lang="en" hreflang="en">English</a>. Nhờ đó chữ "English" được đọc bằng giọng tiếng Anh, còn hreflang báo trang đích là tiếng Anh.
Mẹo: Với hệ chữ có nhiều biến thể như tiếng Trung, hãy dùng subtag hệ chữ: zh-Hans (giản thể) hoặc zh-Hant (phồn thể), thay vì chỉ zh, để screen reader và font chọn đúng.
Bài tập thực hành
- Kiểm tra site hiện tại. Mở một website tiếng Việt bất kỳ bạn đang làm hoặc yêu thích. Dùng DevTools xem thẻ
<html>. Nó cólangkhông? Giá trị có phảivikhông? Ghi lại kết quả. Nếu thiếu hoặc sai, viết ra dòng code cần sửa.
- Sửa một template. Lấy một HTML template mua sẵn (hoặc tự tạo file mẫu để
<html lang="en">), đổi thành website tiếng Việt và chỉnhlangcho đúng. Kiểm tra lại bằng axe DevTools hoặc Lighthouse để xác nhận không còn cảnh báo về ngôn ngữ.
- Đánh dấu nội dung đa ngôn ngữ. Viết một đoạn HTML mô phỏng bài blog tiếng Việt có: (a) một câu trích tiếng Anh trọn vẹn, (b) một tên sản phẩm tiếng Nhật, (c) vài từ mượn như "marketing", "email". Đánh dấu
langđúng chỗ cho (a) và (b), và cố ý KHÔNG đánh dấu (c). Giải thích vì sao.
- Test bằng screen reader. Nếu dùng máy Mac, bật VoiceOver (Cmd+F5); nếu dùng Windows, cài NVDA (miễn phí). Tạo hai trang: một trang
lang="vi"và một tranglang="en"với cùng nội dung tiếng Việt. Nghe sự khác biệt giọng đọc. Ghi lại cảm nhận — đây là cách hiểu sâu nhất về vì sao thuộc tính này quan trọng.
- Ngôn ngữ động. Viết một hàm JavaScript
setLanguage(langCode)cập nhật cảdocument.documentElement.langlẫn nội dung hiển thị. Thử đổi qua lại giữavivàenvà quan sát thẻ<html>thay đổi trong DevTools.
Tóm tắt
Thuộc tính ngôn ngữ là một trong những viên gạch nền tảng của accessibility — nhỏ về code nhưng lớn về tác động. Hãy ghi nhớ những điểm cốt lõi:
- WCAG 3.1.1 (Level A): Mọi trang phải khai báo ngôn ngữ mặc định trên thẻ
<html>, ví dụ<html lang="vi">. Đây là mức bắt buộc thấp nhất. - WCAG 3.1.2 (Level AA): Mỗi đoạn nội dung thuộc ngôn ngữ khác với mặc định phải được đánh dấu
langriêng, ví dụ<span lang="en">. - Screen reader dựa vào
langđể chọn đúng giọng đọc và phát âm. Thiếu hoặc sailangkhiến nội dung trở nên vô nghĩa với người dùng khiếm thị. - Dùng đúng mã BCP 47:
vicho tiếng Việt, không phảivnhayvietnamese. - Đừng đánh dấu quá tay các từ mượn đã hòa nhập vào tiếng Việt như "email", "marketing".
- Trong app đa ngôn ngữ và SPA, cập nhật
langđộng mỗi khi đổi ngôn ngữ. - Kiểm tra bằng cả công cụ tự động (axe, WAVE, Lighthouse) lẫn thử nghe thủ công bằng screen reader.
<html lang> là điều đầu tiên bạn làm. Một dòng code, nhưng là cánh cửa để cả một nhóm người dùng bước vào được nội dung của bạn.