Hướng dẫn lập trình Flask – Phần 13: Hỗ trợ đa ngôn ngữ

flask_tutorial_1

Trong phần này, chúng ta sẽ tiếp tục mở rộng ứng dụng của chúng ta để hỗ trợ đa ngôn ngữ. Trong quá trình đó, chúng ta cũng sẽ tìm hiểu cách để tạo ra các lệnh Flask mở rộng mới.

Để giúp cho bạn dễ theo dõi, sau đây là danh sách các bài viết trong loạt bài hướng dẫn này:

Bạn có thể truy cập mã nguồn cho phần này tại GitHub.

Chủ đề chính của phần này là quốc tế hóa và địa phương hóa (Internationalization and Localization) – thường được biết với tên gọi I18n và L10n. Để giúp cho các user không dùng tiếng Anh, chúng ta sẽ xây dựng một quy trình dịch thuật ngôn ngữ với sự trợ giúp của các thư viện phiên dịch. Nhờ đó, user có thể lựa chọn ngôn ngữ thích hợp của họ khi dùng ứng dụng.

Giới thiệu Flask-Babel

Không khó để đoán ra có một thư viện Flask mở rộng để hỗ trợ cho việc dịch thuật ngôn ngữ trong ứng dụng. Thư viện này gọi là Flask-Babel và được cài đặt bằng pip:

Chúng ta cũng khởi tạo Flask-Babel như các thư viện khác:

app/__init__.py: Khởi tạo Flask-Babel.

Trong phần này, chúng ta sẽ dịch các giao tiếp bằng tiếng Anh trong ứng dụng sang tiếng Việt. Chúng ta cũng có thể thử dịch sang một số ngôn ngữ khác. Để theo dõi danh sách các ngôn ngữ được hỗ trợ, chúng ta sẽ sử dụng một tham số cấu hình mới:

config.py: Danh sách các ngôn ngữ được hỗ trợ

Chúng ta sẽ sử dụng mã ngôn ngữ với hai ký tự, nhưng nếu bạn muốn chi tiết hơn, bạn cũng có thể sử dụng thêm mã quốc gia. Ví dụ như bạn có thể dùng en-US, en-GB và en-CA để hỗ trợ cho tiếng Anh theo Mỹ, theo Anh và Canada như là các ngôn ngữ riêng biệt.

Babel cung cấp một decorator gọi là localselector. Decorator này sẽ được gọi để chọn ngôn ngữ sẽ được dịch sang mỗi khi ứng dụng nhận được một yêu cầu từ trình duyệt.

app/__init__.py: Chọn ngôn ngữ để dịch.

Trong phần mã ở trên, chúng ta sử dụng một thuộc tính của đối tượng request từ Flask gọi là accept_languages. Thuộc tính này cung cấp giao tiếp cần thiết để làm việc với tham số Accept-Language trong header (phần chứa thông tin ở phần đầu của một yêu cầu từ trình duyệt của user). Tham số này chứa một danh sách theo thứ tự ưu tiên các tham chiếu về ngôn ngữ và địa phương từ user. Nội dung của tham số này có thể được thiết lập từ trình duyệt của user với các giá trị mặc định được lấy từ hệ điều hành mà user đang sử dụng. Phần lớn người sử dụng không biết sự tồn tại của các thiết lập này, nhưng đây là một thiết lập hữu dụng vì người sử dụng có thể cung cấp một danh sách các ngôn ngữ mà họ muốn được hỗ trợ theo thứ tự ưu tiên. Nếu bạn muốn tìm hiểu, sau đây là một ví dụ về tham số Accept-Language:

Tham số trên sẽ thiết lập tiếng Việt (vi) là ngôn ngữ user muốn sử dụng (với độ ưu tiên mặc định là 1.0), sau đó là tiếng Anh kiểu Anh (en-GB) với độ ưu tiên 0.9, và cuối cùng là tiếng Anh tổng quát (en) với độ ưu tiên 0.7.

Để chọn ngôn ngữ phù hợp nhất, bạn cần so sánh danh sách ngôn ngữ do user yêu cầu với danh sách các ngôn ngữ mà ứng dụng hỗ trợ và sử dụng độ ưu tiên mà user cung cấp. Mã nguồn theo yêu cầu này tương đối phức tạp, nhưng đã có sẵn trong hàm best_match(). Chúng ta chỉ cần cung cấp danh sách các ngôn ngữ được ứng dụng hỗ trợ cho hàm này và nó sẽ trả về ngôn ngữ phù hợp nhất.

