3.11. Hỗ trợ đa ngôn ngữ nhờ Qt Linguist

Chúng ta cần nhìn thẳng vào sự thật là tiếng Việt không quá thông dụng cũng như không được hỗ trợ nhiều trong thế giới công nghệ thông tin. Thế nhưng nếu tôi vẫn quyết tâm viết 1 phần mềm thuần Việt dành cho những người như mẹ tôi, không quá quen thuộc với tiếng Anh, thì sao ? Nếu tôi muốn ứng dụng hỗ trợ cả tiếng Anh và tiếng Việt để có thể hướng tới lượng người dùng lớn hơn ? Nếu tôi muốn 1 ngày kia những cậu bạn người Pháp của tôi có thể sử dụng mà không gặp rào cản nào về ngôn ngữ ? Và còn biết bao nhiêu cái nếu như khác…

Việc dịch cả 1 phần mềm không phải là công việc quá đơn giản bởi đây không phải chỉ là dịch 1 cụm từ hay 1 câu. Không phải lúc nào thì « Open » tương đương với « Mở » cũng đáp ứng được nhu cầu của người dùng.

Trong thực tế, chúng ta đang nói đến chuyện tạo ra ứng dụng có thể sử dụng được trong hàng nghìn ngôn ngữ và thổ ngữ trên thế giới, dù bảng chữ cái của nó có là La Tinh hay Ả Rập, Trung Quốc hay Nhật Bản, vv…

Chúng ta không chỉ đơn thuần nói về chuyện dịch từ ngôn ngữ này sang ngôn ngữ khác mà còn thảo luận về việc làm sao chương trình có thể định vị được máy tính người dùng và tự động thay đổi phù hợp với ngôn ngữ của họ. Thậm chí đôi khi thay vì hiển thị văn bản từ trái qua phải thì phải hiển thị theo chiều ngược lại nếu cần thiết.

Đây chính là lúc mà chúng ta thấy Qt thật tuyệt vời vì có thể giúp đơn giản hóa rất nhiều nhiệm vụ này. Tất cả đều đã được tính trước. Các công cụ chúng ta cần đều đã được cài đặt mặc định ngày mà chúng ta bắt đầu cài Qt.

Hãy cùng xem làm sao chúng ta hoàn thành nhiệm vụ trên trong bài học dưới đây.

Các bước chuẩn bị cho phiên dịch

Việc phiên dịch chương trình là 1 quá trình cần được chuẩn bị cẩn thận. Nhưng chúng ta trước hết cần hiểu cơ chế hoạt động của nó.

Qt nhận định rằng người lập trình (các bạn và tôi) không phải là người phiên dịch. Với nó, đây là 2 người khác nhau.

Vậy nên mọi thứ được thiết kế để hỗ trợ người phiên dịch, dù không có tí kỹ năng nào về tin học, cũng có thể dịch hoàn hảo chương trình đích.

! Trong trường hợp của các dự án nhỏ và cá nhân thì chúng ta cũng sẽ chính là người phiên dịch cho chương trình của mình. Thế nhưng ở đây, chúng ta hãy giả định đó là 1 người khác.

Hãy quan sát sơ đồ dưới đây, nó thể hiện các bước của quá trình phiên dịch chương trình.

  1. Đầu tiên, chúng ta có lập trình viên, chính là bản thân chúng ta. Chúng ta viết mã nguồn chương trình như bình thường, dùng 1 ngôn ngữ xác định, có thể là tiếng Việt, tiếng Anh, vv… Về cơ bản thì không có mã nguồn sẽ không khác mấy so với trước do những thay đổi cần thiết đều rất nhỏ nhặt.
  2. Mã nguồn sẽ sinh ra 1 tệp chứa các cụm từ hoặc câu cần được dịch. 1 chương trình đi kèm với Qt tự động giúp bạn thực hiện việc này. Các tệp sinh ra sẽ có phần mở rộng là .ts, thường với tên có dạng tenChuongTrinh_ngonNgu.ts. Hãy lấy ví dụ chương trình KienTrucSuX mà chúng ta tạo ra lúc trước. Các tệp sinh ra sẽ có tên nhưng kientrucsux_vi.ts, kientrucsux_en.ts, vv… 2 chữ cái cuối trong tên thường chính là mã ngôn ngữ của 1 nước. Chúng khá giống với tên miền, ví dụ như : fr (Pháp) hay ru (Nga), vv…
  3. Người dịch nhận lấy tệp TS tương ứng với ngôn ngữ của mình. Họ sẽ dịch chúng qua sự hỗ trợ của Qt Linguist mà chúng ta sẽ tìm hiểu sau đây.
  4. Sau khi dịch xong, người dịch chuyển tệp TS lại cho lập trình viên và chúng ta sẽ biên dịch những tệp này thành những tệp nhị phân .qm. Khác biệt giữa 1 tệp .ts và 1 tệp .qm hơi giống như khác biệt giữa tệp mã nguồn .cpp với tệp thực thi .exe vậy. Tệp .qm chứa phần dịch dưới định dạng nhi phân, giúp Qt có thể tải và đọc nhanh hơn khi chạy chương trình. Chính nhờ thế mà chúng ta không cảm thấy chương trình sau khi dịch chạy chậm hơn chương trình gốc.

