diff --git a/.env.example b/.env.example index d0090b2..7e22c96 100644 --- a/.env.example +++ b/.env.example @@ -14,3 +14,5 @@ MATRIX_HOMESERVER_URL=https://matrix.example.com # Synapse Admin API shared secret or admin access token # Used to auto-provision Matrix accounts for users MATRIX_ADMIN_TOKEN= + +RESEND_API_KEY= \ No newline at end of file diff --git a/messages/en.json b/messages/en.json index 76bc6e1..457619b 100644 --- a/messages/en.json +++ b/messages/en.json @@ -69,7 +69,7 @@ "kanban_add_column": "Add Column", "kanban_column_name_label": "Column Name", "kanban_column_name_placeholder": "e.g. To Do, In Progress, Done", - "kanban_add_card": "Add Card", + "kanban_add_card": "Add card", "kanban_card_details": "Card Details", "kanban_card_title_label": "Title", "kanban_card_title_placeholder": "Card title", @@ -147,7 +147,7 @@ "settings_members_unknown": "Unknown User", "settings_members_no_name": "No name", "settings_members_no_email": "No email", - "settings_members_remove": "Remove from Org", + "settings_members_remove": "Remove from organization", "settings_invite_title": "Invite Member", "settings_invite_email": "Email address", "settings_invite_email_placeholder": "colleague@example.com", @@ -461,6 +461,16 @@ "toast_error_create_checklist": "Failed to create checklist", "toast_error_delete_checklist": "Failed to delete checklist", "toast_error_rename_checklist": "Failed to rename checklist", + "toast_error_create_note": "Failed to create note", + "toast_error_update_note": "Failed to update note", + "toast_error_delete_note": "Failed to delete note", + "toast_error_create_stage": "Failed to create stage", + "toast_error_delete_stage": "Failed to delete stage", + "toast_error_create_block": "Failed to create schedule block", + "toast_error_update_block": "Failed to update schedule block", + "toast_error_delete_block": "Failed to delete schedule block", + "toast_error_create_contact": "Failed to create contact", + "toast_error_update_contact": "Failed to update contact", "toast_error_delete_contact": "Failed to delete contact", "toast_error_create_category": "Failed to create category", "toast_error_delete_category": "Failed to delete category", @@ -720,5 +730,262 @@ "map_show_shape_labels": "Show shape labels", "map_address": "Address", "map_address_placeholder": "e.g., Tallinn, Estonia", - "map_change_address": "Change address" -} \ No newline at end of file + "map_change_address": "Change address", + "map_rename": "Rename", + "map_duplicate": "Duplicate", + "map_or": "or", + "map_load": "Load", + "map_go": "Go", + "event_nav_finances": "Finances", + "event_nav_sponsors": "Sponsors", + "event_nav_contacts": "Contacts", + "event_nav_files": "Files", + "event_nav_departments": "Departments", + "module_kanban_label": "Kanban", + "module_kanban_desc": "Task boards with columns and cards for tracking work", + "module_files_label": "Files", + "module_files_desc": "Shared documents, folders, and file storage", + "module_checklist_label": "Checklist", + "module_checklist_desc": "Simple to-do lists with progress tracking", + "module_notes_label": "Notes", + "module_notes_desc": "Freeform notes and documentation", + "module_schedule_label": "Schedule", + "module_schedule_desc": "Timeline and timetable for stages and sessions", + "module_contacts_label": "Contacts", + "module_contacts_desc": "Vendor and contact directory with categories", + "module_budget_label": "Budget", + "module_budget_desc": "Income and expense tracking with planned vs actual", + "module_sponsors_label": "Sponsors", + "module_sponsors_desc": "Sponsor CRM with tiers, deliverables, and pipeline", + "module_map_label": "Map", + "module_map_desc": "Interactive venue map with pins and areas", + "contact_cat_general": "General", + "contact_cat_vendor": "Vendor", + "contact_cat_sponsor": "Sponsor", + "contact_cat_speaker": "Speaker", + "contact_cat_venue": "Venue", + "contact_cat_catering": "Catering", + "contact_cat_av_tech": "AV / Tech", + "contact_cat_transport": "Transport", + "contact_cat_security": "Security", + "contact_cat_media": "Media", + "contacts_all_categories": "All Categories", + "sponsor_status_prospect": "Prospect", + "sponsor_status_contacted": "Contacted", + "sponsor_status_confirmed": "Confirmed", + "sponsor_status_declined": "Declined", + "sponsor_status_active": "Active", + "sponsors_all_statuses": "All Statuses", + "sponsors_all_tiers": "All Tiers", + "sponsors_no_tier_filter": "No Tier", + "accent_blue": "Blue (Default)", + "accent_green": "Green", + "accent_red": "Red", + "accent_amber": "Amber", + "accent_purple": "Purple", + "accent_pink": "Pink", + "accent_indigo": "Indigo", + "accent_teal": "Teal", + "role_color_red": "Red", + "role_color_amber": "Amber", + "role_color_emerald": "Emerald", + "role_color_blue": "Blue", + "role_color_indigo": "Indigo", + "role_color_violet": "Violet", + "role_color_pink": "Pink", + "role_color_gray": "Gray", + "kanban_import_json": "Import JSON", + "kanban_export_json": "Export JSON", + "kanban_rename_from_list": "Rename from the documents list.", + "admin_tab_overview": "Overview", + "admin_tab_organizations": "Organizations", + "admin_tab_users": "Users", + "admin_tab_events": "Events", + "card_add_title": "Add Card", + "card_details_title": "Card Details", + "card_title_label": "Title", + "card_title_placeholder": "Card title", + "card_description_label": "Description", + "card_description_placeholder": "Add a more detailed description...", + "card_tags": "Tags", + "card_tags_done": "Done", + "card_tags_manage": "Manage", + "card_tags_new_placeholder": "New tag name...", + "card_tags_new_short": "New tag...", + "card_tags_add": "+ Add tag", + "card_tags_all_added": "All tags added", + "card_assignee": "Assignee", + "card_due_date": "Due Date", + "card_priority": "Priority", + "card_priority_low": "Low", + "card_priority_medium": "Medium", + "card_priority_high": "High", + "card_priority_urgent": "Urgent", + "card_checklist": "Checklist", + "card_checklist_add_placeholder": "Add an item...", + "card_checklist_add_item_placeholder": "Add checklist item...", + "card_comments": "Comments", + "card_comments_none": "No comments yet", + "card_comments_add_placeholder": "Add a comment...", + "card_comments_unknown": "Unknown", + "card_delete": "Delete Card", + "card_save_changes": "Save Changes", + "card_loading": "Loading...", + "kanban_add_col": "Add column", + "kanban_confirm_delete_column": "Delete this column and all its cards?", + "kanban_confirm_delete_card": "Delete this card?", + "kanban_toast_changes_saved": "Changes saved", + "kanban_toast_save_failed": "Failed to save changes", + "kanban_toast_card_created": "Card created", + "kanban_toast_create_failed": "Failed to create card", + "tasks_no_columns": "No task columns yet", + "tasks_add_column": "Add column", + "tasks_add_column_title": "Add Column", + "tasks_column_name": "Column name", + "tasks_column_placeholder": "e.g. In Review", + "tasks_add_task_title": "Add Task", + "tasks_task_title_label": "Task title", + "tasks_task_placeholder": "What needs to be done?", + "tasks_add_task_btn": "Add Task", + "chat_confirm_delete_message": "Delete this message?", + "chat_toast_message_deleted": "Message deleted", + "btn_send": "Send", + "btn_add": "Add", + "settings_confirm_disconnect_cal": "Disconnect Google Calendar?", + "kanban_confirm_delete_tag": "Delete this tag from the organization?", + "tasks_toast_create_column_failed": "Failed to create column", + "tasks_toast_delete_column_failed": "Failed to delete column", + "tasks_toast_rename_column_failed": "Failed to rename column", + "tasks_toast_create_task_failed": "Failed to create task", + "tasks_toast_delete_task_failed": "Failed to delete task", + "toast_success_sponsor_updated": "Sponsor updated", + "toast_success_sponsor_added": "Sponsor added", + "toast_error_save_sponsor": "Failed to save sponsor", + "toast_success_sponsor_removed": "Sponsor removed", + "toast_success_contact_updated": "Contact updated", + "toast_success_contact_added": "Contact added", + "toast_error_save_contact": "Failed to save contact", + "toast_success_contact_removed": "Contact removed", + "toast_error_update_planned_budget": "Failed to update planned budget", + "toast_error_save_allocation": "Failed to save allocation", + "toast_error_delete_allocation": "Failed to delete allocation", + "toast_error_no_google_avatar": "No Google avatar found.", + "toast_error_sync_avatar": "Failed to sync avatar.", + "toast_success_sync_avatar": "Google avatar synced.", + "toast_error_select_image": "Please select an image file.", + "toast_error_image_too_large": "Image must be under 2MB.", + "toast_error_upload_avatar": "Failed to upload avatar.", + "toast_error_save_avatar": "Failed to save avatar.", + "toast_success_avatar_updated": "Avatar updated.", + "toast_error_avatar_upload": "Avatar upload failed.", + "toast_error_remove_avatar": "Failed to remove avatar.", + "toast_success_avatar_removed": "Avatar removed.", + "toast_error_save_profile": "Failed to save profile.", + "toast_success_profile_saved": "Profile saved.", + "toast_error_save_preferences": "Failed to save preferences.", + "toast_success_preferences_saved": "Preferences saved.", + "toast_error_save_settings": "Failed to save settings.", + "toast_success_settings_saved": "Settings saved.", + "toast_error_save_slug": "Failed to update slug.", + "toast_success_slug_saved": "Slug updated.", + "toast_error_save_links": "Failed to save links.", + "toast_success_links_saved": "Links saved.", + "toast_error_create_tag": "Failed to create tag.", + "toast_success_tag_created": "Tag created.", + "toast_error_delete_tag": "Failed to delete tag.", + "toast_error_update_tag": "Failed to update tag.", + "toast_error_create_role": "Failed to create role.", + "toast_error_delete_role_system": "Cannot delete system roles.", + "toast_error_create_document": "Failed to create document.", + "toast_error_create_folder": "Failed to create folder.", + "toast_error_rename_document": "Failed to rename document.", + "toast_error_delete_document": "Failed to delete document.", + "toast_error_move_document": "Failed to move document.", + "toast_error_upload_file": "Failed to upload file.", + "toast_success_document_created": "Document created.", + "toast_success_folder_created": "Folder created.", + "toast_error_import_json": "Failed to import board.", + "toast_success_import_json": "Board imported successfully.", + "toast_error_export_json": "Failed to export board.", + "toast_success_export_json": "Board exported.", + "toast_error_save_avatar_url": "Failed to save avatar URL.", + "toast_error_save_event_defaults": "Failed to save event defaults.", + "toast_success_event_defaults_saved": "Event defaults saved.", + "toast_error_save_features": "Failed to save feature settings.", + "toast_success_features_saved": "Feature settings saved.", + "toast_error_save_social": "Failed to save social links.", + "toast_success_social_saved": "Social links saved.", + "toast_error_save_document": "Failed to save document", + "toast_error_load_kanban": "Failed to load kanban board", + "toast_error_move_card": "Failed to move card", + "toast_error_no_columns_import": "No columns exist to import cards into", + "toast_error_import_json_format": "Failed to import JSON - check file format", + "toast_success_board_exported": "Board exported as JSON", + "toast_error_move_shape": "Failed to move shape", + "toast_error_create_shape": "Failed to create shape", + "toast_error_move_pin": "Failed to move pin", + "toast_error_update_pin": "Failed to update pin", + "toast_error_create_pin": "Failed to create pin", + "toast_error_delete_pin": "Failed to delete pin", + "toast_error_update_shape": "Failed to update shape", + "toast_error_delete_shape": "Failed to delete shape", + "toast_error_duplicate_shape": "Failed to duplicate shape", + "toast_error_create_layer": "Failed to create layer", + "toast_error_delete_layer": "Failed to delete layer", + "toast_error_rename_layer": "Failed to rename layer", + "toast_error_reorder_objects": "Failed to reorder objects", + "toast_error_load_image": "Failed to load image", + "toast_error_upload_image": "Failed to upload image", + "toast_error_load_image_url": "Failed to load image from URL", + "toast_error_export_map": "Failed to export map", + "toast_error_create_board_widget": "Failed to create board", + "toast_success_message_edited": "Message edited", + "toast_error_file_too_large": "File too large. Maximum size is 50MB.", + "toast_success_file_sent": "File sent!", + "toast_error_create_room": "Failed to create room", + "toast_success_room_created": "Room created", + "toast_error_create_space": "Failed to create space", + "toast_success_space_created": "Space created", + "toast_error_send_file": "Failed to send file", + "toast_error_upload_failed": "Upload failed", + "toast_error_update_room": "Failed to update room settings", + "toast_success_room_updated": "Room settings updated", + "toast_error_leave_room": "Failed to leave room", + "toast_error_enter_room_name": "Please enter a room name", + "toast_error_enter_space_name": "Please enter a space name", + "toast_error_notification_settings": "Failed to change notification settings", + "toast_success_board_created": "Kanban board created", + "login_name_label": "Display name", + "login_name_placeholder": "Your full name", + "login_name_required": "Please enter your name", + "invite_title": "Invitation", + "invite_invalid_title": "Invalid Invite", + "invite_go_home": "Go Home", + "invite_youre_invited": "You're Invited!", + "invite_join_text": "You've been invited to join", + "invite_as_role": "as", + "invite_signed_in_as": "Signed in as", + "invite_accept_btn": "Accept Invite & Join", + "invite_wrong_account": "Wrong account?", + "invite_sign_out": "Sign out", + "invite_not_logged_in": "Sign in or create an account to accept this invite.", + "invite_sign_in": "Sign In", + "invite_create_account": "Create Account", + "invite_email_mismatch": "This invite was sent to {email}. Please sign in with that email address.", + "invite_already_member": "You're already a member of this organization.", + "invite_join_failed": "Failed to join organization. Please try again.", + "invite_generic_error": "Something went wrong. Please try again.", + "onboarding_title": "Complete Your Profile", + "onboarding_subtitle": "Help your team know who you are", + "onboarding_phone_label": "Phone number", + "onboarding_phone_placeholder": "+372 5xx xxxx", + "onboarding_discord_label": "Discord handle", + "onboarding_discord_placeholder": "username#1234", + "onboarding_shirt_label": "Shirt size", + "onboarding_hoodie_label": "Hoodie size", + "onboarding_save": "Save & Continue", + "onboarding_skip": "Skip for now", + "invite_email_subject": "{orgName} — You're invited to join", + "invite_email_sent": "Invite email sent to {email}", + "toast_error_send_invite_email": "Failed to send invite email" +} diff --git a/messages/et.json b/messages/et.json index 0793396..95ee853 100644 --- a/messages/et.json +++ b/messages/et.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "https://inlang.com/schema/inlang-message-format", "app_name": "Root", "nav_files": "Failid", @@ -183,7 +183,7 @@ "settings_connect_cal_desc": "Kleebi oma Google'i kalendri jagamislink või kalendri ID. Kalender peab olema Google'i kalendri seadetes avalikuks seatud.", "settings_connect_cal_how": "Kuidas saada kalendri linki:", "settings_connect_cal_step1": "Ava Google'i kalender", - "settings_connect_cal_step2": "Kliki kalendri kõrval 3 punkti → Seaded", + "settings_connect_cal_step2": "Kliki kalendri kõrval 3 punkti → Seaded", "settings_connect_cal_step3": "\"Juurdepääsuõiguste\" all märgi \"Tee avalikuks\"", "settings_connect_cal_step4": "Keri alla \"Kalendri integreerimine\" ja kopeeri kalendri ID või avalik URL", "settings_connect_cal_input_label": "Kalendri URL või ID", @@ -730,5 +730,262 @@ "map_show_shape_labels": "Näita kujundite silte", "map_address": "Aadress", "map_address_placeholder": "nt. Tallinn, Eesti", - "map_change_address": "Muuda aadressi" -} \ No newline at end of file + "map_change_address": "Muuda aadressi", + "map_rename": "Nimeta ümber", + "map_duplicate": "Dubleeri", + "map_or": "või", + "map_load": "Laadi", + "map_go": "Mine", + "event_nav_finances": "Rahandus", + "event_nav_sponsors": "Sponsorid", + "event_nav_contacts": "Kontaktid", + "event_nav_files": "Failid", + "event_nav_departments": "Valdkonnad", + "module_kanban_label": "Kanban", + "module_kanban_desc": "Ülesannete tahvlid veergude ja kaartidega töö jälgimiseks", + "module_files_label": "Failid", + "module_files_desc": "Jagatud dokumendid, kaustad ja failihoidla", + "module_checklist_label": "Kontrollnimekiri", + "module_checklist_desc": "Lihtsad ülesannete nimekirjad edenemise jälgimisega", + "module_notes_label": "Märkmed", + "module_notes_desc": "Vabas vormis märkmed ja dokumentatsioon", + "module_schedule_label": "Ajakava", + "module_schedule_desc": "Ajajoon ja ajakava lavade ja sessioonide jaoks", + "module_contacts_label": "Kontaktid", + "module_contacts_desc": "Tarnijate ja kontaktide kataloog kategooriatega", + "module_budget_label": "Eelarve", + "module_budget_desc": "Tulude ja kulude jälgimine planeeritud vs tegelik", + "module_sponsors_label": "Sponsorid", + "module_sponsors_desc": "Sponsorite haldus tasemete, kohustuste ja müügitoruga", + "module_map_label": "Kaart", + "module_map_desc": "Interaktiivne koha kaart markerite ja aladega", + "contact_cat_general": "Üldine", + "contact_cat_vendor": "Tarnija", + "contact_cat_sponsor": "Sponsor", + "contact_cat_speaker": "Esineja", + "contact_cat_venue": "Toimumiskoht", + "contact_cat_catering": "Toitlustus", + "contact_cat_av_tech": "AV / Tehnika", + "contact_cat_transport": "Transport", + "contact_cat_security": "Turvalisus", + "contact_cat_media": "Meedia", + "contacts_all_categories": "Kõik kategooriad", + "sponsor_status_prospect": "Potentsiaalne", + "sponsor_status_contacted": "Kontakteeritud", + "sponsor_status_confirmed": "Kinnitatud", + "sponsor_status_declined": "Keeldunud", + "sponsor_status_active": "Aktiivne", + "sponsors_all_statuses": "Kõik staatused", + "sponsors_all_tiers": "Kõik tasemed", + "sponsors_no_tier_filter": "Tase puudub", + "accent_blue": "Sinine (vaikimisi)", + "accent_green": "Roheline", + "accent_red": "Punane", + "accent_amber": "Merevaik", + "accent_purple": "Lilla", + "accent_pink": "Roosa", + "accent_indigo": "Indigo", + "accent_teal": "Sinakasroheline", + "role_color_red": "Punane", + "role_color_amber": "Merevaik", + "role_color_emerald": "Smaragd", + "role_color_blue": "Sinine", + "role_color_indigo": "Indigo", + "role_color_violet": "Violetne", + "role_color_pink": "Roosa", + "role_color_gray": "Hall", + "kanban_import_json": "Impordi JSON", + "kanban_export_json": "Ekspordi JSON", + "kanban_rename_from_list": "Nimeta ümber dokumentide nimekirjast.", + "admin_tab_overview": "Ülevaade", + "admin_tab_organizations": "Organisatsioonid", + "admin_tab_users": "Kasutajad", + "admin_tab_events": "Üritused", + "card_add_title": "Lisa kaart", + "card_details_title": "Kaardi üksikasjad", + "card_title_label": "Pealkiri", + "card_title_placeholder": "Kaardi pealkiri", + "card_description_label": "Kirjeldus", + "card_description_placeholder": "Lisa üksikasjalikum kirjeldus...", + "card_tags": "Sildid", + "card_tags_done": "Valmis", + "card_tags_manage": "Halda", + "card_tags_new_placeholder": "Uue sildi nimi...", + "card_tags_new_short": "Uus silt...", + "card_tags_add": "+ Lisa silt", + "card_tags_all_added": "Kõik sildid lisatud", + "card_assignee": "Vastutaja", + "card_due_date": "Tähtaeg", + "card_priority": "Prioriteet", + "card_priority_low": "Madal", + "card_priority_medium": "Keskmine", + "card_priority_high": "Kõrge", + "card_priority_urgent": "Kiire", + "card_checklist": "Kontrollnimekiri", + "card_checklist_add_placeholder": "Lisa element...", + "card_checklist_add_item_placeholder": "Lisa kontrollnimekirja element...", + "card_comments": "Kommentaarid", + "card_comments_none": "Kommentaare pole veel", + "card_comments_add_placeholder": "Lisa kommentaar...", + "card_comments_unknown": "Tundmatu", + "card_delete": "Kustuta kaart", + "card_save_changes": "Salvesta muudatused", + "card_loading": "Laadimine...", + "kanban_add_col": "Lisa veerg", + "kanban_confirm_delete_column": "Kustuta see veerg ja kõik selle kaardid?", + "kanban_confirm_delete_card": "Kustuta see kaart?", + "kanban_toast_changes_saved": "Muudatused salvestatud", + "kanban_toast_save_failed": "Muudatuste salvestamine ebaõnnestus", + "kanban_toast_card_created": "Kaart loodud", + "kanban_toast_create_failed": "Kaardi loomine ebaõnnestus", + "tasks_no_columns": "Ülesannete veerge pole veel", + "tasks_add_column": "Lisa veerg", + "tasks_add_column_title": "Lisa veerg", + "tasks_column_name": "Veeru nimi", + "tasks_column_placeholder": "nt Ülevaatamisel", + "tasks_add_task_title": "Lisa ülesanne", + "tasks_task_title_label": "Ülesande pealkiri", + "tasks_task_placeholder": "Mida on vaja teha?", + "tasks_add_task_btn": "Lisa ülesanne", + "chat_confirm_delete_message": "Kustuta see sõnum?", + "chat_toast_message_deleted": "Sõnum kustutatud", + "btn_send": "Saada", + "btn_add": "Lisa", + "settings_confirm_disconnect_cal": "Katkesta Google Calendar ühendus?", + "kanban_confirm_delete_tag": "Kustuta see silt organisatsioonist?", + "tasks_toast_create_column_failed": "Veeru loomine ebaõnnestus", + "tasks_toast_delete_column_failed": "Veeru kustutamine ebaõnnestus", + "tasks_toast_rename_column_failed": "Veeru ümbernimetamine ebaõnnestus", + "tasks_toast_create_task_failed": "Ülesande loomine ebaõnnestus", + "tasks_toast_delete_task_failed": "Ülesande kustutamine ebaõnnestus", + "toast_success_sponsor_updated": "Sponsor uuendatud", + "toast_success_sponsor_added": "Sponsor lisatud", + "toast_error_save_sponsor": "Sponsori salvestamine ebaõnnestus", + "toast_success_sponsor_removed": "Sponsor eemaldatud", + "toast_success_contact_updated": "Kontakt uuendatud", + "toast_success_contact_added": "Kontakt lisatud", + "toast_error_save_contact": "Kontakti salvestamine ebaõnnestus", + "toast_success_contact_removed": "Kontakt eemaldatud", + "toast_error_update_planned_budget": "Planeeritud eelarve uuendamine ebaõnnestus", + "toast_error_save_allocation": "Jaotuse salvestamine ebaõnnestus", + "toast_error_delete_allocation": "Jaotuse kustutamine ebaõnnestus", + "toast_error_no_google_avatar": "Google avatari ei leitud.", + "toast_error_sync_avatar": "Avatari sünkroonimine ebaõnnestus.", + "toast_success_sync_avatar": "Google avatar sünkroonitud.", + "toast_error_select_image": "Palun vali pildifail.", + "toast_error_image_too_large": "Pilt peab olema alla 2MB.", + "toast_error_upload_avatar": "Avatari üleslaadimine ebaõnnestus.", + "toast_error_save_avatar": "Avatari salvestamine ebaõnnestus.", + "toast_success_avatar_updated": "Avatar uuendatud.", + "toast_error_avatar_upload": "Avatari üleslaadimine ebaõnnestus.", + "toast_error_remove_avatar": "Avatari eemaldamine ebaõnnestus.", + "toast_success_avatar_removed": "Avatar eemaldatud.", + "toast_error_save_profile": "Profiili salvestamine ebaõnnestus.", + "toast_success_profile_saved": "Profiil salvestatud.", + "toast_error_save_preferences": "Eelistuste salvestamine ebaõnnestus.", + "toast_success_preferences_saved": "Eelistused salvestatud.", + "toast_error_save_settings": "Seadete salvestamine ebaõnnestus.", + "toast_success_settings_saved": "Seaded salvestatud.", + "toast_error_save_slug": "Lühinime uuendamine ebaõnnestus.", + "toast_success_slug_saved": "Lühinimi uuendatud.", + "toast_error_save_links": "Linkide salvestamine ebaõnnestus.", + "toast_success_links_saved": "Lingid salvestatud.", + "toast_error_create_tag": "Sildi loomine ebaõnnestus.", + "toast_success_tag_created": "Silt loodud.", + "toast_error_delete_tag": "Sildi kustutamine ebaõnnestus.", + "toast_error_update_tag": "Sildi uuendamine ebaõnnestus.", + "toast_error_create_role": "Rolli loomine ebaõnnestus.", + "toast_error_delete_role_system": "Süsteemirolle ei saa kustutada.", + "toast_error_create_document": "Dokumendi loomine ebaõnnestus.", + "toast_error_create_folder": "Kausta loomine ebaõnnestus.", + "toast_error_rename_document": "Dokumendi ümbernimetamine ebaõnnestus.", + "toast_error_delete_document": "Dokumendi kustutamine ebaõnnestus.", + "toast_error_move_document": "Dokumendi teisaldamine ebaõnnestus.", + "toast_error_upload_file": "Faili üleslaadimine ebaõnnestus.", + "toast_success_document_created": "Dokument loodud.", + "toast_success_folder_created": "Kaust loodud.", + "toast_error_import_json": "Tahvli importimine ebaõnnestus.", + "toast_success_import_json": "Tahvel edukalt imporditud.", + "toast_error_export_json": "Tahvli eksportimine ebaõnnestus.", + "toast_success_export_json": "Tahvel eksporditud.", + "toast_error_save_avatar_url": "Avatari URL-i salvestamine ebaõnnestus.", + "toast_error_save_event_defaults": "Ürituse vaikeväärtuste salvestamine ebaõnnestus.", + "toast_success_event_defaults_saved": "Ürituse vaikeväärtused salvestatud.", + "toast_error_save_features": "Funktsioonide seadete salvestamine ebaõnnestus.", + "toast_success_features_saved": "Funktsioonide seaded salvestatud.", + "toast_error_save_social": "Sotsiaalmeedia linkide salvestamine ebaõnnestus.", + "toast_success_social_saved": "Sotsiaalmeedia lingid salvestatud.", + "toast_error_save_document": "Dokumendi salvestamine ebaõnnestus", + "toast_error_load_kanban": "Kanban tahvli laadimine ebaõnnestus", + "toast_error_move_card": "Kaardi teisaldamine ebaõnnestus", + "toast_error_no_columns_import": "Veerge pole, kuhu kaarte importida", + "toast_error_import_json_format": "JSON importimine ebaõnnestus - kontrolli faili formaati", + "toast_success_board_exported": "Tahvel eksporditud JSON-ina", + "toast_error_move_shape": "Kujundi teisaldamine ebaõnnestus", + "toast_error_create_shape": "Kujundi loomine ebaõnnestus", + "toast_error_move_pin": "Nõela teisaldamine ebaõnnestus", + "toast_error_update_pin": "Nõela uuendamine ebaõnnestus", + "toast_error_create_pin": "Nõela loomine ebaõnnestus", + "toast_error_delete_pin": "Nõela kustutamine ebaõnnestus", + "toast_error_update_shape": "Kujundi uuendamine ebaõnnestus", + "toast_error_delete_shape": "Kujundi kustutamine ebaõnnestus", + "toast_error_duplicate_shape": "Kujundi dubleerimine ebaõnnestus", + "toast_error_create_layer": "Kihi loomine ebaõnnestus", + "toast_error_delete_layer": "Kihi kustutamine ebaõnnestus", + "toast_error_rename_layer": "Kihi ümbernimetamine ebaõnnestus", + "toast_error_reorder_objects": "Objektide järjestamine ebaõnnestus", + "toast_error_load_image": "Pildi laadimine ebaõnnestus", + "toast_error_upload_image": "Pildi üleslaadimine ebaõnnestus", + "toast_error_load_image_url": "Pildi laadimine URL-ilt ebaõnnestus", + "toast_error_export_map": "Kaardi eksportimine ebaõnnestus", + "toast_error_create_board_widget": "Tahvli loomine ebaõnnestus", + "toast_success_message_edited": "Sõnum muudetud", + "toast_error_file_too_large": "Fail on liiga suur. Maksimaalne suurus on 50MB.", + "toast_success_file_sent": "Fail saadetud!", + "toast_error_create_room": "Ruumi loomine ebaõnnestus", + "toast_success_room_created": "Ruum loodud", + "toast_error_create_space": "Ruumi loomine ebaõnnestus", + "toast_success_space_created": "Ruum loodud", + "toast_error_send_file": "Faili saatmine ebaõnnestus", + "toast_error_upload_failed": "Üleslaadimine ebaõnnestus", + "toast_error_update_room": "Ruumi seadete uuendamine ebaõnnestus", + "toast_success_room_updated": "Ruumi seaded uuendatud", + "toast_error_leave_room": "Ruumist lahkumine ebaõnnestus", + "toast_error_enter_room_name": "Palun sisesta ruumi nimi", + "toast_error_enter_space_name": "Palun sisesta ruumi nimi", + "toast_error_notification_settings": "Teavituste seadete muutmine ebaõnnestus", + "toast_success_board_created": "Kanban tahvel loodud", + "login_name_label": "Kuvatav nimi", + "login_name_placeholder": "Sinu täisnimi", + "login_name_required": "Palun sisesta oma nimi", + "invite_title": "Kutse", + "invite_invalid_title": "Vigane kutse", + "invite_go_home": "Avalehele", + "invite_youre_invited": "Oled kutsutud!", + "invite_join_text": "Sind on kutsutud liituma", + "invite_as_role": "kui", + "invite_signed_in_as": "Sisse logitud kui", + "invite_accept_btn": "Nõustu ja liitu", + "invite_wrong_account": "Vale konto?", + "invite_sign_out": "Logi välja", + "invite_not_logged_in": "Logi sisse või loo konto, et kutse vastu võtta.", + "invite_sign_in": "Logi sisse", + "invite_create_account": "Loo konto", + "invite_email_mismatch": "See kutse saadeti aadressile {email}. Palun logi sisse selle e-posti aadressiga.", + "invite_already_member": "Oled juba selle organisatsiooni liige.", + "invite_join_failed": "Organisatsiooniga liitumine ebaõnnestus. Palun proovi uuesti.", + "invite_generic_error": "Midagi läks valesti. Palun proovi uuesti.", + "onboarding_title": "Täida oma profiil", + "onboarding_subtitle": "Aita oma meeskonnal sind tundma õppida", + "onboarding_phone_label": "Telefoninumber", + "onboarding_phone_placeholder": "+372 5xx xxxx", + "onboarding_discord_label": "Discordi kasutajanimi", + "onboarding_discord_placeholder": "kasutajanimi#1234", + "onboarding_shirt_label": "Särgi suurus", + "onboarding_hoodie_label": "Pusa suurus", + "onboarding_save": "Salvesta ja jätka", + "onboarding_skip": "Jäta vahele", + "invite_email_subject": "{orgName} — Oled kutsutud liituma", + "invite_email_sent": "Kutse e-kiri saadetud aadressile {email}", + "toast_error_send_invite_email": "Kutse e-kirja saatmine ebaõnnestus" +} diff --git a/policy_dump.sql b/policy_dump.sql new file mode 100644 index 0000000..e69de29 diff --git a/policy_dump.txt b/policy_dump.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/lib/components/documents/FileBrowser.svelte b/src/lib/components/documents/FileBrowser.svelte index 25286a5..f5393c8 100644 --- a/src/lib/components/documents/FileBrowser.svelte +++ b/src/lib/components/documents/FileBrowser.svelte @@ -217,7 +217,7 @@ documents = [...documents, newDoc]; toasts.success(`Copied "${doc.name}"`); } catch { - toasts.error("Failed to copy document"); + toasts.error(m.toast_error_create_document()); } } @@ -282,7 +282,7 @@ : `Uploaded ${fileArr.length} files`, ); } catch { - toasts.error("Failed to upload file"); + toasts.error(m.toast_error_upload_file()); } finally { fileUploading = false; fileUploadProgress = ""; @@ -415,7 +415,7 @@ try { await moveDocument(supabase, docId, newParentId); } catch { - toasts.error("Failed to move file"); + toasts.error(m.toast_error_move_document()); const { data: freshDocs } = await supabase .from("documents") .select( @@ -439,7 +439,7 @@ .select() .single(); if (boardError || !newBoard) { - toasts.error("Failed to create kanban board"); + toasts.error(m.toast_error_create_document()); return; } await supabase.from("kanban_columns").insert([ @@ -494,7 +494,7 @@ } } } catch { - toasts.error("Failed to create document"); + toasts.error(m.toast_error_create_document()); } showCreateModal = false; @@ -510,7 +510,7 @@ d.id === selectedDoc!.id ? { ...d, content } : d, ); } catch { - toasts.error("Failed to save document"); + toasts.error(m.toast_error_create_document()); } } @@ -536,7 +536,7 @@ selectedDoc = { ...selectedDoc, name: newDocName }; } } catch { - toasts.error("Failed to rename document"); + toasts.error(m.toast_error_rename_document()); } showEditModal = false; editingDoc = null; @@ -594,7 +594,7 @@ selectedDoc = null; } } catch { - toasts.error("Failed to delete document"); + toasts.error(m.toast_error_delete_document()); } } diff --git a/src/lib/components/kanban/CardChecklist.svelte b/src/lib/components/kanban/CardChecklist.svelte index 4f0cb8c..b725f6b 100644 --- a/src/lib/components/kanban/CardChecklist.svelte +++ b/src/lib/components/kanban/CardChecklist.svelte @@ -1,6 +1,7 @@ @@ -480,8 +480,8 @@ }, ); if (error) - toasts.error("Failed to send reset email."); - else toasts.success("Password reset email sent."); + toasts.error(m.toast_error_reset_email()); + else toasts.success(m.toast_success_reset_email()); }} > {m.account_send_reset()} @@ -502,7 +502,7 @@ size="sm" onclick={async () => { await supabase.auth.signOut({ scope: "others" }); - toasts.success("Other sessions signed out."); + toasts.success(m.toast_success_signout_others()); }} > {m.account_signout_others()} diff --git a/src/routes/[orgSlug]/chat/+page.svelte b/src/routes/[orgSlug]/chat/+page.svelte index ba925b2..291a2fc 100644 --- a/src/routes/[orgSlug]/chat/+page.svelte +++ b/src/routes/[orgSlug]/chat/+page.svelte @@ -308,7 +308,7 @@ try { await editMessage($selectedRoomId, editingMsg.eventId, newContent); editingMsg = null; - toasts.success("Message edited"); + toasts.success(m.toast_success_message_edited()); } catch (e: unknown) { toasts.error(getErrorMessage(e, "Failed to edit message")); } @@ -320,10 +320,10 @@ async function handleDeleteMessage(messageId: string) { if (!$selectedRoomId) return; - if (!confirm("Delete this message?")) return; + if (!confirm(m.chat_confirm_delete_message())) return; try { await deleteMessage($selectedRoomId, messageId); - toasts.success("Message deleted"); + toasts.success(m.chat_toast_message_deleted()); } catch (e: unknown) { toasts.error(getErrorMessage(e, "Failed to delete message")); } @@ -357,7 +357,7 @@ const file = files[0]; if (file.size > 50 * 1024 * 1024) { - toasts.error("File too large. Maximum size is 50MB."); + toasts.error(m.toast_error_file_too_large()); return; } @@ -366,7 +366,7 @@ toasts.info(`Uploading ${file.name}...`); const contentUri = await uploadFile(file); await sendFileMessage($selectedRoomId, file, contentUri); - toasts.success("File sent!"); + toasts.success(m.toast_success_file_sent()); } catch (e: unknown) { toasts.error(getErrorMessage(e, "Failed to upload file")); } finally { diff --git a/src/routes/[orgSlug]/documents/file/[id]/+page.svelte b/src/routes/[orgSlug]/documents/file/[id]/+page.svelte index e94e6b3..23aecf7 100644 --- a/src/routes/[orgSlug]/documents/file/[id]/+page.svelte +++ b/src/routes/[orgSlug]/documents/file/[id]/+page.svelte @@ -1,6 +1,7 @@ @@ -680,21 +681,20 @@ {} }, { - label: "Rename Board", + label: m.kanban_rename_board(), icon: "edit", - onclick: () => - toasts.info("Rename from the documents list."), + onclick: () => toasts.info(m.kanban_rename_from_list()), }, ]} /> diff --git a/src/routes/[orgSlug]/events/[eventSlug]/+layout.svelte b/src/routes/[orgSlug]/events/[eventSlug]/+layout.svelte index 92a5e3e..13498cb 100644 --- a/src/routes/[orgSlug]/events/[eventSlug]/+layout.svelte +++ b/src/routes/[orgSlug]/events/[eventSlug]/+layout.svelte @@ -35,22 +35,22 @@ }, { href: `${basePath}/finances`, - label: "Finances", + label: m.event_nav_finances(), icon: "account_balance", }, { href: `${basePath}/sponsors`, - label: "Sponsors", + label: m.event_nav_sponsors(), icon: "handshake", }, { href: `${basePath}/contacts`, - label: "Contacts", + label: m.event_nav_contacts(), icon: "contacts", }, { href: `${basePath}/files`, - label: "Files", + label: m.event_nav_files(), icon: "folder", }, { @@ -165,7 +165,7 @@