Đánh dấu văn bản cần được dịch trong mã nguồn của ứng dụng

Đến đây, chúng ta phải đối diện với một vấn đề mới. Để ứng dụng có thể hỗ trợ đa ngôn ngữ, chúng ta cần phải đánh dấu tất cả các đoạn văn bản (text) cần được phiên dịch trong mã nguồn. Sau khi các đoạn văn bản đã được đánh dấu, Flask-Babel sẽ quét tất cả các file và đưa các đoạn văn bản này vào một file dịch thuật riêng nhờ công cụ gettext. Đây là một công việc rất tẻ nhạt nhưng lại cần phải có để hỗ trợ cho chức năng đa ngôn ngữ của ứng dụng.

Trong phần dưới, chúng ta sẽ đưa ra một vài ví dụ điển hình về việc đánh dấu. Nhưng bạn có thể tải về mã nguồn trong phần này từ GitHub để thấy toàn bộ các thay đổi mà chúng ta cần thực hiện.

Để đánh dấu các đoạn văn bản cần dịch, chúng ta cần đóng gói chúng trong một hàm gọi là _(). Trường hợp đơn giản nhất là với các chuỗi văn bản cố định (literal string) trong mã nguồn. Sau đây là một ví dụ cho trường hợp này với lệnh flash():

Chủ ý ở đây là hàm _() sẽ đóng gói đoạn văn bản trong ngôn ngữ gốc (trong trường hợp này là tiếng Anh). Hàm này sẽ dùng ngôn ngữ phù hợp nhất do các hàm có đi kèm với decorator localselector để tìm ra văn bản tương ứng trong ngôn ngữ được dịch sang.  Kết quả do hàm _() trả về là văn bản đã được dịch và sẽ trở thành tham số cho hàm flash() trong trường hợp này.

Thật không may là không phải mọi trường hợp đều đơn giản như vậy. Ví dụ như trường hợp gọi hàm flash() dưới đây:

Lần này, trong chuỗi văn bản mà hàm flash sử dụng, có một thành phần thay đổi. Hàm _() có một biến thể cho phép sử dụng chuỗi văn bản kiểu này, nhưng nó lại dựa trên cú pháp thay thế chuỗi tương đối cũ:

Tuy vậy vẫn chưa hết, còn có những trường hợp phức tạp hơn. Một số các chuỗi văn bản lại được gán bên ngoài các yêu cầu từ trình duyệt của user, thường là khi ứng dụng bắt đầu. Vì vậy không có cách nào để biết phải sử dụng ngôn ngữ nào để dịch khi đánh giá các đoạn văn bản này. Một ví dụ về trường hợp này là khi dịch các nhãn (label) được gán cho các trường trong form. Giải pháp duy nhất ở đây là tìm cách để trì hoãn quá trình đánh giá và chọn lựa ngôn ngữ phiên dịch cho đến khi nó được sử dụng do nhận được yêu cầu từ user. Flask-Babel cung cấp một phiên bản thích hợp cho quá trình này gọi là đánh giá trì hoãn (lazy evaluation) thông qua hàm lazy_gettext():

Ở đây, chúng ta sử dụng một hàm khác để đánh dấu văn bản cần dịch gọi là _l(). Hàm này cũng tương tự như hàm _(), tuy nhiên nó chỉ đánh dấu văn bản cần được dịch trong một đối tượng đặc biệt và chỉ gọi đến chức năng dịch khi chuỗi văn bản này được sử dụng.

Thư viện mở rộng Flask-Login sẽ hiển thị một thông điệp flash mỗi khi user được chuyển hướng đến trang đăng nhập. Thông điệp được hiển thị bằng tiếng Anh và có sẵn trong thư viện. Để dịch thông điệp này, chúng ta sẽ phải thay đổi chuỗi văn bản mặc định và đưa nó vào trong hàm _l() như trong đoạn mã sau đây:

Đánh dấu văn bản cần được dịch trong các Template

Trong phần trên, chúng ta đã thấy cách đánh dấu các đoạn văn bản cần dịch trong mã Python, nhưng điều đó chưa đủ bởi vì các template cũng có các văn bản cần phải dịch. Để làm việc này, chúng ta có thể sử dụng hàm _() với các template tương tự như phần trước. Sau đây là ví dụ với mã HTML trong template 404.html:

