Việc triển khai quốc tế hóa (i18n) cho các ứng dụng thường là một thách thức lớn, đặc biệt khi yêu cầu này không được xem xét từ giai đoạn đầu của dự án. Phương pháp truyền thống đòi hỏi việc chỉnh sửa hàng loạt mã nguồn, thay thế từng chuỗi văn bản bằng các placeholder như ${t.xxx} (điển hình như với vue-i18n). Với một dự án lớn, khối lượng công việc này có thể trở nên khổng lồ và không thể chấp nhận được ở các giai đoạn phát triển sau.
Mặc dù có nhiều giải pháp i18n hiện có, nhưng đa số chúng chỉ giải quyết vấn đề cơ bản – giúp bạn có thể sử dụng i18n, nhưng vẫn tồn tại một điểm yếu chung: quá phức tạp và tốn công.
Vậy, có giải pháp plugin nào cho phép chúng ta tự động hóa hoàn toàn quy trình này mà không cần động tay vào mã nguồn chính không?
Chắc chắn là có!
Giới thiệu Plugin i18n Tự Động
wenps/auto-i18n-translation-plugins chính là một plugin đa năng được thiết kế để giải quyết vấn đề này. Nó yêu cầu tối thiểu chỉ ba dòng cấu hình đơn giản:
import { createAutoI18nPlugin } from 'vite-auto-i18n-plugin';
import { YoudaoTranslator } from 'auto-i18n-translation-plugins'; // Giả định đây là lớp dịch Youdao
const i18nAutomationPlugin = createAutoI18nPlugin({
targetLanguages: ['en', 'ko', 'ja'], // Danh sách các ngôn ngữ đích
translationService: new YoudaoTranslator({ // Bộ dịch API
appId: 'your-youdao-app-id',
appKey: 'your-youdao-app-key'
})
});
Sau đó, bạn chỉ cần tích hợp i18nAutomationPlugin vào mảng plugins trong file cấu hình Vite của mình:
import { defineConfig } from 'vite';
export default defineConfig({
resolve: {},
plugins: [i18nAutomationPlugin] // Plugin đã cấu hình ở trên
});
Khi plugin chạy thành công, nó sẽ tạo ra một gói ngôn ngữ cuối cùng trong thư mục lang ở thư mục gốc của dự án. Bước tiếp theo là nhập gói này vào điểm khởi đầu của ứng dụng. Ví dụ, trong ứng dụng Vue, bạn sẽ thêm vào main.ts:
// main.ts
import '../lang/index.js'; // Đường dẫn đến file runtime của plugin
Plugin sẽ đọc ngôn ngữ hiện tại từ localStorage. Vì vậy, để chuyển đổi ngôn ngữ, bạn chỉ cần cập nhật giá trị trong localStorage và tải lại trang:
function setApplicationLanguage(languageCode) {
window.localStorage.setItem('currentAppLanguage', languageCode); // Mã ngôn ngữ, ví dụ: 'en'
window.location.reload(); // Tải lại trang để áp dụng ngôn ngữ mới
}
Đáng chú ý, plugin này cũng tương thích với các công cụ đóng gói khác như Webpack và Rollup.
Cài đặt
Bạn có thể cài đặt phiên bản phù hợp với công cụ đóng gói của mình:
pnpm i vite-auto-i18n-plugin -D
pnpm i webpack-auto-i18n-plugin -D
Đối với YoudaoTranslator, bạn cần đăng ký API key của riêng mình. Hoặc, bạn có thể cấu hình để sử dụng Google Translator miễn phí thông qua proxy (xem chi tiết trong README.md của plugin).
Trang đăng ký API dịch thuật Youdao: https://ai.youdao.com/product-fanyi-text.s
Ưu điểm nổi bật
Điểm khác biệt cốt lõi giữa plugin này và các giải pháp khác trên thị trường là khả năng tự động hóa hoàn toàn cả hai bước: dịch thuật và thay thế văn bản. Quá trình dịch diễn ra trước, còn việc thay thế chuỗi văn bản được thực hiện trong quá trình build, hoàn toàn trong suốt và không yêu cầu sự can thiệp của người dùng. Sau khi tích hợp, bạn không cần thay đổi bất kỳ văn bản nào trong mã nguồn của mình, và plugin cũng không chỉnh sửa trực tiếp các file code của bạn. Mọi thứ dường như "như cũ" – thật tuyệt vời!
So với các phương pháp truyền thống, việc sử dụng plugin này có thể giúp giảm hơn 90% khối lượng công việc liên quan đến i18n.
Mặc dù dịch máy có thể có những sai lệch nhỏ trong ngữ cảnh cụ thể, nhưng bạn hoàn toàn có thể thủ công chỉnh sửa những đoạn văn bản dịch đã được tạo ra. Plugin sẽ chỉ dịch những nội dung mới hoặc chưa có, bảo toàn các chỉnh sửa của bạn.
Sau khi plugin chạy thành công, một thư mục lang sẽ được tạo trong thư mục gốc của dự án, chứa file lang/index.json – đây chính là gói dịch thuật cuối cùng.
Mẹo: Đối với Vite, để tiết kiệm số lượng lượt gọi API dịch thuật, bạn nên chạy
npm run buildtrước.
Cấu trúc của file index.json sẽ trông như sau:
{
"h_home": {
"zh-cn": "首页",
"en": "Home page",
"ko": "첫 페이지",
"ja": "トップページです"
},
"h_product": {
"zh-cn": "产品",
"en": "product",
"ko": "제품",
"ja": "製品です"
},
"h_about": {
"zh-cn": "关于",
"en": "About us",
"ko": "관련",
"ja": "についてです"
}
}
Các khóa như h_home là một hàm băm (hash) duy nhất của văn bản gốc. Miễn là văn bản gốc không thay đổi, plugin sẽ không dịch lại, cho phép bạn tự do chỉnh sửa kết quả dịch mà không lo bị ghi đè tự động.
Nếu file này không đầy đủ hoặc văn bản gốc thay đổi (dẫn đến hash thay đổi), plugin sẽ tự động bổ sung hoặc cập nhật (dịch tăng cường) trong quá trình build.
Phân tích Nguyên lý hoạt động
Phần này đi sâu vào cách
auto-i18n-translation-pluginshoạt động. Nếu bạn không muốn đi sâu vào chi tiết kỹ thuật, có thể bỏ qua phần này.
Làm thế nào plugin nhận diện văn bản cần dịch?
Để hiểu cách plugin xác định các văn bản cần dịch, trước tiên chúng ta cần làm quen với cơ chế của Babel. Babel là một công cụ biên dịch JavaScript chuyển đổi mã nguồn thành biểu diễn trung gian có thể thao tác được thông qua quy trình sau:
1. Đánh dấu văn bản
- Phân tích cú pháp (Parse): Babel phân tích mã JavaScript đầu vào thành cây cú pháp trừu tượng (AST), phân tách cấu trúc mã thành các nút (Node) rõ ràng theo cấp bậc. Ví dụ, các chuỗi ký tự, template literals, hoặc phần tử JSX sẽ được chuyển đổi thành các kiểu nút AST tương ứng (như
StringLiteral,TemplateLiteral,JSXText). Với văn bản tiếng Việt, chúng thường xuất hiện trong các nút AST này, do đó việc xử lý các nút này là trọng tâm. - Chuyển đổi (Transform): Sử dụng phương thức
Babel.transformđể duyệt sâu các nút AST (nhưStringLiteral,TemplateLiteral,JSXText) có thể chứa văn bản gốc. Quá trình này quét các văn bản mục tiêu:- Định vị văn bản: Duyệt qua AST, lọc các nút
StringLiteral,TemplateLiteral,JSXText. Nếu ngôn ngữ nguồn là tiếng Việt, plugin sẽ kiểm tra xem giá trị của nút có khớp với regex tiếng Việt không. - Lọc nội dung không cần dịch: Loại bỏ các tham chiếu đường dẫn (
import '/path'), khóa đối tượng ({ key: 'value' }), bình luận, và các văn bản không phải nội dung khác. - Đánh dấu văn bản: Nếu tìm thấy văn bản phù hợp với regex tiếng Việt và không thuộc loại cần lọc, plugin sẽ tạo một hàm băm duy nhất cho văn bản đó (để đảm bảo không có bản dịch trùng lặp) và thay thế nó bằng một lời gọi hàm dịch (ví dụ:
_i18n_lookup('mã_băm', 'Văn bản gốc')). - Sau khi tất cả các file được duyệt, quá trình đánh dấu văn bản hoàn tất.
- Định vị văn bản: Duyệt qua AST, lọc các nút
- Tạo mã (Generate): Chuyển đổi AST đã sửa đổi trở lại thành mã JavaScript, xuất ra file nguồn cuối cùng chứa các đánh dấu dịch thuật.
2. Thu thập văn bản
Sau khi đánh dấu, cần thu thập các văn bản đã đánh dấu và hàm băm của chúng. Có hai phương án để thực hiện điều này:
- Thu thập đồng bộ trong quá trình duyệt:
- Quy trình: Trong khi duyệt AST và đánh dấu văn bản, lưu trữ giá trị hàm băm và văn bản gốc vào một đối tượng toàn cục theo thời gian thực.
- Ưu điểm: Chỉ cần duyệt một lần, tiết kiệm thời gian.
- Xử lý từng bước:
- Quy trình:
- Duyệt lần đầu: Thay thế văn bản bằng lời gọi
_i18n_lookup, không lưu trữ dữ liệu. - Duyệt lần hai: Thu thập chuyên biệt tất cả các tham số từ các lời gọi
_i18n_lookup(hàm băm và văn bản) và lưu trữ vào một đối tượng toàn cục.
- Duyệt lần đầu: Thay thế văn bản bằng lời gọi
- Ưu điểm: Logic rõ ràng, tránh tác dụng phụ.
- Quy trình:
Hiện tại, plugin sử dụng phương án thứ hai. Sau bước này, tất cả các văn bản cần dịch đã được lưu trữ trong một đối tượng toàn cục, sẵn sàng cho quá trình dịch.
Ví dụ về Thu thập Văn bản
Mã gốc:
<div>Nút bấm</div>
<script>
const message = 'Thông báo hệ thống';
</script>
import './styles.css'; // Đường dẫn không cần dịch
Sau khi được xử lý bởi plugin Babel, đầu ra có thể là:
_createElement('div', null, _i18n_lookup('hash_button_text', 'Nút bấm'));
const message = _i18n_lookup('hash_system_message', 'Thông báo hệ thống');
import './styles.css'; // Đường dẫn không thay đổi
Hàm duyệt cuối cùng sẽ đọc các hàm băm và văn bản, thu thập chúng vào một đối tượng toàn cục, tạo ra một bảng ánh xạ:
{
"hash_button_text": "Nút bấm",
"hash_system_message": "Thông báo hệ thống"
}
Đến đây, quá trình chuyển đổi văn bản thành lời gọi hàm và thu thập văn bản mục tiêu đã hoàn tất.
Nhờ cơ chế này, nhà phát triển không cần phải đánh dấu văn bản thủ công. Babel tự động nhận diện và chuẩn bị nội dung cần dịch, đồng thời đảm bảo tính chính xác của mã cấu trúc.
Plugin thực hiện dịch thuật như thế nào?
Sau khi Babel hoàn thành việc thu thập văn bản, bước tiếp theo là dịch chúng. Quá trình này chủ yếu gồm hai giai đoạn:
Giai đoạn 1: Khởi tạo Bộ dịch
Plugin cung cấp sẵn hai lớp bộ dịch (translator classes) và một lớp cơ sở bộ dịch. Các bộ dịch có sẵn bao gồm lớp Youdao Translator và lớp Google Translator, có thể sử dụng ngay sau khi khởi tạo:
Youdao Translator (rất được khuyến nghị):
import { YoudaoTranslator } from 'auto-i18n-translation-plugins';
new YoudaoTranslator({
appId: 'your-app-id',
appKey: 'your-app-key'
});
Google Translator:
import { GoogleTranslator } from 'auto-i18n-translation-plugins';
new GoogleTranslator({
proxyConfig: { // Cần cấu hình proxy nếu sử dụng ở một số khu vực
host: '127.0.0.1',
port: 8899,
headers: {
'User-Agent': 'Node'
}
}
});
Các bộ dịch này thực hiện quá trình dịch thông qua một hàm performTranslation tích hợp.
Cả Google Translator và Youdao Translator đều kế thừa từ lớp cơ sở BaseTranslator:
BaseTranslator là một lớp cốt lõi để đóng gói chức năng dịch, sử dụng API dịch thuật đã cấu hình (ví dụ: dịch máy) để chuyển đổi văn bản từ ngôn ngữ nguồn sang ngôn ngữ đích. Mục tiêu thiết kế là chuẩn hóa quy trình gọi dịch, quản lý tần suất yêu cầu API và cung cấp cơ chế xử lý lỗi.
Mã nguồn ví dụ cho BaseTranslator:
interface TranslationConfig {
name?: string;
requestFn: (text: string, sourceLang: string, targetLang: string) => Promise<string>;
rateLimitInterval?: number; // Đơn vị mili giây
}
export class BaseTranslator {
protected settings: TranslationConfig;
constructor(config: TranslationConfig) {
this.settings = config;
if (this.settings.rateLimitInterval) {
// Áp dụng giới hạn tần suất cho requestFn nếu interval được đặt
this.settings.requestFn = this.applyRateLimit(this.settings.requestFn, this.settings.rateLimitInterval);
}
}
private applyRateLimit(fn: Function, intervalMs: number) {
let lastCallTime = 0;
return async (...args: any[]) => {
const now = Date.now();
if (now - lastCallTime < intervalMs) {
await new Promise(resolve => setTimeout(resolve, intervalMs - (now - lastCallTime)));
}
lastCallTime = Date.now();
return fn(...args);
};
}
protected formatError(err: unknown): string {
return err instanceof Error ? err.message : String(err);
}
async performTranslation(textToTranslate: string, sourceLangCode: string, targetLangCode: string): Promise<string> {
let translationResult = '';
try {
translationResult = await this.settings.requestFn(textToTranslate, sourceLangCode, targetLangCode);
} catch (error) {
const translatorName = this.settings.name || 'Translator';
console.error(
`Lỗi yêu cầu API dịch [${translatorName}]: ${this.formatError(error)}`
);
}
return translationResult;
}
}
Điều này cũng có nghĩa là bạn có thể tạo một
CustomTranslatorcủa riêng mình!
Giai đoạn 2: Dịch các ngôn ngữ đích
Sau khi khởi tạo bộ dịch, chúng ta cần dịch các văn bản đã được quét. Các bước cụ thể như sau:
- Xác định văn bản mới cần dịch:
- Bước 1: Đọc hai tập dữ liệu:
- Đối tượng toàn cục: Chứa tất cả văn bản được tạo từ mã nguồn (ví dụ:
"hash1" → "Xác nhận", "hash2" → "Xin chào"). - File đã dịch cũ (
index.json): Chứa các bản dịch đã hoàn thành trước đó (ví dụ:hash1đã có tiếng Việt và tiếng Anh, nhưnghash2chưa được dịch).
- Đối tượng toàn cục: Chứa tất cả văn bản được tạo từ mã nguồn (ví dụ:
- Quy tắc lọc: Tìm các văn bản chưa được dịch trong file cũ (ví dụ: "Xin chào" của
hash2cần được dịch sang tiếng Anh) và lưu trữ chúng trong một đối tượng tạm thời.
- Bước 1: Đọc hai tập dữ liệu:
- Gộp văn bản để dịch một lần:
- Thao tác: Đọc đối tượng tạm thời theo khóa, nối các văn bản gốc cần dịch lại với nhau bằng một ký hiệu đặc biệt
\n┋┋┋\nđể tạo thành một chuỗi dài.- Ví dụ:
- Danh sách văn bản gốc:
["Xin chào", "Chào mừng đến với hệ thống"] - Chuỗi dài đã gộp:
Xin chào\n┋┋┋\nChào mừng đến với hệ thống
- Danh sách văn bản gốc:
- Ví dụ:
- Lý do: Gộp nhiều văn bản ngắn thành một chuỗi dài cho phép dịch hàng loạt trong một lần gọi API, giảm thời gian và chi phí. Việc sử dụng khóa để đọc đảm bảo thứ tự nhất quán vì khóa không thay đổi.
- Thao tác: Đọc đối tượng tạm thời theo khóa, nối các văn bản gốc cần dịch lại với nhau bằng một ký hiệu đặc biệt
- Dịch và tách kết quả theo từng ngôn ngữ:
- Bước 1: Chọn các ngôn ngữ đích, ví dụ: tiếng Anh, tiếng Hàn.
- Bước 2: Dịch từng ngôn ngữ:
- Gọi hàm dịch của bộ dịch với chuỗi dài đã gộp, thiết lập tham số ngôn ngữ (ví dụ: tiếng Anh, tiếng Hàn).
- Ví dụ kết quả:
- Dịch tiếng Anh →
Hello\n┋┋┋\nWelcome to the system - Dịch tiếng Hàn →
안녕하세요\n┋┋┋\n시스템에 오신 것을 환영합니다
- Dịch tiếng Anh →
- Bước 3: Tách kết quả: Dựa vào ký hiệu
\n┋┋┋\n, tách chuỗi dịch đã hoàn thành trở lại thành từng văn bản riêng lẻ. Ví dụ:- Kết quả tiếng Anh →
[ "Hello", "Welcome..." ] - Kết quả tiếng Hàn →
[ "안녕하세요", ... ]Lưu ý: Thứ tự của mảng này sẽ khớp với thứ tự các khóa của biến đối tượng tạm thời đã tạo.
- Kết quả tiếng Anh →
- Đối chiếu thứ tự văn bản gốc và cập nhật bảng ánh xạ dịch thuật:
- Điểm mấu chốt: Vì thứ tự văn bản gốc được duy trì khi gộp (ví dụ: theo hàm băm
hash2), kết quả dịch đã tách cũng có thể được đối chiếu theo thứ tự đó. - Thao tác:
- Tạo một đối tượng lưu trữ tạm thời mới, phân loại kết quả dịch theo hàm băm. Ví dụ:
{ "hash2": { "zh-cn": "Xin chào", // Văn bản gốc (không dịch lại) "en": "Hello", // Tiếng Anh mới dịch "ko": "안녕하세요", // Tiếng Hàn mới dịch }, ... } - Hợp nhất vào file cũ: Thêm nội dung dịch mới từ đối tượng tạm thời vào bảng ánh xạ hiện có.
- Kết quả cuối cùng:
{ "hash1": { "zh-cn": "Xác nhận", "en": "Confirm" }, // Dữ liệu cũ đã có "hash2": { "zh-cn": "Xin chào", "en": "Hello", "ko": "안녕하세요" } // Bản dịch mới được thêm }
- Kết quả cuối cùng:
- Ghi lại sau khi hợp nhất: Ghi đối tượng đã hợp nhất trở lại vào file cấu hình.
- Tạo một đối tượng lưu trữ tạm thời mới, phân loại kết quả dịch theo hàm băm. Ví dụ:
- Điểm mấu chốt: Vì thứ tự văn bản gốc được duy trì khi gộp (ví dụ: theo hàm băm
Thông qua quy trình này, các văn bản mới sẽ tự động được dịch và tích hợp vào file, đồng thời đảm bảo nội dung đã dịch không bị ảnh hưởng. Toàn bộ quá trình giống như "ghép các mảnh vỡ thành một bức tranh hoàn chỉnh → dịch đồng loạt → sau đó tách ra để hiển thị" một cách đơn giản.
Làm thế nào plugin xử lý ngôn ngữ mới và văn bản tăng cường?
Thêm ngôn ngữ mới
Khi cần thêm ngôn ngữ đích mới (ví dụ: mở rộng từ "Trung → Anh" sang "Trung → Anh → Hàn"), bạn không cần chỉnh sửa thủ công bảng ánh xạ:
Khi plugin khởi động, nó sẽ tự động kiểm tra danh sách ngôn ngữ đã cấu hình hiện tại (ví dụ: zh-cn, en, ko) với các ngôn ngữ đã có trong bảng ánh xạ (ví dụ: chỉ tồn tại zh-cn và en). Nếu phát hiện ngôn ngữ mới chưa được khởi tạo (ví dụ: ko), nó sẽ kích hoạt quy trình tự động bổ sung –
Các bước cụ thể:
- Trích xuất văn bản ngôn ngữ nguồn: Trực tiếp lấy tất cả nội dung văn bản thuần túy của ngôn ngữ gốc (ví dụ: tiếng Việt) từ bảng ánh xạ (ví dụ:
"Xác nhận", "Tiếng Hàn"). - Dịch hàng loạt cho ngôn ngữ mới: Nối văn bản gốc bằng ký hiệu gộp
\n┇┇┇\n(ví dụ:Xác nhận\n┇┇┇\nTiếng Hàn), sau đó dịch thống nhất thành ngôn ngữ đích (ví dụ: tiếng Hàn:확인\n┇┇┇\n한국어). Tách lại theo ký hiệu\n┇┇┇\nđể nhận được các bản dịch riêng lẻ như:확인,한국어. - Thêm dữ liệu ngôn ngữ: Ghi trực tiếp kết quả dịch mới vào vị trí tương ứng trong bảng ánh xạ, ví dụ:
"ConfirmationButton": { "zh-cn": "Xác nhận", "en": "Confirm", "ko": "확인" }
Quá trình này hoàn toàn tự động. Nhà phát triển chỉ cần thêm mã ngôn ngữ đích vào cấu hình, và plugin sẽ mở rộng gói ngôn ngữ một cách liền mạch, không cần bảo trì thủ công bảng ánh xạ hoặc lo lắng về sự sai lệch biến số.
Thêm văn bản mới
- Mỗi khi mã nguồn được biên dịch, plugin sẽ quét tất cả các văn bản (ví dụ:
"Nút mới"), tự động tạo lời gọi hàm dịch (ví dụ:_i18n_lookup('hash_new_btn', 'Nút mới')). - Quá trình này hoàn toàn trong suốt, nhà phát triển không cần phải đánh dấu thủ công văn bản mới.
- Khi plugin ghi hàm băm và nội dung gốc của văn bản mới vào bảng ánh xạ toàn cục, nó sẽ kiểm tra xem hàm băm đó đã tồn tại chưa:
- Nếu chưa tồn tại: Thêm mục mới (ví dụ:
"hash_new_btn": "Nút mới"). - Nếu đã tồn tại: Bỏ qua việc ghi, tránh ghi đè các bản dịch đã có.
- Nếu chưa tồn tại: Thêm mục mới (ví dụ:
- Trong giai đoạn dịch, plugin sẽ so sánh:
- Bảng ánh xạ toàn cục trong mã hiện tại (biến toàn cục lưu trữ văn bản và hash).
- File cấu hình dịch thuật hiện có (
index.json).
- Tự động lọc ra các văn bản mới chưa được dịch, chỉ kích hoạt quy trình dịch cho chúng.
- Sau đó, phân tách kết quả dịch và ghi lại vào file cấu hình dịch thuật.
Kịch bản ví dụ:
Bảng ánh xạ ban đầu
{ "hash_confirm_btn": { "zh-cn": "Xác nhận", "en": "Confirm" } }Văn bản mã nguồn mới:
<button>Đặt lại</button>Hoạt động của plugin:
- Tự động tạo
_i18n_lookup('hash_reset_btn', 'Đặt lại').- Cập nhật bảng ánh xạ:
{ "hash_reset_btn": { "zh-cn": "Đặt lại" }, // Tiếng Việt tự động điền, các ngôn ngữ khác chờ dịch }- Lời nhắc dịch: Chỉ cần bổ sung bản dịch tiếng Anh
"Reset", tiếng Nhật"リセット", v.v., không cần xử lý lại nút "Xác nhận" đã tồn tại.
Thông qua thiết kế từng bước như trên, nhà phát triển có thể tập trung vào phát triển mã, và công việc dịch thuật chỉ tập trung vào các nội dung thực sự mới.
Làm thế nào plugin hiển thị kết quả trên trang?
Sau khi văn bản cần dịch được chuyển đổi thành lời gọi hàm dịch và đã được dịch thông qua các bộ dịch, bước tiếp theo là hiển thị kết quả trên giao diện người dùng.
Hãy xem xét mã nguồn đã biên dịch trông như thế nào:
_createElement('div', null, _i18n_lookup('hash_button_text', 'Nút bấm'));
const statusMessage = _i18n_lookup('hash_system_message', 'Thông báo hệ thống');
import './styles/main.css'; // Đường dẫn không thay đổi
Để hiển thị các bản dịch này, chúng ta cần triển khai hàm _i18n_lookup toàn cục.
Dưới đây là một phần mã nguồn minh họa cho file runtime (thường được import ở đầu dự án của bạn):
import appTranslationsData from './translations.json'; // Nhập file JSON quốc tế hóa được tạo bởi plugin
(function () {
// Hàm trợ giúp để trích xuất một cột ngôn ngữ cụ thể từ dữ liệu dịch đầy đủ
const extractLanguagePack = (languageCode, fullData) => {
const pack = {};
for (const key in fullData) {
if (Object.prototype.hasOwnProperty.call(fullData, key)) {
pack[key] = fullData[key][languageCode];
}
}
return pack;
};
// Hàm dịch chính được ứng dụng sử dụng
let translate = function (translationKey, defaultValue, namespace = 'app') {
const currentNamespacePack = translate.langPacks?.[namespace];
return (currentNamespacePack && currentNamespacePack[translationKey]) !== undefined
? currentNamespacePack[translationKey]
: defaultValue;
};
// Phương thức để đặt gói ngôn ngữ hoạt động cho một namespace
translate.setLanguage = function (languagePack, namespace = 'app') {
if (!translate.langPacks) {
translate.langPacks = {};
}
translate.langPacks[namespace] = languagePack || {};
};
// Đặt hàm dịch có thể truy cập toàn cục
if (!window._i18n_lookup) {
window._i18n_lookup = translate;
}
// Điền các gói ngôn ngữ có sẵn từ JSON đã nhập
const availableLanguageResources = {
'en': window?.i18nLoaded?.en || extractLanguagePack('en', appTranslationsData),
'ko': window?.i18nLoaded?.ko || extractLanguagePack('ko', appTranslationsData),
'ja': window?.i18nLoaded?.ja || extractLanguagePack('ja', appTranslationsData),
'zh-cn': window?.i18nLoaded?.['zh-cn'] || extractLanguagePack('zh-cn', appTranslationsData)
};
// Xác định ngôn ngữ hoạt động dựa trên localStorage hoặc mặc định
const activeLanguageCode = localStorage.getItem('user_preferred_lang') || 'zh-cn';
// Đặt gói ngôn ngữ hoạt động cho ứng dụng
window._i18n_lookup.setLanguage(availableLanguageResources[activeLanguageCode], 'app');
})();
Từ mã trên, có thể thấy rằng để hiển thị trên trang, chúng ta nhập file JSON dịch đã tạo. Sau đó, thông qua window._i18n_lookup.setLanguage(availableLanguageResources[activeLanguageCode], 'app'), gói ngôn ngữ tương ứng được thiết lập cho hàm dịch. Bằng cách này, hàm _i18n_lookup có thể được sử dụng trên trang để hiển thị các bản dịch.
Tại sao plugin không ảnh hưởng đến mã hiện có?
Plugin đạt được sự chuyển đổi "trong suốt" nhờ phân tích cây cú pháp trừu tượng (AST) trong quá trình biên dịch. Trong giai đoạn build mã, bằng cách phân tích AST của mã nguồn, plugin xác định chính xác nội dung văn bản cần dịch và thay thế chúng một cách thông minh bằng hàm dịch được chỉ định (ví dụ: _i18n_lookup('hash', 'Văn bản gốc')). Đồng thời, quá trình này tự động thu thập tất cả dữ liệu văn bản cần dịch và tạo ra bảng ánh xạ (index.json). Toàn bộ xử lý này diễn ra trong quy trình build, không sửa đổi cấu trúc gốc của các file mã nguồn và không gây bất kỳ nhiễu loạn nào cho logic JavaScript trong thời gian chạy, đảm bảo sự ổn định của môi trường phát triển và sản xuất.
(Ví dụ, <div>Nội dung</div> được tự động chuyển đổi thành _i18n_lookup('hash','Nội dung'), nhưng file mã nguồn gốc vẫn giữ nguyên. Trong quá trình phát triển và gỡ lỗi, bạn vẫn có thể trực tiếp xem nội dung chuỗi gốc.)
Tại sao plugin tương thích với mọi framework front-end?
Thiết kế của auto-i18n-translation-plugins dựa trên nguyên tắc xử lý hậu kỳ độc lập với framework. Vì tất cả các framework front-end (như Vue, React, Svelte, v.v.) cuối cùng đều biên dịch cú pháp tùy chỉnh của chúng (template, component, JSX, v.v.) thành mã JavaScript tiêu chuẩn, logic trích xuất văn bản và thay thế hàm dịch của plugin được đặt một cách rõ ràng để thực thi ở giai đoạn cuối cùng của quy trình build. Chiến lược này có ý nghĩa cốt lõi:
- Tất cả các hoạt động phân tích và biên dịch của framework (như biên dịch template của Vue, chuyển đổi JSX của React) đều hoàn thành trước khi plugin chạy;
- Plugin xử lý trực tiếp mã JavaScript thuần túy cuối cùng, không cần hiểu cú pháp hoặc cấu trúc nội bộ của từng framework cụ thể;
- Chỉ cần đảm bảo plugin được kích hoạt ở giai đoạn cuối cùng của pipeline build (ví dụ: thứ tự
loadercủa Webpack, thứ tự cấu hìnhplugincủa Vite), nó có thể tương thích với mọi framework front-end tuân thủ quy trình biên dịch tiêu chuẩn.
Kết quả: Nhà phát triển chỉ cần cấu hình plugin ở giai đoạn cuối của quy trình build là có thể hỗ trợ "trong suốt" bất kỳ stack công nghệ nào như Vue + TS, React + SWC, các dự án JS thuần túy, mà không cần cấu hình lớp thích ứng riêng cho từng framework.
Địa chỉ kho lưu trữ và ví dụ
Github: wenps/auto-i18n-translation-plugins
NPM phiên bản Vite: https://www.npmjs.com/package/vite-auto-i18n-plugin
NPM phiên bản Webpack: https://www.npmjs.com/package/webpack-auto-i18n-plugin?activeTab=readme
Các ví dụ: https://github.com/wenps/auto-i18n-translation-plugins/tree/main/example
Kế hoạch phát triển (TODO)
- Hỗ trợ SSR hoàn toàn tự động (hiện tại cần thích ứng thủ công).
- Tự động nhập
lang/index.js.