- Departments + {m.event_nav_departments()}

{#each data.eventDepartments as dept} c.id === updated.id ? updated : c, ); - toasts.success("Contact updated"); + toasts.success(m.toast_success_contact_updated()); } else { const created = await createOrgContact(supabase, data.org.id, { name: formName.trim(), @@ -123,11 +123,11 @@ notes: formNotes.trim() || undefined, }); contacts = [...contacts, created]; - toasts.success("Contact added"); + toasts.success(m.toast_success_contact_added()); } showModal = false; } catch { - toasts.error("Failed to save contact"); + toasts.error(m.toast_error_save_contact()); } finally { saving = false; } @@ -137,9 +137,9 @@ try { await deleteOrgContact(supabase, contact.id); contacts = contacts.filter((c) => c.id !== contact.id); - toasts.success("Contact removed"); + toasts.success(m.toast_success_contact_removed()); } catch { - toasts.error("Failed to delete contact"); + toasts.error(m.toast_error_delete_contact()); } } @@ -190,10 +190,10 @@ bind:value={filterCategory} placeholder="" options={[ - { value: "all", label: "All Categories" }, + { value: "all", label: m.contacts_all_categories() }, ...CONTACT_CATEGORIES.map((cat) => ({ value: cat, - label: cat.charAt(0).toUpperCase() + cat.slice(1), + label: CATEGORY_LABELS[cat] ?? cat, })), ]} /> @@ -399,7 +399,7 @@ placeholder="" options={CONTACT_CATEGORIES.map((cat) => ({ value: cat, - label: cat.charAt(0).toUpperCase() + cat.slice(1), + label: CATEGORY_LABELS[cat] ?? cat, }))} /> @@ -441,14 +441,18 @@
(showModal = false)}>{m.btn_cancel()}
diff --git a/src/routes/[orgSlug]/events/[eventSlug]/dept/[deptId]/+page.svelte b/src/routes/[orgSlug]/events/[eventSlug]/dept/[deptId]/+page.svelte index e2a0f01..50ab3d3 100644 --- a/src/routes/[orgSlug]/events/[eventSlug]/dept/[deptId]/+page.svelte +++ b/src/routes/[orgSlug]/events/[eventSlug]/dept/[deptId]/+page.svelte @@ -315,7 +315,7 @@ await updateDashboardLayout(supabase, dashboard.id, layout); dashboard = { ...dashboard, layout }; } catch { - toasts.error("Failed to update layout"); + toasts.error(m.toast_error_update_layout()); } } @@ -361,7 +361,7 @@ showAddModuleModal = false; } catch { - toasts.error("Failed to add module"); + toasts.error(m.toast_error_add_module()); } } @@ -375,7 +375,7 @@ panels: dashboard.panels.filter((p) => p.id !== panelId), }; } catch { - toasts.error("Failed to remove module"); + toasts.error(m.toast_error_remove_module()); } } @@ -411,7 +411,7 @@ } catch { // Revert on failure dashboard = { ...dashboard, panels: sorted }; - toasts.error("Failed to reorder modules"); + toasts.error(m.toast_error_reorder_modules()); } } @@ -436,7 +436,7 @@ c.id === checklistId ? { ...c, items: [...c.items, item] } : c, ); } catch { - toasts.error("Failed to add item"); + toasts.error(m.toast_error_add_item()); } } @@ -458,7 +458,7 @@ i.id === itemId ? { ...i, is_completed: !checked } : i, ), })); - toasts.error("Failed to update item"); + toasts.error(m.toast_error_update_item()); } } @@ -472,7 +472,7 @@ await deleteChecklistItem(supabase, itemId); } catch { checklists = prev; - toasts.error("Failed to delete item"); + toasts.error(m.toast_error_delete_item()); } } @@ -486,7 +486,7 @@ ), })); } catch { - toasts.error("Failed to update item"); + toasts.error(m.toast_error_update_item()); } } @@ -495,7 +495,7 @@ const cl = await createChecklist(supabase, department.id, title); checklists = [...checklists, { ...cl, items: [] }]; } catch { - toasts.error("Failed to create checklist"); + toasts.error(m.toast_error_create_checklist()); } } @@ -504,7 +504,7 @@ await deleteChecklist(supabase, checklistId); checklists = checklists.filter((c) => c.id !== checklistId); } catch { - toasts.error("Failed to delete checklist"); + toasts.error(m.toast_error_delete_checklist()); } } @@ -515,7 +515,7 @@ c.id === checklistId ? { ...c, title } : c, ); } catch { - toasts.error("Failed to rename checklist"); + toasts.error(m.toast_error_rename_checklist()); } } @@ -528,7 +528,7 @@ const note = await createNote(supabase, department.id, title); notes = [...notes, note]; } catch { - toasts.error("Failed to create note"); + toasts.error(m.toast_error_create_note()); } } @@ -540,7 +540,7 @@ const updated = await updateNote(supabase, noteId, params); notes = notes.map((n) => (n.id === noteId ? updated : n)); } catch { - toasts.error("Failed to update note"); + toasts.error(m.toast_error_update_note()); } } @@ -549,7 +549,7 @@ await deleteNote(supabase, noteId); notes = notes.filter((n) => n.id !== noteId); } catch { - toasts.error("Failed to delete note"); + toasts.error(m.toast_error_delete_note()); } } @@ -567,7 +567,7 @@ ); scheduleStages = [...scheduleStages, stage]; } catch { - toasts.error("Failed to create stage"); + toasts.error(m.toast_error_create_stage()); } } @@ -576,7 +576,7 @@ await deleteStageApi(supabase, stageId); scheduleStages = scheduleStages.filter((s) => s.id !== stageId); } catch { - toasts.error("Failed to delete stage"); + toasts.error(m.toast_error_delete_stage()); } } @@ -597,7 +597,7 @@ new Date(b.start_time).getTime(), ); } catch { - toasts.error("Failed to create schedule block"); + toasts.error(m.toast_error_create_block()); } } @@ -626,7 +626,7 @@ new Date(b.start_time).getTime(), ); } catch { - toasts.error("Failed to update schedule block"); + toasts.error(m.toast_error_update_block()); } } @@ -635,7 +635,7 @@ await deleteBlockApi(supabase, blockId); scheduleBlocks = scheduleBlocks.filter((b) => b.id !== blockId); } catch { - toasts.error("Failed to delete schedule block"); + toasts.error(m.toast_error_delete_block()); } } @@ -664,7 +664,7 @@ a.name.localeCompare(b.name), ); } catch { - toasts.error("Failed to create contact"); + toasts.error(m.toast_error_create_contact()); } } @@ -691,7 +691,7 @@ .map((c) => (c.id === contactId ? updated : c)) .sort((a, b) => a.name.localeCompare(b.name)); } catch { - toasts.error("Failed to update contact"); + toasts.error(m.toast_error_update_contact()); } } @@ -700,7 +700,7 @@ await deleteContactApi(supabase, contactId); contacts = contacts.filter((c) => c.id !== contactId); } catch { - toasts.error("Failed to delete contact"); + toasts.error(m.toast_error_delete_contact()); } } @@ -718,7 +718,7 @@ ); budgetCategories = [...budgetCategories, cat]; } catch { - toasts.error("Failed to create category"); + toasts.error(m.toast_error_create_category()); } } @@ -729,7 +729,7 @@ (c) => c.id !== categoryId, ); } catch { - toasts.error("Failed to delete category"); + toasts.error(m.toast_error_delete_category()); } } @@ -749,7 +749,7 @@ ); budgetItems = [...budgetItems, item]; } catch { - toasts.error("Failed to create budget item"); + toasts.error(m.toast_error_create_budget_item()); } } @@ -773,7 +773,7 @@ i.id === itemId ? updated : i, ); } catch { - toasts.error("Failed to update budget item"); + toasts.error(m.toast_error_update_budget_item()); } } @@ -782,7 +782,7 @@ await deleteBudgetItemApi(supabase, itemId); budgetItems = budgetItems.filter((i) => i.id !== itemId); } catch { - toasts.error("Failed to delete budget item"); + toasts.error(m.toast_error_delete_budget_item()); } } @@ -845,9 +845,11 @@ ? { ...i, receipt_document_id: doc.id } : i, ); - toasts.success(`Receipt "${file.name}" attached`); + toasts.success( + m.toast_success_receipt_attached({ name: file.name }), + ); } catch { - toasts.error("Failed to upload receipt"); + toasts.error(m.toast_error_upload_receipt()); } finally { receiptTargetItemId = null; } @@ -872,7 +874,7 @@ ); sponsorTiers = [...sponsorTiers, tier]; } catch { - toasts.error("Failed to create tier"); + toasts.error(m.toast_error_create_tier()); } } @@ -881,7 +883,7 @@ await deleteSponsorTierApi(supabase, tierId); sponsorTiers = sponsorTiers.filter((t) => t.id !== tierId); } catch { - toasts.error("Failed to delete tier"); + toasts.error(m.toast_error_delete_tier()); } } @@ -906,7 +908,7 @@ a.name.localeCompare(b.name), ); } catch { - toasts.error("Failed to create sponsor"); + toasts.error(m.toast_error_create_sponsor()); } } @@ -933,7 +935,7 @@ .map((s) => (s.id === sponsorId ? updated : s)) .sort((a, b) => a.name.localeCompare(b.name)); } catch { - toasts.error("Failed to update sponsor"); + toasts.error(m.toast_error_update_sponsor()); } } @@ -945,7 +947,7 @@ (d) => d.sponsor_id !== sponsorId, ); } catch { - toasts.error("Failed to delete sponsor"); + toasts.error(m.toast_error_delete_sponsor()); } } @@ -963,7 +965,7 @@ ); sponsorDeliverables = [...sponsorDeliverables, del]; } catch { - toasts.error("Failed to create deliverable"); + toasts.error(m.toast_error_create_deliverable()); } } @@ -983,7 +985,7 @@ sponsorDeliverables = sponsorDeliverables.map((d) => d.id === deliverableId ? { ...d, is_completed: !completed } : d, ); - toasts.error("Failed to update deliverable"); + toasts.error(m.toast_error_update_deliverable()); } } @@ -994,7 +996,7 @@ (d) => d.id !== deliverableId, ); } catch { - toasts.error("Failed to delete deliverable"); + toasts.error(m.toast_error_delete_deliverable()); } } diff --git a/src/routes/[orgSlug]/events/[eventSlug]/finances/+page.svelte b/src/routes/[orgSlug]/events/[eventSlug]/finances/+page.svelte index eb2df3d..39cb9e7 100644 --- a/src/routes/[orgSlug]/events/[eventSlug]/finances/+page.svelte +++ b/src/routes/[orgSlug]/events/[eventSlug]/finances/+page.svelte @@ -182,7 +182,7 @@ const dept = data.departments.find((d) => d.id === deptId); if (dept) (dept as any).planned_budget = val; } catch { - toasts.error("Failed to update planned budget"); + toasts.error(m.toast_error_update_planned_budget()); } editingDeptId = null; } @@ -240,7 +240,7 @@ } showAllocModal = false; } catch { - toasts.error("Failed to save allocation"); + toasts.error(m.toast_error_save_allocation()); } } @@ -251,7 +251,7 @@ (a) => a.id !== allocId, ); } catch { - toasts.error("Failed to delete allocation"); + toasts.error(m.toast_error_delete_allocation()); } } diff --git a/src/routes/[orgSlug]/events/[eventSlug]/sponsors/+page.svelte b/src/routes/[orgSlug]/events/[eventSlug]/sponsors/+page.svelte index 79e7319..23a2d10 100644 --- a/src/routes/[orgSlug]/events/[eventSlug]/sponsors/+page.svelte +++ b/src/routes/[orgSlug]/events/[eventSlug]/sponsors/+page.svelte @@ -154,7 +154,7 @@ sponsors = sponsors.map((s) => s.id === updated.id ? updated : s, ); - toasts.success("Sponsor updated"); + toasts.success(m.toast_success_sponsor_updated()); } else { const created = await createSponsor(supabase, formDeptId, { name: formName.trim(), @@ -168,11 +168,11 @@ notes: formNotes.trim() || undefined, }); sponsors = [...sponsors, created]; - toasts.success("Sponsor added"); + toasts.success(m.toast_success_sponsor_added()); } showModal = false; } catch { - toasts.error("Failed to save sponsor"); + toasts.error(m.toast_error_save_sponsor()); } finally { saving = false; } @@ -182,9 +182,9 @@ try { await deleteSponsor(supabase, sponsor.id); sponsors = sponsors.filter((s) => s.id !== sponsor.id); - toasts.success("Sponsor removed"); + toasts.success(m.toast_success_sponsor_removed()); } catch { - toasts.error("Failed to delete sponsor"); + toasts.error(m.toast_error_delete_sponsor()); } } @@ -271,7 +271,7 @@ bind:value={filterStatus} placeholder="" options={[ - { value: "all", label: "All Statuses" }, + { value: "all", label: m.sponsors_all_statuses() }, ...Object.entries(STATUS_LABELS).map(([val, label]) => ({ value: val, label, @@ -284,7 +284,7 @@ bind:value={filterTier} placeholder="" options={[ - { value: "all", label: "All Tiers" }, + { value: "all", label: m.sponsors_all_tiers() }, ...tiers.map((t) => ({ value: t.id, label: t.name })), ]} /> @@ -507,14 +507,18 @@
(showModal = false)}>{m.btn_cancel()}
diff --git a/src/routes/[orgSlug]/events/[eventSlug]/tasks/+page.svelte b/src/routes/[orgSlug]/events/[eventSlug]/tasks/+page.svelte index 03c28fe..b9a002f 100644 --- a/src/routes/[orgSlug]/events/[eventSlug]/tasks/+page.svelte +++ b/src/routes/[orgSlug]/events/[eventSlug]/tasks/+page.svelte @@ -284,7 +284,7 @@ await reloadColumns(); } catch (e) { log.error("Failed to create column", { error: e }); - toasts.error("Failed to create column"); + toasts.error(m.tasks_toast_create_column_failed()); } } @@ -294,7 +294,7 @@ taskColumns = taskColumns.filter((c) => c.id !== columnId); } catch (e) { log.error("Failed to delete column", { error: e }); - toasts.error("Failed to delete column"); + toasts.error(m.tasks_toast_delete_column_failed()); } } @@ -306,7 +306,7 @@ ); } catch (e) { log.error("Failed to rename column", { error: e }); - toasts.error("Failed to rename column"); + toasts.error(m.tasks_toast_rename_column_failed()); } } @@ -333,7 +333,7 @@ await reloadColumns(); } catch (e) { log.error("Failed to create task", { error: e }); - toasts.error("Failed to create task"); + toasts.error(m.tasks_toast_create_task_failed()); } } @@ -346,7 +346,7 @@ })); } catch (e) { log.error("Failed to delete task", { error: e }); - toasts.error("Failed to delete task"); + toasts.error(m.tasks_toast_delete_task_failed()); } } @@ -384,14 +384,14 @@ class="material-symbols-rounded mb-4 block text-[48px] leading-none" >task_alt -

