diff --git a/MealMood.xcodeproj/project.pbxproj b/MealMood.xcodeproj/project.pbxproj index 8e2af9d..bc7f8f2 100644 --- a/MealMood.xcodeproj/project.pbxproj +++ b/MealMood.xcodeproj/project.pbxproj @@ -122,6 +122,7 @@ 89C47DD7B8F4B1441780E611 /* HomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = ""; }; 8CFA001E1BFA916ABC4C44C1 /* PremiumUpsellBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumUpsellBanner.swift; sourceTree = ""; }; 8EE10285FEE9BE0684CC66A3 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; + 93B4B31A8F6E4D7AAB1B6A01 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; 965555A7774FF7EB975F0A07 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 9F86EB5DDB126D73486FD956 /* MealMood.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MealMood.entitlements; sourceTree = ""; }; 9FBA11C8A81D5C0CA8A3C13D /* MealWindowsStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MealWindowsStepView.swift; sourceTree = ""; }; @@ -141,6 +142,9 @@ D63CA2A8666E1FBBAD65F141 /* DishFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DishFormView.swift; sourceTree = ""; }; DBD33453B7DF86EDBAEB80F1 /* DateHelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateHelpersTests.swift; sourceTree = ""; }; DFA97AD958C922D8AFF73314 /* PrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryButton.swift; sourceTree = ""; }; + E18A2F6B56D64C1EBC91E219 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + E18A2F6B56D64C1EBC91E21A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + E18A2F6B56D64C1EBC91E21B /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = ""; }; E8472961AE65E2A3460B67C3 /* DishViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DishViewModel.swift; sourceTree = ""; }; E8825212ACFA0D3F6C716E94 /* ReviewPromptService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewPromptService.swift; sourceTree = ""; }; F0200C0C11C912711C724558 /* PremiumAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PremiumAccess.swift; sourceTree = ""; }; @@ -436,8 +440,12 @@ hasScannedForEncodings = 0; knownRegions = ( Base, + de, en, es, + fr, + it, + "pt-BR", ); mainGroup = 011822327F563ED89CB571FB; minimizedProjectReferenceProxies = 1; @@ -556,6 +564,10 @@ children = ( FF2DE77AD57DB6DAB188CD9E /* en */, 965555A7774FF7EB975F0A07 /* es */, + 93B4B31A8F6E4D7AAB1B6A01 /* fr */, + E18A2F6B56D64C1EBC91E219 /* de */, + E18A2F6B56D64C1EBC91E21A /* it */, + E18A2F6B56D64C1EBC91E21B /* pt-BR */, ); name = Localizable.strings; sourceTree = ""; @@ -643,7 +655,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = MealMood/Resources/MealMood.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 6; DEVELOPMENT_TEAM = 2825Q76T7H; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = MealMood/Resources/Info.plist; @@ -652,7 +664,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.mealmood; PRODUCT_NAME = MealMood; SDKROOT = iphoneos; @@ -747,7 +759,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = MealMood/Resources/MealMood.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 6; DEVELOPMENT_TEAM = 2825Q76T7H; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = MealMood/Resources/Info.plist; @@ -756,7 +768,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.1; + MARKETING_VERSION = 1.0.2; PRODUCT_BUNDLE_IDENTIFIER = com.alexandrevazquez.mealmood; PRODUCT_NAME = MealMood; SDKROOT = iphoneos; diff --git a/MealMood/Models/AppSettings.swift b/MealMood/Models/AppSettings.swift index aa1c58e..040e072 100644 --- a/MealMood/Models/AppSettings.swift +++ b/MealMood/Models/AppSettings.swift @@ -16,6 +16,7 @@ final class AppSettings { var eventPrefix: String var reminderMinutesBefore: Int? var iCloudSyncEnabled: Bool? + var weekExportStyle: String? var isPremium: Bool var onboardingCompleted: Bool @@ -41,6 +42,7 @@ final class AppSettings { self.eventPrefix = "🍽️" self.reminderMinutesBefore = nil self.iCloudSyncEnabled = true + self.weekExportStyle = nil self.isPremium = false self.onboardingCompleted = false } @@ -64,6 +66,11 @@ final class AppSettings { get { iCloudSyncEnabled ?? true } set { iCloudSyncEnabled = newValue } } + + var weekExportStyleEnum: WeekExportStyle { + get { WeekExportStyle(rawValue: weekExportStyle ?? "") ?? .defaultStyle } + set { weekExportStyle = newValue.rawValue } + } } enum MealWindows: String, CaseIterable { @@ -92,12 +99,20 @@ enum AppLanguage: String, CaseIterable { case system case spanish case english + case french + case german + case italian + case portugueseBrazil var displayName: String { switch self { case .system: return "System" case .spanish: return "Español" case .english: return "English" + case .french: return "Français" + case .german: return "Deutsch" + case .italian: return "Italiano" + case .portugueseBrazil: return "Português (Brasil)" } } @@ -106,12 +121,23 @@ enum AppLanguage: String, CaseIterable { case .system: return resolved().localeIdentifier case .spanish: return "es" case .english: return "en" + case .french: return "fr" + case .german: return "de" + case .italian: return "it" + case .portugueseBrazil: return "pt-BR" } } func resolved() -> AppLanguage { if self != .system { return self } - return Locale.current.identifier.lowercased().hasPrefix("es") ? .spanish : .english + let localeIdentifier = Locale.current.identifier.lowercased() + + if localeIdentifier.hasPrefix("es") { return .spanish } + if localeIdentifier.hasPrefix("fr") { return .french } + if localeIdentifier.hasPrefix("de") { return .german } + if localeIdentifier.hasPrefix("it") { return .italian } + if localeIdentifier.hasPrefix("pt") { return .portugueseBrazil } + return .english } } @@ -138,3 +164,17 @@ enum CalendarSyncMode: String, CaseIterable { } } } + +enum WeekExportStyle: String, CaseIterable { + case defaultStyle + case schoolTimetable + case vertical + + var localizedKey: String { + switch self { + case .defaultStyle: return "share_export_style_default" + case .schoolTimetable: return "share_export_style_school" + case .vertical: return "share_export_style_vertical" + } + } +} diff --git a/MealMood/Models/Tag.swift b/MealMood/Models/Tag.swift index ca9d695..0012b1c 100644 --- a/MealMood/Models/Tag.swift +++ b/MealMood/Models/Tag.swift @@ -41,8 +41,8 @@ final class Tag { func localizedName(language: AppLanguage) -> String { switch language.resolved() { case .spanish: return name - case .english: return nameEN.isEmpty ? name : nameEN - case .system: return name + case .english, .french, .german, .italian, .portugueseBrazil: return nameEN.isEmpty ? name : nameEN + case .system: return nameEN.isEmpty ? name : nameEN } } @@ -57,7 +57,7 @@ final class Tag { switch resolvedLanguage { case .spanish: parts.append("Max \(max)/semana") - case .english: + case .english, .french, .german, .italian, .portugueseBrazil: parts.append("Max \(max)/week") case .system: parts.append("Max \(max)/week") diff --git a/MealMood/Resources/de.lproj/Localizable.strings b/MealMood/Resources/de.lproj/Localizable.strings new file mode 100644 index 0000000..0b6ab12 --- /dev/null +++ b/MealMood/Resources/de.lproj/Localizable.strings @@ -0,0 +1,286 @@ +/* Onboarding */ +"onboarding_welcome_title" = "Organisiere deine Mahlzeiten in 1 Minute"; +"onboarding_welcome_subtitle" = "Beantworte 4 kurze Schritte und plane noch heute los."; +"onboarding_welcome_step_1" = "Wähle, ob du Mittagessen, Abendessen oder beides planen möchtest"; +"onboarding_welcome_step_2" = "Entscheide, ob Wochenenden eingeschlossen werden sollen"; +"onboarding_welcome_step_3" = "Verbinde optional deinen Kalender"; +"onboarding_welcome_step_4" = "Füge deine typischen Gerichte hinzu, um schnell zu starten"; +"onboarding_nav_intro" = "Schnelleinrichtung"; +"onboarding_nav_setup" = "Einrichtung"; +"onboarding_nav_step_short" = "Schritt %d"; +"onboarding_nav_step_counter" = "Schritt %d/%d"; +"onboarding_start" = "Loslegen"; +"onboarding_continue" = "Weiter"; +"onboarding_skip" = "Überspringen"; +"onboarding_finish" = "Fertig"; + +/* Meal Windows */ +"meal_windows_title" = "Schritt 1: Mittagessen, Abendessen oder beides?"; +"meal_windows_dinner_only" = "Nur Abendessen"; +"meal_windows_lunch_only" = "Nur Mittagessen"; +"meal_windows_both" = "Mittag- und Abendessen"; + +/* Weekends */ +"weekends_title" = "Schritt 2: Auch Wochenenden planen?"; +"weekends_toggle" = "Auch Samstage und Sonntage planen"; +"weekends_note" = "Das kannst du später in den Einstellungen ändern"; + +/* Calendar */ +"calendar_title" = "Schritt 3: Kalender verbinden?"; +"calendar_sync" = "Synchronisieren"; +"calendar_select" = "Kalender"; +"calendar_lunch_time" = "Mittagszeit"; +"calendar_dinner_time" = "Abendessenszeit"; +"calendar_optional" = "Optional, du kannst es später aktivieren"; +"calendar_permission_title" = "Kalenderzugriff"; +"calendar_permission_message" = "MealMood benötigt Zugriff auf deinen Kalender, um Ereignisse zu synchronisieren."; +"calendar_permission_settings" = "Zu Einstellungen"; + +/* First Dishes */ +"first_dishes_title" = "Schritt 4: Füge deine Lieblingsgerichte hinzu"; +"first_dishes_subtitle" = "Füge mindestens 2 Gerichte hinzu, um zu starten"; +"first_dishes_name_placeholder" = "Gerichtsname"; +"first_dishes_add_tag" = "Tag hinzufügen"; +"first_dishes_add_dish" = "Gericht hinzufügen"; +"first_dishes_added" = "Hinzugefügte Gerichte:"; + +/* Home */ +"home_title" = "MealMood"; +"home_previous" = "Zurück"; +"home_next" = "Weiter"; +"home_complete" = "Abschließen"; +"home_undo_last_action" = "Letzte Aktion rückgängig machen"; +"home_reset" = "Zurücksetzen"; +"home_copy_previous_week" = "Vorherige Woche kopieren"; +"home_my_dishes" = "Meine Gerichte"; +"home_add_first_dish" = "Füge dein erstes Gericht hinzu, um zu starten"; +"home_add_dish" = "Gericht hinzufügen"; +"home_my_dishes_search" = "Gerichte suchen"; +"home_my_dishes_search_empty" = "Keine Gerichte gefunden"; +"home_my_dishes_hide_used" = "Diese Woche verwendete ausblenden"; +"home_pick_dish_title" = "Gericht auswählen"; +"home_pick_dish_search" = "Gerichte suchen"; +"home_pick_dish_empty" = "Keine Gerichte passen zu deiner Suche"; +"home_pick_dish_add_new" = "Neues Gericht hinzufügen"; +"home_pick_dish_no_dishes" = "Noch keine Gerichte. Füge eines hinzu, um zu starten."; +"home_select_week" = "Woche auswählen"; +"home_week_picker_title" = "Woche auswählen"; +"home_week_picker_date" = "Datum"; +"home_week_picker_go" = "Los"; + +/* Reset Alert */ +"reset_title" = "Die ganze Woche löschen?"; +"reset_message" = "Dies kann nicht rückgängig gemacht werden"; +"reset_cancel" = "Abbrechen"; +"reset_confirm" = "Löschen"; + +/* Toasts */ +"toast_dish_assigned" = "Gericht zugewiesen ✓"; +"toast_week_complete" = "Woche abgeschlossen! 🎉"; +"toast_week_reset" = "Woche gelöscht 🔄"; +"toast_dish_saved" = "Gericht gespeichert ✓"; +"toast_event_deleted" = "Kalendereintrag gelöscht"; +"toast_cannot_complete" = "Nicht alle Plätze konnten gefüllt werden"; +"toast_dish_deleted" = "Gericht gelöscht"; +"toast_no_free_slots" = "Keine freien Plätze verfügbar"; +"toast_calendar_synced" = "Kalender synchronisiert ✓"; +"toast_copied_previous_week" = "Aus vorheriger Woche kopiert ✓"; +"toast_previous_week_empty" = "Kein Plan für die vorherige Woche gefunden"; +"toast_undo_applied" = "Letzte Aktion rückgängig gemacht"; +"copy_previous_confirm_title" = "Aktuelle Woche ersetzen?"; +"copy_previous_confirm_message" = "Es sind bereits Gerichte zugewiesen. Das Kopieren der vorherigen Woche überschreibt die aktuellen Zuweisungen."; +"copy_previous_confirm_confirm" = "Woche ersetzen"; + +/* Dish Form */ +"dish_new_title" = "Neues Gericht"; +"dish_edit_title" = "Gericht bearbeiten"; +"dish_name_label" = "Gerichtsname *"; +"dish_name_placeholder" = "Z. B.: Brathähnchen"; +"dish_description_label" = "Beschreibung (optional)"; +"dish_description_placeholder" = "Z. B.: Saftig und mit Kräutern im Ofen gebacken"; +"dish_tags_label" = "Tags (optional)"; +"dish_add_tag" = "Tag hinzufügen"; +"dish_delete" = "Gericht löschen"; +"dish_delete_title" = "Dieses Gericht löschen?"; +"dish_delete_message" = "Diese Aktion kann nicht rückgängig gemacht werden"; +"dish_cancel" = "Abbrechen"; +"dish_delete_blocked_title" = "Löschen nicht möglich"; +"dish_delete_blocked_message" = "Du kannst kein Gericht löschen, das der aktuellen Woche zugewiesen ist."; +"dish_delete_blocked_ok" = "OK"; +"dish_list_empty" = "Du hast noch keine Gerichte"; + +/* Tag Selector */ +"tag_selector_title" = "Tags auswählen"; +"tag_selector_done" = "Fertig"; + +/* Tags */ +"tags_title" = "Tags"; +"tags_max_per_week" = "Max pro Woche"; +"tags_no_consecutive" = "Nicht hintereinander"; +"tags_no_consecutive_desc" = "Verhindert aufeinanderfolgende Tage"; +"tags_no_duplicate" = "Keine Wiederholung am selben Tag"; +"tags_no_duplicate_desc" = "Kann nicht mittags und abends am selben Tag erscheinen"; +"tags_restriction" = "Zeitbeschränkung"; +"tags_no_restriction" = "Keine Beschränkung"; +"tags_lunch_only" = "Nur Mittagessen"; +"tags_dinner_only" = "Nur Abendessen"; +"tags_no_limit" = "Keine Begrenzung"; + +/* Settings */ +"settings_title" = "Einstellungen"; +"settings_planning" = "Planung"; +"settings_meal_windows" = "Mahlzeitenfenster"; +"settings_include_weekends" = "Wochenenden einschließen"; +"settings_export_style" = "Wöchentlicher Exportstil"; +"settings_calendar" = "Kalender"; +"settings_icloud_sync" = "Daten mit iCloud synchronisieren"; +"settings_sync" = "Synchronisieren"; +"settings_sync_mode" = "Synchronisierungsmodus"; +"settings_sync_mode_week_complete" = "Automatisch (wenn die Woche vollständig ist)"; +"settings_sync_mode_manual" = "Manuell (Synchronisieren-Taste)"; +"settings_sync_now" = "Jetzt synchronisieren"; +"settings_lunch_time" = "Mittagszeit"; +"settings_dinner_time" = "Abendessenszeit"; +"settings_event_duration" = "Ereignisdauer"; +"settings_event_prefix" = "Titelpräfix"; +"settings_reminder" = "Erinnerung"; +"settings_reminder_none" = "Keine Erinnerung"; +"settings_tags" = "Tags"; +"settings_manage_tags" = "Tags verwalten"; +"settings_language" = "Sprache"; +"settings_premium" = "Premium"; +"settings_premium_status" = "Status"; +"settings_remove_ads" = "Werbung entfernen"; +"settings_about" = "Info"; +"settings_website" = "Website"; +"settings_support" = "Support-E-Mail"; +"settings_rate_app" = "MealMood bewerten"; +"settings_app_version" = "Version"; +"settings_app_build" = "Build"; +"settings_danger_zone" = "Gefahrenbereich"; +"settings_reset_all_data" = "Alle Daten zurücksetzen"; +"settings_reset_all_data_title" = "Alle App-Daten zurücksetzen?"; +"settings_reset_all_data_message" = "Dadurch werden Gerichte, Tags, Pläne und Einstellungen gelöscht und das Onboarding neu gestartet."; +"settings_reset_all_data_confirm" = "Alles zurücksetzen"; + +/* Premium */ +"premium_title" = "MealMood Premium"; +"premium_subtitle" = "Nutze alle Funktionen"; +"premium_no_ads" = "Keine Werbung"; +"premium_unlimited_dishes" = "Unbegrenzte Gerichte"; +"premium_custom_tags" = "Benutzerdefinierte Tags"; +"premium_advanced_rules" = "Erweiterte Tag-Regeln"; +"premium_future_weeks" = "Unbegrenzt zukünftige Wochen planen"; +"premium_share_week" = "Wöchentlicher Bildexport"; +"premium_family_sharing" = "Unterstützt Familienfreigabe"; +"premium_plans" = "Verfügbare Pläne:"; +"premium_monthly" = "Monatlich"; +"premium_yearly" = "Jährlich"; +"premium_save" = "44 % sparen"; +"premium_month" = "Monat"; +"premium_price_note" = "2,99 EUR pro Monat"; +"premium_terms_note" = "Automatisch verlängerbares Abo. Jederzeit in deinen Apple-Kontoeinstellungen kündbar."; +"premium_active" = "Premium ist aktiv"; +"premium_subscribe" = "Abonnieren"; +"premium_restore" = "Käufe wiederherstellen"; +"premium_processing" = "Wird verarbeitet..."; +"premium_loading_products" = "Kaufoptionen werden geladen..."; +"premium_loading_products_hint" = "Wenn es zu lange dauert, tippe auf Erneut versuchen."; +"premium_loading_timeout" = "Laden hat zu lange gedauert"; +"premium_loading_timeout_hint" = "StoreKit hat nicht rechtzeitig geantwortet. Tippe auf Erneut versuchen."; +"premium_products_not_found" = "Keine Produkte verfügbar"; +"premium_products_not_found_hint" = "Prüfe die Produkt-IDs in App Store Connect / der StoreKit-Konfiguration."; +"premium_loading_failed" = "Produkte konnten nicht geladen werden"; +"premium_loading_failed_hint" = "Bitte prüfe deine StoreKit-Konfiguration und versuche es erneut."; +"premium_retry_products" = "Erneut versuchen"; +"premium_purchase_pending" = "Der Kauf wartet auf Genehmigung."; +"premium_purchase_cancelled" = "Der Kauf wurde abgebrochen."; +"premium_purchase_failed" = "Der Kauf ist fehlgeschlagen. Bitte versuche es erneut."; +"tag_selector_empty" = "Noch keine Tags verfügbar"; +"premium_limit_dishes" = "Limit des Gratisplans erreicht: 20 Gerichte"; +"premium_limit_rules" = "Erweiterte Tag-Regeln sind Premium"; +"premium_limit_future_weeks" = "Der Gratisplan erlaubt Planung bis zur nächsten Woche"; + +/* Home extras */ +"rule_override_title" = "Regelkonflikt"; +"rule_override_message" = "Dieses Gericht verletzt eine oder mehrere Tag-Regeln. Trotzdem zuweisen?"; +"rule_override_confirm" = "Trotzdem zuweisen"; +"share_week_button" = "Woche teilen"; +"share_week_title" = "MealMood Wochenplan"; +"share_week_callout_title" = "Deine Woche ist bereit zum Export"; +"share_week_callout_subtitle" = "Erstelle ein schönes Bild zum Teilen oder Drucken deines Essensplans."; +"share_export_style_default" = "Standard"; +"share_export_style_school" = "Stundenplan"; +"share_export_style_vertical" = "Vertikal"; +"share_export_style_picker_title" = "Exportstil wählen"; +"share_export_style_picker_subtitle" = "Du kannst ihn jederzeit in den Einstellungen ändern."; +"share_export_style_picker_apply" = "Diesen Stil verwenden"; +"share_export_school_title" = "Wöchentlicher Essensplan"; +"share_export_school_subtitle" = "Stundenplan"; +"share_export_vertical_title" = "Vertikaler Essensplan"; +"ads_placeholder" = "Anzeige"; +"ads_loading" = "Anzeige wird geladen..."; +"ads_unavailable" = "Anzeige nicht verfügbar"; +"history_title" = "Monatlicher Verlauf"; +"history_month_empty" = "In diesem Monat gibt es keine geplanten Wochen"; +"history_week_complete" = "Woche abgeschlossen"; +"history_week_incomplete" = "Woche in Arbeit"; + +/* Onboarding suggestions */ +"first_dishes_suggestions" = "Schnelle Vorschläge"; +"first_dishes_suggestion_add" = "Vorschlag hinzufügen"; +"first_dishes_suggestion_added" = "Hinzugefügt"; +"first_dishes_no_tags" = "Standard-Tags werden geladen, bitte versuche es gleich noch einmal"; + +/* Onboarding trial */ +"onboarding_trial_title" = "Premium freischalten"; +"onboarding_trial_subtitle" = "Schalte jetzt alle Premium-Funktionen frei."; +"onboarding_trial_cta" = "Premium freischalten"; +"onboarding_trial_skip" = "Mit Gratisplan fortfahren"; +"onboarding_trial_price_format" = "%@ / Monat"; + +/* Review funnel */ +"review_funnel_title" = "Wie gefällt dir MealMood?"; +"review_funnel_message" = "Deine Meinung hilft uns, MealMood zu verbessern."; +"review_funnel_positive" = "Gefällt mir"; +"review_funnel_negative" = "Verbesserungsbedarf"; +"review_feedback_title" = "Sag uns, was wir verbessern sollen"; +"review_feedback_message" = "Sende uns dein Feedback, damit wir die App verbessern können."; +"review_feedback_contact" = "Feedback senden"; +"onboarding_auto_assign_title" = "Sollen wir deine Woche automatisch füllen?"; +"onboarding_auto_assign_message" = "Wir können diese Woche automatisch mit deinen Gerichten und Regeln füllen."; +"onboarding_auto_assign_yes" = "Ja, automatisch zuweisen"; +"onboarding_auto_assign_no" = "Nein, ich mache es"; +"onboarding_premium_prompt_title" = "Premium freischalten?"; +"onboarding_premium_prompt_message" = "Keine Werbung, unbegrenzte Gerichte, erweiterte Regeln und Planung zukünftiger Wochen."; +"onboarding_premium_prompt_cta" = "Premium ansehen"; + +/* Notifications */ +"notification_planning_title" = "Plane deine nächste Woche"; +"notification_planning_body" = "Deine neue Woche beginnt morgen und ist noch nicht geplant."; + +/* Meal Types */ +"lunch" = "Mittagessen"; +"dinner" = "Abendessen"; + +/* Days */ +"day_mon" = "Mo"; +"day_tue" = "Di"; +"day_wed" = "Mi"; +"day_thu" = "Do"; +"day_fri" = "Fr"; +"day_sat" = "Sa"; +"day_sun" = "So"; + +/* Durations */ +"duration_30" = "30 Min."; +"duration_60" = "1 Stunde"; +"duration_90" = "1,5 Stunden"; +"duration_120" = "2 Stunden"; + +/* Reminders */ +"reminder_30" = "30 Min. vorher"; +"reminder_60" = "1 Stunde vorher"; +"reminder_120" = "2 Stunden vorher"; + +"first_dishes_limit_reached" = "Du hast 10 Gerichte erreicht. Tippe auf Fertig, wenn du bereit bist."; diff --git a/MealMood/Resources/en.lproj/Localizable.strings b/MealMood/Resources/en.lproj/Localizable.strings index 05ac925..a201b0c 100644 --- a/MealMood/Resources/en.lproj/Localizable.strings +++ b/MealMood/Resources/en.lproj/Localizable.strings @@ -131,6 +131,7 @@ "settings_planning" = "Planning"; "settings_meal_windows" = "Meal windows"; "settings_include_weekends" = "Include weekends"; +"settings_export_style" = "Weekly export style"; "settings_calendar" = "Calendar"; "settings_icloud_sync" = "Sync data with iCloud"; "settings_sync" = "Sync"; @@ -208,6 +209,15 @@ "share_week_title" = "MealMood weekly plan"; "share_week_callout_title" = "Your week is ready to export"; "share_week_callout_subtitle" = "Create a beautiful image to share or print your meal plan."; +"share_export_style_default" = "Default"; +"share_export_style_school" = "School Timetable"; +"share_export_style_vertical" = "Vertical"; +"share_export_style_picker_title" = "Choose export style"; +"share_export_style_picker_subtitle" = "You can change it anytime in Settings."; +"share_export_style_picker_apply" = "Use this style"; +"share_export_school_title" = "Weekly Meal Plan"; +"share_export_school_subtitle" = "School Timetable"; +"share_export_vertical_title" = "Vertical Meal Plan"; "ads_placeholder" = "Ad"; "ads_loading" = "Loading ad..."; "ads_unavailable" = "Ad unavailable"; diff --git a/MealMood/Resources/es.lproj/Localizable.strings b/MealMood/Resources/es.lproj/Localizable.strings index 33cb1ad..3df7935 100644 --- a/MealMood/Resources/es.lproj/Localizable.strings +++ b/MealMood/Resources/es.lproj/Localizable.strings @@ -131,6 +131,7 @@ "settings_planning" = "Planificación"; "settings_meal_windows" = "Ventanas de comida"; "settings_include_weekends" = "Incluir fines de semana"; +"settings_export_style" = "Estilo de exportación semanal"; "settings_calendar" = "Calendario"; "settings_icloud_sync" = "Sincronizar datos con iCloud"; "settings_sync" = "Sincronizar"; @@ -208,6 +209,15 @@ "share_week_title" = "Plan semanal MealMood"; "share_week_callout_title" = "Tu semana está lista para exportar"; "share_week_callout_subtitle" = "Genera una imagen bonita para compartir o imprimir tu planificación."; +"share_export_style_default" = "Default"; +"share_export_style_school" = "School Timetable"; +"share_export_style_vertical" = "Vertical"; +"share_export_style_picker_title" = "Elige estilo de exportación"; +"share_export_style_picker_subtitle" = "Puedes cambiarlo cuando quieras en Ajustes."; +"share_export_style_picker_apply" = "Usar este estilo"; +"share_export_school_title" = "Weekly Meal Plan"; +"share_export_school_subtitle" = "School Timetable"; +"share_export_vertical_title" = "Meal Plan Vertical"; "ads_placeholder" = "Publicidad"; "ads_loading" = "Cargando anuncio..."; "ads_unavailable" = "Anuncio no disponible"; diff --git a/MealMood/Resources/fr.lproj/Localizable.strings b/MealMood/Resources/fr.lproj/Localizable.strings new file mode 100644 index 0000000..146c663 --- /dev/null +++ b/MealMood/Resources/fr.lproj/Localizable.strings @@ -0,0 +1,286 @@ +/* Onboarding */ +"onboarding_welcome_title" = "Organisez vos repas en 1 minute"; +"onboarding_welcome_subtitle" = "Répondez à 4 étapes rapides et commencez à planifier dès aujourd'hui."; +"onboarding_welcome_step_1" = "Choisissez si vous voulez planifier les déjeuners, les dîners ou les deux"; +"onboarding_welcome_step_2" = "Décidez si vous voulez inclure les week-ends"; +"onboarding_welcome_step_3" = "Connectez éventuellement votre calendrier"; +"onboarding_welcome_step_4" = "Ajoutez vos plats habituels pour démarrer vite"; +"onboarding_nav_intro" = "Configuration rapide"; +"onboarding_nav_setup" = "Configuration"; +"onboarding_nav_step_short" = "Étape %d"; +"onboarding_nav_step_counter" = "Étape %d/%d"; +"onboarding_start" = "Commencer"; +"onboarding_continue" = "Continuer"; +"onboarding_skip" = "Ignorer"; +"onboarding_finish" = "Terminer"; + +/* Meal Windows */ +"meal_windows_title" = "Étape 1 : Déjeuners, dîners ou les deux ?"; +"meal_windows_dinner_only" = "Dîners seulement"; +"meal_windows_lunch_only" = "Déjeuners seulement"; +"meal_windows_both" = "Déjeuners et dîners"; + +/* Weekends */ +"weekends_title" = "Étape 2 : Planifier aussi les week-ends ?"; +"weekends_toggle" = "Planifier aussi les samedis et dimanches"; +"weekends_note" = "Vous pourrez le modifier plus tard dans les réglages"; + +/* Calendar */ +"calendar_title" = "Étape 3 : Connecter votre calendrier ?"; +"calendar_sync" = "Synchroniser"; +"calendar_select" = "Calendrier"; +"calendar_lunch_time" = "Heure du déjeuner"; +"calendar_dinner_time" = "Heure du dîner"; +"calendar_optional" = "Facultatif, vous pourrez l'activer plus tard"; +"calendar_permission_title" = "Accès au calendrier"; +"calendar_permission_message" = "MealMood a besoin d'accéder à votre calendrier pour synchroniser les événements."; +"calendar_permission_settings" = "Aller aux réglages"; + +/* First Dishes */ +"first_dishes_title" = "Étape 4 : Ajoutez vos plats favoris"; +"first_dishes_subtitle" = "Ajoutez au moins 2 plats pour commencer"; +"first_dishes_name_placeholder" = "Nom du plat"; +"first_dishes_add_tag" = "Ajouter un tag"; +"first_dishes_add_dish" = "Ajouter un plat"; +"first_dishes_added" = "Plats ajoutés :"; + +/* Home */ +"home_title" = "MealMood"; +"home_previous" = "Précédent"; +"home_next" = "Suivant"; +"home_complete" = "Terminer"; +"home_undo_last_action" = "Annuler la dernière action"; +"home_reset" = "Réinitialiser"; +"home_copy_previous_week" = "Copier la semaine précédente"; +"home_my_dishes" = "Mes plats"; +"home_add_first_dish" = "Ajoutez votre premier plat pour commencer"; +"home_add_dish" = "Ajouter un plat"; +"home_my_dishes_search" = "Rechercher des plats"; +"home_my_dishes_search_empty" = "Aucun plat trouvé"; +"home_my_dishes_hide_used" = "Masquer ceux utilisés cette semaine"; +"home_pick_dish_title" = "Choisir un plat"; +"home_pick_dish_search" = "Rechercher des plats"; +"home_pick_dish_empty" = "Aucun plat ne correspond à votre recherche"; +"home_pick_dish_add_new" = "Ajouter un nouveau plat"; +"home_pick_dish_no_dishes" = "Aucun plat pour le moment. Ajoutez-en un pour commencer."; +"home_select_week" = "Choisir une semaine"; +"home_week_picker_title" = "Choisir une semaine"; +"home_week_picker_date" = "Date"; +"home_week_picker_go" = "Aller"; + +/* Reset Alert */ +"reset_title" = "Effacer toute la semaine ?"; +"reset_message" = "Cette action est irréversible"; +"reset_cancel" = "Annuler"; +"reset_confirm" = "Effacer"; + +/* Toasts */ +"toast_dish_assigned" = "Plat assigné ✓"; +"toast_week_complete" = "Semaine terminée ! 🎉"; +"toast_week_reset" = "Semaine effacée 🔄"; +"toast_dish_saved" = "Plat enregistré ✓"; +"toast_event_deleted" = "Événement du calendrier supprimé"; +"toast_cannot_complete" = "Impossible de remplir tous les créneaux"; +"toast_dish_deleted" = "Plat supprimé"; +"toast_no_free_slots" = "Aucun créneau libre disponible"; +"toast_calendar_synced" = "Calendrier synchronisé ✓"; +"toast_copied_previous_week" = "Copié depuis la semaine précédente ✓"; +"toast_previous_week_empty" = "Aucun plan trouvé pour la semaine précédente"; +"toast_undo_applied" = "Dernière action annulée"; +"copy_previous_confirm_title" = "Remplacer la semaine actuelle ?"; +"copy_previous_confirm_message" = "Vous avez déjà des plats assignés. Copier la semaine précédente remplacera les assignations actuelles."; +"copy_previous_confirm_confirm" = "Remplacer la semaine"; + +/* Dish Form */ +"dish_new_title" = "Nouveau plat"; +"dish_edit_title" = "Modifier le plat"; +"dish_name_label" = "Nom du plat *"; +"dish_name_placeholder" = "Ex. : Poulet rôti"; +"dish_description_label" = "Description (facultative)"; +"dish_description_placeholder" = "Ex. : Savoureux et rôti au four avec des herbes"; +"dish_tags_label" = "Tags (facultatif)"; +"dish_add_tag" = "Ajouter un tag"; +"dish_delete" = "Supprimer le plat"; +"dish_delete_title" = "Supprimer ce plat ?"; +"dish_delete_message" = "Cette action est irréversible"; +"dish_cancel" = "Annuler"; +"dish_delete_blocked_title" = "Suppression impossible"; +"dish_delete_blocked_message" = "Vous ne pouvez pas supprimer un plat assigné à la semaine en cours."; +"dish_delete_blocked_ok" = "OK"; +"dish_list_empty" = "Vous n'avez pas encore de plats"; + +/* Tag Selector */ +"tag_selector_title" = "Sélectionner des tags"; +"tag_selector_done" = "Terminé"; + +/* Tags */ +"tags_title" = "Tags"; +"tags_max_per_week" = "Max par semaine"; +"tags_no_consecutive" = "Pas consécutif"; +"tags_no_consecutive_desc" = "Empêche l'apparition sur des jours consécutifs"; +"tags_no_duplicate" = "Pas de répétition le même jour"; +"tags_no_duplicate_desc" = "Impossible au déjeuner et au dîner le même jour"; +"tags_restriction" = "Restriction horaire"; +"tags_no_restriction" = "Aucune restriction"; +"tags_lunch_only" = "Déjeuner seulement"; +"tags_dinner_only" = "Dîner seulement"; +"tags_no_limit" = "Aucune limite"; + +/* Settings */ +"settings_title" = "Réglages"; +"settings_planning" = "Planification"; +"settings_meal_windows" = "Créneaux de repas"; +"settings_include_weekends" = "Inclure les week-ends"; +"settings_export_style" = "Style d'export hebdomadaire"; +"settings_calendar" = "Calendrier"; +"settings_icloud_sync" = "Synchroniser les données avec iCloud"; +"settings_sync" = "Synchroniser"; +"settings_sync_mode" = "Mode de synchronisation"; +"settings_sync_mode_week_complete" = "Auto (quand la semaine est complète)"; +"settings_sync_mode_manual" = "Manuel (bouton de synchro)"; +"settings_sync_now" = "Synchroniser maintenant"; +"settings_lunch_time" = "Heure du déjeuner"; +"settings_dinner_time" = "Heure du dîner"; +"settings_event_duration" = "Durée de l'événement"; +"settings_event_prefix" = "Préfixe du titre"; +"settings_reminder" = "Rappel"; +"settings_reminder_none" = "Aucun rappel"; +"settings_tags" = "Tags"; +"settings_manage_tags" = "Gérer les tags"; +"settings_language" = "Langue"; +"settings_premium" = "Premium"; +"settings_premium_status" = "Statut"; +"settings_remove_ads" = "Supprimer les publicités"; +"settings_about" = "À propos"; +"settings_website" = "Site web"; +"settings_support" = "E-mail d'assistance"; +"settings_rate_app" = "Noter MealMood"; +"settings_app_version" = "Version"; +"settings_app_build" = "Build"; +"settings_danger_zone" = "Zone dangereuse"; +"settings_reset_all_data" = "Réinitialiser toutes les données"; +"settings_reset_all_data_title" = "Réinitialiser toutes les données de l'app ?"; +"settings_reset_all_data_message" = "Cela supprimera les plats, tags, plans et réglages, puis relancera l'onboarding."; +"settings_reset_all_data_confirm" = "Tout réinitialiser"; + +/* Premium */ +"premium_title" = "MealMood Premium"; +"premium_subtitle" = "Profitez de toutes les fonctionnalités"; +"premium_no_ads" = "Sans publicités"; +"premium_unlimited_dishes" = "Plats illimités"; +"premium_custom_tags" = "Tags personnalisés"; +"premium_advanced_rules" = "Règles de tags avancées"; +"premium_future_weeks" = "Planification illimitée des semaines à venir"; +"premium_share_week" = "Export hebdomadaire en image"; +"premium_family_sharing" = "Compatible avec le partage familial"; +"premium_plans" = "Forfaits disponibles :"; +"premium_monthly" = "Mensuel"; +"premium_yearly" = "Annuel"; +"premium_save" = "Économisez 44 %"; +"premium_month" = "mois"; +"premium_price_note" = "2,99 EUR par mois"; +"premium_terms_note" = "Abonnement à renouvellement automatique. Annulez à tout moment dans les réglages de votre compte Apple."; +"premium_active" = "Premium est actif"; +"premium_subscribe" = "S'abonner"; +"premium_restore" = "Restaurer les achats"; +"premium_processing" = "Traitement..."; +"premium_loading_products" = "Chargement des options d'achat..."; +"premium_loading_products_hint" = "Si cela prend trop de temps, touchez Réessayer."; +"premium_loading_timeout" = "Temps de chargement écoulé"; +"premium_loading_timeout_hint" = "StoreKit n'a pas répondu à temps. Touchez Réessayer."; +"premium_products_not_found" = "Aucun produit disponible"; +"premium_products_not_found_hint" = "Vérifiez les identifiants produit dans App Store Connect / la configuration StoreKit."; +"premium_loading_failed" = "Impossible de charger les produits"; +"premium_loading_failed_hint" = "Vérifiez votre configuration StoreKit et réessayez."; +"premium_retry_products" = "Réessayer"; +"premium_purchase_pending" = "L'achat est en attente d'approbation."; +"premium_purchase_cancelled" = "L'achat a été annulé."; +"premium_purchase_failed" = "L'achat a échoué. Veuillez réessayer."; +"tag_selector_empty" = "Aucun tag disponible pour le moment"; +"premium_limit_dishes" = "Limite du plan gratuit atteinte : 20 plats"; +"premium_limit_rules" = "Les règles de tags avancées sont réservées au Premium"; +"premium_limit_future_weeks" = "Le plan gratuit permet de planifier jusqu'à la semaine prochaine"; + +/* Home extras */ +"rule_override_title" = "Conflit de règles"; +"rule_override_message" = "Ce plat enfreint une ou plusieurs règles de tags. L'assigner quand même ?"; +"rule_override_confirm" = "Assigner quand même"; +"share_week_button" = "Partager la semaine"; +"share_week_title" = "Planning hebdomadaire MealMood"; +"share_week_callout_title" = "Votre semaine est prête à être exportée"; +"share_week_callout_subtitle" = "Créez une belle image à partager ou à imprimer avec votre planning de repas."; +"share_export_style_default" = "Par défaut"; +"share_export_style_school" = "Emploi du temps scolaire"; +"share_export_style_vertical" = "Vertical"; +"share_export_style_picker_title" = "Choisir le style d'export"; +"share_export_style_picker_subtitle" = "Vous pouvez le modifier à tout moment dans les réglages."; +"share_export_style_picker_apply" = "Utiliser ce style"; +"share_export_school_title" = "Planning hebdomadaire des repas"; +"share_export_school_subtitle" = "Emploi du temps scolaire"; +"share_export_vertical_title" = "Planning vertical des repas"; +"ads_placeholder" = "Publicité"; +"ads_loading" = "Chargement de la publicité..."; +"ads_unavailable" = "Publicité indisponible"; +"history_title" = "Historique mensuel"; +"history_month_empty" = "Aucune semaine planifiée ce mois-ci"; +"history_week_complete" = "Semaine terminée"; +"history_week_incomplete" = "Semaine en cours"; + +/* Onboarding suggestions */ +"first_dishes_suggestions" = "Suggestions rapides"; +"first_dishes_suggestion_add" = "Ajouter la suggestion"; +"first_dishes_suggestion_added" = "Ajouté"; +"first_dishes_no_tags" = "Les tags par défaut sont en cours de chargement, veuillez réessayer dans un instant"; + +/* Onboarding trial */ +"onboarding_trial_title" = "Débloquer Premium"; +"onboarding_trial_subtitle" = "Débloquez maintenant toutes les fonctionnalités premium."; +"onboarding_trial_cta" = "Débloquer Premium"; +"onboarding_trial_skip" = "Continuer avec le plan gratuit"; +"onboarding_trial_price_format" = "%@ / mois"; + +/* Review funnel */ +"review_funnel_title" = "Comment se passe votre expérience avec MealMood ?"; +"review_funnel_message" = "Votre avis nous aide à améliorer MealMood."; +"review_funnel_positive" = "J'aime"; +"review_funnel_negative" = "À améliorer"; +"review_feedback_title" = "Dites-nous quoi améliorer"; +"review_feedback_message" = "Envoyez-nous votre retour et nous l'utiliserons pour améliorer l'app."; +"review_feedback_contact" = "Envoyer un retour"; +"onboarding_auto_assign_title" = "Voulez-vous que nous remplissions automatiquement votre semaine ?"; +"onboarding_auto_assign_message" = "Nous pouvons remplir cette semaine automatiquement en utilisant vos plats et vos règles."; +"onboarding_auto_assign_yes" = "Oui, assigner automatiquement"; +"onboarding_auto_assign_no" = "Non, je le ferai"; +"onboarding_premium_prompt_title" = "Voulez-vous débloquer Premium ?"; +"onboarding_premium_prompt_message" = "Profitez de l'absence de publicités, de plats illimités, de règles avancées et de la planification des semaines futures."; +"onboarding_premium_prompt_cta" = "Voir Premium"; + +/* Notifications */ +"notification_planning_title" = "Planifiez votre semaine suivante"; +"notification_planning_body" = "Votre nouvelle semaine commence demain et elle n'est pas encore planifiée."; + +/* Meal Types */ +"lunch" = "Déjeuner"; +"dinner" = "Dîner"; + +/* Days */ +"day_mon" = "Lun"; +"day_tue" = "Mar"; +"day_wed" = "Mer"; +"day_thu" = "Jeu"; +"day_fri" = "Ven"; +"day_sat" = "Sam"; +"day_sun" = "Dim"; + +/* Durations */ +"duration_30" = "30 min"; +"duration_60" = "1 heure"; +"duration_90" = "1,5 heure"; +"duration_120" = "2 heures"; + +/* Reminders */ +"reminder_30" = "30 min avant"; +"reminder_60" = "1 heure avant"; +"reminder_120" = "2 heures avant"; + +"first_dishes_limit_reached" = "Vous avez atteint 10 plats. Touchez Terminer quand vous êtes prêt."; diff --git a/MealMood/Resources/it.lproj/Localizable.strings b/MealMood/Resources/it.lproj/Localizable.strings new file mode 100644 index 0000000..82e4f69 --- /dev/null +++ b/MealMood/Resources/it.lproj/Localizable.strings @@ -0,0 +1,286 @@ +/* Onboarding */ +"onboarding_welcome_title" = "Organizza i tuoi pasti in 1 minuto"; +"onboarding_welcome_subtitle" = "Rispondi a 4 passaggi rapidi e inizia a pianificare oggi stesso."; +"onboarding_welcome_step_1" = "Scegli se vuoi pianificare pranzi, cene o entrambi"; +"onboarding_welcome_step_2" = "Decidi se includere i weekend"; +"onboarding_welcome_step_3" = "Collega facoltativamente il tuo calendario"; +"onboarding_welcome_step_4" = "Aggiungi i tuoi piatti abituali per iniziare in fretta"; +"onboarding_nav_intro" = "Configurazione rapida"; +"onboarding_nav_setup" = "Configurazione"; +"onboarding_nav_step_short" = "Passo %d"; +"onboarding_nav_step_counter" = "Passo %d/%d"; +"onboarding_start" = "Inizia"; +"onboarding_continue" = "Continua"; +"onboarding_skip" = "Salta"; +"onboarding_finish" = "Fine"; + +/* Meal Windows */ +"meal_windows_title" = "Passo 1: Pranzi, cene o entrambi?"; +"meal_windows_dinner_only" = "Solo cene"; +"meal_windows_lunch_only" = "Solo pranzi"; +"meal_windows_both" = "Pranzi e cene"; + +/* Weekends */ +"weekends_title" = "Passo 2: Pianificare anche i weekend?"; +"weekends_toggle" = "Pianifica anche sabati e domeniche"; +"weekends_note" = "Potrai cambiarlo più tardi nelle impostazioni"; + +/* Calendar */ +"calendar_title" = "Passo 3: Collegare il calendario?"; +"calendar_sync" = "Sincronizza"; +"calendar_select" = "Calendario"; +"calendar_lunch_time" = "Ora di pranzo"; +"calendar_dinner_time" = "Ora di cena"; +"calendar_optional" = "Facoltativo, potrai attivarlo più tardi"; +"calendar_permission_title" = "Accesso al calendario"; +"calendar_permission_message" = "MealMood ha bisogno di accedere al tuo calendario per sincronizzare gli eventi."; +"calendar_permission_settings" = "Vai alle Impostazioni"; + +/* First Dishes */ +"first_dishes_title" = "Passo 4: Aggiungi i tuoi piatti preferiti"; +"first_dishes_subtitle" = "Aggiungi almeno 2 piatti per iniziare"; +"first_dishes_name_placeholder" = "Nome del piatto"; +"first_dishes_add_tag" = "Aggiungi tag"; +"first_dishes_add_dish" = "Aggiungi piatto"; +"first_dishes_added" = "Piatti aggiunti:"; + +/* Home */ +"home_title" = "MealMood"; +"home_previous" = "Precedente"; +"home_next" = "Successivo"; +"home_complete" = "Completa"; +"home_undo_last_action" = "Annulla l'ultima azione"; +"home_reset" = "Reimposta"; +"home_copy_previous_week" = "Copia la settimana precedente"; +"home_my_dishes" = "I miei piatti"; +"home_add_first_dish" = "Aggiungi il tuo primo piatto per iniziare"; +"home_add_dish" = "Aggiungi piatto"; +"home_my_dishes_search" = "Cerca piatti"; +"home_my_dishes_search_empty" = "Nessun piatto trovato"; +"home_my_dishes_hide_used" = "Nascondi quelli usati questa settimana"; +"home_pick_dish_title" = "Scegli un piatto"; +"home_pick_dish_search" = "Cerca piatti"; +"home_pick_dish_empty" = "Nessun piatto corrisponde alla ricerca"; +"home_pick_dish_add_new" = "Aggiungi nuovo piatto"; +"home_pick_dish_no_dishes" = "Ancora nessun piatto. Aggiungine uno per iniziare."; +"home_select_week" = "Seleziona settimana"; +"home_week_picker_title" = "Seleziona settimana"; +"home_week_picker_date" = "Data"; +"home_week_picker_go" = "Vai"; + +/* Reset Alert */ +"reset_title" = "Cancellare tutta la settimana?"; +"reset_message" = "Questa azione non può essere annullata"; +"reset_cancel" = "Annulla"; +"reset_confirm" = "Cancella"; + +/* Toasts */ +"toast_dish_assigned" = "Piatto assegnato ✓"; +"toast_week_complete" = "Settimana completata! 🎉"; +"toast_week_reset" = "Settimana cancellata 🔄"; +"toast_dish_saved" = "Piatto salvato ✓"; +"toast_event_deleted" = "Evento del calendario eliminato"; +"toast_cannot_complete" = "Impossibile completare tutti gli slot"; +"toast_dish_deleted" = "Piatto eliminato"; +"toast_no_free_slots" = "Nessuno slot libero disponibile"; +"toast_calendar_synced" = "Calendario sincronizzato ✓"; +"toast_copied_previous_week" = "Copiato dalla settimana precedente ✓"; +"toast_previous_week_empty" = "Nessun piano trovato per la settimana precedente"; +"toast_undo_applied" = "Ultima azione annullata"; +"copy_previous_confirm_title" = "Sostituire la settimana attuale?"; +"copy_previous_confirm_message" = "Hai già piatti assegnati. Copiare la settimana precedente sovrascriverà le assegnazioni attuali."; +"copy_previous_confirm_confirm" = "Sostituisci settimana"; + +/* Dish Form */ +"dish_new_title" = "Nuovo piatto"; +"dish_edit_title" = "Modifica piatto"; +"dish_name_label" = "Nome del piatto *"; +"dish_name_placeholder" = "Es.: Pollo arrosto"; +"dish_description_label" = "Descrizione (facoltativa)"; +"dish_description_placeholder" = "Es.: Succoso e cotto al forno con erbe"; +"dish_tags_label" = "Tag (facoltativi)"; +"dish_add_tag" = "Aggiungi tag"; +"dish_delete" = "Elimina piatto"; +"dish_delete_title" = "Eliminare questo piatto?"; +"dish_delete_message" = "Questa azione non può essere annullata"; +"dish_cancel" = "Annulla"; +"dish_delete_blocked_title" = "Impossibile eliminare"; +"dish_delete_blocked_message" = "Non puoi eliminare un piatto assegnato alla settimana corrente."; +"dish_delete_blocked_ok" = "OK"; +"dish_list_empty" = "Non hai ancora piatti"; + +/* Tag Selector */ +"tag_selector_title" = "Seleziona tag"; +"tag_selector_done" = "Fatto"; + +/* Tags */ +"tags_title" = "Tag"; +"tags_max_per_week" = "Max a settimana"; +"tags_no_consecutive" = "Non consecutivo"; +"tags_no_consecutive_desc" = "Evita che compaia in giorni consecutivi"; +"tags_no_duplicate" = "Nessuna ripetizione nello stesso giorno"; +"tags_no_duplicate_desc" = "Non può essere sia a pranzo che a cena nello stesso giorno"; +"tags_restriction" = "Restrizione oraria"; +"tags_no_restriction" = "Nessuna restrizione"; +"tags_lunch_only" = "Solo pranzo"; +"tags_dinner_only" = "Solo cena"; +"tags_no_limit" = "Nessun limite"; + +/* Settings */ +"settings_title" = "Impostazioni"; +"settings_planning" = "Pianificazione"; +"settings_meal_windows" = "Finestre dei pasti"; +"settings_include_weekends" = "Includi i weekend"; +"settings_export_style" = "Stile di esportazione settimanale"; +"settings_calendar" = "Calendario"; +"settings_icloud_sync" = "Sincronizza i dati con iCloud"; +"settings_sync" = "Sincronizza"; +"settings_sync_mode" = "Modalità di sincronizzazione"; +"settings_sync_mode_week_complete" = "Auto (quando la settimana è completa)"; +"settings_sync_mode_manual" = "Manuale (pulsante sincronizza)"; +"settings_sync_now" = "Sincronizza ora"; +"settings_lunch_time" = "Ora di pranzo"; +"settings_dinner_time" = "Ora di cena"; +"settings_event_duration" = "Durata evento"; +"settings_event_prefix" = "Prefisso titolo"; +"settings_reminder" = "Promemoria"; +"settings_reminder_none" = "Nessun promemoria"; +"settings_tags" = "Tag"; +"settings_manage_tags" = "Gestisci tag"; +"settings_language" = "Lingua"; +"settings_premium" = "Premium"; +"settings_premium_status" = "Stato"; +"settings_remove_ads" = "Rimuovi annunci"; +"settings_about" = "Informazioni"; +"settings_website" = "Sito web"; +"settings_support" = "Email di supporto"; +"settings_rate_app" = "Valuta MealMood"; +"settings_app_version" = "Versione"; +"settings_app_build" = "Build"; +"settings_danger_zone" = "Zona di pericolo"; +"settings_reset_all_data" = "Reimposta tutti i dati"; +"settings_reset_all_data_title" = "Reimpostare tutti i dati dell'app?"; +"settings_reset_all_data_message" = "Questo eliminerà piatti, tag, piani e impostazioni, poi riavvierà l'onboarding."; +"settings_reset_all_data_confirm" = "Reimposta tutto"; + +/* Premium */ +"premium_title" = "MealMood Premium"; +"premium_subtitle" = "Goditi tutte le funzionalità"; +"premium_no_ads" = "Niente annunci"; +"premium_unlimited_dishes" = "Piatti illimitati"; +"premium_custom_tags" = "Tag personalizzati"; +"premium_advanced_rules" = "Regole avanzate dei tag"; +"premium_future_weeks" = "Pianifica settimane future illimitate"; +"premium_share_week" = "Esportazione settimanale in immagine"; +"premium_family_sharing" = "Supporto In famiglia"; +"premium_plans" = "Piani disponibili:"; +"premium_monthly" = "Mensile"; +"premium_yearly" = "Annuale"; +"premium_save" = "Risparmia il 44%"; +"premium_month" = "mese"; +"premium_price_note" = "2,99 EUR al mese"; +"premium_terms_note" = "Abbonamento con rinnovo automatico. Annulla quando vuoi dalle impostazioni del tuo account Apple."; +"premium_active" = "Premium è attivo"; +"premium_subscribe" = "Abbonati"; +"premium_restore" = "Ripristina acquisti"; +"premium_processing" = "Elaborazione..."; +"premium_loading_products" = "Caricamento delle opzioni di acquisto..."; +"premium_loading_products_hint" = "Se richiede troppo tempo, tocca Riprova."; +"premium_loading_timeout" = "Tempo di caricamento scaduto"; +"premium_loading_timeout_hint" = "StoreKit non ha risposto in tempo. Tocca Riprova."; +"premium_products_not_found" = "Nessun prodotto disponibile"; +"premium_products_not_found_hint" = "Controlla gli ID prodotto in App Store Connect / configurazione StoreKit."; +"premium_loading_failed" = "Impossibile caricare i prodotti"; +"premium_loading_failed_hint" = "Controlla la configurazione StoreKit e riprova."; +"premium_retry_products" = "Riprova"; +"premium_purchase_pending" = "L'acquisto è in attesa di approvazione."; +"premium_purchase_cancelled" = "L'acquisto è stato annullato."; +"premium_purchase_failed" = "L'acquisto non è riuscito. Riprova."; +"tag_selector_empty" = "Nessun tag disponibile al momento"; +"premium_limit_dishes" = "Limite del piano gratuito raggiunto: 20 piatti"; +"premium_limit_rules" = "Le regole avanzate dei tag sono Premium"; +"premium_limit_future_weeks" = "Il piano gratuito consente di pianificare fino alla prossima settimana"; + +/* Home extras */ +"rule_override_title" = "Conflitto di regole"; +"rule_override_message" = "Questo piatto viola una o più regole dei tag. Assegnarlo comunque?"; +"rule_override_confirm" = "Assegna comunque"; +"share_week_button" = "Condividi settimana"; +"share_week_title" = "Piano settimanale MealMood"; +"share_week_callout_title" = "La tua settimana è pronta per l'esportazione"; +"share_week_callout_subtitle" = "Crea una bella immagine da condividere o stampare con il tuo piano pasti."; +"share_export_style_default" = "Predefinito"; +"share_export_style_school" = "Orario scolastico"; +"share_export_style_vertical" = "Verticale"; +"share_export_style_picker_title" = "Scegli stile di esportazione"; +"share_export_style_picker_subtitle" = "Puoi cambiarlo in qualsiasi momento nelle Impostazioni."; +"share_export_style_picker_apply" = "Usa questo stile"; +"share_export_school_title" = "Piano pasti settimanale"; +"share_export_school_subtitle" = "Orario scolastico"; +"share_export_vertical_title" = "Piano pasti verticale"; +"ads_placeholder" = "Annuncio"; +"ads_loading" = "Caricamento annuncio..."; +"ads_unavailable" = "Annuncio non disponibile"; +"history_title" = "Storico mensile"; +"history_month_empty" = "Nessuna settimana pianificata in questo mese"; +"history_week_complete" = "Settimana completa"; +"history_week_incomplete" = "Settimana in corso"; + +/* Onboarding suggestions */ +"first_dishes_suggestions" = "Suggerimenti rapidi"; +"first_dishes_suggestion_add" = "Aggiungi suggerimento"; +"first_dishes_suggestion_added" = "Aggiunto"; +"first_dishes_no_tags" = "I tag predefiniti si stanno caricando, riprova tra un momento"; + +/* Onboarding trial */ +"onboarding_trial_title" = "Sblocca Premium"; +"onboarding_trial_subtitle" = "Sblocca ora tutte le funzionalità premium."; +"onboarding_trial_cta" = "Sblocca Premium"; +"onboarding_trial_skip" = "Continua con il piano gratuito"; +"onboarding_trial_price_format" = "%@ / mese"; + +/* Review funnel */ +"review_funnel_title" = "Come sta andando MealMood?"; +"review_funnel_message" = "La tua opinione ci aiuta a migliorare MealMood."; +"review_funnel_positive" = "Mi piace"; +"review_funnel_negative" = "Da migliorare"; +"review_feedback_title" = "Dicci cosa migliorare"; +"review_feedback_message" = "Inviaci il tuo feedback e lo useremo per migliorare l'app."; +"review_feedback_contact" = "Invia feedback"; +"onboarding_auto_assign_title" = "Vuoi che assegnamo automaticamente la tua settimana?"; +"onboarding_auto_assign_message" = "Possiamo riempire automaticamente questa settimana usando i tuoi piatti e le tue regole."; +"onboarding_auto_assign_yes" = "Sì, assegna automaticamente"; +"onboarding_auto_assign_no" = "No, lo faccio io"; +"onboarding_premium_prompt_title" = "Vuoi sbloccare Premium?"; +"onboarding_premium_prompt_message" = "Ottieni niente annunci, piatti illimitati, regole avanzate e pianificazione delle settimane future."; +"onboarding_premium_prompt_cta" = "Vedi Premium"; + +/* Notifications */ +"notification_planning_title" = "Pianifica la tua prossima settimana"; +"notification_planning_body" = "La tua nuova settimana inizia domani e non è ancora pianificata."; + +/* Meal Types */ +"lunch" = "Pranzo"; +"dinner" = "Cena"; + +/* Days */ +"day_mon" = "Lun"; +"day_tue" = "Mar"; +"day_wed" = "Mer"; +"day_thu" = "Gio"; +"day_fri" = "Ven"; +"day_sat" = "Sab"; +"day_sun" = "Dom"; + +/* Durations */ +"duration_30" = "30 min"; +"duration_60" = "1 ora"; +"duration_90" = "1,5 ore"; +"duration_120" = "2 ore"; + +/* Reminders */ +"reminder_30" = "30 min prima"; +"reminder_60" = "1 ora prima"; +"reminder_120" = "2 ore prima"; + +"first_dishes_limit_reached" = "Hai raggiunto 10 piatti. Tocca Fine quando sei pronto."; diff --git a/MealMood/Resources/pt-BR.lproj/Localizable.strings b/MealMood/Resources/pt-BR.lproj/Localizable.strings new file mode 100644 index 0000000..6ad6ba6 --- /dev/null +++ b/MealMood/Resources/pt-BR.lproj/Localizable.strings @@ -0,0 +1,286 @@ +/* Onboarding */ +"onboarding_welcome_title" = "Organize suas refeições em 1 minuto"; +"onboarding_welcome_subtitle" = "Responda 4 etapas rápidas e comece a planejar hoje mesmo."; +"onboarding_welcome_step_1" = "Escolha se você quer planejar almoços, jantares ou ambos"; +"onboarding_welcome_step_2" = "Decida se quer incluir fins de semana"; +"onboarding_welcome_step_3" = "Conecte seu calendário opcionalmente"; +"onboarding_welcome_step_4" = "Adicione seus pratos habituais para começar rápido"; +"onboarding_nav_intro" = "Configuração rápida"; +"onboarding_nav_setup" = "Configuração"; +"onboarding_nav_step_short" = "Etapa %d"; +"onboarding_nav_step_counter" = "Etapa %d/%d"; +"onboarding_start" = "Começar"; +"onboarding_continue" = "Continuar"; +"onboarding_skip" = "Pular"; +"onboarding_finish" = "Concluir"; + +/* Meal Windows */ +"meal_windows_title" = "Etapa 1: Almoços, jantares ou ambos?"; +"meal_windows_dinner_only" = "Só jantares"; +"meal_windows_lunch_only" = "Só almoços"; +"meal_windows_both" = "Almoços e jantares"; + +/* Weekends */ +"weekends_title" = "Etapa 2: Planejar fins de semana também?"; +"weekends_toggle" = "Planejar sábados e domingos também"; +"weekends_note" = "Você pode alterar isso depois nas configurações"; + +/* Calendar */ +"calendar_title" = "Etapa 3: Conectar seu calendário?"; +"calendar_sync" = "Sincronizar"; +"calendar_select" = "Calendário"; +"calendar_lunch_time" = "Horário do almoço"; +"calendar_dinner_time" = "Horário do jantar"; +"calendar_optional" = "Opcional, você pode ativar depois"; +"calendar_permission_title" = "Acesso ao Calendário"; +"calendar_permission_message" = "O MealMood precisa acessar seu calendário para sincronizar eventos."; +"calendar_permission_settings" = "Ir para Ajustes"; + +/* First Dishes */ +"first_dishes_title" = "Etapa 4: Adicione seus pratos favoritos"; +"first_dishes_subtitle" = "Adicione pelo menos 2 pratos para começar"; +"first_dishes_name_placeholder" = "Nome do prato"; +"first_dishes_add_tag" = "Adicionar tag"; +"first_dishes_add_dish" = "Adicionar prato"; +"first_dishes_added" = "Pratos adicionados:"; + +/* Home */ +"home_title" = "MealMood"; +"home_previous" = "Anterior"; +"home_next" = "Próximo"; +"home_complete" = "Concluir"; +"home_undo_last_action" = "Desfazer última ação"; +"home_reset" = "Redefinir"; +"home_copy_previous_week" = "Copiar semana anterior"; +"home_my_dishes" = "Meus pratos"; +"home_add_first_dish" = "Adicione seu primeiro prato para começar"; +"home_add_dish" = "Adicionar prato"; +"home_my_dishes_search" = "Buscar pratos"; +"home_my_dishes_search_empty" = "Nenhum prato encontrado"; +"home_my_dishes_hide_used" = "Ocultar usados nesta semana"; +"home_pick_dish_title" = "Escolha um prato"; +"home_pick_dish_search" = "Buscar pratos"; +"home_pick_dish_empty" = "Nenhum prato corresponde à sua busca"; +"home_pick_dish_add_new" = "Adicionar novo prato"; +"home_pick_dish_no_dishes" = "Ainda não há pratos. Adicione um para começar."; +"home_select_week" = "Selecionar semana"; +"home_week_picker_title" = "Selecionar semana"; +"home_week_picker_date" = "Data"; +"home_week_picker_go" = "Ir"; + +/* Reset Alert */ +"reset_title" = "Limpar a semana inteira?"; +"reset_message" = "Isso não pode ser desfeito"; +"reset_cancel" = "Cancelar"; +"reset_confirm" = "Limpar"; + +/* Toasts */ +"toast_dish_assigned" = "Prato atribuído ✓"; +"toast_week_complete" = "Semana concluída! 🎉"; +"toast_week_reset" = "Semana limpa 🔄"; +"toast_dish_saved" = "Prato salvo ✓"; +"toast_event_deleted" = "Evento do calendário excluído"; +"toast_cannot_complete" = "Não foi possível completar todos os slots"; +"toast_dish_deleted" = "Prato excluído"; +"toast_no_free_slots" = "Não há slots livres disponíveis"; +"toast_calendar_synced" = "Calendário sincronizado ✓"; +"toast_copied_previous_week" = "Copiado da semana anterior ✓"; +"toast_previous_week_empty" = "Nenhum plano da semana anterior encontrado"; +"toast_undo_applied" = "Última ação desfeita"; +"copy_previous_confirm_title" = "Substituir a semana atual?"; +"copy_previous_confirm_message" = "Você já tem pratos atribuídos. Copiar a semana anterior substituirá as atribuições atuais."; +"copy_previous_confirm_confirm" = "Substituir semana"; + +/* Dish Form */ +"dish_new_title" = "Novo prato"; +"dish_edit_title" = "Editar prato"; +"dish_name_label" = "Nome do prato *"; +"dish_name_placeholder" = "Ex.: Frango assado"; +"dish_description_label" = "Descrição (opcional)"; +"dish_description_placeholder" = "Ex.: Suculento e assado no forno com ervas"; +"dish_tags_label" = "Tags (opcional)"; +"dish_add_tag" = "Adicionar tag"; +"dish_delete" = "Excluir prato"; +"dish_delete_title" = "Excluir este prato?"; +"dish_delete_message" = "Essa ação não pode ser desfeita"; +"dish_cancel" = "Cancelar"; +"dish_delete_blocked_title" = "Não é possível excluir"; +"dish_delete_blocked_message" = "Você não pode excluir um prato atribuído à semana atual."; +"dish_delete_blocked_ok" = "OK"; +"dish_list_empty" = "Você ainda não tem pratos"; + +/* Tag Selector */ +"tag_selector_title" = "Selecionar tags"; +"tag_selector_done" = "Concluído"; + +/* Tags */ +"tags_title" = "Tags"; +"tags_max_per_week" = "Máx. por semana"; +"tags_no_consecutive" = "Sem consecutivos"; +"tags_no_consecutive_desc" = "Evita aparecer em dias consecutivos"; +"tags_no_duplicate" = "Sem repetição no mesmo dia"; +"tags_no_duplicate_desc" = "Não pode estar no almoço e no jantar no mesmo dia"; +"tags_restriction" = "Restrição de horário"; +"tags_no_restriction" = "Sem restrição"; +"tags_lunch_only" = "Só almoço"; +"tags_dinner_only" = "Só jantar"; +"tags_no_limit" = "Sem limite"; + +/* Settings */ +"settings_title" = "Ajustes"; +"settings_planning" = "Planejamento"; +"settings_meal_windows" = "Janelas de refeição"; +"settings_include_weekends" = "Incluir fins de semana"; +"settings_export_style" = "Estilo de exportação semanal"; +"settings_calendar" = "Calendário"; +"settings_icloud_sync" = "Sincronizar dados com o iCloud"; +"settings_sync" = "Sincronizar"; +"settings_sync_mode" = "Modo de sincronização"; +"settings_sync_mode_week_complete" = "Automático (quando a semana estiver completa)"; +"settings_sync_mode_manual" = "Manual (botão sincronizar)"; +"settings_sync_now" = "Sincronizar agora"; +"settings_lunch_time" = "Horário do almoço"; +"settings_dinner_time" = "Horário do jantar"; +"settings_event_duration" = "Duração do evento"; +"settings_event_prefix" = "Prefixo do título"; +"settings_reminder" = "Lembrete"; +"settings_reminder_none" = "Sem lembrete"; +"settings_tags" = "Tags"; +"settings_manage_tags" = "Gerenciar tags"; +"settings_language" = "Idioma"; +"settings_premium" = "Premium"; +"settings_premium_status" = "Status"; +"settings_remove_ads" = "Remover anúncios"; +"settings_about" = "Sobre"; +"settings_website" = "Site"; +"settings_support" = "E-mail de suporte"; +"settings_rate_app" = "Avaliar MealMood"; +"settings_app_version" = "Versão"; +"settings_app_build" = "Build"; +"settings_danger_zone" = "Zona de perigo"; +"settings_reset_all_data" = "Redefinir todos os dados"; +"settings_reset_all_data_title" = "Redefinir todos os dados do app?"; +"settings_reset_all_data_message" = "Isso apagará pratos, tags, planos e ajustes, depois reiniciará o onboarding."; +"settings_reset_all_data_confirm" = "Redefinir tudo"; + +/* Premium */ +"premium_title" = "MealMood Premium"; +"premium_subtitle" = "Aproveite todos os recursos"; +"premium_no_ads" = "Sem anúncios"; +"premium_unlimited_dishes" = "Pratos ilimitados"; +"premium_custom_tags" = "Tags personalizadas"; +"premium_advanced_rules" = "Regras avançadas de tags"; +"premium_future_weeks" = "Planeje semanas futuras sem limite"; +"premium_share_week" = "Exportação semanal em imagem"; +"premium_family_sharing" = "Compatível com Compartilhamento Familiar"; +"premium_plans" = "Planos disponíveis:"; +"premium_monthly" = "Mensal"; +"premium_yearly" = "Anual"; +"premium_save" = "Economize 44%"; +"premium_month" = "mês"; +"premium_price_note" = "2,99 EUR por mês"; +"premium_terms_note" = "Assinatura com renovação automática. Cancele a qualquer momento nos ajustes da sua conta Apple."; +"premium_active" = "O Premium está ativo"; +"premium_subscribe" = "Assinar"; +"premium_restore" = "Restaurar compras"; +"premium_processing" = "Processando..."; +"premium_loading_products" = "Carregando opções de compra..."; +"premium_loading_products_hint" = "Se isso demorar demais, toque em Tentar novamente."; +"premium_loading_timeout" = "Tempo de carregamento esgotado"; +"premium_loading_timeout_hint" = "O StoreKit não respondeu a tempo. Toque em Tentar novamente."; +"premium_products_not_found" = "Nenhum produto disponível"; +"premium_products_not_found_hint" = "Verifique os IDs de produto no App Store Connect / configuração do StoreKit."; +"premium_loading_failed" = "Não foi possível carregar os produtos"; +"premium_loading_failed_hint" = "Verifique sua configuração do StoreKit e tente novamente."; +"premium_retry_products" = "Tentar novamente"; +"premium_purchase_pending" = "A compra está aguardando aprovação."; +"premium_purchase_cancelled" = "A compra foi cancelada."; +"premium_purchase_failed" = "A compra falhou. Tente novamente."; +"tag_selector_empty" = "Ainda não há tags disponíveis"; +"premium_limit_dishes" = "Limite do plano grátis atingido: 20 pratos"; +"premium_limit_rules" = "Regras avançadas de tags são Premium"; +"premium_limit_future_weeks" = "O plano grátis permite planejar até a próxima semana"; + +/* Home extras */ +"rule_override_title" = "Conflito de regras"; +"rule_override_message" = "Este prato quebra uma ou mais regras de tags. Atribuir mesmo assim?"; +"rule_override_confirm" = "Atribuir mesmo assim"; +"share_week_button" = "Compartilhar semana"; +"share_week_title" = "Plano semanal MealMood"; +"share_week_callout_title" = "Sua semana está pronta para exportar"; +"share_week_callout_subtitle" = "Crie uma imagem bonita para compartilhar ou imprimir seu plano de refeições."; +"share_export_style_default" = "Padrão"; +"share_export_style_school" = "Grade escolar"; +"share_export_style_vertical" = "Vertical"; +"share_export_style_picker_title" = "Escolha o estilo de exportação"; +"share_export_style_picker_subtitle" = "Você pode mudar isso a qualquer momento nos Ajustes."; +"share_export_style_picker_apply" = "Usar este estilo"; +"share_export_school_title" = "Plano semanal de refeições"; +"share_export_school_subtitle" = "Grade escolar"; +"share_export_vertical_title" = "Plano vertical de refeições"; +"ads_placeholder" = "Anúncio"; +"ads_loading" = "Carregando anúncio..."; +"ads_unavailable" = "Anúncio indisponível"; +"history_title" = "Histórico mensal"; +"history_month_empty" = "Nenhuma semana planejada neste mês"; +"history_week_complete" = "Semana concluída"; +"history_week_incomplete" = "Semana em andamento"; + +/* Onboarding suggestions */ +"first_dishes_suggestions" = "Sugestões rápidas"; +"first_dishes_suggestion_add" = "Adicionar sugestão"; +"first_dishes_suggestion_added" = "Adicionado"; +"first_dishes_no_tags" = "As tags padrão estão carregando, tente novamente em instantes"; + +/* Onboarding trial */ +"onboarding_trial_title" = "Desbloquear Premium"; +"onboarding_trial_subtitle" = "Desbloqueie agora todos os recursos premium."; +"onboarding_trial_cta" = "Desbloquear Premium"; +"onboarding_trial_skip" = "Continuar com o plano grátis"; +"onboarding_trial_price_format" = "%@ / mês"; + +/* Review funnel */ +"review_funnel_title" = "Como está sendo usar o MealMood?"; +"review_funnel_message" = "Sua opinião nos ajuda a melhorar o MealMood."; +"review_funnel_positive" = "Estou gostando"; +"review_funnel_negative" = "Precisa melhorar"; +"review_feedback_title" = "Conte o que devemos melhorar"; +"review_feedback_message" = "Envie seu feedback e vamos usá-lo para melhorar o app."; +"review_feedback_contact" = "Enviar feedback"; +"onboarding_auto_assign_title" = "Quer que a gente preencha sua semana automaticamente?"; +"onboarding_auto_assign_message" = "Podemos preencher esta semana automaticamente usando seus pratos e regras."; +"onboarding_auto_assign_yes" = "Sim, preencher automaticamente"; +"onboarding_auto_assign_no" = "Não, eu faço"; +"onboarding_premium_prompt_title" = "Quer desbloquear o Premium?"; +"onboarding_premium_prompt_message" = "Tenha zero anúncios, pratos ilimitados, regras avançadas e planejamento de semanas futuras."; +"onboarding_premium_prompt_cta" = "Ver Premium"; + +/* Notifications */ +"notification_planning_title" = "Planeje sua próxima semana"; +"notification_planning_body" = "Sua nova semana começa amanhã e ainda não foi planejada."; + +/* Meal Types */ +"lunch" = "Almoço"; +"dinner" = "Jantar"; + +/* Days */ +"day_mon" = "Seg"; +"day_tue" = "Ter"; +"day_wed" = "Qua"; +"day_thu" = "Qui"; +"day_fri" = "Sex"; +"day_sat" = "Sáb"; +"day_sun" = "Dom"; + +/* Durations */ +"duration_30" = "30 min"; +"duration_60" = "1 hora"; +"duration_90" = "1,5 hora"; +"duration_120" = "2 horas"; + +/* Reminders */ +"reminder_30" = "30 min antes"; +"reminder_60" = "1 hora antes"; +"reminder_120" = "2 horas antes"; + +"first_dishes_limit_reached" = "Você chegou a 10 pratos. Toque em Concluir quando estiver pronto."; diff --git a/MealMood/Services/ICloudSyncService.swift b/MealMood/Services/ICloudSyncService.swift index 93f7084..4b97a59 100644 --- a/MealMood/Services/ICloudSyncService.swift +++ b/MealMood/Services/ICloudSyncService.swift @@ -70,6 +70,7 @@ final class ICloudSyncService { eventPrefix: $0.eventPrefix, reminderMinutesBefore: $0.reminderMinutesBefore, iCloudSyncEnabled: $0.iCloudSyncEnabled, + weekExportStyle: $0.weekExportStyle, isPremium: $0.isPremium, onboardingCompleted: $0.onboardingCompleted ) @@ -151,6 +152,7 @@ final class ICloudSyncService { settings.eventPrefix = settingsPayload.eventPrefix settings.reminderMinutesBefore = settingsPayload.reminderMinutesBefore settings.iCloudSyncEnabled = settingsPayload.iCloudSyncEnabled + settings.weekExportStyle = settingsPayload.weekExportStyle settings.isPremium = settingsPayload.isPremium settings.onboardingCompleted = settingsPayload.onboardingCompleted context.insert(settings) @@ -233,6 +235,7 @@ private struct SettingsPayload: Codable { let eventPrefix: String let reminderMinutesBefore: Int? let iCloudSyncEnabled: Bool? + let weekExportStyle: String? let isPremium: Bool let onboardingCompleted: Bool } diff --git a/MealMood/ViewModels/SettingsViewModel.swift b/MealMood/ViewModels/SettingsViewModel.swift index 2b372e8..0c0729d 100644 --- a/MealMood/ViewModels/SettingsViewModel.swift +++ b/MealMood/ViewModels/SettingsViewModel.swift @@ -137,6 +137,7 @@ final class SettingsViewModel: ObservableObject { settings.eventDuration = defaults.eventDuration settings.eventPrefix = defaults.eventPrefix settings.reminderMinutesBefore = defaults.reminderMinutesBefore + settings.weekExportStyle = defaults.weekExportStyle settings.iCloudSyncEnabled = false settings.onboardingCompleted = false } diff --git a/MealMood/Views/Home/HomeView.swift b/MealMood/Views/Home/HomeView.swift index ac5ec50..6f93230 100644 --- a/MealMood/Views/Home/HomeView.swift +++ b/MealMood/Views/Home/HomeView.swift @@ -1,5 +1,6 @@ import SwiftUI import SwiftData +import UIKit struct HomeView: View { @Environment(\.horizontalSizeClass) private var horizontalSizeClass @@ -24,6 +25,10 @@ struct HomeView: View { @State private var showPostOnboardingAutoAssignPrompt: Bool = false @State private var showPostOnboardingPremiumPrompt: Bool = false @State private var hasEvaluatedPostOnboardingPrompts: Bool = false + @State private var showExportStylePicker: Bool = false + @State private var pendingExportPlan: WeekPlan? + @State private var shareImageURL: URL? + @State private var showShareSheet: Bool = false private var settings: AppSettings? { allSettings.first } @@ -383,6 +388,26 @@ struct HomeView: View { PremiumView(settings: settings) } } + .sheet(isPresented: $showExportStylePicker) { + WeekExportStylePickerSheet( + selectedStyle: settings.weekExportStyleEnum + ) { selectedStyle in + settings.weekExportStyleEnum = selectedStyle + try? context.save() + showExportStylePicker = false + + if let planToShare = pendingExportPlan { + prepareShareImage(plan: planToShare, settings: settings) + } + pendingExportPlan = nil + } + .presentationDetents([.medium, .large]) + } + .sheet(isPresented: $showShareSheet) { + if let shareImageURL { + ShareSheet(activityItems: [shareImageURL]) + } + } .sheet(isPresented: $showMonthlyHistory) { MonthlyHistoryView( weekPlans: weekPlans, @@ -584,13 +609,10 @@ struct HomeView: View { .font(.mealMoodCaption) .foregroundColor(.mealMoodTextSecondary) - if settings.isPremium, - let image = renderWeekShareImage(plan: plan, settings: settings), - let shareURL = persistShareImage(image) { - ShareLink( - item: shareURL, - preview: SharePreview(String(localized: "share_week_title"), image: Image(uiImage: image)) - ) { + if settings.isPremium { + Button { + startShareFlow(plan: plan, settings: settings) + } label: { Label("share_week_button", systemImage: "square.and.arrow.up") .font(.mealMoodBodyBold) .foregroundColor(.mealMoodTextPrimary) @@ -599,6 +621,7 @@ struct HomeView: View { .background(Color.white.opacity(0.75)) .clipShape(RoundedRectangle(cornerRadius: 10)) } + .buttonStyle(.plain) } else { Button { showPremiumFromExport = true @@ -637,19 +660,42 @@ struct HomeView: View { ) } + private func startShareFlow(plan: WeekPlan, settings: AppSettings) { + if settings.weekExportStyle == nil { + pendingExportPlan = plan + showExportStylePicker = true + return + } + prepareShareImage(plan: plan, settings: settings) + } + + private func prepareShareImage(plan: WeekPlan, settings: AppSettings) { + showShareSheet = false + shareImageURL = nil + guard let image = renderWeekShareImage(plan: plan, settings: settings), + let url = persistShareImage(image, style: settings.weekExportStyleEnum) else { + return + } + shareImageURL = url + showShareSheet = true + } + private func renderWeekShareImage(plan: WeekPlan, settings: AppSettings) -> UIImage? { let renderer = ImageRenderer(content: WeekPlanShareView(plan: plan, settings: settings, dishes: dishes, tags: tags)) + let canvasSize = WeekPlanShareView.canvasSize(for: settings.weekExportStyleEnum) renderer.proposedSize = ProposedViewSize( - width: WeekPlanShareView.a4LandscapeWidth, - height: WeekPlanShareView.a4LandscapeHeight + width: canvasSize.width, + height: canvasSize.height ) renderer.scale = 1 return renderer.uiImage } - private func persistShareImage(_ image: UIImage) -> URL? { + private func persistShareImage(_ image: UIImage, style: WeekExportStyle) -> URL? { guard let data = image.pngData() else { return nil } - let url = FileManager.default.temporaryDirectory.appendingPathComponent("mealmood-week-plan.png") + let timestamp = Int(Date().timeIntervalSince1970 * 1000) + let filename = "mealmood-week-plan-\(style.rawValue)-\(timestamp).png" + let url = FileManager.default.temporaryDirectory.appendingPathComponent(filename) try? data.write(to: url, options: .atomic) return url } @@ -731,6 +777,185 @@ struct HomeView: View { } } +private struct ShareSheet: UIViewControllerRepresentable { + let activityItems: [Any] + + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: activityItems, applicationActivities: nil) + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} +} + +private struct WeekExportStylePickerSheet: View { + let selectedStyle: WeekExportStyle + let onConfirm: (WeekExportStyle) -> Void + + @State private var temporarySelection: WeekExportStyle + + init(selectedStyle: WeekExportStyle, onConfirm: @escaping (WeekExportStyle) -> Void) { + self.selectedStyle = selectedStyle + self.onConfirm = onConfirm + _temporarySelection = State(initialValue: selectedStyle) + } + + var body: some View { + NavigationStack { + VStack(spacing: 14) { + Text("share_export_style_picker_title") + .font(.mealMoodH2) + .foregroundColor(.mealMoodTextPrimary) + .padding(.top, 8) + + Text("share_export_style_picker_subtitle") + .font(.mealMoodCaption) + .foregroundColor(.mealMoodTextSecondary) + + ScrollView { + VStack(spacing: 12) { + ForEach(WeekExportStyle.allCases, id: \.self) { style in + Button { + temporarySelection = style + } label: { + VStack(alignment: .leading, spacing: 10) { + WeekExportStyleThumbnail(style: style) + .frame(height: 110) + .clipShape(RoundedRectangle(cornerRadius: 10)) + + HStack { + Text(LocalizedStringKey(style.localizedKey)) + .font(.mealMoodBodyBold) + .foregroundColor(.mealMoodTextPrimary) + Spacer() + Image(systemName: temporarySelection == style ? "checkmark.circle.fill" : "circle") + .foregroundColor(temporarySelection == style ? .mealMoodCoral : .mealMoodTextSecondary) + } + } + .padding(10) + .background(Color.white) + .clipShape(RoundedRectangle(cornerRadius: 12)) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke( + temporarySelection == style ? Color.mealMoodCoral : Color.mealMoodMint.opacity(0.55), + lineWidth: temporarySelection == style ? 2 : 1 + ) + ) + } + .buttonStyle(.plain) + } + } + .padding(.vertical, 6) + } + + Button("share_export_style_picker_apply") { + onConfirm(temporarySelection) + } + .buttonStyle(.borderedProminent) + .tint(.mealMoodCoral) + } + .padding(.horizontal, 16) + .padding(.bottom, 12) + .background(Color.mealMoodBackground.ignoresSafeArea()) + .navigationBarTitleDisplayMode(.inline) + } + } +} + +private struct WeekExportStyleThumbnail: View { + let style: WeekExportStyle + + var body: some View { + ZStack { + switch style { + case .defaultStyle: + defaultPreview + case .schoolTimetable: + schoolPreview + case .vertical: + verticalPreview + } + } + } + + private var defaultPreview: some View { + ZStack { + LinearGradient(colors: [Color(hex: "#FFEFE6"), Color(hex: "#F1FBF5")], startPoint: .topLeading, endPoint: .bottomTrailing) + VStack(spacing: 6) { + RoundedRectangle(cornerRadius: 5) + .fill(Color.white.opacity(0.9)) + .frame(height: 20) + RoundedRectangle(cornerRadius: 8) + .fill(Color.white.opacity(0.8)) + .frame(height: 62) + .overlay( + VStack(spacing: 4) { + Rectangle().fill(Color.mealMoodCoral.opacity(0.35)).frame(height: 8) + Rectangle().fill(Color.mealMoodMint.opacity(0.35)).frame(height: 8) + Rectangle().fill(Color(hex: "#E9EDF7")).frame(height: 8) + } + .padding(8) + ) + } + .padding(8) + } + } + + private var schoolPreview: some View { + ZStack { + Color(hex: "#FFF9F5") + VStack(spacing: 5) { + RoundedRectangle(cornerRadius: 5) + .fill(Color.white) + .frame(height: 16) + HStack(spacing: 4) { + RoundedRectangle(cornerRadius: 4) + .fill(Color.mealMoodCoral.opacity(0.5)) + .frame(width: 42) + VStack(spacing: 4) { + Rectangle().fill(Color.mealMoodMint.opacity(0.4)).frame(height: 12) + Rectangle().fill(Color(hex: "#FFFDFB")).frame(height: 20) + Rectangle().fill(Color(hex: "#FFFDFB")).frame(height: 20) + } + VStack(spacing: 4) { + Rectangle().fill(Color.mealMoodMint.opacity(0.4)).frame(height: 12) + Rectangle().fill(Color(hex: "#FFFDFB")).frame(height: 20) + Rectangle().fill(Color(hex: "#FFFDFB")).frame(height: 20) + } + } + } + .padding(8) + } + } + + private var verticalPreview: some View { + ZStack { + Color(hex: "#FFFDF8") + VStack(spacing: 6) { + RoundedRectangle(cornerRadius: 5) + .fill(Color.white) + .frame(height: 18) + ForEach(0..<3, id: \.self) { _ in + HStack(spacing: 6) { + Capsule() + .fill(Color.mealMoodCoral.opacity(0.45)) + .frame(width: 46, height: 14) + VStack(spacing: 4) { + Rectangle().fill(Color.mealMoodMint.opacity(0.35)).frame(height: 7) + Rectangle().fill(Color(hex: "#EDE8E1")).frame(height: 7) + } + } + .padding(.horizontal, 6) + .padding(.vertical, 4) + .background(Color.white) + .clipShape(RoundedRectangle(cornerRadius: 6)) + } + } + .padding(8) + } + } +} + private struct MonthlyHistoryView: View { let weekPlans: [WeekPlan] let currentWeekStart: Date diff --git a/MealMood/Views/Home/WeekPlanShareView.swift b/MealMood/Views/Home/WeekPlanShareView.swift index fcc2ce1..bdf4063 100644 --- a/MealMood/Views/Home/WeekPlanShareView.swift +++ b/MealMood/Views/Home/WeekPlanShareView.swift @@ -6,14 +6,40 @@ struct WeekPlanShareView: View { let settings: AppSettings let dishes: [Dish] let tags: [Tag] + let exportStyleOverride: WeekExportStyle? static let a4LandscapeWidth: CGFloat = 3508 static let a4LandscapeHeight: CGFloat = 2480 + static let a4PortraitWidth: CGFloat = 2480 + static let a4PortraitHeight: CGFloat = 3508 private let shareURL = "https://mealmood.app" private let qrContext = CIContext() private let qrFilter = CIFilter.qrCodeGenerator() + init( + plan: WeekPlan, + settings: AppSettings, + dishes: [Dish], + tags: [Tag], + exportStyleOverride: WeekExportStyle? = nil + ) { + self.plan = plan + self.settings = settings + self.dishes = dishes + self.tags = tags + self.exportStyleOverride = exportStyleOverride + } + + static func canvasSize(for style: WeekExportStyle) -> CGSize { + switch style { + case .vertical: + return CGSize(width: a4PortraitWidth, height: a4PortraitHeight) + case .defaultStyle, .schoolTimetable: + return CGSize(width: a4LandscapeWidth, height: a4LandscapeHeight) + } + } + private var dayRange: ClosedRange { settings.includeWeekends ? 0...6 : 0...4 } @@ -26,6 +52,18 @@ struct WeekPlanShareView: View { } } + private var exportStyle: WeekExportStyle { + exportStyleOverride ?? settings.weekExportStyleEnum + } + + private var canvasSize: CGSize { + Self.canvasSize(for: exportStyle) + } + + private var locale: Locale { + Locale(identifier: settings.languageEnum.resolved().localeIdentifier) + } + private var exportDateString: String { Date.now.formatted(date: .abbreviated, time: .omitted) } @@ -41,77 +79,42 @@ struct WeekPlanShareView: View { var body: some View { ZStack { - backgroundLayer + switch exportStyle { + case .defaultStyle: + defaultBackground + case .schoolTimetable: + schoolBackground + case .vertical: + verticalBackground + } - VStack(alignment: .leading, spacing: 34) { + VStack(spacing: 24) { headerBlock - ZStack { - RoundedRectangle(cornerRadius: 30) - .fill(Color.white.opacity(0.62)) - RoundedRectangle(cornerRadius: 30) - .stroke(Color.white.opacity(0.75), lineWidth: 2) - - Grid(horizontalSpacing: 10, verticalSpacing: 10) { - GridRow { - gridHeaderCell("", day: nil) - .frame(minWidth: 220) - ForEach(Array(dayRange), id: \.self) { day in - gridHeaderCell(dayTitle(for: day), day: day) - } - } - - ForEach(mealTypes, id: \.self) { mealType in - GridRow { - gridMealCell(mealTypeLabel(mealType), icon: mealType.icon, mealType: mealType) - .frame(minWidth: 220) - ForEach(Array(dayRange), id: \.self) { day in - gridDishCell(dishName(day: day, mealType: mealType), day: day) - } - } - } + Group { + switch exportStyle { + case .defaultStyle: + defaultGridLayout + case .schoolTimetable: + schoolTimetableLayout + case .vertical: + verticalLayout } - .padding(18) } - .shadow(color: Color(hex: "#DDAA99").opacity(0.18), radius: 26, x: 0, y: 16) - - Spacer() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) footerBlock } - .padding(.horizontal, 84) - .padding(.vertical, 72) + .padding(.horizontal, exportStyle == .vertical ? 72 : 84) + .padding(.vertical, exportStyle == .vertical ? 58 : 70) } - .frame(width: Self.a4LandscapeWidth, height: Self.a4LandscapeHeight) + .frame(width: canvasSize.width, height: canvasSize.height) } - private func dayTitle(for day: Int) -> String { - let keys = ["day_mon", "day_tue", "day_wed", "day_thu", "day_fri", "day_sat", "day_sun"] - let localizedDay = day >= 0 && day < keys.count ? NSLocalizedString(keys[day], comment: "") : "" - let dayDate = plan.weekStartDate.addingDays(day) - let dayNumber = Calendar.current.component(.day, from: dayDate) - return "\(localizedDay) \(dayNumber)" - } - - private func mealTypeLabel(_ mealType: MealType) -> String { - mealType == .lunch ? String(localized: "lunch") : String(localized: "dinner") - } - - private func dishName(day: Int, mealType: MealType) -> String { - let slot = plan.slots.first { $0.dayOfWeek == day && $0.mealType == mealType.rawValue } - guard let slot, let dishId = slot.dishId, let dish = dishes.first(where: { $0.id == dishId }) else { - return "—" - } - return dish.name - } - - private var backgroundLayer: some View { + private var defaultBackground: some View { ZStack { LinearGradient( - colors: [ - Color(hex: "#FFEFE6"), - Color(hex: "#F1FBF5") - ], + colors: [Color(hex: "#FFEFE6"), Color(hex: "#F1FBF5")], startPoint: .topLeading, endPoint: .bottomTrailing ) @@ -127,57 +130,262 @@ struct WeekPlanShareView: View { .frame(width: 680, height: 680) .blur(radius: 20) .offset(x: 1050, y: 760) - - GeometryReader { geo in - Canvas { context, size in - let spacing: CGFloat = 80 - var x: CGFloat = -size.height - while x < size.width { - var path = Path() - path.move(to: CGPoint(x: x, y: 0)) - path.addLine(to: CGPoint(x: x + size.height, y: size.height)) - context.stroke(path, with: .color(Color(hex: "#FFFFFF").opacity(0.26)), lineWidth: 1.2) - x += spacing - } - } - .frame(width: geo.size.width, height: geo.size.height) - } } } + private var schoolBackground: some View { + ZStack { + Color(hex: "#FFF9F5") + + RoundedRectangle(cornerRadius: 220) + .fill(Color.mealMoodMint.opacity(0.22)) + .frame(width: 1500, height: 520) + .offset(x: 920, y: -920) + + RoundedRectangle(cornerRadius: 220) + .fill(Color.mealMoodCoral.opacity(0.18)) + .frame(width: 1600, height: 560) + .offset(x: -920, y: 940) + } + } + + private var verticalBackground: some View { + ZStack { + Color(hex: "#FFFDF8") + + Ellipse() + .fill(Color.mealMoodMint.opacity(0.28)) + .frame(width: 1100, height: 900) + .offset(x: 800, y: -1200) + + Ellipse() + .fill(Color.mealMoodCoral.opacity(0.2)) + .frame(width: 900, height: 680) + .offset(x: -800, y: 1200) + } + } + + @ViewBuilder private var headerBlock: some View { + switch exportStyle { + case .defaultStyle: + defaultHeader + case .schoolTimetable, .vertical: + elegantHeader + } + } + + private var defaultHeader: some View { HStack(alignment: .center, spacing: 24) { AppIconPlaceholder(size: 84) - VStack(alignment: .leading, spacing: 8) { - Text("MealMood Premium") - .font(.system(size: 50, weight: .bold, design: .rounded)) + VStack(alignment: .leading, spacing: 6) { + Text(exportHeaderTitle) + .font(.system(size: 52, weight: .bold, design: .rounded)) .foregroundColor(.mealMoodTextPrimary) - Text(String(localized: "share_week_title")) + Text(plan.weekStartDate.formattedWeekRange()) .font(.system(size: 28, weight: .semibold, design: .rounded)) .foregroundColor(Color(hex: "#5D645F")) } Spacer() - VStack(alignment: .trailing, spacing: 8) { - Text(plan.weekStartDate.formattedWeekRange()) - .font(.system(size: 36, weight: .bold, design: .rounded)) - .foregroundColor(Color(hex: "#4A4F4B")) - Text(exportDateString) - .font(.system(size: 24, weight: .medium, design: .rounded)) - .foregroundColor(.mealMoodTextSecondary) - } + Text(exportDateString) + .font(.system(size: 24, weight: .medium, design: .rounded)) + .foregroundColor(.mealMoodTextSecondary) } .padding(.horizontal, 30) .padding(.vertical, 24) - .background(Color.white.opacity(0.76)) + .background(Color.white.opacity(0.8)) .clipShape(RoundedRectangle(cornerRadius: 24)) .overlay( RoundedRectangle(cornerRadius: 24) - .stroke(Color(hex: "#F3C4B5").opacity(0.7), lineWidth: 1) + .stroke(Color.mealMoodCoral.opacity(0.5), lineWidth: 1) ) - .shadow(color: Color(hex: "#DDAA99").opacity(0.15), radius: 20, x: 0, y: 12) + } + + private var elegantHeader: some View { + VStack(spacing: 8) { + Text(plan.weekStartDate.formattedWeekRange()) + .font(.custom("Didot", size: exportStyle == .vertical ? 126 : 108)) + .foregroundColor(Color(hex: "#30473F")) + .multilineTextAlignment(.center) + .lineLimit(1) + .minimumScaleFactor(0.45) + .frame(maxWidth: .infinity) + + Text(exportHeaderTitle) + .font(.system(size: exportStyle == .vertical ? 58 : 54, weight: .semibold, design: .serif)) + .foregroundColor(.mealMoodTextPrimary) + + Text(exportDateString) + .font(.system(size: 34, weight: .medium, design: .serif)) + .foregroundColor(.mealMoodTextSecondary) + } + .padding(.horizontal, 24) + .padding(.vertical, 24) + .background(Color.white.opacity(0.88)) + .clipShape(RoundedRectangle(cornerRadius: 24)) + .overlay( + RoundedRectangle(cornerRadius: 24) + .stroke(Color.mealMoodCoral.opacity(0.45), lineWidth: 1) + ) + } + + private var exportHeaderTitle: String { + switch exportStyle { + case .defaultStyle: + return String(localized: "share_week_title") + case .schoolTimetable: + return String(localized: "share_export_school_title") + case .vertical: + return String(localized: "share_export_vertical_title") + } + } + + private var defaultGridLayout: some View { + ZStack { + RoundedRectangle(cornerRadius: 30) + .fill(Color.white.opacity(0.62)) + RoundedRectangle(cornerRadius: 30) + .stroke(Color.white.opacity(0.75), lineWidth: 2) + + Grid(horizontalSpacing: 10, verticalSpacing: 10) { + GridRow { + gridHeaderCell("", day: nil) + .frame(minWidth: 220) + ForEach(Array(dayRange), id: \.self) { day in + gridHeaderCell(dayShortTitle(for: day), day: day) + } + } + + ForEach(mealTypes, id: \.self) { mealType in + GridRow { + gridMealCell(mealTypeLabel(mealType), icon: mealType.icon, mealType: mealType) + .frame(minWidth: 220) + ForEach(Array(dayRange), id: \.self) { day in + gridDishCell(dishName(day: day, mealType: mealType), day: day) + } + } + } + } + .padding(18) + } + .shadow(color: Color(hex: "#DDAA99").opacity(0.18), radius: 26, x: 0, y: 16) + } + + private var schoolTimetableLayout: some View { + ZStack { + RoundedRectangle(cornerRadius: 28) + .fill(Color.white) + .shadow(color: Color.black.opacity(0.06), radius: 20, x: 0, y: 10) + + VStack(spacing: 18) { + Text(String(localized: "share_export_school_subtitle")) + .font(.custom("Didot", size: 48)) + .foregroundColor(.mealMoodTextSecondary) + + Grid(horizontalSpacing: 12, verticalSpacing: 12) { + GridRow { + Text(" ") + .frame(width: 280) + ForEach(mealTypes, id: \.self) { mealType in + Text(mealTypeLabel(mealType).uppercased(with: locale)) + .font(.system(size: 34, weight: .bold, design: .serif)) + .foregroundColor(Color(hex: "#355548")) + .frame(maxWidth: .infinity, minHeight: 88) + .background(Color.mealMoodMint.opacity(0.3)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } + } + + ForEach(Array(dayRange), id: \.self) { day in + GridRow { + Text(dayHeaderTitle(for: day).uppercased(with: locale)) + .font(.system(size: 30, weight: .bold, design: .serif)) + .foregroundColor(.mealMoodTextPrimary) + .multilineTextAlignment(.center) + .frame(width: 280) + .frame(minHeight: 138) + .background(dayHeaderBackground(day: day)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + + ForEach(mealTypes, id: \.self) { mealType in + Text(dishName(day: day, mealType: mealType)) + .font(.system(size: 31, weight: .medium, design: .serif)) + .foregroundColor(.mealMoodTextPrimary) + .multilineTextAlignment(.leading) + .lineLimit(3) + .frame(maxWidth: .infinity, minHeight: 138, alignment: .leading) + .padding(.horizontal, 16) + .background(Color(hex: "#FFFDFB")) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color(hex: "#ECDDD5"), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 12)) + } + } + } + } + } + .padding(.horizontal, 24) + .padding(.vertical, 22) + } + } + + private var verticalLayout: some View { + ZStack { + RoundedRectangle(cornerRadius: 30) + .fill(Color.white.opacity(0.95)) + .overlay( + RoundedRectangle(cornerRadius: 30) + .stroke(Color.mealMoodMint.opacity(0.45), lineWidth: 2) + ) + + VStack(spacing: 16) { + ForEach(Array(dayRange), id: \.self) { day in + VStack(alignment: .leading, spacing: 10) { + Text(dayHeaderTitle(for: day).uppercased(with: locale)) + .font(.custom("Didot", size: 50)) + .foregroundColor(.mealMoodTextPrimary) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 20) + .padding(.vertical, 10) + .background(dayHeaderBackground(day: day)) + .clipShape(RoundedRectangle(cornerRadius: 12)) + + VStack(alignment: .leading, spacing: 8) { + ForEach(mealTypes, id: \.self) { mealType in + HStack(alignment: .top, spacing: 10) { + Text("\(mealTypeLabel(mealType)):") + .font(.system(size: 34, weight: .bold, design: .serif)) + .foregroundColor(Color(hex: "#385549")) + + Text(dishName(day: day, mealType: mealType)) + .font(.system(size: 35, weight: .regular, design: .serif)) + .foregroundColor(.mealMoodTextPrimary) + .lineLimit(2) + .minimumScaleFactor(0.7) + } + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, 6) + } + .padding(.horizontal, 16) + .padding(.vertical, 14) + .background(Color.white) + .overlay( + RoundedRectangle(cornerRadius: 16) + .stroke(Color(hex: "#EEE4DE"), lineWidth: 1) + ) + .clipShape(RoundedRectangle(cornerRadius: 16)) + } + } + .padding(.horizontal, 22) + .padding(.vertical, 20) + } } private var footerBlock: some View { @@ -211,13 +419,48 @@ struct WeekPlanShareView: View { } .padding(.horizontal, 26) .padding(.vertical, 18) - .background(Color.white.opacity(0.8)) + .background(Color.white.opacity(0.82)) .clipShape(RoundedRectangle(cornerRadius: 22)) .overlay( RoundedRectangle(cornerRadius: 22) .stroke(Color(hex: "#DDEEE6"), lineWidth: 1) ) - .shadow(color: Color(hex: "#7F9E90").opacity(0.14), radius: 16, x: 0, y: 8) + } + + private func dayShortTitle(for day: Int) -> String { + let dayDate = plan.weekStartDate.addingDays(day) + let formatter = DateFormatter() + formatter.locale = locale + formatter.setLocalizedDateFormatFromTemplate("EEE d") + return formatter.string(from: dayDate).capitalized(with: locale) + } + + private func dayLongTitle(for day: Int) -> String { + let dayDate = plan.weekStartDate.addingDays(day) + let formatter = DateFormatter() + formatter.locale = locale + formatter.setLocalizedDateFormatFromTemplate("EEEE") + return formatter.string(from: dayDate).capitalized(with: locale) + } + + private func dayHeaderTitle(for day: Int) -> String { + let dayDate = plan.weekStartDate.addingDays(day) + let formatter = DateFormatter() + formatter.locale = locale + formatter.setLocalizedDateFormatFromTemplate("EEEE d") + return formatter.string(from: dayDate).capitalized(with: locale) + } + + private func mealTypeLabel(_ mealType: MealType) -> String { + mealType == .lunch ? String(localized: "lunch") : String(localized: "dinner") + } + + private func dishName(day: Int, mealType: MealType) -> String { + let slot = plan.slots.first { $0.dayOfWeek == day && $0.mealType == mealType.rawValue } + guard let slot, let dishId = slot.dishId, let dish = dishes.first(where: { $0.id == dishId }) else { + return "—" + } + return dish.name } private func gridHeaderCell(_ title: String, day: Int?) -> some View { diff --git a/MealMood/Views/Settings/SettingsView.swift b/MealMood/Views/Settings/SettingsView.swift index 371de2f..32d86f8 100644 --- a/MealMood/Views/Settings/SettingsView.swift +++ b/MealMood/Views/Settings/SettingsView.swift @@ -45,6 +45,15 @@ struct SettingsView: View { set: { settings.includeWeekends = $0 } )) .tint(.mealMoodCoral) + + Picker("settings_export_style", selection: Binding( + get: { settings.weekExportStyleEnum }, + set: { settings.weekExportStyleEnum = $0 } + )) { + ForEach(WeekExportStyle.allCases, id: \.self) { style in + Text(LocalizedStringKey(style.localizedKey)).tag(style) + } + } } header: { Label("settings_planning", systemImage: "fork.knife") } diff --git a/scripts/archive_and_upload_appstore.sh b/scripts/archive_and_upload_appstore.sh new file mode 100755 index 0000000..7d59c62 --- /dev/null +++ b/scripts/archive_and_upload_appstore.sh @@ -0,0 +1,140 @@ +#!/bin/zsh + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +PROJECT_PATH="${PROJECT_PATH:-$ROOT_DIR/MealMood.xcodeproj}" +SCHEME="${SCHEME:-MealMood}" +CONFIGURATION="${CONFIGURATION:-Release}" +ARCHIVE_DIR="${ARCHIVE_DIR:-$ROOT_DIR/build/appstore}" +ARCHIVE_PATH="${ARCHIVE_PATH:-$ARCHIVE_DIR/${SCHEME}.xcarchive}" +EXPORT_PATH="${EXPORT_PATH:-$ARCHIVE_DIR/export}" +EXPORT_OPTIONS_PLIST="${EXPORT_OPTIONS_PLIST:-$ARCHIVE_DIR/ExportOptions.plist}" +KEY_ID="${APPSTORE_KEY_ID:-YJLSGZGP79}" +ISSUER_ID="${APPSTORE_ISSUER_ID:-33f82bb1-0dbd-4b3f-a0ef-1a863dfd9453}" +TEAM_ID="${DEVELOPMENT_TEAM_ID:-2825Q76T7H}" +P8_PATH="${APPSTORE_P8_PATH:-}" +P8_DIR="" + +fail() { + echo "Error: $*" >&2 + exit 1 +} + +find_p8_file() { + if [[ -n "$P8_PATH" ]]; then + [[ -f "$P8_PATH" ]] || fail "No existe el fichero APPSTORE_P8_PATH=$P8_PATH" + echo "$P8_PATH" + return + fi + + local default_path="$HOME/AuthKey_${KEY_ID}.p8" + if [[ -f "$default_path" ]]; then + echo "$default_path" + return + fi + + local matches=("${HOME}"/*.p8(N)) + if (( ${#matches[@]} == 1 )); then + echo "${matches[1]}" + return + fi + + if (( ${#matches[@]} > 1 )); then + fail "Hay varios ficheros .p8 en $HOME. Define APPSTORE_P8_PATH con la ruta correcta." + fi + + fail "No se encontro AuthKey_${KEY_ID}.p8 ni un unico fichero .p8 en $HOME." +} + +create_export_options() { + mkdir -p "$ARCHIVE_DIR" + cat > "$EXPORT_OPTIONS_PLIST" < + + + + destination + upload + manageAppVersionAndBuildNumber + + method + app-store-connect + signingStyle + automatic + stripSwiftSymbols + + teamID + ${TEAM_ID} + uploadSymbols + + + +EOF +} + +archive_app() { + echo "==> Archivando ${SCHEME}" + xcodebuild \ + -project "$PROJECT_PATH" \ + -scheme "$SCHEME" \ + -configuration "$CONFIGURATION" \ + -archivePath "$ARCHIVE_PATH" \ + -destination "generic/platform=iOS" \ + clean archive +} + +export_ipa() { + echo "==> Exportando IPA" + xcodebuild \ + -exportArchive \ + -archivePath "$ARCHIVE_PATH" \ + -exportPath "$EXPORT_PATH" \ + -exportOptionsPlist "$EXPORT_OPTIONS_PLIST" +} + +upload_ipa() { + local ipa + if [[ ! -d "$EXPORT_PATH" ]]; then + echo "==> Xcode ha subido el build directamente a App Store Connect" + return + fi + + ipa="$(find "$EXPORT_PATH" -maxdepth 1 -name '*.ipa' -print -quit)" + if [[ -z "$ipa" ]]; then + echo "==> No hay .ipa exportado. La subida probablemente ya se ha completado desde xcodebuild." + return + fi + + echo "==> Subiendo $(basename "$ipa") a App Store Connect" + API_PRIVATE_KEYS_DIR="$P8_DIR" \ + xcrun altool \ + --upload-app \ + --type ios \ + --file "$ipa" \ + --apiKey "$KEY_ID" \ + --apiIssuer "$ISSUER_ID" +} + +main() { + command -v xcodebuild >/dev/null 2>&1 || fail "xcodebuild no esta disponible" + command -v xcrun >/dev/null 2>&1 || fail "xcrun no esta disponible" + + P8_PATH="$(find_p8_file)" + P8_DIR="$(dirname "$P8_PATH")" + + echo "Usando proyecto: $PROJECT_PATH" + echo "Usando scheme: $SCHEME" + echo "Usando clave API: $KEY_ID" + echo "Usando fichero .p8: $P8_PATH" + + rm -rf "$ARCHIVE_PATH" "$EXPORT_PATH" + create_export_options + archive_app + export_ipa + upload_ipa + + echo "==> Proceso completado" +} + +main "$@"