Updated the "name(for:)" method for the ContentViewModel view model in the Sample app target to append the region flag emoji to the locale name.
This commit is contained in:
@@ -163,19 +163,29 @@ extension ContentView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the localized name of a locale for the locale picker.
|
/// Returns the localized language name of a locale, followed by the flag emoji of its region, for the locale picker.
|
||||||
///
|
///
|
||||||
/// - Parameter locale: The locale to name.
|
/// - Parameter locale: The locale to name.
|
||||||
/// - Returns: The name of the locale in the user's current locale, or its identifier when no name is available.
|
/// - Returns: The localized language name — in the user's current locale — followed by the region's flag emoji when the
|
||||||
|
/// locale has one, or the locale's identifier when the language has no localized name.
|
||||||
func name(
|
func name(
|
||||||
for locale: Locale
|
for locale: Locale
|
||||||
) -> String {
|
) -> String {
|
||||||
Locale
|
let name = locale
|
||||||
.current
|
.language
|
||||||
.localizedString(
|
.languageCode
|
||||||
forIdentifier: locale.identifier
|
.flatMap { Locale.current.localizedString(forLanguageCode: $0.identifier) }?
|
||||||
)?.localizedCapitalized
|
.localizedCapitalized
|
||||||
?? locale.identifier
|
?? locale.identifier
|
||||||
|
|
||||||
|
guard
|
||||||
|
let region = locale.region,
|
||||||
|
let flag = flag(for: region)
|
||||||
|
else {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
return "\(name) \(flag)"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles the transcription of a processed recording, presenting it in the modal sheet.
|
/// Handles the transcription of a processed recording, presenting it in the modal sheet.
|
||||||
@@ -202,6 +212,35 @@ private extension ContentView.Model {
|
|||||||
|
|
||||||
// MARK: Methods
|
// MARK: Methods
|
||||||
|
|
||||||
|
/// Returns the flag emoji of the given region, built from its two-letter code, or `nil` when the region has no such code.
|
||||||
|
///
|
||||||
|
/// - Parameter region: The region to build a flag emoji for.
|
||||||
|
/// - Returns: The region's flag emoji, or `nil` when its identifier is not a two-letter code — a numeric UN M49 region, for example.
|
||||||
|
func flag(
|
||||||
|
for region: Locale.Region
|
||||||
|
) -> String? {
|
||||||
|
let code = region.identifier
|
||||||
|
|
||||||
|
guard
|
||||||
|
code.count == 2,
|
||||||
|
code.allSatisfy(\.isLetter)
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var flag = ""
|
||||||
|
|
||||||
|
for scalar in code.uppercased().unicodeScalars {
|
||||||
|
guard let indicator = Unicode.Scalar(Constant.Flag.base + scalar.value) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.unicodeScalars.append(indicator)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flag
|
||||||
|
}
|
||||||
|
|
||||||
/// Starts listening to the events emitted by the preinstalling service, unless already listening: every started, cancelled, and
|
/// Starts listening to the events emitted by the preinstalling service, unless already listening: every started, cancelled, and
|
||||||
/// failed download is posted to the ``ContentView/Model/notifier``, and a cancellation or failure clears the preinstalled locale
|
/// failed download is posted to the ``ContentView/Model/notifier``, and a cancellation or failure clears the preinstalled locale
|
||||||
/// — only when it is still the affected one, so a newer preinstallation is never forgotten — letting a re-pick retry.
|
/// — only when it is still the affected one, so a newer preinstallation is never forgotten — letting a re-pick retry.
|
||||||
@@ -254,6 +293,12 @@ private extension ContentView.Model {
|
|||||||
|
|
||||||
/// The constant values used across the model.
|
/// The constant values used across the model.
|
||||||
private enum Constant {
|
private enum Constant {
|
||||||
|
/// The flag constants.
|
||||||
|
enum Flag {
|
||||||
|
/// The distance from an ASCII uppercase letter to its Regional Indicator Symbol, used to build a flag emoji from a region code.
|
||||||
|
static let base: UInt32 = 0x1F1E6 - 0x41
|
||||||
|
}
|
||||||
|
|
||||||
/// The symbol constants.
|
/// The symbol constants.
|
||||||
enum Symbol {
|
enum Symbol {
|
||||||
/// The system symbol of a cancelled download.
|
/// The system symbol of a cancelled download.
|
||||||
|
|||||||
@@ -226,6 +226,36 @@ struct ContentViewModelTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Name
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
@Suite("Name")
|
||||||
|
struct Name {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `name pairs the localized language name with the region flag`() {
|
||||||
|
let model = Model(preinstaller: PreinstallingMock())
|
||||||
|
|
||||||
|
#expect(model.name(for: .english) == "\(languageName("en")) 🇺🇸")
|
||||||
|
#expect(model.name(for: .dutch) == "\(languageName("nl")) 🇳🇱")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `name omits the flag for a locale without a region`() {
|
||||||
|
let model = Model(preinstaller: PreinstallingMock())
|
||||||
|
|
||||||
|
#expect(!model.name(for: Locale(identifier: "en")).containsFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func `name omits the flag for a numeric region`() {
|
||||||
|
let model = Model(preinstaller: PreinstallingMock())
|
||||||
|
|
||||||
|
#expect(!model.name(for: Locale(identifier: "es-419")).containsFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Constants
|
// MARK: - Constants
|
||||||
@@ -238,3 +268,20 @@ private extension Locale {
|
|||||||
/// Another supported locale to preinstall in the tests.
|
/// Another supported locale to preinstall in the tests.
|
||||||
static let english = Locale(identifier: "en-US")
|
static let english = Locale(identifier: "en-US")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Helpers
|
||||||
|
|
||||||
|
/// The capitalized, localized language name for a language code, mirroring how the model builds the language part of a locale's name.
|
||||||
|
///
|
||||||
|
/// - Parameter code: The language code to name.
|
||||||
|
/// - Returns: The localized language name in the current locale, capitalized.
|
||||||
|
private func languageName(_ code: String) -> String {
|
||||||
|
(Locale.current.localizedString(forLanguageCode: code) ?? code).localizedCapitalized
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension String {
|
||||||
|
/// Whether the string contains a Regional Indicator Symbol scalar — part of a flag emoji.
|
||||||
|
var containsFlag: Bool {
|
||||||
|
unicodeScalars.contains { $0.value >= 0x1F1E6 && $0.value <= 0x1F1FF }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user