Chúng ta sẽ cùng tìm hiểu từng bước trong phần tiếp theo của bài học này. Hãy bắt đầu bằng lập trình viên chúng ta nhé. Có đôi chỗ nhỏ cần chúng ta thay đổi trong mã nguồn chương trình.

Mã nguồn phù hợp cho phiên dịch

Bước đầu tiên của quá trình, đó là thay đổi mã nguồn để phù hợp cho việc phiên dịch, giúp người dịch có thể nhặt ra được những cụm văn bản và thông điệp cần dịch.

Sử dụng QString để thao tác với chuỗi ký tự

Như các bạn đã biết, Qt tạo ra lớp QString để quản lý các chuỗi ký tự. Lớp này rất hoàn chỉnh, có hỗ trợ làm việc với định dạng mã hóa Unicode.

! Unicode là định dạng mã hóa chuẩn dùng để quản lý các ký tự trong máy tính. Nó cho phép máy tính hiển thị đủ loại ký tự, nhất là các ký tự đặc biệt. Xem thêm ở đây.

QString không gặp phải vấn đề khó khăn nào khi quản lý các ký tự tượng hình như tiếng Trung hay Ả Rập. Chính vì vậy, khi các bạn muốn dịch chương trình ra nhiều ngôn ngữ, tôi khuyến khích việc sử dụng QString để thao tác với các chuỗi ký tự.

QString chuoiKyTu = "Xin chào"; // Thich hop cho viec dich
char chuoiKyTu[] = " Xin chào"; // Khong thich hop cho viec dich

Vậy lời khuyên ở đây là : dùng QString bất cứ lúc nào có thể thay vì mảng ký tự.

Đặt các cụm cần dịch vào trong phương thức tr()

Ứng dụng cơ bản

Phương thức tr() cho phép chỉ ra là 1 cụm từ cần được dịch. Ví dụ :

thoat = new QPushButton("&Thoát");

… thì không cho phép nhãn của nút được dịch. Thay vào đó, nếu chúng ta sử dụng phương thức tr() :

thoat = new QPushButton(tr("&Thoát"));

... thì nhãn có thể được dịch.

Tóm lại, viết các thông điệp và nhãn được sử dụng trong chương trình bằng ngôn ngữ bạn muốn nhưng hãy đặt tất chúng vào trong phương thức tr().

! Phương thức tr() được định nghĩa trong lớp QObject. Đây là 1 phương thức tĩnh được kế thừa bởi tất cả các lớp trong Qt do chúng đều là lớp con, cháu của QObject. Trong đa số trường hợp, viết tr() là đủ. Tuy nhiên, nếu 1 ngày chúng ta phải làm việc với 1 lớp không kế thừa QObject, vậy thì chúng ta sẽ cần thêm tiền tố QObject:: vào trước tên phương thức : thoat = new QPushButton(QObject::tr("&Thoát"));

Tùy chỉnh : thêm ngữ cảnh

Đôi khi chúng ta bắt gặp 1 cụm từ cần phải có ngữ cảnh chính xác thì mới có thể dịch sát nghĩa nó. Trong trường hợp đó, chúng ta có thể sử dụng tới tham số thứ 2 của phương thức tr() để đưa ra ngữ cảnh của từ cho người dịch.

thoat = new QPushButton(tr("&Thoát ", "Dùng làm nút thoát chương trình"));

Đoạn văn bản ngữ cảnh này sẽ không được hiển thị trong chương trình mà chỉ dành để hỗ trợ cho dịch giả.

Về cơ bản thì tham số ngữ cảnh này là không bắt buộc. Tuy nhiên trong 1 số trường hợp nó lại trở nên vô cùng cần thiết và không thể không có, ví dụ như khi chúng ta muốn dịch …tổ hợp phím tắt.