Để đánh dấu văn bản cần dịch, chúng ta làm như sau:

Lưu ý là chúng ta cần phải sử dụng cặp ngoặc nhọn {{ … }} để báo với Flask rằng cần phải gọi hàm _() thay vì thay thế nó bằng một chuỗi nào đó.

Đối với các câu phức tạp hơn và có các thành phần động, chúng ta có thể sử dụng tham số như sau:

Tuy nhiên, trường hợp rắc rối nhất là với template _post.html:

Vấn đề ở chỗ là chúng ta muốn username là một liên kết đến trang hồ sơ cá nhân chứ không phải chỉ là tên của một user. Vì vậy, chúng ta phải dùng một biến tạm thời là user_link nhờ các chỉ thị setendset của template và truyền biến này cho hàm _() để dịch.

Vì chúng ta thay đổi mã nguồn khá nhiều trong phần này, bạn có thể tải toàn bộ mã nguồn từ GitHub.

Tách các chuỗi văn bản cần được dịch

Sau khi đã đánh dấu toàn bộ các chuỗi văn bản cần được dịch trong ứng dụng với các hàm _()_l(), chúng ta có thể dùng lệnh pybabel để tách và đưa toàn bộ các chuỗi này vào một file với phần mở rộng là .pot (viết tắt của portable object template). File này sẽ được dùng làm một template để tạo ra các file dịch thuật tương ứng với các ngôn ngữ khác nhau.

Để tiến hành quá trình tách văn bản, chúng ta cần khai báo một file cấu hình để pybabel biết cần phải quét file nào. Sau đây là tập tin cấu hình babel.cfg chúng ta sẽ dùng trong ứng dụng này:

babel.cfg: File cấu hình PyBabel.

Hai dòng đầu tiên khai báo các kiểu mẫu (pattern) cho tên các file Python và Jinja2 template. Dòng thứ ba khai báo hai thư viện mở rộng của Jinja2 để Flask_Babel có thể sử dụng khi phân tích văn bản trong các file template.

Để tách và đưa toàn bộ các văn bản cần dịch vào file .pot, chúng ta có thể sử dụng lệnh sau:

Lệnh pybabel extract sẽ đọc file cấu hình theo sau tùy chọn –F và quét toàn bộ các file có mã nguồn và file template trong các thư mục được định nghĩa trong file cấu hình, bắt đầu từ thư mục được nhập vào trong lệnh (thư mục hiện hành hay . trong trường hợp này). Theo mặc định, pybabel sẽ tìm các đoạn văn bản được đánh dấu với hàm _(), nhưng vì chúng ta có sử dụng cả phiên bản trì hoãn (lazy) của hàm này – được tham chiếu là _l() trong mã nguồn của ứng dụng – chúng ta cũng cần thông báo cho pybabel quét cả các hàm này với tham số -k _l. Và cuối cùng, tùy chọn -o chỉ định tên của file kết quả (messages.pot)

Cũng cần lưu ý là file messages.pot không phải là file cố định trong dự án. Chúng ta có thể tạo ra file này bất kỳ khi nào chúng ta cần bằng cách chạy lệnh pybabel như trên. Vì vậy, chúng ta không cần đưa file này vào source control.

Tạo danh mục (catalog)

Bước tiếp theo là tạo ra các bản dịch cho các ngôn ngữ khác nhau mà ứng dụng sẽ hỗ trợ ngoài ngôn ngữ chính (tiếng Anh trong trường hợp này). Chúng ta sẽ bắt đầu với tiếng Việt (có mã ngôn ngữ là vi) và dùng lệnh sau đây để làm điều đó:

Lệnh pybabel init sẽ sử dụng đầu vào là file messages.pot và tạo ra một danh mục cho ngôn ngữ được chỉ định với tùy chọn –l (vi) trong thư mục được chỉ định với tùy chọn –d. Chúng ta sẽ đặt tất cả các bản dịch cho các ngôn ngữ khác nhau vào thư mục app/translation vì đó là thư mục mặc định để Flask-Babel tìm các file dịch. Lệnh này sẽ tạo ra một thư mục con vi trong thư mục này cho các file tiếng Việt. Trong trường hợp này, nó sẽ tạo ra một file gọi là messages.po tại thư mục app/translations/vi/LC_MESSAGES/ cho các thông điệp trong ứng dụng bằng tiếng Việt.

Nếu muốn hỗ trợ các ngôn ngữ khác, bạn chỉ cần lặp lại lệnh trên với các mã ngôn ngữ thích hợp, mỗi ngôn ngữ sẽ được lưu trong file messages.po trong các thư mục riêng cho ngôn ngữ đó. Các file messages.po cho mỗi ngôn ngữ sẽ có cùng một định dạng tiêu chuẩn do công cụ gettext sử dụng. Sau đây là trích đoạn của file này cho tiếng Việt:

Bỏ qua phần mở đầu (header), bạn sẽ thấy theo sau đó là một danh sách các văn bản được trích ra khi gọi các hàm _()_l(). Tại đầu vào của mỗi chuỗi văn bản, bạn sẽ thấy vị trí của chuỗi đó trong ứng dụng. Tiếp theo là dòng msgid chứa chuỗi văn bản đó trong ngôn ngữ gốc (tạm gọi là ngôn ngữ nguồn) và sau đó là dòng msgstr với một chuỗi trống. Các chuỗi trống này cần được thay thế bởi chuỗi phù hợp trong ngôn ngữ sẽ được dịch sang (tạm gọn là ngôn ngữ đích).

Có nhiều ứng dụng có thể soạn thảo các file .po. Ở mức cơ bản, bạn chỉ cần biết sử dụng một chương trình soạn thảo văn bản để làm việc với các file này, nhưng nếu bạn đang có một dự án lớn, bạn nên tìm một chương trình soạn thảo chuyên dụng. Ứng dụng phổ biến nhất cho việc phiên dịch là chương trình mã nguồn mở poedit và có thể sử dụng trong hầu hết các hệ điều hành phổ biến. Nếu bạn quen thuộc với vim, một số phím tắt từ thư viện hỗ trợ po.vim sẽ giúp cho công việc của bạn dễ dàng hơn.

Sau đây là một phần của file messages.po với các thông điệp đã được dịch sang tiếng Việt:

Một lần nữa, mã nguồn từ GitHub đã có sẵn file này, vì vậy, bạn không cần phải bận tâm về file này. File messages.po là một phần của quá trình phiên dịch, nhưng để sử dụng được các thông điệp đã được dịch, chúng ta cần chuyển đổi file này sang một định dạng mới để ứng dụng có thể sử dụng một cách có hiệu quả khi chạy. Để chuyển đổi các phần đã được phiên dịch trong ứng dụng, bạn có thể sử dụng lệnh pybabel compile như sau:

Lệnh này sẽ tạo ra một file messages.mo ngoài file messages.po đã có sẵn cho mỗi ngôn ngữ. Đây là file sẽ được Flask-Babel sử dụng để dịch ngôn ngữ cho ứng dụng.

Sau khi bạn tạo ra file messages.mo cho tiếng Việt và các ngôn ngữ khác mà bạn muốn hỗ trợ trong dự án, ứng dụng có thể hiển thị các thông điệp bằng các ngôn ngữ này. Nếu bạn muốn dùng tiếng Việt trong ứng dụng, bạn có thể thiết lập tùy chọn ngôn ngữ trong trình duyệt để sử dụng tiếng Việt. Với trình duyệt Firefox, bạn có thể chọn tiếng Việt từ mục Language trong phần Preferences:

Language setting in Firefox

Còn với trình duyệt Chrome, bạn có thể làm điều này trong mục Advanced từ trang Setting:

Language setting in Chrome

Nếu không muốn thay đổi thiết lập trong trình duyệt, bạn có thể dùng cách khác là bắt buộc ứng dụng phải sử dụng một ngôn ngữ bằng cách trả về mã ngôn ngữ tương ứng từ hàm localselector. Sau đây là ví dụ với tiếng Việt:

app/__init__.py: Chọn ngôn ngữ.

Sau khi thực hiện một trong hai cách này, toàn bộ giao diện của ứng dụng sẽ được hiển thị bằng tiếng Việt.

Cập nhật quá trình phiên dịch

Một trong những tình huống thường gặp trong quá trình phiên dịch là bạn muốn sử dụng các file đã được phiên dịch khi chúng còn chưa hoàn tất. Điều này hoàn toàn khả thi, bạn có thể chuyển đổi một file messages.po chưa hoàn chỉnh và ứng dụng có thể sử dụng chúng. Phần nào đã được phiên dịch sẽ được hiển thị với ngôn ngữ tương ứng, phần nào chưa được phiên dịch sẽ được hiển thị trong ngôn ngữ gốc. Bạn có thể tiếp tục quá trình phiên dịch và chuyển đổi file messages.po lần nữa sau khi đã dịch xong.

