10 lỗi i18n phổ biến và cách bạn tránh chúng
Tìm hiểu các lỗi i18n phổ biến nhất mà các nhà phát triển mắc phải và cách khắc phục chúng. Nâng cao chất lượng bản địa hóa cho ứng dụng của bạn với những thực tiễn tốt nhất này.
Quốc tế hóa (i18n) dường như đơn giản — cho đến khi bạn nhận ra người dùng Đức của bạn thấy các nút bị cắt ngắn, người dùng Nhật Bản nhận được các mẩu câu bị cắt và người dùng nói tiếng Ả Rập gặp bố cục hoàn toàn hỏng. Đó không phải là ngoại lệ. Đó là hậu quả dự đoán được của những sai lầm phổ biến mà hầu hết các nhóm phát triển mắc phải khi lần đầu tiếp cận localization. Trong hướng dẫn này, chúng ta sẽ đi qua 10 lỗi i18n phổ biến nhất, giải thích tại sao chúng xảy ra và chỉ cho bạn từng bước cách khắc phục từng lỗi. Dù bạn đang xây một ứng dụng mới hay nâng cấp một ứng dụng hiện có — tránh các rơi bẫy này sẽ tiết kiệm cho bạn vài tuần gỡ lỗi.
Lỗi #1: Chuỗi được nhúng cứng
Hardcoding các chuỗi là lỗi i18n cơ bản nhất.
['Thiết lập khung i18n của bạn trước khi viết dòng UI đầu tiên', 'Sử dụng plugin Linter để phát hiện chuỗi cứng trong templates và components', 'Hãy giữ các khóa dịch có mô tả và được tổ chức theo tính năng hoặc trang']
Cấu hình khung i18n của bạn trước khi viết dòng UI đầu tiên.
Sử dụng một plugin linter để nhận diện chuỗi hard-coded trong template và component.
Giữ các khóa dịch có mô tả và được tổ chức theo tính năng hoặc trang.
Một tình huống điển hình
Một nhà phát triển viết nhãn nút trực tiếp trong JSX: <button>Submit Order</button>.
Ứng dụng được phân phối bằng tiếng Anh và hoạt động hoàn hảo. Sáu tháng sau, công ty mở rộng ra Đức.
Đội ngũ i18n phát hiện hơn 2.000 chuỗi được viết cứng. Việc bổ sung lại mất 3 tuần và gây ra 47 lỗi.
Tại sao điều này là một vấn đề
Trong một cơ sở mã đã trưởng thành, các chuỗi được nhúng cứng có thể lên tới hàng nghìn. Việc tách chúng sau này đòi hỏi sửa từng tệp, kiểm tra lại từng thành phần và đối mặt với rủi ro các lỗi tương ứng.
Chuỗi được nhúng cứng trực tiếp vào mã nguồn, mẫu hoặc thành phần. Chúng không thể được trích xuất, dịch hoặc thay thế khi chạy mà không sửa đổi chính mã.
Người dùng ở các locale không phải English gặp các phần UI chưa được dịch xen lẫn với nội dung đã dịch — tạo ấn tượng hỗn độn và thiếu chuyên nghiệp.
Cách khắc phục
Chuyển tất cả các chuỗi hiển thị cho người dùng từ đầu sang các tệp tài nguyên.
Thiết lập một khung i18n (ví dụ next-intl, react-i18next, vue-i18n) trước khi viết thành phần đầu tiên.
Tạo cấu trúc tệp tài nguyên (ví dụ messages/de.json), và tham chiếu tất cả chuỗi qua các khóa dịch như t('checkout.submitButton').
Thêm một quy tắc lint hoặc một hook pre-commit, đánh dấu chuỗi nguyên thủy trong các thành phần UI.
Lỗi #10: Dịch tất cả theo nghĩa đen
Không phải tất cả nội dung đều nên được dịch. Tên thương hiệu, tên pháp nhân, thuật ngữ kỹ thuật và một số tên sản phẩm phải giữ nguyên ngôn ngữ gốc. Dịch quá mức có thể gây ra vấn đề pháp lý, không nhất quán thương hiệu và sự nhầm lẫn cho người dùng.
["Duy trì một từ điển 'Không dịch' và chia sẻ nó với tất cả các dịch giả", 'Sử dụng các segment bị khóa hoặc namespace riêng cho các thuật ngữ thương hiệu và pháp lý', 'Luôn cung cấp ghi chú ngữ cảnh cho các chuỗi dễ nhầm lẫn để ngăn ngừa sai bản dịch']
Giữ một glossary 'không dịch' và chia sẻ với mọi người dịch.
Sử dụng các segment bị khóa hoặc namespace riêng cho thuật ngữ thương hiệu và pháp lý.
Luôn cung cấp ghi chú ngữ cảnh cho các chuỗi dễ gây hiểu sai để ngăn chặn dịch sai
Một tình huống điển hình
Một tệp dịch chứa tên công ty 'CloudForge Inc.' và thuật ngữ kỹ thuật 'OAuth 2.0 Token' ở dạng chuỗi có thể dịch.
Biên dịch viên Tây Ban Nha dịch 'CloudForge' sang 'ForjaNube' và 'OAuth 2.0 Token' sang 'token OAuth 2.0'.
Kết quả: người dùng sẽ không tìm thấy công ty với tên thật của nó, và các nhà phát triển đọc tài liệu tiếng Tây Ban Nha sẽ bị nhầm lẫn bởi các thuật ngữ chuyên ngành được dịch không quen.
Tại sao điều này là một vấn đề
Một tên thương hiệu bị dịch sai trong một tài liệu pháp lý có thể làm cho hợp đồng trở nên vô hiệu. Việc sửa các chuỗi ở mọi ngôn ngữ để sửa lỗi này có thể mất vài tuần kiểm tra.
Khi tất cả chuỗi được gửi đến bản dịch mà không có ngữ cảnh, người dịch có thể dịch sai tên thương hiệu ('Apple' → 'Apfel'), thuật ngữ pháp lý ('GmbH' → 'LLC') hoặc các khái niệm kỹ thuật phải giữ bằng tiếng Anh.
Người dùng không tìm thấy sản phẩm dưới tên thương hiệu quen thuộc của họ, các tài liệu pháp lý tham chiếu công ty sai, và tài liệu kỹ thuật trở nên khó hiểu khi các thuật ngữ như 'API Endpoint' được dịch.
Cách khắc phục
Rõ ràng đánh dấu nội dung không thể dịch và cung cấp ghi chú ngữ cảnh cho người dịch.
Tạo một từ điển 'Không dịch' liệt kê tất cả tên thương hiệu, tên sản phẩm, pháp nhân và thuật ngữ chuyên ngành cần giữ nguyên.
Sử dụng một namespace riêng hoặc các key đặc biệt cho nội dung không dịch. Nhiều công cụ i18n hỗ trợ các segment 'khóa' mà dịch giả không thể chỉnh sửa.
Thêm chú giải/miêu tả cho các tệp dịch của bạn để giải thích ngữ cảnh: 'Đây là tên thương hiệu — không dịch' hoặc 'Thuật ngữ chuyên ngành — giữ nguyên bằng tiếng Anh'.
Lỗi #2: Nối chuỗi
Việc ghép các câu từ các fragment nghe có vẻ hợp lý bằng tiếng Anh, nhưng trong các ngôn ngữ khác lại không. Vị trí từ, ngữ pháp và cấu trúc câu khác nhau đáng kể giữa các ngôn ngữ, khiến các chuỗi ghép thành không thể dịch được.
['Không bao giờ xây dựng câu bằng cách nối các đoạn đã dịch', 'Sử dụng chỗ giữ chỗ có tên ('{name}') thay vì theo vị trí ({0}) để rõ ràng hơn', 'Cung cấp bình luận ngữ cảnh cho dịch giả để giải thích nội dung của từng chỗ giữ chỗ']
Không bao giờ ghép câu bằng cách ghép các đoạn đã dịch.
Sử dụng chỗ giữ chỗ có tên ('{name}') thay vì theo vị trí ({0}) để rõ ràng hơn
Cung cấp chú thích ngữ cảnh cho người dịch để giải thích nội dung của từng ký hiệu giữ chỗ
Một tình huống điển hình
Nhà phát triển ghi chú: 'You have ' + count + ' items in your ' + cartType + ' cart' — hoạt động hoàn hảo bằng tiếng Anh.
Người dịch tiếng Đức nhận được ba đoạn riêng biệt và không thể tạo được một câu đúng ngữ pháp, vì trật tự từ cần được thay đổi.
Kết quả: Người dùng tiếng Đức thấy 'Sie haben 5 Artikel in Ihrem Warenkorb Standard' — thô và thiếu chuyên nghiệp.
Tại sao điều này là một vấn đề
Mỗi chuỗi được ghép lại như một quả bom hẹn giờ. Với 20 ngôn ngữ và 50 chuỗi ghép lại, bạn có tới 1.000 lỗi ngữ pháp tiềm ẩn, cần được sửa thủ công.
Việc ghép chuỗi như 'Willkommen ' + userName + ', du hast ' + count + ' neue Nachrichten' đòi hỏi trật tự từ nhất định. Người dịch nhận được các fragment thiếu ngữ cảnh, họ không thể sắp xếp lại.
Người dùng thấy các câu văn ngữ pháp sai như '1 Nachrichten' hoặc các dạng số nhiều hoàn toàn sai. Trong các ngữ cảnh trang trọng, điều này làm giảm độ tin cậy.
Cách khắc phục
Sử dụng thông điệp có tham số với các placeholder có tên để biên dịch viên có thể bố trí lại câu đầy đủ.
Thay thế nối chuỗi bằng một khóa tin nhắn duy nhất với chỗ giữ chỗ: 'cart.summary': 'Bạn có {count} mặt hàng trong giỏ hàng '{cartType}''.
Gọi biến như tham số cho hàm dịch: t('cart.summary', { count: 5, cartType: 'Premium' }).
Dịch giả có thể tự do sắp xếp lại: 'Trong giỏ hàng '{cartType}', có {count} mặt hàng' — tiếng Đức đúng ngữ pháp.
Lỗi #3: Bỏ qua số nhiều
Tiếng Anh có hai dạng số: số ít và số nhiều. Các nhà phát triển thường cho rằng mọi ngôn ngữ hoạt động giống như tiếng Anh. Không phải. Tiếng Ba Lan có 4 dạng, tiếng Ả Rập có 6, và ngay cả những ngôn ngữ như tiếng Pháp xử lý số 0 khác với tiếng Anh.
['Luôn sử dụng ICU MessageFormat hoặc một thư viện tương đương cho nội dung có số đếm', 'Đừng tự viết logic số nhiều của riêng bạn — hãy tin tưởng các quy tắc CLDR', 'Kiểm tra số nhiều với các giá trị như 0, 1, 2, 5, 21 và 100 để bao phủ tất cả các danh mục']
Luôn sử dụng ICU MessageFormat hoặc một thư viện tương đương cho các nội dung có thể đếm được.
Không bao giờ tự viết logic số nhiều của riêng bạn — hãy tin tưởng các quy tắc CLDR.
Kiểm tra số lượng với các giá trị như 0, 1, 2, 5, 21 và 100 để bao phủ tất cả các danh mục.
Một tình huống điển hình
Nhà phát triển viết: count + (count === 1 ? ' Datei' : ' Dateien') — xử lý đúng tiếng Đức.
Biên dịch viên tiếng Ba Lan cần 4 dạng: 1 plik, 2-4 pliki, 5-21 plików, 22-24 pliki. Toán tử ba ngôi đơn giản không thể diễn đạt được.
Kết quả: Người dùng Ba Lan thấy '5 pliki' (dạng sai) thay vì '5 plików' (dạng đúng), và ứng dụng trông như bị hỏng.
Tại sao điều này là một vấn đề
Mỗi danh từ có thể đếm được trong ứng dụng của bạn đều cần xử lý số nhiều. Với 50 chuỗi như vậy và 20 ngôn ngữ, có tới 1.000 quy tắc số nhiều — không thể quản lý thủ công.
Kiểm tra if/else đơn giản (count === 1 ? 'Datei' : 'Dateien') chỉ xử lý tiếng Đức/tiếng Anh. CLDR định nghĩa tới 6 kiểu số nhiều: zero, one, two, few, many và other. Mỗi ngôn ngữ có tập con khác.
Người dùng thấy các câu văn ngữ pháp sai như '1 Nachrichten' hoặc các dạng số nhiều hoàn toàn sai. Trong các ngữ cảnh trang trọng, điều này làm giảm độ tin cậy.
Cách khắc phục
Sử dụng ICU MessageFormat, nó hỗ trợ sẵn tất cả các quy tắc số nhiều của CLDR.
Định nghĩa tin nhắn với ICU-Syntax: 'fileCount': '{count, plural, one {# Datei} other {# Dateien}}'.
Người dịch cung cấp tất cả các dạng số nhiều cần thiết cho ngôn ngữ của họ. Tiếng Ba Lan: '{count, plural, one {# plik} few {# pliki} many {# plików} other {# pliku}}'.
Thư viện i18n của bạn sẽ tự động chọn dạng đúng tại thời điểm chạy dựa trên các quy tắc CLDR của locale đang hoạt động.
Lỗi #4: Chiều rộng phần tử UI cố định
Thiết kế và triển khai các bố cục linh hoạt, có thể thích ứng với độ dài nội dung của tất cả các Locale.
['Không bao giờ sử dụng cố định chiều rộng bằng pixel cho các phần tử có văn bản có thể dịch', 'Lên kế hoạch cho mở rộng 40% của văn bản làm đường cơ sở — một số ngôn ngữ còn mở rộng nhiều hơn', 'Sử dụng CSS Flexbox hoặc Grid cho bố cục có thể thích ứng với độ dài nội dung']
Không bao giờ dùng chiều rộng pixel cố định cho các phần tử có văn bản có thể dịch.
Lên kế hoạch tăng văn bản thêm 40% làm baseline — một số ngôn ngữ sẽ mở rộng hơn.
Sử dụng CSS Flexbox hoặc Grid cho các bố cục tự động điều chỉnh theo độ dài nội dung.
Một tình huống điển hình
Nhà thiết kế tạo một thanh điều hướng gồm 5 nút, mỗi nút có độ rộng đúng 100px — trông rất tuyệt bằng tiếng Anh.
Phiên dịch tiếng Đức: 'Settings' sẽ thành 'Einstellungen' (13 ký tự so với 8), 'Submit' sẽ thành 'Absenden' (8 so với 6). Navbar vượt quá.
Kết quả: Trên thiết bị di động, các nút xếp chồng lên nhau hoặc văn bản bị cắt, điều này khiến điều hướng cho người dùng Đức trở nên vô dụng.
Tại sao das ein Problem ist
Mỗi phần tử có chiều rộng cố định là một điểm gãy tiềm ẩn. Một ứng dụng điển hình có hàng trăm nút, nhãn và cards, tất cả đều phải chịu được mở rộng văn bản.
Container có chiều rộng cố định (width: 120px) và các nút có kích thước cố định sẽ bị cắt hoặc tràn khi văn bản mở rộng. CSS overflow: hidden trong CSS che nội dung im lặng, trong khi overflow: visible phá vỡ bố cục.
Người dùng thấy nhãn bị cắt như 'Einstellu...' thay vì 'Einstellungen', hoặc các nút chồng lên các phần tử kề cạnh. Các thao tác quan trọng trở nên khó đọc hoặc không thể nhấp.
Cách khắc phục điều này
Thiết kế và triển khai các bố cục linh hoạt, có thể thích ứng với độ dài nội dung của tất cả các Locale.
Thay thế chiều rộng cố định bằng min-width、max-width và Flex-Layouts. Sử dụng CSS Grid hoặc Flexbox để phân bổ không gian động.
Đặt container văn bản vào phá vỡ dòng: sử dụng overflow-wrap: break-word và tránh white-space: nowrap với nội dung có thể dịch.
Test UI bằng giả Localization, mở rộng tất cả chuỗi thêm 40% để mô phỏng trường hợp tệ nhất — trước khi gửi chuỗi cho người dịch.
Lỗi #5: Định dạng ngày và số.
Dữ liệu và số liệu có vẻ mang tính phổ quát. Nhưng 01/02/2025 có nghĩa là 2. Januar ở Mỹ và 1. Februar ở châu Âu. Dấu phẩy và dấu chấm trong các số có ý nghĩa khác nhau: 1,000.50 (Mỹ) vs 1.000,50 (Đức). Làm sai dẫn đến nhầm lẫn, lỗi dữ liệu và mất niềm tin.
['Định dạng dữ liệu hoặc số bằng tay bằng chuỗi mẫu — luôn dùng các API Intl', 'Lưu trữ tất cả dữ liệu ở ISO 8601 và tiền tệ ở đơn vị nhỏ nhất (Cent) ở bên trong', 'Kiểm tra với Locales có dấu thập phân, thứ tự ngày và hệ lịch khác nhau']
Không bao giờ dùng chiều rộng pixel cố định cho các phần tử có văn bản có thể dịch.
Lưu trữ mọi dữ liệu ở ISO 8601 và tiền tệ ở đơn vị nhỏ nhất (Cent) ở bên trong.
Kiểm tra số lượng với các locale có dấu phân cách thập phân, thứ tự ngày tháng và hệ lịch khác nhau.
Một tình huống điển hình
Nhà phát triển định dạng ngày thành MM/DD/YYYY và giá thành thành $1,234.50 — đúng cho người dùng Mỹ.
Một người dùng Đức nhìn thấy ngày 03/04/2025 và hiểu nó là 3. April (DD/MM/YYYY-Konvention). Der Preis $1,234.50 sieht aus, als sollte es 1.234,50 sein.
Kết quả: Người dùng đặt vé máy bay cho ngày sai và nghi ngờ về định dạng giá. Yêu cầu hỗ trợ tăng 15% ở thị trường Đức.
Tại sao điều này là một vấn đề
Định dạng ngày và số ảnh hưởng đến mọi hiển thị dữ liệu trong ứng dụng của bạn: bảng, biểu đồ, biểu mẫu, hoá đơn, báo cáo. Một sửa lỗi toàn cầu có thể bao phủ hàng trăm trường hợp.
Chuỗi định dạng cứng như toLocaleDateString('en-US') hoặc định dạng thủ công bằng Template-Literals bỏ qua locale thật sự của người dùng. Ngay cả locale đúng nhưng hệ lịch sai (Gregorianisch vs. Hijri) gây ra vấn đề.
Người dùng đọc dữ liệu sai và nhập dữ liệu ở định dạng sai. Một người dùng châu Âu nhìn thấy 03/04/2025 có thể hiểu là 3. April thay vì 4. März — bỏ lỡ cuộc hẹn hoặc đặt chỗ sai.
Cách khắc phục
Sử dụng API Intl tích hợp hoặc một thư viện định dạng nhận biết locale cho mọi ngày giờ, số và tiền tệ.
Thay thế việc định dạng thủ công bằng Intl.DateTimeFormat(locale) cho dữ liệu và Intl.NumberFormat(locale, { style: 'currency', currency }) cho giá.
Lưu dữ liệu ở định dạng ISO 8601 (YYYY-MM-DD) và chỉ định dạng chúng cho hiển thị theo Locale của người dùng.
Kiểm tra các hiển thị ngày/thời gian và số quan trọng với tối thiểu 5 Locale khác nhau: en-US, de-DE, ja-JP, ar-SA và zh-CN để bao phủ các biến thể định dạng chính.
Lỗi #6: Quên ngôn ngữ RTL
Mô tả: Các ngôn ngữ RTL như Ả Rập, Hebrew và Ba Tư được hơn 500 triệu người dùng. Tuy nhiên, hầu hết các ứng dụng được thiết kế chỉ cho bố cục từ trái sang phải (LTR). Hỗ trợ RTL không chỉ là đảo ngược văn bản — toàn bộ giao diện người dùng phải được lật ngược.
['Sử dụng từ đầu các thuộc tính CSS hợp lý (inline-start/end, block-start/end)', "Kiểm tra ứng dụng với dir='rtl' trên thẻ HTML ở mỗi Sprint-Review", 'Tạo danh sách kiểm tra RTL cho điều hướng, biểu tượng, biểu mẫu và thanh tiến độ']
Từ ngày đầu tiên, chỉ dùng các thuộc tính CSS hợp lý (inline-start/end, block-start/end).
Testa ứng dụng của bạn với dir='rtl' trên phần tử HTML tại mỗi buổi rà soát sprint.
Tạo danh sách kiểm tra RTL cho điều hướng, biểu tượng, biểu mẫu và thanh tiến trình.
Một tình huống điển hình
Nhà phát triển sử dụng margin-left: 16px và text-align: left trên toàn bộ ứng dụng — thói quen LTR chuẩn.
Ứng dụng bắt đầu ở Ả Rập Xê-út. Nút quay lại cho thấy tiến về phía trước, sidebars xuất hiện ở phía sai, và dữ liệu số căn chỉnh sai.
Kết quả: Người dùng Ả Rập rời khỏi ứng dụng sau 30 giây. Đội ngũ cần 4 tuần để tiến hành tái cấu trúc CSS khẩn cấp để khắc phục sự cố.
Tại sao điều này là vấn đề
Hỗ trợ RTL ảnh hưởng tới từng thành phần trong ứng dụng của bạn. Việc thêm RTL sau này thường đòi hỏi phải viết lại 30-50% tất cả các quy tắc CSS và kiểm tra mọi icon và bố cục.
CSS-Properties như margin-left, padding-right, text-align: left và float: left hardcoden die Richtung. Icons mit Richtungsbedeutung (Pfeile, Fortschrittsanzeigen) zeigen in die falsche Richtung. Selbst border-radius-Werte müssen gespiegelt werden.
Người dùng Ả Rập thấy điều hướng ở góc sai, thanh tiến trình quay ngược, và văn bản va chạm với các phần tử UI. Ứng dụng cảm thấy xa lạ và khó sử dụng.
Cách khắc phục
Sử dụng các thuộc tính CSS hợp lý và kiểm tra bố cục RTL từ đầu.
Thay thế mọi thuộc tính CSS vật lý bằng các tương đương logic: margin-left → margin-inline-start, padding-right → padding-inline-end, text-align: left → text-align: start.
Đặt dir trên phần tử gốc HTML dựa trên locale đang hoạt động. Sử dụng :dir(rtl) cho RTL overrides.
Kiểm tra tất cả các icon về ý nghĩa hướng. Thay thế icon định hướng bằng phiên bản đối xứng hoặc dùng transform: scaleX(-1) cho ngữ cảnh RTL.
Lỗi #7: Hình ảnh có chữ
Nhúng văn bản vào hình ảnh — cho dù ở hero banners, nút, infographics hay ảnh chụp màn hình — là ác mộng localization. Mỗi hình ảnh có văn bản phải được làm lại cho mỗi ngôn ngữ, làm tăng chi phí thiết kế và trì hoãn phát hành.
['Đừng bao giờ nhúng văn bản có thể dịch trực tiếp vào hình raster (PNG, JPG)', 'Sử dụng các lớp phủ văn bản CSS trên hình nền cho banner hero và CTA', 'Tự động hóa việc chụp ảnh màn hình cho App Store danh sách và trang tiếp thị']
Đừng bao giờ nhúng văn bản dịch được trực tiếp vào hình raster (PNG, JPG).
Sử dụng Overlay văn bản CSS trên hình nền cho banner hero và CTA.
Tự động hóa việc tạo ảnh chụp màn hình cho danh sách App Store và các trang tiếp thị
Một tình huống điển hình
Một nhà thiết kế tạo banner quảng cáo với văn bản 'Start Your Free Trial' được nhúng trực tiếp vào hình ảnh.
Đội ngũ địa phương hóa dịch tất cả chuỗi UI, nhưng banner trên trang tiếng Đức vẫn hiển thị văn bản tiếng Anh.
Kết quả: Trang đích tiếng Đức có một banner tiếng Anh khiến người dùng khó chịu. Việc tạo banner được địa phương hóa cho 15 ngôn ngữ mất 3 ngày thiết kế và trì hoãn ra mắt.
Tại sao đây là một vấn đề
Một trang tiếp thị điển hình có 5-10 hình ảnh có văn bản. Với 15 ngôn ngữ, đó là 75-150 biến thể hình ảnh, cần được tạo, bảo trì và cập nhật ở mỗi thay đổi thiết kế.
Văn bản, được nhúng vào hình ảnh (PNG, JPG, SVG với văn bản nhúng), không thể được trích xuất bởi các công cụ dịch. Mỗi phiên bản địa phương hóa đòi hỏi một nhà thiết kế chỉnh sửa thủ công tệp nguồn, xuất và tải lên.
Người dùng nhìn thấy hình ảnh có văn bản bằng một ngôn ngữ nước ngoài, hoặc tệ hơn, sự kết hợp giữa giao diện người dùng được dịch và hình ảnh chưa dịch. Điều này trông thiếu nhất quán và làm giảm độ tin cậy của thương hiệu.
Cách khắc phục
Phân tách văn bản khỏi hình ảnh bằng lớp phủ CSS, SVG với các phần tử văn bản có thể dịch hoặc sinh hình ảnh động.
Áp dụng CSS để đặt văn bản có thể dịch lên trên nền hình ảnh: đặt lớp văn bản ở vị trí tuyệt đối trên container hình ảnh.
Đối với đồ họa thông tin hoặc sơ đồ, hãy sử dụng SVG với các phần tử <text> tham chiếu khóa dịch, thay vì nhúng chuỗi thô.
Đối với ảnh chụp màn hình ứng dụng trong tài liệu tiếp thị, tự động hóa quá trình chụp ảnh màn hình bằng các công cụ như Fastlane (di động) hoặc Playwright (Web), để chụp ảnh cho mỗi locale.
Lỗi #8: Thiếu bản dịch chưa được xử lý
Trong quá trình phát triển, bản dịch luôn không đầy đủ. Các tính năng mới thêm chuỗi nhanh hơn người dịch có thể dịch. Nếu không có xử lý fallback thích hợp, thiếu bản dịch sẽ gây ra lỗi, giao diện người dùng trống hoặc các khóa dịch thô hiển thị cho người dùng.
['Luôn cấu hình ít nhất một ngôn ngữ dự phòng trong thiết lập i18n của bạn', 'Ghi log các khóa dịch thiếu vào hệ thống giám sát để theo dõi', 'Đặt tỷ lệ bao phủ dịch tối thiểu trước khi một locale được phát hành']
Luôn cấu hình ít nhất một ngôn ngữ dự phòng trong cài đặt i18n của bạn
Ghi nhận các khóa dịch còn thiếu vào hệ thống giám sát của bạn để theo dõi
Đặt mức độ bao phủ dịch tối thiểu trước khi một locale được phát hành
Một kịch bản điển hình
Lập trình viên thêm một khu vực 'Premium Features' mới với 15 khóa dịch. Phiên bản tiếng Anh được phát hành ngay.
Các bản dịch tiếng Pháp vẫn chưa hoàn tất. Trang tiếng Pháp hiển thị các khóa thô: 'premium.feature1.title', 'premium.feature1.description'.
Kết quả: Người dùng Pháp thấy một trang hỏng đầy các key tên phía nhà phát triển. Đội ngũ hỗ trợ nhận được hàng chục báo cáo lỗi.
Tại sao đó là một vấn đề
Khi ứng dụng của bạn lớn lên, khoảng cách giữa chuỗi tiếng Anh và bản dịch sang các ngôn ngữ khác càng lớn. Một ứng dụng hỗ trợ 100 ngôn ngữ và 2.000 chuỗi có thể có hơn 10.000 bản dịch thiếu.
Nếu thiếu fallback logic, một khóa dịch thiếu sẽ trả về undefined, null hoặc chuỗi khóa gốc (ví dụ 'checkout.confirmButton'). Các engine mẫu có thể ném lỗi, khiến trang bị treo hoặc không render.
Người dùng thấy giao diện hỏng: các nút trống, nhãn thiếu hoặc chuỗi rắc rối như 'nav.settings.title' thay vì văn bản thực tế. Điều này gây bối rối và thiếu chuyên nghiệp.
Cách khắc phục
Cấu hình một chuỗi fallback mạnh mẽ và theo dõi tính bao phủ bản dịch trên tất cả locale.
Thiết lập một chuỗi fallback ngôn ngữ trong cấu hình i18n của bạn: các khóa tiếng Pháp thiếu (fr) sẽ tự động quay về tiếng Anh (en).
Thêm một trình xử lý Missing-Key để ghi lại các khóa chưa được dịch tới hệ thống giám sát của bạn (ví dụ Sentry, Datadog) mà không làm gián đoạn trải nghiệm người dùng.
Xây dựng một bảng điều khiển bao phủ dịch (translation coverage dashboard) theo dõi tỉ lệ hoàn thiện cho từng locale và chặn phát hành khi mức bao phủ dưới ngưỡng (ví dụ 95%).
Lỗi #9: Vấn đề mã hóa ký tự
Vấn đề mã hóa ký tự là kẻ giết người thầm lặng của quá trình địa phương hóa. Mọi thứ trông ổn với tiếng Anh và các ngôn ngữ châu Âu, nhưng khi bạn thêm tiếng Trung, Nhật Bản, tiếng Hàn, tiếng Ả Rập hoặc emoji, các ký tự bị méo mó (Mojibake) sẽ xuất hiện. Những lỗi này nổi tiếng khó phát hiện.
['Sử dụng mã hóa UTF-8 ở mọi nơi — tệp nguồn, cơ sở dữ liệu, phản hồi API và thẻ meta HTML', 'Sử dụng utf8mb4 trong MySQL (không phải utf8) để hỗ trợ đầy đủ Unicode bao gồm Emoji', 'Kiểm tra với nội dung thật bằng CJK, Ả Rập và Emoji để bắt đầu phát hiện sớm các vấn đề mã hóa']
Sử dụng mã UTF-8 ở mọi nơi — tệp nguồn, cơ sở dữ liệu, phản hồi API và thẻ meta HTML
Nutzt utf8mb4 trong MySQL (không phải utf8) để hỗ trợ đầy đủ vùng Unicode bao gồm emoji.
Kiểm tra bằng nội dung thật, gồm CJK, tiếng Ả Rập và Emoji, để phát hiện sớm các vấn đề mã hóa.
Một tình huống điển hình
Nhà phát triển thiết lập cơ sở dữ liệu MySQL với collation latin1 (tiêu chuẩn cũ). Mã nguồn ứng dụng dùng UTF-8.
Người dùng Nhật Bản đăng ký bằng tên thật. Cơ sở dữ liệu lưu '田中太郎' ở dạng bytes bị hỏng.
Kết quả: hồ sơ người dùng hiển thị văn bản bị méo. Tệ hơn nữa: tìm kiếm và sắp xếp cho tất cả các tên CJK sẽ bị hỏng, ảnh hưởng tới hàng nghìn người dùng.
Tại sao đây là một vấn đề
Các sự cố mã hóa dữ liệu lan rộng khắp stack của bạn. Một cơ sở dữ liệu với collation được cấu hình sai có thể làm hỏng hàng triệu bản ghi — việc sửa chữa đòi hỏi di chuyển dữ liệu tốn kém.
Mã hóa không nhất quán trên toàn bộ stack — UTF-8 trong các tệp nguồn, Latin-1 trong cơ sở dữ liệu và Windows-1252 trong phản hồi API — phá hỏng ký tự đa byte. Chỉ một lớp được cấu hình sai cũng có thể biến '日本語' thành '????' hoặc '日本èª'.
Người dùng thấy văn bản bị méo mó, dấu hỏi hoặc ô vuông ở nơi ngôn ngữ của họ nên hiển thị. Trong trường hợp xấu nhất, các nhập liệu biểu mẫu có thể bị hỏng vĩnh viễn trong cơ sở dữ liệu.
Cách khắc phục
Ép buộc mã hóa UTF-8 nhất quán trên mọi lớp của stack ứng dụng.
Đặt tất cả các tệp nguồn ở UTF-8 (cấu hình trình soạn thảo và .editorconfig). Thêm <meta charset='UTF-8'> vào HTML và 'Content-Type: application/json; charset=utf-8' vào phản hồi API.
Cấu hình cơ sở dữ liệu ở utf8mb4 (không phải chỉ utf8, là một tập con 3-byte của MySQL). Đặt collation kết nối là utf8mb4_unicode_ci.
Chọn các phông chữ bao phủ hệ chữ mục tiêu của bạn: Latin, Cyrillic, CJK, Ả Rập, Devanagari. Sử dụng hệ thống Font Stacks hoặc Google Fonts với các Subsets ngôn ngữ để tối ưu hóa tải.
Kiểm tra triển khai i18n của bạn
Kiểm tra mở rộng độ dài
Phát triển độ dài: Mở rộng tất cả các chuỗi dịch thêm 30-40% để mô phỏng sự mở rộng văn bản ở các ngôn ngữ phong phú từ vựng như Đức, Phần Lan hoặc Hy Lạp. Điều này giúp bao phủ các container có chiều rộng cố định, nhãn bị cắt và các nút tràn ra trước khi bắt đầu dịch. Nhiều công cụ giả địa phương hóa cung cấp tính năng này như một tính năng tích hợp.
"Senden" → "Ṡééééñðéñ_éxpáñðéð" (40% länger)
Định vị giả
Định vị giả (pseudo-localization) thay thế mọi ký tự bằng một ký tự có dấu tương ứng (ví dụ 'a' → 'á') và bọc chuỗi bằng các dấu hiệu như [!! và !!]. Điều này làm nổi bật ngay các chuỗi được gán cứng và các chuỗi từ hệ thống dịch. Hãy chạy Định vị giả như một phần của pipeline CI để tự động bắt các regression.
Mọi văn bản trên màn hình không được bao quanh bởi dấu [!! !!] đều là hard-coded và phải được tách ra khỏi mã. Bài kiểm tra này bắt 95% các chuỗi bị bỏ sót trong vòng dưới một phút.
"Gửi tin nhắn" → "[!! Ñáçḥŕíçḥṫ ṡéñðéñ !!]"
Kiểm tra bố cục RTL
Ngay cả khi bạn chưa có bản dịch tiếng Ả Rập hoặc Hebrew, bạn vẫn có thể kiểm tra RTL bằng cách thêm dir='rtl' vào gốc HTML của mình. Điều này ngay lập tức phát hiện các lỗi CSS định hướng: biểu tượng căn chỉnh sai, lề ở bên sai, điều hướng bị hỏng và các phần tử Flex bị sắp xếp sai. Hãy biến nó thành một kiểm tra tiêu chuẩn trong mỗi sprint — nó chỉ mất 10 giây để bật/tắt và có thể bắt các vấn đề mà trước đây có thể mất vài tuần để sửa trong Production.
Danh sách kiểm tra i18n
['Tất cả các chuỗi hiển thị cho người dùng đã được đưa vào các tệp tài nguyên', 'Không ghép chuỗi để tạo câu', 'Quy tắc số nhiều được triển khai bằng ICU MessageFormat hoặc tương đương', 'Định dạng ngày, giờ và số bằng các API nhận diện theo locale', 'Đã kiểm tra bố cục RTL với nội dung tiếng Ả Rập hoặc Do Thái', 'Giao diện người dùng linh hoạt, không có chiều rộng cố định cho các phần tử chứa văn bản', 'Đã cấu hình ngôn ngữ dự phòng và kiểm tra khi thiếu khóa', 'Mã hóa UTF-8 nhất quán trên mọi tệp và cơ sở dữ liệu', 'Siêu dữ liệu App Store và Play Store được localize cho mỗi thị trường', 'Ảnh chụp màn hình và tài sản tiếp thị cho mỗi ngôn ngữ được cập nhật']