diff --git a/app/soapbox/__fixtures__/fedibird-account.json b/app/soapbox/__fixtures__/fedibird-account.json new file mode 100644 index 000000000..07bbd7057 --- /dev/null +++ b/app/soapbox/__fixtures__/fedibird-account.json @@ -0,0 +1,35 @@ +{ + "id": "66768", + "username": "alex", + "acct": "alex", + "display_name": "", + "locked": false, + "bot": false, + "cat": false, + "discoverable": false, + "group": false, + "created_at": "2020-01-27T00:00:00.000Z", + "note": "

", + "url": "https://fedibird.com/@alex", + "avatar": "https://fedibird.com/avatars/original/missing.png", + "avatar_static": "https://fedibird.com/avatars/original/missing.png", + "header": "https://fedibird.com/headers/original/missing.png", + "header_static": "https://fedibird.com/headers/original/missing.png", + "followers_count": 1, + "following_count": 1, + "subscribing_count": 0, + "statuses_count": 5, + "last_status_at": "2022-02-20", + "emojis": [], + "fields": [], + "other_settings": { + "birthday": "1993-07-03", + "location": "Texas, USA", + "noindex": false, + "hide_network": false, + "hide_statuses_count": false, + "hide_following_count": false, + "hide_followers_count": false, + "enable_reaction": true + } +} diff --git a/app/soapbox/__fixtures__/mk.json b/app/soapbox/__fixtures__/mk.json new file mode 100644 index 000000000..a7c841f1e --- /dev/null +++ b/app/soapbox/__fixtures__/mk.json @@ -0,0 +1,123 @@ +{ + "acct": "mk", + "avatar": "https://media.spinster.xyz/4043b9fb3f9d468aa48a8d68294f338914d9d54b2816aa1c789f548efe6c6239.jpg", + "avatar_static": "https://media.spinster.xyz/4043b9fb3f9d468aa48a8d68294f338914d9d54b2816aa1c789f548efe6c6239.jpg", + "bot": false, + "created_at": "2019-08-01T22:06:30.000Z", + "display_name": "M. K. Fain", + "emojis": [ + { + "shortcode": "4w", + "static_url": "https://spinster.xyz/emoji/custom/4w.png", + "url": "https://spinster.xyz/emoji/custom/4w.png", + "visible_in_picker": false + }, + { + "shortcode": "spinster", + "static_url": "https://spinster.xyz/emoji/custom/spinster.png", + "url": "https://spinster.xyz/emoji/custom/spinster.png", + "visible_in_picker": false + } + ], + "fields": [ + { + "name": "Website", + "value": "https://marykatefain.com" + }, + { + "name": "Twitter", + "value": "https://twitter.com/mkay_fain" + }, + { + "name": "Patreon", + "value": "https://www.patreon.com/mkfain" + }, + { + "name": "Paypal", + "value": "https://www.paypal.com/donate?hosted_button_id=NYXHYFQ6CRWJJ" + }, + { + "name": "Facebook", + "value": "https://www.facebook.com/M-K-Fain-102559968375112" + }, + { + "name": "Dog Pics", + "value": "https://www.instagram.com/mmkaayyy92" + }, + { + "name": "$BTC", + "value": "bc1q7fp347muhnuxrtu0pft6eswn0e7pldhssdg8py" + } + ], + "followers_count": 5687, + "following_count": 18017, + "fqn": "mk@spinster.xyz", + "header": "https://media.spinster.xyz/3a5f9d5ef06940d0c319f8f0135b1153a8a42cefd10eace97378875c0347da71.png", + "header_static": "https://media.spinster.xyz/3a5f9d5ef06940d0c319f8f0135b1153a8a42cefd10eace97378875c0347da71.png", + "id": "9y4BZYXEDuQ6K1zW9g", + "last_status_at": "2022-02-27T01:58:21", + "locked": false, + "note": ":spinster: Admin of @spinster
:4w: Editor of @4WPub

Sorry I didn't reply to you.

Boost ≠ agree. All opinions my own.", + "pleroma": { + "accepts_chat_messages": true, + "also_known_as": [], + "ap_id": "https://spinster.xyz/users/mk", + "background_image": null, + "favicon": "https://spinster.xyz/favicon.png", + "hide_favorites": true, + "hide_followers": false, + "hide_followers_count": false, + "hide_follows": false, + "hide_follows_count": false, + "is_admin": true, + "is_confirmed": true, + "is_moderator": false, + "is_suggested": true, + "relationship": {}, + "skip_thread_containment": false, + "tags": [ + "verified" + ] + }, + "source": { + "fields": [ + { + "name": "Website", + "value": "https://marykatefain.com" + }, + { + "name": "Twitter", + "value": "https://twitter.com/mkay_fain" + }, + { + "name": "Patreon", + "value": "https://www.patreon.com/mkfain" + }, + { + "name": "Paypal", + "value": "https://www.paypal.com/donate?hosted_button_id=NYXHYFQ6CRWJJ" + }, + { + "name": "Facebook", + "value": "https://www.facebook.com/M-K-Fain-102559968375112" + }, + { + "name": "Dog Pics", + "value": "https://www.instagram.com/mmkaayyy92" + }, + { + "name": "$BTC", + "value": "bc1q7fp347muhnuxrtu0pft6eswn0e7pldhssdg8py" + } + ], + "note": ":spinster: Admin of @spinster\r\n:4w: Editor of @4WPub\r\n\r\nSorry I didn't reply to you.\r\n\r\nBoost ≠ agree. All opinions my own.", + "pleroma": { + "actor_type": "Person", + "discoverable": false + }, + "sensitive": false + }, + "statuses_count": 9580, + "url": "https://spinster.xyz/users/mk", + "username": "mk" +} diff --git a/app/soapbox/__fixtures__/pleroma-2.2.2-account.json b/app/soapbox/__fixtures__/pleroma-2.2.2-account.json new file mode 100644 index 000000000..7df005d65 --- /dev/null +++ b/app/soapbox/__fixtures__/pleroma-2.2.2-account.json @@ -0,0 +1,46 @@ +{ + "acct": "alex", + "avatar": "https://freespeechextremist.com/images/avi.png", + "avatar_static": "https://freespeechextremist.com/images/avi.png", + "bot": false, + "created_at": "2022-02-28T01:55:05.000Z", + "display_name": "Alex Gleason", + "emojis": [], + "fields": [], + "followers_count": 0, + "following_count": 0, + "header": "https://freespeechextremist.com/images/banner.png", + "header_static": "https://freespeechextremist.com/images/banner.png", + "id": "AGv8wCadU7DqWgMqNk", + "locked": false, + "note": "I'm testing out compatibility with an older Pleroma version", + "pleroma": { + "accepts_chat_messages": true, + "ap_id": "https://freespeechextremist.com/users/alex", + "background_image": null, + "confirmation_pending": false, + "favicon": null, + "hide_favorites": true, + "hide_followers": false, + "hide_followers_count": false, + "hide_follows": false, + "hide_follows_count": false, + "is_admin": false, + "is_moderator": false, + "relationship": {}, + "skip_thread_containment": false, + "tags": [] + }, + "source": { + "fields": [], + "note": "I'm testing out compatibility with an older Pleroma version", + "pleroma": { + "actor_type": "Person", + "discoverable": true + }, + "sensitive": false + }, + "statuses_count": 0, + "url": "https://freespeechextremist.com/users/alex", + "username": "alex" +} diff --git a/app/soapbox/__fixtures__/alex.json b/app/soapbox/__fixtures__/pleroma-account.json similarity index 100% rename from app/soapbox/__fixtures__/alex.json rename to app/soapbox/__fixtures__/pleroma-account.json diff --git a/app/soapbox/__fixtures__/realDonaldTrump.json b/app/soapbox/__fixtures__/realDonaldTrump.json new file mode 100644 index 000000000..c9cf20076 --- /dev/null +++ b/app/soapbox/__fixtures__/realDonaldTrump.json @@ -0,0 +1,26 @@ +{ + "id": "107780257626128497", + "username": "realDonaldTrump", + "acct": "realDonaldTrump", + "display_name": "Donald J. Trump", + "locked": false, + "bot": false, + "discoverable": null, + "group": false, + "created_at": "2022-02-11T00:00:00.000Z", + "note": "

45th President of the United States of America

", + "url": "https://truthsocial.com/@realDonaldTrump", + "avatar": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/avatars/107/780/257/626/128/497/original/573cf5cc8281e7e9.jpeg", + "avatar_static": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/avatars/107/780/257/626/128/497/original/573cf5cc8281e7e9.jpeg", + "header": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/headers/107/780/257/626/128/497/original/3c1acf607b065ded.jpeg", + "header_static": "https://static-assets.truthsocial.com/tmtg:prime-truth-social-assets/accounts/headers/107/780/257/626/128/497/original/3c1acf607b065ded.jpeg", + "followers_count": 51507, + "following_count": 1, + "statuses_count": 1, + "last_status_at": "2022-02-14", + "verified": true, + "location": "", + "website": "", + "emojis": [], + "fields": [] +} diff --git a/app/soapbox/components/display_name.js b/app/soapbox/components/display_name.js index eca526679..87947af46 100644 --- a/app/soapbox/components/display_name.js +++ b/app/soapbox/components/display_name.js @@ -4,7 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; import HoverRefWrapper from 'soapbox/components/hover_ref_wrapper'; -import { isVerified } from 'soapbox/utils/accounts'; import { displayFqn } from 'soapbox/utils/state'; import { getAcct } from '../utils/accounts'; @@ -38,7 +37,7 @@ class DisplayName extends React.PureComponent { const { account, displayFqn, others, children, withDate } = this.props; let displayName, suffix; - const verified = isVerified(account); + const verified = account.get('verified'); const createdAt = account.get('created_at'); diff --git a/app/soapbox/containers/soapbox.js b/app/soapbox/containers/soapbox.js index 19b218877..7f18e8b61 100644 --- a/app/soapbox/containers/soapbox.js +++ b/app/soapbox/containers/soapbox.js @@ -64,7 +64,7 @@ const mapStateToProps = (state) => { // In demo mode, force the default brand color const brandColor = settings.get('demo') ? '#0482d8' : soapboxConfig.get('brandColor'); - const accentColor = settings.get('demo') ? null : soapboxConfig.get('accentColor'); + const accentColor = (settings.get('demo') || settings.get('halloween')) ? null : soapboxConfig.get('accentColor'); const singleUserMode = soapboxConfig.get('singleUserMode') && soapboxConfig.get('singleUserModeProfile'); diff --git a/app/soapbox/features/account/components/header.js b/app/soapbox/features/account/components/header.js index 8b5a5f258..4881d6b7d 100644 --- a/app/soapbox/features/account/components/header.js +++ b/app/soapbox/features/account/components/header.js @@ -27,7 +27,6 @@ import { isStaff, isAdmin, isModerator, - isVerified, isLocal, isRemote, getDomain, @@ -451,7 +450,7 @@ class Header extends ImmutablePureComponent { } } - if (isVerified(account)) { + if (account.get('verified')) { menu.push({ text: intl.formatMessage(messages.unverifyUser, { name: account.get('username') }), action: this.props.onUnverifyUser, diff --git a/app/soapbox/features/birthdays/account.js b/app/soapbox/features/birthdays/account.js index d0961d0ae..e79273f89 100644 --- a/app/soapbox/features/birthdays/account.js +++ b/app/soapbox/features/birthdays/account.js @@ -56,7 +56,7 @@ class Account extends ImmutablePureComponent { if (!account) return null; - const birthday = account.getIn(['pleroma', 'birthday']); + const birthday = account.get('birthday'); if (!birthday) return null; const formattedBirthday = intl.formatDate(birthday, { day: 'numeric', month: 'short', year: 'numeric' }); diff --git a/app/soapbox/features/edit_profile/components/profile_preview.js b/app/soapbox/features/edit_profile/components/profile_preview.js index 3b73e48f8..ad1db11ed 100644 --- a/app/soapbox/features/edit_profile/components/profile_preview.js +++ b/app/soapbox/features/edit_profile/components/profile_preview.js @@ -6,7 +6,7 @@ import { Link } from 'react-router-dom'; import StillImage from 'soapbox/components/still_image'; import VerificationBadge from 'soapbox/components/verification_badge'; -import { getAcct, isVerified } from 'soapbox/utils/accounts'; +import { getAcct } from 'soapbox/utils/accounts'; import { displayFqn } from 'soapbox/utils/state'; const mapStateToProps = state => ({ @@ -28,7 +28,7 @@ const ProfilePreview = ({ account, displayFqn }) => ( {account.get('display_name')} - {isVerified(account) && } + {account.get('verified') && } @{getAcct(account, displayFqn)} diff --git a/app/soapbox/features/edit_profile/index.js b/app/soapbox/features/edit_profile/index.js index ad1197555..300d965a7 100644 --- a/app/soapbox/features/edit_profile/index.js +++ b/app/soapbox/features/edit_profile/index.js @@ -25,7 +25,6 @@ import { SimpleTextarea, } from 'soapbox/features/forms'; import { makeGetAccount } from 'soapbox/selectors'; -import { isVerified } from 'soapbox/utils/accounts'; import { getFeatures } from 'soapbox/utils/features'; import resizeImage from 'soapbox/utils/resize_image'; @@ -114,7 +113,7 @@ class EditProfile extends ImmutablePureComponent { const strangerNotifications = account.getIn(['pleroma', 'notification_settings', 'block_from_strangers']); const acceptsEmailList = account.getIn(['pleroma', 'accepts_email_list']); const discoverable = account.getIn(['source', 'pleroma', 'discoverable']); - const birthday = account.getIn(['pleroma', 'birthday']); + const birthday = account.get('birthday'); const showBirthday = account.getIn(['source', 'pleroma', 'show_birthday']); const initialState = account.withMutations(map => { @@ -263,7 +262,7 @@ class EditProfile extends ImmutablePureComponent { render() { const { intl, maxFields, account, verifiedCanEditName, supportsBirthdays, supportsEmailList } = this.props; - const verified = isVerified(account); + const verified = account.get('verified'); const canEditName = verifiedCanEditName || !verified; return ( diff --git a/app/soapbox/features/ui/components/profile_info_panel.js b/app/soapbox/features/ui/components/profile_info_panel.js index 35d12a627..72e033e1e 100644 --- a/app/soapbox/features/ui/components/profile_info_panel.js +++ b/app/soapbox/features/ui/components/profile_info_panel.js @@ -15,7 +15,7 @@ import Icon from 'soapbox/components/icon'; import VerificationBadge from 'soapbox/components/verification_badge'; import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; import { CryptoAddress } from 'soapbox/features/ui/util/async-components'; -import { getAcct, isAdmin, isModerator, isLocal, isVerified } from 'soapbox/utils/accounts'; +import { getAcct, isAdmin, isModerator, isLocal } from 'soapbox/utils/accounts'; import { displayFqn } from 'soapbox/utils/state'; import ProfileStats from './profile_stats'; @@ -85,7 +85,7 @@ class ProfileInfoPanel extends ImmutablePureComponent { getBirthday = () => { const { account, intl } = this.props; - const birthday = account.getIn(['pleroma', 'birthday']); + const birthday = account.get('birthday'); if (!birthday) return null; const formattedBirthday = intl.formatDate(birthday, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' }); @@ -147,7 +147,7 @@ class ProfileInfoPanel extends ImmutablePureComponent { const deactivated = !account.getIn(['pleroma', 'is_active'], true); const displayNameHtml = deactivated ? { __html: intl.formatMessage(messages.deactivated) } : { __html: account.get('display_name_html') }; const memberSinceDate = intl.formatDate(account.get('created_at'), { month: 'long', year: 'numeric' }); - const verified = isVerified(account); + const verified = account.get('verified'); const badges = this.getBadges(); return ( diff --git a/app/soapbox/features/ui/components/user_panel.js b/app/soapbox/features/ui/components/user_panel.js index 9bf52c4da..36a48ce56 100644 --- a/app/soapbox/features/ui/components/user_panel.js +++ b/app/soapbox/features/ui/components/user_panel.js @@ -9,7 +9,7 @@ import { Link } from 'react-router-dom'; import Avatar from 'soapbox/components/avatar'; import StillImage from 'soapbox/components/still_image'; import VerificationBadge from 'soapbox/components/verification_badge'; -import { getAcct, isVerified } from 'soapbox/utils/accounts'; +import { getAcct } from 'soapbox/utils/accounts'; import { shortNumberFormat } from 'soapbox/utils/numbers'; import { displayFqn } from 'soapbox/utils/state'; @@ -51,7 +51,7 @@ class UserPanel extends ImmutablePureComponent {

- {isVerified(account) && } + {account.get('verified') && } @{getAcct(account, displayFqn)}

diff --git a/app/soapbox/locales/uk.json b/app/soapbox/locales/uk.json index 1ab59de14..3709cd556 100644 --- a/app/soapbox/locales/uk.json +++ b/app/soapbox/locales/uk.json @@ -15,7 +15,7 @@ "account.direct": "Пряме повідомлення @{name}", "account.domain_blocked": "Домен приховано", "account.edit_profile": "Редагувати профіль", - "account.endorse": "Feature on profile", + "account.endorse": "Публікувати у профілі", "account.follow": "Підписатися", "account.followers": "Підписники", "account.followers.empty": "Ніхто ще не підписався на цього користувача.", @@ -23,32 +23,32 @@ "account.follows.empty": "Цей користувач ще ні на кого не підписався.", "account.follows_you": "Підписаний(-а) на Вас", "account.hide_reblogs": "Сховати передмухи від @{name}", - "account.last_status": "Last active", + "account.last_status": "Крайня активність", "account.link_verified_on": "Права власності на це посилання були перевірені {date}", "account.locked_info": "Статус конфіденційності цього облікового запису встановлено у заблокований. Власник вручну переглядає, хто може за ним стежити.", - "account.login": "Log in", + "account.login": "Увійти", "account.media": "Медіа", - "account.member_since": "Joined {date}", + "account.member_since": "Долучення {date}", "account.mention": "Згадати", "account.moved_to": "{name} переїхав на:", "account.mute": "Заглушити @{name}", "account.muted": "Заглушений", - "account.never_active": "Never", + "account.never_active": "Ніколи", "account.posts": "Дмухи", "account.posts_with_replies": "Дмухи й відповіді", "account.profile": "Profile", - "account.register": "Sign up", + "account.register": "Зареєструватися", "account.remote_follow": "Remote follow", "account.report": "Поскаржитися на @{name}", "account.requested": "Очікує підтвердження. Натисніть щоб відмінити запит", - "account.requested_small": "Awaiting approval", + "account.requested_small": "Очікує підтвердження", "account.share": "Поширити профіль @{name}", "account.show_reblogs": "Показати передмухи від @{name}", "account.subscribe": "Subscribe to notifications from @{name}", "account.subscribed": "Subscribed", "account.unblock": "Розблокувати @{name}", "account.unblock_domain": "Розблокувати {domain}", - "account.unendorse": "Don't feature on profile", + "account.unendorse": "Не публікувати у профілі", "account.unfollow": "Відписатися", "account.unmute": "Зняти глушення з @{name}", "account.unsubscribe": "Unsubscribe to notifications from @{name}", @@ -67,21 +67,21 @@ "admin.dashboard.registration_mode_label": "Registrations", "admin.dashboard.settings_saved": "Settings saved!", "admin.dashcounters.domain_count_label": "peers", - "admin.dashcounters.mau_label": "monthly active users", + "admin.dashcounters.mau_label": "активні користувачі місяця", "admin.dashcounters.retention_label": "user retention", "admin.dashcounters.status_count_label": "posts", "admin.dashcounters.user_count_label": "total users", "admin.dashwidgets.email_list_header": "Email list", - "admin.dashwidgets.software_header": "Software", + "admin.dashwidgets.software_header": "Програмне забезпечення", "admin.latest_accounts_panel.expand_message": "Click to see {count} more {count, plural, one {account} other {accounts}}", "admin.latest_accounts_panel.title": "Latest Accounts", "admin.moderation_log.empty_message": "You have not performed any moderation actions yet. When you do, a history will be shown here.", - "admin.reports.actions.close": "Close", - "admin.reports.actions.view_status": "View post", + "admin.reports.actions.close": "Закрити", + "admin.reports.actions.view_status": "Показати статус", "admin.reports.empty_message": "There are no open reports. If a user gets reported, they will show up here.", "admin.reports.report_closed_message": "Report on @{name} was closed", "admin.reports.report_title": "Report on {acct}", - "admin.statuses.actions.delete_status": "Delete post", + "admin.statuses.actions.delete_status": "Видалити пост", "admin.statuses.actions.mark_status_not_sensitive": "Mark post not sensitive", "admin.statuses.actions.mark_status_sensitive": "Mark post sensitive", "admin.statuses.status_deleted_message": "Post by @{acct} was deleted", @@ -89,8 +89,8 @@ "admin.statuses.status_marked_message_sensitive": "Post by @{acct} was marked sensitive", "admin.user_index.empty": "No users found.", "admin.user_index.search_input_placeholder": "Who are you looking for?", - "admin.users.actions.deactivate_user": "Deactivate @{name}", - "admin.users.actions.delete_user": "Delete @{name}", + "admin.users.actions.deactivate_user": "Деактивувати @{name}", + "admin.users.actions.delete_user": "Видалити @{name}", "admin.users.actions.demote_to_moderator": "Demote @{name} to a moderator", "admin.users.actions.demote_to_moderator_message": "@{acct} was demoted to a moderator", "admin.users.actions.demote_to_user": "Demote @{name} to a regular user", @@ -110,28 +110,28 @@ "admin.users.user_unverified_message": "@{acct} was unverified", "admin.users.user_verified_message": "@{acct} was verified", "admin_nav.awaiting_approval": "Awaiting Approval", - "admin_nav.dashboard": "Dashboard", - "admin_nav.reports": "Reports", + "admin_nav.dashboard": "Приборна панель", + "admin_nav.reports": "Скарги", "alert.unexpected.clear_cookies": "clear cookies and browser data", "alert.unexpected.help_text": "If the problem persists, please notify a site admin with a screenshot and information about your web browser. You may also {clear_cookies} (this will log you out).", "alert.unexpected.message": "Трапилась неочікувана помилка.", "alert.unexpected.return_home": "Return Home", "alert.unexpected.title": "Ой!", - "aliases.account.add": "Create alias", + "aliases.account.add": "Створити псевдонім", "aliases.account_label": "Old account:", "aliases.aliases_list_delete": "Unlink alias", "aliases.search": "Search your old account", "aliases.success.add": "Account alias created successfully", "aliases.success.remove": "Account alias removed successfully", - "app_create.name_label": "App name", + "app_create.name_label": "Назва додатку", "app_create.name_placeholder": "e.g. 'Soapbox'", - "app_create.redirect_uri_label": "Redirect URIs", + "app_create.redirect_uri_label": "URI перенаправлення", "app_create.restart": "Create another", "app_create.results.app_label": "App", "app_create.results.explanation_text": "You created a new app and token! Please copy the credentials somewhere; you will not see them again after navigating away from this page.", "app_create.results.explanation_title": "App created successfully", "app_create.results.token_label": "OAuth token", - "app_create.scopes_label": "Scopes", + "app_create.scopes_label": "Рамки", "app_create.scopes_placeholder": "e.g. 'read write follow'", "app_create.submit": "Create app", "app_create.website_label": "Website", @@ -152,7 +152,7 @@ "chat_box.actions.send": "Send", "chat_box.input.placeholder": "Send a message…", "chat_panels.main_window.empty": "No chats found. To start a chat, visit a user's profile.", - "chat_panels.main_window.title": "Chats", + "chat_panels.main_window.title": "Чат", "chats.actions.delete": "Delete message", "chats.actions.more": "More", "chats.actions.report": "Report user", @@ -163,12 +163,12 @@ "chats.dividers.today": "Today", "chats.search_placeholder": "Start a chat with…", "column.admin.awaiting_approval": "Awaiting Approval", - "column.admin.dashboard": "Dashboard", + "column.admin.dashboard": "Приборна панель", "column.admin.moderation_log": "Moderation Log", - "column.admin.reports": "Reports", + "column.admin.reports": "Скарги", "column.admin.reports.menu.moderation_log": "Moderation Log", "column.admin.users": "Users", - "column.aliases": "Account aliases", + "column.aliases": "Псевдоніми облікового запису", "column.aliases.create_error": "Error creating alias", "column.aliases.delete": "Delete", "column.aliases.delete_error": "Error deleting alias", @@ -177,63 +177,63 @@ "column.app_create": "Create app", "column.backups": "Backups", "column.blocks": "Заблоковані користувачі", - "column.bookmarks": "Bookmarks", - "column.chats": "Chats", + "column.bookmarks": "Закладки", + "column.chats": "Чат", "column.community": "Локальна стрічка", "column.crypto_donate": "Donate Cryptocurrency", - "column.developers": "Developers", + "column.developers": "Розробникам", "column.direct": "Прямі повідомлення", - "column.directory": "Browse profiles", + "column.directory": "Переглянути профілі", "column.domain_blocks": "Приховані домени", - "column.edit_profile": "Edit profile", - "column.export_data": "Export data", + "column.edit_profile": "Редагувати профіль", + "column.export_data": "Експорт даних", "column.favourited_statuses": "Liked posts", - "column.favourites": "Likes", + "column.favourites": "Вподобане", "column.federation_restrictions": "Federation Restrictions", - "column.filters": "Muted words", - "column.filters.add_new": "Add New Filter", - "column.filters.conversations": "Conversations", + "column.filters": "Приховані слова", + "column.filters.add_new": "Додати фільтр", + "column.filters.conversations": "Повідомлення", "column.filters.create_error": "Error adding filter", - "column.filters.delete": "Delete", + "column.filters.delete": "Видалити", "column.filters.delete_error": "Error deleting filter", - "column.filters.drop_header": "Drop instead of hide", - "column.filters.drop_hint": "Filtered posts will disappear irreversibly, even if filter is later removed", - "column.filters.expires": "Expire after", + "column.filters.drop_header": "Видалити назавжди, а не просто сховати", + "column.filters.drop_hint": "Відсіяні дмухи зникнуть назавжди, навіть якщо фільтр потім буде знято", + "column.filters.expires": "Закінчується після", "column.filters.expires_hint": "Expiration dates are not currently supported", - "column.filters.home_timeline": "Home timeline", - "column.filters.keyword": "Keyword or phrase", - "column.filters.notifications": "Notifications", - "column.filters.public_timeline": "Public timeline", - "column.filters.subheading_add_new": "Add New Filter", + "column.filters.home_timeline": "Ваша стрічка", + "column.filters.keyword": "Ключове слово або фраза", + "column.filters.notifications": "Сповіщення", + "column.filters.public_timeline": "Глобальні стрічки", + "column.filters.subheading_add_new": "Додати фільтр", "column.filters.subheading_filters": "Current Filters", - "column.filters.whole_word_header": "Whole word", - "column.filters.whole_word_hint": "When the keyword or phrase is alphanumeric only, it will only be applied if it matches the whole word", + "column.filters.whole_word_header": "Ціле слово", + "column.filters.whole_word_hint": "Якщо пошукове слово або фраза містить лише літери та цифри, воно має збігатися цілком", "column.follow_requests": "Запити на підписку", - "column.followers": "Followers", - "column.following": "Following", + "column.followers": "Підписники", + "column.following": "Підписки", "column.groups": "Groups", "column.home": "Головна", - "column.import_data": "Import data", + "column.import_data": "Імпорт даних", "column.info": "Server information", "column.lists": "Списки", "column.mentions": "Mentions", - "column.mfa": "Multi-Factor Authentication", - "column.mfa_cancel": "Cancel", + "column.mfa": "Двофакторна авторизація", + "column.mfa_cancel": "Відмінити", "column.mfa_confirm_button": "Confirm", "column.mfa_disable_button": "Disable", "column.mfa_setup": "Proceed to Setup", "column.mutes": "Заглушені користувачі", "column.notifications": "Сповіщення", - "column.pins": "Pinned posts", - "column.preferences": "Preferences", - "column.profile_directory": "Profile directory", + "column.pins": "Закріплені дмухи", + "column.preferences": "Налаштування", + "column.profile_directory": "Каталог профілів", "column.public": "Глобальна стрічка", "column.reactions": "Reactions", - "column.reblogs": "Reposts", + "column.reblogs": "Передмухи", "column.remote": "Federated timeline", "column.scheduled_statuses": "Scheduled Posts", - "column.search": "Search", - "column.security": "Security", + "column.search": "Пошук", + "column.security": "Безпека", "column.soapbox_config": "Soapbox config", "column_back_button.label": "Назад", "column_forbidden.body": "You do not have permission to access this page.", @@ -257,8 +257,8 @@ "compose_form.poll.duration": "Тривалість опитування", "compose_form.poll.option_placeholder": "Варіант {number}", "compose_form.poll.remove_option": "Видалити цей варіант", - "compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices", - "compose_form.poll.switch_to_single": "Change poll to allow for a single choice", + "compose_form.poll.switch_to_multiple": "Перемкнути у режим вибору декількох відповідей", + "compose_form.poll.switch_to_single": "Change Перемкнути у режим вибору однієї відповіді", "compose_form.publish": "Дмухнути", "compose_form.publish_loud": "{publish}!", "compose_form.schedule": "Schedule", @@ -313,10 +313,10 @@ "developers.navigation.app_create_label": "Create an app", "developers.navigation.intentional_error_label": "Trigger an error", "direct.search_placeholder": "Send a message to…", - "directory.federated": "From known fediverse", - "directory.local": "From {domain} only", - "directory.new_arrivals": "New arrivals", - "directory.recently_active": "Recently active", + "directory.federated": "З відомого федесвіту", + "directory.local": "Тільки з домену {domain}", + "directory.new_arrivals": "Нові надходження", + "directory.recently_active": "Активні нещодавно", "donate": "Donate", "donate_crypto": "Donate cryptocurrency", "edit_federation.followers_only": "Hide posts except to followers", @@ -328,32 +328,32 @@ "edit_federation.unlisted": "Force posts unlisted", "edit_profile.error": "Profile update failed", "edit_profile.fields.accepts_email_list_label": "Subscribe to newsletter", - "edit_profile.fields.avatar_label": "Avatar", - "edit_profile.fields.bio_label": "Bio", + "edit_profile.fields.avatar_label": "Аватар", + "edit_profile.fields.bio_label": "Про Вас", "edit_profile.fields.bio_placeholder": "Tell us about yourself.", - "edit_profile.fields.bot_label": "This is a bot account", + "edit_profile.fields.bot_label": "Це обліковий запис бота", "edit_profile.fields.discoverable_label": "Allow account discovery", - "edit_profile.fields.display_name_label": "Display name", + "edit_profile.fields.display_name_label": "Ім'я", "edit_profile.fields.display_name_placeholder": "Name", - "edit_profile.fields.header_label": "Header", + "edit_profile.fields.header_label": "Заголовок", "edit_profile.fields.hide_network_label": "Hide network", - "edit_profile.fields.locked_label": "Lock account", - "edit_profile.fields.meta_fields.content_placeholder": "Content", - "edit_profile.fields.meta_fields.label_placeholder": "Label", - "edit_profile.fields.meta_fields_label": "Profile metadata", + "edit_profile.fields.locked_label": "Зробити акаунт приватним", + "edit_profile.fields.meta_fields.content_placeholder": "Вміст", + "edit_profile.fields.meta_fields.label_placeholder": "Позначка", + "edit_profile.fields.meta_fields_label": "Метадані профіля", "edit_profile.fields.stranger_notifications_label": "Block notifications from strangers", "edit_profile.fields.verified_display_name": "Verified users may not update their display name", "edit_profile.hints.accepts_email_list": "Opt-in to news and marketing updates.", - "edit_profile.hints.avatar": "PNG, GIF or JPG. Will be downscaled to {size}", - "edit_profile.hints.bot": "This account mainly performs automated actions and might not be monitored", + "edit_profile.hints.avatar": "PNG, GIF, або JPG. Буде зменшено до {size}", + "edit_profile.hints.bot": "Цей акаунт виконує автоматичні дії та може не відстежуватися", "edit_profile.hints.discoverable": "Display account in profile directory and allow indexing by external services", - "edit_profile.hints.header": "PNG, GIF or JPG. Will be downscaled to {size}", - "edit_profile.hints.hide_network": "Who you follow and who follows you will not be shown on your profile", + "edit_profile.hints.header": "PNG, GIF, або JPG. Буде зменшено до {size}", + "edit_profile.hints.hide_network": "У вашому профілі не буде відображено підписки та підписників", "edit_profile.hints.locked": "Requires you to manually approve followers", - "edit_profile.hints.meta_fields": "You can have up to {count, plural, one {# item} other {# items}} displayed as a table on your profile", + "edit_profile.hints.meta_fields": "До {count, plural, one {# елемента} many {# елементів}} може бути відображено як таблиця у вашому профілі", "edit_profile.hints.stranger_notifications": "Only show notifications from people you follow", "edit_profile.meta_fields.add": "Add new item", - "edit_profile.save": "Save", + "edit_profile.save": "Зберегти", "edit_profile.success": "Profile saved!", "embed.instructions": "Вбудуйте цей статус до вашого вебсайту, скопіювавши код нижче.", "embed.preview": "Ось як він виглядатиме:", @@ -375,17 +375,17 @@ "empty_column.account_favourited_statuses": "This user doesn't have any liked posts yet.", "empty_column.account_timeline": "Тут дмухалок немає!", "empty_column.account_unavailable": "Профіль недоступний", - "empty_column.aliases": "You haven't created any account alias yet.", + "empty_column.aliases": "У вас немає псевдонімів.", "empty_column.aliases.suggestions": "There are no account suggestions available for the provided term.", "empty_column.blocks": "Ви ще не заблокували жодного користувача.", - "empty_column.bookmarks": "You don't have any bookmarks yet. When you add one, it will show up here.", + "empty_column.bookmarks": "У вас ще немає дмухів у закладках. Коли ви щось додасте до заклкдок, воно з'явиться тут.", "empty_column.community": "Локальна стрічка пуста. Напишіть щось, щоб розігріти народ!", "empty_column.direct": "У вас ще немає прямих повідомлень. Коли ви відправите чи отримаєте якесь, воно з'явиться тут.", "empty_column.domain_blocks": "Тут поки немає прихованих доменів.", "empty_column.favourited_statuses": "У вас ще немає вподобаних дмухів. Коли ви щось вподобаєте, воно з'явиться тут.", "empty_column.favourites": "Ніхто ще не вподобав цього дмуху. Коли хтось це зробить, вони з'являться тут.", "empty_column.filters": "You haven't created any muted words yet.", - "empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.", + "empty_column.follow_recommendations": "Схоже, для вас не буде створено жодної пропозиції. Ви можете спробувати скористатися пошуком людей, яких ви можете знати або переглянути популярні хештеґи.", "empty_column.follow_requests": "У вас ще немає запитів на підписку. Коли ви їх отримаєте, вони з'являться тут.", "empty_column.group": "There is nothing in this group yet. When members of this group make new posts, they will appear here.", "empty_column.hashtag": "Дописів з цим хештегом поки не існує.", @@ -401,16 +401,16 @@ "empty_column.search.accounts": "There are no people results for \"{term}\"", "empty_column.search.hashtags": "There are no hashtags results for \"{term}\"", "empty_column.search.statuses": "There are no posts results for \"{term}\"", - "export_data.actions.export": "Export", + "export_data.actions.export": "Експорт", "export_data.actions.export_blocks": "Export blocks", "export_data.actions.export_follows": "Export follows", "export_data.actions.export_mutes": "Export mutes", - "export_data.blocks_label": "Blocks", - "export_data.follows_label": "Follows", + "export_data.blocks_label": "Список блокувань", + "export_data.follows_label": "Підписки", "export_data.hints.blocks": "Get a CSV file containing a list of blocked accounts", "export_data.hints.follows": "Get a CSV file containing a list of followed accounts", "export_data.hints.mutes": "Get a CSV file containing a list of muted accounts", - "export_data.mutes_label": "Mutes", + "export_data.mutes_label": "Список глушення", "export_data.success.blocks": "Blocks exported successfully", "export_data.success.followers": "Followers exported successfully", "export_data.success.mutes": "Mutes exported successfully", @@ -427,22 +427,22 @@ "fediverse_tab.explanation_box.explanation": "{site_title} is part of the Fediverse, a social network made up of thousands of independent social media sites (aka \"servers\"). The posts you see here are from 3rd-party servers. You have the freedom to engage with them, or to block any server you don't like. Pay attention to the full username after the second @ symbol to know which server a post is from. To see only {site_title} posts, visit {local}.", "fediverse_tab.explanation_box.title": "What is the Fediverse?", "filters.added": "Filter added.", - "filters.context_header": "Filter contexts", - "filters.context_hint": "One or multiple contexts where the filter should apply", - "filters.filters_list_context_label": "Filter contexts:", - "filters.filters_list_delete": "Delete", + "filters.context_header": "Фільтрувати контексти", + "filters.context_hint": "Один або кілька контекстів, до яких повинні бути застосовані фільтри", + "filters.filters_list_context_label": "Фільтрувати контексти:", + "filters.filters_list_delete": "Видалити", "filters.filters_list_details_label": "Filter settings:", "filters.filters_list_drop": "Drop", "filters.filters_list_hide": "Hide", - "filters.filters_list_phrase_label": "Keyword or phrase:", - "filters.filters_list_whole-word": "Whole word", + "filters.filters_list_phrase_label": "Ключове слово або фраза:", + "filters.filters_list_whole-word": "Ціле слово", "filters.removed": "Filter deleted.", - "follow_recommendations.done": "Done", - "follow_recommendations.heading": "Follow people you'd like to see posts from! Here are some suggestions.", - "follow_recommendations.lead": "Posts from people you follow will show up in chronological order on your home feed. Don't be afraid to make mistakes, you can unfollow people just as easily any time!", + "follow_recommendations.done": "Готово", + "follow_recommendations.heading": "Підпишіться на людей, чиї дописи ви хочете бачити! Ось деякі пропозиції.", + "follow_recommendations.lead": "Дописи від людей, за якими ви стежите, з'являться в хронологічному порядку у вашій домашній стрічці. Не бійся помилятися, ви можете відписатися від людей так само легко в будь-який час!", "follow_request.authorize": "Авторизувати", "follow_request.reject": "Відмовити", - "forms.copy": "Copy", + "forms.copy": "Копіювати", "forms.hide_password": "Hide password", "forms.show_password": "Show password", "getting_started.open_source_notice": "{code_name} — програма з відкритим сирцевим кодом. Ви можете допомогти проекту, або повідомити про проблеми на GitLab за адресою {code_link} (v{code_version}).", @@ -477,21 +477,21 @@ "hashtag.column_header.tag_mode.any": "або {additional}", "hashtag.column_header.tag_mode.none": "без {additional}", "header.about.label": "About", - "header.back_to.label": "Back to {siteTitle}", - "header.home.label": "Home", - "header.login.label": "Log in", + "header.back_to.label": "Назад до {siteTitle}", + "header.home.label": "Головна", + "header.login.label": "Увійти", "home.column_settings.show_direct": "Show direct messages", "home.column_settings.show_reblogs": "Показувати передмухи", "home.column_settings.show_replies": "Показувати відповіді", "home.column_settings.title": "Home settings", - "home_column.lists": "Lists", + "home_column.lists": "Списки", "home_column_header.all": "All", "home_column_header.fediverse": "Fediverse", "home_column_header.home": "Головна", "icon_button.icons": "Icons", "icon_button.label": "Select icon", "icon_button.not_found": "No icons!! (╯°□°)╯︵ ┻━┻", - "import_data.actions.import": "Import", + "import_data.actions.import": "Імпорт", "import_data.actions.import_blocks": "Import blocks", "import_data.actions.import_follows": "Import follows", "import_data.actions.import_mutes": "Import mutes", @@ -508,7 +508,7 @@ "intervals.full.hours": "{number, plural, one {# година} few {# години} other {# годин}}", "intervals.full.minutes": "{number, plural, one {# хвилина} few {# хвилини} other {# хвилин}}", "introduction.federation.action": "Next", - "introduction.federation.home.headline": "Home", + "introduction.federation.home.headline": "Головна", "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!", "introduction.interactions.action": "Finish tutorial!", "introduction.interactions.favourite.headline": "Favorite", @@ -536,7 +536,7 @@ "keyboard_shortcuts.muted": "відкрити список заглушених користувачів", "keyboard_shortcuts.my_profile": "відкрити ваш профіль", "keyboard_shortcuts.notifications": "відкрити колонку сповіщень", - "keyboard_shortcuts.open_media": "to open media", + "keyboard_shortcuts.open_media": "відкрити медіа", "keyboard_shortcuts.pinned": "відкрити список закріплених дмухів", "keyboard_shortcuts.profile": "відкрити профіль автора", "keyboard_shortcuts.react": "to react", @@ -571,9 +571,9 @@ "login.fields.instance_placeholder": "example.com", "login.fields.otp_code_hint": "Enter the two-factor code generated by your phone app or use one of your recovery codes", "login.fields.otp_code_label": "Two-factor code:", - "login.fields.password_placeholder": "Password", + "login.fields.password_placeholder": "Пароль", "login.fields.username_placeholder": "Username", - "login.log_in": "Log in", + "login.log_in": "Увійти", "login.otp_log_in": "OTP Login", "login.otp_log_in.fail": "Invalid code, please try again.", "login.reset_password_hint": "Trouble logging in?", @@ -593,7 +593,7 @@ "mfa.setup_otp_title": "OTP Disabled", "mfa.setup_recoverycodes": "Recovery codes", "mfa.setup_warning": "Write these 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.", - "missing_description_modal.cancel": "Cancel", + "missing_description_modal.cancel": "Відмінити", "missing_description_modal.continue": "Post", "missing_description_modal.text": "You have not entered a description for all attachments. Continue anyway?", "missing_indicator.label": "Не знайдено", @@ -601,18 +601,18 @@ "morefollows.followers_label": "…and {count} more {count, plural, one {follower} other {followers}} on remote sites.", "morefollows.following_label": "…and {count} more {count, plural, one {follow} other {follows}} on remote sites.", "mute_modal.hide_notifications": "Приховати сповіщення від користувача?", - "navigation.chats": "Chats", + "navigation.chats": "Чат", "navigation.dashboard": "Dashboard", - "navigation.developers": "Developers", - "navigation.direct_messages": "Messages", - "navigation.home": "Home", + "navigation.developers": "Розробникам", + "navigation.direct_messages": "Прямі повідомлення", + "navigation.home": "Головна", "navigation.invites": "Invites", - "navigation.notifications": "Notifications", - "navigation.search": "Search", - "navigation_bar.account_aliases": "Account aliases", + "navigation.notifications": "Сповіщення", + "navigation.search": "Пошук", + "navigation_bar.account_aliases": "Псевдоніми облікового запису", "navigation_bar.admin_settings": "Admin settings", "navigation_bar.blocks": "Заблоковані користувачі", - "navigation_bar.bookmarks": "Bookmarks", + "navigation_bar.bookmarks": "Закладки", "navigation_bar.compose": "Написати новий дмух", "navigation_bar.compose_direct": "Direct message", "navigation_bar.compose_reply": "Reply to post", @@ -626,19 +626,19 @@ "navigation_bar.info": "Про сайт", "navigation_bar.invites": "Invites", "navigation_bar.keyboard_shortcuts": "Гарячі клавіші", - "navigation_bar.lists": "Lists", + "navigation_bar.lists": "Списки", "navigation_bar.logout": "Вийти", "navigation_bar.messages": "Messages", "navigation_bar.mutes": "Заглушені користувачі", "navigation_bar.pins": "Закріплені дмухи", "navigation_bar.preferences": "Налаштування", - "navigation_bar.profile_directory": "Profile directory", + "navigation_bar.profile_directory": "Переглянути профілі", "navigation_bar.security": "Безпека", "navigation_bar.soapbox_config": "Soapbox config", "notification.chat_mention": "{name} sent you a message", "notification.favourite": "{name} вподобав(-ла) ваш допис", "notification.follow": "{name} підписався(-лась) на Вас", - "notification.follow_request": "{name} has requested to follow you", + "notification.follow_request": "{name} відправив(-ла) запит на підписку", "notification.mention": "{name} згадав(-ла) Вас", "notification.move": "{name} moved to {targetName}", "notification.pleroma:emoji_reaction": "{name} reacted to your post", @@ -653,7 +653,7 @@ "notifications.column_settings.filter_bar.category": "Панель швидкого фільтру", "notifications.column_settings.filter_bar.show": "Показати", "notifications.column_settings.follow": "Нові підписники:", - "notifications.column_settings.follow_request": "New follow requests:", + "notifications.column_settings.follow_request": "Нові запити на підписку:", "notifications.column_settings.mention": "Згадки:", "notifications.column_settings.move": "Moves:", "notifications.column_settings.poll": "Результати опитування:", @@ -682,8 +682,8 @@ "poll.refresh": "Оновити", "poll.total_votes": "{count, plural, one {# голос} few {# голоси} many {# голосів} other {# голосів}}", "poll.vote": "Проголосувати", - "poll.voted": "You voted for this answer", - "poll.votes": "{votes, plural, one {# vote} other {# votes}}", + "poll.voted": "Ви голосували за цю відповідь", + "poll.votes": "{votes, plural, one {# голос} few {# голоси} many {# голосів} other {# голоса}}", "poll_button.add_poll": "Додати опитування", "poll_button.remove_poll": "Видалити опитування", "preferences.fields.auto_play_gif_label": "Auto-play animated GIFs", @@ -743,7 +743,7 @@ "registration.confirmation_modal.close": "Close", "registration.fields.confirm_placeholder": "Password (again)", "registration.fields.email_placeholder": "E-Mail address", - "registration.fields.password_placeholder": "Password", + "registration.fields.password_placeholder": "Пароль", "registration.fields.username_hint": "Only letters, numbers, and underscores are allowed.", "registration.fields.username_placeholder": "Username", "registration.lead": "With an account on {instance} you'll be able to follow people on any server in the fediverse.", @@ -764,7 +764,7 @@ "remote_instance.federation_panel.no_restrictions_message": "{siteTitle} has placed no restrictions on {host}.", "remote_instance.federation_panel.restricted_message": "{siteTitle} blocks all activities from {host}.", "remote_instance.federation_panel.some_restrictions_message": "{siteTitle} has placed some restrictions on {host}.", - "remote_instance.pin_host": "Pin {host}", + "remote_instance.pin_host": "Відкріпити {host}", "remote_instance.unpin_host": "Unpin {host}", "remote_interaction.account_placeholder": "Enter your username@domain you want to act from", "remote_interaction.divider": "or", @@ -798,7 +798,7 @@ "schedule.remove": "Remove schedule", "schedule_button.add_schedule": "Schedule post for later", "schedule_button.remove_schedule": "Post immediately", - "scheduled_status.cancel": "Cancel", + "scheduled_status.cancel": "Відмінити", "search.action": "Search for “{query}”", "search.placeholder": "Пошук", "search_results.accounts": "Люди", @@ -874,7 +874,7 @@ "status.admin_account": "Відкрити інтерфейс модерації для @{name}", "status.admin_status": "Відкрити цей статус в інтерфейсі модерації", "status.block": "Заблокувати @{name}", - "status.bookmark": "Bookmark", + "status.bookmark": "Додати в закладки", "status.bookmarked": "Bookmark added.", "status.cancel_reblog_private": "Відмінити передмухання", "status.cannot_reblog": "Цей допис не може бути передмухнутий", @@ -923,7 +923,7 @@ "status.show_thread": "Показати ланцюжок", "status.title": "Post", "status.title_direct": "Direct message", - "status.unbookmark": "Remove bookmark", + "status.unbookmark": "Видалити закладку", "status.unbookmarked": "Bookmark removed.", "status.unmute_conversation": "Зняти глушення з діалогу", "status.unpin": "Відкріпити від профілю", @@ -932,7 +932,7 @@ "suggestions.dismiss": "Відхилити пропозицію", "tabs_bar.all": "All", "tabs_bar.apps": "Apps", - "tabs_bar.chats": "Chats", + "tabs_bar.chats": "Чат", "tabs_bar.dashboard": "Dashboard", "tabs_bar.fediverse": "Fediverse", "tabs_bar.header": "Account Info", @@ -949,7 +949,7 @@ "time_remaining.moments": "Moments remaining", "time_remaining.seconds": "{number, plural, one {# секунда} few {# секунди} other {# секунд}}", "trends.count_by_accounts": "{count} {rawCount, plural, one {людина} few {людини} many {людей} other {людей}} обговорюють це", - "trends.title": "Trends", + "trends.title": "Актуальні", "ui.beforeunload": "Вашу чернетку буде втрачено, якщо ви покинете Soapbox FE.", "unauthorized_modal.footer": "Already have an account? {login}.", "unauthorized_modal.text": "You need to be logged in to do that.", @@ -963,7 +963,7 @@ "upload_form.undo": "Видалити", "upload_progress.label": "Завантаження...", "video.close": "Закрити відео", - "video.download": "Download file", + "video.download": "Завантаження файла", "video.exit_fullscreen": "Вийти з повноекранного режиму", "video.expand": "Розширити відео", "video.fullscreen": "На весь екран", diff --git a/app/soapbox/normalizers/__tests__/account-test.js b/app/soapbox/normalizers/__tests__/account-test.js new file mode 100644 index 000000000..f00dd57c5 --- /dev/null +++ b/app/soapbox/normalizers/__tests__/account-test.js @@ -0,0 +1,57 @@ +import { fromJS } from 'immutable'; + +import { normalizeAccount } from '../account'; + +describe('normalizeAccount()', () => { + it('normalizes Fedibird birthday', () => { + const account = fromJS(require('soapbox/__fixtures__/fedibird-account.json')); + const result = normalizeAccount(account); + + expect(result.get('birthday')).toEqual('1993-07-03'); + }); + + it('normalizes Pleroma birthday', () => { + const account = fromJS(require('soapbox/__fixtures__/pleroma-account.json')); + const result = normalizeAccount(account); + + expect(result.get('birthday')).toEqual('1993-07-03'); + }); + + it('normalizes Pleroma legacy fields', () => { + const account = fromJS(require('soapbox/__fixtures__/pleroma-2.2.2-account.json')); + const result = normalizeAccount(account); + + expect(result.getIn(['pleroma', 'is_active'])).toBe(true); + expect(result.getIn(['pleroma', 'is_confirmed'])).toBe(true); + expect(result.getIn(['pleroma', 'is_approved'])).toBe(true); + + expect(result.hasIn(['pleroma', 'confirmation_pending'])).toBe(false); + }); + + it('prefers new Pleroma fields', () => { + const account = fromJS(require('soapbox/__fixtures__/pleroma-account.json')); + const result = normalizeAccount(account); + + expect(result.getIn(['pleroma', 'is_active'])).toBe(true); + expect(result.getIn(['pleroma', 'is_confirmed'])).toBe(true); + expect(result.getIn(['pleroma', 'is_approved'])).toBe(true); + }); + + it('normalizes a verified Pleroma user', () => { + const account = fromJS(require('soapbox/__fixtures__/mk.json')); + const result = normalizeAccount(account); + expect(result.get('verified')).toBe(true); + }); + + it('normalizes an unverified Pleroma user', () => { + const account = fromJS(require('soapbox/__fixtures__/pleroma-account.json')); + const result = normalizeAccount(account); + expect(result.get('verified')).toBe(false); + }); + + it('normalizes a verified Truth Social user', () => { + const account = fromJS(require('soapbox/__fixtures__/realDonaldTrump.json')); + const result = normalizeAccount(account); + expect(result.get('verified')).toBe(true); + }); +}); diff --git a/app/soapbox/normalizers/account.js b/app/soapbox/normalizers/account.js new file mode 100644 index 000000000..aa49f647f --- /dev/null +++ b/app/soapbox/normalizers/account.js @@ -0,0 +1,47 @@ +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; + +import { mergeDefined } from 'soapbox/utils/normalizers'; + +// https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/549 +const normalizePleromaLegacyFields = account => { + return account.update('pleroma', ImmutableMap(), pleroma => { + return pleroma.withMutations(pleroma => { + const legacy = ImmutableMap({ + is_active: !pleroma.get('deactivated'), + is_confirmed: !pleroma.get('confirmation_pending'), + is_approved: !pleroma.get('approval_pending'), + }); + + pleroma.mergeWith(mergeDefined, legacy); + pleroma.deleteAll(['deactivated', 'confirmation_pending', 'approval_pending']); + }); + }); +}; + +// Normalize Pleroma/Fedibird birthday +const normalizeBirthday = account => { + const birthday = [ + account.getIn(['pleroma', 'birthday']), + account.getIn(['other_settings', 'birthday']), + ].find(Boolean); + + return account.set('birthday', birthday); +}; + +// Normalize Truth Social/Pleroma verified +const normalizeVerified = account => { + return account.update('verified', verified => { + return [ + verified === true, + account.getIn(['pleroma', 'tags'], ImmutableList()).includes('verified'), + ].some(Boolean); + }); +}; + +export const normalizeAccount = account => { + return account.withMutations(account => { + normalizePleromaLegacyFields(account); + normalizeVerified(account); + normalizeBirthday(account); + }); +}; diff --git a/app/soapbox/normalizers/instance.js b/app/soapbox/normalizers/instance.js index d95990670..155f6c8ae 100644 --- a/app/soapbox/normalizers/instance.js +++ b/app/soapbox/normalizers/instance.js @@ -1,6 +1,7 @@ import { Map as ImmutableMap } from 'immutable'; import { parseVersion, PLEROMA } from 'soapbox/utils/features'; +import { mergeDefined } from 'soapbox/utils/normalizers'; import { isNumber } from 'soapbox/utils/numbers'; // Use Mastodon defaults @@ -36,9 +37,6 @@ const pleromaToMastodonConfig = instance => { }); }; -// Use new value only if old value is undefined -const mergeDefined = (oldVal, newVal) => oldVal === undefined ? newVal : oldVal; - // Get the software's default attachment limit const getAttachmentLimit = software => software === PLEROMA ? Infinity : 4; diff --git a/app/soapbox/normalizers/status.js b/app/soapbox/normalizers/status.js index 8927546e8..ab6ce6312 100644 --- a/app/soapbox/normalizers/status.js +++ b/app/soapbox/normalizers/status.js @@ -1,6 +1,7 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { accountToMention } from 'soapbox/utils/accounts'; +import { mergeDefined } from 'soapbox/utils/normalizers'; // Some backends can return null, or omit these required fields const baseStatus = ImmutableMap({ @@ -40,9 +41,6 @@ const basePoll = ImmutableMap({ votes_count: 0, }); -// Merger function for only overriding undefined values -const mergeDefined = (oldVal, newVal) => oldVal === undefined ? newVal : oldVal; - // Merge base status const mergeBase = status => { return status.mergeDeepWith(mergeDefined, baseStatus); diff --git a/app/soapbox/reducers/accounts.js b/app/soapbox/reducers/accounts.js index c8b52dd37..c3cbcb9d6 100644 --- a/app/soapbox/reducers/accounts.js +++ b/app/soapbox/reducers/accounts.js @@ -30,7 +30,7 @@ import { import { CHATS_FETCH_SUCCESS, CHATS_EXPAND_SUCCESS, CHAT_FETCH_SUCCESS } from 'soapbox/actions/chats'; import { normalizeAccount as normalizeAccount2 } from 'soapbox/actions/importer/normalizer'; import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming'; -import { normalizePleromaUserFields } from 'soapbox/utils/pleroma'; +import { normalizeAccount } from 'soapbox/normalizers/account'; import { ACCOUNT_IMPORT, @@ -40,27 +40,23 @@ import { const initialState = ImmutableMap(); -const normalizePleroma = account => { - if (!account.pleroma) return account; - account.pleroma = normalizePleromaUserFields(account.pleroma); - delete account.pleroma.chat_token; - return account; -}; - -const normalizeAccount = (state, account) => { - const normalized = fromJS(normalizePleroma(account)).deleteAll([ +const minifyAccount = account => { + return account.deleteAll([ 'followers_count', 'following_count', 'statuses_count', 'source', ]); +}; +const fixAccount = (state, account) => { + const normalized = minifyAccount(normalizeAccount(fromJS(account))); return state.set(account.id, normalized); }; const normalizeAccounts = (state, accounts) => { accounts.forEach(account => { - state = normalizeAccount(state, account); + state = fixAccount(state, account); }); return state; @@ -68,7 +64,7 @@ const normalizeAccounts = (state, accounts) => { const importAccountFromChat = (state, chat) => // TODO: Fix this monstrosity - normalizeAccount(state, normalizeAccount2(chat.account)); + fixAccount(state, normalizeAccount2(chat.account)); const importAccountsFromChats = (state, chats) => state.withMutations(mutable => @@ -202,7 +198,7 @@ const setSuggested = (state, accountIds, isSuggested) => { export default function accounts(state = initialState, action) { switch(action.type) { case ACCOUNT_IMPORT: - return normalizeAccount(state, action.account); + return fixAccount(state, action.account); case ACCOUNTS_IMPORT: return normalizeAccounts(state, action.accounts); case ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP: diff --git a/app/soapbox/utils/__tests__/accounts-test.js b/app/soapbox/utils/__tests__/accounts-test.js index d3fd6f9ba..a9a77ffe1 100644 --- a/app/soapbox/utils/__tests__/accounts-test.js +++ b/app/soapbox/utils/__tests__/accounts-test.js @@ -119,7 +119,7 @@ describe('isModerator', () => { describe('accountToMention', () => { it('converts the account to a mention', () => { - const account = fromJS(require('soapbox/__fixtures__/alex.json')); + const account = fromJS(require('soapbox/__fixtures__/pleroma-account.json')); const expected = fromJS({ id: '9v5bmRalQvjOy0ECcC', diff --git a/app/soapbox/utils/accounts.ts b/app/soapbox/utils/accounts.ts index a6d418968..2300b4363 100644 --- a/app/soapbox/utils/accounts.ts +++ b/app/soapbox/utils/accounts.ts @@ -1,8 +1,4 @@ -import { - Map as ImmutableMap, - List as ImmutableList, - OrderedSet as ImmutableOrderedSet, -} from 'immutable'; +import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; const getDomainFromURL = (account: ImmutableMap): string => { try { @@ -67,11 +63,6 @@ export const isLocal = (account: ImmutableMap): boolean => { export const isRemote = (account: ImmutableMap): boolean => !isLocal(account); -export const isVerified = (account: ImmutableMap): boolean => { - const tags: any = account.getIn(['pleroma', 'tags'], ImmutableList()); - return tags.includes('verified'); -}; - export const accountToMention = (account: ImmutableMap): ImmutableMap => { return ImmutableMap({ id: account.get('id'), diff --git a/app/soapbox/utils/normalizers.js b/app/soapbox/utils/normalizers.js new file mode 100644 index 000000000..7d205f21c --- /dev/null +++ b/app/soapbox/utils/normalizers.js @@ -0,0 +1,2 @@ +// Use new value only if old value is undefined +export const mergeDefined = (oldVal, newVal) => oldVal === undefined ? newVal : oldVal; diff --git a/app/soapbox/utils/pleroma.js b/app/soapbox/utils/pleroma.js deleted file mode 100644 index dd8937938..000000000 --- a/app/soapbox/utils/pleroma.js +++ /dev/null @@ -1,10 +0,0 @@ -// https://gitlab.com/soapbox-pub/soapbox-fe/-/issues/549 -export const normalizePleromaUserFields = obj => { - obj.is_active = obj.is_active === undefined ? !obj.deactivated : obj.is_active; - obj.is_confirmed = obj.is_confirmed === undefined ? !obj.confirmation_pending : obj.is_confirmed; - obj.is_approved = obj.is_approved === undefined ? !obj.approval_pending : obj.is_approved; - delete obj.deactivated; - delete obj.confirmation_pending; - delete obj.approval_pending; - return obj; -};