From 9de7fa0d9a50be424084dc91a41c42a298a24a46 Mon Sep 17 00:00:00 2001 From: Carlos Enumo Date: Sun, 13 Oct 2024 22:44:25 +0100 Subject: [PATCH] Add option to choose the native calendar view mode --- Calendr/Assets/Strings.generated.swift | 8 +++++ Calendr/Assets/cs.lproj/Localizable.strings | 4 +++ Calendr/Assets/de.lproj/Localizable.strings | 4 +++ Calendr/Assets/en.lproj/Localizable.strings | 4 +++ Calendr/Assets/es.lproj/Localizable.strings | 4 +++ Calendr/Assets/fr.lproj/Localizable.strings | 4 +++ Calendr/Assets/it.lproj/Localizable.strings | 4 +++ Calendr/Assets/pt.lproj/Localizable.strings | 4 +++ Calendr/Assets/sk.lproj/Localizable.strings | 4 +++ Calendr/Assets/sv.lproj/Localizable.strings | 4 +++ .../Assets/zh-Hans.lproj/Localizable.strings | 4 +++ Calendr/Automation/CalendarScript.swift | 7 +--- Calendr/Main/MainViewController.swift | 12 +++++-- Calendr/Mocks/MockCalendarSettings.swift | 2 ++ .../GeneralSettingsViewController.swift | 33 ++++++++++++++++++- Calendr/Settings/Prefs+UserDefaults.swift | 7 ++++ Calendr/Settings/SettingsViewModel.swift | 33 +++++++++++++++++-- CalendrTests/Mocks/MockCalendarSettings.swift | 3 ++ MISSING_TRANSLATIONS.md | 17 +++++----- 19 files changed, 142 insertions(+), 20 deletions(-) diff --git a/Calendr/Assets/Strings.generated.swift b/Calendr/Assets/Strings.generated.swift index 55d5c3d..69c5918 100644 --- a/Calendr/Assets/Strings.generated.swift +++ b/Calendr/Assets/Strings.generated.swift @@ -139,8 +139,14 @@ internal enum Strings { internal static let transparency = Strings.tr("Localizable", "settings.appearance.transparency", fallback: "Transparency") } internal enum Calendar { + /// Calendar app view mode + internal static let calendarAppViewMode = Strings.tr("Localizable", "settings.calendar.calendar_app_view_mode", fallback: "Calendar app view mode") /// Hold ⌥ to peek at hovered dates internal static let dateHoverOption = Strings.tr("Localizable", "settings.calendar.date_hover_option", fallback: "Hold ⌥ to peek at hovered dates") + /// day + internal static let day = Strings.tr("Localizable", "settings.calendar.day", fallback: "day") + /// month + internal static let month = Strings.tr("Localizable", "settings.calendar.month", fallback: "month") /// Preserve selected date on hide internal static let preserveSelectedDate = Strings.tr("Localizable", "settings.calendar.preserve_selected_date", fallback: "Preserve selected date on hide") /// Show declined events @@ -149,6 +155,8 @@ internal enum Strings { internal static let showDeclinedEventsTooltip = Strings.tr("Localizable", "settings.calendar.show_declined_events_tooltip", fallback: "This only works if it is also enabled in the native Calendar app.") /// Show week numbers internal static let showWeekNumbers = Strings.tr("Localizable", "settings.calendar.show_week_numbers", fallback: "Show week numbers") + /// week + internal static let week = Strings.tr("Localizable", "settings.calendar.week", fallback: "week") } internal enum Events { /// Show finished events diff --git a/Calendr/Assets/cs.lproj/Localizable.strings b/Calendr/Assets/cs.lproj/Localizable.strings index da23ba8..344ff68 100644 --- a/Calendr/Assets/cs.lproj/Localizable.strings +++ b/Calendr/Assets/cs.lproj/Localizable.strings @@ -48,6 +48,10 @@ "settings.calendar.show_declined_events" = "Zobrazit odmítnuté události"; "settings.calendar.show_declined_events_tooltip" = "Funguje pouze tehdy, pokud je také zapnuto v nativní aplikaci Kalendář."; //"settings.calendar.date_hover_option" = "Hold ⌥ to peek at hovered dates"; +//"settings.calendar.calendar_app_view_mode" = "Calendar app view mode"; +//"settings.calendar.month" = "month"; +//"settings.calendar.week" = "week"; +//"settings.calendar.day" = "day"; "settings.events" = "Události"; //"settings.events.show_map" = "Show map and weather"; diff --git a/Calendr/Assets/de.lproj/Localizable.strings b/Calendr/Assets/de.lproj/Localizable.strings index 0be2705..4b09d53 100644 --- a/Calendr/Assets/de.lproj/Localizable.strings +++ b/Calendr/Assets/de.lproj/Localizable.strings @@ -48,6 +48,10 @@ "settings.calendar.show_declined_events" = "Abgelehnte Ereignisse anzeigen"; "settings.calendar.show_declined_events_tooltip" = "Dies funktioniert nur, wenn es auch in der nativen Kalender-App aktiviert ist."; //"settings.calendar.date_hover_option" = "Hold ⌥ to peek at hovered dates"; +//"settings.calendar.calendar_app_view_mode" = "Calendar app view mode"; +//"settings.calendar.month" = "month"; +//"settings.calendar.week" = "week"; +//"settings.calendar.day" = "day"; "settings.events" = "Termine"; "settings.events.show_map" = "Karte und Wetter anzeigen"; diff --git a/Calendr/Assets/en.lproj/Localizable.strings b/Calendr/Assets/en.lproj/Localizable.strings index 6391541..cc88861 100644 --- a/Calendr/Assets/en.lproj/Localizable.strings +++ b/Calendr/Assets/en.lproj/Localizable.strings @@ -48,6 +48,10 @@ "settings.calendar.show_declined_events" = "Show declined events"; "settings.calendar.show_declined_events_tooltip" = "This only works if it is also enabled in the native Calendar app."; "settings.calendar.date_hover_option" = "Hold ⌥ to peek at hovered dates"; +"settings.calendar.calendar_app_view_mode" = "Calendar app view mode"; +"settings.calendar.month" = "month"; +"settings.calendar.week" = "week"; +"settings.calendar.day" = "day"; "settings.events" = "Events"; "settings.events.show_map" = "Show map and weather"; diff --git a/Calendr/Assets/es.lproj/Localizable.strings b/Calendr/Assets/es.lproj/Localizable.strings index 89354bc..e2ad4f5 100644 --- a/Calendr/Assets/es.lproj/Localizable.strings +++ b/Calendr/Assets/es.lproj/Localizable.strings @@ -48,6 +48,10 @@ "settings.calendar.show_declined_events" = "Mostrar eventos rechazados"; "settings.calendar.show_declined_events_tooltip" = "Esto solo funciona si también está habilitado en la aplicación Calendario nativa."; //"settings.calendar.date_hover_option" = "Hold ⌥ to peek at hovered dates"; +//"settings.calendar.calendar_app_view_mode" = "Calendar app view mode"; +//"settings.calendar.month" = "month"; +//"settings.calendar.week" = "week"; +//"settings.calendar.day" = "day"; "settings.events" = "Eventos"; "settings.events.show_map" = "Mostrar mapa y clima"; diff --git a/Calendr/Assets/fr.lproj/Localizable.strings b/Calendr/Assets/fr.lproj/Localizable.strings index ec82379..b6eb4b4 100644 --- a/Calendr/Assets/fr.lproj/Localizable.strings +++ b/Calendr/Assets/fr.lproj/Localizable.strings @@ -48,6 +48,10 @@ "settings.calendar.show_declined_events" = "Afficher les événements refusés"; "settings.calendar.show_declined_events_tooltip" = "Cela ne fonctionne que s'il est également activé dans l'application Calendrier native."; //"settings.calendar.date_hover_option" = "Hold ⌥ to peek at hovered dates"; +//"settings.calendar.calendar_app_view_mode" = "Calendar app view mode"; +//"settings.calendar.month" = "month"; +//"settings.calendar.week" = "week"; +//"settings.calendar.day" = "day"; "settings.events" = "Événements"; "settings.events.show_map" = "Afficher la carte et la météo"; diff --git a/Calendr/Assets/it.lproj/Localizable.strings b/Calendr/Assets/it.lproj/Localizable.strings index 492017f..1842eac 100644 --- a/Calendr/Assets/it.lproj/Localizable.strings +++ b/Calendr/Assets/it.lproj/Localizable.strings @@ -48,6 +48,10 @@ "settings.calendar.show_declined_events" = "Mostra eventi rifiutati"; "settings.calendar.show_declined_events_tooltip" = "Funziona solo se è abilitato anche nell'app Calendario nativa."; //"settings.calendar.date_hover_option" = "Hold ⌥ to peek at hovered dates"; +//"settings.calendar.calendar_app_view_mode" = "Calendar app view mode"; +//"settings.calendar.month" = "month"; +//"settings.calendar.week" = "week"; +//"settings.calendar.day" = "day"; "settings.events" = "Eventi"; "settings.events.show_map" = "Mostra mappa e meteo"; diff --git a/Calendr/Assets/pt.lproj/Localizable.strings b/Calendr/Assets/pt.lproj/Localizable.strings index 885dbd0..f57d15d 100644 --- a/Calendr/Assets/pt.lproj/Localizable.strings +++ b/Calendr/Assets/pt.lproj/Localizable.strings @@ -48,6 +48,10 @@ "settings.calendar.show_declined_events" = "Mostrar eventos recusados"; "settings.calendar.show_declined_events_tooltip" = "Isso só funciona se também estiver ativado no aplicativo Calendário nativo."; "settings.calendar.date_hover_option" = "Segure ⌥ para espiar datas sob o cursor"; +//"settings.calendar.calendar_app_view_mode" = "Calendar app view mode"; +//"settings.calendar.month" = "month"; +//"settings.calendar.week" = "week"; +//"settings.calendar.day" = "day"; "settings.events" = "Eventos"; "settings.events.show_map" = "Mostrar mapa e clima"; diff --git a/Calendr/Assets/sk.lproj/Localizable.strings b/Calendr/Assets/sk.lproj/Localizable.strings index 83bb06d..d69ba7a 100644 --- a/Calendr/Assets/sk.lproj/Localizable.strings +++ b/Calendr/Assets/sk.lproj/Localizable.strings @@ -48,6 +48,10 @@ "settings.calendar.show_declined_events" = "Zobraziť odmietnuté udalosti"; "settings.calendar.show_declined_events_tooltip" = "Funguje, len ak je to povolené aj v natívnej aplikácii Kalendár."; //"settings.calendar.date_hover_option" = "Hold ⌥ to peek at hovered dates"; +//"settings.calendar.calendar_app_view_mode" = "Calendar app view mode"; +//"settings.calendar.month" = "month"; +//"settings.calendar.week" = "week"; +//"settings.calendar.day" = "day"; "settings.events" = "Udalosti"; "settings.events.show_map" = "Zobraziť mapu a počasie"; diff --git a/Calendr/Assets/sv.lproj/Localizable.strings b/Calendr/Assets/sv.lproj/Localizable.strings index 692b080..01ae13d 100644 --- a/Calendr/Assets/sv.lproj/Localizable.strings +++ b/Calendr/Assets/sv.lproj/Localizable.strings @@ -48,6 +48,10 @@ "settings.calendar.show_declined_events" = "Visa avböjda händelser"; "settings.calendar.show_declined_events_tooltip" = "Detta fungerar bara om det också är aktiverat i den inbyggda kalenderappen."; //"settings.calendar.date_hover_option" = "Hold ⌥ to peek at hovered dates"; +//"settings.calendar.calendar_app_view_mode" = "Calendar app view mode"; +//"settings.calendar.month" = "month"; +//"settings.calendar.week" = "week"; +//"settings.calendar.day" = "day"; "settings.events" = "Händelser"; //"settings.events.show_map" = "Show map and weather"; diff --git a/Calendr/Assets/zh-Hans.lproj/Localizable.strings b/Calendr/Assets/zh-Hans.lproj/Localizable.strings index 1c80795..b24eba4 100644 --- a/Calendr/Assets/zh-Hans.lproj/Localizable.strings +++ b/Calendr/Assets/zh-Hans.lproj/Localizable.strings @@ -48,6 +48,10 @@ "settings.calendar.show_declined_events" = "显示拒绝的日程"; "settings.calendar.show_declined_events_tooltip" = "此功能需在原生日历应用中同样启用。"; //"settings.calendar.date_hover_option" = "Hold ⌥ to peek at hovered dates"; +//"settings.calendar.calendar_app_view_mode" = "Calendar app view mode"; +//"settings.calendar.month" = "month"; +//"settings.calendar.week" = "week"; +//"settings.calendar.day" = "day"; "settings.events" = "日程"; //"settings.events.show_map" = "Show map and weather"; diff --git a/Calendr/Automation/CalendarScript.swift b/Calendr/Automation/CalendarScript.swift index 1dff5b1..295f32e 100644 --- a/Calendr/Automation/CalendarScript.swift +++ b/Calendr/Automation/CalendarScript.swift @@ -16,18 +16,13 @@ class CalendarScript { self.workspace = workspace } - enum CalendarViewMode: String { - case day - case month - } - func openCalendar(at date: Date, mode: CalendarViewMode) { Task { do { try await runScript(""" tell application "Calendar" - switch view to \(mode) view view calendar at date ("\(formatter.string(from: date))") + switch view to \(mode) view activate end tell """) diff --git a/Calendr/Main/MainViewController.swift b/Calendr/Main/MainViewController.swift index 79fc30c..fcfdb84 100644 --- a/Calendr/Main/MainViewController.swift +++ b/Calendr/Main/MainViewController.swift @@ -393,9 +393,15 @@ class MainViewController: NSViewController { .disposed(by: disposeBag) calendarBtn.rx.tap - .withLatestFrom(selectedDate) - .bind { date in - calendarScript.openCalendar(at: date, mode: .month) + .withLatestFrom( + Observable.combineLatest(selectedDate, settingsViewModel.calendarAppViewMode) + ) + .bind { [dateProvider] date, mode in + var date = date + if mode == .week, let week = dateProvider.calendar.dateInterval(of: .weekOfYear, for: date) { + date = week.start + } + calendarScript.openCalendar(at: date, mode: mode) } .disposed(by: disposeBag) diff --git a/Calendr/Mocks/MockCalendarSettings.swift b/Calendr/Mocks/MockCalendarSettings.swift index 695f786..a0f1227 100644 --- a/Calendr/Mocks/MockCalendarSettings.swift +++ b/Calendr/Mocks/MockCalendarSettings.swift @@ -20,6 +20,7 @@ class MockCalendarSettings: CalendarSettings { let showDeclinedEvents: Observable let preserveSelectedDate: Observable let dateHoverOption: Observable + let calendarAppViewMode: Observable init( calendarScaling: Double = 1, @@ -38,6 +39,7 @@ class MockCalendarSettings: CalendarSettings { self.preserveSelectedDate = .just(false) self.showDeclinedEvents = .just(false) self.dateHoverOption = .just(false) + self.calendarAppViewMode = .just(.month) } } diff --git a/Calendr/Settings/GeneralSettingsViewController.swift b/Calendr/Settings/GeneralSettingsViewController.swift index 99d7a97..2035c24 100644 --- a/Calendr/Settings/GeneralSettingsViewController.swift +++ b/Calendr/Settings/GeneralSettingsViewController.swift @@ -33,6 +33,8 @@ class GeneralSettingsViewController: NSViewController, SettingsUI { private let showDeclinedEventsCheckbox = Checkbox(title: Strings.Settings.Calendar.showDeclinedEvents) private let preserveSelectedDateCheckbox = Checkbox(title: Strings.Settings.Calendar.preserveSelectedDate) private let dateHoverOptionCheckbox = Checkbox(title: Strings.Settings.Calendar.dateHoverOption) + private let calendarAppViewModeLabel = Label(text: Strings.Settings.Calendar.calendarAppViewMode) + private let calendarAppViewModeDropdown = Dropdown() // Events private let showMapCheckbox = Checkbox(title: Strings.Settings.Events.showMap) @@ -203,6 +205,9 @@ class GeneralSettingsViewController: NSViewController, SettingsUI { firstWeekdayPrev.setContentHuggingPriority(.fittingSizeCompression, for: .horizontal) firstWeekdayNext.setContentHuggingPriority(.fittingSizeCompression, for: .horizontal) + calendarAppViewModeDropdown.isBordered = false + calendarAppViewModeDropdown.setContentHuggingPriority(.required, for: .horizontal) + return NSStackView(views: [ NSStackView(views: [firstWeekdayPrev, highlightedWeekdaysButtons, firstWeekdayNext]) .with(distribution: .fillProportionally), @@ -210,7 +215,9 @@ class GeneralSettingsViewController: NSViewController, SettingsUI { showWeekNumbersCheckbox, NSStackView(views: [showDeclinedEventsCheckbox, showDeclinedEventsTooltip]), preserveSelectedDateCheckbox, - dateHoverOptionCheckbox + dateHoverOptionCheckbox, + .dummy, + NSStackView(views: [calendarAppViewModeLabel, calendarAppViewModeDropdown]) ]) .with(orientation: .vertical) }() @@ -270,6 +277,8 @@ class GeneralSettingsViewController: NSViewController, SettingsUI { setUpIconStyle() setUpDateFormat() + + setUpCalendarAppViewMode() } private func setUpIconStyle() { @@ -372,6 +381,28 @@ class GeneralSettingsViewController: NSViewController, SettingsUI { .disposed(by: disposeBag) } + private func setUpCalendarAppViewMode() { + + let calendarAppViewModeControl = calendarAppViewModeDropdown.rx.controlProperty( + getter: \.indexOfSelectedItem, + setter: { $0.selectItem(at: $1) } + ) + + let options = viewModel.calendarAppViewModeOptions + calendarAppViewModeDropdown.addItems(withTitles: options.map { "\($0.title) " }) + + calendarAppViewModeControl + .skip(1) + .map { options[$0].mode } + .bind(to: viewModel.calendarAppViewModeObserver) + .disposed(by: disposeBag) + + viewModel.calendarAppViewMode + .compactMap(options.map(\.mode).firstIndex(of:)) + .bind(to: calendarAppViewModeControl) + .disposed(by: disposeBag) + } + private func setUpCalendar() { setUpfirstWeekday() diff --git a/Calendr/Settings/Prefs+UserDefaults.swift b/Calendr/Settings/Prefs+UserDefaults.swift index 9530ea0..cb145da 100644 --- a/Calendr/Settings/Prefs+UserDefaults.swift +++ b/Calendr/Settings/Prefs+UserDefaults.swift @@ -33,6 +33,7 @@ enum Prefs { static let showDeclinedEvents = "show_declined_events" static let preserveSelectedDate = "preserve_selected_date" static let dateHoverOption = "date_hover_option" + static let calendarAppViewMode = "calendar_app_view_mode" // Event Details static let showMap = "show_map" @@ -83,6 +84,7 @@ func registerDefaultPrefs(in userDefaults: UserDefaults, calendar: Calendar = .c Prefs.showDeclinedEvents: false, Prefs.preserveSelectedDate: false, Prefs.dateHoverOption: false, + Prefs.calendarAppViewMode: CalendarViewMode.month.rawValue, // Event Details Prefs.showMap: true, @@ -210,6 +212,11 @@ extension UserDefaults { set { set(newValue, forKey: Prefs.dateHoverOption) } } + @objc dynamic var calendarAppViewMode: String { + get { string(forKey: Prefs.calendarAppViewMode) ?? "" } + set { set(newValue, forKey: Prefs.calendarAppViewMode) } + } + // Event Details @objc dynamic var showMap: Bool { diff --git a/Calendr/Settings/SettingsViewModel.swift b/Calendr/Settings/SettingsViewModel.swift index 8262bc0..fc3f2be 100644 --- a/Calendr/Settings/SettingsViewModel.swift +++ b/Calendr/Settings/SettingsViewModel.swift @@ -31,6 +31,12 @@ extension PopoverMaterial { } } +enum CalendarViewMode: String, CaseIterable { + case month + case week + case day +} + enum StatusItemIconStyle: String, CaseIterable { case calendar case date @@ -57,6 +63,7 @@ protocol CalendarSettings { var showDeclinedEvents: Observable { get } var preserveSelectedDate: Observable { get } var dateHoverOption: Observable { get } + var calendarAppViewMode: Observable { get } } protocol AppearanceSettings { @@ -101,6 +108,11 @@ class SettingsViewModel: let title: String } + struct CalendarViewModeOption: Equatable { + let mode: CalendarViewMode + let title: String + } + // Observers let toggleAutoLaunch: AnyObserver let toggleStatusItemIcon: AnyObserver @@ -129,6 +141,7 @@ class SettingsViewModel: let transparencyObserver: AnyObserver let textScalingObserver: AnyObserver let calendarTextScalingObserver: AnyObserver + let calendarAppViewModeObserver: AnyObserver // Observables let autoLaunch: Observable @@ -163,9 +176,23 @@ class SettingsViewModel: let popoverMaterial: Observable let textScaling: Observable let calendarTextScaling: Observable + let calendarAppViewMode: Observable let isPresented = BehaviorSubject(value: false) + let calendarAppViewModeOptions = CalendarViewMode.allCases.map { + let title = switch $0 { + case .month: + Strings.Settings.Calendar.month + case .week: + Strings.Settings.Calendar.week + case .day: + Strings.Settings.Calendar.day + } + + return CalendarViewModeOption(mode: $0, title: title) + } + let dateFormatPlaceholder = AppConstants.defaultCustomDateFormat private let autoLauncher: AutoLauncher @@ -207,6 +234,7 @@ class SettingsViewModel: transparencyObserver = userDefaults.rx.observer(for: \.transparencyLevel) textScalingObserver = userDefaults.rx.observer(for: \.textScaling) calendarTextScalingObserver = userDefaults.rx.observer(for: \.calendarTextScaling) + calendarAppViewModeObserver = userDefaults.rx.observer(for: \.calendarAppViewMode).mapObserver(\.rawValue) // MARK: - Observables @@ -226,8 +254,8 @@ class SettingsViewModel: /* ----------------------- */ showStatusItemBackground = userDefaults.rx.observe(\.statusItemBackgroundEnabled) - statusItemIconStyle = userDefaults.rx.observe(\.statusItemIconStyle).map { StatusItemIconStyle(rawValue: $0) ?? .calendar } - statusItemDateStyle = userDefaults.rx.observe(\.statusItemDateStyle).map { StatusItemDateStyle(rawValue: $0) ?? .none } + statusItemIconStyle = userDefaults.rx.observe(\.statusItemIconStyle).map { .init(rawValue: $0) ?? .calendar } + statusItemDateStyle = userDefaults.rx.observe(\.statusItemDateStyle).map { .init(rawValue: $0) ?? .none } statusItemDateFormat = userDefaults.rx.observe(\.statusItemDateFormat) showEventStatusItem = userDefaults.rx.observe(\.showEventStatusItem) eventStatusItemFontSize = userDefaults.rx.observe(\.eventStatusItemFontSize) @@ -248,6 +276,7 @@ class SettingsViewModel: popoverTransparency = userDefaults.rx.observe(\.transparencyLevel) textScaling = userDefaults.rx.observe(\.textScaling) calendarTextScaling = userDefaults.rx.observe(\.calendarTextScaling) + calendarAppViewMode = userDefaults.rx.observe(\.calendarAppViewMode).map { .init(rawValue: $0) ?? .month } let localeChangeObservable = notificationCenter.rx .notification(NSLocale.currentLocaleDidChangeNotification) diff --git a/CalendrTests/Mocks/MockCalendarSettings.swift b/CalendrTests/Mocks/MockCalendarSettings.swift index 3d16a83..8362338 100644 --- a/CalendrTests/Mocks/MockCalendarSettings.swift +++ b/CalendrTests/Mocks/MockCalendarSettings.swift @@ -27,6 +27,8 @@ class MockCalendarSettings: CalendarSettings { let preserveSelectedDate: Observable + let calendarAppViewMode: Observable + let calendarScaling: Observable let textScaling: Observable let calendarTextScaling: Observable @@ -38,6 +40,7 @@ class MockCalendarSettings: CalendarSettings { (showDeclinedEvents, toggleDeclinedEvents) = BehaviorSubject.pipe(value: false) (dateHoverOption, toggleDateHoverOption) = BehaviorSubject.pipe(value: false) preserveSelectedDate = .just(false) + calendarAppViewMode = .just(.month) calendarScaling = .just(1) textScaling = .just(1) calendarTextScaling = .just(1) diff --git a/MISSING_TRANSLATIONS.md b/MISSING_TRANSLATIONS.md index 931401d..888f008 100644 --- a/MISSING_TRANSLATIONS.md +++ b/MISSING_TRANSLATIONS.md @@ -1,14 +1,15 @@ # The following languages have missing translations Language|Count -|- -[German - Deutsch - (de)](Calendr/Assets/de.lproj/Localizable.strings)|6 -[Chinese - 中文 - (zh-Hans)](Calendr/Assets/zh-Hans.lproj/Localizable.strings)|18 -[Spanish - español - (es)](Calendr/Assets/es.lproj/Localizable.strings)|3 -[Italian - italiano - (it)](Calendr/Assets/it.lproj/Localizable.strings)|6 -[Slovak - slovenčina - (sk)](Calendr/Assets/sk.lproj/Localizable.strings)|4 -[Swedish - svenska - (sv)](Calendr/Assets/sv.lproj/Localizable.strings)|18 -[Czech - čeština - (cs)](Calendr/Assets/cs.lproj/Localizable.strings)|18 -[French - français - (fr)](Calendr/Assets/fr.lproj/Localizable.strings)|3 +[German - Deutsch - (de)](Calendr/Assets/de.lproj/Localizable.strings)|10 +[Chinese - 中文 - (zh-Hans)](Calendr/Assets/zh-Hans.lproj/Localizable.strings)|22 +[Spanish - español - (es)](Calendr/Assets/es.lproj/Localizable.strings)|7 +[Italian - italiano - (it)](Calendr/Assets/it.lproj/Localizable.strings)|10 +[Slovak - slovenčina - (sk)](Calendr/Assets/sk.lproj/Localizable.strings)|8 +[Swedish - svenska - (sv)](Calendr/Assets/sv.lproj/Localizable.strings)|22 +[Czech - čeština - (cs)](Calendr/Assets/cs.lproj/Localizable.strings)|22 +[French - français - (fr)](Calendr/Assets/fr.lproj/Localizable.strings)|7 +[Portuguese - português - (pt)](Calendr/Assets/pt.lproj/Localizable.strings)|4 Feel free to open a new issue or pull request with the missing values.