nutThoat->setShortcut(QKeySequence(tr("Ctrl+Q", "Phím tắt để thoát")));

Nhờ đó, người dịch có thể dịch tổ hợp phím tắt này thành 1 tổ hợp phím, có thể giống hoặc khác tổ hợp phím gốc, như “Ctrl+X” để thích hợp với ngôn ngữ sử dụng.

Tùy chỉnh : quản lý số lượng

Đôi khi, 1 cụm từ cần phải được viết khác đi tùy thuộc vào số lương đối tượng. Đây là vấn đề rất thường gặp khi dịch từ tiếng Việt ra tiếng nước ngoài do có những ngôn ngữ, như tiếng Anh hay tiếng Pháp, thay đổi 1 từ để hợp giống và hợp số.

Hãy lấy 1 ví dụ đơn giản :

Số lượng

Tiếng Việt

Tiếng Anh

Tiếng Pháp

0

Tải 0 tệp

Download 0 files

Télécharger 0 fichier

1

Tải 1 tệp

Download 1 file

Télécharger 1 fichier

2

Tải 2 tệp

Download 2 files

Télécharger 2 fichiers

 

Tùy vào số lượng mà chúng ta thêm chữ “s” vào trong danh từ. Tôi biết là tiếng Nga thậm chí còn rắc rối hơn thế.

? Vậy làm sao để giải quyết vấn đề này ?

Yên tâm, Qt ở đây để giúp các bạn quản lý tất cả những rắc rối bên lề này, chỉ cần là người dịch có thể dịch đúng từng tình huống là được.

Làm sao Qt làm được ? Đó là nhờ thông qua 1 tham số thứ 3 cho phương thức tr().

tr("Tải xuống %n tệp", "", soTep);

! Nếu không muốn đưa ra tham số ngữ cảnh, chúng ta bắt buộc phải truyền vào tham số thứ 2 của phương thức 1 chuỗi ký tự rỗng để có thể sử dụng được tham số thứ 3. Đây là nhắc lại 1 chút về các hàm với tham số không bắt buộc trong C++.

Vậy là Qt sẽ tự động tìm ra được bản dịch đúng cho cụm từ dựa vào ngôn ngữ cần dịch và số lượng của đối tượng. Ngoài ra, giá trị của biến số tệp sẽ tự động thay thế vị trí  của %n trong chuỗi ký tự.

Thế là mã nguồn của chúng ta đã sẵn sàng để được dịch ra các ngôn ngữ khác. Chuyển qua bước 2 thôi hả ?

Tạo các tệp .ts

Chúng ta đã có trong tay chương trình sử dụng tr() để chỉ ra tất cả các cụm từ cần dịch.

Chúng ta sẽ sử dụng lại ví dụ của chương trình KienTrucSuX để minh họa. Tôi đã thay đổi nó 1 chút sử dụng phương thức tr().

Chúng ta muốn chương trình KienTrucSuX của chúng ta được dịch ra tiếng Anh và tiếng Pháp.

Vậy cần tạo ra 2 tệp dịch kientrucsux_en.ts dành cho tiếng Anh và kientrucsux_fr.ts cho tiếng Pháp.

Chúng ta cần thay đổi tệp .pro 1 chút. Đây là tệp tự động sinh ra bởi Qt Creator khi chúng ta tạo dự án.

Mở tệp này với trình soạn thảo văn bản.

HEADERS += CuaSoMa.h CuaSoChinh.h
SOURCES += CuaSoMa.cpp CuaSoChinh.cpp main.cpp

QT += widgets

Hãy thêm vào dòng lệnh cho biết là cần tạo ra 2 tệp để dịch chương trình ra 2 thứ tiếng.

HEADERS += CuaSoMa.h CuaSoChinh.h
SOURCES += CuaSoMa.cpp CuaSoChinh.cpp main.cpp
TRANSLATIONS = kientrucsux_en.ts kientrucsux_fr.ts

QT += widgets

Tốt rồi, giờ chúng ta sẽ cần sử dụng đến 1 chương trình kiểu dòng lệnh của Qt để tạo ra các tệp .ts yêu cầu.

Hãy mở ra cửa sổ dòng lệnh với đường dẫn tắt Qt Command Prompt và chuyển tới thư mục của dự án.

lupdate KienTrucSuX.pro

lupdate là 1 chương trình tự động cập nhật nội dung cho các tệp .ts hoặc tạo ra chúng nếu chưa tồn tại.

