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.
|
||||
/// - 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(
|
||||
for locale: Locale
|
||||
) -> String {
|
||||
Locale
|
||||
.current
|
||||
.localizedString(
|
||||
forIdentifier: locale.identifier
|
||||
)?.localizedCapitalized
|
||||
let name = locale
|
||||
.language
|
||||
.languageCode
|
||||
.flatMap { Locale.current.localizedString(forLanguageCode: $0.identifier) }?
|
||||
.localizedCapitalized
|
||||
?? 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.
|
||||
@@ -202,6 +212,35 @@ private extension ContentView.Model {
|
||||
|
||||
// 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
|
||||
/// 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.
|
||||
@@ -254,6 +293,12 @@ private extension ContentView.Model {
|
||||
|
||||
/// The constant values used across the model.
|
||||
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.
|
||||
enum Symbol {
|
||||
/// 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
|
||||
@@ -238,3 +268,20 @@ private extension Locale {
|
||||
/// Another supported locale to preinstall in the tests.
|
||||
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