Một tình huống nữa là bạn để sót một số các chuỗi văn bản khi đánh dấu với hàm _(). Trong trường hợp này, các chuỗi không được đánh dấu sẽ vẫn được trình bày bằng ngôn ngữ gốc (tiếng Anh trong ứng dụng của chúng ta) vì Flask-Babel không biết về chúng. Với tình huống này, bạn sẽ cần phải thêm các hàm _() hoặc _l() ở những nơi nào mà bạn thấy vẫn còn sử dụng ngôn ngữ gốc và thực hiện quá trình cập nhật gồm hai bước như sau:

Lệnh extract ở đây hoàn toàn giống như lệnh mà chúng ta đã sử dụng trước đó, nhưng nó sẽ tạo ra một phiên bản mới của file messages.pot gồm có các chuỗi văn bản hiện có và các chuỗi mà bạn mới thêm vào bằng hàm _() hoặc _l(). Lệnh update sẽ quét nội dung file messages.pot và đưa nó vào các file messages.po trong dự án. Đây là một quá trình “thông minh” vì các chuỗi có sẵn sẽ không bị thay đổi, còn các chuỗi được thêm vào hay loại bỏ trong messages.pot sẽ được thêm hay loại bỏ một cách phù hợp.

Sau khi file messages.po được cập nhật, bạn có thể tiếp tục và dịch các chuỗi mới và thực hiện việc chuyển đổi lần nữa để ứng dụng có thể sử dụng các thay đổi này.

Phiên dịch ngày và giờ

Đến đây, chúng ta đã dịch toàn bộ các chuỗi văn bản trong mã Python và template sang tiếng Việt. Tuy vậy, khi chạy ứng dụng, nếu tinh mắt bạn sẽ thấy có một vài nơi vẫn có tiếng Anh. Cụ thể là các thông điệp ngày giờ được tạo ra bởi Flask-Moment và moment.js. Hiển nhiên là các thông điệp này không được dịch vì chúng không nằm trong mã Python hoặc template trong ứng dụng của chúng ta.

Thư viện moment.js hỗ trợ quốc tế hóa và địa phương hóa. Vì vậy việc duy nhất mà chúng ta cần làm là thiết lập cấu hình để chọn ngôn ngữ thích hợp. Flask-Babel trả về ngôn ngữ và địa phương được chọn trong mỗi yêu cầu qua hàm get_locale(), vì vậy chúng ta sẽ thêm địa phương vào đối tượng g để có thể sử dụng đối tượng này trong template base.html

app/routes.py: Lưu ngôn ngữ được chọn trong đối tượng flask.g

Hàm get_locale() từ thư viện Flask-Babel trả về một đối tượng gọi là locale (địa phương), nhưng chúng ta chỉ cần lấy mã ngôn ngữ bằng cách đổi đối tượng này thành một chuỗi. Sau khi đã có g.locale, chúng ta có thể sử dụng nó trong template base.html để thiết lập cấu hình ngôn ngữ cho moment.js

app/templates/base.html: Thiết lập giá trị địa phương cho moment.js.

Ngày và giờ sẽ được hiển thị theo đúng ngôn ngữ được dùng cho các văn bản. Bạn có thể thấy ứng dụng được Việt hóa hoàn toàn như hình dưới đây:

Myblog with Vietnamese UI

Đến đây, toàn bộ các văn bản ngoại trừ các nội dung được nhập bởi user trong các bài viết và hồ sơ cá nhân đã được dịch sang một ngôn ngữ khác.

Tạo ra các lệnh mới

Có lẽ bạn cũng đồng ý rằng các lệnh pybabel hơi dài và khó nhớ. Chúng ta sẽ tận dụng cơ hội này để tìm hiểu cách tạo ra các lệnh tùy biến có thể được sử dụng chung với lệnh flask. Cho đến bây giờ, chúng ta đã sử dụng qua các lệnh như là flask run, flask shell và một vài lệnh flask db do thư viện Flask-Migrate cung cấp. Thật ra việc tạo ra các lệnh tùy biến cho flask tương tự như các lệnh trên không khó lắm. Chúng ta sẽ thử tạo ra một vài lệnh đơn giản để gọi lệnh pybabel với các tham số cần thiết cho ứng dụng. Các lệnh mà chúng ta sẽ tạo ra bao gồm:

  • flask translate init LANG để thêm một ngôn ngữ mới
  • flask translate update để cập nhật tất cả các ngôn ngữ được phiên dịch
  • flask translate compile để chuyển đổi tất các ngôn ngữ được phiên dịch

