Locale — це клас Foundation в iOS і macOS, що інкапсулює лінгвістичні та культурні угоди користувача: формат дат, чисел, валют і одиниць вимірювання. За даними Apple Developer Documentation, 2024, Locale визначає, як DateFormatter відображає місяць (січень або January), роздільник десятих у числі (кома або крапка) та символ валюти (рубль, долар або євро). Кожен екземпляр Locale прив'язаний до ідентифікатора виду ru_RU або en_US, де перша частина — код мови (ISO 639-1), друга — код регіону (ISO 3166-1). На відміну від TimeZone, Locale не впливає на абсолютне значення часу, тільки на його рядкове представлення.
Головне
Locale — це value-тип в Swift (NSLocale в Objective-C), що представляє набір правил форматування, характерних для конкретної мови та регіону. На відміну від TimeZone, який визначає абсолютне зміщення часу, Locale визначає, як час, числа та валюта виглядають у рядковому представленні. Одна й та сама дата 2024-07-21 буде відображена як «21 липня 2024 р.» для ru_RU і «July 21, 2024» для en_US.
Кожен екземпляр Locale складається з двох компонентів: Language (мова — визначає назви місяців, днів тижня, порядок слів) і Region (регіон — визначає формат чисел, валюту, календар). Комбінація цих компонентів кодується в ідентифікаторі за стандартом BCP 47: ru_RU (російська мова, Росія), en_US (англійська, США), de_DE (німецька, Німеччина).
За даними Unicode CLDR (2024), список підтримуваних локалей в iOS перевищує 700 комбінацій мова-регіон. Foundation використовує дані з CLDR (Common Locale Data Repository) — найповнішого репозиторію локалізаційних даних, що підтримується Unicode Consortium. Це гарантує єдність форматування на всіх пристроях Apple.
DateFormatter використовує Locale для вибору правильних назв місяців і днів тижня, визначення порядку компонентів дати (день/місяць/рік або місяць/день/рік) та роздільників. Без явного зазначення Locale DateFormatter використовує локаль пристрою — це коректно для UI, але небезпечно для серверних даних, де формат має бути фіксованим.
| Компонент | ru_RU | en_US | de_DE |
|---|---|---|---|
| Дата (medium) | 21 липня 2024 р. | Jul 21, 2024 | 21.07.2024 |
| Число (1000.5) | 1 000,5 | 1,000.5 | 1.000,5 |
| Валюта (100) | 100,00 ₽ | $100.00 | 100,00 € |
| Календар | Григоріанський | Григоріанський | Григоріанський |
| Роздільник списку | ; | , | ; |
NumberFormatter використовує Locale для визначення десяткового роздільника (кома або крапка), групуючого роздільника (пробіл, кома, крапка) та символу валюти. Ігнорування Locale при парсингу чисел — одна з частих причин багів в інтернаціональних застосунках: число «1,5» означає «один з половиною» для ru_RU, але для en_US його цифровий аналізатор прочитає як «п'ять» після коми.
Важливо: Calendar, створений через Calendar.current, успадковує локаль пристрою. Calendar(identifier: .gregorian) з явно заданою locale — рекомендований спосіб для передбачуваного форматування. При роботі з ISO 8601 датами завжди використовуйте Locale(identifier: «en_US_POSIX») — спеціальну локаль для технічного форматування, що не піддається впливу регіональних налаштувань.
Ідентифікатор Locale складається з коду мови (ISO 639-1, два символи) та коду регіону (ISO 3166-1, два символи), розділених підкресленням. Приклади: ru_RU, en_US, fr_FR, zh_Hans_CN (китайська, спрощене письмо, Китай). Foundation також підтримує ідентифікатори у форматі BCP 47: ru-RU, en-US, що використовуються у веб-стандартах.
Крім повних ідентифікаторів, Locale можна створити тільки за мовою: Locale(identifier: «ru») поверне локаль з російською мовою та регіоном за замовчуванням для цієї мови (зазвичай Росія). Аналогічно для англійської: Locale(identifier: «en») використовує регіон США. Такий підхід корисний для встановлення мови форматування без прив'язки до конкретного регіону.
Спеціальні локалі включають en_US_POSIX — технічну локаль для машинного форматування дат і чисел, що гарантує стабільний формат незалежно від користувацьких налаштувань. Ця локаль обов'язкова для парсингу дат із серверних API, особливо для формату ISO 8601. Вона використовує григоріанський календар, 24-годинний формат часу та крапку як десятковий роздільник.
import Foundation
// Доступні ідентифікатори локалей
let available: [String] = Locale.availableIdentifiers
print("Всього локалей: \(available.count)")
// Фільтрувати російські локалі
let russianLocales = available.filter { $0.hasPrefix("ru") }
print("Російських локалей: \(russianLocales)")
// Компоненти локалі
let locale = Locale(identifier: "de_DE")
print("Мова: \(locale.languageCode ?? "nil")")
print("Регіон: \(locale.regionCode ?? "nil")")
print("Валюта: \(locale.currencyCode ?? "nil")")
print("Календар: \(locale.calendar.identifier)")
Перевірка доступних локалей через Locale.availableIdentifiers повертає масив усіх ідентифікаторів, підтримуваних поточною версією iOS. Для фільтрації за регіоном використовуйте Locale.availableIdentifiers.filter з перевіркою regionCode. Це корисно для побудови UI вибору регіону без жорстко закодованого списку.
Locale.current — основний спосіб отримання поточної локалі пристрою, встановленої користувачем у налаштуваннях iOS (Settings > General > Language & Region). Ця властивість автоматично оновлюється при зміні мови або регіону в налаштуваннях без перезапуску застосунку. Однак вона може не збігатися з локалью, бажаною для відображення контенту: користувач може встановити мову інтерфейсу англійською, але переглядати дати в українському форматі.
Для більш точного визначення вподобань користувача використовуйте Locale.preferredLanguages — масив мов, упорядкованих за пріоритетом користувача. Перший елемент — основна мова інтерфейсу. Цей список відповідає налаштуванням у Language & Region, включаючи перетягування мов у порядку вподобання. Застосунки для спілкування (месенджери, email-клієнти) повинні враховувати цей порядок при виборі мови відображення контенту.
import Foundation
// Поточна системна локаль
let current = Locale.current
print("Поточна локаль: \(current.identifier)")
print("Мова: \(current.language?.disjointName ?? "nil")")
// Бажані мови користувача
let preferred = Locale.preferredLanguages
print("Бажані мови: \(preferred)")
// Отримати регіон з поточної локалі
if let region = current.regionCode {
let regionLocale = Locale(identifier: "en_\(region)")
let countryName = regionLocale.localizedString(
forRegionCode: region
)
print("Країна: \(countryName ?? region)")
}
// Перевірити 24-годинний формат
let uses24h = current.uses24hClock(
for: .dateAndTime
)
print("Використовує 24 год: \(uses24h)")
Локалізація в UI: для відображення назв місяців і днів тижня мовою інтерфейсу використовуйте Calendar з встановленою locale. Calendar.current.symbols(for: .month) поверне назви місяців мовою поточної локалі. Для відображення назв країн мовою користувача використовуйте Locale.current.localizedString(forRegionCode:).
Форматування дати з урахуванням локалі — ключове завдання при відображенні дат користувачеві. DateFormatter з встановленим locale автоматично вибирає правильний формат дати та часу для регіону користувача. Для dateStyle та timeStyle зі значеннями .short, .medium, .long, .full форматер використовує правила локалі для компонування компонентів дати.
import Foundation
let date = Date()
// Форматування з різними локалями
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.locale = Locale(identifier: "ru_RU")
print("Російська: \(formatter.string(from: date))")
formatter.locale = Locale(identifier: "en_US")
print("Англійська: \(formatter.string(from: date))")
formatter.locale = Locale(identifier: "ja_JP")
print("Японська: \(formatter.string(from: date))")
// Форматування валюти з локаллю
let numFormatter = NumberFormatter()
numFormatter.numberStyle = .currency
numFormatter.locale = Locale(identifier: "de_DE")
print("Німецька валюта: \(numFormatter.string(from: 1234.56) ?? "nil")")
numFormatter.locale = Locale(identifier: "en_US")
print("Американська валюта: \(numFormatter.string(from: 1234.56) ?? "nil")")
Парсинг дат із серверних API завжди повинен використовувати Locale(identifier: «en_US_POSIX») для фіксованого формату. Сервери зазвичай надсилають дати у форматі ISO 8601 з англійськими назвами місяців, і використання поточної локалі пристрою може призвести до помилки парсингу, якщо користувач знаходиться в регіоні з неанглійською мовою. en_US_POSIX гарантує, що парсинг не залежить від налаштувань пристрою.
import Foundation
// Правильний парсинг серверної дати
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("Розпарсена дата: \(parsed)")
}
// Локалізована назва валюти
let usLocale = Locale(identifier: "en_US")
let currencyName = usLocale.localizedString(
forCurrencyCode: "RUB"
)
print("Російський рубль в американській локалі: \(currencyName ?? "nil")")
Додаткові можливості: Locale надає локалізований опис своїх компонентів через методи localizedString(forRegionCode:), localizedString(forLanguageCode:), localizedString(forCurrencyCode:) та localizedString(forCalendarIdentifier:). Ці методи повертають назви мовою тієї локалі, у якої вони викликані. Наприклад, Locale(identifier: «ru_RU»).localizedString(forCountryCode: «DE») поверне «Німеччина».
Ігнорування Locale при парсингу чисел — критична помилка в інтернаціональних застосунках. NumberFormatter без явної локалі використовує поточну локаль пристрою. Якщо користувач в Україні вводить «1,5», NumberFormatter.number(from: «1,5») коректно поверне 1.5. Але якщо цей же код виконується на пристрої з en_US локалью, парсинг поверне nil, тому що для en_US десятковий роздільник — крапка.
Відсутність en_US_POSIX для серверних дат веде до невловимих багів. DateFormatter з dateFormat та locale = Locale.current може зламатися у користувачів із регіонів, де формат дати відрізняється від американського. Наприклад, у Німеччині DateFormatter може очікувати «21.07.2024», а сервер надсилає «07/21/2024». en_US_POSIX гарантує фіксований формат для машинного парсингу незалежно від регіону користувача.
Порівняння рядків дат замість використання Date — ще одна поширена помилка. Розробники іноді порівнюють рядкові представлення дат із різних локалей, отримуючи невірні результати. Locale змінює тільки відображення, але не абсолютне значення дати. Завжди порівнюйте об'єкти Date, а не їх рядкові представлення. Для порівняння компонентів дати використовуйте Calendar з явно заданою locale.
За даними WWDC 2023, близько 30% проблем з інтернаціоналізацією в застосунках пов'язані з неправильним налаштуванням Locale. Apple рекомендує завжди явно задавати locale для DateFormatter та NumberFormatter при роботі з серверними даними та використовувати Locale.current тільки при відображенні в UI. Ця проста практика усуває більшість багів, пов'язаних із регіональними налаштуваннями.
Часті запитання
Locale — клас Foundation, що представляє культурні та лінгвістичні правила форматування: формат дати, чисел, валюти та одиниць вимірювання. Використовується спільно з DateFormatter, NumberFormatter та Calendar для локалізованого відображення даних.
Locale визначає формат відображення (мова, регіональні угоди), а TimeZone — абсолютне зміщення часу відносно UTC. Locale впливає на рядкове представлення, TimeZone — на числове значення часу. Обидва використовуються разом для повного форматування дати.
en_US_POSIX — спеціальна локаль для технічного форматування, що гарантує стабільний формат незалежно від користувацьких налаштувань. Обов'язкова для парсингу серверних дат (ISO 8601) та роботи з API, де формат має бути передбачуваним.
Locale.availableIdentifiers повертає масив рядків з ідентифікаторами всіх підтримуваних локалей. Для фільтрації за мовою використовуйте filter з hasPrefix, для отримання регіону — Locale(identifier:).regionCode.
NumberFormatter використовує Locale для визначення десяткового роздільника (кома або крапка), символу валюти та групуючого роздільника. Для фіксованого формату встановлюйте locale в en_US_POSIX або явно задавайте властивості formatter.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також