Chương trình này khá thông minh. Nếu lần thứ 2 chúng ta thực thi nó, nó chỉ cập nhật những chuỗi ký tự có thay đổi. Việc này rất có ích khi người dịch thấy ngay lập tức những cụm từ cần phải thay đổi so với bản trước.

Thế là bây giờ chúng ta đã có 2 tệp kientrucsux_en.tskientrucsux_fr.ts. Hãy gửi chúng cho người dịch ngôn ngữ tương ứng.

Dịch chương trình với Qt Linguist

Qt đã cài đặt cho chúng ta 1 số chương trình lúc ban đầu, các bạn còn nhớ chứ ?

1 trong số chúng sẽ mang đến trợ giúp lớn lao cho chúng ta vào thời điểm này. Đó là Qt Linguist.

Người dịch sẽ cần 2 thứ để thực hiện công việc của mình

  • Tệp tin .ts cần dịch
  • Chương trình Qt Linguist để dịch

Khi khởi động chương trình, người dịch sẽ thấy cửa sổ dưới đây.

Không thể nói là nó siêu cấp phức tạp một khi đem đi so sánh với những chương trình như của Qt Designer. Sau khi đã học xong bài học về cửa sổ chính, chúng ta dễ dàng nhận ra cửa sổ này với danh lục, thanh công cụ, vùng trung tâm, vv… Đúng vậy, chương trình này bản thân nó cũng được viết bằng Qt, giống như tất cả các phần mềm đi kèm Qt khác.

! Hơi khó nhận ra vùng trung tâm do vùng xung quanh của cửa sổ này chiếm diện tích khá lớn. Vùng trung tâm thật sự là vùng chữ nhật viền tròn với chữ “Source text” và “Translation” ở trong. Hãy chú ý là vùng xung quanh hoàn toàn có thể thay đổi vị trí. Vậy nên hãy sắp xếp cửa sổ hợp lý để thuận lợi nhất cho việc dịch. Thậm chí chúng ta có thể kéo vùng xung quanh ra khỏi cửa sổ chính và tạo thành các cửa sổ con nếu cần thiết.

Dùng chương trình này để mở tệp .ts, ví dụ kientrucsux_en.ts. Các cửa sổ con sẽ tự động được điền giá trị.

Chi tiết từng mảng của cửa sổ chương trình :

  • Context : vùng này chứa danh sách các tệp nguồn cần dịch. Các bạn sẽ tìm thấy CuaSoChinhCuaSoMa ở đây. Bên cạnh đó là số lượng cụm từ cần dịch.
  • Strings : danh sách các cụm từ cần dịch của tệp được chọn. Đây là những cụm từ được nhặt ra nhờ tr().
  • Vùng dịch : chọn 1 cụm từ để dịch, đây sẽ là nơi nhập giá trị bản dịch cho cụm từ đó. Chú ý là Qt tự động nhận biết ngôn ngữ chúng ta muốn dịch nhờ phần cuối tên của tệp .ts. Nếu cụm từ cần nhiều phiên bản dịch cho các trường hợp số nhiều, vùng dịch sẽ tự động yêu cầu thêm 1 bản dịch thứ 2.
  • Warnings : nơi hiện các thông báo hữu ích để cảnh báo về chi tiết các bản dịch. Đây cũng là nơi hiển thị cụm từ cần dịch trong ngữ cảnh của mã nguồn.

Giờ thì tới lượt người dịch làm công việc của mình.

Sau khi hoàn thành việc dịch, người dịch sẽ cần xác nhận với chương trình bằng cách ấn vào biểu tượng ? hoặc tổ hợp phím Ctrl+Enter. Biểu tượng cần chuyển sang biểu tượng xác nhận màu xanh. Công việc hoàn thành khi trong vùng “Context” cần hiển thị rằng tất cả các cụm đều được dịch.

Chúng ta chỉ còn 1 bước cuối là biên dịch tệp .ts này sang tệp .qm và chương trình của chúng ta sẽ có thể tự động thay đổi phù hợp với ngôn ngữ người dùng.

Chạy chương trình sau khi dịch

Vậy bây giờ cần biên dịch tệp .ts thành tệp .qm và sử dụng nó trong chương trình.

Biên dịch .ts thành .qm

Để thực hiện việc này, chúng ta lại dùng đến 1 chương trình đi kèm khác của Qt, đấy là lrelease.

Vẫn là cửa sổ dòng lệnh Qt Command Prompt, trong thư mục của dự án, chúng ta hãy gõ

