Get the word "Month", "Day", "Week" localized

How can I can the localized string for the month, day, week units. I'm not speaking about "january", "february"... But the localized word "Month". Is the only solution is to do this by hand with NSLocalizedString?

Answered by chepiok in 420304022

Finally found the solution by myself with:


extension Calendar {

    /// get a calendar configured with the language of the user
    static var localized: Calendar {
        let prefLanguage = Locale.preferredLanguages[0]
        var calendar = Calendar(identifier: .gregorian)
        calendar.locale = Locale(identifier: prefLanguage)
        return calendar
    }
    
     func unitTitle(_ unit: NSCalendar.Unit, value: Int = 1, locale: Locale? = nil) -> String {
        let emptyString = String()
        let date = Date()
        let component = getComponent(from: unit)
        guard let sinceUnits = self.date(byAdding: component, value: value, to: date) else {
            return emptyString
        }

        let timeInterval = sinceUnits.timeIntervalSince(date)
        let formatter = DateComponentsFormatter()

        formatter.calendar = self
        formatter.allowedUnits = [unit]
        formatter.unitsStyle = .full
        guard let string = formatter.string(from: timeInterval) else {
            return emptyString
        }

        return string.replacingOccurrences(of: String(value), with: emptyString).trimmingCharacters(in: .whitespaces).capitalized
    }
    
    // swiftlint:disable:next cyclomatic_complexity
    private func getComponent(from unit: NSCalendar.Unit) -> Component {
        let component: Component

        switch unit {
        case .era:
            component = .era
        case .year:
            component = .year
        case .month:
            component = .month
        case .day:
            component = .day
        case .hour:
            component = .hour
        case .minute:
            component = .minute
        case .second:
            component = .second
        case .weekday:
            component = .weekday
        case .weekdayOrdinal:
            component = .weekdayOrdinal
        case .quarter:
            component = .quarter
        case .weekOfMonth:
            component = .weekOfMonth
        case .weekOfYear:
            component = .weekOfYear
        case .yearForWeekOfYear:
            component = .yearForWeekOfYear
        case .nanosecond:
            component = .nanosecond
        case .calendar:
            component = .calendar
        case .timeZone:
            component = .timeZone
        default:
            component = .calendar
        }
        return component
    }
}

And the use is:

Calendar.localized.unitTitle(.day)
Calendar.localized.unitTitle(.weekofMonth)
Calendcar.localized.unitTitle(.month)

Where do you get "month" in the date display ?


Could you show an example of how you get the date, how you localize, how you set locale and what you get with "month" inside.


If you write "month" as a string you define yourself, yes, you need NSLocalized.

Accepted Answer

Finally found the solution by myself with:


extension Calendar {

    /// get a calendar configured with the language of the user
    static var localized: Calendar {
        let prefLanguage = Locale.preferredLanguages[0]
        var calendar = Calendar(identifier: .gregorian)
        calendar.locale = Locale(identifier: prefLanguage)
        return calendar
    }
    
     func unitTitle(_ unit: NSCalendar.Unit, value: Int = 1, locale: Locale? = nil) -> String {
        let emptyString = String()
        let date = Date()
        let component = getComponent(from: unit)
        guard let sinceUnits = self.date(byAdding: component, value: value, to: date) else {
            return emptyString
        }

        let timeInterval = sinceUnits.timeIntervalSince(date)
        let formatter = DateComponentsFormatter()

        formatter.calendar = self
        formatter.allowedUnits = [unit]
        formatter.unitsStyle = .full
        guard let string = formatter.string(from: timeInterval) else {
            return emptyString
        }

        return string.replacingOccurrences(of: String(value), with: emptyString).trimmingCharacters(in: .whitespaces).capitalized
    }
    
    // swiftlint:disable:next cyclomatic_complexity
    private func getComponent(from unit: NSCalendar.Unit) -> Component {
        let component: Component

        switch unit {
        case .era:
            component = .era
        case .year:
            component = .year
        case .month:
            component = .month
        case .day:
            component = .day
        case .hour:
            component = .hour
        case .minute:
            component = .minute
        case .second:
            component = .second
        case .weekday:
            component = .weekday
        case .weekdayOrdinal:
            component = .weekdayOrdinal
        case .quarter:
            component = .quarter
        case .weekOfMonth:
            component = .weekOfMonth
        case .weekOfYear:
            component = .weekOfYear
        case .yearForWeekOfYear:
            component = .yearForWeekOfYear
        case .nanosecond:
            component = .nanosecond
        case .calendar:
            component = .calendar
        case .timeZone:
            component = .timeZone
        default:
            component = .calendar
        }
        return component
    }
}

And the use is:

Calendar.localized.unitTitle(.day)
Calendar.localized.unitTitle(.weekofMonth)
Calendcar.localized.unitTitle(.month)

Just add 3 days to timeInterval if the unit is month to accommodate varying month lengths.

        var timeInterval = sinceUnits.timeIntervalSince(date)
        if unit == .month {
            timeInterval = timeInterval.advanced(by: .days(3))
        }

I managed to do it using this:

func localizedUnit(for unit: Unit, locale: Locale) -> String {

    let numberFormatter = NumberFormatter()
    numberFormatter.notANumberSymbol = ""

    let formatter = MeasurementFormatter()
    formatter.unitStyle = .long
    formatter.numberFormatter = numberFormatter
    formatter.locale = locale

    return formatter.string(from: Measurement(value: .nan, unit: unit))
}

Following up my previous comment (still being approved)

A more type-safe version:

enum UnitSymbol: String {
  case month
  case week
  case day
}

func localizedUnit(for unitSymbol: UnitSymbol, locale: Locale) -> String {

    let numberFormatter = NumberFormatter()
    numberFormatter.notANumberSymbol = ""

    let formatter = MeasurementFormatter()
    formatter.unitStyle = .long
    formatter.numberFormatter = numberFormatter
    formatter.locale = locale

    return formatter.string(from: Measurement(value: .nan, unit: Unit(symbol: unitSymbol.rawValue)))
}
Get the word "Month", "Day", "Week" localized
 
 
Q