Flask sử dụng gói Click cho các tác vụ liên quan đến dòng lệnh (cli). Các lệnh có các lệnh phụ (sub-command) như là translate sẽ được tạo ra nhờ decorator app.cli.group(). Chúng ta sẽ đưa các lệnh này vào một module gọi là app/cli.py:

app/cli.py: Nhóm lệnh translate

Tên của lệnh sẽ là tên của hàm có sử dụng decorator app.cli.group và các thông báo trợ giúp cho lệnh sẽ là chuỗi văn bản mô tả hàm (chuỗi nằm giữa ba dấu trích dẫn kép (“””) như trên). Bởi vì lệnh này sẽ là một lệnh chính để làm nền cho các lệnh phụ kèm theo, chúng ta không cần làm gì với hàm này.

Tạo ra các lệnh updatecompile rất dễ dàng vì các lệnh này không cần tham số:

app/cli.py: Các lệnh phụ update và compile

Lưu ý là decorator cho các hàm trên được tạo ra từ hàm chính là translate. Cách sử dụng như vậy hơi khó hiểu bởi vì bản thân translate() cũng là một hàm, nhưng đây là cách chuẩn để Click tạo ra các nhóm lệnh.  Tương tự như trong hàm translate(), chuỗi mô tả hàm cho các hàm này sẽ được dùng để tạo ra các thông báo trợ giúp khi chúng ta sử dụng lệnh với tham số --help.

Bạn có thể thấy đối với tất cả các lệnh, chúng ta luôn thực hiện và kiểm tra giá trị trả về là 0 để bảo đảm rằng lệnh đã được thi hành thành công. Nếu có lỗi trong quá trình thực hiện lệnh, chúng ta sẽ tạo ra lỗi RuntimeError để chấm dứt việc thực thi. Hàm update() sẽ thực hiện hai bước extractupdate trong cùng một lệnh, và nếu cả hai đều được thực hiện thành công, nó sẽ xóa file messages.pot sau khi hoàn thành quá trình update (cập nhật) vì chúng ta có thể tạo ra file này nếu cần.

Lệnh init cần một tham số là mã ngôn ngữ. Mã nguồn Python cho lệnh này như sau:

app/cli.py: Lệnh phụ init.

Lệnh này sử dụng decorator @click.argument để khai báo nó sẽ sử dụng tham số là mã ngôn ngữ. Click sẽ truyền giá trị được nhập vào khi lệnh được gọi vào hàm dưới dạng tham số, và chúng ta sẽ đưa tham số này vào lệnh init.

Cuối cùng, chúng ta cần tham chiếu đến các lệnh này để có thể sử dụng được chúng. Chúng ta sẽ làm việc này trong file myblog.py trong thư mục gốc của dự án:

myblog.py: đăng ký các lệnh mới

Điều duy nhất mà chúng ta cần làm ở đây là tham chiếu đến module cli.py. Sau đó, các lệnh mới sẽ được tự động đưa vào trong ứng dụng nhờ các decorator.

Đến đây, khi chúng ta gọi lệnh flask --help, chúng ta sẽ thấy lệnh translate được liệt kê trong số các tùy chọn. Và lệnh flask translate --help sẽ trình bày ba lệnh phụ mà chúng ta vừa tạo ra ở trên:

Với các lệnh mới này, công việc của chúng ta sẽ đơn giản hơn nhiều vì chúng ta không cần nhớ các lệnh gốc vừa dài vừa phức tạp. Để hỗ trợ thêm một ngôn ngữ mới trong ứng dụng, bạn có thể dùng lệnh sau:

Để cập nhật tất cả các ngôn ngữ sau khi đã đánh dấu các chuỗi cần dịch với các hàm _()_l():

Và để chuyển các ngôn ngữ sau khi đã cập nhật các file dịch ngôn ngữ:

Chúng ta sẽ tạm ngừng ở đây, hẹn gặp bạn trong phần tiếp theo.

Leave a Reply

Your email address will not be published. Required fields are marked *