1. Internationalization (i18n) là gì?
Trong quá trình phát triển ứng dụng, lập trình viên thường xây dựng giao diện bằng một ngôn ngữ ban đầu, chẳng hạn như tiếng Việt. Khi ứng dụng được sử dụng bởi người dùng ở nhiều quốc gia, việc chỉ hỗ trợ một ngôn ngữ có thể khiến người dùng gặp khó khăn.
Ví dụ, một ứng dụng bán hàng được phát triển tại Việt Nam có những nội dung như “Trang chủ”, “Sản phẩm”, “Giỏ hàng”, “Thanh toán” và “Đặt hàng thành công”.
Nếu một khách hàng sử dụng tiếng Anh truy cập ứng dụng, họ có thể không hiểu những nội dung này.
Một cách giải quyết đơn giản là lập trình viên trực tiếp thay các chuỗi tiếng Việt bằng tiếng Anh. Tuy nhiên, cách này chỉ tạo ra một phiên bản ngôn ngữ khác, chưa giải quyết được việc hỗ trợ nhiều ngôn ngữ trong cùng một ứng dụng.
Để xây dựng phần mềm có khả năng hỗ trợ nhiều ngôn ngữ và khu vực, lập trình viên cần tìm hiểu Internationalization.
Internationalization, viết tắt là i18n, là quá trình thiết kế và phát triển phần mềm sao cho ứng dụng có thể thích nghi với nhiều ngôn ngữ, quốc gia và quy ước văn hóa mà không cần viết lại phần logic cốt lõi cho từng khu vực.
Thuật ngữ i18n được tạo bằng cách lấy chữ “i” đầu tiên và chữ “n” cuối cùng của từ Internationalization. Giữa hai chữ này có 18 ký tự, vì vậy được viết tắt thành i18n.
Điều quan trọng cần hiểu là Internationalization không chỉ đơn giản là dịch văn bản. Đây là công việc chuẩn bị kiến trúc và cách tổ chức ứng dụng để việc hỗ trợ nhiều ngôn ngữ trở nên dễ dàng, nhất quán và có thể mở rộng.
Ví dụ, thay vì viết trực tiếp:
Text('Giỏ hàng')
trong nhiều màn hình, ứng dụng có thể sử dụng một hệ thống quản lý chuỗi ngôn ngữ tập trung.
Khi người dùng chọn tiếng Việt, hệ thống hiển thị “Giỏ hàng”.
Khi người dùng chọn tiếng Anh, hệ thống hiển thị “Cart”.
Phần xử lý thêm sản phẩm vào giỏ hàng vẫn được giữ nguyên. Chỉ có nội dung hiển thị thay đổi theo ngôn ngữ.
Đó là một trong những mục tiêu cơ bản của Internationalization.
2. Tại sao cần Internationalization?
2.1. Hỗ trợ người dùng ở nhiều quốc gia
Mỗi quốc gia có ngôn ngữ và thói quen sử dụng phần mềm khác nhau.
Một ứng dụng chỉ có tiếng Việt sẽ khó tiếp cận người dùng không hiểu tiếng Việt.
Khi được thiết kế theo hướng Internationalization, ứng dụng có thể bổ sung tiếng Anh, tiếng Nhật hoặc những ngôn ngữ khác mà không cần xây dựng lại toàn bộ giao diện.
2.2. Tách nội dung hiển thị khỏi logic chương trình
Trong một dự án nhỏ, lập trình viên có thể viết trực tiếp văn bản trong Widget.
Ví dụ:
ElevatedButton(
onPressed: () {},
child: const Text('Đăng nhập'),
)
Cách này dễ thực hiện khi ứng dụng chỉ có một vài màn hình.
Tuy nhiên, khi dự án có hàng trăm Widget, việc tìm và thay đổi từng chuỗi sẽ mất nhiều thời gian.
Nếu muốn bổ sung tiếng Anh, lập trình viên phải xác định tất cả vị trí chứa văn bản tiếng Việt.
Điều này làm tăng nguy cơ bỏ sót nội dung và khiến việc bảo trì trở nên khó khăn.
Internationalization khuyến khích tách nội dung cần dịch ra khỏi code giao diện.
Nhờ đó, lập trình viên có thể thay đổi bản dịch mà không cần chỉnh sửa logic xử lý của từng chức năng.
2.3. Dễ bảo trì và mở rộng
Giả sử ứng dụng ban đầu hỗ trợ hai ngôn ngữ là tiếng Việt và tiếng Anh.
Sau một thời gian, công ty muốn bổ sung tiếng Nhật.
Nếu ứng dụng đã có hệ thống i18n phù hợp, nhóm phát triển chủ yếu cần bổ sung tài nguyên ngôn ngữ, kiểm tra giao diện và xử lý những khác biệt về định dạng.
Ngược lại, nếu toàn bộ văn bản được viết trực tiếp trong Widget, việc bổ sung ngôn ngữ mới sẽ phức tạp hơn.
2.4. Tạo trải nghiệm người dùng nhất quán
Một ứng dụng có thể có nhiều màn hình như đăng nhập, trang chủ, giỏ hàng, thanh toán và lịch sử đơn hàng.
Nếu không có hệ thống quản lý ngôn ngữ tập trung, có thể xuất hiện tình trạng màn hình này hiển thị tiếng Việt nhưng màn hình khác lại hiển thị tiếng Anh.
Internationalization giúp nhóm phát triển quản lý các chuỗi theo một cấu trúc thống nhất.
Tuy nhiên, tính nhất quán vẫn phụ thuộc vào việc lập trình viên sử dụng đúng hệ thống dịch trong toàn bộ ứng dụng.
3. Phân biệt Internationalization và Translation
Translation là quá trình dịch nội dung từ ngôn ngữ này sang ngôn ngữ khác.
Ví dụ:
“Đăng nhập” → “Login”.
“Giỏ hàng” → “Cart”.
“Đặt hàng” → “Place order”.
Internationalization có phạm vi rộng hơn.
Nó bao gồm việc thiết kế ứng dụng để có thể sử dụng các bản dịch, xử lý định dạng, bố cục, chiều văn bản và những khác biệt giữa các khu vực.
Ví dụ, một nút bấm có chiều rộng đủ để hiển thị chữ “Lưu” chưa chắc đủ để hiển thị cụm từ dài hơn trong ngôn ngữ khác.
Do đó, ứng dụng cần thiết kế giao diện có khả năng thích nghi khi nội dung thay đổi.
Có thể hiểu:
Translation là dịch nội dung. Internationalization là chuẩn bị hệ thống để ứng dụng hỗ trợ những nội dung và quy ước khác nhau một cách có tổ chức.
4. Những vấn đề cần quan tâm khi xây dựng i18n
4.1. Văn bản giao diện
Đây là phần dễ nhận thấy nhất.
Những nội dung như tiêu đề, nút bấm, nhãn biểu mẫu, thông báo và hướng dẫn cần được quản lý theo ngôn ngữ.
Ví dụ:
- Đăng nhập / Login.
- Đăng ký / Register.
- Giỏ hàng / Cart.
- Thanh toán / Checkout.
- Đơn hàng / Orders.
4.2. Độ dài văn bản
Cùng một ý nghĩa nhưng mỗi ngôn ngữ có thể sử dụng số lượng ký tự khác nhau.
Ví dụ, một nhãn ngắn trong tiếng Anh có thể dài hơn khi dịch sang tiếng Việt hoặc tiếng Đức.
Nếu giao diện sử dụng chiều rộng cố định quá nhỏ, văn bản có thể bị cắt hoặc tràn.
Vì vậy, khi xây dựng giao diện đa ngôn ngữ, cần sử dụng bố cục linh hoạt và kiểm tra các tình huống nội dung dài.
4.3. Chiều đọc văn bản
Tiếng Việt và tiếng Anh thường được đọc từ trái sang phải.
Một số ngôn ngữ như tiếng Ả Rập và tiếng Do Thái thường sử dụng chiều đọc từ phải sang trái.
Flutter có cơ chế hỗ trợ Text Direction và các Widget định hướng theo chiều đọc.
Ví dụ, EdgeInsetsDirectional cho phép khai báo khoảng cách theo phía bắt đầu và kết thúc thay vì cố định trái và phải.
Điều này giúp giao diện dễ thích nghi hơn với các ngôn ngữ có chiều đọc khác nhau.
4.4. Số lượng và dạng số nhiều
Một vấn đề quan trọng của i18n là cách diễn đạt số lượng.
Ví dụ, trong tiếng Anh:
1 item
2 items
Trong tiếng Việt:
1 sản phẩm
2 sản phẩm
Tiếng Anh có sự thay đổi giữa “item” và “items”, trong khi tiếng Việt không áp dụng quy tắc tương tự.
Một số ngôn ngữ khác có nhiều dạng số lượng phức tạp hơn.
Vì vậy, không nên tự động ghép chuỗi theo cách chỉ phù hợp với một ngôn ngữ.
Hệ thống i18n cần hỗ trợ những quy tắc số nhiều phù hợp với từng locale.
4.5. Ngày, giờ, số và tiền tệ
Các quốc gia có thể sử dụng những quy ước hiển thị khác nhau.
Ví dụ, ngày 25 tháng 12 năm 2026 có thể được trình bày là 25/12/2026 hoặc 12/25/2026.
Số tiền cũng có thể sử dụng dấu phân cách và ký hiệu tiền tệ khác nhau.
Đây là những nội dung liên quan chặt chẽ đến Localization và Formatting.
5. Locale là gì?
Locale là thông tin mô tả ngôn ngữ và những quy ước khu vực được sử dụng để hiển thị nội dung.
Trong Flutter, locale được biểu diễn bằng lớp Locale.
Ví dụ:
const Locale('vi', 'VN');
Biểu diễn locale tiếng Việt tại Việt Nam.
const Locale('en', 'US');
Biểu diễn locale tiếng Anh tại Hoa Kỳ.
Trong đó:
vi và en là mã ngôn ngữ.
VN và US là mã quốc gia hoặc khu vực.
Một locale không nhất thiết phải có đầy đủ cả mã ngôn ngữ và mã quốc gia.
Ví dụ:
const Locale('vi');
Locale không đồng nghĩa với vị trí địa lý thực tế của người dùng.
Một người đang ở Việt Nam vẫn có thể chọn giao diện tiếng Anh.
Tương tự, người sử dụng tiếng Việt có thể đang sống ở quốc gia khác.
Do đó, ứng dụng không nên mặc định ngôn ngữ giao diện chỉ dựa trên vị trí thiết bị.
6. Flutter hỗ trợ Internationalization như thế nào?
Flutter cung cấp hệ thống hỗ trợ quốc tế hóa thông qua những package và công cụ như:
flutter_localizations.intl.- Công cụ tạo mã localization từ các tệp ARB.
MaterialAppvà những thuộc tính liên quan đến locale.
flutter_localizations cung cấp các bản địa hóa cho nhiều thành phần giao diện của Flutter, chẳng hạn các thành phần Material và Cupertino.
intl hỗ trợ định dạng ngày, số, tiền tệ và một số cơ chế quốc tế hóa.
Các tệp ARB giúp lưu trữ nội dung dịch theo từng ngôn ngữ.
Khi kết hợp những thành phần này, ứng dụng có thể quản lý ngôn ngữ một cách có tổ chức.
7. ARB là gì?
ARB là viết tắt của Application Resource Bundle.
Đây là định dạng dựa trên JSON, thường được Flutter sử dụng để lưu trữ tài nguyên ngôn ngữ.
Ví dụ, tạo thư mục:
lib/
l10n/
app_vi.arb
app_en.arb
Trong file app_vi.arb:
{
"@@locale": "vi",
"appTitle": "Ứng dụng bán hàng",
"login": "Đăng nhập",
"cart": "Giỏ hàng",
"checkout": "Thanh toán",
"orderSuccess": "Đặt hàng thành công"
}
Trong file app_en.arb:
{
"@@locale": "en",
"appTitle": "Shopping application",
"login": "Login",
"cart": "Cart",
"checkout": "Checkout",
"orderSuccess": "Order placed successfully"
}
Hai file sử dụng cùng những key như login, cart và checkout.
Giá trị tương ứng được dịch theo ngôn ngữ.
Nhờ đó, code Flutter có thể truy cập nội dung thông qua key thay vì viết trực tiếp văn bản.
8. Cấu hình i18n trong Flutter
Trước tiên, cần bổ sung các dependency phù hợp.
Có thể sử dụng:
flutter pub add flutter_localizations --sdk=flutter
flutter pub add intl
Trong pubspec.yaml, bật tính năng tạo mã:
flutter:
generate: true
Có thể tạo file l10n.yaml tại thư mục gốc dự án:
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
Sau khi chuẩn bị các tệp ARB, chạy:
flutter gen-l10n
Flutter sẽ tạo các lớp localization tương ứng.
Với cấu hình tạo mã mặc định hiện đại, lớp AppLocalizations thường được import từ đường dẫn trong mã nguồn dự án, chẳng hạn:
import 'l10n/app_localizations.dart';
Đường dẫn cụ thể phụ thuộc cấu hình l10n.yaml và vị trí file đang import.
9. Sử dụng AppLocalizations trong MaterialApp
Ví dụ:
import 'package:flutter/material.dart';
import 'l10n/app_localizations.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
supportedLocales:
AppLocalizations.supportedLocales,
localizationsDelegates:
AppLocalizations.localizationsDelegates,
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
),
body: Center(
child: Text(l10n.cart),
),
);
}
}
Trong ví dụ này, AppLocalizations là lớp được tạo từ các tệp ARB.
AppLocalizations.supportedLocales cung cấp danh sách locale được hỗ trợ.
AppLocalizations.localizationsDelegates cung cấp các delegate cần thiết.
AppLocalizations.of(context) cho phép lấy nội dung tương ứng với locale đang được sử dụng.
Nếu ứng dụng sử dụng tiếng Việt, l10n.cart trả về “Giỏ hàng”.
Nếu ứng dụng sử dụng tiếng Anh, giá trị trả về là “Cart”.
10. Vì sao không nên viết trực tiếp văn bản trong Widget?
Giả sử dự án có 50 màn hình và mỗi màn hình có nhiều nội dung tiếng Việt.
Nếu toàn bộ văn bản được viết trực tiếp, việc bổ sung tiếng Anh có thể yêu cầu chỉnh sửa rất nhiều file.
Ngoài ra, cùng một từ có thể được dịch không nhất quán.
Ví dụ, “Đơn hàng” có thể được dịch thành “Order” ở một màn hình và “Purchase” ở màn hình khác mà không có chủ đích rõ ràng.
Khi sử dụng tài nguyên ngôn ngữ tập trung, nhóm phát triển có thể kiểm tra và thống nhất thuật ngữ.
Tuy nhiên, cũng cần lưu ý rằng một từ có thể có nghĩa khác nhau tùy ngữ cảnh.
Ví dụ, “Order” có thể là danh từ chỉ đơn hàng hoặc động từ chỉ hành động đặt hàng.
Do đó, nên đặt key theo ý nghĩa và ngữ cảnh sử dụng, chẳng hạn orderHistoryTitle hoặc placeOrderButton, thay vì dùng một key chung cho mọi trường hợp.
11. Placeholder trong Internationalization
Trong ứng dụng thực tế, nhiều câu hiển thị chứa dữ liệu thay đổi.
Ví dụ:
“Xin chào, Thương!”
“Bạn có 3 sản phẩm trong giỏ hàng.”
Không nên tạo chuỗi bằng cách nối nhiều đoạn văn bản cố định vì thứ tự từ có thể khác nhau giữa các ngôn ngữ.
Thay vào đó, sử dụng placeholder.
Ví dụ trong app_vi.arb:
{
"welcomeUser": "Xin chào, {name}!",
"@welcomeUser": {
"placeholders": {
"name": {
"type": "String"
}
}
}
}
Trong app_en.arb:
{
"welcomeUser": "Hello, {name}!",
"@welcomeUser": {
"placeholders": {
"name": {
"type": "String"
}
}
}
}
Sau khi tạo mã, có thể sử dụng:
Text(
l10n.welcomeUser('Thương'),
)
Điểm quan trọng là placeholder giúp bản dịch kiểm soát vị trí của dữ liệu động trong câu.
Nhờ đó, người dịch có thể thay đổi cấu trúc câu mà không cần thay đổi logic Flutter.
12. Pluralization trong Internationalization
Pluralization là cơ chế lựa chọn cách diễn đạt phù hợp với số lượng.
Ví dụ, trong tiếng Anh, một sản phẩm và nhiều sản phẩm có thể cần cách viết khác nhau.
Flutter hỗ trợ khai báo ICU plural trong các tệp ARB.
Ví dụ:
{
"cartItemCount": "{count, plural, =0{No items} =1{1 item} other{{count} items}}",
"@cartItemCount": {
"placeholders": {
"count": {
"type": "int"
}
}
}
}
Khi count = 0, nội dung có thể là “No items”.
Khi count = 1, nội dung là “1 item”.
Khi count = 3, nội dung là “3 items”.
Những quy tắc số nhiều có thể khác nhau theo ngôn ngữ.
Do đó, nên sử dụng hệ thống localization thay vì tự viết điều kiện chỉ phù hợp với tiếng Anh.
13. Internationalization ảnh hưởng đến thiết kế giao diện như thế nào?
Internationalization không chỉ tác động đến văn bản.
Khi hỗ trợ nhiều ngôn ngữ, giao diện cần đủ linh hoạt để thích nghi với những nội dung khác nhau.
Ví dụ, một nút bấm có chữ “Mua” rất ngắn.
Nhưng trong ngôn ngữ khác, cùng hành động có thể được diễn đạt bằng một cụm từ dài hơn.
Nếu nút có chiều rộng cố định, văn bản có thể bị tràn.
Một số giải pháp gồm sử dụng bố cục linh hoạt, cho phép văn bản xuống dòng khi phù hợp và kiểm tra giao diện với nhiều locale.
Ngoài ra, cần chú ý font chữ.
Không phải font nào cũng hỗ trợ đầy đủ ký tự của tất cả ngôn ngữ.
Ví dụ, nếu ứng dụng hỗ trợ tiếng Việt, font cần hiển thị chính xác những ký tự có dấu.
Nếu hỗ trợ tiếng Nhật hoặc tiếng Ả Rập, cần kiểm tra khả năng hiển thị và bố cục tương ứng.
14. Internationalization trong ứng dụng sử dụng API
Trong ứng dụng Flutter kết nối Backend, không phải tất cả nội dung đều nằm trong các tệp ARB.
Ví dụ, tên sản phẩm và mô tả sản phẩm có thể được lấy từ API.
Nếu ứng dụng bán hàng hỗ trợ nhiều ngôn ngữ, nhóm phát triển cần quyết định cách quản lý những nội dung động này.
Một cách là Backend lưu nhiều bản dịch.
Ví dụ:
{
"id": 10,
"name": {
"vi": "Sữa dinh dưỡng",
"en": "Nutrition milk"
}
}
Một cách khác là Backend nhận thông tin locale và trả về nội dung theo ngôn ngữ phù hợp.
Ví dụ:
GET /api/products?lang=vi
Hoặc sử dụng HTTP Header như Accept-Language theo thiết kế API.
Điều quan trọng là phân biệt:
Chuỗi giao diện như “Đăng nhập”, “Thanh toán” thường được quản lý trong hệ thống localization của Flutter.
Dữ liệu nội dung như tên sản phẩm, bài viết và mô tả có thể cần cơ chế đa ngôn ngữ riêng ở Backend.
Không nên cho rằng việc bổ sung ARB sẽ tự động dịch toàn bộ dữ liệu từ API.
15. Kiểm thử Internationalization
Khi ứng dụng hỗ trợ nhiều ngôn ngữ, cần kiểm tra từng locale.
Ví dụ, với tiếng Việt và tiếng Anh, cần kiểm tra:
- Các màn hình có sử dụng đúng bản dịch không.
- Có nội dung nào vẫn bị viết cố định bằng một ngôn ngữ không.
- Văn bản dài có làm tràn giao diện không.
- Placeholder có hiển thị đúng dữ liệu không.
- Số lượng có sử dụng đúng quy tắc plural không.
- Ngày, số và tiền tệ có hiển thị phù hợp không.
Có thể sử dụng Widget Test để kiểm tra giao diện theo locale cụ thể.
Ví dụ, cấu hình MaterialApp với:
locale: const Locale('en'),
Sau đó kiểm tra giao diện có hiển thị nội dung tiếng Anh mong đợi hay không.
16. Những sai lầm thường gặp
Sai lầm đầu tiên là nghĩ rằng i18n chỉ là dịch văn bản.
Sai lầm thứ hai là viết trực tiếp quá nhiều chuỗi trong Widget.
Sai lầm thứ ba là sử dụng một key dịch cho nhiều ngữ cảnh khác nhau.
Sai lầm thứ tư là không kiểm tra giao diện khi văn bản dài hơn.
Sai lầm thứ năm là nối chuỗi thủ công thay vì sử dụng placeholder.
Sai lầm thứ sáu là tự xử lý số nhiều mà không quan tâm quy tắc ngôn ngữ.
Sai lầm cuối cùng là cho rằng các tệp ARB có thể tự động dịch nội dung động từ Backend.
17. Kết luận
Internationalization là quá trình thiết kế ứng dụng để có thể hỗ trợ nhiều ngôn ngữ và quy ước khu vực mà không cần viết lại toàn bộ phần mềm.
Trong Flutter, i18n thường được triển khai bằng cách kết hợp flutter_localizations, intl, các tệp ARB và lớp AppLocalizations được tạo tự động.
Internationalization giúp tách nội dung hiển thị khỏi logic, tăng khả năng bảo trì và tạo nền tảng để ứng dụng mở rộng sang nhiều ngôn ngữ.
Tuy nhiên, i18n không chỉ là vấn đề kỹ thuật dịch chữ. Nó còn liên quan đến cấu trúc câu, số nhiều, chiều văn bản, bố cục giao diện và cách xử lý dữ liệu động.
Đối với dự án Flutter thực tế, việc xây dựng i18n ngay từ đầu thường giúp giảm đáng kể công sức khi bổ sung ngôn ngữ trong tương lai.
18. Tài liệu tham khảo
- Flutter Internationalization: https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization
- Dart intl: https://pub.dev/packages/intl
- Flutter Localization API: https://api.flutter.dev/flutter/flutter_localizations/
- Unicode CLDR: https://cldr.unicode.org/








Khoá học lập trình game con rắn cho trẻ em