lrelease tenTep.ts

… để biên dịch tệp chỉ định, hoặc

lrelease tenDuAn.pro

… để biên dịch tất cả tệp .ts của dự án.

Các bạn có thể thấy là lrelease chỉ biên dịch nhưng cụm từ được đánh dấu là đã dịch hoàn thành với Qt Linguist. Những cụm từ khác chưa hoàn thành sẽ không được biên dịch trong tệp .qm.

! Những cụm không được dịch hoặc dịch chưa hoàn thành thì sẽ không xuất hiện trong chương trình của chúng ta. Thay vào đó, cụm từ gốc sẽ hiển thị ở vị trí đó. Mặt khác, chúng ta cũng có thể biên dịch với Qt Linguist bằng cách thực hiện File > Release trong danh mục.

Vậy là chúng ta đã có tệp kientrucsux_en.qm (hoặc kientrucsux_fr.qm) trong thư mục của dự án. Vậy cần làm sao để sử dụng nó.

Sử dụng tệp .qm trong chương trình

Việc yêu cầu sử dụng tệp .qm diễn ra ở đầu hàm main(). Hiện giờ, chúng ta có :

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);
    CuaSoChinh cuaSo;
    cuaSo.show();
    return app.exec();
}

Chúng ta sẽ thêm 1 số dòng lệnh để sử dụng tệp .qm.

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);

    QTranslator phienDich;
    phienDich.load("kientrucsux_en");
    app.installTranslator(&phienDich);

    CuaSoChinh cuaSo;
    cuaSo.show();
    return app.exec();
}

! Chú ý là cần đặt tệp .qm trong cùng thư mục với tệp thực thi, nếu không bản dịch sẽ không được sử dụng và chương trình sẽ chỉ được hiển thị trong ngôn ngữ gốc.

Nếu mọi thứ tốt đẹp, chúng ta sẽ nhận được kết quả sau.

? Có vẻ hiệu quả đấy, thế nhưng chương trình của chúng ta lúc nào cũng chỉ có thể hiện tiếng Anh. Làm sao để nó tự phù hợp theo ngôn ngữ người dùng ?

Đây sẽ bước tiếp theo trong thay đổi xử lý của chúng ta.

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);

    QString maNgonNgu = QLocale::system().name().section('_', 0, 0);
    QTranslator phienDich;
    phienDich.load(QString("kientrucsux_") + maNgonNgu);
    app.installTranslator(&phienDich);

    CuaSoChinh cuaSo;
    cuaSo.show();
    return app.exec();
}

Giải thích : Chúng ta muốn lấy ra mã ngôn ngữ của máy tính người dùng. Để lấy các thông tin về hệ điều hành trong đó chương trình được thực thi, chúng ta có thể sử dụng 1 danh sách các phương thức tĩnh của lớp QLocale.

Phương thức QLocale::system().name() sẽ trả về cho chúng ta kết quả dưới dạng sau : maNgonNgu_maNuoc, ví dụ như tiếng Việt sẽ là vi_VN.

Chúng ta cần nhặt ra mã ngôn ngữ, là phần nằm đằng trước dấu _ nên sẽ cần dùng đến phương thức section() để thao tác lấy ra chuỗi con. Kết quả là chúng ta sẽ lấy ra được mã ngôn ngữ của máy tính người dùng để kết hợp, xác định tệp dịch cần được sử dụng.

QString("kientrucsux_") + maNgonNgu

Nếu người dùng sử dụng tiếng Anh, chúng ta sẽ có kientrucsux_en còn nếu ngôn ngữ dùng là tiếng Pháp thì chúng ta có kientrucsux_fr.

Dựa vào đó Qt sẽ tìm ra tệp dịch cần sử dụng. Nếu không thể tìm ra tệp này thì ngôn ngữ mặc định sẽ được sử dụng là ngôn ngữ chúng ta đã dùng để viết chương trình.

Thêm vào 1 ví dụ chương trình của chúng ta được dịch ra ngôn ngữ thứ 3, tiếng Pháp.

Như các bạn có thể nhận thấy, quy trình dịch trong Qt đã được thiết kế khá tỉ mỉ để giúp chúng ta. Chương trình viết ra có thể hiển thị trong nhiều ngôn ngữ khác nhau và có thể hướng tới nhiều người dùng hơn.

Còn việc dịch ra những ngôn ngữ đó thì … xin lỗi, tôi không thể giúp được gì hơn.