File dịch / Tài liệu dịch
Hướng dẫn toàn tập về File dịch và Tài liệu dịch trong ngành Local hóa
1. File dịch là gì? Nói một cách dễ hiểu
Hãy tưởng tượng bạn có một phần mềm viết bằng tiếng Anh, và bạn muốn biến nó thành tiếng Việt, tiếng Pháp, tiếng Nhật… Để làm được điều đó, bạn không thể ngồi sửa từng dòng code được — mà phải dùng đến file dịch.
File dịch (translation file) chính là một tệp tin chứa các cặp bản dịch: một bên là nguồn (text gốc) và một bên là đích (text đã được dịch sang ngôn ngữ mục tiêu). Các phần mềm, ứng dụng sẽ đọc những file này và hiển thị nội dung phù hợp với ngôn ngữ người dùng đang chọn.
2. Tại sao lại cần dùng file dịch? (Câu chuyện thực tế)
Hãy cùng xem xét một tình huống cụ thể nhé.
Giả sử bạn đang làm một ứng dụng web bán hàng online. Lúc đầu, bạn viết hardcoded (gắn cứng) toàn bộ text vào code:
// ❌ Cách làm xấu — hardcoded text
const greeting = "Xin chào";
const buyButton = "Mua ngay";
const cartLabel = "Giỏ hàng";
const checkoutLabel = "Thanh toán";
Vấn đề nảy sinh khi bạn muốn thêm tiếng Anh, tiếng Nhật, tiếng Hàn… Bạn sẽ phải sửa code khắp nơi, rất nhiều lần, và dễ gây lỗi.
Giải pháp đúng: Tách riêng text ra file dịch.
// ✅ Cách làm đúng — dùng file dịch
// file vi.js
export const messages = {
greeting: "Xin chào",
buyButton: "Mua ngay",
cartLabel: "Giỏ hàng",
checkoutLabel: "Thanh toán",
};
// file en.js
export const messages = {
greeting: "Hello",
buyButton: "Buy Now",
cartLabel: "Cart",
checkoutLabel: "Checkout",
};
Và trong code chính, bạn chỉ cần:
import { messages as vi } from "./locales/vi.js";
import { messages as en } from "./locales/en.js";
const currentLocale = detectUserLocale(); // tự động phát hiện ngôn ngữ
const messages = currentLocale === "vi" ? vi : en;
// Hiển thị
console.log(messages.greeting); // "Xin chào" hoặc "Hello"
Như bạn thấy, việc thêm ngôn ngữ mới chỉ cần thêm một file mới — không động chạm gì đến code chính.
3. Các định dạng file dịch phổ biến nhất
3.1 JSON — Phổ biến nhất cho Web & Mobile
{
"common": {
"greeting": "Xin chào",
"goodbye": "Tạm biệt",
"loading": "Đang tải..."
},
"buttons": {
"submit": "Gửi",
"cancel": "Hủy",
"save": "Lưu"
},
"messages": {
"error_network": "Lỗi kết nối mạng, vui lòng thử lại",
"success_save": "Đã lưu thành công!"
}
}
Ưu điểm: Dễ đọc, dễ viết, tương thích với mọi ngôn ngữ lập trình.
Nhược điểm: Không hỗ trợ tốt các trường hợp đặc biệt như số nhiều (pluralization), gender (giới tính).
3.2 PO / MO — Chuẩn mực trong thế giới phần mềm
Đây là định dạng được dùng bởi gettext, một công cụ localize hóa huyền thoại. File PO là text thuần, còn file MO là bản dịch nhị phân đã được biên dịch từ PO.
# File: messages.po
msgid ""
msgstr ""
"Project-Id-Version: MyApp 1.0\n"
"Language: vi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
#: src/components/Button.js:12
msgid "greeting"
msgstr "Xin chào"
#: src/components/Button.js:15
msgid "buy_button"
msgstr "Mua ngay"
#: src/pages/Product.js:45
msgid "cart_total"
msgstr "Tổng cộng: {amount} VND"
Cấu trúc mỗi entry trong file PO gồm:
- msgid — văn bản nguồn (gốc)
- msgstr — bản dịch sang ngôn ngữ đích
- Comment / fuzzy flag — ghi chú cho dịch giả
- Plural forms — hỗ trợ số nhiều
# Ví dụ về pluralization trong tiếng Việt (chỉ có số ít)
msgid "items_count"
msgid_plural "items_count_plural"
msgstr[0] "có {n} sản phẩm"
msgstr[1] "có {n} sản phẩm"
# Ví dụ tiếng Anh (số ít và số nhiều khác nhau)
msgid "item_count"
msgid_plural "item_count_plural"
msgstr[0] "có {n} sản phẩm"
msgstr[1] "có {n} sản phẩm"
Công cụ dịch PO: Dùng Poedit — phần mềm phổ biến nhất, giao diện trực quan, hỗ trợ kiểm tra lỗi, đếm tiến độ dịch.
3.3 i18n JSON / XLIFF — Định dạng cho Enterprise
XLIFF (XML Localization Interchange File Format) là chuẩn ISO, thường được dùng trong các công ty lớn:
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" target-language="vi" datatype="plaintext">
<body>
<trans-unit id="greeting">
<source>Hello</source>
<target>Xin chào</target>
</trans-unit>
<trans-unit id="farewell">
<source>Goodbye</source>
<target>Tạm biệt</target>
</trans-unit>
</body>
</file>
</xliff>
Ưu điểm của XLIFF: Hỗ trợ metadata phong phú (comment, context, trạng thái dịch, độ tin cậy), phù hợp làm việc với các nền tảng CAT (Computer-Aided Translation) như Smartling, Phrase, Crowdin.
3.4 YAML — Nhẹ nhàng và sạch sẽ
# vi.yml
app:
title: "Ứng dụng của tôi"
welcome: "Chào mừng bạn đến"
nav:
home: "Trang chủ"
about: "Giới thiệu"
contact: "Liên hệ"
Thường dùng với Ruby on Rails, Django, một số framework JavaScript.
4. Cấu trúc một file dịch chuẩn
Một file dịch chuyên nghiệp thường có cấu trúc phân cấp rõ ràng. Dưới đây là ví dụ thực tế từ một dự án e-commerce:
locales/
├── vi/
│ ├── common.json # Văn bản chung (nút bấm, nhãn, thông báo)
│ ├── validation.json # Thông báo lỗi form (email không đúng định dạng...)
│ ├── cart.json # Nội dung giỏ hàng
│ ├── checkout.json # Quy trình thanh toán
│ └── profile.json # Trang cá nhân
├── en/
│ ├── common.json
│ ├── validation.json
│ ├── cart.json
│ ├── checkout.json
│ └── profile.json
└── ja/
├── common.json
├── validation.json
└── ...
Cách phân tách như vậy giúp:
- Dịch giả chỉ làm việc với file cần thiết, không bị rối
- Developer dễ load từng phần riêng lẻ
- Reviewer dễ kiểm tra tính nhất quán
5. Quy trình dịch một file từ đầu đến cuối
Đây là quy trình thực tế mà các đội localize hóa hay áp dụng:
Bước 1: Trích xuất chuỗi cần dịch (Extract)
Dùng công cụ để quét code và tìm tất cả text cần dịch.
# Ví dụ dùng i18next-extract
npx i18next-extract --config i18next-parser.config.js
# Kết quả tạo ra các file mẫu (.pot) cho từng ngôn ngữ
Hoặc đơn giản hơn, viết một script Python tự động:
# extract_strings.py
import re
import os
def extract_strings(directory):
strings = set()
patterns = [
r't["\']([^"\']+?)["\']', # t("text")
r't\{["\']([^"\']+?)["\']\}', # t({key})
r'__\(["\']([^"\']+?)["\']\)', # __("text")
]
for root, _, files in os.walk(directory):
for f in files:
if f.endswith(('.js', '.jsx', '.ts', '.tsx', '.vue', '.py')):
with open(os.path.join(root, f), encoding='utf-8') as fp:
content = fp.read()
for pattern in patterns:
strings.update(re.findall(pattern, content))
return sorted(strings)
if __name__ == "__main__":
result = extract_strings("./src")
for s in result:
print(s)
Bước 2: Tạo file dịch mẫu
Sau khi trích xuất, tạo file mẫu cho ngôn ngữ mục tiêu:
{
"Hello": "",
"Goodbye": "",
"Login": "",
"Password": "",
"Email address": ""
}
Phần msgstr (giá trị) trống — chờ dịch giả điền vào.
Bước 3: Dịch
Dịch giả dùng Poedit, Google Translate (có chọn lọc), hoặc dịch thủ công. Quan trọng nhất là giữ nguyên các placeholder như {name}, {count}, %s…
Bước 4: Kiểm tra chất lượng
Các lỗi thường gặp khi dịch:
| Lỗi | Ví dụ sai | Ví dụ đúng |
|---|---|---|
| Thiếu placeholder | "Xin chào, bạn" |
"Xin chào, {name}" |
| Placeholder sai vị trí | "{name} chào bạn" (tiếng Anh) |
"Xin chào, {name}" (tiếng Việt) |
| Ký tự đặc biệt bị lỗi | "O\"k" |
"OK" hoặc "Được" |
| Khoảng trắng thừa | " Lưu " |
"Lưu" |
| HTML tag bị dịch | <b>Tôi đang chờ</b> dịch thành <b>Je attend</b> |
Giữ nguyên tag, chỉ dịch text |
Bước 5: Tích hợp vào project
// i18n.js — Cấu hình i18next
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import viCommon from "./locales/vi/common.json";
import enCommon from "./locales/en/common.json";
i18n.use(initReactI18next).init({
resources: {
vi: { common: viCommon },
en: { common: enCommon },
},
lng: "vi",
fallbackLng: "en",
interpolation: {
escapeValue: false, // React tự escape
},
});
export default i18n;
6. Những thách thức thực tế khi dịch file
6.1 Vấn đề mở rộng chiều dài text
Tiếng Việt thường dài hơn tiếng Anh 30-50%. Ví dụ:
- EN: “Save” → VN: “Lưu”
- EN: “Close” → VN: “Đóng”
- EN: “Send” → VN: “Gửi”
- EN: “No results found” → VN: “Không tìm thấy kết quả nào”
Giải pháp: Thiết kế UI linh hoạt, không hardcode chiều rộng button.
/* ❌ Đừng làm thế này */
.button {
width: 100px; /* cứng nhắc, tiếng Việt sẽ bị vỡ */
}
/* ✅ Làm thế này */
.button {
min-width: 100px;
padding: 8px 16px;
white-space: nowrap; /* tránh xuống dòng */
}
6.2 Vấn đề số nhiều
Tiếng Việt không phân biệt số ít/số nhiều qua hình thái từ. Nhưng tiếng Anh thì có:
- “1 item” vs “5 items”
- “1 comment” vs “10 comments”
File dịch phải xử lý được điều này:
{
"item_count_one": "có {count} sản phẩm",
"item_count_other": "có {count} sản phẩm"
}
// Dùng i18next để xử lý
const count = 5;
t('item_count', { count }); // → "có 5 sản phẩm"
6.3 Vấn đề gender (giới tính)
Một số ngôn ngữ (Pháp, Tây Ban Nha, Ả Rập) có giới tính trong từ vựng. Tiếng Việt thì may mắn không có vấn đề này. Nhưng nếu project hướng ra nhiều ngôn ngữ, cần chuẩn bị:
{
"user_message": "{{userName}} đã gửi tin nhắn cho {{recipientName}}",
"user_message_male": "{{userName}} (nam) đã gửi tin nhắn cho {{recipientName}}",
"user_message_female": "{{userName}} (nữ) đã gửi tin nhắn cho {{recipientName}}"
}
6.4 Vấn đề RTL (Right-to-Left)
Tiếng Ả Rập, Hebrew đọc từ phải sang trái. File dịch cần lưu ý:
{
"address": "{{street}}, {{city}}, {{country}}"
}
Khi hiển thị tiếng Ả Rập, toàn bộ layout phải lật ngược lại (mirrored). CSS hỗ trợ điều này:
[dir="rtl"] {
text-align: right;
}
7. Công cụ hỗ trợ dịch file hiệu quả
7.1 Poedit (miễn phí cho personal)
- Phần mềm desktop, mở file PO trực tiếp
- Hỗ trợ memory dịch (dịch những câu tương tự sẽ gợi ý)
- Kiểm tra lỗi chính tả, định dạng
- Hỗ trợplural forms tự động
7.2 Crowdin / Smartling / Phrase (nền tảng đám mây)
- Collaborative translation: nhiều người cùng dịch
- Integration với GitHub, GitLab — tự động sync khi push code
- Context preview: xem text trong ngữ cảnh thực tế (screenshots)
- Translation memory + term base (quản lý thuật ngữ)
7.3 script tự động — CI/CD pipeline
# .github/workflows/i18n-sync.yml
name: Sync Translation Files
on:
push:
branches: [main]
jobs:
extract-and-sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Extract strings
run: npx i18next-extract
- name: Upload to Crowdin
uses: crowdin/github-action@master
with:
upload_sources: true
upload_translations: true
download_translations: true
env:
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
8. Best Practices — Những điều nên và không nên làm
✅ Nên làm
- Giữ key nhất quán — dùng snake_case hoặc camelCase đồng nhất “`json // ✅ Tốt “user_profile_name”: “Tên người dùng” “user_profile_email”: “Email người dùng”
// ❌ Xấu “profile.name”: “Tên” “UserProfileEmail”: “Email”
2. **Thêm comment cho dịch giả**
```json
{
"submit": "Gửi",
"_comment_submit": "Dùng cho nút gửi form, không phải nút gửi email"
}
Phân trang file theo module — như ví dụ ở mục 4
Luôn có fallback language — tiếng Anh là fallback phổ biến nhất
Test với text dài nhất có thể — trước khi xuất bản, thử đổi ngôn ngữ sang tiếng Đức (dài) hoặc tiếng Nhật (rất ngắn) để kiểm tra UI
❌ Không nên làm
- Không dịch HTML tag — chỉ dịch nội dung text bên trong
- Không hardcoded URL trong file dịch — nếu có, dùng parameter
- Không quên escape character — dấu
"trong text dịch có thể phá file JSON - Không bỏ qua null/undefined — luôn có fallback value
- Không dịch trực tiếp từ Google Translate — dùng làm tham khảo, nhưng cần người bản xứ校对 (proofread)
9. Một project thực tế hoàn chỉnh
Dưới đây là cấu trúc một dự án React + i18next thực sự:
my-app/
├── src/
│ ├── locales/
│ │ ├── en/
│ │ │ ├── common.json
│ │ │ ├── auth.json
│ │ │ └── dashboard.json
│ │ └── vi/
│ │ ├── common.json
│ │ ├── auth.json
│ │ └── dashboard.json
│ ├── i18n/
│ │ └── config.js
│ ├── components/
│ │ ├── Button.jsx
│ │ └── LoginForm.jsx
│ ├── pages/
│ │ └── Dashboard.jsx
│ └── App.jsx
├── package.json
└── i18next-parser.config.js
i18next-parser.config.js:
module.exports = {
locales: ["en", "vi"],
keySeparator: false,
namespaceSeparator: ".",
defaultNamespace: "common",
output: "src/locales/$LOCALE/$NAMESPACE.json",
input: ["src/**/*.{js,jsx}"],
sort: true,
};
src/i18n/config.js:
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import enCommon from "../locales/en/common.json";
import viCommon from "../locales/vi/common.json";
import enAuth from "../locales/en/auth.json";
import viAuth from "../locales/vi/auth.json";
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: { common: enCommon, auth: enAuth },
vi: { common: viCommon, auth: viAuth },
},
fallbackLng: "en",
debug: process.env.NODE_ENV === "development",
interpolation: {
escapeValue: false,
},
react: { useSuspense: true },
});
export default i18n;
src/components/LoginForm.jsx:
import { useTranslation } from "react-i18next";
export default function LoginForm() {
const { t } = useTranslation("auth");
return (
<form>
<h1>{t("welcome_title")}</h1>
<input placeholder={t("email_placeholder")} />
<input type="password" placeholder={t("password_placeholder")} />
<button type="submit">{t("login_button")}</button>
<p>{t("forgot_password")}</p>
</form>
);
}
src/locales/vi/auth.json:
{
"welcome_title": "Đăng nhập",
"email_placeholder": "Nhập email của bạn",
"password_placeholder": "Mật khẩu",
"login_button": "Đăng nhập",
"forgot_password": "Quên mật khẩu?",
"error_invalid_credentials": "Email hoặc mật khẩu không đúng"
}
10. Tóm tắt nhanh — Bảng đối chiếu
| Định dạng | Ngôn ngữ | Công cụ phổ biến | Phù hợp cho |
|---|---|---|---|
| JSON | Mọi ngôn ngữ | i18next, react-i18next | Web app, React, Vue |
| PO/MO | Mọi ngôn ngữ | Poedit, gettext | Desktop app, Python, PHP |
| XLIFF | Mọi ngôn ngữ | Crowdin, Smartling | Enterprise, đội ngũ lớn |
| YAML | Mọi ngôn ngữ | Rails i18n, Django | Ruby, Python projects |
Lời kết
File dịch không chỉ đơn thuần là một tập tin chứa text. Đó là cầu nối giữa sản phẩm và người dùng trên khắp thế giới. Một file dịch được cấu trúc tốt, được dịch cẩn thận, sẽ quyết định trải nghiệm người dùng cuối cùng — ngay cả khi code của bạn hoàn hảo đến đâu.
Hy vọng bài viết này đã giúp bạn hiểu rõ hơn về file dịch, từ khái niệm đến thực hành. Nếu bạn đang bắt đầu một dự án local hóa, hãy nhớ: đầu tư vào cấu trúc file dịch ngay từ đầu — sau này sẽ tiết kiệm rất nhiều thời gian và công sức.
