Допустим, вы создаете аналитическое приложение и вам нужен простой способ обозначить местоположение аналитического события. Если вы, как и я, подумали: «Да, флажки эмодзи — забавный способ показать страны», то наверняка сразу же начали создавать такой словарь
let emojiFlags = [ "AU": "🇦🇺", "BE": "🇧🇪", "BR": "🇧🇷", "BS": "🇧🇸", ... ]
Но подождите. Оказывается, есть гораздо более простой способ сделать это (ну как «простой»… с программной точки зрения).
Флаги Юникода
Флаги эмодзи, как и все эмодзи, представлены с помощью именованных последовательностей символов Юникода. Удивительным может показаться тот факт, что юникодовое представление каждого флага напрямую соотносится с двухбуквенным кодом страны по стандарту ISO (он же ISO 3166-1 alpha-2).
Чтобы понять, как это работает, давайте рассмотрим набор флагов эмодзи, который мы определили ранее, но на этот раз добавим их представления в юникоде.
Код страны | Эмодзи | Unicode | Страна |
---|---|---|---|
AU | 🇦🇺 | U+1F1E6 U+1F1FA |
Australia |
BE | 🇧🇪 | U+1F1E7 U+1F1EA |
Belgium |
BR | 🇧🇷 | U+1F1E7 U+1F1F7 |
Brazil |
BS | 🇧🇸 | U+1F1E7 U+1F1F8 |
Bahamas |
Во-первых, обратите внимание, что каждый флаг имеет два символа юникода.
Затем обратите внимание, что все страны с префиксом B
имеют один и тот же первый символ юникода — U+1F1E7
. Это не совпадение!
Символы региональных индикаторов
Из Википедии:
Cимволы региональных индикаторов — это набор из 26 буквенных символов Юникода (A-Z), предназначенных для использования при кодировании двухбуквенных кодов стран ISO 3166-1 alpha-2 таким образом, чтобы обеспечить необязательную специальную обработку.
Они кодируются в диапазоне от U+1F1E6 для 🇦 в символе регионального индикатора до U+1F1FF для 🇿.
Таким образом, если мы вычтем U+1F1E6
из каждой из наших последовательностей юникода, приведенных выше, мы получим следующие смещения:
Код страны | Emoji | Смещение | Страна |
---|---|---|---|
AU | 🇦🇺 | 0 20 |
Australia |
BE | 🇧🇪 | 1 4 |
Belgium |
BR | 🇧🇷 | 1 17 |
Brazil |
BS | 🇧🇸 | 1 18 |
Bahamas |
Теперь можно просто перевести соответствующий код страны с помощью алфавита. Вот таблица на случай, если вы захотите попробовать сделать это вручную.
A | B | C | D | E | F | G | H | I | J |
---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
K | L | M | N | O | P | Q | R | S |
---|---|---|---|---|---|---|---|---|
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
T | U | V | W | X | Y | Z |
---|---|---|---|---|---|---|
19 | 20 | 21 | 22 | 23 | 24 | 25 |
Функция получения эмодзи
Теперь, когда мы знаем, как создавать эмодзи из кодов стран, мы можем создать функцию, которая будет делать именно это.
func emojiFlag(countryCode: String) -> String? { guard countryCode.count == 2 else { return nil } // https://en.wikipedia.org/wiki/Regional_indicator_symbol let regionalIndicatorStartIndex: UInt32 = 0x1F1E6 let alphabetOffset = UnicodeScalar(unicodeScalarLiteral: "A").value return String(countryCode .uppercased() .unicodeScalars .compactMap { UnicodeScalar( regionalIndicatorStartIndex + ($0.value - alphabetOffset) )} .map { Character($0) } ) } emojiFlag(countryCode: "CA") // "🇨🇦"
Это гораздо лучше, чем вести словарь.
P.S. Политика Unicode в отношении флагов
Наверное, важно отметить, что консорциум Unicode пару лет назад сообщил, что больше не принимает предложения по созданию новых флагов.
Основываясь на вопросах и ответах, я не думаю, что это обязательно означает, что страны не получат эмодзи с флагами, это просто означает, что консорциум Unicode не рассматривает предложения по новым нестандартизированным флагам (то есть тем, у которых в Unicode нет кода региона).
Подождите, если страна получит независимость и будет признана ISO, значит ли это, что для нее не будет эмодзи флага?
Флаги для стран с кодами регионов в Unicode рекомендуются автоматически, без каких-либо предложений! Сначала их коды и переведенные названия добавляются в Common Locale Data Repository [CLDR] Юникода, а затем эмодзи становятся действительными в следующей версии Юникода. Эти эмодзи также автоматически рекомендуются для общего обмена и широкого применения.
P.P.S.: Регионы
Я как раз закончил писать эту статью, когда увидел сноску в спецификациях Unicode, касающуюся регионов стран, и не смог удержаться, чтобы не покопаться еще немного.
Оказывается, Emoji 5.0 «ввел поддержку флагов регионов и включил Англию, Шотландию и Уэльс в список рекомендованных для общего обмена (RGI)». Это означает, что большинство платформ должны поддерживать эти флаги, но они не реализованы с помощью тех же смещений кодов стран, которые мы использовали выше.
Вместо этого они используют забавную последовательность тегов emoji, которая начинается с черного флага (🏴 U+1F3F4
) и заканчивается специальным кодом символа отмены тега (U+E007F
).
Если мы прочитаем ISO 3166-2:GB и сопоставим его с опубликованным списком последовательностей эмодзи Unicode, то увидим, что следующие последовательности позволяют нам указывать следующие флаги:
Unicode | Код | Флаг | Регион |
---|---|---|---|
U+1F3F4 U+E0067 U+E0062 U+E0065 U+E006E U+E0067 U+E007F |
🏴GBENG✦ | 🏴 | England |
U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F |
🏴GBSCT✦ | 🏴 | Scotland |
U+1F3F4 U+E0067 U+E0062 U+E0077 U+E006C U+E0073 U+E007F |
🏴GBWLS✦ | 🏴 | Wales |
Мы можем отталкиваться от предыдущей функции, чтобы добавить вариацию регионов:
func emojiFlag(subdivision: String) -> String? { guard let blackFlag = Unicode.Scalar(0x1F3F4), let cancelTag = Unicode.Scalar(0xE007F) else { return nil } // https://en.wikipedia.org/wiki/Tags_(Unicode_block) let tagLetterOffset: UInt32 = 0xE0061 let alphabetOffset = UnicodeScalar(unicodeScalarLiteral: "a").value return String(Character(blackFlag)) + String(subdivision .lowercased() .unicodeScalars .compactMap { Unicode.Scalar( tagLetterOffset + ($0.value - alphabetOffset) )} .map { Character($0) } ) + String(Character(cancelTag)) } emojiFlag(subdivision: "GBENG") // "🏴" emojiFlag(subdivision: "GBSCT") // "🏴" emojiFlag(subdivision: "GBWLS") // "🏴"
Очень аккуратно!