de431386ea
Messages: - Reply-to: hover action, composer chip with cancel, quote bubble inside the replying message with tap-to-jump + amber highlight ring - Search: header search button toggles in-conversation search bar with prev/next + match counter, auto-jump to active match - Forward: multi-select conversation picker. Attachments are now carried over: download + decrypt source, re-encrypt under each target conv-key, re-upload with fresh per-attachment keys, insert new attachment rows Conversations: - Archive + mute per member. New migration 20260420000001 adds `archived` + `muted_until` on conversation_members. Shared helpers: setConversationArchived / setConversationMutedUntil / isConversationMuted - ChatsPage: archive toggle in header with unread badge for archived bucket, split active/archived lists, muted indicator (BellOff icon, dimmed unread badge) - ConversationRowMenu via createPortal (escapes sidebar overflow clip), forwardRef-based MenuItem so submenu positioning refs survive React 18 - ConversationsContext: suppresses notification sound + OS notif when target conversation is muted - Refresh on `profiles UPDATE` realtime so peer avatar / displayName changes flow to conversation.members without manual refresh Resilience: - ErrorBoundary (Discord-style): centred spinner + escalating copy, no manual reload button. Backoff retry schedule [2s, 4s, 8s, 15s, 30s]. Uses a keyed Fragment (not a div wrapper) so flex h-full chains survive - App wrapped root + per-route RouteBoundary, conversation-level boundary - AuthContext: flip `ready` immediately on cached session read; validate getUser in background so a stalled/offline Supabase doesn't freeze the app on the loading spinner Crypto: - Swap libsodium-wrappers -> libsodium-wrappers-sumo (compact build was missing crypto_pwhash so Argon2id vault KDF threw, falling back to plaintext localStorage on every launch) - Shim d.ts for sumo types (sumo is API superset, no official types ship) - vite optimizeDeps includes sumo with the "require" condition - secureFileStore: exists(dir) check before mkdir; surface genuine permission errors instead of silent catch Tauri: - fs scope adds `$APPLOCALDATA` direct entry (not just /**) so the app data directory itself can be mkdir'd on first launch Chat layout: - Skip call_event messages when computing avatar run boundaries so a regular bubble followed by a call event from the same sender still shows its avatar
206 lines
7.5 KiB
JSON
206 lines
7.5 KiB
JSON
{
|
|
"nav": {
|
|
"chats": "Chats",
|
|
"friends": "Friends",
|
|
"settings": "Settings",
|
|
"admin": "Admin"
|
|
},
|
|
"sidebar": {
|
|
"search_placeholder": "Search…",
|
|
"new_chat": "New chat",
|
|
"sign_out": "Sign out"
|
|
},
|
|
"presence": {
|
|
"online": "Online",
|
|
"idle": "Idle",
|
|
"dnd": "Do not disturb",
|
|
"invisible": "Invisible",
|
|
"offline": "Offline"
|
|
},
|
|
"chats": {
|
|
"empty_title": "No conversations yet",
|
|
"empty_subtitle": "Start a chat from the Friends tab.",
|
|
"select_prompt": "Select a conversation",
|
|
"select_subtitle": "Pick a chat from the list, or start a new one.",
|
|
"deleted": "(deleted)",
|
|
"edited": "edited",
|
|
"seen": "Seen",
|
|
"typing_one": "{{name}} is typing…",
|
|
"typing_many": "{{count}} people are typing…",
|
|
"new_chat": "New chat",
|
|
"new_group": "New group",
|
|
"call_outgoing": "Outgoing call",
|
|
"call_incoming": "Incoming call",
|
|
"call_missed": "Missed call",
|
|
"call_no_answer": "No answer",
|
|
"call_declined": "Call declined",
|
|
"you": "You",
|
|
"attachment": "Attachment",
|
|
"reply": "Reply",
|
|
"forward": "Forward",
|
|
"replying_to": "Replying to {{name}}",
|
|
"quote_unavailable": "Message not available",
|
|
"search_in_conv": "Search in conversation…",
|
|
"search_none": "No matches",
|
|
"forward_preview": "Preview",
|
|
"forward_attachments_dropped": "Attachments are not forwarded.",
|
|
"forward_no_targets": "No other conversations available.",
|
|
"forward_done": "Sent",
|
|
"forward_send": "Send to {{count}}",
|
|
"forward_attachments_count_one": "{{count}} attachment forwarded",
|
|
"forward_attachments_count_other": "{{count}} attachments forwarded",
|
|
"archive": "Archive",
|
|
"unarchive": "Unarchive",
|
|
"archived_title": "Archive",
|
|
"show_archived": "Show archive",
|
|
"show_active": "Show active",
|
|
"archived_empty_title": "Nothing archived",
|
|
"archived_empty_subtitle": "Archived conversations appear here.",
|
|
"mute": "Mute",
|
|
"unmute": "Unmute",
|
|
"mute_1h": "1 hour",
|
|
"mute_8h": "8 hours",
|
|
"mute_24h": "24 hours",
|
|
"mute_1w": "1 week",
|
|
"mute_forever": "Until further notice",
|
|
"row_menu": "Actions"
|
|
},
|
|
"call": {
|
|
"start_audio": "Voice call",
|
|
"incoming_title": "Incoming call",
|
|
"incoming_from": "{{name}} is calling",
|
|
"incoming_group_from": "{{name}} is calling the group",
|
|
"outgoing_ringing": "Calling…",
|
|
"connecting": "Connecting…",
|
|
"connected": "In call",
|
|
"accept": "Accept",
|
|
"decline": "Decline",
|
|
"hangup": "Hang up",
|
|
"mute": "Mute",
|
|
"unmute": "Unmute",
|
|
"busy": "Busy — already in a call",
|
|
"active_in_conv": "Active call · {{count}} in room",
|
|
"join": "Join",
|
|
"in_call": "In call",
|
|
"waiting_for_peers": "Waiting for others…",
|
|
"voice_connected": "Voice connected",
|
|
"still_live": "Call still live",
|
|
"share_screen": "Share screen",
|
|
"stop_share_screen": "Stop sharing",
|
|
"is_sharing_screen": "{{name}} is sharing their screen",
|
|
"watch_screen": "Watch screen",
|
|
"stop_watching": "Stop watching",
|
|
"fullscreen": "Fullscreen",
|
|
"e2ee_active_hint": "Audio + video are end-to-end encrypted"
|
|
},
|
|
"group": {
|
|
"create_title": "New group",
|
|
"create_name_label": "Group name",
|
|
"create_name_placeholder": "Team chat",
|
|
"create_members_label": "Add friends",
|
|
"create_members_empty": "No friends yet — add some first.",
|
|
"create_cta": "Create group",
|
|
"create_cta_loading": "Creating…",
|
|
"info_title": "Group info",
|
|
"info_members": "Members",
|
|
"info_role_admin": "Admin",
|
|
"info_role_mod": "Mod",
|
|
"info_role_member": "Member",
|
|
"info_add_title": "Add members",
|
|
"info_add_empty": "All your friends are already in this group.",
|
|
"info_add_help": "Pick a friend to add them directly.",
|
|
"info_leave": "Leave group",
|
|
"info_leave_confirm": "Leave this group?"
|
|
},
|
|
"friends": {
|
|
"title": "Friends",
|
|
"search_placeholder": "Search by username…",
|
|
"search_min_chars": "Type at least 2 characters.",
|
|
"search_no_results": "No users matched.",
|
|
"search_results_title": "Search results",
|
|
"send_request": "Send request",
|
|
"request_sent": "Request sent",
|
|
"already_friends": "Already friends",
|
|
"incoming_request": "Wants to be friends",
|
|
"tab_friends": "Friends",
|
|
"tab_pending": "Pending",
|
|
"tab_requests": "Requests",
|
|
"empty_friends": "No friends yet.",
|
|
"empty_pending": "No outgoing requests.",
|
|
"empty_requests": "No incoming requests.",
|
|
"action_message": "Message",
|
|
"action_unfriend": "Unfriend",
|
|
"action_accept": "Accept",
|
|
"action_decline": "Decline",
|
|
"action_cancel": "Cancel",
|
|
"confirm_unfriend": "Remove this friend?"
|
|
},
|
|
"admin": {
|
|
"nav": "Admin",
|
|
"title": "Admin panel",
|
|
"settings_title": "Global settings",
|
|
"invites_enabled": "Allow new signups",
|
|
"invites_enabled_hint": "When off, no new users can sign up even with a valid invite.",
|
|
"invites_title": "Invite codes",
|
|
"invites_create": "New code",
|
|
"invites_empty": "No invites yet.",
|
|
"invites_disable": "Disable",
|
|
"invites_enable": "Enable",
|
|
"invites_delete": "Delete",
|
|
"invites_copy": "Copy",
|
|
"invites_copied": "Copied",
|
|
"users_title": "Users",
|
|
"users_empty": "No profiles yet.",
|
|
"users_flag_admin": "Admin",
|
|
"users_flag_banned": "Banned",
|
|
"users_flag_blocked_inviting": "Blocked from inviting",
|
|
"invite_col_code": "Code",
|
|
"invite_col_uses": "Uses",
|
|
"invite_col_expires": "Expires",
|
|
"invite_col_status": "Status",
|
|
"invite_col_created": "Created",
|
|
"invite_status_active": "Active",
|
|
"invite_status_disabled": "Disabled",
|
|
"invite_status_expired": "Expired",
|
|
"invite_expires_never": "Never"
|
|
},
|
|
"settings": {
|
|
"title": "Settings",
|
|
"section_account": "Account",
|
|
"section_appearance": "Appearance",
|
|
"section_privacy": "Privacy",
|
|
"section_devices": "Devices",
|
|
"section_voice": "Voice",
|
|
"audio_quality": "Audio quality",
|
|
"audio_voice": "Voice (Recommended)",
|
|
"audio_hifi": "HiFi / Music",
|
|
"audio_voice_hint": "Mono 48 kbps with noise suppression, echo cancellation and auto-gain. Optimised for speech in a room.",
|
|
"audio_hifi_hint": "Stereo 510 kbps Opus with all DSP off — best for music/broadcast. Requires a quiet environment.",
|
|
"e2ee_calls": "End-to-end encryption (calls)",
|
|
"e2ee_calls_hint": "Audio + video are encrypted before upload. The server only sees ciphertext. All participants must have the option enabled.",
|
|
"e2ee_calls_unsupported": "Your browser doesn't support Insertable Streams. E2EE calls unavailable.",
|
|
"ptt_enabled": "Push-to-Talk",
|
|
"ptt_enabled_hint": "Mic stays muted until the key is held. Overrides the regular mute button.",
|
|
"ptt_key": "Hotkey",
|
|
"ptt_press_key": "Press a key…",
|
|
"section_screen_share": "Screen share",
|
|
"screen_share_quality": "Quality",
|
|
"screen_share_hint": "WebRTC dynamically adjusts bitrate + resolution to match network conditions (SVC/VP9). Values are upper bounds. Changes apply on the next call.",
|
|
"language": "Language",
|
|
"presence": "Presence",
|
|
"show_read_receipts": "Show read receipts",
|
|
"show_read_receipts_hint": "When off, others can't see when you read their messages — and you won't see when they read yours.",
|
|
"allow_dms_strangers": "Allow DMs from strangers",
|
|
"allow_dms_strangers_hint": "When off, only friends can DM you.",
|
|
"this_device": "This device",
|
|
"danger_zone": "Danger zone",
|
|
"sign_out": "Sign out"
|
|
},
|
|
"update": {
|
|
"available": "Update available",
|
|
"install": "Install & restart",
|
|
"downloading": "Downloading",
|
|
"installing": "Installing…"
|
|
}
|
|
}
|