Locale là một lớp Foundation trong iOS và macOS, đóng gói các quy ước ngôn ngữ và văn hóa của người dùng: định dạng ngày tháng, số, tiền tệ và đơn vị đo lường. Theo Apple Developer Documentation, 2024, Locale xác định cách DateFormatter hiển thị tháng (tháng Một hoặc January), dấu phân cách thập phân trong số (dấu phẩy hoặc dấu chấm) và ký hiệu tiền tệ (rúp, đô la hoặc euro). Mỗi phiên bản Locale được gắn với một định danh như ru_RU hoặc en_US, trong đó phần đầu là mã ngôn ngữ (ISO 639-1) và phần thứ hai là mã khu vực (ISO 3166-1). Không giống như TimeZone, Locale không ảnh hưởng đến giá trị tuyệt đối của thời gian, chỉ ảnh hưởng đến biểu diễn chuỗi của nó.
Các điểm chính
Locale là một kiểu giá trị trong Swift (NSLocale trong Objective-C) đại diện cho một tập hợp các quy tắc định dạng cụ thể cho một ngôn ngữ và khu vực nhất định. Không giống như TimeZone, xác định độ lệch tuyệt đối của thời gian, Locale xác định cách thời gian, số và tiền tệ hiển thị trong biểu diễn chuỗi. Cùng một ngày 2024-07-21 sẽ được hiển thị là “21 tháng 7 năm 2024” cho ru_RU và “July 21, 2024” cho en_US.
Mỗi phiên bản Locale bao gồm hai thành phần: Ngôn ngữ (xác định tên tháng, tên ngày trong tuần, thứ tự từ) và Khu vực (xác định định dạng số, tiền tệ, lị). Sự kết hợp của các thành phần này được mã hóa thành định danh theo tiêu chuẩn BCP 47: ru_RU (tiếng Nga, Nga), en_US (tiếng Anh, Mỹ), de_DE (tiếng Đức, Đức).
Theo Unicode CLDR (2024), số lượng ngôn ngữ vùng được hỗ trợ trong iOS vượt quá 700 tổ hợp ngôn ngữ-khu vực. Foundation sử dụng dữ liệu từ CLDR (Common Locale Data Repository) — kho dữ liệu bản địa hóa toàn diện nhất được duy trì bởi Unicode Consortium. Điều này đảm bảo định dạng nhất quán trên tất cả các thiết bị Apple.
DateFormatter sử dụng Locale để chọn tên tháng và ngày chính xác, xác định thứ tự các thành phần ngày (ngày/tháng/năm hoặc tháng/ngày/năm) và dấu phân cách. Nếu không chỉ định Locale rõ ràng, DateFormatter sử dụng ngôn ngữ vùng của thiết bị — điều này đúng cho giao diện người dùng nhưng nguy hiểm cho dữ liệu máy chủ nơi định dạng phải cố định.
| Thành phần | ru_RU | en_US | de_DE |
|---|---|---|---|
| Ngày (medium) | 21 tháng 7 năm 2024 | Jul 21, 2024 | 21.07.2024 |
| Số (1000.5) | 1 000,5 | 1,000.5 | 1.000,5 |
| Tiền tệ (100) | 100,00 ₽ | $100.00 | 100,00 € |
| Lịch | Gregorian | Gregorian | Gregorian |
| Dấu phân cách danh sách | ; | , | ; |
NumberFormatter sử dụng Locale để xác định dấu phân cách thập phân (dấu phẩy hoặc dấu chấm), dấu phân cách nhóm (khoảng trắng, dấu phẩy, dấu chấm) và ký hiệu tiền tệ. Bỏ qua Locale khi phân tích số là một trong những nguyên nhân phổ biến gây lỗi trong các ứng dụng quốc tế: số “1,5” có nghĩa là “một rưỡi” cho ru_RU, nhưng đối với en_US, trình phân tích kỹ thuật số sẽ đọc nó là “năm” sau dấu phẩy.
Quan trọng: Calendar được tạo qua Calendar.current kế thừa ngôn ngữ vùng của thiết bị. Calendar(identifier: .gregorian) với ngôn ngữ vùng được đặt rõ ràng là cách tiếp cận được khuyến nghị để định dạng có thể dự đoán được. Khi làm việc với ngày ISO 8601, luôn sử dụng Locale(identifier: “en_US_POSIX”) — một ngôn ngữ vùng đặc biệt cho định dạng kỹ thuật không bị ảnh hưởng bởi cài đặt khu vực.
Định danh Locale bao gồm mã ngôn ngữ (ISO 639-1, hai ký tự) và mã khu vực (ISO 3166-1, hai ký tự), được phân tách bằng dấu gạch dưới. Ví dụ: ru_RU, en_US, fr_FR, zh_Hans_CN (tiếng Trung, chữ viết đơn giản hóa, Trung Quốc). Foundation cũng hỗ trợ các định danh ở định dạng BCP 47: ru-RU, en-US, được sử dụng trong các tiêu chuẩn web.
Ngoài các định danh đầy đủ, Locale có thể được tạo chỉ bằng ngôn ngữ: Locale(identifier: “ru”) trả về ngôn ngữ vùng với tiếng Nga và khu vực mặc định cho ngôn ngữ đó (thường là Nga). Tương tự cho tiếng Anh: Locale(identifier: “en”) sử dụng khu vực Mỹ. Cách tiếp cận này hữu ích để đặt ngôn ngữ định dạng mà không bị ràng buộc với một khu vực cụ thể.
Ngôn ngữ vùng đặc biệt bao gồm en_US_POSIX — một ngôn ngữ vùng kỹ thuật để định dạng ngày và số tự động, đảm bảo định dạng ổn định bất kể cài đặt người dùng. Ngôn ngữ vùng này là bắt buộc để phân tích ngày từ API máy chủ, đặc biệt là cho định dạng ISO 8601. Nó sử dụng lịch Gregorian, định dạng 24 giờ và dấu chấm làm dấu phân cách thập phân.
import Foundation
// Định danh ngôn ngữ vùng khả dụng
let available: [String] = Locale.availableIdentifiers
print("Tổng số ngôn ngữ vùng: \(available.count)")
// Lọc ngôn ngữ vùng tiếng Nga
let russianLocales = available.filter { $0.hasPrefix("ru") }
print("Ngôn ngữ vùng tiếng Nga: \(russianLocales)")
// Thành phần ngôn ngữ vùng
let locale = Locale(identifier: "de_DE")
print("Ngôn ngữ: \(locale.languageCode ?? "nil")")
print("Khu vực: \(locale.regionCode ?? "nil")")
print("Tiền tệ: \(locale.currencyCode ?? "nil")")
print("Lịch: \(locale.calendar.identifier)")
Kiểm tra ngôn ngữ vùng khả dụng qua Locale.availableIdentifiers trả về một mảng tất cả các định danh được hỗ trợ bởi phiên bản iOS hiện tại. Để lọc theo khu vực, hãy sử dụng Locale.availableIdentifiers.filter với kiểm tra regionCode. Điều này hữu ích để xây dựng giao diện chọn khu vực mà không cần danh sách cứng.
Locale.current là cách chính để lấy ngôn ngữ vùng hiện tại của thiết bị do người dùng đặt trong cài đặt iOS (Settings > General > Language & Region). Thuộc tính này tự động cập nhật khi thay đổi ngôn ngữ hoặc khu vực trong cài đặt mà không cần khởi động lại ứng dụng. Tuy nhiên, nó có thể không khớp với ngôn ngữ vùng ưa thích để hiển thị nội dung: người dùng có thể đặt ngôn ngữ giao diện là tiếng Anh nhưng xem ngày tháng theo định dạng tiếng Nga.
Để xác định chính xác hơn sở thích của người dùng, hãy sử dụng Locale.preferredLanguages — một mảng các ngôn ngữ được sắp xếp theo ưu tiên của người dùng. Phần tử đầu tiên là ngôn ngữ giao diện chính. Danh sách này tương ứng với cài đặt trong Language & Region, bao gồm việc kéo thả ngôn ngữ theo thứ tự ưu tiên. Các ứng dụng giao tiếp (trình nhắn tin, ứng dụng email) nên xem xét thứ tự này khi chọn ngôn ngữ hiển thị nội dung.
import Foundation
// Ngôn ngữ vùng hệ thống hiện tại
let current = Locale.current
print("Ngôn ngữ vùng hiện tại: \(current.identifier)")
print("Ngôn ngữ: \(current.language?.disjointName ?? "nil")")
// Ngôn ngữ ưa thích của người dùng
let preferred = Locale.preferredLanguages
print("Ngôn ngữ ưa thích: \(preferred)")
// Lấy khu vực từ ngôn ngữ vùng hiện tại
if let region = current.regionCode {
let regionLocale = Locale(identifier: "en_\(region)")
let countryName = regionLocale.localizedString(
forRegionCode: region
)
print("Quốc gia: \(countryName ?? region)")
}
// Kiểm tra định dạng 24h
let uses24h = current.uses24hClock(
for: .dateAndTime
)
print("Sử dụng 24h: \(uses24h)")
Bản địa hóa trong giao diện người dùng: để hiển thị tên tháng và ngày bằng ngôn ngữ giao diện, hãy sử dụng Calendar với ngôn ngữ vùng được đặt. Calendar.current.symbols(for: .month) trả về tên tháng bằng ngôn ngữ của ngôn ngữ vùng hiện tại. Để hiển thị tên quốc gia bằng ngôn ngữ của người dùng, hãy sử dụng Locale.current.localizedString(forRegionCode:).
Định dạng ngày có nhận biết ngôn ngữ vùng là một nhiệm vụ quan trọng khi hiển thị ngày cho người dùng. DateFormatter với ngôn ngữ vùng được đặt tự động chọn định dạng ngày và giờ chính xác cho khu vực của người dùng. Đối với dateStyle và timeStyle với các giá trị .short, .medium, .long, .full, bộ định dạng sử dụng quy tắc ngôn ngữ vùng để tổng hợp các thành phần ngày.
import Foundation
let date = Date()
// Định dạng với các ngôn ngữ vùng khác nhau
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.locale = Locale(identifier: "ru_RU")
print("Tiếng Nga: \(formatter.string(from: date))")
formatter.locale = Locale(identifier: "en_US")
print("Tiếng Anh: \(formatter.string(from: date))")
formatter.locale = Locale(identifier: "ja_JP")
print("Tiếng Nhật: \(formatter.string(from: date))")
// Định dạng tiền tệ với ngôn ngữ vùng
let numFormatter = NumberFormatter()
numFormatter.numberStyle = .currency
numFormatter.locale = Locale(identifier: "de_DE")
print("Tiền tệ Đức: \(numFormatter.string(from: 1234.56) ?? "nil")")
numFormatter.locale = Locale(identifier: "en_US")
print("Tiền tệ Mỹ: \(numFormatter.string(from: 1234.56) ?? "nil")")
Phân tích ngày từ API máy chủ phải luôn sử dụng Locale(identifier: “en_US_POSIX”) cho định dạng cố định. Máy chủ thường gửi ngày ở định dạng ISO 8601 với tên tháng bằng tiếng Anh và việc sử dụng ngôn ngữ vùng hiện tại của thiết bị có thể gây ra lỗi phân tích nếu người dùng ở khu vực không nói tiếng Anh. en_US_POSIX đảm bảo việc phân tích không phụ thuộc vào cài đặt thiết bị.
import Foundation
// Phân tích ngày máy chủ chính xác
let isoFormatter = DateFormatter()
isoFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
isoFormatter.locale = Locale(identifier: "en_US_POSIX")
isoFormatter.timeZone = TimeZone(secondsFromGMT: 0)
let serverDate = "2024-07-21T14:30:00+0000"
if let parsed = isoFormatter.date(from: serverDate) {
print("Ngày đã phân tích: \(parsed)")
}
// Tên tiền tệ được bản địa hóa
let usLocale = Locale(identifier: "en_US")
let currencyName = usLocale.localizedString(
forCurrencyCode: "RUB"
)
print("Rúp Nga trong ngôn ngữ vùng Mỹ: \(currencyName ?? "nil")")
Khả năng bổ sung: Locale cung cấp mô tả được bản địa hóa của các thành phần thông qua các phương thức localizedString(forRegionCode:), localizedString(forLanguageCode:), localizedString(forCurrencyCode:) và localizedString(forCalendarIdentifier:). Các phương thức này trả về tên bằng ngôn ngữ của ngôn ngữ vùng mà chúng được gọi. Ví dụ, Locale(identifier: “ru_RU”).localizedString(forCountryCode: “DE”) trả về “Đức”.
Bỏ qua Locale khi phân tích số là một lỗi nghiêm trọng trong các ứng dụng quốc tế. NumberFormatter không có ngôn ngữ vùng rõ ràng sử dụng ngôn ngữ vùng hiện tại của thiết bị. Nếu người dùng ở Nga nhập “1,5”, NumberFormatter.number(from: “1,5”) trả về chính xác 1.5. Nhưng nếu cùng mã đó chạy trên thiết bị có ngôn ngữ vùng en_US, việc phân tích trả về nil vì đối với en_US, dấu phân cách thập phân là dấu chấm.
Thiếu en_US_POSIX cho ngày máy chủ dẫn đến các lỗi khó phát hiện. DateFormatter với dateFormat và locale = Locale.current có thể hỏng đối với người dùng từ các khu vực có định dạng ngày khác với định dạng Mỹ. Ví dụ, ở Đức, DateFormatter có thể mong đợi “21.07.2024” trong khi máy chủ gửi “07/21/2024”. en_US_POSIX đảm bảo định dạng cố định cho phân tích tự động bất kể khu vực của người dùng.
So sánh chuỗi ngày thay vì sử dụng Date là một lỗi phổ biến khác. Các nhà phát triển đôi khi so sánh biểu diễn chuỗi của ngày từ các ngôn ngữ vùng khác nhau, nhận được kết quả không chính xác. Locale chỉ thay đổi cách hiển thị, không thay đổi giá trị tuyệt đối của ngày. Luôn so sánh các đối tượng Date, không phải biểu diễn chuỗi của chúng. Để so sánh các thành phần ngày, hãy sử dụng Calendar với ngôn ngữ vùng được đặt rõ ràng.
Theo WWDC 2023, khoảng 30% vấn đề quốc tế hóa trong ứng dụng liên quan đến cấu hình Locale không chính xác. Apple khuyến nghị luôn đặt rõ ràng ngôn ngữ vùng cho DateFormatter và NumberFormatter khi làm việc với dữ liệu máy chủ và chỉ sử dụng Locale.current để hiển thị giao diện người dùng. Thực hành đơn giản này loại bỏ hầu hết các lỗi liên quan đến cài đặt khu vực.
Các câu hỏi thường gặp
Locale là một lớp Foundation đại diện cho các quy tắc định dạng văn hóa và ngôn ngữ: định dạng ngày, số, tiền tệ và đơn vị đo lường. Nó được sử dụng cùng với DateFormatter, NumberFormatter và Calendar để hiển thị dữ liệu được bản địa hóa.
Locale xác định định dạng hiển thị (ngôn ngữ, quy ước khu vực), trong khi TimeZone xác định độ lệch tuyệt đối của thời gian so với UTC. Locale ảnh hưởng đến biểu diễn chuỗi, TimeZone ảnh hưởng đến giá trị số của thời gian. Cả hai được sử dụng cùng nhau để định dạng ngày hoàn chỉnh.
en_US_POSIX là một ngôn ngữ vùng đặc biệt cho định dạng kỹ thuật đảm bảo định dạng ổn định bất kể cài đặt người dùng. Nó bắt buộc để phân tích ngày máy chủ (ISO 8601) và làm việc với các API nơi định dạng phải có thể dự đoán được.
Locale.availableIdentifiers trả về một mảng chuỗi với các định danh của tất cả ngôn ngữ vùng được hỗ trợ. Để lọc theo ngôn ngữ, sử dụng filter với hasPrefix; để lấy khu vực, sử dụng Locale(identifier:).regionCode.
NumberFormatter sử dụng Locale để xác định dấu phân cách thập phân (dấu phẩy hoặc dấu chấm), ký hiệu tiền tệ và dấu phân cách nhóm. Để có định dạng cố định, hãy đặt ngôn ngữ vùng thành en_US_POSIX hoặc đặt rõ ràng các thuộc tính của bộ định dạng.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm