Đa ngôn ngữ Internationalization| i18n
Back To BlogsI18n mang đến giải pháp hoàn hảo để xây dựng các ứng dụng web đa ngôn ngữ một cách dễ dàng và hiệu quả.
1. Khái niệm
Internationalization (i18n) là quá trình thiết kế và phát triển một ứng dụng phần mềm sao cho nó có thể dễ dàng thích nghi với các ngôn ngữ, khu vực và văn hóa khác nhau mà không cần thay đổi mã nguồn cốt lõi.
"i" là chữ cái đầu, "n" là chữ cái cuối của từ "Internationalization", "18" biểu thị số chữ cái ở giữa (từ chữ thứ 2 đến chữ thứ 18).
Cho phép ứng dụng hỗ trợ nhiều ngôn ngữ Ví dụ: tiếng Anh, tiếng Việt, tiếng Pháp.
Đáp ứng các định dạng địa phương ngày tháng, tiền tệ, v.v..
Tăng tính tiếp cận và trải nghiệm người dùng trên toàn cầu.
2. Sử dụng trong tệp HTML
- Tạo các tệp ngôn ngữ
message_<tên ngôn ngữ>.properties
trong thư mụcsrc/main/resources/messages
. Lưu ý tệpmessages.properties
sẽ là tệp mặc định và bằng tiếng anh.
locale đây là một khu vực địa lý hoặc văn hóa cụ thể. Nó thường là một ký hiệu ngôn ngữ được theo sau bởi một ký hiệu quốc gia, phân biệt nhau bởi dấu gạch dưới. Ví dụ: en_US biểu diễn English locale cho US
Tên ngôn ngữ được ký hiệu bằng 2 chữ cái thông qua bảng sau
- Sử dụng
[[#{message_key}]]
nếu trong phương pháp thẻ, ngoài ra còn có thể sử dụngth:<tên_thuộc_tính>="#{message_key}"
Ví dụ: <span>[[#{message_key}]]</span>
, <span th:text="#{message_key}"></span>
- Nếu khóa thông báo của bạn là một biến, bạn có thể sử dụng
[[#{${tên_biến}}]]
bên trong thẻ hoặc${tên_biến}
bên trong thuộc tính. - Nếu khóa thông báo của bạn có tham số ví dụ
hello=Hello {0} {1}
bạn có thể sử dụng bên trong thẻ:[[${#messages.msg('message_key', value0, value1)}]]
hoặc bên trong thuộc tính:${#messages.msg('message_key', value0, value1)}
- Nếu bạn muốn chuyển đổi thông báo thành chữ thường, bạn có thể sử dụng bên trong thẻ:
[[${#strings.toLowerCase(#messages.msg('message_key'))}]]
hoặc bên trong thuộc tính:${#strings.toLowerCase(#messages.msg('message_key'))}
- Đối với các thông báo có định dạng HTML, bạn có thể sử dụng thuộc tính
th:utext
ví dụ:<p th:utext="#{home.welcome}"></p>
Bạn có thể tham khảo tài liệu Thymeleaf để biết thêm chi tiết
3. Sử dụng trong JavaScript
- Đối với admin, chúng ta cần sử dụng bên trong thẻ<script>
như sau:<script th:fragment="common" th:inline="javascript"> /*<![CDATA[*/ ezyadmin.messages.message_key1 = /*[[#{message_key1}]]*/ ''; /*]]>*/ // other javascript code </script>
<script>
như sau:<script th:fragment="common" th:inline="javascript"> /*<![CDATA[*/ ezyweb.messages.message_key1 = /*[[#{message_key1}]]*/ ''; /*]]>*/ // other javascript code </script>
4. Ví dụ

- Người dùng truy cập URL http://localhost:8080/login
Hành động: Gửi yêu cầu GET đến /login.
Xử lý: LoginController.loginGet được gọi.
Nhận tham số lang (mặc định = "en") và error (mặc định = rỗng).
Trả về View:
Template: user-login.html.
Biến: lang (ngôn ngữ), error (có lỗi hay không).
Kết quả: Trang đăng nhập hiển thị với ngôn ngữ mặc định (tiếng Anh).
- Người dùng chuyển đổi ngôn ngữ
Hành động: Nhấp vào liên kết "Tiếng Việt" (/login?lang=vi) hoặc "English" (/login?lang=en).
Xử lý: Yêu cầu GET mới được gửi với tham số lang.
LoginController.loginGet cập nhật locale dựa trên lang.
Trả về View với template user-login.html và ngôn ngữ mới.
Kết quả: Trang đăng nhập được làm mới với ngôn ngữ đã chọn VD: "Đăng nhập" thay vì "Login".
- Thêm các dependency cần thiết
<dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.1.2.RELEASE</version> </dependency> <dependency> <groupId>com.tvd12</groupId> <artifactId>ezyhttp-server-thymeleaf</artifactId> <version>1.3.7</version> </dependency> <dependency> <groupId>com.tvd12</groupId> <artifactId>ezyhttp-server-core</artifactId> <version>1.3.7</version> </dependency> <dependency> <groupId>com.tvd12</groupId> <artifactId>ezyhttp-server-boot</artifactId> <version>1.3.7</version> </dependency>
messages_en.properties
và file messages_vi.properties
trong thư mục src/main/resources/messages

File messages_en.properties
login=Login
username=Username
username.placeholder=Enter your username
password=Password
password.placeholder=Enter your password
login.button=Login
login.language.en=English
login.language.vi=Tiếng Việt
login.error=Invalid username or password
file messages_vi.properties
login=Đăng nhập username=Tên người dùng username.placeholder=Nhập tên người dùng password=Mật khẩu password.placeholder=Nhập mật khẩu login.button=Đăng nhập login.language.en=English login.language.vi=Tiếng Việt login.error=Tên người dùng hoặc mật khẩu không đúng
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title th:text="#{login}">Login</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> </head> <body> <div class="container mt-5 mx-auto" style="max-width: 600px;"> <div class="mb-3 text-end"> <a th:href="'/login?lang=vi'" th:text="#{login.language.vi}">Vietnamese</a> | <a th:href="'/login?lang=en'" th:text="#{login.language.en}">English</a> </div> <h2 class="text-center mb-4" th:text="#{login}">Login</h2> <form action="/login" method="post"> <input type="hidden" name="lang" th:value="${lang}"> <div class="mb-3"> <label for="username" class="form-label" th:text="#{username}">Username</label> <input type="text" class="form-control" id="username" name="username" th:placeholder="#{username.placeholder}" required> </div> <div class="mb-3"> <label for="password" class="form-label" th:text="#{password}">Password</label> <input type="password" class="form-control" id="password" name="password" th:placeholder="#{password.placeholder}" required> </div> <button type="submit" class="btn btn-primary w-100" th:text="#{login.button}">Login</button> </form> <div th:if="${error}" class="alert alert-danger mt-3 text-center" th:text="#{login.error}"> Invalid username or password </div> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script> </body> </html>
package org.hello.controller; import com.tvd12.ezyhttp.server.core.annotation.Controller; import com.tvd12.ezyhttp.server.core.annotation.DoGet; import com.tvd12.ezyhttp.server.core.annotation.DoPost; import com.tvd12.ezyhttp.server.core.annotation.RequestParam; import com.tvd12.ezyhttp.server.core.view.Redirect; import com.tvd12.ezyhttp.server.core.view.View; @Controller public class LoginController { @DoGet("/login") public View loginGet( @RequestParam(value = "lang", defaultValue = "en") String language, @RequestParam(value = "error", defaultValue = "") String error) { return View.builder() .locale(language) .template("user-login") .addVariable("lang", language) .addVariable("error", !error.isEmpty()) .build(); } @DoPost("/login") public Redirect loginPost( @RequestParam("username") String username, @RequestParam("password") String password, @RequestParam(value = "lang", defaultValue = "en") String language) { if ("admin".equals(username) && "password".equals(password)) { return Redirect.to("/book-store/books"); } else { return Redirect.to("/login?lang=" + language + "&error=1"); } } }
http://localhost:8080/login


5. Tóm lại
Trên đây là những thông tin liên quan đến đa ngôn ngữ Internationalization| i18n . Hy vọng với những chia sẻ trên đây giúp bạn nắm được đặc điểm và ứng dụng.