diff --git a/src/i18n/en.json b/src/i18n/en.json
index cd190ba1..7b2df5f4 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -333,6 +333,7 @@
"domain_mutes": "Domains",
"avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
"pad_emoji": "Pad emoji with spaces when adding from picker",
+ "swap_reacts": "Swap Reactions with Favorite Button",
"emoji_reactions_on_timeline": "Show emoji reactions on timeline",
"export_theme": "Save preset",
"filtering": "Filtering",
@@ -353,6 +354,9 @@
"hide_all_muted_posts": "Hide muted posts",
"max_thumbnails": "Maximum amount of thumbnails per post",
"hide_isp": "Hide instance-specific panel",
+ "show_third_column": "Move Notifications to a seperate column",
+ "compact_nav_panel": "Compact navigation panel",
+ "compact_user_panel": "Compact user panel",
"hide_shoutbox": "Hide instance frothbox",
"right_sidebar": "Show sidebar on the right side",
"always_show_post_button": "Always show floating New Post button",
@@ -461,6 +465,14 @@
"subject_line_email": "Like email: \"re: subject\"",
"subject_line_mastodon": "Like mastodon: copy as is",
"subject_line_noop": "Do not copy",
+ "conversation_display": "Conversation display style",
+ "conversation_display_tree": "Tree-style",
+ "conversation_display_simple_tree": "Simplified tree-style",
+ "conversation_display_linear": "Linear-style",
+ "conversation_other_replies_button": "Show the \"other replies\" button",
+ "conversation_other_replies_button_below": "Below statuses",
+ "conversation_other_replies_button_inside": "Inside statuses",
+ "max_depth_in_thread": "Maximum number of levels in thread to display by default",
"post_status_content_type": "Post status content type",
"sensitive_by_default": "Mark posts as sensitive by default",
"stop_gifs": "Play-on-hover GIFs",
@@ -707,6 +719,7 @@
"reply_to": "Reply to",
"mentions": "Mentions",
"replies_list": "Replies:",
+ "replies_list_with_others": "Replies (+{numReplies} other): | Replies (+{numReplies} others):",
"mute_conversation": "Mute conversation",
"unmute_conversation": "Unmute conversation",
"status_unavailable": "Status unavailable",
@@ -722,7 +735,18 @@
"nsfw": "NSFW",
"expand": "Expand",
"you": "(You)",
- "plus_more": "+{number} more"
+ "plus_more": "+{number} more",
+ "thread_hide": "Hide this thread",
+ "thread_show": "Show this thread",
+ "thread_show_full": "Show everything under this thread ({numStatus} status in total, max depth {depth}) | Show everything under this thread ({numStatus} statuses in total, max depth {depth})",
+ "thread_show_full_with_icon": "{icon} {text}",
+ "thread_follow": "See the remaining part of this thread ({numStatus} status in total) | See the remaining part of this thread ({numStatus} statuses in total)",
+ "thread_follow_with_icon": "{icon} {text}",
+ "ancestor_follow": "See {numReplies} other reply under this status | See {numReplies} other replies under this status",
+ "ancestor_follow_with_icon": "{icon} {text}",
+ "show_all_conversation_with_icon": "{icon} {text}",
+ "show_all_conversation": "Show full conversation ({numStatus} other status) | Show full conversation ({numStatus} other statuses)",
+ "show_only_conversation_under_this": "Only show replies to this status"
},
"user_card": {
"approve": "Approve",
diff --git a/src/i18n/en_nyan.json b/src/i18n/en_nyan.json
new file mode 100644
index 00000000..dd91343b
--- /dev/null
+++ b/src/i18n/en_nyan.json
@@ -0,0 +1,775 @@
+{
+ "about": {
+ "mrf": {
+ "federation": "Federation",
+ "keyword": {
+ "keyword_policies": "Keyword Policies",
+ "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+ "reject": "Reject",
+ "replace": "Replace",
+ "is_replaced_by": "→"
+ },
+ "mrf_policies": "Enabled MRF Policies",
+ "mrf_policies_desc": "MRF policies manipulate the federation behaviour of the instance. The following policies are enabled:",
+ "simple": {
+ "simple_policies": "Instance-specific Policies",
+ "accept": "Accept",
+ "accept_desc": "This instance only accepts messages from the following instances:",
+ "reject": "Reject",
+ "reject_desc": "This instance will not accept messages from the following instances:",
+ "quarantine": "Quarantine",
+ "quarantine_desc": "This instance will send only public posts to the following instances:",
+ "ftl_removal": "Removal from \"The Whole Known Network\" Timeline",
+ "ftl_removal_desc": "This instance removes these instances from \"The Whole Known Network\" timeline:",
+ "media_removal": "Media Removal",
+ "media_removal_desc": "This instance removes media from posts on the following instances:",
+ "media_nsfw": "Media Force-set As Sensitive",
+ "media_nsfw_desc": "This instance forces media to be set sensitive in posts on the following instances:"
+ }
+ },
+ "staff": "Staff"
+ },
+ "chat": {
+ "title": "Chat"
+ },
+ "domain_mute_card": {
+ "mute": "Mute",
+ "mute_progress": "Muting...",
+ "unmute": "Unmute",
+ "unmute_progress": "Unmuting..."
+ },
+ "exporter": {
+ "export": "Export",
+ "processing": "Processing, you'll soon be asked to download your file"
+ },
+ "features_panel": {
+ "chat": "Chat",
+ "gopher": "Gopher",
+ "media_proxy": "Media proxy",
+ "scope_options": "Scope options",
+ "text_limit": "Text limit",
+ "title": "Features",
+ "who_to_follow": "Who to follow"
+ },
+ "finder": {
+ "error_fetching_user": "Error fetching user",
+ "find_user": "Find user"
+ },
+ "general": {
+ "apply": "Apply",
+ "submit": "Nyan",
+ "more": "More",
+ "generic_error": "An error occured",
+ "optional": "optional",
+ "show_more": "Show more",
+ "show_less": "Show less",
+ "dismiss": "Dismiss",
+ "cancel": "Cancel",
+ "disable": "Disable",
+ "enable": "Enable",
+ "confirm": "Confirm",
+ "verify": "Verify"
+ },
+ "image_cropper": {
+ "crop_picture": "Crop picture",
+ "save": "Save",
+ "save_without_cropping": "Save without cropping",
+ "cancel": "Cancel"
+ },
+ "importer": {
+ "submit": "Submit",
+ "success": "Imported successfully.",
+ "error": "An error occured while importing this file."
+ },
+ "login": {
+ "login": "Log in",
+ "description": "Log in with OAuth",
+ "logout": "Log out",
+ "password": "Password",
+ "placeholder": "e.g. lain",
+ "register": "Register",
+ "username": "Username",
+ "hint": "Log in to join the discussion",
+ "authentication_code": "Authentication code",
+ "enter_recovery_code": "Enter a recovery code",
+ "enter_two_factor_code": "Enter a two-factor code",
+ "recovery_code": "Recovery code",
+ "heading" : {
+ "totp" : "Two-factor authentication",
+ "recovery" : "Two-factor recovery"
+ }
+ },
+ "media_modal": {
+ "previous": "Previous",
+ "next": "Next"
+ },
+ "nav": {
+ "about": "About",
+ "administration": "Administration",
+ "back": "Back",
+ "chat": "Local Chat",
+ "friend_requests": "Follow Requests",
+ "mentions": "Mentions",
+ "interactions": "Interactions",
+ "dms": "Direct Messages",
+ "public_tl": "Public Timeline",
+ "timeline": "Timeline",
+ "twkn": "The Whole Known Network",
+ "user_search": "User Search",
+ "search": "Search",
+ "who_to_follow": "Who to follow",
+ "preferences": "Preferences",
+ "chats": "Chats"
+ },
+ "notifications": {
+ "broken_favorite": "Unknown status, searching for it...",
+ "favorited_you": "patted your status",
+ "followed_you": "snuggled you",
+ "follow_request": "wants to snuggle you",
+ "load_older": "Load older notifications",
+ "notifications": "Notifications",
+ "read": "Read!",
+ "repeated_you": "renyaned your status",
+ "no_more_notifications": "No more notifications",
+ "migrated_to": "migrated to",
+ "reacted_with": "reacted with {0}"
+ },
+ "polls": {
+ "add_poll": "Add Poll",
+ "add_option": "Add Option",
+ "option": "Option",
+ "votes": "votes",
+ "vote": "Vote",
+ "type": "Poll type",
+ "single_choice": "Single choice",
+ "multiple_choices": "Multiple choices",
+ "expiry": "Poll age",
+ "expires_in": "Poll ends in {0}",
+ "expired": "Poll ended {0} ago",
+ "not_enough_options": "Too few unique options in poll"
+ },
+ "emoji": {
+ "stickers": "Stickers",
+ "emoji": "Emoji",
+ "keep_open": "Keep picker open",
+ "search_emoji": "Search for an emoji",
+ "add_emoji": "Insert emoji",
+ "custom": "Custom emoji",
+ "unicode": "Unicode emoji",
+ "load_all_hint": "Loaded first {saneAmount} emoji, loading all emoji may cause performance issues.",
+ "load_all": "Loading all {emojiAmount} emoji"
+ },
+ "interactions": {
+ "favs_repeats": "Renyans and Pats",
+ "follows": "New Snuggles",
+ "moves": "User migrates",
+ "load_older": "Load older interactions"
+ },
+ "post_status": {
+ "new_status": "Nyan new status",
+ "account_not_locked_warning": "Your account is not {0}. Anyone can follow you to view your follower-only nyans.",
+ "account_not_locked_warning_link": "locked",
+ "attachments_sensitive": "Mark attachments as sensitive",
+ "content_type": {
+ "text/plain": "Plain text",
+ "text/html": "HTML",
+ "text/markdown": "Markdown",
+ "text/bbcode": "BBCode"
+ },
+ "content_warning": "Subject (optional)",
+ "default": "A nyanbox to nyan",
+ "direct_warning_to_all": "This nyan will be visible to all the mentioned users.",
+ "direct_warning_to_first_only": "This nyan will only be visible to the mentioned users at the beginning of the message.",
+ "posting": "Nyaning",
+ "scope_notice": {
+ "public": "This nyan will be visible to everyone",
+ "private": "This nyan will be visible to your followers only",
+ "unlisted": "This nyan will not be visible in Public Timeline and The Whole Known Network"
+ },
+ "scope": {
+ "direct": "Direct - Nyan to mentioned users only",
+ "private": "Followers-only - Nyan to followers only",
+ "public": "Public - Nyan to public timelines",
+ "unlisted": "Unlisted - Do not nyan to public timelines"
+ }
+ },
+ "registration": {
+ "bio": "Bio",
+ "email": "Email",
+ "fullname": "Display name",
+ "password_confirm": "Password confirmation",
+ "registration": "Registration",
+ "token": "Invite token",
+ "captcha": "CAPTCHA",
+ "new_captcha": "Click the image to get a new captcha",
+ "username_placeholder": "e.g. lain",
+ "fullname_placeholder": "e.g. Lain Iwakura",
+ "bio_placeholder": "e.g.\nHi, I'm Lain.\nI’m an anime girl living in suburban Japan. You may know me from the Wired.",
+ "validations": {
+ "username_required": "cannot be left blank",
+ "fullname_required": "cannot be left blank",
+ "email_required": "cannot be left blank",
+ "password_required": "cannot be left blank",
+ "password_confirmation_required": "cannot be left blank",
+ "password_confirmation_match": "should be the same as password"
+ }
+ },
+ "remote_user_resolver": {
+ "remote_user_resolver": "Remote user resolver",
+ "searching_for": "Searching for",
+ "error": "Not found."
+ },
+ "selectable_list": {
+ "select_all": "Select all"
+ },
+ "settings": {
+ "app_name": "App name",
+ "security": "Security",
+ "enter_current_password_to_confirm": "Enter your current password to confirm your identity",
+ "mfa": {
+ "otp" : "OTP",
+ "setup_otp" : "Setup OTP",
+ "wait_pre_setup_otp" : "presetting OTP",
+ "confirm_and_enable" : "Confirm & enable OTP",
+ "title": "Two-factor Authentication",
+ "generate_new_recovery_codes" : "Generate new recovery codes",
+ "warning_of_generate_new_codes" : "When you generate new recovery codes, your old codes won’t work anymore.",
+ "recovery_codes" : "Recovery codes.",
+ "waiting_a_recovery_codes": "Receiving backup codes...",
+ "recovery_codes_warning" : "Write the codes down or save them somewhere secure - otherwise you won't see them again. If you lose access to your 2FA app and recovery codes you'll be locked out of your account.",
+ "authentication_methods" : "Authentication methods",
+ "scan": {
+ "title": "Scan",
+ "desc": "Using your two-factor app, scan this QR code or enter text key:",
+ "secret_code": "Key"
+ },
+ "verify": {
+ "desc": "To enable two-factor authentication, enter the code from your two-factor app:"
+ }
+ },
+ "allow_following_move": "Allow auto-follow when following account moves",
+ "attachmentRadius": "Attachments",
+ "attachments": "Attachments",
+ "autoload": "Enable automatic loading when scrolled to the bottom",
+ "avatar": "Avatar",
+ "avatarAltRadius": "Avatars (Notifications)",
+ "avatarRadius": "Avatars",
+ "background": "Background",
+ "bio": "Bio",
+ "block_export": "Block export",
+ "block_export_button": "Export your blocks to a csv file",
+ "block_import": "Block import",
+ "block_import_error": "Error importing blocks",
+ "blocks_imported": "Blocks imported! Processing them will take a while.",
+ "blocks_tab": "Blocks",
+ "btnRadius": "Buttons",
+ "cBlue": "Blue (Reply, follow)",
+ "cGreen": "Green (Retweet)",
+ "cOrange": "Orange (Favorite)",
+ "cRed": "Red (Cancel)",
+ "change_email": "Change Email",
+ "change_email_error": "There was an issue changing your email.",
+ "changed_email": "Email changed successfully!",
+ "change_password": "Change Password",
+ "change_password_error": "There was an issue changing your password.",
+ "changed_password": "Password changed successfully!",
+ "chatMessageRadius": "Chat message",
+ "collapse_subject": "Collapse posts with subjects",
+ "composing": "Composing",
+ "confirm_new_password": "Confirm new password",
+ "current_avatar": "Your current avatar",
+ "current_password": "Current password",
+ "current_profile_banner": "Your current profile banner",
+ "data_import_export_tab": "Data Import / Export",
+ "default_vis": "Default visibility scope",
+ "delete_account": "Delete Account",
+ "delete_account_description": "Permanently delete your data and deactivate your account.",
+ "delete_account_error": "There was an issue deleting your account. If this persists please contact your instance administrator.",
+ "delete_account_instructions": "Type your password in the input below to confirm account deletion.",
+ "discoverable": "Allow discovery of this account in search results and other services",
+ "domain_mutes": "Domains",
+ "avatar_size_instruction": "The recommended minimum size for avatar images is 150x150 pixels.",
+ "pad_emoji": "Pad emoji with spaces when adding from picker",
+ "emoji_reactions_on_timeline": "Show emoji reactions on timeline",
+ "export_theme": "Save preset",
+ "filtering": "Filtering",
+ "filtering_explanation": "All statuses containing these words will be muted, one per line",
+ "follow_export": "Follow export",
+ "follow_export_button": "Export your follows to a csv file",
+ "follow_import": "Follow import",
+ "follow_import_error": "Error importing followers",
+ "follows_imported": "Follows imported! Processing them will take a while.",
+ "accent": "Accent",
+ "foreground": "Foreground",
+ "general": "General",
+ "hide_attachments_in_convo": "Hide attachments in conversations",
+ "hide_attachments_in_tl": "Hide attachments in timeline",
+ "hide_muted_posts": "Hide posts of muted users",
+ "max_thumbnails": "Maximum amount of thumbnails per post",
+ "hide_isp": "Hide instance-specific panel",
+ "preload_images": "Preload images",
+ "use_one_click_nsfw": "Open NSFW attachments with just one click",
+ "hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
+ "hide_user_stats": "Hide user statistics (e.g. the number of followers)",
+ "hide_filtered_statuses": "Hide filtered statuses",
+ "import_blocks_from_a_csv_file": "Import blocks from a csv file",
+ "import_followers_from_a_csv_file": "Import follows from a csv file",
+ "import_theme": "Load preset",
+ "inputRadius": "Input fields",
+ "checkboxRadius": "Checkboxes",
+ "instance_default": "(default: {value})",
+ "instance_default_simple": "(default)",
+ "interface": "Interface",
+ "interfaceLanguage": "Interface language",
+ "invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
+ "limited_availability": "Unavailable in your browser",
+ "links": "Links",
+ "lock_account_description": "Restrict your account to approved followers only",
+ "loop_video": "Loop videos",
+ "loop_video_silent_only": "Loop only videos without sound (i.e. Mastodon's \"gifs\")",
+ "mutes_tab": "Mutes",
+ "play_videos_in_modal": "Play videos in a popup frame",
+ "use_contain_fit": "Don't crop the attachment in thumbnails",
+ "name": "Name",
+ "name_bio": "Name & Bio",
+ "new_email": "New Email",
+ "new_password": "New password",
+ "notification_visibility": "Types of notifications to show",
+ "notification_visibility_follows": "Follows",
+ "notification_visibility_likes": "Likes",
+ "notification_visibility_mentions": "Mentions",
+ "notification_visibility_repeats": "Repeats",
+ "notification_visibility_moves": "User Migrates",
+ "notification_visibility_emoji_reactions": "Reactions",
+ "no_rich_text_description": "Strip rich text formatting from all posts",
+ "no_blocks": "No blocks",
+ "no_mutes": "No mutes",
+ "hide_follows_description": "Don't show who I'm following",
+ "hide_followers_description": "Don't show who's following me",
+ "hide_follows_count_description": "Don't show follow count",
+ "hide_followers_count_description": "Don't show follower count",
+ "show_admin_badge": "Show Admin badge in my profile",
+ "show_moderator_badge": "Show Moderator badge in my profile",
+ "nsfw_clickthrough": "Enable clickthrough NSFW attachment hiding",
+ "oauth_tokens": "OAuth tokens",
+ "token": "Token",
+ "refresh_token": "Refresh Token",
+ "valid_until": "Valid Until",
+ "revoke_token": "Revoke",
+ "panelRadius": "Panels",
+ "pause_on_unfocused": "Pause streaming when tab is not focused",
+ "presets": "Presets",
+ "profile_background": "Profile Background",
+ "profile_banner": "Profile Banner",
+ "profile_tab": "Profile",
+ "radii_help": "Set up interface edge rounding (in pixels)",
+ "replies_in_timeline": "Replies in timeline",
+ "reply_link_preview": "Enable reply-link preview on mouse hover",
+ "reply_visibility_all": "Show all replies",
+ "reply_visibility_following": "Only show replies directed at me or users I'm following",
+ "reply_visibility_self": "Only show replies directed at me",
+ "autohide_floating_post_button": "Automatically hide New Post button (mobile)",
+ "saving_err": "Error saving settings",
+ "saving_ok": "Settings saved",
+ "search_user_to_block": "Search whom you want to block",
+ "search_user_to_mute": "Search whom you want to mute",
+ "security_tab": "Security",
+ "scope_copy": "Copy scope when replying (DMs are always copied)",
+ "minimal_scopes_mode": "Minimize post scope selection options",
+ "set_new_avatar": "Set new avatar",
+ "set_new_profile_background": "Set new profile background",
+ "set_new_profile_banner": "Set new profile banner",
+ "settings": "Settings",
+ "subject_input_always_show": "Always show subject field",
+ "subject_line_behavior": "Copy subject when replying",
+ "subject_line_email": "Like email: \"re: subject\"",
+ "subject_line_mastodon": "Like mastodon: copy as is",
+ "subject_line_noop": "Do not copy",
+ "post_status_content_type": "Post status content type",
+ "stop_gifs": "Play-on-hover GIFs",
+ "streaming": "Enable automatic streaming of new posts when scrolled to the top",
+ "user_mutes": "Users",
+ "useStreamingApi": "Receive posts and notifications real-time",
+ "useStreamingApiWarning": "(Not recommended, experimental, known to skip posts)",
+ "text": "Text",
+ "theme": "Theme",
+ "theme_help": "Use hex color codes (#rrggbb) to customize your color theme.",
+ "theme_help_v2_1": "You can also override certain component's colors and opacity by toggling the checkbox, use \"Clear all\" button to clear all overrides.",
+ "theme_help_v2_2": "Icons underneath some entries are background/text contrast indicators, hover over for detailed info. Please keep in mind that when using transparency contrast indicators show the worst possible case.",
+ "tooltipRadius": "Tooltips/alerts",
+ "type_domains_to_mute": "Type in domains to mute",
+ "upload_a_photo": "Upload a photo",
+ "user_settings": "User Settings",
+ "values": {
+ "false": "no",
+ "true": "yes"
+ },
+ "fun": "Fun",
+ "greentext": "Meme arrows",
+ "notifications": "Notifications",
+ "notification_setting_filters": "Filters",
+ "notification_setting": "Receive notifications from:",
+ "notification_setting_follows": "Users you follow",
+ "notification_setting_non_follows": "Users you do not follow",
+ "notification_setting_followers": "Users who follow you",
+ "notification_setting_non_followers": "Users who do not follow you",
+ "notification_setting_privacy": "Privacy",
+ "notification_setting_privacy_option": "Hide the sender and contents of push notifications",
+ "notification_mutes": "To stop receiving notifications from a specific user, use a mute.",
+ "notification_blocks": "Blocking a user stops all notifications as well as unsubscribes them.",
+ "enable_web_push_notifications": "Enable web push notifications",
+ "style": {
+ "switcher": {
+ "keep_color": "Keep colors",
+ "keep_shadows": "Keep shadows",
+ "keep_opacity": "Keep opacity",
+ "keep_roundness": "Keep roundness",
+ "keep_fonts": "Keep fonts",
+ "save_load_hint": "\"Keep\" options preserve currently set options when selecting or loading themes, it also stores said options when exporting a theme. When all checkboxes unset, exporting theme will save everything.",
+ "reset": "Reset",
+ "clear_all": "Clear all",
+ "clear_opacity": "Clear opacity",
+ "load_theme": "Load theme",
+ "keep_as_is": "Keep as is",
+ "use_snapshot": "Old version",
+ "use_source": "New version",
+ "help": {
+ "upgraded_from_v2": "PleromaFE has been upgraded, theme could look a little bit different than you remember.",
+ "v2_imported": "File you imported was made for older FE. We try to maximize compatibility but there still could be inconsistencies.",
+ "future_version_imported": "File you imported was made in newer version of FE.",
+ "older_version_imported": "File you imported was made in older version of FE.",
+ "snapshot_present": "Theme snapshot is loaded, so all values are overriden. You can load theme's actual data instead.",
+ "snapshot_missing": "No theme snapshot was in the file so it could look different than originally envisioned.",
+ "fe_upgraded": "PleromaFE's theme engine upgraded after version update.",
+ "fe_downgraded": "PleromaFE's version rolled back.",
+ "migration_snapshot_ok": "Just to be safe, theme snapshot loaded. You can try loading theme data.",
+ "migration_napshot_gone": "For whatever reason snapshot was missing, some stuff could look different than you remember.",
+ "snapshot_source_mismatch": "Versions conflict: most likely FE was rolled back and updated again, if you changed theme using older version of FE you most likely want to use old version, otherwise use new version."
+ }
+ },
+ "common": {
+ "color": "Color",
+ "opacity": "Opacity",
+ "contrast": {
+ "hint": "Contrast ratio is {ratio}, it {level} {context}",
+ "level": {
+ "aa": "meets Level AA guideline (minimal)",
+ "aaa": "meets Level AAA guideline (recommended)",
+ "bad": "doesn't meet any accessibility guidelines"
+ },
+ "context": {
+ "18pt": "for large (18pt+) text",
+ "text": "for text"
+ }
+ }
+ },
+ "common_colors": {
+ "_tab_label": "Common",
+ "main": "Common colors",
+ "foreground_hint": "See \"Advanced\" tab for more detailed control",
+ "rgbo": "Icons, accents, badges"
+ },
+ "advanced_colors": {
+ "_tab_label": "Advanced",
+ "alert": "Alert background",
+ "alert_error": "Error",
+ "alert_warning": "Warning",
+ "alert_neutral": "Neutral",
+ "post": "Posts/User bios",
+ "badge": "Badge background",
+ "popover": "Tooltips, menus, popovers",
+ "badge_notification": "Notification",
+ "panel_header": "Panel header",
+ "top_bar": "Top bar",
+ "borders": "Borders",
+ "buttons": "Buttons",
+ "inputs": "Input fields",
+ "faint_text": "Faded text",
+ "underlay": "Underlay",
+ "poll": "Poll graph",
+ "icons": "Icons",
+ "highlight": "Highlighted elements",
+ "pressed": "Pressed",
+ "selectedPost": "Selected post",
+ "selectedMenu": "Selected menu item",
+ "disabled": "Disabled",
+ "toggled": "Toggled",
+ "tabs": "Tabs",
+ "chat": {
+ "incoming_background": "Incoming background",
+ "incoming_text": "Incoming text",
+ "incoming_link": "Incoming link",
+ "incoming_border": "Incoming border",
+ "outgoing_background": "Outgoing background",
+ "outgoing_text": "Outgoing text",
+ "outgoing_link": "Outgoing link",
+ "outgoing_border": "Outgoing border"
+ }
+ },
+ "radii": {
+ "_tab_label": "Roundness"
+ },
+ "shadows": {
+ "_tab_label": "Shadow and lighting",
+ "component": "Component",
+ "override": "Override",
+ "shadow_id": "Shadow #{value}",
+ "blur": "Blur",
+ "spread": "Spread",
+ "inset": "Inset",
+ "hintV3": "For shadows you can also use the {0} notation to use other color slot.",
+ "filter_hint": {
+ "always_drop_shadow": "Warning, this shadow always uses {0} when browser supports it.",
+ "drop_shadow_syntax": "{0} does not support {1} parameter and {2} keyword.",
+ "avatar_inset": "Please note that combining both inset and non-inset shadows on avatars might give unexpected results with transparent avatars.",
+ "spread_zero": "Shadows with spread > 0 will appear as if it was set to zero",
+ "inset_classic": "Inset shadows will be using {0}"
+ },
+ "components": {
+ "panel": "Panel",
+ "panelHeader": "Panel header",
+ "topBar": "Top bar",
+ "avatar": "User avatar (in profile view)",
+ "avatarStatus": "User avatar (in post display)",
+ "popup": "Popups and tooltips",
+ "button": "Button",
+ "buttonHover": "Button (hover)",
+ "buttonPressed": "Button (pressed)",
+ "buttonPressedHover": "Button (pressed+hover)",
+ "input": "Input field"
+ }
+ },
+ "fonts": {
+ "_tab_label": "Fonts",
+ "help": "Select font to use for elements of UI. For \"custom\" you have to enter exact font name as it appears in system.",
+ "components": {
+ "interface": "Interface",
+ "input": "Input fields",
+ "post": "Post text",
+ "postCode": "Monospaced text in a post (rich text)"
+ },
+ "family": "Font name",
+ "size": "Size (in px)",
+ "weight": "Weight (boldness)",
+ "custom": "Custom"
+ },
+ "preview": {
+ "header": "Preview",
+ "content": "Content",
+ "error": "Example error",
+ "button": "Button",
+ "text": "A bunch of more {0} and {1}",
+ "mono": "content",
+ "input": "Just landed in L.A.",
+ "faint_link": "helpful manual",
+ "fine_print": "Read our {0} to learn nothing useful!",
+ "header_faint": "This is fine",
+ "checkbox": "I have skimmed over terms and conditions",
+ "link": "a nice lil' link"
+ }
+ },
+ "version": {
+ "title": "Version",
+ "backend_version": "Backend Version",
+ "frontend_version": "Frontend Version"
+ }
+ },
+ "time": {
+ "day": "{0} day",
+ "days": "{0} days",
+ "day_short": "{0}d",
+ "days_short": "{0}d",
+ "hour": "{0} hour",
+ "hours": "{0} hours",
+ "hour_short": "{0}h",
+ "hours_short": "{0}h",
+ "in_future": "in {0}",
+ "in_past": "{0} ago",
+ "minute": "{0} minute",
+ "minutes": "{0} minutes",
+ "minute_short": "{0}min",
+ "minutes_short": "{0}min",
+ "month": "{0} month",
+ "months": "{0} months",
+ "month_short": "{0}mo",
+ "months_short": "{0}mo",
+ "now": "just now",
+ "now_short": "now",
+ "second": "{0} second",
+ "seconds": "{0} seconds",
+ "second_short": "{0}s",
+ "seconds_short": "{0}s",
+ "week": "{0} week",
+ "weeks": "{0} weeks",
+ "week_short": "{0}w",
+ "weeks_short": "{0}w",
+ "year": "{0} year",
+ "years": "{0} years",
+ "year_short": "{0}y",
+ "years_short": "{0}y"
+ },
+ "timeline": {
+ "collapse": "Collapse",
+ "conversation": "Conversation",
+ "error_fetching": "Error fetching updates",
+ "load_older": "Load older nyans",
+ "no_retweet_hint": "Nyan is marked as followers-only or direct and cannot be renyaned",
+ "repeated": "renyaned",
+ "show_new": "Show new",
+ "up_to_date": "Up-to-date",
+ "no_more_statuses": "No more statuses",
+ "no_statuses": "No statuses"
+ },
+ "status": {
+ "favorites": "Favorites",
+ "repeats": "Repeats",
+ "delete": "Delete status",
+ "pin": "Pin on profile",
+ "unpin": "Unpin from profile",
+ "pinned": "Pinned",
+ "delete_confirm": "Do you really want to delete this status?",
+ "reply_to": "Reply to",
+ "replies_list": "Replies:",
+ "mute_conversation": "Mute conversation",
+ "unmute_conversation": "Unmute conversation",
+ "status_unavailable": "Status unavailable",
+ "copy_link": "Copy link to status"
+ },
+ "user_card": {
+ "approve": "Approve",
+ "block": "Block",
+ "blocked": "Blocked!",
+ "deny": "Deny",
+ "favorites": "Favorites",
+ "follow": "Follow",
+ "follow_sent": "Request sent!",
+ "follow_progress": "Requesting…",
+ "follow_again": "Send request again?",
+ "follow_unfollow": "Unfollow",
+ "followees": "Following",
+ "followers": "Followers",
+ "following": "Following!",
+ "follows_you": "Follows you!",
+ "hidden": "Hidden",
+ "its_you": "It's you!",
+ "media": "Media",
+ "mention": "Mention",
+ "message": "Message",
+ "mute": "Mute",
+ "muted": "Muted",
+ "per_day": "per day",
+ "remote_follow": "Remote follow",
+ "report": "Report",
+ "statuses": "Statuses",
+ "subscribe": "Subscribe",
+ "unsubscribe": "Unsubscribe",
+ "unblock": "Unblock",
+ "unblock_progress": "Unblocking...",
+ "block_progress": "Blocking...",
+ "unmute": "Unmute",
+ "unmute_progress": "Unmuting...",
+ "mute_progress": "Muting...",
+ "hide_repeats": "Hide repeats",
+ "show_repeats": "Show repeats",
+ "admin_menu": {
+ "moderation": "Moderation",
+ "grant_admin": "Grant Admin",
+ "revoke_admin": "Revoke Admin",
+ "grant_moderator": "Grant Moderator",
+ "revoke_moderator": "Revoke Moderator",
+ "activate_account": "Activate account",
+ "deactivate_account": "Deactivate account",
+ "delete_account": "Delete account",
+ "force_nsfw": "Mark all posts as NSFW",
+ "strip_media": "Remove media from posts",
+ "force_unlisted": "Force posts to be unlisted",
+ "sandbox": "Force posts to be followers-only",
+ "disable_remote_subscription": "Disallow following user from remote instances",
+ "disable_any_subscription": "Disallow following user at all",
+ "quarantine": "Disallow user posts from federating",
+ "delete_user": "Delete user",
+ "delete_user_confirmation": "Are you absolutely sure? This action cannot be undone."
+ }
+ },
+ "user_profile": {
+ "timeline_title": "User Timeline",
+ "profile_does_not_exist": "Sorry, this profile does not exist.",
+ "profile_loading_error": "Sorry, there was an error loading this profile."
+ },
+ "user_reporting": {
+ "title": "Reporting {0}",
+ "add_comment_description": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
+ "additional_comments": "Additional comments",
+ "forward_description": "The account is from another server. Send a copy of the report there as well?",
+ "forward_to": "Forward to {0}",
+ "submit": "Submit",
+ "generic_error": "An error occurred while processing your request."
+ },
+ "who_to_follow": {
+ "more": "More",
+ "who_to_follow": "Who to follow"
+ },
+ "tool_tip": {
+ "media_upload": "Upload Media",
+ "repeat": "Renyan",
+ "reply": "Reply",
+ "favorite": "Pat",
+ "add_reaction": "Add Reaction",
+ "user_settings": "User Settings",
+ "accept_follow_request": "Accept snuggle request",
+ "reject_follow_request": "Reject snuggle request"
+ },
+ "upload":{
+ "error": {
+ "base": "Upload failed.",
+ "file_too_big": "File too big [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
+ "default": "Try again later"
+ },
+ "file_size_units": {
+ "B": "B",
+ "KiB": "KiB",
+ "MiB": "MiB",
+ "GiB": "GiB",
+ "TiB": "TiB"
+ }
+ },
+ "search": {
+ "people": "People",
+ "hashtags": "Hashtags",
+ "person_talking": "{count} person talking",
+ "people_talking": "{count} people talking",
+ "no_results": "No results"
+ },
+ "password_reset": {
+ "forgot_password": "Forgot password?",
+ "password_reset": "Password reset",
+ "instruction": "Enter your email address or username. We will send you a link to reset your password.",
+ "placeholder": "Your email or username",
+ "check_email": "Check your email for a link to reset your password.",
+ "return_home": "Return to the home page",
+ "not_found": "We couldn't find that email or username.",
+ "too_many_requests": "You have reached the limit of attempts, try again later.",
+ "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator.",
+ "password_reset_required": "You must reset your password to log in.",
+ "password_reset_required_but_mailer_is_disabled": "You must reset your password, but password reset is disabled. Please contact your instance administrator."
+ },
+ "chats": {
+ "message_user": "Message {nickname}",
+ "write_message": "Write a message",
+ "delete": "Delete",
+ "chats": "Chats",
+ "new": "New Chat",
+ "empty_message_error": "Cannot post empty message",
+ "more": "More",
+ "delete_confirm": "Do you really want to delete this message?"
+ },
+ "file_type": {
+ "audio": "Audio",
+ "video": "Video",
+ "image": "Image",
+ "file": "File"
+ },
+ "display_date": {
+ "today": "Today"
+ }
+}
diff --git a/src/i18n/messages.js b/src/i18n/messages.js
index 2a1161be..17d0421d 100644
--- a/src/i18n/messages.js
+++ b/src/i18n/messages.js
@@ -8,6 +8,7 @@
// There's only problem that apostrophe character ' gets replaced by \\ so you have to fix it manually, sorry.
const loaders = {
+ en_nyan: () => import('./en_nyan.json'),
ar: () => import('./ar.json'),
ca: () => import('./ca.json'),
cs: () => import('./cs.json'),
diff --git a/src/main.js b/src/main.js
index 3895da89..03493525 100644
--- a/src/main.js
+++ b/src/main.js
@@ -45,6 +45,8 @@ Vue.use(VueClickOutside)
Vue.use(PortalVue)
Vue.use(VBodyScrollLock)
+Vue.config.ignoredElements = ['pinch-zoom']
+
Vue.component('FAIcon', FontAwesomeIcon)
Vue.component('FALayers', FontAwesomeLayers)
diff --git a/src/modules/config.js b/src/modules/config.js
index 82580de9..6bfd6fc6 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -11,13 +11,17 @@ const browserLocale = (window.navigator.language || 'en').split('-')[0]
*/
export const multiChoiceProperties = [
'postContentType',
- 'subjectLineBehavior'
+ 'subjectLineBehavior',
+ 'conversationDisplay', // tree | linear
+ 'conversationOtherRepliesButton' // below | inside
]
export const defaultState = {
colors: {},
theme: undefined,
customTheme: undefined,
+ compactNavPanel: false,
+ compactUserPanel: false,
customThemeSource: undefined,
hideISP: false,
hideInstanceWallpaper: false,
@@ -26,6 +30,7 @@ export const defaultState = {
hideMutedPosts: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default
padEmoji: true,
+ swapReacts: true,
hideAttachments: false,
hideAttachmentsInConv: false,
maxThumbnails: 16,
@@ -72,7 +77,10 @@ export const defaultState = {
hidePostStats: undefined, // instance default
hideUserStats: undefined, // instance default
virtualScrolling: undefined, // instance default
- sensitiveByDefault: undefined // instance default
+ sensitiveByDefault: undefined, // instance default
+ conversationDisplay: undefined, // instance default
+ conversationOtherRepliesButton: undefined, // instance default
+ maxDepthInThread: 18
}
// caching the instance default properties
diff --git a/src/modules/instance.js b/src/modules/instance.js
index 539b9c66..c107d239 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -43,6 +43,9 @@ const defaultState = {
theme: 'pleroma-dark',
virtualScrolling: true,
sensitiveByDefault: false,
+ conversationDisplay: 'simple_tree',
+ conversationOtherRepliesButton: 'below',
+ maxDepthInThread: 6,
// Nasty stuff
customEmoji: [],
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index ac5d25c4..a9db1e24 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -698,6 +698,14 @@ const statuses = {
commit('dismissNotification', { id })
rootState.api.backendInteractor.dismissNotification({ id })
},
+ markMultipleNotificationsAsSeen ({ rootState, commit }, { finder }) {
+ const notifications = rootState.statuses.notifications.data.filter(finder)
+
+ notifications.forEach(n => {
+ commit('markSingleNotificationAsSeen', { id: n.id })
+ rootState.api.backendInteractor.markNotificationsAsSeen({ id: n.id, single: true })
+ })
+ },
updateNotification ({ rootState, commit }, { id, updater }) {
commit('updateNotification', { id, updater })
},
diff --git a/src/modules/users.js b/src/modules/users.js
index fb92cc91..f09b8be9 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -234,6 +234,12 @@ export const mutations = {
signUpFailure (state, errors) {
state.signUpPending = false
state.signUpErrors = errors
+ },
+ updateUnreadChatCount (state, { userId, unreadChatCount }) {
+ const user = state.usersObject[userId]
+ if (user) {
+ user.unread_chat_count = unreadChatCount
+ }
}
}
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
index 04bb45a4..10942221 100644
--- a/src/services/entity_normalizer/entity_normalizer.service.js
+++ b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -379,6 +379,7 @@ export const parseNotification = (data) => {
output.type = mastoDict[data.type] || data.type
output.seen = data.pleroma.is_seen
output.status = isStatusNotification(output.type) ? parseStatus(data.status) : null
+ output.chatMessage = output.type === 'pleroma:chat_mention' ? parseChatMessage(data.chat_message) : null
output.action = output.status // TODO: Refactor, this is unneeded
output.target = output.type !== 'move'
? null
diff --git a/src/services/gesture_service/gesture_service.js b/src/services/gesture_service/gesture_service.js
index 88a328f3..741d4a12 100644
--- a/src/services/gesture_service/gesture_service.js
+++ b/src/services/gesture_service/gesture_service.js
@@ -4,9 +4,15 @@ const DIRECTION_RIGHT = [1, 0]
const DIRECTION_UP = [0, -1]
const DIRECTION_DOWN = [0, 1]
+const BUTTON_LEFT = 0
+
const deltaCoord = (oldCoord, newCoord) => [newCoord[0] - oldCoord[0], newCoord[1] - oldCoord[1]]
-const touchEventCoord = e => ([e.touches[0].screenX, e.touches[0].screenY])
+const touchCoord = touch => [touch.screenX, touch.screenY]
+
+const touchEventCoord = e => touchCoord(e.touches[0])
+
+const pointerEventCoord = e => [e.clientX, e.clientY]
const vectorLength = v => Math.sqrt(v[0] * v[0] + v[1] * v[1])
@@ -19,6 +25,9 @@ const project = (v1, v2) => {
return [scalar * v2[0], scalar * v2[1]]
}
+// const debug = console.log
+const debug = () => {}
+
// direction: either use the constants above or an arbitrary 2d vector.
// threshold: how many Px to move from touch origin before checking if the
// callback should be called.
@@ -61,6 +70,143 @@ const updateSwipe = (event, gesture) => {
gesture._swiping = false
}
+class SwipeAndClickGesture {
+ // swipePreviewCallback(offsets: Array[Number])
+ // offsets: the offset vector which the underlying component should move, from the starting position
+ // swipeEndCallback(sign: 0|-1|1)
+ // sign: if the swipe does not meet the threshold, 0
+ // if the swipe meets the threshold in the positive direction, 1
+ // if the swipe meets the threshold in the negative direction, -1
+ constructor ({
+ direction,
+ // swipeStartCallback
+ swipePreviewCallback,
+ swipeEndCallback,
+ swipeCancelCallback,
+ swipelessClickCallback,
+ threshold = 30, perpendicularTolerance = 1.0
+ }) {
+ const nop = () => { debug('Warning: Not implemented') }
+ this.direction = direction
+ this.swipePreviewCallback = swipePreviewCallback || nop
+ this.swipeEndCallback = swipeEndCallback || nop
+ this.swipeCancelCallback = swipeCancelCallback || nop
+ this.swipelessClickCallback = swipelessClickCallback || nop
+ this.threshold = typeof threshold === 'function' ? threshold : () => threshold
+ this.perpendicularTolerance = perpendicularTolerance
+ this._reset()
+ }
+
+ _reset () {
+ this._startPos = [0, 0]
+ this._pointerId = -1
+ this._swiping = false
+ this._swiped = false
+ this._preventNextClick = false
+ }
+
+ start (event) {
+ debug('start() called', event)
+
+ // Only handle left click
+ if (event.button !== BUTTON_LEFT) {
+ return
+ }
+
+ this._startPos = pointerEventCoord(event)
+ this._pointerId = event.pointerId
+ debug('start pos:', this._startPos)
+ this._swiping = true
+ this._swiped = false
+ }
+
+ move (event) {
+ if (this._swiping && this._pointerId === event.pointerId) {
+ this._swiped = true
+
+ const coord = pointerEventCoord(event)
+ const delta = deltaCoord(this._startPos, coord)
+
+ this.swipePreviewCallback(delta)
+ }
+ }
+
+ cancel (event) {
+ debug('cancel called')
+ if (!this._swiping || this._pointerId !== event.pointerId) {
+ return
+ }
+
+ this.swipeCancelCallback()
+ }
+
+ end (event) {
+ if (!this._swiping) {
+ debug('not swiping')
+ return
+ }
+
+ if (this._pointerId !== event.pointerId) {
+ debug('pointer id does not match')
+ return
+ }
+
+ this._swiping = false
+
+ debug('end: is swipe event')
+
+ debug('button = ', event.button)
+
+ // movement too small
+ const coord = pointerEventCoord(event)
+ const delta = deltaCoord(this._startPos, coord)
+
+ const sign = (() => {
+ debug(
+ 'threshold = ', this.threshold(),
+ 'vector len =', vectorLength(delta))
+ if (vectorLength(delta) < this.threshold()) {
+ return 0
+ }
+ // movement is opposite from direction
+ const isPositive = dotProduct(delta, this.direction) > 0
+
+ // movement perpendicular to direction is too much
+ const towardsDir = project(delta, this.direction)
+ const perpendicularDir = perpendicular(this.direction)
+ const towardsPerpendicular = project(delta, perpendicularDir)
+ if (
+ vectorLength(towardsDir) * this.perpendicularTolerance <
+ vectorLength(towardsPerpendicular)
+ ) {
+ return 0
+ }
+
+ return isPositive ? 1 : -1
+ })()
+
+ const swiped = this._swiped
+ if (this._swiped) {
+ this.swipeEndCallback(sign)
+ }
+ this._reset()
+ // Only a mouse will fire click event when
+ // the end point is far from the starting point
+ // so for other kinds of pointers do not check
+ // whether we have swiped
+ if (swiped && event.pointerType === 'mouse') {
+ this._preventNextClick = true
+ }
+ }
+
+ click (event) {
+ if (!this._preventNextClick) {
+ this.swipelessClickCallback()
+ }
+ this._reset()
+ }
+}
+
const GestureService = {
DIRECTION_LEFT,
DIRECTION_RIGHT,
@@ -68,7 +214,8 @@ const GestureService = {
DIRECTION_DOWN,
swipeGesture,
beginSwipe,
- updateSwipe
+ updateSwipe,
+ SwipeAndClickGesture
}
export default GestureService
diff --git a/static/themes/breezy-dark.json b/static/themes/breezy-dark.json
index 76b962c5..6fb9d09c 100644
--- a/static/themes/breezy-dark.json
+++ b/static/themes/breezy-dark.json
@@ -115,7 +115,8 @@
"cOrange": "#f67400",
"btnPressed": "--accent",
"selectedMenu": "--accent",
- "selectedMenuPopover": "--accent"
+ "selectedMenuPopover": "--accent",
+ "chatMessageIncomingBorder": "#3d4349"
},
"radii": {
"btn": "2",
diff --git a/yarn.lock b/yarn.lock
index 9329cc3a..ecb964ee 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -915,6 +915,12 @@
resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.0.tgz#63da3e459147cebb0a8d58eed81d6071db9f5973"
integrity sha512-N3VKw7KzRfOm8hShUVldpinlm13HpvLBQgT63QS+aCrIRLwjoEUXY5Rcmttbfb6HkzZaeqjLqd/aZCQ53UjQpg==
+"@kazvmoe-infra/pinch-zoom-element@https://lily.kazv.moe/infra/pinch-zoom-element.git":
+ version "1.1.1"
+ resolved "https://lily.kazv.moe/infra/pinch-zoom-element.git#b5d2e9fb41231e1bff12058bbfc55df05cfdc1eb"
+ dependencies:
+ pointer-tracker "^2.0.3"
+
"@nodelib/fs.scandir@2.1.3":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b"
@@ -7004,6 +7010,11 @@ pngjs@^3.3.0:
version "3.3.3"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b"
+pointer-tracker@^2.0.3:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/pointer-tracker/-/pointer-tracker-2.4.0.tgz#78721c2d2201486db11ec1094377f03023b621b3"
+ integrity sha512-pWI2tpaM/XNtc9mUTv42Rmjf6mkHvE8LT5DDEq0G7baPNhxNM9E3CepubPplSoSLk9E5bwQrAMyDcPVmJyTW4g==
+
portal-vue@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.4.tgz#1fc679d77e294dc8d026f1eb84aa467de11b392e"