1.0.2: Add multilingual support, redesign share view, and iCloud sync improvements
- Add localisations for German, French, Italian, and Brazilian Portuguese - Redesign WeekPlanShareView with richer layout and sharing options - Improve HomeView with various UI enhancements - Update AppSettings and Tag models for new features - Minor iCloud sync and Settings fixes - Add archive & upload script for App Store submissions Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.";
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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.";
|
||||
@@ -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.";
|
||||
@@ -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.";
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Int> {
|
||||
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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user