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
// Available locale identifiers
let available: [String] = Locale.availableIdentifiers
print("Total locales: \(available.count)")
// Filter Russian locales
let russianLocales = available.filter { $0.hasPrefix("ru") }
print("Russian locales: \(russianLocales)")
// Locale components
let locale = Locale(identifier: "de_DE")
print("Language: \(locale.languageCode ?? "nil")")
print("Region: \(locale.regionCode ?? "nil")")
print("Currency: \(locale.currencyCode ?? "nil")")
print("Calendar: \(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
// Current system locale
let current = Locale.current
print("Current locale: \(current.identifier)")
print("Language: \(current.language?.disjointName ?? "nil")")
// User preferred languages
let preferred = Locale.preferredLanguages
print("Preferred languages: \(preferred)")
// Get region from current locale
if let region = current.regionCode {
let regionLocale = Locale(identifier: "en_\(region)")
let countryName = regionLocale.localizedString(
forRegionCode: region
)
print("Country: \(countryName ?? region)")
}
// Check 24h format
let uses24h = current.uses24hClock(
for: .dateAndTime
)
print("Uses 24h: \(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()
// Format with different locales
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.locale = Locale(identifier: "ru_RU")
print("Russian: \(formatter.string(from: date))")
formatter.locale = Locale(identifier: "en_US")
print("English: \(formatter.string(from: date))")
formatter.locale = Locale(identifier: "ja_JP")
print("Japanese: \(formatter.string(from: date))")
// Currency formatting with locale
let numFormatter = NumberFormatter()
numFormatter.numberStyle = .currency
numFormatter.locale = Locale(identifier: "de_DE")
print("German currency: \(numFormatter.string(from: 1234.56) ?? "nil")")
numFormatter.locale = Locale(identifier: "en_US")
print("US currency: \(numFormatter.string(from: 1234.56) ?? "nil")")
Парсинг дат из серверных API всегда должен использовать Locale(identifier: "en_US_POSIX") для фиксированного формата. Серверы обычно отправляют даты в формате ISO 8601 с английскими названиями месяцев, и использование текущей локали устройства может привести к ошибке парсинга, если пользователь находится в регионе с неанглийским языком. en_US_POSIX гарантирует, что парсинг не зависит от настроек устройства.
import Foundation
// Correct server date parsing
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 date: \(parsed)")
}
// Localized currency name
let usLocale = Locale(identifier: "en_US")
let currencyName = usLocale.localizedString(
forCurrencyCode: "RUB"
)
print("Russian ruble in US locale: \(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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также