No task columns yet

+

{m.tasks_no_columns()}

{#if canEdit} {/if} @@ -403,7 +403,7 @@ (showAddColumnModal = false)} >
(showAddColumnModal = false)} + >{m.btn_cancel()} {m.btn_create()}
@@ -433,7 +434,7 @@ (showAddCardModal = false)} >
(showAddCardModal = false)} + >{m.btn_cancel()} {m.tasks_add_task_btn()}
diff --git a/src/routes/[orgSlug]/events/[eventSlug]/team/+page.svelte b/src/routes/[orgSlug]/events/[eventSlug]/team/+page.svelte index af5f359..8f52c55 100644 --- a/src/routes/[orgSlug]/events/[eventSlug]/team/+page.svelte +++ b/src/routes/[orgSlug]/events/[eventSlug]/team/+page.svelte @@ -151,66 +151,66 @@ const allModules = [ { id: "kanban", - label: "Kanban", + label: m.module_kanban_label(), icon: "view_kanban", color: "#6366f1", - desc: "Task boards with columns and cards for tracking work", + desc: m.module_kanban_desc(), }, { id: "files", - label: "Files", + label: m.module_files_label(), icon: "folder", color: "#F59E0B", - desc: "Shared documents, folders, and file storage", + desc: m.module_files_desc(), }, { id: "checklist", - label: "Checklist", + label: m.module_checklist_label(), icon: "checklist", color: "#10B981", - desc: "Simple to-do lists with progress tracking", + desc: m.module_checklist_desc(), }, { id: "notes", - label: "Notes", + label: m.module_notes_label(), icon: "description", color: "#8B5CF6", - desc: "Freeform notes and documentation", + desc: m.module_notes_desc(), }, { id: "schedule", - label: "Schedule", + label: m.module_schedule_label(), icon: "calendar_today", color: "#EC4899", - desc: "Timeline and timetable for stages and sessions", + desc: m.module_schedule_desc(), }, { id: "contacts", - label: "Contacts", + label: m.module_contacts_label(), icon: "contacts", color: "#00A3E0", - desc: "Vendor and contact directory with categories", + desc: m.module_contacts_desc(), }, { id: "budget", - label: "Budget", + label: m.module_budget_label(), icon: "account_balance", color: "#10B981", - desc: "Income and expense tracking with planned vs actual", + desc: m.module_budget_desc(), }, { id: "sponsors", - label: "Sponsors", + label: m.module_sponsors_label(), icon: "handshake", color: "#F59E0B", - desc: "Sponsor CRM with tiers, deliverables, and pipeline", + desc: m.module_sponsors_desc(), }, { id: "map", - label: "Map", + label: m.module_map_label(), icon: "map", color: "#EF4444", - desc: "Interactive venue map with pins and areas", + desc: m.module_map_desc(), }, ] as const; diff --git a/src/routes/[orgSlug]/kanban/+page.svelte b/src/routes/[orgSlug]/kanban/+page.svelte index 1a56e6a..35f4c68 100644 --- a/src/routes/[orgSlug]/kanban/+page.svelte +++ b/src/routes/[orgSlug]/kanban/+page.svelte @@ -394,7 +394,7 @@ async function handleDeleteColumn(columnId: string) { if (!selectedBoard) return; - if (!confirm("Delete this column and all its cards?")) return; + if (!confirm(m.kanban_confirm_delete_column())) return; const { error } = await supabase .from("kanban_columns") diff --git a/src/routes/[orgSlug]/settings/+page.svelte b/src/routes/[orgSlug]/settings/+page.svelte index 5cb063a..a8427f1 100644 --- a/src/routes/[orgSlug]/settings/+page.svelte +++ b/src/routes/[orgSlug]/settings/+page.svelte @@ -314,6 +314,7 @@ import { enhance } from "$app/forms"; import { invalidateAll } from "$app/navigation"; + import * as m from "$lib/paraglide/messages"; import { Button, Badge, @@ -222,14 +223,18 @@ (activeTab = v)} diff --git a/src/routes/api/send-invite-email/+server.ts b/src/routes/api/send-invite-email/+server.ts new file mode 100644 index 0000000..cf68f8b --- /dev/null +++ b/src/routes/api/send-invite-email/+server.ts @@ -0,0 +1,112 @@ +import { json } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; +import type { RequestHandler } from './$types'; +import { createLogger } from '$lib/utils/logger'; + +const log = createLogger('api:send-invite-email'); + +export const POST: RequestHandler = async ({ request, locals }) => { + const session = await locals.safeGetSession(); + if (!session.user) { + return json({ error: 'Unauthorized' }, { status: 401 }); + } + + if (!env.RESEND_API_KEY) { + log.warn('RESEND_API_KEY not configured, skipping email send'); + return json({ error: 'Email sending not configured' }, { status: 501 }); + } + + const { email, orgName, inviteUrl, role } = await request.json(); + + if (!email || !orgName || !inviteUrl) { + return json({ error: 'email, orgName, and inviteUrl are required' }, { status: 400 }); + } + + try { + const res = await fetch('https://api.resend.com/emails', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${env.RESEND_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + from: `${orgName} `, + to: [email], + subject: `${orgName} — You're invited to join`, + html: buildInviteEmailHtml(orgName, role, inviteUrl), + }), + }); + + if (!res.ok) { + const err = await res.json().catch(() => ({})); + log.error('Resend API error', { error: err, data: { email, orgName } }); + return json({ error: err.message || 'Failed to send email' }, { status: 500 }); + } + + const data = await res.json(); + return json({ id: data.id, sent: true }); + } catch (e) { + log.error('Failed to send invite email', { error: e }); + return json({ error: 'Failed to send email' }, { status: 500 }); + } +}; + +function buildInviteEmailHtml(orgName: string, role: string, inviteUrl: string): string { + return ` + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ 👥 +
+
+

You're Invited!

+
+

You've been invited to join

+
+

${orgName}

+
+

as ${role}

+
+ + Accept Invitation + +
+

This invite expires in 7 days.

+
+
+ +`; +} diff --git a/src/routes/invite/[token]/+page.svelte b/src/routes/invite/[token]/+page.svelte index b0c5d8f..e381a78 100644 --- a/src/routes/invite/[token]/+page.svelte +++ b/src/routes/invite/[token]/+page.svelte @@ -1,9 +1,10 @@