diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 8232ec8ec3..13fb25d333 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.75.1. +# using RuboCop version 1.75.2. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -62,12 +62,6 @@ Style/FormatStringToken: Style/GuardClause: Enabled: false -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/HashTransformValues: - Exclude: - - 'app/serializers/rest/web_push_subscription_serializer.rb' - - 'app/services/import_service.rb' - # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: diff --git a/CHANGELOG.md b/CHANGELOG.md index c7b0f64146..4dd4783597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ All notable changes to this project will be documented in this file. +## [4.3.7] - 2025-04-02 + +### Add + +- Add delay to profile updates to debounce them (#34137 by @ClearlyClaire) +- Add support for paginating partial collections in `SynchronizeFollowersService` (#34272 and #34277 by @ClearlyClaire) + +### Changed + +- Change account suspensions to be federated to recently-followed accounts as well (#34294 by @ClearlyClaire) +- Change `AccountReachFinder` to consider statuses based on suspension date (#32805 and #34291 by @ClearlyClaire and @mjankowski) +- Change user archive signed URL TTL from 10 seconds to 1 hour (#34254 by @ClearlyClaire) + +### Fixed + +- Fix static version of animated PNG emojis not being properly extracted (#34337 by @ClearlyClaire) +- Fix filters not applying in detailed view, favourites and bookmarks (#34259 and #34260 by @ClearlyClaire) +- Fix handling of malformed/unusual HTML (#34201 by @ClearlyClaire) +- Fix `CacheBuster` being queued for missing media attachments (#34253 by @ClearlyClaire) +- Fix incorrect URL being used when cache busting (#34189 by @ClearlyClaire) +- Fix streaming server refusing unix socket path in `DATABASE_URL` (#34091 by @ClearlyClaire) +- Fix “x” hotkey not working on boosted filtered posts (#33758 by @ClearlyClaire) + ## [4.3.6] - 2025-03-13 ### Security diff --git a/Gemfile.lock b/Gemfile.lock index 80049a7dc2..1ed4b71318 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -94,7 +94,7 @@ GEM ast (2.4.3) attr_required (1.0.2) aws-eventstream (1.3.2) - aws-partitions (1.1066.0) + aws-partitions (1.1080.0) aws-sdk-core (3.215.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -126,7 +126,7 @@ GEM blurhash (0.1.8) bootsnap (1.18.4) msgpack (~> 1.2) - brakeman (7.0.0) + brakeman (7.0.2) racc browser (6.2.0) brpoplpush-redis_script (0.1.3) @@ -194,14 +194,14 @@ GEM devise_pam_authenticatable2 (9.2.0) devise (>= 4.0.0) rpam2 (~> 4.0) - diff-lcs (1.6.0) + diff-lcs (1.6.1) discard (1.4.0) activerecord (>= 4.2, < 9.0) docile (1.4.1) domain_name (0.6.20240107) - doorkeeper (5.8.1) + doorkeeper (5.8.2) railties (>= 5) - dotenv (3.1.7) + dotenv (3.1.8) drb (2.2.1) elasticsearch (7.17.11) elasticsearch-api (= 7.17.11) @@ -266,10 +266,10 @@ GEM raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) - google-protobuf (4.30.1) + google-protobuf (4.30.2) bigdecimal rake (>= 13) - googleapis-common-protos-types (1.18.0) + googleapis-common-protos-types (1.19.0) google-protobuf (>= 3.18, < 5.a) haml (6.3.0) temple (>= 0.8.2) @@ -280,7 +280,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.61.1) + haml_lint (0.62.0) haml (>= 5.0) parallel (~> 1.10) rainbow @@ -328,7 +328,7 @@ GEM activesupport (>= 3.0) nokogiri (>= 1.6) io-console (0.8.0) - irb (1.15.1) + irb (1.15.2) pp (>= 0.6.0) rdoc (>= 4.0.0) reline (>= 0.4.2) @@ -395,7 +395,7 @@ GEM rexml link_header (0.0.8) lint_roller (1.1.0) - linzer (0.6.3) + linzer (0.6.5) openssl (~> 3.0, >= 3.0.0) rack (>= 2.2, < 4.0) starry (~> 0.2) @@ -426,7 +426,7 @@ GEM mime-types (3.6.2) logger mime-types-data (~> 3.2015) - mime-types-data (3.2025.0318) + mime-types-data (3.2025.0402) mini_mime (1.1.5) mini_portile2 (2.8.8) minitest (5.25.5) @@ -688,7 +688,7 @@ GEM link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.7.0) rdf (~> 3.3) - rdoc (6.12.0) + rdoc (6.13.1) psych (>= 4.0.0) redcarpet (3.6.1) redis (4.8.1) @@ -697,7 +697,7 @@ GEM redlock (1.3.2) redis (>= 3.0.0, < 6.0) regexp_parser (2.10.0) - reline (0.6.0) + reline (0.6.1) io-console (~> 0.5) request_store (1.7.0) rack (>= 1.4) @@ -740,7 +740,7 @@ GEM rspec-mocks (~> 3.0) sidekiq (>= 5, < 9) rspec-support (3.13.2) - rubocop (1.75.1) + rubocop (1.75.2) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -748,10 +748,10 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.43.0, < 2.0) + rubocop-ast (>= 1.44.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.43.0) + rubocop-ast (1.44.0) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-capybara (2.22.1) @@ -797,7 +797,7 @@ GEM activerecord (>= 4.0.0) railties (>= 4.0.0) securerandom (0.4.1) - selenium-webdriver (4.30.1) + selenium-webdriver (4.31.0) base64 (~> 0.2) logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) @@ -840,7 +840,7 @@ GEM stoplight (4.1.1) redlock (~> 1.0) stringio (3.1.6) - strong_migrations (2.2.1) + strong_migrations (2.3.0) activerecord (>= 7) swd (2.0.3) activesupport (>= 3) @@ -851,7 +851,7 @@ GEM temple (0.10.3) terminal-table (4.0.0) unicode-display_width (>= 1.1.1, < 4) - terrapin (1.0.1) + terrapin (1.1.0) climate_control test-prof (1.4.4) thor (1.3.2) @@ -1085,4 +1085,4 @@ RUBY VERSION ruby 3.4.1p0 BUNDLED WITH - 2.6.6 + 2.6.7 diff --git a/app/controllers/api/v1/suggestions_controller.rb b/app/controllers/api/v1/suggestions_controller.rb index 918ec45beb..df9346832f 100644 --- a/app/controllers/api/v1/suggestions_controller.rb +++ b/app/controllers/api/v1/suggestions_controller.rb @@ -4,7 +4,7 @@ class Api::V1::SuggestionsController < Api::BaseController include Authorization include DeprecationConcern - deprecate_api '2021-05-16' + deprecate_api '2021-05-16', only: [:index] before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :index diff --git a/app/javascript/mastodon/actions/dropdown_menu.ts b/app/javascript/mastodon/actions/dropdown_menu.ts index 3694df1ae0..d9d395ba33 100644 --- a/app/javascript/mastodon/actions/dropdown_menu.ts +++ b/app/javascript/mastodon/actions/dropdown_menu.ts @@ -1,11 +1,11 @@ import { createAction } from '@reduxjs/toolkit'; export const openDropdownMenu = createAction<{ - id: string; + id: number; keyboard: boolean; - scrollKey: string; + scrollKey?: string; }>('dropdownMenu/open'); -export const closeDropdownMenu = createAction<{ id: string }>( +export const closeDropdownMenu = createAction<{ id: number }>( 'dropdownMenu/close', ); diff --git a/app/javascript/mastodon/actions/tags.js b/app/javascript/mastodon/actions/tags.js deleted file mode 100644 index 6e0c95288a..0000000000 --- a/app/javascript/mastodon/actions/tags.js +++ /dev/null @@ -1,81 +0,0 @@ -import api, { getLinks } from '../api'; - -export const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST'; -export const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS'; -export const FOLLOWED_HASHTAGS_FETCH_FAIL = 'FOLLOWED_HASHTAGS_FETCH_FAIL'; - -export const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUEST'; -export const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS'; -export const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL'; - -export const fetchFollowedHashtags = () => (dispatch) => { - dispatch(fetchFollowedHashtagsRequest()); - - api().get('/api/v1/followed_tags').then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(fetchFollowedHashtagsSuccess(response.data, next ? next.uri : null)); - }).catch(err => { - dispatch(fetchFollowedHashtagsFail(err)); - }); -}; - -export function fetchFollowedHashtagsRequest() { - return { - type: FOLLOWED_HASHTAGS_FETCH_REQUEST, - }; -} - -export function fetchFollowedHashtagsSuccess(followed_tags, next) { - return { - type: FOLLOWED_HASHTAGS_FETCH_SUCCESS, - followed_tags, - next, - }; -} - -export function fetchFollowedHashtagsFail(error) { - return { - type: FOLLOWED_HASHTAGS_FETCH_FAIL, - error, - }; -} - -export function expandFollowedHashtags() { - return (dispatch, getState) => { - const url = getState().getIn(['followed_tags', 'next']); - - if (url === null) { - return; - } - - dispatch(expandFollowedHashtagsRequest()); - - api().get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(expandFollowedHashtagsSuccess(response.data, next ? next.uri : null)); - }).catch(error => { - dispatch(expandFollowedHashtagsFail(error)); - }); - }; -} - -export function expandFollowedHashtagsRequest() { - return { - type: FOLLOWED_HASHTAGS_EXPAND_REQUEST, - }; -} - -export function expandFollowedHashtagsSuccess(followed_tags, next) { - return { - type: FOLLOWED_HASHTAGS_EXPAND_SUCCESS, - followed_tags, - next, - }; -} - -export function expandFollowedHashtagsFail(error) { - return { - type: FOLLOWED_HASHTAGS_EXPAND_FAIL, - error, - }; -} diff --git a/app/javascript/mastodon/api/tags.ts b/app/javascript/mastodon/api/tags.ts index 2cb802800c..4b111def81 100644 --- a/app/javascript/mastodon/api/tags.ts +++ b/app/javascript/mastodon/api/tags.ts @@ -1,4 +1,4 @@ -import { apiRequestPost, apiRequestGet } from 'mastodon/api'; +import api, { getLinks, apiRequestPost, apiRequestGet } from 'mastodon/api'; import type { ApiHashtagJSON } from 'mastodon/api_types/tags'; export const apiGetTag = (tagId: string) => @@ -9,3 +9,15 @@ export const apiFollowTag = (tagId: string) => export const apiUnfollowTag = (tagId: string) => apiRequestPost(`v1/tags/${tagId}/unfollow`); + +export const apiGetFollowedTags = async (url?: string) => { + const response = await api().request({ + method: 'GET', + url: url ?? '/api/v1/followed_tags', + }); + + return { + tags: response.data, + links: getLinks(response), + }; +}; diff --git a/app/javascript/mastodon/components/account.tsx b/app/javascript/mastodon/components/account.tsx index f43b17aef0..c6c2204085 100644 --- a/app/javascript/mastodon/components/account.tsx +++ b/app/javascript/mastodon/components/account.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react'; import type React from 'react'; -import { useCallback } from 'react'; +import { useCallback, useMemo } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; @@ -14,18 +14,19 @@ import { muteAccount, unmuteAccount, } from 'mastodon/actions/accounts'; +import { openModal } from 'mastodon/actions/modal'; import { initMuteModal } from 'mastodon/actions/mutes'; import { Avatar } from 'mastodon/components/avatar'; import { Button } from 'mastodon/components/button'; import { FollowersCounter } from 'mastodon/components/counters'; import { DisplayName } from 'mastodon/components/display_name'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { FollowButton } from 'mastodon/components/follow_button'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import { ShortNumber } from 'mastodon/components/short_number'; import { Skeleton } from 'mastodon/components/skeleton'; import { VerifiedBadge } from 'mastodon/components/verified_badge'; -import DropdownMenu from 'mastodon/containers/dropdown_menu_container'; -import { me } from 'mastodon/initial_state'; +import type { MenuItem } from 'mastodon/models/dropdown_menu'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; const messages = defineMessages({ @@ -48,6 +49,14 @@ const messages = defineMessages({ mute: { id: 'account.mute_short', defaultMessage: 'Mute' }, block: { id: 'account.block_short', defaultMessage: 'Block' }, more: { id: 'status.more', defaultMessage: 'More' }, + addToLists: { + id: 'account.add_or_remove_from_list', + defaultMessage: 'Add or Remove from lists', + }, + openOriginalPage: { + id: 'account.open_original_page', + defaultMessage: 'Open original page', + }, }); export const Account: React.FC<{ @@ -73,6 +82,7 @@ export const Account: React.FC<{ const account = useAppSelector((state) => state.accounts.get(id)); const relationship = useAppSelector((state) => state.relationships.get(id)); const dispatch = useAppDispatch(); + const accountUrl = account?.url; const handleBlock = useCallback(() => { if (relationship?.blocking) { @@ -90,13 +100,62 @@ export const Account: React.FC<{ } }, [dispatch, id, account, relationship]); - const handleMuteNotifications = useCallback(() => { - dispatch(muteAccount(id, true)); - }, [dispatch, id]); + const menu = useMemo(() => { + let arr: MenuItem[] = []; - const handleUnmuteNotifications = useCallback(() => { - dispatch(muteAccount(id, false)); - }, [dispatch, id]); + if (defaultAction === 'mute') { + const handleMuteNotifications = () => { + dispatch(muteAccount(id, true)); + }; + + const handleUnmuteNotifications = () => { + dispatch(muteAccount(id, false)); + }; + + arr = [ + { + text: intl.formatMessage( + relationship?.muting_notifications + ? messages.unmute_notifications + : messages.mute_notifications, + ), + action: relationship?.muting_notifications + ? handleUnmuteNotifications + : handleMuteNotifications, + }, + ]; + } else if (defaultAction !== 'block') { + const handleAddToLists = () => { + dispatch( + openModal({ + modalType: 'LIST_ADDER', + modalProps: { + accountId: id, + }, + }), + ); + }; + + arr = [ + { + text: intl.formatMessage(messages.addToLists), + action: handleAddToLists, + }, + ]; + + if (accountUrl) { + arr.unshift( + { + text: intl.formatMessage(messages.openOriginalPage), + href: accountUrl, + }, + null, + ); + } + } + + return arr; + }, [dispatch, intl, id, accountUrl, relationship, defaultAction]); if (hidden) { return ( @@ -107,73 +166,46 @@ export const Account: React.FC<{ ); } - let buttons; + let button: React.ReactNode, dropdown: React.ReactNode; - if (account && account.id !== me && relationship) { - const { requested, blocking, muting } = relationship; + if (menu.length > 0) { + dropdown = ( + + ); + } - if (requested) { - buttons = ; - } else if (blocking) { - buttons = ( - + ); + } else if (isExternalLinkItem(option)) { + element = ( + + {text} + + ); + } else { + element = ( + + {text} + + ); + } + + return ( +
  • + {element} +
  • + ); + }; + + const renderItemMethod = renderItem ?? nativeRenderItem; + + return ( +
    + {(loading || !items) && } + + {!loading && renderHeader && items && ( +
    + {renderHeader(items)} +
    + )} + + {!loading && items && ( +
      + {items.map((option, i) => + renderItemMethod(option, i, { + onClick: handleItemClick, + onKeyUp: handleItemKeyUp, + }), + )} +
    + )} +
    + ); +}; + +interface DropdownProps { + children?: React.ReactElement; + icon?: string; + iconComponent?: IconProp; + items?: Item[]; + loading?: boolean; + title?: string; + disabled?: boolean; + scrollable?: boolean; + active?: boolean; + scrollKey?: string; + status?: ImmutableMap; + renderItem?: RenderItemFn; + renderHeader?: RenderHeaderFn; + onOpen?: () => void; + onItemClick?: ItemClickFn; +} + +const offset = [5, 5] as OffsetValue; +const popperConfig = { strategy: 'fixed' } as UsePopperOptions; + +export const Dropdown = ({ + children, + icon, + iconComponent, + items, + loading, + title = 'Menu', + disabled, + scrollable, + active, + status, + renderItem, + renderHeader, + onOpen, + onItemClick, + scrollKey, +}: DropdownProps) => { + const dispatch = useAppDispatch(); + const openDropdownId = useAppSelector((state) => state.dropdownMenu.openId); + const openedViaKeyboard = useAppSelector( + (state) => state.dropdownMenu.keyboard, + ); + const [currentId] = useState(id++); + const open = currentId === openDropdownId; + const activeElement = useRef(null); + const targetRef = useRef(null); + + const handleClose = useCallback(() => { + if (activeElement.current) { + activeElement.current.focus({ preventScroll: true }); + activeElement.current = null; + } + + dispatch( + closeModal({ + modalType: 'ACTIONS', + ignoreFocus: false, + }), + ); + + dispatch(closeDropdownMenu({ id: currentId })); + }, [dispatch, currentId]); + + const handleItemClick = useCallback( + (e: React.MouseEvent | React.KeyboardEvent) => { + const i = Number(e.currentTarget.getAttribute('data-index')); + const item = items?.[i]; + + handleClose(); + + if (!item) { + return; + } + + if (typeof onItemClick === 'function') { + e.preventDefault(); + onItemClick(item, i); + } else if (isActionItem(item)) { + e.preventDefault(); + item.action(); + } + }, + [handleClose, onItemClick, items], + ); + + const handleClick = useCallback( + (e: React.MouseEvent | React.KeyboardEvent) => { + const { type } = e; + + if (open) { + handleClose(); + } else { + onOpen?.(); + + if (status) { + dispatch(fetchRelationships([status.getIn(['account', 'id'])])); + } + + if (isUserTouching()) { + dispatch( + openModal({ + modalType: 'ACTIONS', + modalProps: { + status, + actions: items, + onClick: handleItemClick, + }, + }), + ); + } else { + dispatch( + openDropdownMenu({ + id: currentId, + keyboard: type !== 'click', + scrollKey, + }), + ); + } + } + }, + [ + dispatch, + currentId, + scrollKey, + onOpen, + handleItemClick, + open, + status, + items, + handleClose, + ], + ); + + const handleMouseDown = useCallback(() => { + if (!open && document.activeElement instanceof HTMLElement) { + activeElement.current = document.activeElement; + } + }, [open]); + + const handleButtonKeyDown = useCallback( + (e: React.KeyboardEvent) => { + switch (e.key) { + case ' ': + case 'Enter': + handleMouseDown(); + break; + } + }, + [handleMouseDown], + ); + + const handleKeyPress = useCallback( + (e: React.KeyboardEvent) => { + switch (e.key) { + case ' ': + case 'Enter': + handleClick(e); + e.stopPropagation(); + e.preventDefault(); + break; + } + }, + [handleClick], + ); + + useEffect(() => { + return () => { + if (currentId === openDropdownId) { + handleClose(); + } + }; + }, [currentId, openDropdownId, handleClose]); + + let button: React.ReactElement; + + if (children) { + button = cloneElement(Children.only(children), { + onClick: handleClick, + onMouseDown: handleMouseDown, + onKeyDown: handleButtonKeyDown, + onKeyPress: handleKeyPress, + ref: targetRef, + }); + } else if (icon && iconComponent) { + button = ( + + ); + } else { + return null; + } + + return ( + <> + {button} + + + {({ props, arrowProps, placement }) => ( +
    +
    +
    + + +
    +
    + )} + + + ); +}; diff --git a/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js b/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js deleted file mode 100644 index 726fee9076..0000000000 --- a/app/javascript/mastodon/components/edited_timestamp/containers/dropdown_menu_container.js +++ /dev/null @@ -1,32 +0,0 @@ -import { connect } from 'react-redux'; - -import { openDropdownMenu, closeDropdownMenu } from 'mastodon/actions/dropdown_menu'; -import { fetchHistory } from 'mastodon/actions/history'; -import DropdownMenu from 'mastodon/components/dropdown_menu'; - -/** - * - * @param {import('mastodon/store').RootState} state - * @param {*} props - */ -const mapStateToProps = (state, { statusId }) => ({ - openDropdownId: state.dropdownMenu.openId, - openedViaKeyboard: state.dropdownMenu.keyboard, - items: state.getIn(['history', statusId, 'items']), - loading: state.getIn(['history', statusId, 'loading']), -}); - -const mapDispatchToProps = (dispatch, { statusId }) => ({ - - onOpen (id, onItemClick, keyboard) { - dispatch(fetchHistory(statusId)); - dispatch(openDropdownMenu({ id, keyboard })); - }, - - onClose (id) { - dispatch(closeDropdownMenu({ id })); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu); diff --git a/app/javascript/mastodon/components/edited_timestamp/index.jsx b/app/javascript/mastodon/components/edited_timestamp/index.jsx deleted file mode 100644 index f8fa043259..0000000000 --- a/app/javascript/mastodon/components/edited_timestamp/index.jsx +++ /dev/null @@ -1,77 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { FormattedMessage, injectIntl } from 'react-intl'; - -import { connect } from 'react-redux'; - -import { openModal } from 'mastodon/actions/modal'; -import { FormattedDateWrapper } from 'mastodon/components/formatted_date'; -import InlineAccount from 'mastodon/components/inline_account'; -import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; - -import DropdownMenu from './containers/dropdown_menu_container'; - -const mapDispatchToProps = (dispatch, { statusId }) => ({ - - onItemClick (index) { - dispatch(openModal({ - modalType: 'COMPARE_HISTORY', - modalProps: { index, statusId }, - })); - }, - -}); - -class EditedTimestamp extends PureComponent { - - static propTypes = { - statusId: PropTypes.string.isRequired, - timestamp: PropTypes.string.isRequired, - intl: PropTypes.object.isRequired, - onItemClick: PropTypes.func.isRequired, - }; - - handleItemClick = (item, i) => { - const { onItemClick } = this.props; - onItemClick(i); - }; - - renderHeader = items => { - return ( - - ); - }; - - renderItem = (item, index, { onClick, onKeyPress }) => { - const formattedDate = ; - const formattedName = ; - - const label = item.get('original') ? ( - - ) : ( - - ); - - return ( -
  • - -
  • - ); - }; - - render () { - const { timestamp, statusId } = this.props; - - return ( - - - - ); - } - -} - -export default connect(null, mapDispatchToProps)(injectIntl(EditedTimestamp)); diff --git a/app/javascript/mastodon/components/edited_timestamp/index.tsx b/app/javascript/mastodon/components/edited_timestamp/index.tsx new file mode 100644 index 0000000000..4a33210199 --- /dev/null +++ b/app/javascript/mastodon/components/edited_timestamp/index.tsx @@ -0,0 +1,140 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import type { Map as ImmutableMap, List as ImmutableList } from 'immutable'; + +import { fetchHistory } from 'mastodon/actions/history'; +import { openModal } from 'mastodon/actions/modal'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; +import { FormattedDateWrapper } from 'mastodon/components/formatted_date'; +import InlineAccount from 'mastodon/components/inline_account'; +import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +type HistoryItem = ImmutableMap; + +export const EditedTimestamp: React.FC<{ + statusId: string; + timestamp: string; +}> = ({ statusId, timestamp }) => { + const dispatch = useAppDispatch(); + const items = useAppSelector( + (state) => + ( + state.history.getIn([statusId, 'items']) as + | ImmutableList + | undefined + )?.toArray() as HistoryItem[], + ); + const loading = useAppSelector( + (state) => state.history.getIn([statusId, 'loading']) as boolean, + ); + + const handleOpen = useCallback(() => { + dispatch(fetchHistory(statusId)); + }, [dispatch, statusId]); + + const handleItemClick = useCallback( + (_item: HistoryItem, index: number) => { + dispatch( + openModal({ + modalType: 'COMPARE_HISTORY', + modalProps: { index, statusId }, + }), + ); + }, + [dispatch, statusId], + ); + + const renderHeader = useCallback((items: HistoryItem[]) => { + return ( + + ); + }, []); + + const renderItem = useCallback( + ( + item: HistoryItem, + index: number, + { + onClick, + onKeyUp, + }: { + onClick: React.MouseEventHandler; + onKeyUp: React.KeyboardEventHandler; + }, + ) => { + const formattedDate = ( + + ); + const formattedName = ( + + ); + + const label = (item.get('original') as boolean) ? ( + + ) : ( + + ); + + return ( +
  • + +
  • + ); + }, + [], + ); + + return ( + + items={items} + loading={loading} + renderItem={renderItem} + scrollable + renderHeader={renderHeader} + onOpen={handleOpen} + onItemClick={handleItemClick} + > + + + ); +}; diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx index 1d3fdd21b0..f21ad60240 100644 --- a/app/javascript/mastodon/components/follow_button.tsx +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -16,8 +16,7 @@ const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' }, followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' }, - mutual: { id: 'account.mutual', defaultMessage: 'Mutual' }, - edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, + editProfile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, }); export const FollowButton: React.FC<{ @@ -73,15 +72,9 @@ export const FollowButton: React.FC<{ if (!signedIn) { label = intl.formatMessage(messages.follow); } else if (accountId === me) { - label = intl.formatMessage(messages.edit_profile); + label = intl.formatMessage(messages.editProfile); } else if (!relationship) { label = ; - } else if ( - relationship.following && - isShowItem('relationships') && - relationship.followed_by - ) { - label = intl.formatMessage(messages.mutual); } else if (relationship.following || relationship.requested) { label = intl.formatMessage(messages.unfollow); } else if (relationship.followed_by && isShowItem('relationships')) { diff --git a/app/javascript/mastodon/components/hashtag.tsx b/app/javascript/mastodon/components/hashtag.tsx index 30c20e0abd..346c95183f 100644 --- a/app/javascript/mastodon/components/hashtag.tsx +++ b/app/javascript/mastodon/components/hashtag.tsx @@ -102,10 +102,11 @@ export interface HashtagProps { description?: React.ReactNode; history?: number[]; name: string; - people: number; + people?: number; to: string; uses?: number; withGraph?: boolean; + children?: React.ReactNode; } export const Hashtag: React.FC = ({ @@ -117,6 +118,7 @@ export const Hashtag: React.FC = ({ className, description, withGraph = true, + children, }) => (
    @@ -158,5 +160,7 @@ export const Hashtag: React.FC = ({
    )} + + {children &&
    {children}
    }
    ); diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx index 9e1d74bb74..ce8f17ddb9 100644 --- a/app/javascript/mastodon/components/hashtag_bar.tsx +++ b/app/javascript/mastodon/components/hashtag_bar.tsx @@ -20,6 +20,7 @@ export type StatusLike = Record<{ contentHTML: string; media_attachments: List; spoiler_text?: string; + account: Record<{ id: string }>; }>; function normalizeHashtag(hashtag: string) { @@ -195,19 +196,36 @@ export function getHashtagBarForStatus(status: StatusLike) { return { statusContentProps, - hashtagBar: , + hashtagBar: ( + + ), }; } -export function getFeaturedHashtagBar(acct: string, tags: string[]) { - return ; +export function getFeaturedHashtagBar( + accountId: string, + acct: string, + tags: string[], +) { + return ( + + ); } const HashtagBar: React.FC<{ hashtags: string[]; + accountId: string; acct?: string; defaultExpanded?: boolean; -}> = ({ hashtags, acct, defaultExpanded }) => { +}> = ({ hashtags, accountId, acct, defaultExpanded }) => { const [expanded, setExpanded] = useState(false); const handleClick = useCallback(() => { setExpanded(true); @@ -228,6 +246,7 @@ const HashtagBar: React.FC<{ #{hashtag} diff --git a/app/javascript/mastodon/components/icon_button.tsx b/app/javascript/mastodon/components/icon_button.tsx index 73fc46d281..7e0b3e7a22 100644 --- a/app/javascript/mastodon/components/icon_button.tsx +++ b/app/javascript/mastodon/components/icon_button.tsx @@ -1,4 +1,4 @@ -import { PureComponent, createRef } from 'react'; +import { useState, useEffect, useCallback, forwardRef } from 'react'; import classNames from 'classnames'; @@ -15,101 +15,110 @@ interface Props { onMouseDown?: React.MouseEventHandler; onKeyDown?: React.KeyboardEventHandler; onKeyPress?: React.KeyboardEventHandler; - active: boolean; + active?: boolean; expanded?: boolean; style?: React.CSSProperties; activeStyle?: React.CSSProperties; - disabled: boolean; + disabled?: boolean; inverted?: boolean; - animate: boolean; - overlay: boolean; - tabIndex: number; + animate?: boolean; + overlay?: boolean; + tabIndex?: number; counter?: number; href?: string; - ariaHidden: boolean; + ariaHidden?: boolean; data_id?: string; } -interface States { - activate: boolean; - deactivate: boolean; -} -export class IconButton extends PureComponent { - buttonRef = createRef(); - static defaultProps = { - active: false, - disabled: false, - animate: false, - overlay: false, - tabIndex: 0, - ariaHidden: false, - }; - - state = { - activate: false, - deactivate: false, - }; - - UNSAFE_componentWillReceiveProps(nextProps: Props) { - if (!nextProps.animate) return; - - if (this.props.active && !nextProps.active) { - this.setState({ activate: false, deactivate: true }); - } else if (!this.props.active && nextProps.active) { - this.setState({ activate: true, deactivate: false }); - } - } - - handleClick: React.MouseEventHandler = (e) => { - e.preventDefault(); - - if (!this.props.disabled && this.props.onClick != null) { - this.props.onClick(e); - } - }; - - handleKeyPress: React.KeyboardEventHandler = (e) => { - if (this.props.onKeyPress && !this.props.disabled) { - this.props.onKeyPress(e); - } - }; - - handleMouseDown: React.MouseEventHandler = (e) => { - if (!this.props.disabled && this.props.onMouseDown) { - this.props.onMouseDown(e); - } - }; - - handleKeyDown: React.KeyboardEventHandler = (e) => { - if (!this.props.disabled && this.props.onKeyDown) { - this.props.onKeyDown(e); - } - }; - - render() { - const style = { - ...this.props.style, - ...(this.props.active ? this.props.activeStyle : {}), - }; - - const { - active, +export const IconButton = forwardRef( + ( + { className, - disabled, expanded, icon, iconComponent, inverted, - overlay, - tabIndex, title, counter, href, - ariaHidden, - data_id, - } = this.props; + style, + activeStyle, + onClick, + onKeyDown, + onKeyPress, + onMouseDown, + active = false, + disabled = false, + animate = false, + overlay = false, + tabIndex = 0, + ariaHidden = false, + data_id = undefined, + }, + buttonRef, + ) => { + const [activate, setActivate] = useState(false); + const [deactivate, setDeactivate] = useState(false); - const { activate, deactivate } = this.state; + useEffect(() => { + if (!animate) { + return; + } + + if (activate && !active) { + setActivate(false); + setDeactivate(true); + } else if (!activate && active) { + setActivate(true); + setDeactivate(false); + } + }, [setActivate, setDeactivate, animate, active, activate]); + + const handleClick: React.MouseEventHandler = useCallback( + (e) => { + e.preventDefault(); + + if (!disabled) { + onClick?.(e); + } + }, + [disabled, onClick], + ); + + const handleKeyPress: React.KeyboardEventHandler = + useCallback( + (e) => { + if (!disabled) { + onKeyPress?.(e); + } + }, + [disabled, onKeyPress], + ); + + const handleMouseDown: React.MouseEventHandler = + useCallback( + (e) => { + if (!disabled) { + onMouseDown?.(e); + } + }, + [disabled, onMouseDown], + ); + + const handleKeyDown: React.KeyboardEventHandler = + useCallback( + (e) => { + if (!disabled) { + onKeyDown?.(e); + } + }, + [disabled, onKeyDown], + ); + + const buttonStyle = { + ...style, + ...(active ? activeStyle : {}), + }; const classes = classNames(className, 'icon-button', { active, @@ -148,19 +157,20 @@ export class IconButton extends PureComponent { aria-hidden={ariaHidden} title={title} className={classes} - onClick={this.handleClick} - onMouseDown={this.handleMouseDown} - onKeyDown={this.handleKeyDown} - // eslint-disable-next-line @typescript-eslint/no-deprecated - onKeyPress={this.handleKeyPress} - style={style} + onClick={handleClick} + onMouseDown={handleMouseDown} + onKeyDown={handleKeyDown} + onKeyPress={handleKeyPress} // eslint-disable-line @typescript-eslint/no-deprecated + style={buttonStyle} tabIndex={tabIndex} disabled={disabled} data-id={data_id} - ref={this.buttonRef} + ref={buttonRef} > {contents} ); - } -} + }, +); + +IconButton.displayName = 'IconButton'; diff --git a/app/javascript/mastodon/components/navigation_portal.tsx b/app/javascript/mastodon/components/navigation_portal.tsx index 08f91ce18a..d3ac8baa6e 100644 --- a/app/javascript/mastodon/components/navigation_portal.tsx +++ b/app/javascript/mastodon/components/navigation_portal.tsx @@ -1,25 +1,6 @@ -import { Switch, Route } from 'react-router-dom'; - -import AccountNavigation from 'mastodon/features/account/navigation'; import Trends from 'mastodon/features/getting_started/containers/trends_container'; import { showTrends } from 'mastodon/initial_state'; -const DefaultNavigation: React.FC = () => (showTrends ? : null); - export const NavigationPortal: React.FC = () => ( -
    - - - - - - - - - -
    +
    {showTrends && }
    ); diff --git a/app/javascript/mastodon/components/remote_hint.tsx b/app/javascript/mastodon/components/remote_hint.tsx new file mode 100644 index 0000000000..772aa805db --- /dev/null +++ b/app/javascript/mastodon/components/remote_hint.tsx @@ -0,0 +1,43 @@ +import { FormattedMessage } from 'react-intl'; + +import { useAppSelector } from 'mastodon/store'; + +import { TimelineHint } from './timeline_hint'; + +interface RemoteHintProps { + accountId?: string; +} + +export const RemoteHint: React.FC = ({ accountId }) => { + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + const domain = account?.acct ? account.acct.split('@')[1] : undefined; + if ( + !account || + !account.url || + account.acct !== account.username || + !domain + ) { + return null; + } + + return ( + + } + label={ + {domain} }} + /> + } + /> + ); +}; diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index 5e4593160c..4721e9da93 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -25,9 +25,8 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; - -import DropdownMenuContainer from '../containers/dropdown_menu_container'; import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { enableEmojiReaction , bookmarkCategoryNeeded, simpleTimelineMenu, me, isHideItem, boostMenu, boostModal } from '../initial_state'; import { IconButton } from './icon_button'; @@ -349,10 +348,9 @@ class StatusActionBar extends ImmutablePureComponent { if (writtenByMe && pinnableStatus) { menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); + menu.push(null); } - menu.push(null); - if (writtenByMe || withDismiss) { menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); menu.push(null); @@ -498,7 +496,7 @@ class StatusActionBar extends ImmutablePureComponent {
    {reblogMenu.length === 0 ? reblogButton : ( - - {reblogButton} - + /> )}
    @@ -522,7 +518,7 @@ class StatusActionBar extends ImmutablePureComponent {
    {emojiPickerDropdown}
    - ({ - openDropdownId: state.dropdownMenu.openId, - openedViaKeyboard: state.dropdownMenu.keyboard, -}); - -/** - * @param {any} dispatch - * @param {Object} root0 - * @param {any} [root0.status] - * @param {any} root0.items - * @param {any} [root0.scrollKey] - */ -const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({ - onOpen(id, onItemClick, keyboard) { - if (status) { - dispatch(fetchRelationships([status.getIn(['account', 'id'])])); - } - - dispatch(isUserTouching() ? openModal({ - modalType: 'ACTIONS', - modalProps: { - status, - actions: items, - onClick: onItemClick, - }, - }) : openDropdownMenu({ id, keyboard, scrollKey })); - }, - - onClose(id) { - dispatch(closeModal({ - modalType: 'ACTIONS', - ignoreFocus: false, - })); - dispatch(closeDropdownMenu({ id })); - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu); diff --git a/app/javascript/mastodon/features/account/components/featured_tags.jsx b/app/javascript/mastodon/features/account/components/featured_tags.jsx deleted file mode 100644 index 56a9efac02..0000000000 --- a/app/javascript/mastodon/features/account/components/featured_tags.jsx +++ /dev/null @@ -1,51 +0,0 @@ -import PropTypes from 'prop-types'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -import { Hashtag } from 'mastodon/components/hashtag'; - -const messages = defineMessages({ - lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' }, - empty: { id: 'account.featured_tags.last_status_never', defaultMessage: 'No posts' }, -}); - -class FeaturedTags extends ImmutablePureComponent { - - static propTypes = { - account: ImmutablePropTypes.record, - featuredTags: ImmutablePropTypes.list, - tagged: PropTypes.string, - intl: PropTypes.object.isRequired, - }; - - render () { - const { account, featuredTags, intl } = this.props; - - if (!account || account.get('suspended') || featuredTags.isEmpty()) { - return null; - } - - return ( -
    -

    }} />

    - - {featuredTags.take(3).map(featuredTag => ( - 0) ? intl.formatMessage(messages.lastStatusAt, { date: intl.formatDate(featuredTag.get('last_status_at'), { month: 'short', day: '2-digit' }) }) : intl.formatMessage(messages.empty)} - /> - ))} -
    - ); - } - -} - -export default injectIntl(FeaturedTags); diff --git a/app/javascript/mastodon/features/account/containers/featured_tags_container.js b/app/javascript/mastodon/features/account/containers/featured_tags_container.js deleted file mode 100644 index 726c805f78..0000000000 --- a/app/javascript/mastodon/features/account/containers/featured_tags_container.js +++ /dev/null @@ -1,17 +0,0 @@ -import { List as ImmutableList } from 'immutable'; -import { connect } from 'react-redux'; - -import { makeGetAccount } from 'mastodon/selectors'; - -import FeaturedTags from '../components/featured_tags'; - -const mapStateToProps = () => { - const getAccount = makeGetAccount(); - - return (state, { accountId }) => ({ - account: getAccount(state, accountId), - featuredTags: state.getIn(['user_lists', 'featured_tags', accountId, 'items'], ImmutableList()), - }); -}; - -export default connect(mapStateToProps)(FeaturedTags); diff --git a/app/javascript/mastodon/features/account/navigation.jsx b/app/javascript/mastodon/features/account/navigation.jsx deleted file mode 100644 index aa78135de2..0000000000 --- a/app/javascript/mastodon/features/account/navigation.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { connect } from 'react-redux'; - -import FeaturedTags from 'mastodon/features/account/containers/featured_tags_container'; -import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; - -const mapStateToProps = (state, { match: { params: { acct } } }) => { - const accountId = state.getIn(['accounts_map', normalizeForLookup(acct)]); - - if (!accountId) { - return { - isLoading: true, - }; - } - - return { - accountId, - isLoading: false, - }; -}; - -class AccountNavigation extends PureComponent { - - static propTypes = { - match: PropTypes.shape({ - params: PropTypes.shape({ - acct: PropTypes.string, - tagged: PropTypes.string, - }).isRequired, - }).isRequired, - - accountId: PropTypes.string, - isLoading: PropTypes.bool, - }; - - render () { - const { accountId, isLoading, match: { params: { tagged } } } = this.props; - - if (isLoading) { - return null; - } - - return ( - - ); - } - -} - -export default connect(mapStateToProps)(AccountNavigation); diff --git a/app/javascript/mastodon/features/account_featured/components/empty_message.tsx b/app/javascript/mastodon/features/account_featured/components/empty_message.tsx new file mode 100644 index 0000000000..9dd8ffdfe0 --- /dev/null +++ b/app/javascript/mastodon/features/account_featured/components/empty_message.tsx @@ -0,0 +1,50 @@ +import { FormattedMessage } from 'react-intl'; + +import { LimitedAccountHint } from 'mastodon/features/account_timeline/components/limited_account_hint'; + +interface EmptyMessageProps { + suspended: boolean; + hidden: boolean; + blockedBy: boolean; + accountId?: string; +} + +export const EmptyMessage: React.FC = ({ + accountId, + suspended, + hidden, + blockedBy, +}) => { + if (!accountId) { + return null; + } + + let message: React.ReactNode = null; + + if (suspended) { + message = ( + + ); + } else if (hidden) { + message = ; + } else if (blockedBy) { + message = ( + + ); + } else { + message = ( + + ); + } + + return
    {message}
    ; +}; diff --git a/app/javascript/mastodon/features/account_featured/components/featured_tag.tsx b/app/javascript/mastodon/features/account_featured/components/featured_tag.tsx new file mode 100644 index 0000000000..7b476ba01d --- /dev/null +++ b/app/javascript/mastodon/features/account_featured/components/featured_tag.tsx @@ -0,0 +1,51 @@ +import { defineMessages, useIntl } from 'react-intl'; + +import type { Map as ImmutableMap } from 'immutable'; + +import { Hashtag } from 'mastodon/components/hashtag'; + +export type TagMap = ImmutableMap< + 'id' | 'name' | 'url' | 'statuses_count' | 'last_status_at' | 'accountId', + string | null +>; + +interface FeaturedTagProps { + tag: TagMap; + account: string; +} + +const messages = defineMessages({ + lastStatusAt: { + id: 'account.featured_tags.last_status_at', + defaultMessage: 'Last post on {date}', + }, + empty: { + id: 'account.featured_tags.last_status_never', + defaultMessage: 'No posts', + }, +}); + +export const FeaturedTag: React.FC = ({ tag, account }) => { + const intl = useIntl(); + const name = tag.get('name') ?? ''; + const count = Number.parseInt(tag.get('statuses_count') ?? ''); + return ( + 0 + ? intl.formatMessage(messages.lastStatusAt, { + date: intl.formatDate(tag.get('last_status_at') ?? '', { + month: 'short', + day: '2-digit', + }), + }) + : intl.formatMessage(messages.empty) + } + /> + ); +}; diff --git a/app/javascript/mastodon/features/account_featured/index.tsx b/app/javascript/mastodon/features/account_featured/index.tsx new file mode 100644 index 0000000000..70e411f61a --- /dev/null +++ b/app/javascript/mastodon/features/account_featured/index.tsx @@ -0,0 +1,156 @@ +import { useEffect } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { useParams } from 'react-router'; + +import type { Map as ImmutableMap } from 'immutable'; +import { List as ImmutableList } from 'immutable'; + +import { fetchFeaturedTags } from 'mastodon/actions/featured_tags'; +import { expandAccountFeaturedTimeline } from 'mastodon/actions/timelines'; +import { ColumnBackButton } from 'mastodon/components/column_back_button'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; +import { RemoteHint } from 'mastodon/components/remote_hint'; +import StatusContainer from 'mastodon/containers/status_container'; +import { useAccountId } from 'mastodon/hooks/useAccountId'; +import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +import { AccountHeader } from '../account_timeline/components/account_header'; +import Column from '../ui/components/column'; + +import { EmptyMessage } from './components/empty_message'; +import { FeaturedTag } from './components/featured_tag'; +import type { TagMap } from './components/featured_tag'; + +interface Params { + acct?: string; + id?: string; +} + +const AccountFeatured = () => { + const accountId = useAccountId(); + const { suspended, blockedBy, hidden } = useAccountVisibility(accountId); + const forceEmptyState = suspended || blockedBy || hidden; + const { acct = '' } = useParams(); + + const dispatch = useAppDispatch(); + + useEffect(() => { + if (accountId) { + void dispatch(expandAccountFeaturedTimeline(accountId)); + dispatch(fetchFeaturedTags(accountId)); + } + }, [accountId, dispatch]); + + const isLoading = useAppSelector( + (state) => + !accountId || + !!(state.timelines as ImmutableMap).getIn([ + `account:${accountId}:pinned`, + 'isLoading', + ]) || + !!state.user_lists.getIn(['featured_tags', accountId, 'isLoading']), + ); + const featuredTags = useAppSelector( + (state) => + state.user_lists.getIn( + ['featured_tags', accountId, 'items'], + ImmutableList(), + ) as ImmutableList, + ); + const featuredStatusIds = useAppSelector( + (state) => + (state.timelines as ImmutableMap).getIn( + [`account:${accountId}:pinned`, 'items'], + ImmutableList(), + ) as ImmutableList, + ); + + if (isLoading) { + return ( + +
    + +
    +
    + ); + } + + if (featuredStatusIds.isEmpty() && featuredTags.isEmpty()) { + return ( + + + ); + } + + return ( + + + +
    + {accountId && ( + + )} + {!featuredTags.isEmpty() && ( + <> +

    + +

    + {featuredTags.map((tag) => ( + + ))} + + )} + {!featuredStatusIds.isEmpty() && ( + <> +

    + +

    + {featuredStatusIds.map((statusId) => ( + + ))} + + )} + +
    +
    + ); +}; + +const AccountFeaturedWrapper = ({ + children, + accountId, +}: React.PropsWithChildren<{ accountId?: string }>) => { + return ( + + +
    + {accountId && } + {children} +
    +
    + ); +}; + +// eslint-disable-next-line import/no-default-export +export default AccountFeatured; diff --git a/app/javascript/mastodon/features/account_gallery/index.tsx b/app/javascript/mastodon/features/account_gallery/index.tsx index 60afdadc81..0027329c93 100644 --- a/app/javascript/mastodon/features/account_gallery/index.tsx +++ b/app/javascript/mastodon/features/account_gallery/index.tsx @@ -2,25 +2,22 @@ import { useEffect, useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import { useParams } from 'react-router-dom'; - import { createSelector } from '@reduxjs/toolkit'; import type { Map as ImmutableMap } from 'immutable'; import { List as ImmutableList } from 'immutable'; -import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts'; import { openModal } from 'mastodon/actions/modal'; import { expandAccountMediaTimeline } from 'mastodon/actions/timelines'; import { ColumnBackButton } from 'mastodon/components/column_back_button'; +import { RemoteHint } from 'mastodon/components/remote_hint'; import ScrollableList from 'mastodon/components/scrollable_list'; -import { TimelineHint } from 'mastodon/components/timeline_hint'; import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header'; import { LimitedAccountHint } from 'mastodon/features/account_timeline/components/limited_account_hint'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; import Column from 'mastodon/features/ui/components/column'; +import { useAccountId } from 'mastodon/hooks/useAccountId'; +import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility'; import type { MediaAttachment } from 'mastodon/models/media_attachment'; -import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; -import { getAccountHidden } from 'mastodon/selectors/accounts'; import type { RootState } from 'mastodon/store'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -56,53 +53,11 @@ const getAccountGallery = createSelector( }, ); -interface Params { - acct?: string; - id?: string; -} - -const RemoteHint: React.FC<{ - accountId: string; -}> = ({ accountId }) => { - const account = useAppSelector((state) => state.accounts.get(accountId)); - const acct = account?.acct; - const url = account?.url; - const domain = acct ? acct.split('@')[1] : undefined; - - if (!url) { - return null; - } - - return ( - - } - label={ - {domain} }} - /> - } - /> - ); -}; - export const AccountGallery: React.FC<{ multiColumn: boolean; }> = ({ multiColumn }) => { - const { acct, id } = useParams(); const dispatch = useAppDispatch(); - const accountId = useAppSelector( - (state) => - id ?? - (state.accounts_map.get(normalizeForLookup(acct)) as string | undefined), - ); + const accountId = useAccountId(); const attachments = useAppSelector((state) => accountId ? getAccountGallery(state, accountId) @@ -123,33 +78,15 @@ export const AccountGallery: React.FC<{ const account = useAppSelector((state) => accountId ? state.accounts.get(accountId) : undefined, ); - const blockedBy = useAppSelector( - (state) => - state.relationships.getIn([accountId, 'blocked_by'], false) as boolean, - ); - const suspended = useAppSelector( - (state) => state.accounts.getIn([accountId, 'suspended'], false) as boolean, - ); const isAccount = !!account; - const remote = account?.acct !== account?.username; - const hidden = useAppSelector((state) => - accountId ? getAccountHidden(state, accountId) : false, - ); + + const { suspended, blockedBy, hidden } = useAccountVisibility(accountId); + const maxId = attachments.last()?.getIn(['status', 'id']) as | string | undefined; useEffect(() => { - if (!accountId) { - dispatch(lookupAccount(acct)); - } - }, [dispatch, accountId, acct]); - - useEffect(() => { - if (accountId && !isAccount) { - dispatch(fetchAccount(accountId)); - } - if (accountId && isAccount) { void dispatch(expandAccountMediaTimeline(accountId)); } @@ -233,7 +170,7 @@ export const AccountGallery: React.FC<{ defaultMessage='Profile unavailable' /> ); - } else if (remote && attachments.isEmpty()) { + } else if (attachments.isEmpty()) { emptyMessage = ; } else { emptyMessage = ( @@ -259,7 +196,7 @@ export const AccountGallery: React.FC<{ ) } alwaysPrepend - append={remote && accountId && } + append={accountId && } scrollKey='account_gallery' isLoading={isLoading} hasMore={!forceEmptyState && hasMore} diff --git a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx index 0d4f20795c..f78ee58f81 100644 --- a/app/javascript/mastodon/features/account_timeline/components/account_header.tsx +++ b/app/javascript/mastodon/features/account_timeline/components/account_header.tsx @@ -44,27 +44,21 @@ import { FollowingCounter, StatusesCounter, } from 'mastodon/components/counters'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; +import { FollowButton } from 'mastodon/components/follow_button'; import { FormattedDateWrapper } from 'mastodon/components/formatted_date'; import { getFeaturedHashtagBar } from 'mastodon/components/hashtag_bar'; import { Icon } from 'mastodon/components/icon'; import { IconButton } from 'mastodon/components/icon_button'; -import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { ShortNumber } from 'mastodon/components/short_number'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import { DomainPill } from 'mastodon/features/account/components/domain_pill'; import AccountNoteContainer from 'mastodon/features/account/containers/account_note_container'; import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container'; import { useLinks } from 'mastodon/hooks/useLinks'; import { useIdentity } from 'mastodon/identity_context'; -import { - autoPlayGif, - me, - domain as localDomain, - isShowItem, -} from 'mastodon/initial_state'; +import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state'; import type { Account } from 'mastodon/models/account'; -import type { DropdownMenu } from 'mastodon/models/dropdown_menu'; -import type { Relationship } from 'mastodon/models/relationship'; +import type { MenuItem } from 'mastodon/models/dropdown_menu'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION, @@ -204,24 +198,6 @@ const titleFromAccount = (account: Account) => { return `${prefix} (@${acct})`; }; -const messageForFollowButton = (relationship?: Relationship) => { - if (!relationship) return messages.follow; - - if ( - relationship.get('following') && - relationship.get('followed_by') && - isShowItem('relationships') - ) { - return messages.mutual; - } else if (relationship.get('following') || relationship.get('requested')) { - return messages.unfollow; - } else if (relationship.get('followed_by') && isShowItem('relationships')) { - return messages.followBack; - } else { - return messages.follow; - } -}; - const dateFormatOptions: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', @@ -249,20 +225,6 @@ export const AccountHeader: React.FC<{ ); const handleLinkClick = useLinks(); - const handleFollow = useCallback(() => { - if (!account) { - return; - } - - if (relationship?.following || relationship?.requested) { - dispatch( - openModal({ modalType: 'CONFIRM_UNFOLLOW', modalProps: { account } }), - ); - } else { - dispatch(followAccount(account.id)); - } - }, [dispatch, account, relationship]); - const handleBlock = useCallback(() => { if (!account) { return; @@ -446,23 +408,6 @@ export const AccountHeader: React.FC<{ ); }, [dispatch, account]); - const handleInteractionModal = useCallback(() => { - if (!account) { - return; - } - - dispatch( - openModal({ - modalType: 'INTERACTION', - modalProps: { - type: 'follow', - accountId: account.id, - url: account.uri, - }, - }), - ); - }, [dispatch, account]); - const handleOpenAvatar = useCallback( (e: React.MouseEvent) => { if (e.button !== 0 || e.ctrlKey || e.metaKey) { @@ -498,10 +443,6 @@ export const AccountHeader: React.FC<{ }); }, [account]); - const handleEditProfile = useCallback(() => { - window.open('/settings/profile', '_blank'); - }, []); - const handleMouseEnter = useCallback( ({ currentTarget }: React.MouseEvent) => { if (autoPlayGif) { @@ -537,7 +478,7 @@ export const AccountHeader: React.FC<{ const remoteDomain = isRemote ? account?.acct.split('@')[1] : null; const menu = useMemo(() => { - const arr: DropdownMenu = []; + const arr: MenuItem[] = []; if (!account) { return arr; @@ -778,9 +719,12 @@ export const AccountHeader: React.FC<{ return null; } - let actionBtn, bellBtn, lockedIcon, shareBtn; + let actionBtn: React.ReactNode, + bellBtn: React.ReactNode, + lockedIcon: React.ReactNode, + shareBtn: React.ReactNode; - const info = []; + const info: React.ReactNode[] = []; if (me !== account.id && relationship?.blocking) { info.push( @@ -848,43 +792,17 @@ export const AccountHeader: React.FC<{ ); } - if (me !== account.id) { - if (signedIn && !relationship) { - // Wait until the relationship is loaded - actionBtn = ( - - ); - } else if (!relationship?.blocking) { - actionBtn = ( -
    diff --git a/app/javascript/mastodon/features/bookmark_category_statuses/index.jsx b/app/javascript/mastodon/features/bookmark_category_statuses/index.jsx deleted file mode 100644 index 6a3feb46a4..0000000000 --- a/app/javascript/mastodon/features/bookmark_category_statuses/index.jsx +++ /dev/null @@ -1,195 +0,0 @@ -import PropTypes from 'prop-types'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - - -import { Helmet } from 'react-helmet'; -import { withRouter } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - - -import { debounce } from 'lodash'; - -import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg'; -import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; -import EditIcon from '@/material-icons/400-24px/edit.svg?react'; -import { deleteBookmarkCategory, expandBookmarkCategoryStatuses, fetchBookmarkCategory, fetchBookmarkCategoryStatuses } from 'mastodon/actions/bookmark_categories'; -import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; -import { openModal } from 'mastodon/actions/modal'; -import Column from 'mastodon/components/column'; -import ColumnHeader from 'mastodon/components/column_header'; -import { Icon } from 'mastodon/components/icon'; -import { LoadingIndicator } from 'mastodon/components/loading_indicator'; -import StatusList from 'mastodon/components/status_list'; -import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; -import { getBookmarkCategoryStatusList } from 'mastodon/selectors'; -import { WithRouterPropTypes } from 'mastodon/utils/react_router'; - - -const messages = defineMessages({ - deleteMessage: { id: 'confirmations.delete_bookmark_category.message', defaultMessage: 'Are you sure you want to permanently delete this category?' }, - deleteConfirm: { id: 'confirmations.delete_bookmark_category.confirm', defaultMessage: 'Delete' }, - heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, -}); - -const mapStateToProps = (state, { params }) => ({ - bookmarkCategory: state.getIn(['bookmark_categories', params.id]), - statusIds: getBookmarkCategoryStatusList(state, params.id), - isLoading: state.getIn(['status_lists', 'bookmark_category_statuses', params.id, 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'bookmark_category_statuses', params.id, 'next']), -}); - -class BookmarkCategoryStatuses extends ImmutablePureComponent { - - static propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - bookmarkCategory: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]), - intl: PropTypes.object.isRequired, - columnId: PropTypes.string, - multiColumn: PropTypes.bool, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - ...WithRouterPropTypes, - }; - - UNSAFE_componentWillMount () { - this.props.dispatch(fetchBookmarkCategory(this.props.params.id)); - this.props.dispatch(fetchBookmarkCategoryStatuses(this.props.params.id)); - } - - UNSAFE_componentWillReceiveProps (nextProps) { - const { dispatch } = this.props; - const { id } = nextProps.params; - - if (id !== this.props.params.id) { - dispatch(fetchBookmarkCategory(id)); - dispatch(fetchBookmarkCategoryStatuses(id)); - } - } - - handlePin = () => { - const { columnId, dispatch } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn('BOOKMARKS_EX', { id: this.props.params.id })); - this.props.history.push('/'); - } - }; - - handleMove = (dir) => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - handleEditClick = () => { - this.props.history.push(`/bookmark_categories/${this.props.params.id}/edit`); - }; - - handleDeleteClick = () => { - const { dispatch, columnId, intl } = this.props; - const { id } = this.props.params; - - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => { - dispatch(deleteBookmarkCategory(id)); - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - this.props.history.push('/bookmark_categories'); - } - }, - }, - })); - }; - - setRef = c => { - this.column = c; - }; - - handleLoadMore = debounce(() => { - this.props.dispatch(expandBookmarkCategoryStatuses(this.props.params.id)); - }, 300, { leading: true }); - - render () { - const { intl, bookmarkCategory, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; - const pinned = !!columnId; - - if (typeof bookmarkCategory === 'undefined') { - return ( - -
    - -
    -
    - ); - } else if (bookmarkCategory === false) { - return ( - - ); - } - - const emptyMessage = ; - - return ( - - -
    -
    - - - -
    -
    -
    - - - - - {intl.formatMessage(messages.heading)} - - -
    - ); - } - -} - -export default withRouter(connect(mapStateToProps)(injectIntl(BookmarkCategoryStatuses))); diff --git a/app/javascript/mastodon/features/bookmark_category_statuses/index.tsx b/app/javascript/mastodon/features/bookmark_category_statuses/index.tsx new file mode 100644 index 0000000000..7facdcc449 --- /dev/null +++ b/app/javascript/mastodon/features/bookmark_category_statuses/index.tsx @@ -0,0 +1,121 @@ +import { useEffect, useRef, useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; +import { useParams } from 'react-router'; + +import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; +import { + expandBookmarkCategoryStatuses, + fetchBookmarkCategory, + fetchBookmarkCategoryStatuses, +} from 'mastodon/actions/bookmark_categories'; +import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusList from 'mastodon/components/status_list'; +import { getSubStatusList } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const BookmarkCategoryStatuses: React.FC<{ + columnId: string; + multiColumn: boolean; +}> = ({ columnId, multiColumn }) => { + const dispatch = useAppDispatch(); + const { id } = useParams<{ id: string }>(); + const columnRef = useRef(null); + const statusIds = useAppSelector((state) => + getSubStatusList(state, 'bookmark_category', id), + ); + const isLoading = useAppSelector( + (state) => + state.status_lists.getIn( + ['bookmark_category_statuses', id, 'isLoading'], + true, + ) as boolean, + ); + const hasMore = useAppSelector( + (state) => + !!state.status_lists.getIn(['bookmark_category_statuses', id, 'next']), + ); + const bookmarkCategory = useAppSelector((state) => + state.bookmark_categories.get(id), + ); + + useEffect(() => { + dispatch(fetchBookmarkCategory(id)); + dispatch(fetchBookmarkCategoryStatuses(id)); + }, [dispatch, id]); + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('BOOKMARKS_EX', { id })); + } + }, [dispatch, columnId, id]); + + const handleMove = useCallback( + (dir: number) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback(() => { + dispatch(expandBookmarkCategoryStatuses(id)); + }, [dispatch, id]); + + const pinned = !!columnId; + + const emptyMessage = ( + + ); + + return ( + + + + + + + {bookmarkCategory?.get('title')} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default BookmarkCategoryStatuses; diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx b/app/javascript/mastodon/features/bookmarked_statuses/index.jsx deleted file mode 100644 index 284342d4ee..0000000000 --- a/app/javascript/mastodon/features/bookmarked_statuses/index.jsx +++ /dev/null @@ -1,117 +0,0 @@ -// Kmyblue tracking marker: copied bookmark_category_statuses, circle_statuses - -import PropTypes from 'prop-types'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -import { Helmet } from 'react-helmet'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - -import { debounce } from 'lodash'; - -import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; -import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'mastodon/actions/bookmarks'; -import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; -import Column from 'mastodon/components/column'; -import ColumnHeader from 'mastodon/components/column_header'; -import StatusList from 'mastodon/components/status_list'; -import { getStatusList } from 'mastodon/selectors'; - -const messages = defineMessages({ - heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, -}); - -const mapStateToProps = state => ({ - statusIds: getStatusList(state, 'bookmarks'), - isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']), -}); - -class Bookmarks extends ImmutablePureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - intl: PropTypes.object.isRequired, - columnId: PropTypes.string, - multiColumn: PropTypes.bool, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - }; - - UNSAFE_componentWillMount () { - this.props.dispatch(fetchBookmarkedStatuses()); - } - - handlePin = () => { - const { columnId, dispatch } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn('BOOKMARKS', {})); - } - }; - - handleMove = (dir) => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - setRef = c => { - this.column = c; - }; - - handleLoadMore = debounce(() => { - this.props.dispatch(expandBookmarkedStatuses()); - }, 300, { leading: true }); - - render () { - const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; - const pinned = !!columnId; - - const emptyMessage = ; - - return ( - - - - - - - {intl.formatMessage(messages.heading)} - - - - ); - } - -} - -export default connect(mapStateToProps)(injectIntl(Bookmarks)); diff --git a/app/javascript/mastodon/features/bookmarked_statuses/index.tsx b/app/javascript/mastodon/features/bookmarked_statuses/index.tsx new file mode 100644 index 0000000000..5d4574b05b --- /dev/null +++ b/app/javascript/mastodon/features/bookmarked_statuses/index.tsx @@ -0,0 +1,116 @@ +import { useEffect, useRef, useCallback } from 'react'; + +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; + +import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react'; +import { + fetchBookmarkedStatuses, + expandBookmarkedStatuses, +} from 'mastodon/actions/bookmarks'; +import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusList from 'mastodon/components/status_list'; +import { getStatusList } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, +}); + +const Bookmarks: React.FC<{ + columnId: string; + multiColumn: boolean; +}> = ({ columnId, multiColumn }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const columnRef = useRef(null); + const statusIds = useAppSelector((state) => + getStatusList(state, 'bookmarks'), + ); + const isLoading = useAppSelector( + (state) => + state.status_lists.getIn(['bookmarks', 'isLoading'], true) as boolean, + ); + const hasMore = useAppSelector( + (state) => !!state.status_lists.getIn(['bookmarks', 'next']), + ); + + useEffect(() => { + dispatch(fetchBookmarkedStatuses()); + }, [dispatch]); + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('BOOKMARKS', {})); + } + }, [dispatch, columnId]); + + const handleMove = useCallback( + (dir: number) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback(() => { + dispatch(expandBookmarkedStatuses()); + }, [dispatch]); + + const pinned = !!columnId; + + const emptyMessage = ( + + ); + + return ( + + + + + + + {intl.formatMessage(messages.heading)} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default Bookmarks; diff --git a/app/javascript/mastodon/features/circle_statuses/index.jsx b/app/javascript/mastodon/features/circle_statuses/index.jsx deleted file mode 100644 index 1c0ee455e8..0000000000 --- a/app/javascript/mastodon/features/circle_statuses/index.jsx +++ /dev/null @@ -1,195 +0,0 @@ -import PropTypes from 'prop-types'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - - -import { Helmet } from 'react-helmet'; -import { withRouter } from 'react-router-dom'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - - -import { debounce } from 'lodash'; - -import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react'; -import DeleteIcon from '@/material-icons/400-24px/delete.svg?react'; -import EditIcon from '@/material-icons/400-24px/edit.svg?react'; -import { deleteCircle, expandCircleStatuses, fetchCircle, fetchCircleStatuses } from 'mastodon/actions/circles'; -import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; -import { openModal } from 'mastodon/actions/modal'; -import Column from 'mastodon/components/column'; -import ColumnHeader from 'mastodon/components/column_header'; -import { Icon } from 'mastodon/components/icon'; -import { LoadingIndicator } from 'mastodon/components/loading_indicator'; -import StatusList from 'mastodon/components/status_list'; -import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; -import { getCircleStatusList } from 'mastodon/selectors'; -import { WithRouterPropTypes } from 'mastodon/utils/react_router'; - - -const messages = defineMessages({ - deleteMessage: { id: 'confirmations.delete_circle.message', defaultMessage: 'Are you sure you want to permanently delete this circle?' }, - deleteConfirm: { id: 'confirmations.delete_circle.confirm', defaultMessage: 'Delete' }, - heading: { id: 'column.circles', defaultMessage: 'Circles' }, -}); - -const mapStateToProps = (state, { params }) => ({ - circle: state.getIn(['circles', params.id]), - statusIds: getCircleStatusList(state, params.id), - isLoading: state.getIn(['status_lists', 'circle_statuses', params.id, 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'circle_statuses', params.id, 'next']), -}); - -class CircleStatuses extends ImmutablePureComponent { - - static propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - circle: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]), - intl: PropTypes.object.isRequired, - columnId: PropTypes.string, - multiColumn: PropTypes.bool, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - ...WithRouterPropTypes, - }; - - UNSAFE_componentWillMount () { - this.props.dispatch(fetchCircle(this.props.params.id)); - this.props.dispatch(fetchCircleStatuses(this.props.params.id)); - } - - UNSAFE_componentWillReceiveProps (nextProps) { - const { dispatch } = this.props; - const { id } = nextProps.params; - - if (id !== this.props.params.id) { - dispatch(fetchCircle(id)); - dispatch(fetchCircleStatuses(id)); - } - } - - handlePin = () => { - const { columnId, dispatch } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn('CIRCLE_STATUSES', { id: this.props.params.id })); - this.props.history.push('/'); - } - }; - - handleMove = (dir) => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - handleEditClick = () => { - this.props.history.push(`/circles/${this.props.params.id}/edit`); - }; - - handleDeleteClick = () => { - const { dispatch, columnId, intl } = this.props; - const { id } = this.props.params; - - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => { - dispatch(deleteCircle(id)); - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - this.props.history.push('/circles'); - } - }, - }, - })); - }; - - setRef = c => { - this.column = c; - }; - - handleLoadMore = debounce(() => { - this.props.dispatch(expandCircleStatuses(this.props.params.id)); - }, 300, { leading: true }); - - render () { - const { intl, circle, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; - const pinned = !!columnId; - - if (typeof circle === 'undefined') { - return ( - -
    - -
    -
    - ); - } else if (circle === false) { - return ( - - ); - } - - const emptyMessage = ; - - return ( - - -
    -
    - - - -
    -
    -
    - - - - - {intl.formatMessage(messages.heading)} - - -
    - ); - } - -} - -export default withRouter(connect(mapStateToProps)(injectIntl(CircleStatuses))); diff --git a/app/javascript/mastodon/features/circle_statuses/index.tsx b/app/javascript/mastodon/features/circle_statuses/index.tsx new file mode 100644 index 0000000000..2f962a5f13 --- /dev/null +++ b/app/javascript/mastodon/features/circle_statuses/index.tsx @@ -0,0 +1,118 @@ +import { useEffect, useRef, useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; +import { useParams } from 'react-router'; + +import CircleIcon from '@/material-icons/400-24px/account_circle.svg?react'; +import { + expandCircleStatuses, + fetchCircle, + fetchCircleStatuses, +} from 'mastodon/actions/circles'; +import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusList from 'mastodon/components/status_list'; +import { getSubStatusList } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const CircleStatuses: React.FC<{ + columnId: string; + multiColumn: boolean; +}> = ({ columnId, multiColumn }) => { + const dispatch = useAppDispatch(); + const { id } = useParams<{ id: string }>(); + const columnRef = useRef(null); + const statusIds = useAppSelector((state) => + getSubStatusList(state, 'circle', id), + ); + const isLoading = useAppSelector( + (state) => + state.status_lists.getIn( + ['circle_statuses', id, 'isLoading'], + true, + ) as boolean, + ); + const hasMore = useAppSelector( + (state) => !!state.status_lists.getIn(['circle_statuses', id, 'next']), + ); + const circle = useAppSelector((state) => state.circles.get(id)); + + useEffect(() => { + dispatch(fetchCircle(id)); + dispatch(fetchCircleStatuses(id)); + }, [dispatch, id]); + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('CIRCLE_STATUSES', { id })); + } + }, [dispatch, columnId, id]); + + const handleMove = useCallback( + (dir: number) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback(() => { + dispatch(expandCircleStatuses(id)); + }, [dispatch, id]); + + const pinned = !!columnId; + + const emptyMessage = ( + + ); + + return ( + + + + + + + {circle?.get('title')} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default CircleStatuses; diff --git a/app/javascript/mastodon/features/circles/index.tsx b/app/javascript/mastodon/features/circles/index.tsx index e1d02e1633..38291febc7 100644 --- a/app/javascript/mastodon/features/circles/index.tsx +++ b/app/javascript/mastodon/features/circles/index.tsx @@ -13,9 +13,9 @@ import { fetchCircles } from 'mastodon/actions/circles'; import { openModal } from 'mastodon/actions/modal'; import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import { getOrderedCircles } from 'mastodon/selectors/circles'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -60,12 +60,11 @@ const CircleItem: React.FC<{ {title} - diff --git a/app/javascript/mastodon/features/compose/components/action_bar.jsx b/app/javascript/mastodon/features/compose/components/action_bar.tsx similarity index 50% rename from app/javascript/mastodon/features/compose/components/action_bar.jsx rename to app/javascript/mastodon/features/compose/components/action_bar.tsx index 5abc93d877..4a92485209 100644 --- a/app/javascript/mastodon/features/compose/components/action_bar.jsx +++ b/app/javascript/mastodon/features/compose/components/action_bar.tsx @@ -2,67 +2,94 @@ import { useMemo } from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { useDispatch } from 'react-redux'; - import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { openModal } from 'mastodon/actions/modal'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; +import { useAppDispatch } from 'mastodon/store'; const messages = defineMessages({ edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' }, - preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, - reaction_deck: { id: 'navigation_bar.reaction_deck', defaultMessage: 'Reaction deck' }, - follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, + preferences: { + id: 'navigation_bar.preferences', + defaultMessage: 'Preferences', + }, + reaction_deck: { + id: 'navigation_bar.reaction_deck', + defaultMessage: 'Reaction deck', + }, + follow_requests: { + id: 'navigation_bar.follow_requests', + defaultMessage: 'Follow requests', + }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' }, - emoji_reactions: { id: 'navigation_bar.emoji_reactions', defaultMessage: 'Stamps' }, + emoji_reactions: { + id: 'navigation_bar.emoji_reactions', + defaultMessage: 'Stamps', + }, lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, - followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' }, + followed_tags: { + id: 'navigation_bar.followed_tags', + defaultMessage: 'Followed hashtags', + }, blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, - domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' }, + domain_blocks: { + id: 'navigation_bar.domain_blocks', + defaultMessage: 'Blocked domains', + }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, }); -export const ActionBar = () => { - const dispatch = useDispatch(); +export const ActionBar: React.FC = () => { + const dispatch = useAppDispatch(); const intl = useIntl(); const menu = useMemo(() => { const handleLogoutClick = () => { - dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' })); + dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} })); }; - return ([ - { text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }, - { text: intl.formatMessage(messages.preferences), href: '/settings/preferences' }, + return [ + { + text: intl.formatMessage(messages.edit_profile), + href: '/settings/profile', + }, + { + text: intl.formatMessage(messages.preferences), + href: '/settings/preferences', + }, { text: intl.formatMessage(messages.pins), to: '/pinned' }, null, - { text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' }, + { + text: intl.formatMessage(messages.follow_requests), + to: '/follow_requests', + }, { text: intl.formatMessage(messages.favourites), to: '/favourites' }, { text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' }, - { text: intl.formatMessage(messages.emoji_reactions), to: '/emoji_reactions' }, + { + text: intl.formatMessage(messages.emoji_reactions), + to: '/emoji_reactions', + }, { text: intl.formatMessage(messages.lists), to: '/lists' }, - { text: intl.formatMessage(messages.followed_tags), to: '/followed_tags' }, + { + text: intl.formatMessage(messages.followed_tags), + to: '/followed_tags', + }, null, { text: intl.formatMessage(messages.mutes), to: '/mutes' }, { text: intl.formatMessage(messages.blocks), to: '/blocks' }, - { text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' }, + { + text: intl.formatMessage(messages.domain_blocks), + to: '/domain_blocks', + }, { text: intl.formatMessage(messages.filters), href: '/filters' }, null, { text: intl.formatMessage(messages.logout), action: handleLogoutClick }, - ]); + ]; }, [intl, dispatch]); - return ( - - ); + return ; }; diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx index 0d154db1e1..c27cd3727f 100644 --- a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx @@ -24,7 +24,7 @@ import AvatarComposite from 'mastodon/components/avatar_composite'; import { IconButton } from 'mastodon/components/icon_button'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import StatusContent from 'mastodon/components/status_content'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { autoPlayGif } from 'mastodon/initial_state'; import { makeGetStatus } from 'mastodon/selectors'; @@ -205,7 +205,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
    - ({ - statusIds: state.getIn(['status_lists', 'emoji_reactions', 'items']), - isLoading: state.getIn(['status_lists', 'emoji_reactions', 'isLoading'], true), - hasMore: !!state.getIn(['status_lists', 'emoji_reactions', 'next']), -}); - -class EmojiReactions extends ImmutablePureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - intl: PropTypes.object.isRequired, - columnId: PropTypes.string, - multiColumn: PropTypes.bool, - hasMore: PropTypes.bool, - isLoading: PropTypes.bool, - }; - - componentWillMount () { - this.props.dispatch(fetchEmojiReactedStatuses()); - } - - handlePin = () => { - const { columnId, dispatch } = this.props; - - if (columnId) { - dispatch(removeColumn(columnId)); - } else { - dispatch(addColumn('EMOJI_REACTIONS', {})); - } - }; - - handleMove = (dir) => { - const { columnId, dispatch } = this.props; - dispatch(moveColumn(columnId, dir)); - }; - - handleHeaderClick = () => { - this.column.scrollTop(); - }; - - setRef = c => { - this.column = c; - }; - - handleLoadMore = debounce(() => { - this.props.dispatch(expandEmojiReactedStatuses()); - }, 300, { leading: true }); - - render () { - const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; - const pinned = !!columnId; - - const emptyMessage = ; - - return ( - - - - - - - {intl.formatMessage(messages.heading)} - - - - ); - } - -} - -export default connect(mapStateToProps)(injectIntl(EmojiReactions)); diff --git a/app/javascript/mastodon/features/emoji_reacted_statuses/index.tsx b/app/javascript/mastodon/features/emoji_reacted_statuses/index.tsx new file mode 100644 index 0000000000..58186cbbd7 --- /dev/null +++ b/app/javascript/mastodon/features/emoji_reacted_statuses/index.tsx @@ -0,0 +1,118 @@ +import { useEffect, useRef, useCallback } from 'react'; + +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; + +import EmojiReactionIcon from '@/material-icons/400-24px/mood.svg?react'; +import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; +import { + fetchEmojiReactedStatuses, + expandEmojiReactedStatuses, +} from 'mastodon/actions/emoji_reactions'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusList from 'mastodon/components/status_list'; +import { getStatusList } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + heading: { id: 'column.emoji_reactions', defaultMessage: 'Stamps' }, +}); + +const Favourites: React.FC<{ columnId: string; multiColumn: boolean }> = ({ + columnId, + multiColumn, +}) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + const columnRef = useRef(null); + const statusIds = useAppSelector((state) => + getStatusList(state, 'emoji_reactions'), + ); + const isLoading = useAppSelector( + (state) => + state.status_lists.getIn( + ['emoji_reactions', 'isLoading'], + true, + ) as boolean, + ); + const hasMore = useAppSelector( + (state) => !!state.status_lists.getIn(['emoji_reactions', 'next']), + ); + + useEffect(() => { + dispatch(fetchEmojiReactedStatuses()); + }, [dispatch]); + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('EMOJI_REACTIONS', {})); + } + }, [dispatch, columnId]); + + const handleMove = useCallback( + (dir: number) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback(() => { + dispatch(expandEmojiReactedStatuses()); + }, [dispatch]); + + const pinned = !!columnId; + + const emptyMessage = ( + + ); + + return ( + + + + + + + {intl.formatMessage(messages.heading)} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default Favourites; diff --git a/app/javascript/mastodon/features/explore/components/card.jsx b/app/javascript/mastodon/features/explore/components/card.jsx index 15470ec24c..9617781b53 100644 --- a/app/javascript/mastodon/features/explore/components/card.jsx +++ b/app/javascript/mastodon/features/explore/components/card.jsx @@ -25,7 +25,7 @@ export const Card = ({ id, source }) => { const dispatch = useDispatch(); const handleDismiss = useCallback(() => { - dispatch(dismissSuggestion(id)); + dispatch(dismissSuggestion({ accountId: id })); }, [id, dispatch]); let label; diff --git a/app/javascript/mastodon/features/favourited_statuses/index.tsx b/app/javascript/mastodon/features/favourited_statuses/index.tsx new file mode 100644 index 0000000000..908a8ae4a1 --- /dev/null +++ b/app/javascript/mastodon/features/favourited_statuses/index.tsx @@ -0,0 +1,116 @@ +import { useEffect, useRef, useCallback } from 'react'; + +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; + +import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; +import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; +import { + fetchFavouritedStatuses, + expandFavouritedStatuses, +} from 'mastodon/actions/favourites'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusList from 'mastodon/components/status_list'; +import { getStatusList } from 'mastodon/selectors'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + heading: { id: 'column.favourites', defaultMessage: 'Favorites' }, +}); + +const Favourites: React.FC<{ columnId: string; multiColumn: boolean }> = ({ + columnId, + multiColumn, +}) => { + const dispatch = useAppDispatch(); + const intl = useIntl(); + const columnRef = useRef(null); + const statusIds = useAppSelector((state) => + getStatusList(state, 'favourites'), + ); + const isLoading = useAppSelector( + (state) => + state.status_lists.getIn(['favourites', 'isLoading'], true) as boolean, + ); + const hasMore = useAppSelector( + (state) => !!state.status_lists.getIn(['favourites', 'next']), + ); + + useEffect(() => { + dispatch(fetchFavouritedStatuses()); + }, [dispatch]); + + const handlePin = useCallback(() => { + if (columnId) { + dispatch(removeColumn(columnId)); + } else { + dispatch(addColumn('FAVOURITES', {})); + } + }, [dispatch, columnId]); + + const handleMove = useCallback( + (dir: number) => { + dispatch(moveColumn(columnId, dir)); + }, + [dispatch, columnId], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback(() => { + dispatch(expandFavouritedStatuses()); + }, [dispatch]); + + const pinned = !!columnId; + + const emptyMessage = ( + + ); + + return ( + + + + + + + {intl.formatMessage(messages.heading)} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default Favourites; diff --git a/app/javascript/mastodon/features/followed_tags/index.jsx b/app/javascript/mastodon/features/followed_tags/index.jsx deleted file mode 100644 index 21248e6de9..0000000000 --- a/app/javascript/mastodon/features/followed_tags/index.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import PropTypes from 'prop-types'; - -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -import { Helmet } from 'react-helmet'; - -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { connect } from 'react-redux'; - -import { debounce } from 'lodash'; - -import TagIcon from '@/material-icons/400-24px/tag.svg?react'; -import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags'; -import ColumnHeader from 'mastodon/components/column_header'; -import { Hashtag } from 'mastodon/components/hashtag'; -import ScrollableList from 'mastodon/components/scrollable_list'; -import Column from 'mastodon/features/ui/components/column'; - -const messages = defineMessages({ - heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' }, -}); - -const mapStateToProps = state => ({ - hashtags: state.getIn(['followed_tags', 'items']), - isLoading: state.getIn(['followed_tags', 'isLoading'], true), - hasMore: !!state.getIn(['followed_tags', 'next']), -}); - -class FollowedTags extends ImmutablePureComponent { - - static propTypes = { - params: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - hashtags: ImmutablePropTypes.list, - isLoading: PropTypes.bool, - hasMore: PropTypes.bool, - multiColumn: PropTypes.bool, - }; - - componentDidMount() { - this.props.dispatch(fetchFollowedHashtags()); - } - - handleLoadMore = debounce(() => { - this.props.dispatch(expandFollowedHashtags()); - }, 300, { leading: true }); - - render () { - const { intl, hashtags, isLoading, hasMore, multiColumn } = this.props; - - const emptyMessage = ; - - return ( - - - - - {hashtags.map((hashtag) => ( - day.get('uses')).toArray()} - /> - ))} - - - - - - - ); - } - -} - -export default connect(mapStateToProps)(injectIntl(FollowedTags)); diff --git a/app/javascript/mastodon/features/followed_tags/index.tsx b/app/javascript/mastodon/features/followed_tags/index.tsx new file mode 100644 index 0000000000..21d63a6fec --- /dev/null +++ b/app/javascript/mastodon/features/followed_tags/index.tsx @@ -0,0 +1,161 @@ +import { useEffect, useState, useCallback, useRef } from 'react'; + +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; + +import { Helmet } from 'react-helmet'; + +import { isFulfilled } from '@reduxjs/toolkit'; + +import TagIcon from '@/material-icons/400-24px/tag.svg?react'; +import { unfollowHashtag } from 'mastodon/actions/tags_typed'; +import { apiGetFollowedTags } from 'mastodon/api/tags'; +import type { ApiHashtagJSON } from 'mastodon/api_types/tags'; +import { Button } from 'mastodon/components/button'; +import { Column } from 'mastodon/components/column'; +import type { ColumnRef } from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import { Hashtag } from 'mastodon/components/hashtag'; +import ScrollableList from 'mastodon/components/scrollable_list'; +import { useAppDispatch } from 'mastodon/store'; + +const messages = defineMessages({ + heading: { id: 'followed_tags', defaultMessage: 'Followed hashtags' }, +}); + +const FollowedTag: React.FC<{ + tag: ApiHashtagJSON; + onUnfollow: (arg0: string) => void; +}> = ({ tag, onUnfollow }) => { + const dispatch = useAppDispatch(); + const tagId = tag.name; + + const handleClick = useCallback(() => { + void dispatch(unfollowHashtag({ tagId })).then((result) => { + if (isFulfilled(result)) { + onUnfollow(tagId); + } + + return ''; + }); + }, [dispatch, onUnfollow, tagId]); + + const people = + parseInt(tag.history[0].accounts) + + parseInt(tag.history[1]?.accounts ?? ''); + + return ( + + + + ); +}; + +const FollowedTags: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => { + const intl = useIntl(); + const [tags, setTags] = useState([]); + const [loading, setLoading] = useState(false); + const [next, setNext] = useState(); + const hasMore = !!next; + const columnRef = useRef(null); + + useEffect(() => { + setLoading(true); + + void apiGetFollowedTags() + .then(({ tags, links }) => { + const next = links.refs.find((link) => link.rel === 'next'); + + setTags(tags); + setLoading(false); + setNext(next?.uri); + + return ''; + }) + .catch(() => { + setLoading(false); + }); + }, [setTags, setLoading, setNext]); + + const handleLoadMore = useCallback(() => { + setLoading(true); + + void apiGetFollowedTags(next) + .then(({ tags, links }) => { + const next = links.refs.find((link) => link.rel === 'next'); + + setLoading(false); + setTags((previousTags) => [...previousTags, ...tags]); + setNext(next?.uri); + + return ''; + }) + .catch(() => { + setLoading(false); + }); + }, [setTags, setLoading, setNext, next]); + + const handleUnfollow = useCallback( + (tagId: string) => { + setTags((tags) => tags.filter((tag) => tag.name !== tagId)); + }, + [setTags], + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const emptyMessage = ( + + ); + + return ( + + + + + {tags.map((tag) => ( + + ))} + + + + {intl.formatMessage(messages.heading)} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default FollowedTags; diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx index 7372fe5284..b7c1ea02d9 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx +++ b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx @@ -12,8 +12,8 @@ import { } from 'mastodon/actions/tags_typed'; import type { ApiHashtagJSON } from 'mastodon/api_types/tags'; import { Button } from 'mastodon/components/button'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { ShortNumber } from 'mastodon/components/short_number'; -import DropdownMenu from 'mastodon/containers/dropdown_menu_container'; import { useIdentity } from 'mastodon/identity_context'; import { PERMISSION_MANAGE_TAXONOMIES } from 'mastodon/permissions'; import { useAppDispatch } from 'mastodon/store'; @@ -153,13 +153,11 @@ export const HashtagHeader: React.FC<{
    {menu.length > 0 && ( - )} diff --git a/app/javascript/mastodon/features/lists/index.tsx b/app/javascript/mastodon/features/lists/index.tsx index ad172908bd..e8f55bc0ad 100644 --- a/app/javascript/mastodon/features/lists/index.tsx +++ b/app/javascript/mastodon/features/lists/index.tsx @@ -13,9 +13,9 @@ import { fetchLists } from 'mastodon/actions/lists'; import { openModal } from 'mastodon/actions/modal'; import { Column } from 'mastodon/components/column'; import { ColumnHeader } from 'mastodon/components/column_header'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; import { getOrderedLists } from 'mastodon/selectors/lists'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; @@ -72,12 +72,11 @@ const ListItem: React.FC<{ -
    diff --git a/app/javascript/mastodon/features/notifications/components/notification_request.jsx b/app/javascript/mastodon/features/notifications/components/notification_request.jsx index 626929ae50..381bb1153f 100644 --- a/app/javascript/mastodon/features/notifications/components/notification_request.jsx +++ b/app/javascript/mastodon/features/notifications/components/notification_request.jsx @@ -17,7 +17,7 @@ import { initReport } from 'mastodon/actions/reports'; import { Avatar } from 'mastodon/components/avatar'; import { CheckBox } from 'mastodon/components/check_box'; import { IconButton } from 'mastodon/components/icon_button'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { makeGetAccount } from 'mastodon/selectors'; import { toCappedNumber } from 'mastodon/utils/numbers'; @@ -105,11 +105,10 @@ export const NotificationRequest = ({ id, accountId, notificationsCount, checked
    -
    diff --git a/app/javascript/mastodon/features/notifications/requests.jsx b/app/javascript/mastodon/features/notifications/requests.jsx index ccaed312b4..b2bdd0ec77 100644 --- a/app/javascript/mastodon/features/notifications/requests.jsx +++ b/app/javascript/mastodon/features/notifications/requests.jsx @@ -23,7 +23,7 @@ import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; import ScrollableList from 'mastodon/components/scrollable_list'; -import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; +import { Dropdown } from 'mastodon/components/dropdown_menu'; import { NotificationRequest } from './components/notification_request'; import { PolicyControls } from './components/policy_controls'; @@ -126,7 +126,7 @@ const SelectRow = ({selectAllChecked, toggleSelectAll, selectedItems, selectionM
    0 && !selectAllChecked} onChange={handleSelectAll} />
    - - +
    ); diff --git a/app/javascript/mastodon/features/status/components/detailed_status.tsx b/app/javascript/mastodon/features/status/components/detailed_status.tsx index 406847382a..e0a68ad263 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.tsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.tsx @@ -14,7 +14,7 @@ import { Link } from 'react-router-dom'; import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; import { AnimatedNumber } from 'mastodon/components/animated_number'; import { ContentWarning } from 'mastodon/components/content_warning'; -import EditedTimestamp from 'mastodon/components/edited_timestamp'; +import { EditedTimestamp } from 'mastodon/components/edited_timestamp'; import { FilterWarning } from 'mastodon/components/filter_warning'; import { FormattedDateWrapper } from 'mastodon/components/formatted_date'; import type { StatusLike } from 'mastodon/components/hashtag_bar'; diff --git a/app/javascript/mastodon/features/ui/components/hashtag_menu_controller.tsx b/app/javascript/mastodon/features/ui/components/hashtag_menu_controller.tsx new file mode 100644 index 0000000000..6707b24672 --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/hashtag_menu_controller.tsx @@ -0,0 +1,157 @@ +import { useEffect, useRef, useState, useCallback, useMemo } from 'react'; + +import { useIntl, defineMessages } from 'react-intl'; + +import { useLocation } from 'react-router-dom'; + +import Overlay from 'react-overlays/Overlay'; +import type { + OffsetValue, + UsePopperOptions, +} from 'react-overlays/esm/usePopper'; + +import { DropdownMenu } from 'mastodon/components/dropdown_menu'; +import { useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + browseHashtag: { + id: 'hashtag.browse', + defaultMessage: 'Browse posts in #{hashtag}', + }, + browseHashtagFromAccount: { + id: 'hashtag.browse_from_account', + defaultMessage: 'Browse posts from @{name} in #{hashtag}', + }, + muteHashtag: { id: 'hashtag.mute', defaultMessage: 'Mute #{hashtag}' }, +}); + +const offset = [5, 5] as OffsetValue; +const popperConfig = { strategy: 'fixed' } as UsePopperOptions; + +const isHashtagLink = ( + element: HTMLAnchorElement | null, +): element is HTMLAnchorElement => { + if (!element) { + return false; + } + + return element.matches('[data-menu-hashtag]'); +}; + +interface TargetParams { + hashtag?: string; + accountId?: string; +} + +export const HashtagMenuController: React.FC = () => { + const intl = useIntl(); + const [open, setOpen] = useState(false); + const [{ accountId, hashtag }, setTargetParams] = useState({}); + const targetRef = useRef(null); + const location = useLocation(); + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + + useEffect(() => { + setOpen(false); + targetRef.current = null; + }, [setOpen, location]); + + useEffect(() => { + const handleClick = (e: MouseEvent) => { + const target = (e.target as HTMLElement).closest('a'); + + if (e.button !== 0 || e.ctrlKey || e.metaKey) { + return; + } + + if (!isHashtagLink(target)) { + return; + } + + const hashtag = target.text.replace(/^#/, ''); + const accountId = target.getAttribute('data-menu-hashtag'); + + if (!hashtag || !accountId) { + return; + } + + e.preventDefault(); + e.stopPropagation(); + targetRef.current = target; + setOpen(true); + setTargetParams({ hashtag, accountId }); + }; + + document.addEventListener('click', handleClick, { capture: true }); + + return () => { + document.removeEventListener('click', handleClick); + }; + }, [setTargetParams, setOpen]); + + const handleClose = useCallback(() => { + setOpen(false); + targetRef.current = null; + }, [setOpen]); + + const menu = useMemo( + () => [ + { + text: intl.formatMessage(messages.browseHashtag, { + hashtag, + }), + to: `/tags/${hashtag}`, + }, + { + text: intl.formatMessage(messages.browseHashtagFromAccount, { + hashtag, + name: account?.username, + }), + to: `/@${account?.acct}/tagged/${hashtag}`, + }, + null, + { + text: intl.formatMessage(messages.muteHashtag, { + hashtag, + }), + href: '/filters', + dangerous: true, + }, + ], + [intl, hashtag, account], + ); + + if (!open) { + return null; + } + + return ( + + {({ props, arrowProps, placement }) => ( +
    +
    +
    + + +
    +
    + )} + + ); +}; diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 883b863be0..7d190b3ce7 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -31,6 +31,7 @@ import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding import BundleColumnError from './components/bundle_column_error'; import Header from './components/header'; import { UploadArea } from './components/upload_area'; +import { HashtagMenuController } from './components/hashtag_menu_controller'; import ColumnsAreaContainer from './containers/columns_area_container'; import LoadingBarContainer from './containers/loading_bar_container'; import ModalContainer from './containers/modal_container'; @@ -91,6 +92,7 @@ import { BookmarkCategoryEdit, ReactionDeck, TermsOfService, + AccountFeatured, } from './util/async-components'; import { ColumnsContextProvider } from './util/columns_context'; import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; @@ -272,6 +274,7 @@ class SwitchingColumnsArea extends PureComponent { + @@ -658,6 +661,7 @@ class UI extends PureComponent { {layout !== 'mobile' && } {!disableHoverCards && } + diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index d8a1641c07..21dc902ae5 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -82,6 +82,10 @@ export function AccountGallery () { return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery'); } +export function AccountFeatured() { + return import(/* webpackChunkName: "features/account_featured" */'../../account_featured'); +} + export function Followers () { return import(/* webpackChunkName: "features/followers" */'../../followers'); } diff --git a/app/javascript/mastodon/hooks/useAccountId.ts b/app/javascript/mastodon/hooks/useAccountId.ts new file mode 100644 index 0000000000..1cc819ca59 --- /dev/null +++ b/app/javascript/mastodon/hooks/useAccountId.ts @@ -0,0 +1,37 @@ +import { useEffect } from 'react'; + +import { useParams } from 'react-router'; + +import { fetchAccount, lookupAccount } from 'mastodon/actions/accounts'; +import { normalizeForLookup } from 'mastodon/reducers/accounts_map'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +interface Params { + acct?: string; + id?: string; +} + +export function useAccountId() { + const { acct, id } = useParams(); + const accountId = useAppSelector( + (state) => + id ?? + (state.accounts_map.get(normalizeForLookup(acct)) as string | undefined), + ); + + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + const isAccount = !!account; + + const dispatch = useAppDispatch(); + useEffect(() => { + if (!accountId) { + dispatch(lookupAccount(acct)); + } else if (!isAccount) { + dispatch(fetchAccount(accountId)); + } + }, [dispatch, accountId, acct, isAccount]); + + return accountId; +} diff --git a/app/javascript/mastodon/hooks/useAccountVisibility.ts b/app/javascript/mastodon/hooks/useAccountVisibility.ts new file mode 100644 index 0000000000..55651af5a0 --- /dev/null +++ b/app/javascript/mastodon/hooks/useAccountVisibility.ts @@ -0,0 +1,20 @@ +import { getAccountHidden } from 'mastodon/selectors/accounts'; +import { useAppSelector } from 'mastodon/store'; + +export function useAccountVisibility(accountId?: string) { + const blockedBy = useAppSelector( + (state) => !!state.relationships.getIn([accountId, 'blocked_by'], false), + ); + const suspended = useAppSelector( + (state) => !!state.accounts.getIn([accountId, 'suspended'], false), + ); + const hidden = useAppSelector((state) => + accountId ? Boolean(getAccountHidden(state, accountId)) : false, + ); + + return { + blockedBy, + suspended, + hidden, + }; +} diff --git a/app/javascript/mastodon/locales/an.json b/app/javascript/mastodon/locales/an.json index 605c86f73e..49b6f41eac 100644 --- a/app/javascript/mastodon/locales/an.json +++ b/app/javascript/mastodon/locales/an.json @@ -25,7 +25,6 @@ "account.endorse": "Amostrar en perfil", "account.featured_tags.last_status_at": "Zaguera publicación lo {date}", "account.featured_tags.last_status_never": "Sin publicacions", - "account.featured_tags.title": "Etiquetas destacadas de {name}", "account.follow": "Seguir", "account.followers": "Seguidores", "account.followers.empty": "Encara no sigue dengún a este usuario.", diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index fc8e559dfc..326dd8fbc5 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -29,7 +29,6 @@ "account.endorse": "أوصِ به على صفحتك الشخصية", "account.featured_tags.last_status_at": "آخر منشور في {date}", "account.featured_tags.last_status_never": "لا توجد رسائل", - "account.featured_tags.title": "وسوم {name} المميَّزة", "account.follow": "متابعة", "account.follow_back": "تابعه بالمثل", "account.followers": "مُتابِعون", diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json index d43a0276dc..5edce9a4d8 100644 --- a/app/javascript/mastodon/locales/ast.json +++ b/app/javascript/mastodon/locales/ast.json @@ -27,7 +27,6 @@ "account.enable_notifications": "Avisame cuando @{name} espublice artículos", "account.endorse": "Destacar nel perfil", "account.featured_tags.last_status_never": "Nun hai nenguna publicación", - "account.featured_tags.title": "Etiquetes destacaes de: {name}", "account.follow": "Siguir", "account.follow_back": "Siguir tamién", "account.followers": "Siguidores", diff --git a/app/javascript/mastodon/locales/az.json b/app/javascript/mastodon/locales/az.json index 6a52c706b4..550312f31d 100644 --- a/app/javascript/mastodon/locales/az.json +++ b/app/javascript/mastodon/locales/az.json @@ -29,7 +29,6 @@ "account.endorse": "Profildə seçilmişlərə əlavə et", "account.featured_tags.last_status_at": "Son paylaşım {date} tarixində olub", "account.featured_tags.last_status_never": "Paylaşım yoxdur", - "account.featured_tags.title": "{name} istifadəçisinin seçilmiş heşteqləri", "account.follow": "İzlə", "account.follow_back": "Sən də izlə", "account.followers": "İzləyicilər", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 6c6e10270f..9011fdfd63 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -29,7 +29,6 @@ "account.endorse": "Паказваць у профілі", "account.featured_tags.last_status_at": "Апошні допіс ад {date}", "account.featured_tags.last_status_never": "Няма допісаў", - "account.featured_tags.title": "Тэгі, выбраныя {name}", "account.follow": "Падпісацца", "account.follow_back": "Падпісацца ў адказ", "account.followers": "Падпісчыкі", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index bd2d37c681..5c032755ff 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -29,7 +29,6 @@ "account.endorse": "Представи в профила", "account.featured_tags.last_status_at": "Последна публикация на {date}", "account.featured_tags.last_status_never": "Няма публикации", - "account.featured_tags.title": "Главни хаштагове на {name}", "account.follow": "Последване", "account.follow_back": "Последване взаимно", "account.followers": "Последователи", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} публикация} other {{counter} публикации}}", "account.unblock": "Отблокиране на @{name}", "account.unblock_domain": "Отблокиране на домейн {domain}", + "account.unblock_domain_short": "Отблокиране", "account.unblock_short": "Отблокиране", "account.unendorse": "Не включвайте в профила", "account.unfollow": "Стоп на следването", @@ -674,7 +674,7 @@ "onboarding.follows.title": "Последвайте хора, за да започнете", "onboarding.profile.discoverable": "Правене на моя профил откриваем", "onboarding.profile.discoverable_hint": "Включвайки откриваемостта в Mastodon, вашите публикации може да се появят при резултатите от търсене и изгряващи неща, и вашия профил може да бъде предложен на хора с подобни интереси като вашите.", - "onboarding.profile.display_name": "Името на показ", + "onboarding.profile.display_name": "Показвано име", "onboarding.profile.display_name_hint": "Вашето пълно име или псевдоним…", "onboarding.profile.note": "Биография", "onboarding.profile.note_hint": "Може да @споменавате други хора или #хаштагове…", diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json index ed2b06289f..ec0f4eb447 100644 --- a/app/javascript/mastodon/locales/bn.json +++ b/app/javascript/mastodon/locales/bn.json @@ -29,7 +29,6 @@ "account.endorse": "প্রোফাইলে ফিচার করুন", "account.featured_tags.last_status_at": "{date} এ সর্বশেষ পোস্ট", "account.featured_tags.last_status_never": "কোনো পোস্ট নেই", - "account.featured_tags.title": "{name} এর ফিচার করা Hashtag সমূহ", "account.follow": "অনুসরণ", "account.follow_back": "তাকে অনুসরণ করো", "account.followers": "অনুসরণকারী", diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index fad38721b0..51e3723d19 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -28,7 +28,6 @@ "account.endorse": "Lakaat war-wel war ar profil", "account.featured_tags.last_status_at": "Toud diwezhañ : {date}", "account.featured_tags.last_status_never": "Embannadur ebet", - "account.featured_tags.title": "Hashtagoù pennañ {name}", "account.follow": "Heuliañ", "account.follow_back": "Heuliañ d'ho tro", "account.followers": "Tud koumanantet", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index cdf79c8edc..d6ebbbc1d8 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -29,7 +29,6 @@ "account.endorse": "Recomana en el perfil", "account.featured_tags.last_status_at": "Darrer tut el {date}", "account.featured_tags.last_status_never": "No hi ha tuts", - "account.featured_tags.title": "etiquetes destacades de {name}", "account.follow": "Segueix", "account.follow_back": "Segueix tu també", "account.followers": "Seguidors", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publicació} other {{counter} publicacions}}", "account.unblock": "Desbloca @{name}", "account.unblock_domain": "Desbloca el domini {domain}", + "account.unblock_domain_short": "Desbloca", "account.unblock_short": "Desbloca", "account.unendorse": "No recomanis en el perfil", "account.unfollow": "Deixa de seguir", diff --git a/app/javascript/mastodon/locales/ckb.json b/app/javascript/mastodon/locales/ckb.json index 765eacd080..31f2dbbc11 100644 --- a/app/javascript/mastodon/locales/ckb.json +++ b/app/javascript/mastodon/locales/ckb.json @@ -28,7 +28,6 @@ "account.endorse": "ناساندن لە پرۆفایل", "account.featured_tags.last_status_at": "دوایین پۆست لە {date}", "account.featured_tags.last_status_never": "هیچ پۆستێک نییە", - "account.featured_tags.title": "هاشتاگە تایبەتەکانی {name}", "account.follow": "بەدواداچوون", "account.follow_back": "فۆڵۆو بکەنەوە", "account.followers": "شوێنکەوتووان", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index deced039c2..25657f9cbf 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -29,7 +29,6 @@ "account.endorse": "Zvýraznit na profilu", "account.featured_tags.last_status_at": "Poslední příspěvek {date}", "account.featured_tags.last_status_never": "Žádné příspěvky", - "account.featured_tags.title": "Hlavní hashtagy uživatele {name}", "account.follow": "Sledovat", "account.follow_back": "Také sledovat", "account.followers": "Sledující", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} many {{counter} příspěvků} other {{counter} příspěvků}}", "account.unblock": "Odblokovat @{name}", "account.unblock_domain": "Odblokovat doménu {domain}", + "account.unblock_domain_short": "Odblokovat", "account.unblock_short": "Odblokovat", "account.unendorse": "Nezvýrazňovat na profilu", "account.unfollow": "Přestat sledovat", @@ -79,7 +79,7 @@ "admin.dashboard.retention.cohort_size": "Noví uživatelé", "admin.impact_report.instance_accounts": "Profily účtů, které by byli odstaněny", "admin.impact_report.instance_followers": "Sledující, o které by naši uživatelé přišli", - "admin.impact_report.instance_follows": "Sledující, o které by naši uživatelé přišli", + "admin.impact_report.instance_follows": "Sledující, o které by jejich uživatelé přišli", "admin.impact_report.title": "Shrnutí dopadu", "alert.rate_limited.message": "Zkuste to prosím znovu po {retry_time, time, medium}.", "alert.rate_limited.title": "Spojení omezena", @@ -101,7 +101,7 @@ "annual_report.summary.archetype.replier": "Sociální motýlek", "annual_report.summary.followers.followers": "sledujících", "annual_report.summary.followers.total": "{count} celkem", - "annual_report.summary.here_it_is": "Zde je tvůj {year} v přehledu:", + "annual_report.summary.here_it_is": "Zde je tvůj rok {year} v přehledu:", "annual_report.summary.highlighted_post.by_favourites": "nejvíce oblíbený příspěvek", "annual_report.summary.highlighted_post.by_reblogs": "nejvíce boostovaný příspěvek", "annual_report.summary.highlighted_post.by_replies": "příspěvek s nejvíce odpověďmi", @@ -267,7 +267,7 @@ "domain_pill.activitypub_like_language": "ActivityPub je jako jazyk, kterým Mastodon mluví s jinými sociálními sítěmi.", "domain_pill.server": "Server", "domain_pill.their_handle": "Handle:", - "domain_pill.their_server": "Jejich digitální domov, kde žijí jejich všechny příspěvky.", + "domain_pill.their_server": "Jejich digitální domov, kde žijí všechny jejich příspěvky.", "domain_pill.their_username": "Jejich jedinečný identifikátor na jejich serveru. Je možné, že na jiných serverech jsou uživatelé se stejným uživatelským jménem.", "domain_pill.username": "Uživatelské jméno", "domain_pill.whats_in_a_handle": "Co obsahuje handle?", @@ -559,7 +559,7 @@ "notification.admin.sign_up.name_and_others": "{name} a {count, plural, one {# další} few {# další} many {# dalších} other {# dalších}} se zaregistrovali", "notification.annual_report.message": "Váš #Wrapstodon {year} na Vás čeká! Podívejte se, jak vypadal tento Váš rok na Mastodonu!", "notification.annual_report.view": "Zobrazit #Wrapstodon", - "notification.favourite": "{name} si oblíbil*a váš příspěvek", + "notification.favourite": "{name} si oblíbil váš příspěvek", "notification.favourite.name_and_others_with_link": "{name} a {count, plural, one {# další si oblíbil} few {# další si oblíbili} other {# dalších si oblíbilo}} Váš příspěvek", "notification.favourite_pm": "{name} si oblíbil vaši soukromou zmínku", "notification.favourite_pm.name_and_others_with_link": "{name} a {count, plural, one {# další si oblíbil} few {# další si oblíbili} other {# dalších si oblíbilo}} Vaši soukromou zmínku", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index c107cbebe3..3bf10be7fb 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -27,9 +27,11 @@ "account.edit_profile": "Golygu proffil", "account.enable_notifications": "Rhowch wybod i fi pan fydd @{name} yn postio", "account.endorse": "Dangos ar fy mhroffil", + "account.featured": "Dethol", + "account.featured.hashtags": "Hashnodau", + "account.featured.posts": "Postiadau", "account.featured_tags.last_status_at": "Y postiad olaf ar {date}", "account.featured_tags.last_status_never": "Dim postiadau", - "account.featured_tags.title": "Prif hashnodau {name}", "account.follow": "Dilyn", "account.follow_back": "Dilyn nôl", "account.followers": "Dilynwyr", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} postiad} two {{counter} bostiad} few {{counter} phostiad} many {{counter} postiad} other {{counter} postiad}}", "account.unblock": "Dadrwystro @{name}", "account.unblock_domain": "Dadrwystro parth {domain}", + "account.unblock_domain_short": "Dadrwystro", "account.unblock_short": "Dadrwystro", "account.unendorse": "Peidio a'i ddangos ar fy mhroffil", "account.unfollow": "Dad-ddilyn", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Canlyniadau chwilio", "emoji_button.symbols": "Symbolau", "emoji_button.travel": "Teithio a Llefydd", + "empty_column.account_featured": "Mae'r rhestr hon yn wag", "empty_column.account_hides_collections": "Mae'r defnyddiwr wedi dewis i beidio rhannu'r wybodaeth yma", "empty_column.account_suspended": "Cyfrif wedi'i atal", "empty_column.account_timeline": "Dim postiadau yma!", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index c5d7fc66f5..b72e40eaf7 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -27,9 +27,11 @@ "account.edit_profile": "Redigér profil", "account.enable_notifications": "Advisér mig, når @{name} poster", "account.endorse": "Fremhæv på profil", + "account.featured": "Fremhævet", + "account.featured.hashtags": "Hashtags", + "account.featured.posts": "Indlæg", "account.featured_tags.last_status_at": "Seneste indlæg {date}", "account.featured_tags.last_status_never": "Ingen indlæg", - "account.featured_tags.title": "{name}s fremhævede etiketter", "account.follow": "Følg", "account.follow_back": "Følg tilbage", "account.followers": "Følgere", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}", "account.unblock": "Fjern blokering af @{name}", "account.unblock_domain": "Fjern blokering af domænet {domain}", + "account.unblock_domain_short": "Afblokér", "account.unblock_short": "Fjern blokering", "account.unendorse": "Fjern visning på din profil", "account.unfollow": "Følg ikke længere", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Søgeresultater", "emoji_button.symbols": "Symboler", "emoji_button.travel": "Rejser og steder", + "empty_column.account_featured": "Denne liste er tom", "empty_column.account_hides_collections": "Brugeren har valgt ikke at gøre denne information tilgængelig", "empty_column.account_suspended": "Konto suspenderet", "empty_column.account_timeline": "Ingen indlæg her!", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index debb2db480..943c5ae8b2 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -27,9 +27,11 @@ "account.edit_profile": "Profil bearbeiten", "account.enable_notifications": "Benachrichtige mich wenn @{name} etwas postet", "account.endorse": "Im Profil empfehlen", + "account.featured": "Empfohlen", + "account.featured.hashtags": "Hashtags", + "account.featured.posts": "Beiträge", "account.featured_tags.last_status_at": "Letzter Beitrag am {date}", "account.featured_tags.last_status_never": "Keine Beiträge", - "account.featured_tags.title": "Von {name} vorgestellte Hashtags", "account.follow": "Folgen", "account.follow_back": "Ebenfalls folgen", "account.followers": "Follower", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}", "account.unblock": "{name} nicht mehr blockieren", "account.unblock_domain": "Blockierung von {domain} aufheben", + "account.unblock_domain_short": "Entsperren", "account.unblock_short": "Blockierung aufheben", "account.unendorse": "Im Profil nicht mehr empfehlen", "account.unfollow": "Entfolgen", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Suchergebnisse", "emoji_button.symbols": "Symbole", "emoji_button.travel": "Reisen & Orte", + "empty_column.account_featured": "Diese Liste ist leer", "empty_column.account_hides_collections": "Das Konto hat sich dazu entschieden, diese Information nicht zu veröffentlichen", "empty_column.account_suspended": "Konto gesperrt", "empty_column.account_timeline": "Keine Beiträge vorhanden!", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 1918c4371e..0b9e42cbe9 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -29,7 +29,6 @@ "account.endorse": "Προβολή στο προφίλ", "account.featured_tags.last_status_at": "Τελευταία ανάρτηση στις {date}", "account.featured_tags.last_status_never": "Καμία ανάρτηση", - "account.featured_tags.title": "προβεβλημένες ετικέτες του/της {name}", "account.follow": "Ακολούθησε", "account.follow_back": "Ακολούθησε και εσύ", "account.followers": "Ακόλουθοι", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 2f461cd19d..b46d02baa9 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -29,7 +29,6 @@ "account.endorse": "Feature on profile", "account.featured_tags.last_status_at": "Last post on {date}", "account.featured_tags.last_status_never": "No posts", - "account.featured_tags.title": "{name}'s featured hashtags", "account.follow": "Follow", "account.follow_back": "Follow back", "account.followers": "Followers", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 77448bd493..ade34591a5 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -38,9 +38,11 @@ "account.edit_profile": "Edit profile", "account.enable_notifications": "Notify me when @{name} posts", "account.endorse": "Feature on profile", + "account.featured": "Featured", + "account.featured.hashtags": "Hashtags", + "account.featured.posts": "Posts", "account.featured_tags.last_status_at": "Last post on {date}", "account.featured_tags.last_status_never": "No posts", - "account.featured_tags.title": "{name}'s featured hashtags", "account.follow": "Follow", "account.follow_back": "Follow back", "account.followers": "Followers", @@ -433,6 +435,7 @@ "emoji_button.search_results": "Search results", "emoji_button.symbols": "Symbols", "emoji_button.travel": "Travel & Places", + "empty_column.account_featured": "This list is empty", "empty_column.account_hides_collections": "This user has chosen to not make this information available", "empty_column.account_suspended": "Account suspended", "empty_column.account_timeline": "No posts here!", @@ -523,6 +526,8 @@ "generic.saved": "Saved", "getting_started.heading": "Getting started", "hashtag.admin_moderation": "Open moderation interface for #{name}", + "hashtag.browse": "Browse posts in #{hashtag}", + "hashtag.browse_from_account": "Browse posts from @{name} in #{hashtag}", "hashtag.column_header.tag_mode.all": "and {additional}", "hashtag.column_header.tag_mode.any": "or {additional}", "hashtag.column_header.tag_mode.none": "without {additional}", @@ -536,6 +541,7 @@ "hashtag.counter_by_uses": "{count, plural, one {{counter} post} other {{counter} posts}}", "hashtag.counter_by_uses_today": "{count, plural, one {{counter} post} other {{counter} posts}} today", "hashtag.follow": "Follow hashtag", + "hashtag.mute": "Mute #{hashtag}", "hashtag.unfollow": "Unfollow hashtag", "hashtags.and_other": "…and {count, plural, other {# more}}", "hints.profiles.followers_may_be_missing": "Followers for this profile may be missing.", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index accc2b8052..1d360e59d7 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -27,9 +27,10 @@ "account.edit_profile": "Redakti la profilon", "account.enable_notifications": "Sciigu min kiam @{name} afiŝos", "account.endorse": "Prezenti ĉe via profilo", + "account.featured.hashtags": "Kradvortoj", + "account.featured.posts": "Afiŝoj", "account.featured_tags.last_status_at": "Lasta afîŝo je {date}", "account.featured_tags.last_status_never": "Neniu afiŝo", - "account.featured_tags.title": "Rekomendataj kradvortoj de {name}", "account.follow": "Sekvi", "account.follow_back": "Sekvu reen", "account.followers": "Sekvantoj", @@ -65,6 +66,7 @@ "account.statuses_counter": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}}", "account.unblock": "Malbloki @{name}", "account.unblock_domain": "Malbloki la domajnon {domain}", + "account.unblock_domain_short": "Malbloki", "account.unblock_short": "Malbloki", "account.unendorse": "Ne plu rekomendi ĉe la profilo", "account.unfollow": "Ĉesi sekvi", @@ -293,6 +295,7 @@ "emoji_button.search_results": "Serĉaj rezultoj", "emoji_button.symbols": "Simboloj", "emoji_button.travel": "Vojaĝoj kaj lokoj", + "empty_column.account_featured": "Ĉi tiu listo estas malplena", "empty_column.account_hides_collections": "Ĉi tiu uzanto elektis ne disponebligi ĉi tiu informon", "empty_column.account_suspended": "Konto suspendita", "empty_column.account_timeline": "Neniuj afiŝoj ĉi tie!", @@ -905,6 +908,8 @@ "video.expand": "Pligrandigi la videon", "video.fullscreen": "Igi plenekrana", "video.hide": "Kaŝu la filmeton", + "video.mute": "Silentigi", "video.pause": "Paŭzigi", - "video.play": "Ekigi" + "video.play": "Ekigi", + "video.unmute": "Ne plu silentigi" } diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 724fd5d274..cc694ebfe7 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -27,9 +27,11 @@ "account.edit_profile": "Editar perfil", "account.enable_notifications": "Notificarme cuando @{name} envíe mensajes", "account.endorse": "Destacar en el perfil", + "account.featured": "Destacados", + "account.featured.hashtags": "Etiquetas", + "account.featured.posts": "Mensajes", "account.featured_tags.last_status_at": "Último mensaje: {date}", "account.featured_tags.last_status_never": "Sin mensajes", - "account.featured_tags.title": "Etiquetas destacadas de {name}", "account.follow": "Seguir", "account.follow_back": "Seguir", "account.followers": "Seguidores", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}", "account.unblock": "Desbloquear a @{name}", "account.unblock_domain": "Desbloquear dominio {domain}", + "account.unblock_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "No destacar en el perfil", "account.unfollow": "Dejar de seguir", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Resultados de búsqueda", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", + "empty_column.account_featured": "Esta lista está vacía", "empty_column.account_hides_collections": "Este usuario eligió no publicar esta información", "empty_column.account_suspended": "Cuenta suspendida", "empty_column.account_timeline": "¡No hay mensajes acá!", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 0fe0f1cad3..45b002ce0f 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -27,9 +27,11 @@ "account.edit_profile": "Editar perfil", "account.enable_notifications": "Notificarme cuando @{name} publique algo", "account.endorse": "Destacar en mi perfil", + "account.featured": "Destacado", + "account.featured.hashtags": "Etiquetas", + "account.featured.posts": "Publicaciones", "account.featured_tags.last_status_at": "Última publicación el {date}", "account.featured_tags.last_status_never": "Sin publicaciones", - "account.featured_tags.title": "Etiquetas destacadas de {name}", "account.follow": "Seguir", "account.follow_back": "Seguir también", "account.followers": "Seguidores", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}", "account.unblock": "Desbloquear a @{name}", "account.unblock_domain": "Mostrar a {domain}", + "account.unblock_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "No mostrar en el perfil", "account.unfollow": "Dejar de seguir", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Resultados de búsqueda", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", + "empty_column.account_featured": "Esta lista está vacía", "empty_column.account_hides_collections": "Este usuario ha elegido no hacer disponible esta información", "empty_column.account_suspended": "Cuenta suspendida", "empty_column.account_timeline": "¡No hay publicaciones aquí!", @@ -909,8 +913,8 @@ "video.pause": "Pausar", "video.play": "Reproducir", "video.skip_backward": "Saltar atrás", - "video.skip_forward": "Adelantar", + "video.skip_forward": "Saltar adelante", "video.unmute": "Dejar de silenciar", - "video.volume_down": "Bajar volumen", - "video.volume_up": "Subir volumen" + "video.volume_down": "Bajar el volumen", + "video.volume_up": "Subir el volumen" } diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 8d6bb4cdbe..2575d901f5 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -27,9 +27,11 @@ "account.edit_profile": "Editar perfil", "account.enable_notifications": "Notificarme cuando @{name} publique algo", "account.endorse": "Destacar en el perfil", + "account.featured": "Destacado", + "account.featured.hashtags": "Etiquetas", + "account.featured.posts": "Publicaciones", "account.featured_tags.last_status_at": "Última publicación el {date}", "account.featured_tags.last_status_never": "Sin publicaciones", - "account.featured_tags.title": "Etiquetas destacadas de {name}", "account.follow": "Seguir", "account.follow_back": "Seguir también", "account.followers": "Seguidores", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}", "account.unblock": "Desbloquear a @{name}", "account.unblock_domain": "Desbloquear dominio {domain}", + "account.unblock_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "No mostrar en el perfil", "account.unfollow": "Dejar de seguir", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Resultados de búsqueda", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viajes y lugares", + "empty_column.account_featured": "Esta lista está vacía", "empty_column.account_hides_collections": "Este usuario ha decidido no mostrar esta información", "empty_column.account_suspended": "Cuenta suspendida", "empty_column.account_timeline": "¡No hay publicaciones aquí!", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index d2fb81bee6..3e0610126e 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -29,7 +29,6 @@ "account.endorse": "Too profiilil esile", "account.featured_tags.last_status_at": "Viimane postitus {date}", "account.featured_tags.last_status_never": "Postitusi pole", - "account.featured_tags.title": "{name} esiletõstetud sildid", "account.follow": "Jälgi", "account.follow_back": "Jälgi vastu", "account.followers": "Jälgijad", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 5507e7f343..0c73c9f540 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -29,7 +29,6 @@ "account.endorse": "Nabarmendu profilean", "account.featured_tags.last_status_at": "Azken bidalketa {date} datan", "account.featured_tags.last_status_never": "Bidalketarik ez", - "account.featured_tags.title": "{name} erabiltzailearen nabarmendutako traolak", "account.follow": "Jarraitu", "account.follow_back": "Jarraitu bueltan", "account.followers": "Jarraitzaileak", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index f4758e5afb..3e31eb8a15 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -29,7 +29,6 @@ "account.endorse": "معرّفی در نمایه", "account.featured_tags.last_status_at": "آخرین فرسته در {date}", "account.featured_tags.last_status_never": "بدون فرسته", - "account.featured_tags.title": "برچسب‌های برگزیدهٔ {name}", "account.follow": "پی‌گرفتن", "account.follow_back": "دنبال کردن متقابل", "account.followers": "پی‌گیرندگان", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}", "account.unblock": "رفع مسدودیت ‎@{name}", "account.unblock_domain": "رفع مسدودیت دامنهٔ {domain}", + "account.unblock_domain_short": "آنبلاک", "account.unblock_short": "رفع مسدودیت", "account.unendorse": "معرّفی نکردن در نمایه", "account.unfollow": "پی‌نگرفتن", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 3e5a327301..b33e9f6163 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -27,9 +27,10 @@ "account.edit_profile": "Muokkaa profiilia", "account.enable_notifications": "Ilmoita minulle, kun @{name} julkaisee", "account.endorse": "Suosittele profiilissasi", + "account.featured.hashtags": "Aihetunnisteet", + "account.featured.posts": "Julkaisut", "account.featured_tags.last_status_at": "Viimeisin julkaisu {date}", "account.featured_tags.last_status_never": "Ei julkaisuja", - "account.featured_tags.title": "Käyttäjän {name} suosittelemat aihetunnisteet", "account.follow": "Seuraa", "account.follow_back": "Seuraa takaisin", "account.followers": "Seuraajat", @@ -65,6 +66,7 @@ "account.statuses_counter": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}", "account.unblock": "Kumoa käyttäjän @{name} esto", "account.unblock_domain": "Kumoa verkkotunnuksen {domain} esto", + "account.unblock_domain_short": "Kumoa esto", "account.unblock_short": "Kumoa esto", "account.unendorse": "Kumoa suosittelu profiilissasi", "account.unfollow": "Älä seuraa", @@ -239,7 +241,7 @@ "conversation.mark_as_read": "Merkitse luetuksi", "conversation.open": "Näytä keskustelu", "conversation.with": "{names} kanssa", - "copy_icon_button.copied": "Sisältö kopioitiin leikepöydälle", + "copy_icon_button.copied": "Kopioitu leikepöydälle", "copypaste.copied": "Kopioitu", "copypaste.copy_to_clipboard": "Kopioi leikepöydälle", "directory.federated": "Tunnetusta fediversumista", @@ -293,6 +295,7 @@ "emoji_button.search_results": "Hakutulokset", "emoji_button.symbols": "Symbolit", "emoji_button.travel": "Matkailu ja paikat", + "empty_column.account_featured": "Tämä lista on tyhjä", "empty_column.account_hides_collections": "Käyttäjä on päättänyt pitää nämä tiedot yksityisinä", "empty_column.account_suspended": "Tili jäädytetty", "empty_column.account_timeline": "Ei viestejä täällä.", diff --git a/app/javascript/mastodon/locales/fil.json b/app/javascript/mastodon/locales/fil.json index 4c33ac01a1..c13d0a8afe 100644 --- a/app/javascript/mastodon/locales/fil.json +++ b/app/javascript/mastodon/locales/fil.json @@ -29,7 +29,6 @@ "account.endorse": "I-tampok sa profile", "account.featured_tags.last_status_at": "Huling post noong {date}", "account.featured_tags.last_status_never": "Walang mga post", - "account.featured_tags.title": "Nakatampok na hashtag ni {name}", "account.follow": "Sundan", "account.follow_back": "Sundan pabalik", "account.followers": "Mga tagasunod", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 06ab07023e..82939adcce 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -27,9 +27,11 @@ "account.edit_profile": "Broyt vanga", "account.enable_notifications": "Boða mær frá, tá @{name} skrivar", "account.endorse": "Víst á vangamyndini", + "account.featured": "Tikin fram", + "account.featured.hashtags": "Frámerki", + "account.featured.posts": "Postar", "account.featured_tags.last_status_at": "Seinasta strongur skrivaður {date}", "account.featured_tags.last_status_never": "Einki uppslag", - "account.featured_tags.title": "Tvíkrossar hjá {name}", "account.follow": "Fylg", "account.follow_back": "Fylg aftur", "account.followers": "Fylgjarar", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} postur} other {{counter} postar}}", "account.unblock": "Banna ikki @{name}", "account.unblock_domain": "Banna ikki økisnavnið {domain}", + "account.unblock_domain_short": "Banna ikki", "account.unblock_short": "Banna ikki", "account.unendorse": "Vís ikki á vanga", "account.unfollow": "Fylg ikki", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Leitiúrslit", "emoji_button.symbols": "Ímyndir", "emoji_button.travel": "Ferðing og støð", + "empty_column.account_featured": "Hesin listin er tómur", "empty_column.account_hides_collections": "Hesin brúkarin hevur valt, at hesar upplýsingarnar ikki skulu vera tøkar", "empty_column.account_suspended": "Kontan gjørd óvirkin", "empty_column.account_timeline": "Einki uppslag her!", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index dd497adbbb..ad71d98ff8 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -29,7 +29,6 @@ "account.endorse": "Inclure sur profil", "account.featured_tags.last_status_at": "Dernière publication {date}", "account.featured_tags.last_status_never": "Aucune publication", - "account.featured_tags.title": "Hashtags inclus de {name}", "account.follow": "Suivre", "account.follow_back": "Suivre en retour", "account.followers": "abonné·e·s", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 5b76cbfde6..de653eec8f 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -29,7 +29,6 @@ "account.endorse": "Recommander sur votre profil", "account.featured_tags.last_status_at": "Dernier message le {date}", "account.featured_tags.last_status_never": "Aucun message", - "account.featured_tags.title": "Les hashtags en vedette de {name}", "account.follow": "Suivre", "account.follow_back": "Suivre en retour", "account.followers": "Abonné·e·s", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index 46da3ea09d..e3c3222868 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -29,7 +29,6 @@ "account.endorse": "Op profyl werjaan", "account.featured_tags.last_status_at": "Lêste berjocht op {date}", "account.featured_tags.last_status_never": "Gjin berjochten", - "account.featured_tags.title": "Utljochte hashtags fan {name}", "account.follow": "Folgje", "account.follow_back": "Weromfolgje", "account.followers": "Folgers", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index d3af400c64..5935b39e2d 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -29,7 +29,6 @@ "account.endorse": "Cuir ar an phróifíl mar ghné", "account.featured_tags.last_status_at": "Postáil is déanaí ar {date}", "account.featured_tags.last_status_never": "Gan aon phoist", - "account.featured_tags.title": "Haischlib faoi thrácht {name}", "account.follow": "Lean", "account.follow_back": "Leanúint ar ais", "account.followers": "Leantóirí", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} poist}}", "account.unblock": "Bain bac de @{name}", "account.unblock_domain": "Bain bac den ainm fearainn {domain}", + "account.unblock_domain_short": "Díbhlocáil", "account.unblock_short": "Díbhlocáil", "account.unendorse": "Ná chuir ar an phróifíl mar ghné", "account.unfollow": "Ná lean a thuilleadh", @@ -905,6 +905,12 @@ "video.expand": "Leath físeán", "video.fullscreen": "Lánscáileán", "video.hide": "Cuir físeán i bhfolach", + "video.mute": "Balbhaigh", "video.pause": "Cuir ar sos", - "video.play": "Cuir ar siúl" + "video.play": "Cuir ar siúl", + "video.skip_backward": "Scipeáil siar", + "video.skip_forward": "Scipeáil ar aghaidh", + "video.unmute": "Díbhalbhú", + "video.volume_down": "Toirt síos", + "video.volume_up": "Toirt suas" } diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 69aa5f69ef..f295db0b43 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -29,7 +29,6 @@ "account.endorse": "Brosnaich air a’ phròifil", "account.featured_tags.last_status_at": "Am post mu dheireadh {date}", "account.featured_tags.last_status_never": "Gun phost", - "account.featured_tags.title": "Na tagaichean hais brosnaichte aig {name}", "account.follow": "Lean", "account.follow_back": "Lean air ais", "account.followers": "Luchd-leantainn", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 8106c86714..57e7b5ee5a 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -27,9 +27,11 @@ "account.edit_profile": "Editar perfil", "account.enable_notifications": "Noficarme cando @{name} publique", "account.endorse": "Amosar no perfil", + "account.featured": "Destacado", + "account.featured.hashtags": "Cancelos", + "account.featured.posts": "Publicacións", "account.featured_tags.last_status_at": "Última publicación o {date}", "account.featured_tags.last_status_never": "Sen publicacións", - "account.featured_tags.title": "Cancelos destacados de {name}", "account.follow": "Seguir", "account.follow_back": "Seguir tamén", "account.followers": "Seguidoras", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publicación} other {{counter} publicacións}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Amosar {domain}", + "account.unblock_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "Non amosar no perfil", "account.unfollow": "Deixar de seguir", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Resultados da procura", "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viaxes e Lugares", + "empty_column.account_featured": "A lista está baleira", "empty_column.account_hides_collections": "A usuaria decideu non facer pública esta información", "empty_column.account_suspended": "Conta suspendida", "empty_column.account_timeline": "Non hai publicacións aquí!", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index f31b862a03..89d36dc962 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -29,7 +29,6 @@ "account.endorse": "קדם את החשבון בפרופיל", "account.featured_tags.last_status_at": "חצרוץ אחרון בתאריך {date}", "account.featured_tags.last_status_never": "אין חצרוצים", - "account.featured_tags.title": "התגיות המועדפות של {name}", "account.follow": "לעקוב", "account.follow_back": "לעקוב בחזרה", "account.followers": "עוקבים", @@ -64,8 +63,9 @@ "account.show_reblogs": "הצג הדהודים מאת @{name}", "account.statuses_counter": "{count, plural, one {הודעה אחת} two {הודעותיים} many {{counter} הודעות} other {{counter} הודעות}}", "account.unblock": "להסיר חסימה ל- @{name}", - "account.unblock_domain": "הסירי את החסימה של קהילת {domain}", - "account.unblock_short": "הסר חסימה", + "account.unblock_domain": "הסרת החסימה של קהילת {domain}", + "account.unblock_domain_short": "הסרת חסימה", + "account.unblock_short": "הסרת חסימה", "account.unendorse": "אל תקדם בפרופיל", "account.unfollow": "הפסקת מעקב", "account.unmute": "הפסקת השתקת @{name}", @@ -905,6 +905,12 @@ "video.expand": "להרחיב וידאו", "video.fullscreen": "מסך מלא", "video.hide": "להסתיר וידאו", + "video.mute": "השתקה", "video.pause": "השהיה", - "video.play": "ניגון" + "video.play": "ניגון", + "video.skip_backward": "דילוג אחורה", + "video.skip_forward": "דילוג קדימה", + "video.unmute": "ביטול השתקה", + "video.volume_down": "הנמכת עוצמת השמע", + "video.volume_up": "הגברת עוצמת שמע" } diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json index 10c5356719..a3eec9544c 100644 --- a/app/javascript/mastodon/locales/hi.json +++ b/app/javascript/mastodon/locales/hi.json @@ -28,7 +28,6 @@ "account.endorse": "प्रोफ़ाइल पर दिखाए", "account.featured_tags.last_status_at": "{date} का अंतिम पोस्ट", "account.featured_tags.last_status_never": "कोई पोस्ट नहीं है", - "account.featured_tags.title": "{name} के चुनिंदा हैशटैग", "account.follow": "फॉलो करें", "account.follow_back": "फॉलो करें", "account.followers": "फॉलोवर", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index 092647bd31..38807b28b2 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -28,7 +28,6 @@ "account.endorse": "Istakni na profilu", "account.featured_tags.last_status_at": "Zadnji post {date}", "account.featured_tags.last_status_never": "Nema postova", - "account.featured_tags.title": "Istaknuti hashtagovi {name}", "account.follow": "Prati", "account.follow_back": "Slijedi natrag", "account.followers": "Pratitelji", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 942c96ffec..826dca6137 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -27,9 +27,11 @@ "account.edit_profile": "Profil szerkesztése", "account.enable_notifications": "Figyelmeztessen, ha @{name} bejegyzést tesz közzé", "account.endorse": "Kiemelés a profilodon", + "account.featured": "Kiemelt", + "account.featured.hashtags": "Hashtagek", + "account.featured.posts": "Bejegyzések", "account.featured_tags.last_status_at": "Legutolsó bejegyzés ideje: {date}", "account.featured_tags.last_status_never": "Nincs bejegyzés", - "account.featured_tags.title": "{name} kiemelt hashtagjei", "account.follow": "Követés", "account.follow_back": "Viszontkövetés", "account.followers": "Követő", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} bejegyzés} other {{counter} bejegyzés}}", "account.unblock": "@{name} letiltásának feloldása", "account.unblock_domain": "{domain} domain tiltásának feloldása", + "account.unblock_domain_short": "Tiltás feloldása", "account.unblock_short": "Tiltás feloldása", "account.unendorse": "Ne jelenjen meg a profilodon", "account.unfollow": "Követés megszüntetése", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Keresési találatok", "emoji_button.symbols": "Szimbólumok", "emoji_button.travel": "Utazás és helyek", + "empty_column.account_featured": "Ez a lista üres", "empty_column.account_hides_collections": "Ez a felhasználó úgy döntött, hogy nem teszi elérhetővé ezt az információt.", "empty_column.account_suspended": "Fiók felfüggesztve", "empty_column.account_timeline": "Itt nincs bejegyzés!", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index d4706dbf8c..7f4f66796e 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -29,7 +29,6 @@ "account.endorse": "Evidentiar sur le profilo", "account.featured_tags.last_status_at": "Ultime message publicate le {date}", "account.featured_tags.last_status_never": "Necun message", - "account.featured_tags.title": "Hashtags eminente de {name}", "account.follow": "Sequer", "account.follow_back": "Sequer in retorno", "account.followers": "Sequitores", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index f4724b6f4f..102e547d40 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -29,7 +29,6 @@ "account.endorse": "Tampilkan di profil", "account.featured_tags.last_status_at": "Kiriman terakhir pada {date}", "account.featured_tags.last_status_never": "Tidak ada kiriman", - "account.featured_tags.title": "Tagar {name} yang difiturkan", "account.follow": "Ikuti", "account.follow_back": "Ikuti balik", "account.followers": "Pengikut", diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json index ba5ad494ce..7cd463727f 100644 --- a/app/javascript/mastodon/locales/ie.json +++ b/app/javascript/mastodon/locales/ie.json @@ -28,7 +28,6 @@ "account.endorse": "Recomandar sur profil", "account.featured_tags.last_status_at": "Ultim posta ye {date}", "account.featured_tags.last_status_never": "Null postas", - "account.featured_tags.title": "Recomandat hashtags de {name}", "account.follow": "Sequer", "account.follow_back": "Sequer reciprocmen", "account.followers": "Sequitores", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 21723e10b8..596ca4c3fe 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -29,7 +29,6 @@ "account.endorse": "Traito di profilo", "account.featured_tags.last_status_at": "Antea posto ye {date}", "account.featured_tags.last_status_never": "Nula posti", - "account.featured_tags.title": "Ekstaca gretvorti di {name}", "account.follow": "Sequar", "account.follow_back": "Anke sequez", "account.followers": "Sequanti", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 47cd31fd6b..5f956c71f6 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -27,9 +27,11 @@ "account.edit_profile": "Breyta notandasniði", "account.enable_notifications": "Láta mig vita þegar @{name} sendir inn", "account.endorse": "Birta á notandasniði", + "account.featured": "Með aukið vægi", + "account.featured.hashtags": "Myllumerki", + "account.featured.posts": "Færslur", "account.featured_tags.last_status_at": "Síðasta færsla þann {date}", "account.featured_tags.last_status_never": "Engar færslur", - "account.featured_tags.title": "Myllumerki hjá {name} með aukið vægi", "account.follow": "Fylgjast með", "account.follow_back": "Fylgjast með til baka", "account.followers": "Fylgjendur", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} færsla} other {{counter} færslur}}", "account.unblock": "Aflétta útilokun af @{name}", "account.unblock_domain": "Aflétta útilokun lénsins {domain}", + "account.unblock_domain_short": "Aflétta útilokun", "account.unblock_short": "Hætta að loka á", "account.unendorse": "Ekki birta á notandasniði", "account.unfollow": "Hætta að fylgja", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Leitarniðurstöður", "emoji_button.symbols": "Tákn", "emoji_button.travel": "Ferðalög og staðir", + "empty_column.account_featured": "Þessi listi er tómur", "empty_column.account_hides_collections": "Notandinn hefur valið að gera ekki tiltækar þessar upplýsingar", "empty_column.account_suspended": "Notandaaðgangur í frysti", "empty_column.account_timeline": "Engar færslur hér!", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 9fa5c725b6..36770c675f 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -27,9 +27,11 @@ "account.edit_profile": "Modifica profilo", "account.enable_notifications": "Avvisami quando @{name} pubblica un post", "account.endorse": "In evidenza sul profilo", + "account.featured": "In primo piano", + "account.featured.hashtags": "Hashtag", + "account.featured.posts": "Post", "account.featured_tags.last_status_at": "Ultimo post il {date}", "account.featured_tags.last_status_never": "Nessun post", - "account.featured_tags.title": "Hashtag in evidenza di {name}", "account.follow": "Segui", "account.follow_back": "Segui a tua volta", "account.followers": "Follower", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} post}}", "account.unblock": "Sblocca @{name}", "account.unblock_domain": "Sblocca il dominio {domain}", + "account.unblock_domain_short": "Sblocca", "account.unblock_short": "Sblocca", "account.unendorse": "Non mostrare sul profilo", "account.unfollow": "Smetti di seguire", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Risultati della ricerca", "emoji_button.symbols": "Simboli", "emoji_button.travel": "Viaggi & Luoghi", + "empty_column.account_featured": "Questa lista è vuota", "empty_column.account_hides_collections": "Questo utente ha scelto di non rendere disponibili queste informazioni", "empty_column.account_suspended": "Profilo sospeso", "empty_column.account_timeline": "Nessun post qui!", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 457aa84628..a2328401f4 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -40,7 +40,6 @@ "account.endorse": "プロフィールで紹介する", "account.featured_tags.last_status_at": "最終投稿 {date}", "account.featured_tags.last_status_never": "投稿がありません", - "account.featured_tags.title": "{name}の注目ハッシュタグ", "account.follow": "フォロー", "account.follow_back": "フォローバック", "account.followers": "フォロワー", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index f62d32226f..a29bd33468 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -20,10 +20,10 @@ "account.cancel_follow_request": "Sefsex taḍfart", "account.copy": "Nɣel assaɣ ɣer umaɣnu", "account.direct": "Bder-d @{name} weḥd-s", - "account.disable_notifications": "Ḥbes ur iyi-d-ttazen ara alɣuten mi ara d-isuffeɣ @{name}", + "account.disable_notifications": "Ḥbes ur iyi-d-ttazen ara ilɣa mi ara d-isuffeɣ @{name}", "account.domain_blocked": "Taɣult yeffren", "account.edit_profile": "Ẓreg amaɣnu", - "account.enable_notifications": "Azen-iyi-d alɣuten mi ara d-isuffeɣ @{name}", + "account.enable_notifications": "Azen-iyi-d ilɣa mi ara d-isuffeɣ @{name}", "account.endorse": "Welleh fell-as deg umaɣnu-inek", "account.featured_tags.last_status_at": "Tasuffeɣt taneggarut ass n {date}", "account.featured_tags.last_status_never": "Ulac tisuffaɣ", @@ -45,7 +45,7 @@ "account.mention": "Bder-d @{name}", "account.moved_to": "{name} yenna-d dakken amiḍan-is amaynut yuɣal :", "account.mute": "Sgugem @{name}", - "account.mute_notifications_short": "Susem alɣuten", + "account.mute_notifications_short": "Susem ilɣa", "account.mute_short": "Sgugem", "account.muted": "Yettwasgugem", "account.mutual": "Temṭafarem", @@ -61,6 +61,7 @@ "account.statuses_counter": "{count, plural, one {{counter} n tsuffeɣt} other {{counter} n tsuffaɣ}}", "account.unblock": "Serreḥ i @{name}", "account.unblock_domain": "Ssken-d {domain}", + "account.unblock_domain_short": "Serreḥ", "account.unblock_short": "Serreḥ", "account.unendorse": "Ur ttwellih ara fell-as deg umaɣnu-inek", "account.unfollow": "Ur ṭṭafaṛ ara", @@ -74,6 +75,8 @@ "alert.unexpected.message": "Yeḍra-d unezri ur netturaǧu ara.", "alert.unexpected.title": "Ayhuh!", "alt_text_badge.title": "Aḍris asegzan", + "alt_text_modal.add_alt_text": "Rnu aḍris amlellay", + "alt_text_modal.add_text_from_image": "Rnu aḍris amlellay seg tugna", "alt_text_modal.cancel": "Semmet", "alt_text_modal.done": "Immed", "announcement.announcement": "Ulɣu", @@ -115,7 +118,7 @@ "column.home": "Agejdan", "column.lists": "Tibdarin", "column.mutes": "Imiḍanen yettwasgugmen", - "column.notifications": "Alɣuten", + "column.notifications": "Ilɣa", "column.pins": "Tisuffaɣ yettwasenṭḍen", "column.public": "Tasuddemt tamatut", "column_back_button.label": "Tuɣalin", @@ -167,6 +170,7 @@ "confirmations.logout.confirm": "Ffeɣ", "confirmations.logout.message": "D tidet tebɣiḍ ad teffɣeḍ?", "confirmations.logout.title": "Tebɣiḍ ad teffɣeḍ ssya?", + "confirmations.missing_alt_text.confirm": "Rnu aḍris amlellay", "confirmations.missing_alt_text.secondary": "Suffeɣ akken yebɣu yili", "confirmations.mute.confirm": "Sgugem", "confirmations.redraft.confirm": "Kkes sakin ɛiwed tira", @@ -228,7 +232,7 @@ "empty_column.home": "Tasuddemt tagejdant n yisallen d tilemt! Ẓer {public} neɣ nadi ad tafeḍ imseqdacen-nniḍen ad ten-ḍefṛeḍ.", "empty_column.list": "Ar tura ur yelli kra deg umuɣ-a. Ad d-yettwasken da ticki iɛeggalen n wumuɣ-a suffɣen-d kra.", "empty_column.mutes": "Ulac ɣur-k·m imseqdacen i yettwasgugmen.", - "empty_column.notifications": "Ulac ɣur-k·m alɣuten. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.", + "empty_column.notifications": "Ulac ɣur-k·m ilɣa. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.", "empty_column.public": "Ulac kra da! Aru kra, neɣ ḍfeṛ imdanen i yellan deg yiqeddacen-nniḍen akken ad d-teččar tsuddemt tazayezt", "error.unexpected_crash.next_steps": "Smiren asebter-a, ma ur yekkis ara wugur, ẓer d akken tzemreḍ ad tesqedceḍ Maṣṭudun deg yiminig-nniḍen neɣ deg usnas anaṣli.", "errors.unexpected_crash.copy_stacktrace": "Nɣel stacktrace ɣef wafus", @@ -247,6 +251,7 @@ "filter_modal.select_filter.search": "Nadi neɣ snulfu-d", "filter_modal.select_filter.title": "Sizdeg tassufeɣt-a", "filter_modal.title.status": "Sizdeg tassufeɣt", + "filtered_notifications_banner.title": "Ilɣa yettwasizdgen", "firehose.all": "Akk", "firehose.local": "Deg uqeddac-ayi", "firehose.remote": "Iqeddacen nniḍen", @@ -326,7 +331,7 @@ "keyboard_shortcuts.mention": "akken ad d-bedreḍ ameskar", "keyboard_shortcuts.muted": "akken ad teldiḍ tabdart n yimseqdacen yettwasgugmen", "keyboard_shortcuts.my_profile": "akken ad d-teldiḍ amaɣnu-ik", - "keyboard_shortcuts.notifications": "akken ad d-teldiḍ ajgu n walɣuten", + "keyboard_shortcuts.notifications": "Ad d-yeldi ajgu n yilɣa", "keyboard_shortcuts.open_media": "i tiɣwalin yeldin", "keyboard_shortcuts.pinned": "akken ad teldiḍ tabdart n tjewwiqin yettwasentḍen", "keyboard_shortcuts.profile": "akken ad d-teldiḍ amaɣnu n umeskar", @@ -408,6 +413,7 @@ "notification.admin.sign_up": "Ijerred {name}", "notification.favourite": "{name} yesmenyaf addad-ik·im", "notification.follow": "iṭṭafar-ik·em-id {name}", + "notification.follow.name_and_others": "{name} akked {count, plural, one {# nniḍen} other {# nniḍen}} iḍfeṛ-k·m-id", "notification.follow_request": "{name} yessuter-d ad k·m-yeḍfeṛ", "notification.label.mention": "Abdar", "notification.label.private_mention": "Abdar uslig", @@ -424,11 +430,12 @@ "notification_requests.dismiss": "Agi", "notification_requests.edit_selection": "Ẓreg", "notification_requests.exit_selection": "Immed", - "notification_requests.notifications_from": "Alɣuten sɣur {name}", - "notifications.clear": "Sfeḍ alɣuten", - "notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk alɣuten-inek·em i lebda?", + "notification_requests.notifications_from": "Ilɣa sɣur {name}", + "notification_requests.title": "Ilɣa yettwasizdgen", + "notifications.clear": "Sfeḍ ilɣa", + "notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk ilɣa-inek·em i lebda?", "notifications.column_settings.admin.report": "Ineqqisen imaynuten:", - "notifications.column_settings.alert": "Alɣuten n tnarit", + "notifications.column_settings.alert": "Ilɣa n tnarit", "notifications.column_settings.favourite": "Imenyafen:", "notifications.column_settings.filter_bar.advanced": "Sken-d akk taggayin", "notifications.column_settings.filter_bar.category": "Iri n usizdeg uzrib", @@ -437,12 +444,12 @@ "notifications.column_settings.group": "Agraw", "notifications.column_settings.mention": "Abdar:", "notifications.column_settings.poll": "Igemmaḍ n usenqed:", - "notifications.column_settings.push": "Alɣuten yettudemmren", + "notifications.column_settings.push": "Ilɣa yettudemmren", "notifications.column_settings.reblog": "Seǧhed:", "notifications.column_settings.show": "Ssken-d tilɣa deg ujgu", "notifications.column_settings.sound": "Rmed imesli", "notifications.column_settings.status": "Tisuffaɣ timaynutin :", - "notifications.column_settings.unread_notifications.category": "Alɣuten ur nettwaɣra", + "notifications.column_settings.unread_notifications.category": "Ilɣa ur nettwaɣra", "notifications.column_settings.update": "Iẓreg:", "notifications.filter.all": "Akk", "notifications.filter.boosts": "Seǧhed", @@ -452,9 +459,9 @@ "notifications.filter.polls": "Igemmaḍ n usenqed", "notifications.filter.statuses": "Ileqman n yimdanen i teṭṭafareḍ", "notifications.grant_permission": "Mudd tasiregt.", - "notifications.group": "{count} n walɣuten", - "notifications.mark_as_read": "Creḍ meṛṛa alɣuten am wakken ttwaɣran", - "notifications.permission_denied": "D awezɣi ad yili wermad n walɣuten n tnarit axateṛ turagt tettwagdel", + "notifications.group": "{count} n yilɣa", + "notifications.mark_as_read": "Creḍ akk ilɣa am wakken ttwaɣran", + "notifications.permission_denied": "D awezɣi ad yili wermad n yilɣa n tnarit axateṛ turagt tettwagdel", "notifications.policy.drop": "Anef-as", "notifications.policy.filter": "Sizdeg", "notifications.policy.filter_new_accounts.hint": "Imiḍanen imaynuten i d-yennulfan deg {days, plural, one {yiwen n wass} other {# n wussan}} yezrin", @@ -464,7 +471,7 @@ "notifications.policy.filter_not_following_hint": "Alamma tqebleḍ-ten s ufus", "notifications.policy.filter_not_following_title": "Wid akked tid ur tettḍafareḍ ara", "notifications.policy.filter_private_mentions_title": "Abdar uslig ur yettwasferken ara", - "notifications_permission_banner.enable": "Rmed alɣuten n tnarit", + "notifications_permission_banner.enable": "Rmed ilɣa n tnarit", "notifications_permission_banner.title": "Ur zeggel acemma", "onboarding.follows.back": "Uɣal", "onboarding.follows.done": "Immed", @@ -632,7 +639,7 @@ "status.unpin": "Kkes asenteḍ seg umaɣnu", "subscribed_languages.save": "Sekles ibeddilen", "tabs_bar.home": "Agejdan", - "tabs_bar.notifications": "Alɣuten", + "tabs_bar.notifications": "Ilɣa", "terms_of_service.title": "Tiwtilin n useqdec", "time_remaining.days": "Mazal {number, plural, one {# wass} other {# wussan}}", "time_remaining.hours": "Mazal {number, plural, one {# usarag} other {# yisragen}}", diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json index 120e2415b5..adc3cdc230 100644 --- a/app/javascript/mastodon/locales/kk.json +++ b/app/javascript/mastodon/locales/kk.json @@ -25,7 +25,6 @@ "account.enable_notifications": "@{name} постары туралы ескерту", "account.endorse": "Профильде ұсыну", "account.featured_tags.last_status_never": "Пост жоқ", - "account.featured_tags.title": "{name} таңдаулы хэштегтері", "account.follow": "Жазылу", "account.followers": "Жазылушы", "account.followers.empty": "Бұл қолданушыға әлі ешкім жазылмаған.", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 76f94fb43f..be6e4be7e1 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -29,7 +29,6 @@ "account.endorse": "프로필에 추천하기", "account.featured_tags.last_status_at": "{date}에 마지막으로 게시", "account.featured_tags.last_status_never": "게시물 없음", - "account.featured_tags.title": "{name} 님의 추천 해시태그", "account.follow": "팔로우", "account.follow_back": "맞팔로우", "account.followers": "팔로워", diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json index f867908f37..23ee9fc932 100644 --- a/app/javascript/mastodon/locales/ku.json +++ b/app/javascript/mastodon/locales/ku.json @@ -29,7 +29,6 @@ "account.endorse": "Taybetiyên li ser profîl", "account.featured_tags.last_status_at": "Şandiya dawî di {date} de", "account.featured_tags.last_status_never": "Şandî tune ne", - "account.featured_tags.title": "{name}'s hashtagên taybet", "account.follow": "Bişopîne", "account.follow_back": "Bişopîne", "account.followers": "Şopîner", diff --git a/app/javascript/mastodon/locales/la.json b/app/javascript/mastodon/locales/la.json index f422230cb8..c6e5d85c07 100644 --- a/app/javascript/mastodon/locales/la.json +++ b/app/javascript/mastodon/locales/la.json @@ -23,7 +23,6 @@ "account.domain_blocked": "Dominium impeditum", "account.edit_profile": "Recolere notionem", "account.featured_tags.last_status_never": "Nulla contributa", - "account.featured_tags.title": "Hashtag notātī {name}", "account.followers_counter": "{count, plural, one {{counter} sectator} other {{counter} sectatores}}", "account.following_counter": "{count, plural, one {{counter} sectans} other {{counter} sectans}}", "account.moved_to": "{name} significavit eum suam rationem novam nunc esse:", diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index ab410dc2a8..f67ef676ad 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -29,7 +29,6 @@ "account.endorse": "Avalia en profil", "account.featured_tags.last_status_at": "Ultima publikasyon de {date}", "account.featured_tags.last_status_never": "No ay publikasyones", - "account.featured_tags.title": "Etiketas avaliadas de {name}", "account.follow": "Sige", "account.follow_back": "Sige tamyen", "account.followers": "Suivantes", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}", "account.unblock": "Dezbloka a @{name}", "account.unblock_domain": "Dezbloka domeno {domain}", + "account.unblock_domain_short": "Dezbloka", "account.unblock_short": "Dezbloka", "account.unendorse": "No avalia en profil", "account.unfollow": "Desige", @@ -86,6 +86,7 @@ "alert.unexpected.message": "Afito un yerro no asperado.", "alert.unexpected.title": "Atyo!", "alt_text_badge.title": "Teksto alternativo", + "alt_text_modal.add_alt_text": "Adjusta teksto alternativo", "alt_text_modal.cancel": "Anula", "alt_text_modal.change_thumbnail": "Troka minyatura", "alt_text_modal.done": "Fecho", @@ -195,9 +196,12 @@ "confirmations.discard_edit_media.message": "Tienes trokamientos no guadrados en la deskripsion o vista previa. Keres efasarlos entanto?", "confirmations.edit.confirm": "Edita", "confirmations.edit.message": "Si edites agora, kitaras el mesaj kualo estas eskriviendo aktualmente. Estas siguro ke keres fazerlo?", + "confirmations.follow_to_list.title": "Segir utilizador?", "confirmations.logout.confirm": "Sal", "confirmations.logout.message": "Estas siguro ke keres salir de tu kuento?", "confirmations.logout.title": "Salir?", + "confirmations.missing_alt_text.confirm": "Adjusta teksto alternativo", + "confirmations.missing_alt_text.title": "Adjustar teksto alternativo?", "confirmations.mute.confirm": "Silensia", "confirmations.redraft.confirm": "Efasa i reeskrive", "confirmations.redraft.message": "Estas siguro ke keres efasar esta publikasyon i reeskrivirla? Pedreras todos los favoritos i repartajasyones asosiados kon esta publikasyon i repuestas a eya seran guerfanadas.", @@ -372,6 +376,7 @@ "ignore_notifications_modal.not_followers_title": "Inyorar avizos de personas a las kualas no te sigen?", "ignore_notifications_modal.not_following_title": "Inyorar avizos de personas a las kualas no siges?", "ignore_notifications_modal.private_mentions_title": "Ignorar avizos de mensyones privadas no solisitadas?", + "info_button.label": "Ayuda", "interaction_modal.on_another_server": "En otro sirvidor", "interaction_modal.on_this_server": "En este sirvidor", "interaction_modal.title.favourite": "Endika ke te plaze publikasyon de {name}", @@ -426,6 +431,7 @@ "link_preview.shares": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}", "lists.add_member": "Adjusta", "lists.add_to_list": "Adjusta a lista", + "lists.create": "Kriya", "lists.create_list": "Kriya lista", "lists.delete": "Efasa lista", "lists.done": "Fecho", @@ -588,6 +594,7 @@ "poll_button.remove_poll": "Kita anketa", "privacy.change": "Troka privasita de publikasyon", "privacy.direct.long": "Todos enmentados en la publikasyon", + "privacy.direct.short": "Enmentadura privada", "privacy.private.long": "Solo para tus suivantes", "privacy.private.short": "Suivantes", "privacy.public.long": "Todos en i afuera de Mastodon", @@ -682,6 +689,7 @@ "search_results.accounts": "Profiles", "search_results.all": "Todos", "search_results.hashtags": "Etiketas", + "search_results.no_results": "No ay rezultados.", "search_results.see_all": "Ve todo", "search_results.statuses": "Publikasyones", "search_results.title": "Bushka por \"{q}\"", @@ -775,6 +783,8 @@ "video.expand": "Espande video", "video.fullscreen": "Ekran kompleto", "video.hide": "Eskonde video", + "video.mute": "Silensia", "video.pause": "Pauza", - "video.play": "Reproduze" + "video.play": "Reproduze", + "video.unmute": "Desilensia" } diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index f112bc73d9..7110e809c1 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -29,7 +29,6 @@ "account.endorse": "Rodyti profilyje", "account.featured_tags.last_status_at": "Paskutinis įrašas {date}", "account.featured_tags.last_status_never": "Nėra įrašų", - "account.featured_tags.title": "{name} rodomi saitažodžiai", "account.follow": "Sekti", "account.follow_back": "Sekti atgal", "account.followers": "Sekėjai", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 203b321574..27760c59ff 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -29,7 +29,6 @@ "account.endorse": "Izcelts profilā", "account.featured_tags.last_status_at": "Beidzamā ziņa {date}", "account.featured_tags.last_status_never": "Ierakstu nav", - "account.featured_tags.title": "{name} izceltie tēmturi", "account.follow": "Sekot", "account.follow_back": "Sekot atpakaļ", "account.followers": "Sekotāji", @@ -54,7 +53,7 @@ "account.muted": "Apklusināts", "account.mutual": "Abpusēji", "account.no_bio": "Apraksts nav sniegts.", - "account.open_original_page": "Atvērt oriģinālo lapu", + "account.open_original_page": "Atvērt pirmavota lapu", "account.posts": "Ieraksti", "account.posts_with_replies": "Ieraksti un atbildes", "account.report": "Sūdzēties par @{name}", @@ -214,7 +213,7 @@ "confirmations.missing_alt_text.secondary": "Vienalga iesūtīt", "confirmations.mute.confirm": "Apklusināt", "confirmations.redraft.confirm": "Dzēst un pārrakstīt", - "confirmations.redraft.message": "Vai tiešām vēlies dzēst šo ziņu un no jauna noformēt to? Izlase un pastiprinājumi tiks zaudēti, un atbildes uz sākotnējo ziņu tiks atstātas bez autoratlīdzības.", + "confirmations.redraft.message": "Vai tiešām vēlies izdzēst šo ierakstu un veidot jaunu tā uzmetumu? Pievienošana izlasēs un pastiprinājumi tiks zaudēti, un sākotnējā ieraksta atbildes paliks bez saiknes ar to.", "confirmations.redraft.title": "Dzēst un rakstīt vēlreiz?", "confirmations.reply.confirm": "Atbildēt", "confirmations.reply.message": "Tūlītēja atbildēšana pārrakstīs pašlaik sastādīto ziņu. Vai tiešām turpināt?", @@ -597,7 +596,7 @@ "report.close": "Darīts", "report.comment.title": "Vai, tavuprāt, mums vēl būtu kas jāzina?", "report.forward": "Pārsūtīt {target}", - "report.forward_hint": "Konts ir no cita servera. Vai nosūtīt anonimizētu sūdzības kopiju arī tam?", + "report.forward_hint": "Konts ir no cita servera. Vai nosūtīt anonimizētu ziņojuma kopiju arī tur?", "report.mute": "Apklusināt", "report.mute_explanation": "Tu neredzēsi viņu ierakstus. Viņi joprojām var Tev sekot un redzēt Tavus ierakstus un nezinās, ka viņi ir apklusināti.", "report.next": "Tālāk", @@ -692,7 +691,7 @@ "status.pinned": "Piesprausts ieraksts", "status.read_more": "Lasīt vairāk", "status.reblog": "Pastiprināt", - "status.reblog_private": "Pastiprināt, nemainot redzamību", + "status.reblog_private": "Pastiprināt ar sākotnējo redzamību", "status.reblogged_by": "{name} pastiprināja", "status.reblogs": "{count, plural, zero {pastiprinājumu} one {pastiprinājums} other {pastiprinājumi}}", "status.reblogs.empty": "Neviens šo ierakstu vēl nav pastiprinājis. Kad būs, tie parādīsies šeit.", @@ -706,7 +705,7 @@ "status.share": "Kopīgot", "status.show_less_all": "Rādīt mazāk visiem", "status.show_more_all": "Rādīt vairāk visiem", - "status.show_original": "Rādīt oriģinālu", + "status.show_original": "Rādīt pirmavotu", "status.title.with_attachments": "{user} publicējis {attachmentCount, plural, one {pielikumu} other {{attachmentCount} pielikumus}}", "status.translate": "Tulkot", "status.translated_from_with": "Tulkots no {lang} izmantojot {provider}", diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json index 94f9aef261..919a34532f 100644 --- a/app/javascript/mastodon/locales/mr.json +++ b/app/javascript/mastodon/locales/mr.json @@ -28,7 +28,6 @@ "account.endorse": "प्रोफाइलवरील वैशिष्ट्य", "account.featured_tags.last_status_at": "शेवटचे पोस्ट {date} रोजी", "account.featured_tags.last_status_never": "पोस्ट नाहीत", - "account.featured_tags.title": "{name} चे वैशिष्ट्यीकृत हॅशटॅग", "account.follow": "अनुयायी व्हा", "account.follow_back": "आपणही अनुसरण करा", "account.followers": "अनुयायी", diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json index af80cc20cf..f6a34116e8 100644 --- a/app/javascript/mastodon/locales/ms.json +++ b/app/javascript/mastodon/locales/ms.json @@ -29,7 +29,6 @@ "account.endorse": "Tampilkan di profil", "account.featured_tags.last_status_at": "Hantaran terakhir pada {date}", "account.featured_tags.last_status_never": "Tiada hantaran", - "account.featured_tags.title": "Tanda pagar pilihan {name}", "account.follow": "Ikuti", "account.follow_back": "Ikut balik", "account.followers": "Pengikut", diff --git a/app/javascript/mastodon/locales/my.json b/app/javascript/mastodon/locales/my.json index 51cba22153..362537edeb 100644 --- a/app/javascript/mastodon/locales/my.json +++ b/app/javascript/mastodon/locales/my.json @@ -28,7 +28,6 @@ "account.endorse": "အကောင့်ပရိုဖိုင်တွင်ဖော်ပြပါ", "account.featured_tags.last_status_at": "နောက်ဆုံးပို့စ်ကို {date} တွင် တင်ခဲ့သည်။", "account.featured_tags.last_status_never": "ပို့စ်တင်ထားခြင်းမရှိပါ", - "account.featured_tags.title": "ဖော်ပြထားသောဟက်ရှ်တက်ခ်များ", "account.follow": "စောင့်ကြည့်", "account.followers": "စောင့်ကြည့်သူများ", "account.followers.empty": "ဤသူကို စောင့်ကြည့်သူ မရှိသေးပါ။", diff --git a/app/javascript/mastodon/locales/nan.json b/app/javascript/mastodon/locales/nan.json index 57eef0a874..1ae348871f 100644 --- a/app/javascript/mastodon/locales/nan.json +++ b/app/javascript/mastodon/locales/nan.json @@ -29,7 +29,6 @@ "account.endorse": "用個人資料推薦對方", "account.featured_tags.last_status_at": "頂kái tī {date} Po文", "account.featured_tags.last_status_never": "無PO文", - "account.featured_tags.title": "{name} ê推薦hashtag", "account.follow": "跟tuè", "account.follow_back": "Tuè tńg去", "account.followers": "跟tuè lí ê", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, other {{count} ê PO文}}", "account.unblock": "取消封鎖 @{name}", "account.unblock_domain": "Kā域名 {domain} 取消封鎖", + "account.unblock_domain_short": "取消封鎖", "account.unblock_short": "取消封鎖", "account.unendorse": "Mài tī個人資料推薦伊", "account.unfollow": "取消跟tuè", @@ -387,8 +387,8 @@ "hashtag.column_settings.tag_mode.none": "Lóng mài", "hashtag.column_settings.tag_toggle": "Kā追加ê標籤加添kàu tsit ê欄", "hashtag.counter_by_accounts": "{count, plural, one {{counter} ê} other {{counter} ê}}參與ê", - "hashtag.counter_by_uses": "{count, plural, one {{counter} ê} other {{counter} ê}} PO文", - "hashtag.counter_by_uses_today": "Kin-á日有 {count, plural, one {{counter} ê} other {{counter} ê}} PO文", + "hashtag.counter_by_uses": "{count, plural, one {{counter} 篇} other {{counter} 篇}} PO文", + "hashtag.counter_by_uses_today": "Kin-á日有 {count, plural, one {{counter} 篇} other {{counter} 篇}} PO文", "hashtag.follow": "跟tuè hashtag", "hashtag.unfollow": "取消跟tuè hashtag", "hashtags.and_other": "……kap 其他 {count, plural, other {# ê}}", @@ -468,6 +468,55 @@ "keyboard_shortcuts.spoilers": "顯示/隱藏內容警告", "keyboard_shortcuts.start": "Phah開「開始用」欄", "keyboard_shortcuts.toggle_hidden": "顯示/隱藏內容警告後壁ê PO文", + "keyboard_shortcuts.toggle_sensitivity": "顯示/tshàng媒體", + "keyboard_shortcuts.toot": "PO新PO文", + "keyboard_shortcuts.translate": "kā PO文翻譯", + "keyboard_shortcuts.unfocus": "離開輸入框仔/tshiau-tshuē格仔", + "keyboard_shortcuts.up": "佇列單內kā suá khah面頂", + "lightbox.close": "關", + "lightbox.next": "下tsi̍t ê", + "lightbox.previous": "頂tsi̍t ê", + "lightbox.zoom_in": "Tshūn-kiu kàu實際ê sài-suh", + "lightbox.zoom_out": "Tshūn-kiu kàu適當ê sài-suh", + "limited_account_hint.action": "一直顯示個人資料", + "limited_account_hint.title": "Tsit ê 個人資料予 {domain} ê管理員tshàng起來ah。", + "link_preview.author": "Tuì {name}", + "link_preview.more_from_author": "看 {name} ê其他內容", + "link_preview.shares": "{count, plural, one {{counter} 篇} other {{counter} 篇}} PO文", + "lists.add_member": "加", + "lists.add_to_list": "加添kàu列單", + "lists.add_to_lists": "Kā {name} 加添kàu列單", + "lists.create": "建立", + "lists.create_a_list_to_organize": "開新ê列單,組織lí tshù ê時間線", + "lists.create_list": "建立列單", + "lists.delete": "Thâi掉列單", + "lists.done": "做好ah", + "lists.edit": "編輯列單", + "lists.exclusive": "佇tshù ê時間線kā成員tshàng起來。", + "lists.exclusive_hint": "Nā bóo-mi̍h口座佇tsit ê列單,ē tuì lí tshù ê時間線kā tsit ê口座tshàng起來,避免koh看見in ê PO文。", + "lists.find_users_to_add": "Tshuē beh加添ê用者", + "lists.list_members": "列單ê成員", + "lists.list_members_count": "{count, plural, other {# 位成員}}", + "lists.list_name": "列單ê名", + "lists.new_list_name": "新ê列單ê名", + "lists.no_lists_yet": "Iáu無列單。", + "lists.no_members_yet": "Iáu無成員。", + "lists.no_results_found": "Tshuē無結果。", + "lists.remove_member": "Suá掉", + "lists.replies_policy.followed": "所跟tuè ê任何用者", + "lists.replies_policy.list": "列單ê成員", + "lists.replies_policy.none": "無半位", + "lists.save": "儲存", + "lists.search": "Tshiau-tshuē", + "lists.show_replies_to": "列單成員回應ê顯示範圍", + "load_pending": "{count, plural, other {# ê 項目}}", + "loading_indicator.label": "Leh載入……", + "media_gallery.hide": "Khàm掉", + "moved_to_account_banner.text": "Lí ê口座 {disabledAccount} 已經停止使用ah,因為suá kàu {movedToAccount}。", + "mute_modal.hide_from_notifications": "Tuì通知內底khàm掉", + "mute_modal.hide_options": "Khàm掉選項", + "mute_modal.indefinite": "直到我取消消音", + "mute_modal.show_options": "顯示選項", "notification.favourite_pm": "{name} kah意lí ê私人提起", "notification.favourite_pm.name_and_others_with_link": "{name} kap{count, plural, other {另外 # ê lâng}}kah意lí ê私人提起", "search_popout.language_code": "ISO語言代碼", diff --git a/app/javascript/mastodon/locales/ne.json b/app/javascript/mastodon/locales/ne.json index 696f9fbed8..af2c922cbd 100644 --- a/app/javascript/mastodon/locales/ne.json +++ b/app/javascript/mastodon/locales/ne.json @@ -25,7 +25,6 @@ "account.enable_notifications": "@{name} ले पोस्ट गर्दा मलाई सूचित गर्नुहोस्", "account.endorse": "प्रोफाइलमा फिचर गर्नुहोस्", "account.featured_tags.last_status_never": "कुनै पोस्ट छैन", - "account.featured_tags.title": "{name}का विशेष ह्यासट्यागहरू", "account.follow": "फलो गर्नुहोस", "account.follow_back": "फलो ब्याक गर्नुहोस्", "account.followers": "फलोअरहरु", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index c1aef7ff7d..e22b7b3774 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -27,9 +27,11 @@ "account.edit_profile": "Profiel bewerken", "account.enable_notifications": "Geef een melding wanneer @{name} een bericht plaatst", "account.endorse": "Op profiel weergeven", + "account.featured": "Uitgelicht", + "account.featured.hashtags": "Hashtags", + "account.featured.posts": "Berichten", "account.featured_tags.last_status_at": "Laatste bericht op {date}", "account.featured_tags.last_status_never": "Geen berichten", - "account.featured_tags.title": "Uitgelichte hashtags van {name}", "account.follow": "Volgen", "account.follow_back": "Terugvolgen", "account.followers": "Volgers", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} bericht} other {{counter} berichten}}", "account.unblock": "@{name} deblokkeren", "account.unblock_domain": "{domain} niet langer blokkeren", + "account.unblock_domain_short": "Deblokkeren", "account.unblock_short": "Deblokkeren", "account.unendorse": "Niet op profiel weergeven", "account.unfollow": "Ontvolgen", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Zoekresultaten", "emoji_button.symbols": "Symbolen", "emoji_button.travel": "Reizen en locaties", + "empty_column.account_featured": "Deze lijst is leeg", "empty_column.account_hides_collections": "Deze gebruiker heeft ervoor gekozen deze informatie niet beschikbaar te maken", "empty_column.account_suspended": "Account opgeschort", "empty_column.account_timeline": "Hier zijn geen berichten!", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 4e0e96d974..ab867017a9 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -27,9 +27,11 @@ "account.edit_profile": "Rediger profil", "account.enable_notifications": "Varsle meg når @{name} skriv innlegg", "account.endorse": "Vis på profilen", + "account.featured": "Utvald", + "account.featured.hashtags": "Emneknaggar", + "account.featured.posts": "Innlegg", "account.featured_tags.last_status_at": "Sist nytta {date}", "account.featured_tags.last_status_never": "Ingen innlegg", - "account.featured_tags.title": "{name} sine framheva emneknaggar", "account.follow": "Fylg", "account.follow_back": "Fylg tilbake", "account.followers": "Fylgjarar", @@ -293,6 +295,7 @@ "emoji_button.search_results": "Søkeresultat", "emoji_button.symbols": "Symbol", "emoji_button.travel": "Reise & stader", + "empty_column.account_featured": "Denne lista er tom", "empty_column.account_hides_collections": "Denne brukaren har valt å ikkje gjere denne informasjonen tilgjengeleg", "empty_column.account_suspended": "Kontoen er utestengd", "empty_column.account_timeline": "Ingen tut her!", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 584132ffe6..a18ccdc0dc 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -29,7 +29,6 @@ "account.endorse": "Vis frem på profilen", "account.featured_tags.last_status_at": "Siste innlegg {date}", "account.featured_tags.last_status_never": "Ingen Innlegg", - "account.featured_tags.title": "{name} sine fremhevede emneknagger", "account.follow": "Følg", "account.follow_back": "Følg tilbake", "account.followers": "Følgere", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 616f8a64af..74ae8fad4d 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -26,7 +26,6 @@ "account.endorse": "Mostrar pel perfil", "account.featured_tags.last_status_at": "Darrièra publicacion lo {date}", "account.featured_tags.last_status_never": "Cap de publicacion", - "account.featured_tags.title": "Etiquetas en avant de {name}", "account.follow": "Sègre", "account.follow_back": "Sègre en retorn", "account.followers": "Seguidors", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index c6b2a5d412..ed827bcf29 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -29,7 +29,6 @@ "account.endorse": "Wyróżnij na profilu", "account.featured_tags.last_status_at": "Ostatni post {date}", "account.featured_tags.last_status_never": "Brak postów", - "account.featured_tags.title": "Polecane hasztagi {name}", "account.follow": "Obserwuj", "account.follow_back": "Również obserwuj", "account.followers": "Obserwujący", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 3ac1946503..55cfeea582 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -29,7 +29,6 @@ "account.endorse": "Recomendar", "account.featured_tags.last_status_at": "Última publicação em {date}", "account.featured_tags.last_status_never": "Sem publicações", - "account.featured_tags.title": "Hashtags em destaque de {name}", "account.follow": "Seguir", "account.follow_back": "Seguir de volta", "account.followers": "Seguidores", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publicação} other {{counter} publicações}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Desbloquear domínio {domain}", + "account.unblock_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "Remover", "account.unfollow": "Deixar de seguir", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index d5df6e59cf..87c9e5846a 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -29,7 +29,6 @@ "account.endorse": "Destacar no perfil", "account.featured_tags.last_status_at": "Última publicação em {date}", "account.featured_tags.last_status_never": "Sem publicações", - "account.featured_tags.title": "Etiquetas destacadas por {name}", "account.follow": "Seguir", "account.follow_back": "Seguir também", "account.followers": "Seguidores", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} publicação} other {{counter} publicações}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Desbloquear o domínio {domain}", + "account.unblock_domain_short": "Desbloquear", "account.unblock_short": "Desbloquear", "account.unendorse": "Não destacar no perfil", "account.unfollow": "Deixar de seguir", diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json index 56d577f3f0..8fec42bbd0 100644 --- a/app/javascript/mastodon/locales/ro.json +++ b/app/javascript/mastodon/locales/ro.json @@ -29,7 +29,6 @@ "account.endorse": "Promovează pe profil", "account.featured_tags.last_status_at": "Ultima postare pe {date}", "account.featured_tags.last_status_never": "Fără postări", - "account.featured_tags.title": "Haștagurile recomandate de {name}", "account.follow": "Urmărește", "account.follow_back": "Urmăreşte înapoi", "account.followers": "Urmăritori", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index bc19a93c60..6763a354f3 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -27,9 +27,9 @@ "account.edit_profile": "Редактировать", "account.enable_notifications": "Уведомлять о постах от @{name}", "account.endorse": "Рекомендовать в профиле", + "account.featured": "Избранное", "account.featured_tags.last_status_at": "Последний пост {date}", "account.featured_tags.last_status_never": "Нет постов", - "account.featured_tags.title": "Избранные хэштеги {name}", "account.follow": "Подписаться", "account.follow_back": "Подписаться в ответ", "account.followers": "Подписчики", @@ -65,6 +65,7 @@ "account.statuses_counter": "{count, plural, one {{counter} пост} few {{counter} поста} other {{counter} постов}}", "account.unblock": "Разблокировать @{name}", "account.unblock_domain": "Разблокировать {domain}", + "account.unblock_domain_short": "Разблокировать", "account.unblock_short": "Разблокировать", "account.unendorse": "Не рекомендовать в профиле", "account.unfollow": "Отписаться", diff --git a/app/javascript/mastodon/locales/ry.json b/app/javascript/mastodon/locales/ry.json index 8fd083efc3..ed9751634e 100644 --- a/app/javascript/mastodon/locales/ry.json +++ b/app/javascript/mastodon/locales/ry.json @@ -28,7 +28,6 @@ "account.endorse": "Указовати на профілови", "account.featured_tags.last_status_at": "Датум послідньої публикації {date}", "account.featured_tags.last_status_never": "Ниє публикацій", - "account.featured_tags.title": "Ублюблені гештеґы {name}", "account.follow": "Пудписати ся", "account.follow_back": "Пудписати ся тоже", "account.followers": "Пудписникы", diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json index 1ecc057023..ce88bda740 100644 --- a/app/javascript/mastodon/locales/sa.json +++ b/app/javascript/mastodon/locales/sa.json @@ -26,7 +26,6 @@ "account.endorse": "व्यक्तिगतविवरणे वैशिष्ट्यम्", "account.featured_tags.last_status_at": "{date} दिने गतस्थापनम्", "account.featured_tags.last_status_never": "न पत्रम्", - "account.featured_tags.title": "{name} इत्यस्य विशेषहैस्टैगः", "account.follow": "अनुस्रियताम्", "account.followers": "अनुसर्तारः", "account.followers.empty": "नाऽनुसर्तारो वर्तन्ते", diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json index c885078a73..79ef6f6ef5 100644 --- a/app/javascript/mastodon/locales/sc.json +++ b/app/javascript/mastodon/locales/sc.json @@ -29,7 +29,6 @@ "account.endorse": "Cussìgia in su profilu tuo", "account.featured_tags.last_status_at": "Ùrtima publicatzione in su {date}", "account.featured_tags.last_status_never": "Peruna publicatzione", - "account.featured_tags.title": "Etichetas de {name} in evidèntzia", "account.follow": "Sighi", "account.follow_back": "Sighi tue puru", "account.followers": "Sighiduras", diff --git a/app/javascript/mastodon/locales/sco.json b/app/javascript/mastodon/locales/sco.json index 5960fb7760..872a61a8a2 100644 --- a/app/javascript/mastodon/locales/sco.json +++ b/app/javascript/mastodon/locales/sco.json @@ -25,7 +25,6 @@ "account.endorse": "Shaw oan profile", "account.featured_tags.last_status_at": "Last post oan {date}", "account.featured_tags.last_status_never": "Nae posts", - "account.featured_tags.title": "{name}'s hielichtit hashtags", "account.follow": "Follae", "account.followers": "Follaers", "account.followers.empty": "Naebody follaes this uiser yit.", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index a55455f658..57fc3be484 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -29,7 +29,6 @@ "account.endorse": "Zobraziť na vlastnom profile", "account.featured_tags.last_status_at": "Posledný príspevok dňa {date}", "account.featured_tags.last_status_never": "Žiadne príspevky", - "account.featured_tags.title": "Odporúčané hashtagy účtu {name}", "account.follow": "Sledovať", "account.follow_back": "Sledovať späť", "account.followers": "Sledovatelia", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} príspevok} other {{counter} príspevkov}}", "account.unblock": "Odblokovať @{name}", "account.unblock_domain": "Odblokovať doménu {domain}", + "account.unblock_domain_short": "Odblokovať", "account.unblock_short": "Odblokovať", "account.unendorse": "Nezobrazovať na vlastnom profile", "account.unfollow": "Zrušiť sledovanie", @@ -826,6 +826,9 @@ "video.expand": "Zväčšiť video", "video.fullscreen": "Zobraziť na celú obrazovku", "video.hide": "Skryť video", + "video.mute": "Stíšiť", "video.pause": "Pozastaviť", - "video.play": "Prehrať" + "video.play": "Prehrať", + "video.volume_down": "Hlasitosť nadol", + "video.volume_up": "Hlasitosť nahor" } diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index e6c0b67427..eef20456de 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -29,7 +29,6 @@ "account.endorse": "Izpostavi v profilu", "account.featured_tags.last_status_at": "Zadnja objava {date}", "account.featured_tags.last_status_never": "Ni objav", - "account.featured_tags.title": "Izpostavljeni ključniki osebe {name}", "account.follow": "Sledi", "account.follow_back": "Sledi nazaj", "account.followers": "Sledilci", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 116b3906a2..bb1668b2ef 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -27,9 +27,11 @@ "account.edit_profile": "Përpunoni profilin", "account.enable_notifications": "Njoftomë, kur poston @{name}", "account.endorse": "Pasqyrojeni në profil", + "account.featured": "Të zgjedhur", + "account.featured.hashtags": "Hashtag-ë", + "account.featured.posts": "Postime", "account.featured_tags.last_status_at": "Postimi i fundit më {date}", "account.featured_tags.last_status_never": "Pa postime", - "account.featured_tags.title": "Hashtagë të zgjedhur të {name}", "account.follow": "Ndiqeni", "account.follow_back": "Ndiqe gjithashtu", "account.followers": "Ndjekës", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} postim} other {{counter} postime}}", "account.unblock": "Zhbllokoje @{name}", "account.unblock_domain": "Zhblloko përkatësinë {domain}", + "account.unblock_domain_short": "Zhbllokoje", "account.unblock_short": "Zhbllokoje", "account.unendorse": "Mos e përfshi në profil", "account.unfollow": "Resht së ndjekuri", @@ -288,6 +291,7 @@ "emoji_button.search_results": "Përfundime kërkimi", "emoji_button.symbols": "Simbole", "emoji_button.travel": "Udhëtime & Vende", + "empty_column.account_featured": "Kjo listë është e zbrazët", "empty_column.account_hides_collections": "Ky përdorues ka zgjedhur të mos e japë këtë informacion", "empty_column.account_suspended": "Llogaria u pezullua", "empty_column.account_timeline": "S’ka mesazhe këtu!", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 3d077cbe22..2d00533e0e 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -28,7 +28,6 @@ "account.endorse": "Istakni na profilu", "account.featured_tags.last_status_at": "Poslednja objava {date}", "account.featured_tags.last_status_never": "Nema objava", - "account.featured_tags.title": "Istaknute heš oznake korisnika {name}", "account.follow": "Prati", "account.follow_back": "Uzvrati praćenje", "account.followers": "Pratioci", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 43c57b3e25..af323bed27 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -28,7 +28,6 @@ "account.endorse": "Истакни на профилу", "account.featured_tags.last_status_at": "Последња објава {date}", "account.featured_tags.last_status_never": "Нема објава", - "account.featured_tags.title": "Истакнуте хеш ознаке корисника {name}", "account.follow": "Прати", "account.follow_back": "Узврати праћење", "account.followers": "Пратиоци", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 8dc6161a94..c23a645135 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -29,7 +29,6 @@ "account.endorse": "Visa på profil", "account.featured_tags.last_status_at": "Senaste inlägg den {date}", "account.featured_tags.last_status_never": "Inga inlägg", - "account.featured_tags.title": "{name}s utvalda hashtaggar", "account.follow": "Följ", "account.follow_back": "Följ tillbaka", "account.followers": "Följare", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, one {{counter} inlägg} other {{counter} inlägg}}", "account.unblock": "Avblockera @{name}", "account.unblock_domain": "Avblockera {domain}", + "account.unblock_domain_short": "Avblockera", "account.unblock_short": "Avblockera", "account.unendorse": "Visa inte på profil", "account.unfollow": "Sluta följ", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 859413b899..429d0db428 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -29,7 +29,6 @@ "account.endorse": "แสดงในโปรไฟล์", "account.featured_tags.last_status_at": "โพสต์ล่าสุดเมื่อ {date}", "account.featured_tags.last_status_never": "ไม่มีโพสต์", - "account.featured_tags.title": "แฮชแท็กที่น่าสนใจของ {name}", "account.follow": "ติดตาม", "account.follow_back": "ติดตามกลับ", "account.followers": "ผู้ติดตาม", @@ -65,6 +64,7 @@ "account.statuses_counter": "{count, plural, other {{counter} โพสต์}}", "account.unblock": "เลิกปิดกั้น @{name}", "account.unblock_domain": "เลิกปิดกั้นโดเมน {domain}", + "account.unblock_domain_short": "เลิกปิดกั้น", "account.unblock_short": "เลิกปิดกั้น", "account.unendorse": "ไม่แสดงในโปรไฟล์", "account.unfollow": "เลิกติดตาม", @@ -688,6 +688,7 @@ "poll_button.remove_poll": "เอาการสำรวจความคิดเห็นออก", "privacy.change": "เปลี่ยนความเป็นส่วนตัวของโพสต์", "privacy.direct.long": "ทุกคนที่กล่าวถึงในโพสต์", + "privacy.direct.short": "การกล่าวถึงแบบส่วนตัว", "privacy.private.long": "เฉพาะผู้ติดตามของคุณเท่านั้น", "privacy.private.short": "ผู้ติดตาม", "privacy.public.long": "ใครก็ตามที่อยู่ในและนอก Mastodon", @@ -892,6 +893,8 @@ "video.expand": "ขยายวิดีโอ", "video.fullscreen": "เต็มหน้าจอ", "video.hide": "ซ่อนวิดีโอ", + "video.mute": "ปิดเสียง", "video.pause": "หยุดชั่วคราว", - "video.play": "เล่น" + "video.play": "เล่น", + "video.unmute": "เลิกปิดเสียง" } diff --git a/app/javascript/mastodon/locales/tok.json b/app/javascript/mastodon/locales/tok.json index 59dd34eb5c..08ce6fd324 100644 --- a/app/javascript/mastodon/locales/tok.json +++ b/app/javascript/mastodon/locales/tok.json @@ -29,7 +29,6 @@ "account.endorse": "lipu jan la o suli e ni", "account.featured_tags.last_status_at": "sitelen pini pi jan ni li lon tenpo {date}", "account.featured_tags.last_status_never": "toki ala li lon", - "account.featured_tags.title": "{name} la kulupu ni pi toki suli li pona", "account.follow": "o kute", "account.follow_back": "jan ni li kute e sina. o kute", "account.followers": "jan kute", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index e95efc642a..fa5a84dd68 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -27,9 +27,11 @@ "account.edit_profile": "Profili düzenle", "account.enable_notifications": "@{name} kişisinin gönderi bildirimlerini aç", "account.endorse": "Profilimde öne çıkar", + "account.featured": "Öne çıkan", + "account.featured.hashtags": "Etiketler", + "account.featured.posts": "Gönderiler", "account.featured_tags.last_status_at": "Son gönderinin tarihi {date}", "account.featured_tags.last_status_never": "Gönderi yok", - "account.featured_tags.title": "{name} kişisinin öne çıkan etiketleri", "account.follow": "Takip et", "account.follow_back": "Geri takip et", "account.followers": "Takipçi", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} gönderi} other {{counter} gönderi}}", "account.unblock": "@{name} adlı kişinin engelini kaldır", "account.unblock_domain": "{domain} alan adının engelini kaldır", + "account.unblock_domain_short": "Engeli kaldır", "account.unblock_short": "Engeli kaldır", "account.unendorse": "Profilimde öne çıkarma", "account.unfollow": "Takibi bırak", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Arama sonuçları", "emoji_button.symbols": "Semboller", "emoji_button.travel": "Seyahat ve Yerler", + "empty_column.account_featured": "Bu liste boş", "empty_column.account_hides_collections": "Bu kullanıcı bu bilgiyi sağlamayı tercih etmemiştir", "empty_column.account_suspended": "Hesap askıya alındı", "empty_column.account_timeline": "Burada hiç gönderi yok!", diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json index 602d676361..ee50180ced 100644 --- a/app/javascript/mastodon/locales/tt.json +++ b/app/javascript/mastodon/locales/tt.json @@ -27,7 +27,6 @@ "account.endorse": "Профильдә тәкъдим итү", "account.featured_tags.last_status_at": "Соңгы хәбәр {date}", "account.featured_tags.last_status_never": "Хәбәрләр юк", - "account.featured_tags.title": "{name} тәкъдим ителгән хэштеглар", "account.follow": "Язылу", "account.followers": "Язылучы", "account.followers.empty": "Әле беркем дә язылмаган.", diff --git a/app/javascript/mastodon/locales/ug.json b/app/javascript/mastodon/locales/ug.json index 378f688ba8..e550d7e678 100644 --- a/app/javascript/mastodon/locales/ug.json +++ b/app/javascript/mastodon/locales/ug.json @@ -1,21 +1,34 @@ { - "about.blocks": "ئوتتۇراھال مۇلازىمېتىر", - "about.contact": "ئالاقىلاشقۇچى:", - "account.badges.bot": "Bot", - "account.cancel_follow_request": "Withdraw follow request", - "account.posts": "Toots", - "account.posts_with_replies": "Toots and replies", + "about.blocks": "باشقۇرۇلىدىغان مۇلازىمېتىر", + "about.contact": "ئالاقە:", + "about.disclaimer": "Mastodon ھەقسىز، ئوچۇق كودلۇق يۇمشاق دېتال تاۋار ماركىسى Mastodon gGmbH غا تەۋە.", + "about.domain_blocks.no_reason_available": "سەۋەبىنى ئىشلەتكىلى بولمايدۇ", + "account.badges.bot": "ماشىنا ئادەم", + "account.cancel_follow_request": "ئەگىشىش ئىلتىماسىدىن ۋاز كەچ", + "account.posts": "يازما", + "account.posts_with_replies": "يازما ۋە ئىنكاس", + "account.report": "@{name} نى پاش قىل", "account.requested": "Awaiting approval", - "account_note.placeholder": "Click to add a note", - "column.pins": "Pinned toot", - "community.column_settings.media_only": "Media only", + "account_note.placeholder": "چېكىلسە ئىزاھات قوشىدۇ", + "column.pins": "چوققىلانغان يازما", + "community.column_settings.media_only": "ۋاسىتەلا", "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.", "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.", "compose_form.placeholder": "What is on your mind?", - "compose_form.publish_form": "Publish", - "compose_form.spoiler.marked": "Text is hidden behind warning", + "compose_form.publish_form": "يېڭى يازما", + "compose_form.reply": "جاۋاب", + "compose_form.save_changes": "يېڭىلا", + "compose_form.spoiler.marked": "مەزمۇن ئاگاھلاندۇرۇشىنى چىقىرىۋەت", "compose_form.spoiler.unmarked": "Text is not hidden", - "confirmations.delete.message": "Are you sure you want to delete this status?", + "compose_form.spoiler_placeholder": "مەزمۇن ئاگاھلاندۇرۇشى (تاللاشچان)", + "confirmation_modal.cancel": "ۋاز كەچ", + "confirmations.block.confirm": "توس", + "confirmations.delete.message": "بۇ يازمىنى راستىنلا ئۆچۈرەمسىز؟", + "confirmations.delete.title": "يازما ئۆچۈرەمدۇ؟", + "confirmations.delete_list.confirm": "ئۆچۈر", + "confirmations.delete_list.message": "بۇ تىزىمنى راستتىنلا مەڭگۈلۈك ئۆچۈرەمسىز؟", + "confirmations.delete_list.title": "تىزىمنى ئۆچۈرەمدۇ؟", + "confirmations.discard_edit_media.confirm": "تاشلىۋەت", "embed.instructions": "Embed this status on your website by copying the code below.", "empty_column.account_timeline": "No toots here!", "empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 85b0125c07..b995713c52 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -27,9 +27,11 @@ "account.edit_profile": "Редагувати профіль", "account.enable_notifications": "Повідомляти мене про дописи @{name}", "account.endorse": "Рекомендувати у моєму профілі", + "account.featured": "Рекомендоване", + "account.featured.hashtags": "Хештеги", + "account.featured.posts": "Дописи", "account.featured_tags.last_status_at": "Останній допис {date}", "account.featured_tags.last_status_never": "Немає дописів", - "account.featured_tags.title": "{name} виділяє хештеґи", "account.follow": "Підписатися", "account.follow_back": "Стежити також", "account.followers": "Підписники", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, one {{counter} допис} few {{counter} дописи} many {{counter} дописів} other {{counter} допис}}", "account.unblock": "Розблокувати @{name}", "account.unblock_domain": "Розблокувати {domain}", + "account.unblock_domain_short": "Розблокувати", "account.unblock_short": "Розблокувати", "account.unendorse": "Не публікувати у профілі", "account.unfollow": "Відписатися", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Результати пошуку", "emoji_button.symbols": "Символи", "emoji_button.travel": "Подорожі та місця", + "empty_column.account_featured": "Список порожній", "empty_column.account_hides_collections": "Цей користувач вирішив не робити цю інформацію доступною", "empty_column.account_suspended": "Обліковий запис заблоковано", "empty_column.account_timeline": "Тут немає дописів!", diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json index cd50b512b4..e28ad93828 100644 --- a/app/javascript/mastodon/locales/ur.json +++ b/app/javascript/mastodon/locales/ur.json @@ -22,7 +22,6 @@ "account.endorse": "مشکص پر نمایاں کریں", "account.featured_tags.last_status_at": "آخری پوسٹ {date} کو", "account.featured_tags.last_status_never": "کوئی مراسلہ نہیں", - "account.featured_tags.title": "{name} کے نمایاں ہیش ٹیگز", "account.follow": "پیروی کریں", "account.follow_back": "اکاؤنٹ کو فالو بیک ", "account.followers": "پیروکار", diff --git a/app/javascript/mastodon/locales/uz.json b/app/javascript/mastodon/locales/uz.json index e58ab35444..6dd350651d 100644 --- a/app/javascript/mastodon/locales/uz.json +++ b/app/javascript/mastodon/locales/uz.json @@ -25,7 +25,6 @@ "account.endorse": "Profildagi xususiyat", "account.featured_tags.last_status_at": "Oxirgi post: {date}", "account.featured_tags.last_status_never": "Habarlar yo'q", - "account.featured_tags.title": "{name} ning taniqli hashtaglari", "account.follow": "Obuna bo‘lish", "account.followers": "Obunachilar", "account.followers.empty": "Bu foydalanuvchini hali hech kim kuzatmaydi.", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index b179f50fa9..a608b3ed85 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -27,9 +27,11 @@ "account.edit_profile": "Sửa hồ sơ", "account.enable_notifications": "Nhận thông báo khi @{name} đăng tút", "account.endorse": "Tôn vinh người này", + "account.featured": "Nổi bật", + "account.featured.hashtags": "Hashtag", + "account.featured.posts": "Tút", "account.featured_tags.last_status_at": "Tút gần nhất {date}", "account.featured_tags.last_status_never": "Chưa có tút", - "account.featured_tags.title": "Hashtag của {name}", "account.follow": "Theo dõi", "account.follow_back": "Theo dõi lại", "account.followers": "Người theo dõi", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, other {{counter} Tút}}", "account.unblock": "Bỏ chặn @{name}", "account.unblock_domain": "Bỏ ẩn {domain}", + "account.unblock_domain_short": "Bỏ chặn", "account.unblock_short": "Bỏ chặn", "account.unendorse": "Ngưng tôn vinh người này", "account.unfollow": "Bỏ theo dõi", @@ -293,6 +296,7 @@ "emoji_button.search_results": "Kết quả tìm kiếm", "emoji_button.symbols": "Biểu tượng", "emoji_button.travel": "Du lịch", + "empty_column.account_featured": "Danh sách trống", "empty_column.account_hides_collections": "Người này đã chọn ẩn thông tin", "empty_column.account_suspended": "Tài khoản vô hiệu hóa", "empty_column.account_timeline": "Chưa có tút nào!", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 050e1308b6..48f12d241a 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -29,7 +29,6 @@ "account.endorse": "在个人资料中推荐此用户", "account.featured_tags.last_status_at": "上次发言于 {date}", "account.featured_tags.last_status_never": "暂无嘟文", - "account.featured_tags.title": "{name} 的精选标签", "account.follow": "关注", "account.follow_back": "回关", "account.followers": "关注者", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index fb920a6b09..493d06b672 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -29,7 +29,6 @@ "account.endorse": "在個人檔案中推薦對方", "account.featured_tags.last_status_at": "上次帖文於 {date}", "account.featured_tags.last_status_never": "暫無文章", - "account.featured_tags.title": "{name} 的精選標籤", "account.follow": "關注", "account.follow_back": "追蹤對方", "account.followers": "追蹤者", @@ -84,6 +83,8 @@ "alert.unexpected.message": "發生意外錯誤。", "alert.unexpected.title": "失敗!", "alt_text_badge.title": "替代文字", + "alt_text_modal.cancel": "取消", + "alt_text_modal.done": "完成", "announcement.announcement": "公告", "attachments_list.unprocessed": "(未處理)", "audio.hide": "隱藏音訊", @@ -135,6 +136,7 @@ "column_header.pin": "置頂", "column_header.show_settings": "顯示設定", "column_header.unpin": "取消置頂", + "column_search.cancel": "取消", "column_subheading.settings": "設定", "community.column_settings.local_only": "只顯示本站", "community.column_settings.media_only": "只顯示多媒體", @@ -344,6 +346,7 @@ "home.pending_critical_update.title": "有重要的安全更新!", "home.show_announcements": "顯示公告", "ignore_notifications_modal.ignore": "忽略推播通知", + "info_button.label": "幫助", "interaction_modal.on_another_server": "於不同伺服器", "interaction_modal.on_this_server": "於此伺服器", "interaction_modal.title.favourite": "把 {name} 的帖文加入最愛", @@ -394,6 +397,7 @@ "limited_account_hint.title": "此個人檔案已被 {domain} 的管理員隱藏。", "link_preview.author": "由 {name} 提供", "lists.delete": "刪除列表", + "lists.done": "完成", "lists.edit": "編輯列表", "lists.replies_policy.followed": "任何已關注的用戶", "lists.replies_policy.list": "列表中的用戶", @@ -461,6 +465,7 @@ "notification.update": "{name} 編輯了帖文", "notification_requests.accept": "接受", "notification_requests.dismiss": "忽略", + "notification_requests.exit_selection": "完成", "notification_requests.notifications_from": "來自 {name} 的通知", "notification_requests.title": "已過濾之通知", "notifications.clear": "清空通知紀錄", @@ -507,6 +512,7 @@ "notifications_permission_banner.enable": "啟用桌面通知", "notifications_permission_banner.how_to_control": "只要啟用桌面通知,便可在 Mastodon 網站沒有打開時收到通知。在已經啟用桌面通知的時候,你可以透過上面的 {icon} 按鈕準確控制哪些類型的互動會產生桌面通知。", "notifications_permission_banner.title": "不放過任何事情", + "onboarding.follows.done": "完成", "onboarding.follows.empty": "很遺憾,現在無法顯示任何結果。你可以嘗試搜尋或瀏覽探索頁面來找使用者來追蹤,或者稍後再試。", "onboarding.profile.discoverable": "將個人檔案設為可被搜尋", "onboarding.profile.discoverable_hint": "當你在 Mastodon 上選擇可被搜尋時,你的帖文可能會出現在搜尋結果和熱門,你的個人檔案也可能被推薦給與你興趣相似的人。", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 710814f12d..3bb8ed4cbc 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -27,9 +27,11 @@ "account.edit_profile": "編輯個人檔案", "account.enable_notifications": "當 @{name} 嘟文時通知我", "account.endorse": "於個人檔案推薦對方", + "account.featured": "精選內容", + "account.featured.hashtags": "主題標籤", + "account.featured.posts": "嘟文", "account.featured_tags.last_status_at": "上次嘟文於 {date}", "account.featured_tags.last_status_never": "沒有嘟文", - "account.featured_tags.title": "{name} 的推薦主題標籤", "account.follow": "跟隨", "account.follow_back": "跟隨回去", "account.followers": "跟隨者", @@ -65,6 +67,7 @@ "account.statuses_counter": "{count, plural, other {{count} 則嘟文}}", "account.unblock": "解除封鎖 @{name}", "account.unblock_domain": "解除封鎖網域 {domain}", + "account.unblock_domain_short": "解除封鎖", "account.unblock_short": "解除封鎖", "account.unendorse": "取消於個人檔案推薦對方", "account.unfollow": "取消跟隨", @@ -293,6 +296,7 @@ "emoji_button.search_results": "搜尋結果", "emoji_button.symbols": "符號", "emoji_button.travel": "旅遊與地點", + "empty_column.account_featured": "此列表為空", "empty_column.account_hides_collections": "這位使用者選擇不提供此資訊", "empty_column.account_suspended": "帳號已被停權", "empty_column.account_timeline": "這裡還沒有嘟文!", diff --git a/app/javascript/mastodon/models/dropdown_menu.ts b/app/javascript/mastodon/models/dropdown_menu.ts index 35a29ab62a..ceea9ad4dd 100644 --- a/app/javascript/mastodon/models/dropdown_menu.ts +++ b/app/javascript/mastodon/models/dropdown_menu.ts @@ -3,16 +3,18 @@ interface BaseMenuItem { dangerous?: boolean; } -interface ActionMenuItem extends BaseMenuItem { +export interface ActionMenuItem extends BaseMenuItem { action: () => void; } -interface LinkMenuItem extends BaseMenuItem { +export interface LinkMenuItem extends BaseMenuItem { to: string; } -interface ExternalLinkMenuItem extends BaseMenuItem { +export interface ExternalLinkMenuItem extends BaseMenuItem { href: string; + target?: string; + method?: 'post' | 'put' | 'delete'; } export type MenuItem = @@ -20,5 +22,3 @@ export type MenuItem = | LinkMenuItem | ExternalLinkMenuItem | null; - -export type DropdownMenu = MenuItem[]; diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index c1afdf1bcc..1fd660e48b 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -624,9 +624,9 @@ export const composeReducer = (state = initialState, action) => { if (action.status.get('poll')) { map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), - multiple: action.status.getIn(['poll', 'multiple']), - expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), + options: ImmutableList(action.status.get('poll').options.map(x => x.title)), + multiple: action.status.get('poll').multiple, + expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at), })); } }); @@ -660,9 +660,9 @@ export const composeReducer = (state = initialState, action) => { if (action.status.get('poll')) { map.set('poll', ImmutableMap({ - options: action.status.getIn(['poll', 'options']).map(x => x.get('title')), - multiple: action.status.getIn(['poll', 'multiple']), - expires_in: expiresInFromExpiresAt(action.status.getIn(['poll', 'expires_at'])), + options: ImmutableList(action.status.get('poll').options.map(x => x.title)), + multiple: action.status.get('poll').multiple, + expires_in: expiresInFromExpiresAt(action.status.get('poll').expires_at), })); } }); diff --git a/app/javascript/mastodon/reducers/dropdown_menu.ts b/app/javascript/mastodon/reducers/dropdown_menu.ts index 59e19bb16d..0e46f0ee80 100644 --- a/app/javascript/mastodon/reducers/dropdown_menu.ts +++ b/app/javascript/mastodon/reducers/dropdown_menu.ts @@ -3,15 +3,15 @@ import { createReducer } from '@reduxjs/toolkit'; import { closeDropdownMenu, openDropdownMenu } from '../actions/dropdown_menu'; interface DropdownMenuState { - openId: string | null; + openId: number | null; keyboard: boolean; - scrollKey: string | null; + scrollKey: string | undefined; } const initialState: DropdownMenuState = { openId: null, keyboard: false, - scrollKey: null, + scrollKey: undefined, }; export const dropdownMenuReducer = createReducer(initialState, (builder) => { @@ -27,7 +27,7 @@ export const dropdownMenuReducer = createReducer(initialState, (builder) => { .addCase(closeDropdownMenu, (state, { payload: { id } }) => { if (state.openId === id) { state.openId = null; - state.scrollKey = null; + state.scrollKey = undefined; } }); }); diff --git a/app/javascript/mastodon/reducers/followed_tags.js b/app/javascript/mastodon/reducers/followed_tags.js deleted file mode 100644 index afea8e3b35..0000000000 --- a/app/javascript/mastodon/reducers/followed_tags.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; - -import { - FOLLOWED_HASHTAGS_FETCH_REQUEST, - FOLLOWED_HASHTAGS_FETCH_SUCCESS, - FOLLOWED_HASHTAGS_FETCH_FAIL, - FOLLOWED_HASHTAGS_EXPAND_REQUEST, - FOLLOWED_HASHTAGS_EXPAND_SUCCESS, - FOLLOWED_HASHTAGS_EXPAND_FAIL, -} from 'mastodon/actions/tags'; - -const initialState = ImmutableMap({ - items: ImmutableList(), - isLoading: false, - next: null, -}); - -export default function followed_tags(state = initialState, action) { - switch(action.type) { - case FOLLOWED_HASHTAGS_FETCH_REQUEST: - return state.set('isLoading', true); - case FOLLOWED_HASHTAGS_FETCH_SUCCESS: - return state.withMutations(map => { - map.set('items', fromJS(action.followed_tags)); - map.set('isLoading', false); - map.set('next', action.next); - }); - case FOLLOWED_HASHTAGS_FETCH_FAIL: - return state.set('isLoading', false); - case FOLLOWED_HASHTAGS_EXPAND_REQUEST: - return state.set('isLoading', true); - case FOLLOWED_HASHTAGS_EXPAND_SUCCESS: - return state.withMutations(map => { - map.update('items', set => set.concat(fromJS(action.followed_tags))); - map.set('isLoading', false); - map.set('next', action.next); - }); - case FOLLOWED_HASHTAGS_EXPAND_FAIL: - return state.set('isLoading', false); - default: - return state; - } -} diff --git a/app/javascript/mastodon/reducers/index.ts b/app/javascript/mastodon/reducers/index.ts index 0c5e748b00..617d7a08d9 100644 --- a/app/javascript/mastodon/reducers/index.ts +++ b/app/javascript/mastodon/reducers/index.ts @@ -16,7 +16,6 @@ import conversations from './conversations'; import custom_emojis from './custom_emojis'; import { dropdownMenuReducer } from './dropdown_menu'; import filters from './filters'; -import followed_tags from './followed_tags'; import height_cache from './height_cache'; import history from './history'; import { listsReducer } from './lists'; @@ -80,7 +79,6 @@ const reducers = { markers: markersReducer, picture_in_picture: pictureInPictureReducer, history, - followed_tags, reaction_deck, notificationPolicy: notificationPolicyReducer, notificationRequests: notificationRequestsReducer, diff --git a/app/javascript/mastodon/reducers/status_lists.js b/app/javascript/mastodon/reducers/status_lists.js index 2e60d831b1..cc660af5c3 100644 --- a/app/javascript/mastodon/reducers/status_lists.js +++ b/app/javascript/mastodon/reducers/status_lists.js @@ -184,6 +184,7 @@ const removeOneFromAllBookmarkCategoriesById = (state, statusId) => { return s; }; +/** @type {import('@reduxjs/toolkit').Reducer} */ export default function statusLists(state = initialState, action) { switch(action.type) { case FAVOURITED_STATUSES_FETCH_REQUEST: diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index 91ff05df4d..a03ee70e3f 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -6,6 +6,7 @@ import { me, isHideItem } from '../initial_state'; import { getFilters } from './filters'; export { makeGetAccount } from "./accounts"; +export { getStatusList, getSubStatusList } from "./statuses"; export const makeGetStatus = () => { return createSelector( @@ -93,15 +94,3 @@ export const makeGetReport = () => createSelector([ (_, base) => base, (state, _, targetAccountId) => state.getIn(['accounts', targetAccountId]), ], (base, targetAccount) => base.set('target_account', targetAccount)); - -export const getStatusList = createSelector([ - (state, type) => state.getIn(['status_lists', type, 'items']), -], (items) => items.toList()); - -export const getBookmarkCategoryStatusList = createSelector([ - (state, bookmarkCategoryId) => state.getIn(['status_lists', 'bookmark_category_statuses', bookmarkCategoryId, 'items']), -], (items) => items ? items.toList() : ImmutableList()); - -export const getCircleStatusList = createSelector([ - (state, circleId) => state.getIn(['status_lists', 'circle_statuses', circleId, `items`]), -], (items) => items ? items.toList() : ImmutableList()); diff --git a/app/javascript/mastodon/selectors/statuses.ts b/app/javascript/mastodon/selectors/statuses.ts new file mode 100644 index 0000000000..d07cc93aec --- /dev/null +++ b/app/javascript/mastodon/selectors/statuses.ts @@ -0,0 +1,33 @@ +import { createSelector } from '@reduxjs/toolkit'; +import type { OrderedSet as ImmutableOrderedSet } from 'immutable'; +import { List as ImmutableList } from 'immutable'; + +import type { RootState } from 'mastodon/store'; + +export const getStatusList = createSelector( + [ + ( + state: RootState, + type: + | 'favourites' + | 'bookmarks' + | 'pins' + | 'trending' + | 'emoji_reactions', + ) => + state.status_lists.getIn([type, 'items']) as ImmutableOrderedSet, + ], + (items) => items.toList(), +); + +export const getSubStatusList = createSelector( + [ + (state: RootState, type: 'bookmark_category' | 'circle', id: string) => + state.status_lists.getIn([ + `${type}_statuses`, + id, + 'items', + ]) as ImmutableOrderedSet | null, + ], + (items) => (items ? items.toList() : ImmutableList()), +); diff --git a/app/javascript/styles/full-dark/variables.scss b/app/javascript/styles/full-dark/variables.scss index 6cedec7df9..1720d716fe 100644 --- a/app/javascript/styles/full-dark/variables.scss +++ b/app/javascript/styles/full-dark/variables.scss @@ -1,11 +1,14 @@ $classic-base-color: #282c37; // Midnight Express $classic-secondary-color: #d9e1e8; // Pattens Blue -// Variables for defaults in UI -$simple-background-color: $classic-base-color !default; +@use '../mastodon/variables' with ( + // Variables for defaults in UI + $simple-background-color: $classic-base-color, -// Tell UI to use selected colors -$ui-base-lighter-color: #969fbc !default; // Lighter darkest + // Tell UI to use selected colors + $ui-base-lighter-color: #969fbc, -// For texts on inverted backgrounds -$inverted-text-color: $classic-secondary-color !default; + // Lighter darkest + // For texts on inverted backgrounds + $inverted-text-color: $classic-secondary-color +); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 2c62522d56..9758ecc62c 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -5582,6 +5582,9 @@ a.status-card { &__results { &__item { + display: flex; + align-items: center; + gap: 0.5em; cursor: pointer; color: $primary-text-color; font-size: 14px; @@ -6586,7 +6589,7 @@ a.status-card { a { text-decoration: none; - color: $inverted-text-color; + color: $highlight-text-color; font-weight: 500; &:hover { @@ -7902,14 +7905,11 @@ a.status-card { } .radio-button__input.checked::before { - position: absolute; - left: 2px; - top: 2px; content: ''; display: block; border-radius: 50%; - width: 12px; - height: 12px; + width: calc(100% - 4px); + height: calc(100% - 4px); background: $ui-highlight-color; } @@ -8574,13 +8574,9 @@ noscript { &__item { display: flex; align-items: center; - padding: 15px; + padding: 16px; border-bottom: 1px solid var(--background-border-color); - gap: 15px; - - &:last-child { - border-bottom: 0; - } + gap: 8px; &__name { flex: 1 1 auto; @@ -8687,7 +8683,7 @@ noscript { } &--compact &__item { - padding: 10px; + padding: 12px; } } diff --git a/app/lib/admin/metrics/dimension/space_usage_dimension.rb b/app/lib/admin/metrics/dimension/space_usage_dimension.rb index f1b6dba040..0d3fd8db33 100644 --- a/app/lib/admin/metrics/dimension/space_usage_dimension.rb +++ b/app/lib/admin/metrics/dimension/space_usage_dimension.rb @@ -45,7 +45,6 @@ class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension PreviewCard.sum(:image_file_size), Account.sum(Arel.sql('COALESCE(avatar_file_size, 0) + COALESCE(header_file_size, 0)')), Backup.sum(:dump_file_size), - Import.sum(:data_file_size), SiteUpload.sum(:file_file_size), ].sum diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb index deead3717d..56ffd2a28c 100644 --- a/app/lib/status_cache_hydrator.rb +++ b/app/lib/status_cache_hydrator.rb @@ -28,13 +28,7 @@ class StatusCacheHydrator def hydrate_non_reblog_payload(empty_payload, account_id, account) empty_payload.tap do |payload| - payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.id) - payload[:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: @status.id) - payload[:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: @status.conversation_id) - payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.id) - payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.id) if @status.account_id == account_id - payload[:filtered] = mapped_applied_custom_filter(account_id, @status) - payload[:emoji_reactions] = @status.emoji_reactions_grouped_by_name(account) + fill_status_payload(payload, @status, account_id, account) if payload[:poll] payload[:poll][:voted] = @status.account_id == account_id @@ -48,19 +42,12 @@ class StatusCacheHydrator payload[:muted] = false payload[:bookmarked] = false payload[:pinned] = false if @status.account_id == account_id - payload[:filtered] = mapped_applied_custom_filter(account_id, @status.reblog) # If the reblogged status is being delivered to the author who disabled the display of the application # used to create the status, we need to hydrate it here too payload[:reblog][:application] = payload_reblog_application if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id - payload[:reblog][:favourited] = Favourite.exists?(account_id: account_id, status_id: @status.reblog_of_id) - payload[:reblog][:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: @status.reblog_of_id) - payload[:reblog][:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: @status.reblog.conversation_id) - payload[:reblog][:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: @status.reblog_of_id) - payload[:reblog][:pinned] = StatusPin.exists?(account_id: account_id, status_id: @status.reblog_of_id) if @status.reblog.account_id == account_id - payload[:reblog][:filtered] = payload[:filtered] - payload[:reblog][:emoji_reactions] = @status.reblog.emoji_reactions_grouped_by_name(account) + fill_status_payload(payload[:reblog], @status.reblog, account_id, account) if payload[:reblog][:poll] if @status.reblog.account_id == account_id @@ -73,11 +60,22 @@ class StatusCacheHydrator end end + payload[:filtered] = payload[:reblog][:filtered] payload[:favourited] = payload[:reblog][:favourited] payload[:reblogged] = payload[:reblog][:reblogged] end end + def fill_status_payload(payload, status, account_id, account) + payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: status.id) + payload[:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: status.id) + payload[:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: status.conversation_id) + payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: status.id) + payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: status.id) if status.account_id == account_id + payload[:filtered] = mapped_applied_custom_filter(account_id, status) + payload[:emoji_reactions] = status.emoji_reactions_grouped_by_name(account) + end + def mapped_applied_custom_filter(account_id, status) CustomFilter .apply_cached_filters(CustomFilter.cached_filters_for(account_id), status, following: following?(account_id)) diff --git a/app/models/import.rb b/app/models/import.rb deleted file mode 100644 index 6b261f8d00..0000000000 --- a/app/models/import.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -# == Schema Information -# -# Table name: imports -# -# id :bigint(8) not null, primary key -# type :integer not null -# approved :boolean default(FALSE), not null -# created_at :datetime not null -# updated_at :datetime not null -# data_file_name :string -# data_content_type :string -# data_updated_at :datetime -# account_id :bigint(8) not null -# overwrite :boolean default(FALSE), not null -# data_file_size :integer -# - -# NOTE: This is a deprecated model, only kept to not break ongoing imports -# on upgrade. See `BulkImport` and `Form::Import` for its replacements. - -class Import < ApplicationRecord - FILE_TYPES = %w(text/plain text/csv application/csv).freeze - MODES = %i(merge overwrite).freeze - - self.inheritance_column = false - - belongs_to :account - - enum :type, { following: 0, blocking: 1, muting: 2, domain_blocking: 3, bookmarks: 4 } - - validates :type, presence: true - - has_attached_file :data - validates_attachment_content_type :data, content_type: FILE_TYPES - validates_attachment_presence :data - - def mode - overwrite? ? :overwrite : :merge - end - - def mode=(str) - self.overwrite = str.to_sym == :overwrite - end -end diff --git a/app/serializers/rest/web_push_subscription_serializer.rb b/app/serializers/rest/web_push_subscription_serializer.rb index 4cb980bb93..01825a3bb0 100644 --- a/app/serializers/rest/web_push_subscription_serializer.rb +++ b/app/serializers/rest/web_push_subscription_serializer.rb @@ -6,7 +6,7 @@ class REST::WebPushSubscriptionSerializer < ActiveModel::Serializer delegate :standard, to: :object def alerts - (object.data&.dig('alerts') || {}).each_with_object({}) { |(k, v), h| h[k] = ActiveModel::Type::Boolean.new.cast(v) } + (object.data&.dig('alerts') || {}).transform_values { |v| ActiveModel::Type::Boolean.new.cast(v) } end def server_key diff --git a/app/services/import_service.rb b/app/services/import_service.rb deleted file mode 100644 index a695df2fc9..0000000000 --- a/app/services/import_service.rb +++ /dev/null @@ -1,144 +0,0 @@ -# frozen_string_literal: true - -require 'csv' - -# NOTE: This is a deprecated service, only kept to not break ongoing imports -# on upgrade. See `BulkImportService` for its replacement. - -class ImportService < BaseService - ROWS_PROCESSING_LIMIT = 20_000 - - def call(import) - @import = import - @account = @import.account - - case @import.type - when 'following' - import_follows! - when 'blocking' - import_blocks! - when 'muting' - import_mutes! - when 'domain_blocking' - import_domain_blocks! - when 'bookmarks' - import_bookmarks! - end - end - - private - - def import_follows! - parse_import_data!(['Account address']) - import_relationships!('follow', 'unfollow', @account.following, ROWS_PROCESSING_LIMIT, reblogs: { header: 'Show boosts', default: true }, notify: { header: 'Notify on new posts', default: false }, languages: { header: 'Languages', default: nil }) - end - - def import_blocks! - parse_import_data!(['Account address']) - import_relationships!('block', 'unblock', @account.blocking, ROWS_PROCESSING_LIMIT) - end - - def import_mutes! - parse_import_data!(['Account address']) - import_relationships!('mute', 'unmute', @account.muting, ROWS_PROCESSING_LIMIT, notifications: { header: 'Hide notifications', default: true }) - end - - def import_domain_blocks! - parse_import_data!(['#domain']) - items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#domain'].strip } - - if @import.overwrite? - presence_hash = items.index_with(true) - - @account.domain_blocks.find_each do |domain_block| - if presence_hash[domain_block.domain] - items.delete(domain_block.domain) - else - @account.unblock_domain!(domain_block.domain) - end - end - end - - items.each do |domain| - @account.block_domain!(domain) - end - - AfterAccountDomainBlockWorker.push_bulk(items) do |domain| - [@account.id, domain] - end - end - - def import_relationships!(action, undo_action, overwrite_scope, limit, extra_fields = {}) - local_domain_suffix = "@#{Rails.configuration.x.local_domain}" - items = @data.take(limit).map { |row| [row['Account address']&.strip&.delete_suffix(local_domain_suffix), extra_fields.to_h { |key, field_settings| [key, row[field_settings[:header]]&.strip || field_settings[:default]] }] }.reject { |(id, _)| id.blank? } - - if @import.overwrite? - presence_hash = items.each_with_object({}) { |(id, extra), mapping| mapping[id] = [true, extra] } - - overwrite_scope.reorder(nil).find_each do |target_account| - if presence_hash[target_account.acct] - items.delete(target_account.acct) - extra = presence_hash[target_account.acct][1] - Import::RelationshipWorker.perform_async(@account.id, target_account.acct, action, extra.stringify_keys) - else - Import::RelationshipWorker.perform_async(@account.id, target_account.acct, undo_action) - end - end - end - - head_items = items.uniq { |acct, _| acct.split('@')[1] } - tail_items = items - head_items - - Import::RelationshipWorker.push_bulk(head_items + tail_items) do |acct, extra| - [@account.id, acct, action, extra.stringify_keys] - end - end - - def import_bookmarks! - parse_import_data!(['#uri']) - items = @data.take(ROWS_PROCESSING_LIMIT).map { |row| row['#uri'].strip } - - if @import.overwrite? - presence_hash = items.index_with(true) - - @account.bookmarks.find_each do |bookmark| - if presence_hash[bookmark.status.uri] - items.delete(bookmark.status.uri) - else - bookmark.destroy! - end - end - end - - statuses = items.filter_map do |uri| - status = ActivityPub::TagManager.instance.uri_to_resource(uri, Status) - next if status.nil? && ActivityPub::TagManager.instance.local_uri?(uri) - - status || ActivityPub::FetchRemoteStatusService.new.call(uri) - rescue *Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::UnexpectedResponseError - nil - rescue => e - Rails.logger.warn "Unexpected error when importing bookmark: #{e}" - nil - end - - account_ids = statuses.map(&:account_id) - preloaded_relations = @account.relations_map(account_ids, skip_blocking_and_muting: true) - - statuses.keep_if { |status| StatusPolicy.new(@account, status, preloaded_relations).show? } - - statuses.each do |status| - @account.bookmarks.find_or_create_by!(account: @account, status: status) - end - end - - def parse_import_data!(default_headers) - data = CSV.parse(import_data, headers: true) - data = CSV.parse(import_data, headers: default_headers) unless data.headers&.first&.strip&.include?(' ') - @data = data.compact_blank - end - - def import_data - Paperclip.io_adapters.for(@import.data).read.force_encoding(Encoding::UTF_8) - end -end diff --git a/app/views/admin/announcements/previews/show.html.haml b/app/views/admin/announcements/previews/show.html.haml index fdfbf598b5..54d5d45ed6 100644 --- a/app/views/admin/announcements/previews/show.html.haml +++ b/app/views/admin/announcements/previews/show.html.haml @@ -7,6 +7,8 @@ = material_symbol 'chevron_left' = t('admin.announcements.back') +.flash-message.info= t('admin.announcements.preview.disclaimer') + %p.lead = t('admin.announcements.preview.explanation_html', count: @user_count, display_count: number_with_delimiter(@user_count)) diff --git a/app/workers/import/relationship_worker.rb b/app/workers/import/relationship_worker.rb deleted file mode 100644 index 2298b095a7..0000000000 --- a/app/workers/import/relationship_worker.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -# NOTE: This is a deprecated worker, only kept to not break ongoing imports -# on upgrade. See `Import::RowWorker` for its replacement. - -class Import::RelationshipWorker - include Sidekiq::Worker - - sidekiq_options queue: 'pull', retry: 8, dead: false - - def perform(account_id, target_account_uri, relationship, options) - from_account = Account.find(account_id) - target_domain = domain(target_account_uri) - target_account = stoplight_wrapper(target_domain).run { ResolveAccountService.new.call(target_account_uri, { check_delivery_availability: true }) } - options.symbolize_keys! - - return if target_account.nil? - - case relationship - when 'follow' - begin - FollowService.new.call(from_account, target_account, **options) - rescue ActiveRecord::RecordInvalid - raise if FollowLimitValidator.limit_for_account(from_account) < from_account.following_count - end - when 'unfollow' - UnfollowService.new.call(from_account, target_account) - when 'block' - BlockService.new.call(from_account, target_account) - when 'unblock' - UnblockService.new.call(from_account, target_account) - when 'mute' - MuteService.new.call(from_account, target_account, **options) - when 'unmute' - UnmuteService.new.call(from_account, target_account) - end - rescue ActiveRecord::RecordNotFound - true - end - - def domain(uri) - domain = uri.is_a?(Account) ? uri.domain : uri.split('@')[1] - TagManager.instance.local_domain?(domain) ? nil : TagManager.instance.normalize_domain(domain) - end - - def stoplight_wrapper(domain) - if domain.present? - Stoplight("source:#{domain}") - .with_fallback { nil } - .with_threshold(1) - .with_cool_off_time(5.minutes.seconds) - .with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) } - else - Stoplight('domain-blank') - end - end -end diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb deleted file mode 100644 index b6afb972a9..0000000000 --- a/app/workers/import_worker.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -# NOTE: This is a deprecated worker, only kept to not break ongoing imports -# on upgrade. See `ImportWorker` for its replacement. - -class ImportWorker - include Sidekiq::Worker - - sidekiq_options queue: 'pull', retry: false - - def perform(import_id) - import = Import.find(import_id) - ImportService.new.call(import) - ensure - import&.destroy - end -end diff --git a/app/workers/unfilter_notifications_worker.rb b/app/workers/unfilter_notifications_worker.rb index 53a35ce12c..cb8a46b8f4 100644 --- a/app/workers/unfilter_notifications_worker.rb +++ b/app/workers/unfilter_notifications_worker.rb @@ -4,25 +4,14 @@ class UnfilterNotificationsWorker include Sidekiq::Worker include Redisable - # Earlier versions of the feature passed a `notification_request` ID - # If `to_account_id` is passed, the first argument is an account ID - # TODO for after 4.3.0: drop the single-argument case - def perform(notification_request_or_account_id, from_account_id = nil) - if from_account_id.present? - @notification_request = nil - @from_account = Account.find_by(id: from_account_id) - @recipient = Account.find_by(id: notification_request_or_account_id) - else - @notification_request = NotificationRequest.find_by(id: notification_request_or_account_id) - @from_account = @notification_request&.from_account - @recipient = @notification_request&.account - end + def perform(account_id, from_account_id) + @from_account = Account.find_by(id: from_account_id) + @recipient = Account.find_by(id: account_id) return if @from_account.nil? || @recipient.nil? push_to_conversations! unfilter_notifications! - remove_request! decrement_worker_count! end @@ -36,10 +25,6 @@ class UnfilterNotificationsWorker filtered_notifications.in_batches.update_all(filtered: false) end - def remove_request! - @notification_request&.destroy! - end - def filtered_notifications Notification.where(account: @recipient, from_account: @from_account, filtered: true) end diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb index ed16d50a76..6d908fa477 100644 --- a/config/initializers/paperclip.rb +++ b/config/initializers/paperclip.rb @@ -169,7 +169,7 @@ else end Rails.application.reloader.to_prepare do - Paperclip.options[:content_type_mappings] = { csv: Import::FILE_TYPES } + Paperclip.options[:content_type_mappings] = { csv: %w(text/plain text/csv application/csv) } end # In some places in the code, we rescue this exception, but we don't always diff --git a/config/locales/activerecord.es-MX.yml b/config/locales/activerecord.es-MX.yml index c3b0562c32..02384f1c71 100644 --- a/config/locales/activerecord.es-MX.yml +++ b/config/locales/activerecord.es-MX.yml @@ -56,7 +56,7 @@ es-MX: user: attributes: date_of_birth: - below_limit: está por debajo de la edad mínima + below_limit: está por debajo del límite de edad email: blocked: utiliza un proveedor de correo no autorizado unreachable: no parece existir diff --git a/config/locales/activerecord.ga.yml b/config/locales/activerecord.ga.yml index e5b07470ae..853c705663 100644 --- a/config/locales/activerecord.ga.yml +++ b/config/locales/activerecord.ga.yml @@ -55,6 +55,8 @@ ga: too_soon: róluath, caithfidh sé bheith níos déanaí ná %{date} user: attributes: + date_of_birth: + below_limit: faoi ​​bhun na teorann aoise email: blocked: úsáideann soláthraí ríomhphoist dícheadaithe unreachable: ní cosúil go bhfuil sé ann diff --git a/config/locales/cy.yml b/config/locales/cy.yml index 9349176842..edd89fedc6 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -511,6 +511,36 @@ cy: new: title: Mewnforio blociau parth no_file: Heb ddewis ffeil + fasp: + debug: + callbacks: + created_at: Crëwyd am + delete: Dileu + ip: Cyfeiriad IP + request_body: Gofyn am y corff + title: Adalwasau Dadfygio + providers: + active: Gweithredol + base_url: URL sylfaen + callback: Adalwad + delete: Dileu + edit: Golygu Darparwr + finish_registration: Gorffen cofrestru + name: Enw + providers: Darparwyr + public_key_fingerprint: Ôl bys allwedd cyhoeddus + registration_requested: Cais am gofrestru + registrations: + confirm: Cadarnhau + description: Rydych wedi derbyn cofrestriad gan FASP. Gwrthodwch hyn os nad chi ofynnodd amdano. Os taw chi gychwynnodd hyn, cymharwch yr enw ac allwedd yr ôl bys yn ofalus cyn cadarnhau'r cofrestriad. + reject: Gwrthod + title: Cadarnhau Cofrestriad FASP + save: Cadw + select_capabilities: Dewis Galluoedd + sign_in: Mewngofnodi + status: Statws + title: Fediverse Auxiliary Service Providers + title: FASP follow_recommendations: description_html: "Mae dilyn yr argymhellion yn helpu i ddefnyddwyr newydd ddod o hyd i gynnwys diddorol yn gyflym. Pan nad yw defnyddiwr wedi rhyngweithio digon ag eraill i ffurfio argymhellion dilyn personol, argymhellir y cyfrifon hyn yn lle hynny. Cânt eu hailgyfrifo'n ddyddiol o gymysgedd o gyfrifon gyda'r ymgysylltiadau diweddar uchaf a'r cyfrif dilynwyr lleol uchaf ar gyfer iaith benodol." language: Ar gyfer iaith diff --git a/config/locales/doorkeeper.kab.yml b/config/locales/doorkeeper.kab.yml index fa9e1c540a..c65bada409 100644 --- a/config/locales/doorkeeper.kab.yml +++ b/config/locales/doorkeeper.kab.yml @@ -111,9 +111,9 @@ kab: lists: Tibdarin media: Imeddayen n umidya mutes: Yeggugem - notifications: Alɣuten + notifications: Ilɣa profile: Amaɣnu-k Mastodon - push: Alɣuten yettudemmren + push: Ilɣa yettudemmren reports: Ineqqisen search: Nadi statuses: Tisuffaɣ @@ -127,7 +127,7 @@ kab: admin:read: ad iɣeṛ akk isefka ɣef uqeddac admin:write: ad iẓreg akk isefka ɣef uqeddac follow: ad ibeddel assaɣen n umiḍan - push: ad iṭṭef-d alɣuten-ik·im yettwademren + push: ad iṭṭef-d ilɣa-k·m yettwademren read: ad iɣeṛ akk isefka n umiḍan-ik·im read:accounts: ẓer isallen n yimiḍanen read:blocks: ẓer imiḍanen i tesḥebseḍ @@ -136,7 +136,7 @@ kab: read:follows: ẓer imeḍfaṛen-ik read:lists: ẓer tibdarin-ik·im read:mutes: ẓer wid i tesgugmeḍ - read:notifications: ad iẓer alɣuten-inek·inem + read:notifications: ad iẓer ilɣa-inek·inem read:reports: ẓer ineqqisen-ik·im read:search: anadi deg umkan-ik·im read:statuses: ad iẓer meṛṛa tisuffaɣ @@ -148,4 +148,4 @@ kab: write:follows: ḍfeṛ imdanen write:lists: ad yesnulfu tibdarin write:media: ad yessali ifuyla n umidya - write:notifications: sfeḍ alɣuten-ik·im + write:notifications: sfeḍ ilɣa-k·m diff --git a/config/locales/el.yml b/config/locales/el.yml index 2c6d91298e..d2f4fbba01 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -318,6 +318,9 @@ el: new: create: Δημιουργία ανακοίνωσης title: Νέα ανακοίνωση + preview: + explanation_html: 'Το email θα αποσταλεί σε %{display_count} χρήστες. Το ακόλουθο κείμενο θα συμπεριληφθεί στο e-mail:' + title: Προεπισκόπηση ειδοποίησης ανακοίνωσης publish: Δημοσίευση published_msg: Επιτυχής δημοσίευση ανακοίνωσης! scheduled_for: Προγραμματισμένη για %{time} @@ -476,6 +479,34 @@ el: new: title: Εισαγωγή αποκλεισμένων τομέων no_file: Δεν επιλέχθηκε αρχείο + fasp: + debug: + callbacks: + created_at: Δημιουργήθηκε στις + delete: Διαγραφή + ip: Διεύθυνση IP + request_body: Σώμα αιτήματος + title: Κλήσεις Αποσφαλμάτωσης + providers: + active: Ενεργό + base_url: URL βάσης + callback: Επανάκληση + delete: Διαγραφή + edit: Επεξεργασία Παρόχου + finish_registration: Ολοκλήρωση εγγραφής + name: Όνομα + providers: Πάροχοι + public_key_fingerprint: Αποτύπωμα δημόσιου κλειδιού + registration_requested: Η εγγραφή ζητήθηκε + registrations: + confirm: Επιβεβαίωση + description: Έλαβες μια εγγραφή από ένα FASP. Απέρριψέ την αν δεν την άρχισες εσύ. Αν το άρχισες εσύ, σύγκρινε προσεκτικά το όνομα και το κλειδί αποτύπωμα πριν από την επιβεβαίωση της εγγραφής. + reject: Απόρριψη + title: Επιβεβαίωση Εγγραφής FASP + save: Αποθήκευση + select_capabilities: Επέλεξε Δυνατότητες + sign_in: Σύνδεση + status: Κατάσταση follow_recommendations: description_html: "Ακολουθώντας συστάσεις βοηθάει τους νέους χρήστες να βρουν γρήγορα ενδιαφέρον περιεχόμενο. Όταν ένας χρήστης δεν έχει αλληλεπιδράσει με άλλους αρκετά για να διαμορφώσει εξατομικευμένες συστάσεις, συνιστώνται αυτοί οι λογαριασμοί. Υπολογίζονται εκ νέου σε καθημερινή βάση από ένα σύνολο λογαριασμών με τις υψηλότερες πρόσφατες αλληλεπιδράσεις και μεγαλύτερο αριθμό τοπικών ακόλουθων για μια δεδομένη γλώσσα." language: Για τη γλώσσα diff --git a/config/locales/en.yml b/config/locales/en.yml index fe8786cebe..dca6a2d9f8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -335,6 +335,7 @@ en: create: Create announcement title: New announcement preview: + disclaimer: As users cannot opt out of them, email notifications should be limited to important announcements such as personal data breach or server closure notifications. explanation_html: 'The email will be sent to %{display_count} users. The following text will be included in the e-mail:' title: Preview announcement notification publish: Publish diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index 5f92f2c85d..a9192740fe 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -500,7 +500,7 @@ es-MX: registration_requested: Se solicitó el registro registrations: confirm: Confirmar - description: Has recibido un registro de un FASP. Recházalo si no lo iniciaste tú. Si lo iniciaste tú, compara cuidadosamente el nombre y la huella de la clave antes de confirmar el registro. + description: Has recibido un registro de un FASP. Recházalo si no lo iniciaste tú. Si lo comenzaste tú, compara cuidadosamente el nombre y la huella de la clave antes de confirmar el registro. reject: Rechazar title: Confirmar registro FASP save: Guardar diff --git a/config/locales/fa.yml b/config/locales/fa.yml index b5dce6dabf..f1c74829c9 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -479,6 +479,22 @@ fa: new: title: درون‌ریزی انسدادهای دامنه no_file: هیچ پرونده‌ای گزیده نشده + fasp: + debug: + callbacks: + delete: حذف + providers: + active: فعال + delete: حذف + finish_registration: تکمیل ثبت‌نام + name: نام + providers: ارائه دهندگان + registrations: + confirm: تایید + reject: رد کردن + save: ذخیره + sign_in: ورود + status: وضعیت follow_recommendations: description_html: "پیشنهادات پیگیری به کاربران جدید کک می‌کند تا سریع‌تر محتوای جالب را پیدا کنند. زمانی که کاربری هنوز به اندازه کافی با دیگران تعامل نداشته است تا پیشنهادات پیگیری شخصی‌سازی‌شده دریافت کند، این حساب‌ها را به جای آن فهرست مشاهده خواهد کرد. این حساب‌ها به صورت روزانه و در ترکیب با بیشتری تعاملات و بالاترین دنبال‌کنندگان محلی برای یک زبان مشخص بازمحاسبه می‌شوند." language: برای زبان diff --git a/config/locales/fi.yml b/config/locales/fi.yml index e999748ad9..fcb180518f 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -457,7 +457,7 @@ fi: domain: Verkkotunnus new: create: Lisää verkkotunnus - resolve: Selvitä verkkotunnus + resolve: Resolvoi verkkotunnus title: Estä uusi sähköpostiverkkotunnus no_email_domain_block_selected: Sähköpostiverkkotunnusten estoja ei muutettu, koska yhtäkään ei ollut valittuna not_permitted: Ei sallittu diff --git a/config/locales/ga.yml b/config/locales/ga.yml index d552c72de7..58d02fc6a9 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -503,6 +503,36 @@ ga: new: title: Iompórtáil bloic fearainn no_file: Níor roghnaíodh aon chomhad + fasp: + debug: + callbacks: + created_at: Cruthaithe ag + delete: Scrios + ip: Seoladh IP + request_body: Comhlacht iarratais + title: Glaonna Dífhabhtaithe + providers: + active: Gníomhach + base_url: Bun-URL + callback: Glao ar ais + delete: Scrios + edit: Cuir Soláthraí in Eagar + finish_registration: Críochnaigh clárú + name: Ainm + providers: Soláthraithe + public_key_fingerprint: Méarloirg eochair phoiblí + registration_requested: Clárú iarrtha + registrations: + confirm: Deimhnigh + description: Fuair ​​tú clárú ó FASP. Diúltaigh é murar chuir tú tús leis seo. Má thionscain tú é seo, déan comparáid chúramach idir an t-ainm agus an eochair-mhéarlorg sula ndearbhaítear an clárúchán. + reject: Diúltaigh + title: Deimhnigh Clárú FASP + save: Sábháil + select_capabilities: Roghnaigh Cumais + sign_in: Sínigh Isteach + status: Stádas + title: Soláthraithe Seirbhíse Cúnta Fediverse + title: FASP follow_recommendations: description_html: "Lean na moltaí cabhraíonn sé le húsáideoirí nua ábhar suimiúil a aimsiú go tapa. Nuair nach mbíonn go leor idirghníomhaithe ag úsáideoir le daoine eile chun moltaí pearsantaithe a leanúint, moltar na cuntais seo ina ionad sin. Déantar iad a athríomh ar bhonn laethúil ó mheascán de chuntais a bhfuil na rannpháirtíochtaí is airde acu le déanaí agus na háirimh áitiúla is airde leanúna do theanga ar leith." language: Don teanga diff --git a/config/locales/he.yml b/config/locales/he.yml index f9b2970fd0..6d575bc564 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -232,7 +232,7 @@ he: silence_account: הגבלת חשבון suspend_account: השעיית חשבון unassigned_report: ביטול הקצאת דו"ח - unblock_email_account: ביטול חסימת כתובת דוא"ל + unblock_email_account: הסרת חסימת כתובת דוא"ל unsensitive_account: ביטול Force-Sensitive לחשבון unsilence_account: ביטול השתקת חשבון unsuspend_account: ביטול השעיית חשבון @@ -263,11 +263,11 @@ he: create_user_role_html: "%{name} יצר את התפקיד של %{target}" demote_user_html: "%{name} הוריד/ה בדרגה את המשתמש %{target}" destroy_announcement_html: "%{name} מחק/ה את ההכרזה %{target}" - destroy_canonical_email_block_html: "%{name} הסיר/ה חסימה מדואל %{target}" + destroy_canonical_email_block_html: "%{name} הסירו חסימה מדואל %{target}" destroy_custom_emoji_html: "%{name} מחק אמוג'י של %{target}" destroy_domain_allow_html: "%{name} לא התיר/ה פדרציה עם הדומיין %{target}" - destroy_domain_block_html: "%{name} הסיר/ה חסימה מהדומיין %{target}" - destroy_email_domain_block_html: '%{name} הסיר/ה חסימה מדומיין הדוא"ל %{target}' + destroy_domain_block_html: החסימה על מתחם %{target} הוסרה ע"י %{name} + destroy_email_domain_block_html: הוסרה חסימת מתחם דוא"ל %{target} בידי %{name} destroy_instance_html: "%{name} טיהר/ה את הדומיין %{target}" destroy_ip_block_html: "%{name} מחק/ה את הכלל עבור IP %{target}" destroy_relay_html: "%{name} מחקו את הממסר %{target}" @@ -495,6 +495,36 @@ he: new: title: יבוא רשימת שרתים חסומים no_file: לא נבחר קובץ + fasp: + debug: + callbacks: + created_at: תאריך יצירה + delete: מחיקה + ip: כתובת IP + request_body: גוף הבקשה + title: ניפוי תקלות בקריאות חוזרות + providers: + active: פעילים + base_url: קישור בסיס + callback: קריאה חוזרת + delete: מחיקה + edit: עריכת ספק + finish_registration: סיום הרשמה + name: שם + providers: ספקים + public_key_fingerprint: טביעת האצבע של המפתח הציבורי + registration_requested: נדרשת הרשמה + registrations: + confirm: אישור + description: קיבלת הרשמה דרך FASP. יש לדחות אותה אם לא ביקשת את ההרשמה הזו מיוזמתך. אם זו בקשה מיוזמתך, יש להשוות בהקפדה אם השם וטביעת האצבע של המפתח הציבורי תואמים לפני אישור הרישום. + reject: דחיה + title: אישור הרשמת FASP + save: שמירה + select_capabilities: בחירת יכולות + sign_in: כניסה + status: מצב + title: ספקי משנה לפדיוורס + title: פרוטוקול FASP follow_recommendations: description_html: "עקבו אחר ההמלצות על מנת לעזור למשתמשים חדשים למצוא תוכן מעניין. במידה ומשתמש לא תקשר מספיק עם משתמשים אחרים כדי ליצור המלצות מעקב, חשבונות אלה יומלצו במקום. הם מחושבים מחדש על בסיסי יומיומי מתערובת של החשבונות הפעילים ביותר עם החשבונות הנעקבים ביותר עבור שפה נתונה." language: עבור שפה diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 1f6fc8f698..85a88be7bb 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -615,7 +615,7 @@ kab: filters: contexts: account: Imeɣna - notifications: Alɣuten + notifications: Ilɣa thread: Idiwenniyen edit: add_keyword: Rnu awal tasarut @@ -810,7 +810,7 @@ kab: import: Kter import_and_export: Taktert d usifeḍ migrate: Tunigin n umiḍan - notifications: Alɣuten s imayl + notifications: Ilɣa s imayl preferences: Imenyafen profile: Ameɣnu relationships: Imeḍfaṛen akked wid i teṭṭafaṛeḍ diff --git a/config/locales/lad.yml b/config/locales/lad.yml index 86dbc668c0..c1364fbb01 100644 --- a/config/locales/lad.yml +++ b/config/locales/lad.yml @@ -462,6 +462,18 @@ lad: new: title: Importa blokos de domeno no_file: Dinguna dosya tiene sido eskojida + fasp: + debug: + callbacks: + delete: Efasa + providers: + name: Nombre + registrations: + confirm: Konfirma + reject: Refuza + save: Guadra + sign_in: Konektate + status: Estado follow_recommendations: description_html: "Las rekomendasyones de kuentos ayudan a los muevos utilizadores a topar presto kontenido enteresante. Kuando un utilizador no tiene enteraktuado kon otros lo sufisiente komo para djenerar rekomendasyones personalizadas de kuentos a las ke segir, en sus lugar se le rekomiendan estes kuentos. Se rekalkulan diariamente a partir de una mikstura de kuentos kon el mayor numero de enteraksyones rezientes i kon el mayor numero de suivantes lokales kon una lingua determinada." language: Para la lingua diff --git a/config/locales/lv.yml b/config/locales/lv.yml index af65e9e02b..2bb5abf2de 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -19,10 +19,10 @@ lv: pin_errors: following: Tev ir jāseko personai, kuru vēlies atbalstīt posts: - one: Ziņa - other: Ziņas - zero: Ziņu - posts_tab_heading: Ziņas + one: Ieraksts + other: Ieraksti + zero: Ierakstu + posts_tab_heading: Ieraksti self_follow_error: Nav ļauts sekot savam kontam admin: account_actions: @@ -120,7 +120,7 @@ lv: public: Publisks push_subscription_expires: PuSH abonements beidzas redownload: Atsvaidzināt profilu - redownloaded_msg: "%{username} profils sekmīgi atsvaidzināts no izcelsmes" + redownloaded_msg: "%{username} profils sekmīgi atsvaidzināts no pirmavota" reject: Noraidīt rejected_msg: "%{username} reģistrēšanās pieteikums sekmīgi noraidīts" remote_suspension_irreversible: Šī konta dati ir neatgriezeniski dzēsti. @@ -152,7 +152,7 @@ lv: targeted_reports: Ziņojuši citi silence: Ierobežot silenced: Ierobežots - statuses: Ziņas + statuses: Ieraksti strikes: Iepriekšējie streiki subscribe: Abonēt suspend: Apturēt @@ -176,7 +176,7 @@ lv: whitelisted: Atļauts federācijai action_logs: action_types: - approve_appeal: Apstiprināt Apelāciju + approve_appeal: Apstiprināt pārsūdzību approve_user: Apstiprināt lietotāju assigned_to_self_report: Piešķirt Pārskatu change_email_user: Mainīt lietotāja e-pasta adresi @@ -218,11 +218,11 @@ lv: memorialize_account: Saglabāt Kontu Piemiņai promote_user: Izceltt Lietotāju publish_terms_of_service: Publicēt pakalpojuma izmantošanas noteikumus - reject_appeal: Noraidīt Apelāciju + reject_appeal: Noraidīt pārsūdzību reject_user: Noraidīt lietotāju remove_avatar_user: Noņemt profila attēlu reopen_report: Atkārtoti Atvērt Ziņojumu - resend_user: Atkārtoti nosūtīt Apstiprinājuma Pastu + resend_user: Atkārtoti nosūtīt apstiprinājuma e-pasta ziņojumu reset_password_user: Atiestatīt Paroli resolve_report: Atrisināt Ziņojumu sensitive_account: Uzspiesti atzimēt kontu kā jūtīgu @@ -241,7 +241,7 @@ lv: update_status: Atjaunināt ziņu update_user_role: Atjaunināt lomu actions: - approve_appeal_html: "%{name} apstiprināja satura pārraudzības lēmuma iebildumu no %{target}" + approve_appeal_html: "%{name} apstiprināja satura pārraudzības lēmuma pārsūdzību no %{target}" approve_user_html: "%{name} apstiprināja reģistrēšanos no %{target}" assigned_to_self_report_html: "%{name} piešķīra pārskatu %{target} sev" change_email_user_html: "%{name} nomainīja lietotāja %{target} e-pasta adresi" @@ -259,9 +259,11 @@ lv: create_user_role_html: "%{name} nomainīja %{target} lomu" demote_user_html: "%{name} pazemināja lietotāju %{target}" destroy_announcement_html: "%{name} izdzēsa paziņojumu %{target}" + destroy_canonical_email_block_html: "%{name} atcēla e-pasta adreses liegumu ar jaucējvērtību %{target}" destroy_custom_emoji_html: "%{name} izdzēsa emocijzīmi %{target}" destroy_domain_allow_html: "%{name} neatļāva federāciju ar domēnu %{target}" destroy_domain_block_html: "%{name} atbloķēja domēnu %{target}" + destroy_email_domain_block_html: "%{name} atcēla e-pasta domēna %{target} liegumu" destroy_instance_html: "%{name} attīrija domēnu %{target}" destroy_ip_block_html: "%{name} izdzēsa nosacījumu priekš IP %{target}" destroy_status_html: "%{name} noņēma ziņu %{target}" @@ -275,7 +277,7 @@ lv: memorialize_account_html: "%{name} pārvērta %{target} kontu par atmiņas lapu" promote_user_html: "%{name} paaugstināja lietotāju %{target}" publish_terms_of_service_html: "%{name} padarīja pieejamus pakalpojuma izmantošanas noteikumu atjauninājumus" - reject_appeal_html: "%{name} noraidīja satura pārraudzības lēmuma iebildumu no %{target}" + reject_appeal_html: "%{name} noraidīja satura pārraudzības lēmuma pārsūdzību no %{target}" reject_user_html: "%{name} noraidīja reģistrēšanos no %{target}" remove_avatar_user_html: "%{name} noņēma %{target} profila attēlu" reopen_report_html: "%{name} atkārtoti atvēra ziņojumu %{target}" @@ -304,6 +306,7 @@ lv: title: Audita žurnāls unavailable_instance: "(domēna vārds nav pieejams)" announcements: + back: Atgriezties pie paziņojumiem destroyed_msg: Paziņojums sekmīgi izdzēsts. edit: title: Labot paziņojumu @@ -312,6 +315,9 @@ lv: new: create: Izveidot paziņojumu title: Jauns paziņojums + preview: + explanation_html: 'E-pasta ziņojums tiks nosūtīts %{display_count} lietotājiem. Šis teksts tiks iekļauts e-pasta ziņojumā:' + title: Priekšskatīt paziņojumu publish: Publicēt published_msg: Paziņojums sekmīgi publicēts. scheduled_for: Plānots uz %{time} @@ -404,7 +410,7 @@ lv: permanent_action: Apturēšanas atsaukšana neatjaunos nekādus datus vai attiecības. preamble_html: Tu gatavojies apturēt domēna %{domain} un tā apakšdomēnu darbību. remove_all_data: Tādējādi no tava servera tiks noņemts viss šī domēna kontu saturs, multivide un profila dati. - stop_communication: Jūsu serveris pārtrauks sazināties ar šiem serveriem. + stop_communication: Tavs serveris pārtrauks sazināties ar šiem serveriem. title: Apstiprināt domēna %{domain} bloķēšanu undo_relationships: Tādējādi tiks atsauktas jebkuras sekošanas attiecības starp šo un tavu serveru kontiem. created_msg: Domēna bloķēšana tagad tiek apstrādāta @@ -455,7 +461,9 @@ lv: create: Pievienot domēnu resolve: Atrisināt domēnu title: Liegt jaunu e-pasta domēnu + no_email_domain_block_selected: Neviens e-pasta domēna bloks netika mainīts, jo neviens netika atlasīts not_permitted: Nav atļauta + resolved_dns_records_hint_html: Domēna vārds saistās ar zemāk norādītajiem MX domēniem, kuri beigās ir atbildīgi par e-pasta pieņemšana. MX domēna liegšana liegs reģistrēšanos no jebkuras e-pasta adreses, kas izmanto to pašu MX domēnu, pat ja redzamais domēna vārds ir atšķirīgs. Jāuzmanās, lai neliegtu galvenos e-pasta pakalpojuma sniedzējus. resolved_through_html: Atrisināts, izmantojot %{domain} title: Bloķētie e-pasta domēni export_domain_allows: @@ -473,6 +481,35 @@ lv: new: title: Importēt bloķētos domēnus no_file: Nav atlasīts neviens fails + fasp: + debug: + callbacks: + created_at: Izveidots + delete: Izdzēst + ip: IP adrese + request_body: Pieprasījuma saturs + title: Atkļūdošanas atpakaļsaukumi + providers: + base_url: Pamata URL + callback: Atpakaļsaukums + delete: Izdzēst + edit: Labot nodrošinātāju + finish_registration: Pabeigt reģistrēšanos + name: Nosaukums + providers: Nodrošinātāji + public_key_fingerprint: Publiskās atslēgas pirkstu nospiedums + registration_requested: Pieprasīta reģistrēšanās + registrations: + confirm: Apstiprināt + description: Tu saņēmi reģistrēšanos no FĀPS. Tā ir jānoraida, ja to neveici. Ja veici, rūpīgi jāsalīdzina nosaukums un atslēgas pirkstu nospiedums, pirms reģistrēšanās apstiprināšanas. + reject: Noraidīt + title: Apstiprināt FĀPS reģistrēšanos + save: Saglabāt + select_capabilities: Atlasīt spējas + sign_in: Pieteikties + status: Stāvoklis + title: Fediverse ārējie pakalpojumu sniedzēji + title: FĀPS follow_recommendations: description_html: "Sekošanas ieteikumi palīdz jauniem lietotājiem ātri arast saistošu saturu. Kad lietotājs nav pietiekami mijiedarbojies ar citiem, lai veidotos pielāgoti sekošanas iteikumi, tiek ieteikti šie konti. Tie tiek pārskaitļoti ik dienas, izmantojot kontu, kuriem ir augstākās nesenās iesaistīšanās un lielākais vietējo sekotāju skaits norādītajā valodā." language: Valodai @@ -523,7 +560,7 @@ lv: instance_languages_dimension: Populārākās valodas instance_media_attachments_measure: saglabātie multivides pielikumi instance_reports_measure: ziņojumi par viņiem - instance_statuses_measure: saglabātās ziņas + instance_statuses_measure: saglabātie ieraksti delivery: all: Visas clear: Notīrīt piegādes kļūdas @@ -588,7 +625,7 @@ lv: disable: Atspējot disabled: Atspējots enable: Iespējot - enable_hint: Kad tas būs iespējots, tavs serveris abonēs visas publiskās ziņas no šī releja un sāks tam sūtīt šī servera publiskās ziņas. + enable_hint: Tiklīdz iespējots, serveris abonēs visus šī releja publiskos ierakstus un sāks tam sūtīt šī iservera publiskos ierakstus. enabled: Iespējots inbox_url: Releja URL pending: Gaida apstiprinājumu no releja @@ -619,6 +656,9 @@ lv: actions_description_remote_html: Izlem, kādas darbības jāveic, lai atrisinātu šo ziņojumu. Tas ietekmēs tikai to, kā tavs serveris sazinās ar šo attālo kontu un apstrādā tā saturu. actions_no_posts: Šim ziņojumam nav saistītu ierakstu, kurus izdzēst add_to_report: Pievienot varāk paziņošanai + already_suspended_badges: + local: Jau ir apturēts šajā serverī + remote: Jau ir apturēts viņu serverī are_you_sure: Vai esi pārliecināts? assign_to_self: Piešķirt man assigned: Piešķirtais satura pārraudzītājs @@ -669,7 +709,7 @@ lv: silence_html: 'Jūs gatavojaties ierobežot @%{acct} kontu. Tas:' suspend_html: 'Jūs gatavojaties apturēt @%{acct} kontu. Tas:' actions: - delete_html: Noņemt aizskarošās ziņas + delete_html: Noņemt aizskarošos ierakstus mark_as_sensitive_html: Atzīmēt aizskarošo ierakstu informācijas nesējus kā jūtīgus silence_html: Ievērojami ierobežo @%{acct} sasniedzamību, padarot viņa profilu un saturu redzamu tikai cilvēkiem, kas jau seko tam vai pašrocīgi uzmeklē profilu suspend_html: Apturēt @%{acct}, padarot viņu profilu un saturu nepieejamu un neiespējamu mijiedarbību ar @@ -678,8 +718,9 @@ lv: delete_data_html: Dzēsiet lietotāja @%{acct} profilu un saturu pēc 30 dienām, ja vien to darbība pa šo laiku netiks atcelta preview_preamble_html: "@%{acct} saņems brīdinājumu ar šādu saturu:" record_strike_html: Ierakstiet brīdinājumu pret @%{acct}, lai palīdzētu jums izvērst turpmākus pārkāpumus no šī konta + send_email_html: Nosūtīt @%{acct} brīdinājuma e-pasta ziņojumu warning_placeholder: Izvēles papildu pamatojums satura pārraudzības darbībai. - target_origin: Ziņotā konta izcelsme + target_origin: Konta, par kuru ziņots, izcelsme title: Ziņojumi unassign: Atsaukt unknown_action_msg: 'Nezināms konts: %{action}' @@ -719,6 +760,7 @@ lv: manage_appeals: Pārvaldīt Pārsūdzības manage_appeals_description: Ļauj lietotājiem pārskatīt iebildumus pret satura pārraudzības darbībām manage_blocks: Pārvaldīt Bloķus + manage_blocks_description: Ļauj lietotājiem liegt e-pasta pakalpojumu sniedzējus un IP adreses manage_custom_emojis: Pārvaldīt Pielāgotās Emocijzīmes manage_custom_emojis_description: Ļauj lietotājiem pārvaldīt pielāgotās emocijzīmes serverī manage_federation: Pārvaldīt Federāciju @@ -736,6 +778,7 @@ lv: manage_taxonomies: Pārvaldīt Taksonomijas manage_taxonomies_description: Ļauj lietotājiem pārskatīt aktuālāko saturu un atjaunināt tēmturu iestatījumus manage_user_access: Pārvaldīt Lietotāju Piekļuves + manage_user_access_description: Ļauj lietotājiem atspējot citu lietotāju divupakāpju autentificēšanos, mainīt savu e-pasta adresi un atiestatīt savu paroli manage_users: Pārvaldīt Lietotājus manage_users_description: Ļauj lietotājiem skatīt citu lietotāju informāciju un veikt pret viņiem satura pārraudzības darbības manage_webhooks: Pārvaldīt Tīmekļa Aizķeres @@ -810,6 +853,7 @@ lv: destroyed_msg: Vietnes augšupielāde sekmīgi izdzēsta. software_updates: critical_update: Kritiski - lūdzu, ātri atjaunini + description: Ir ieteicams uzturēt savu Mastodon serveri atjauninātu, lai gūtu labumu no jaunākajiem labojumiem un iespējām. Vēl jo vairāk, dažreiz ir ļoti svarīgi savlaicīgi atjaunināt Mastodon, lai izvairītos no drošības nepilnībām. Šo iemeslu dēļ Mastodon pārbauda atjauninājumus ik pēc 30 minūtēm, un paziņos atbilstoši e-pasta paziņojumu iestatījumiem. documentation_link: Uzzināt vairāk release_notes: Laidiena piezīmes title: Pieejamie atjauninājumi @@ -838,12 +882,12 @@ lv: title: Multivide metadata: Metadati no_history: Šis ieraksts nav bijis labots - no_status_selected: Neviena ziņa netika mainīta, jo neviena netika atlasīta + no_status_selected: Neviens ieraksts netika mainīts, jo nekas netika atlasīts open: Atvērt ziņu - original_status: Oriģinālā ziņa + original_status: Sākotnējais ieraksts reblogs: Reblogi replied_to_html: Atbildēja %{acct_link} - status_changed: Ziņa mainīta + status_changed: Ieraksts izmainīts status_title: Publicēja @%{name} title: Konta ieraksti - @%{name} trending: Aktuāli @@ -906,11 +950,13 @@ lv: message_html: "Tava objektu krātuve ir nepareizi konfigurēta. Tavu lietotāju privātums ir apdraudēts." tags: moderation: + not_trendable: Nav izplatīts not_usable: Nav izmantojams pending_review: Gaida pārskatīšanu review_requested: Pieprasīta pārskatīšana reviewed: Pārskatīts title: Stāvoklis + trendable: Izplatīts unreviewed: Nepārskatīts usable: Izmantojams name: Nosaukums @@ -1269,7 +1315,7 @@ lv: appeal_approved: Šis brīdinājums tika sekmīgi pārsūdzēts un vairs nav spēkā appeal_rejected: Apelācija ir noraidīta appeal_submitted_at: Apelācija iesniegta - appealed_msg: Jūsu apelācija ir iesniegta. Ja tā tiks apstiprināta, jums tiks paziņots. + appealed_msg: Tava pārsūdzība ir iesniegta. Ja tā tiks apstiprināta, Tev tiks paziņots. appeals: submit: Iesniegt apelāciju approve_appeal: Apstiprināt apelāciju @@ -1289,9 +1335,9 @@ lv: sensitive: Konta atzīmēšana kā jūtīgu silence: Konta ierobežošana suspend: Konta apturēšana - your_appeal_approved: Jūsu apelācija ir apstiprināta + your_appeal_approved: Tava pārsūdzība tika apstiprināta your_appeal_pending: Jūs esat iesniedzis apelāciju - your_appeal_rejected: Jūsu apelācija ir noraidīta + your_appeal_rejected: Tava pārsūdzība tika noraidīta edit_profile: basic_information: Pamata informācija hint_html: "Pielāgo, ko cilvēki redz Tavā publiskajā profilā un blakus Taviem ierakstiem. Ir lielāka iespējamība, ka citi clivēki sekos Tev un mijiedarbosies ar Tevi, ja Tev ir aizpildīts profils un profila attēls." @@ -1422,6 +1468,56 @@ lv: merge_long: Saglabāt esošos ierakstus un pievienot jaunus overwrite: Pārrakstīt overwrite_long: Nomainīt pašreizējos ierakstus ar jauniem + overwrite_preambles: + blocking_html: + one: Tu gatavojies aizstāt savu lieguma sarakstu ar līdz %{count} kontam no %{filename}. + other: Tu gatavojies aizstāt savu lieguma sarakstu ar līdz %{count} kontiem no %{filename}. + zero: Tu gatavojies aizstāt savu lieguma sarakstu ar līdz %{count} kontiem no %{filename}. + bookmarks_html: + one: Tu gatavojies aizstāt savas grāmatzīmes ar līdz %{count} ierakstam no %{filename}. + other: Tu gatavojies aizstāt savas grāmatzīmes ar līdz %{count} ierakstiem no %{filename}. + zero: Tu gatavojies aizstāt savas grāmatzīmes ar līdz %{count} ierakstiem no %{filename}. + domain_blocking_html: + one: Tu gatavojies aizstāt savu liegto domēnu sarakstu ar līdz %{count} domēnam no %{filename}. + other: Tu gatavojies aizstāt savu liegto domēnu sarakstu ar līdz %{count} domēniem no %{filename}. + zero: Tu gatavojies aizstāt savu liegto domēnu sarakstu ar līdz %{count} domēniem no %{filename}. + following_html: + one: Tu gatavojies sekot līdz %{count} kontam no %{filename} un pārtrauksi sekot citiem. + other: Tu gatavojies sekot līdz %{count} kontiem no %{filename} un pārtrauksi sekot citiem. + zero: Tu gatavojies sekot līdz %{count} kontiem no %{filename} un pārtrauksi sekot citiem. + lists_html: + one: Tu gatavojies aizstāt savus sarakstus ar %{filename} saturu. Līdz %{count} kontam tiks pievienoti jaunajos sarakstos. + other: Tu gatavojies aizstāt savus sarakstus ar %{filename} saturu. Līdz %{count} kontiem tiks pievienoti jaunajos sarakstos. + zero: Tu gatavojies aizstāt savus sarakstus ar %{filename} saturu. Līdz %{count} kontiem tiks pievienoti jaunajos sarakstos. + muting_html: + one: Tu gatavojies aizstāt savu apklusināto kontu sarakstu ar līdz %{count} kontam no %{filename}. + other: Tu gatavojies aizstāt savu apklusināto kontu sarakstu ar līdz %{count} kontiem no %{filename}. + zero: Tu gatavojies aizstāt savu apklusināto kontu sarakstu ar līdz %{count} kontiem no %{filename}. + preambles: + blocking_html: + one: Tu gatavojies liegt līdz %{count} kontam no %{filename}. + other: Tu gatavojies liegt līdz %{count} kontiem no %{filename}. + zero: Tu gatavojies liegt līdz %{count} kontiem no %{filename}. + bookmarks_html: + one: Tu gatavojies pievienot līdz %{count} ierakstam no %{filename} savām grāmatzīmēm. + other: Tu gatavojies pievienot līdz %{count} ierakstiem no %{filename} savām grāmatzīmēm. + zero: Tu gatavojies pievienot līdz %{count} ierakstiem no %{filename} savām grāmatzīmēm. + domain_blocking_html: + one: Tu gatavojies liegt līdz %{count} domēnam no %{filename}. + other: Tu gatavojies liegt līdz %{count} domēniem no %{filename}. + zero: Tu gatavojies liegt līdz %{count} domēniem no %{filename}. + following_html: + one: Tu gatavojies sekot līdz %{count} kontam no %{filename}. + other: Tu gatavojies sekot līdz %{count} kontiem no %{filename}. + zero: Tu gatavojies sekot līdz %{count} kontiem no %{filename}. + lists_html: + one: Tu gatavojies pievienot līdz pat %{count} kontam no %{filename} saviem sarakstiem. Tiks izveidoti jauni saraksti, ja nav saraksta, kurā pievienot. + other: Tu gatavojies pievienot līdz pat %{count} kontiem no %{filename} saviem sarakstiem. Tiks izveidoti jauni saraksti, ja nav saraksta, kurā pievienot. + zero: Tu gatavojies pievienot līdz pat %{count} kontiem no %{filename} saviem sarakstiem. Tiks izveidoti jauni saraksti, ja nav saraksta, kurā pievienot. + muting_html: + one: Tu gatavojies apklusināt līdz pat %{count} kontam no %{filename}. + other: Tu gatavojies apklusināt līdz pat %{count} kontiem no %{filename}. + zero: Tu gatavojies apklusināt līdz pat %{count} kontiem no %{filename}. preface: Tu vari ievietot datus, kurus esi izguvis no cita servera, kā, piemēram, cilvēku sarakstu, kuriem Tu seko vai kurus bloķē. recent_imports: Nesen importēts states: @@ -1493,6 +1589,7 @@ lv: unsubscribe: action: Jā, atcelt abonēšanu complete: Anulēts + confirmation_html: Vai tiešām atteikties no %{type} saņemšanas savā e-pasta adresē %{email} par %{domain} esošo Mastodon? Vienmēr var abonēt no jauna savos e-pasta paziņojumu iestatījumos. emails: notification_emails: favourite: izlases paziņojumu e-pasta ziņojumi @@ -1500,10 +1597,13 @@ lv: follow_request: sekošanas pieprasījumu e-pasta ziņojumi mention: pieminēšanas paziņojumu e-pasta ziņojumi reblog: pastiprinājumu paziņojumu e-pasta ziņojumi + resubscribe_html: Ja abonements tika atcelts kļūdas dēļ, abonēt no jauna var savos e-pasta paziņojumu iestatījumos. + success_html: Tu vairs savā e-pasta adresē %{email} nesaņemsi %{type} par %{domain} esošo Mastodon. title: Atcelt abonēšanu media_attachments: validations: images_and_video: Nevar pievienot videoklipu tādai ziņai, kura jau satur attēlus + not_found: Informācijas nesējs %{ids} nav atrasts vai jau pievienots citam ierakstam not_ready: Nevar pievienot failus, kuru apstrāde nav pabeigta. Pēc brīža mēģini vēlreiz! too_many: Nevar pievienot vairāk kā 4 failus migrations: @@ -1581,6 +1681,7 @@ lv: subject: "%{name} laboja ierakstu" notifications: administration_emails: Pārvaldītāju e-pasta paziņojumi + email_events: E-pasta paziņojumu notikumi email_events_hint: 'Atlasi notikumus, par kuriem vēlies saņemt paziņojumus:' number: human: @@ -1637,6 +1738,9 @@ lv: errors: limit_reached: Sasniegts dažādu reakciju limits unrecognized_emoji: nav atpazīta emocijzīme + redirects: + prompt: Ja uzticies šai saitei, jāklikšķina uz tās, lai turpinātu. + title: Tu atstāj %{instance}. relationships: activity: Konta aktivitāte confirm_follow_selected_followers: Vai tiešām vēlies sekot atlasītajiem sekotājiem? @@ -1748,10 +1852,13 @@ lv: severed_relationships: download: Lejupielādēt (%{count}) event_type: + account_suspension: Konta apturēšana (%{target_name}) + domain_block: Servera apturēšana (%{target_name}) user_domain_block: Jūs bloķējāt %{target_name} lost_followers: Zaudētie sekotāji lost_follows: Zaudētie sekojumi preamble: Tu vari zaudēt sekojamos un sekotājus, kad liedz domēnu vai kad satura pārraudzītāji izlemj apturēt attālu serveri. Kad t as notiek, būs iespējams lejupielādēt sarakstus ar pārtrauktajām saiknēm, kurus tad var izpētīt un, iespējams, ievietot citā serverī. + purged: Informāciju par šo serveri notīrīja Tava servera pārvaldītāji. type: Notikums statuses: attached: @@ -1825,7 +1932,7 @@ lv: '7889238': 3 mēneši min_age_label: Vecuma slieksnis min_favs: Saglabāt ziņas izlsasē vismaz - min_favs_hint: Nedzēš nevienu jūsu ziņu, kas ir saņēmusi vismaz tik daudz izcēlumu. Atstājiet tukšu, lai dzēstu ziņas neatkarīgi no to izcēlumu skaita + min_favs_hint: Neizdzēš nevienu no Taviem ierakstiem, kas ir pievienoti šādā daudzumā izlašu. Atstāt tukšu, lai izdzēstu ierakstus neatkarīgi no tā, cik izlasēs tie ir pievienoti min_reblogs: Saglabāt ziņas izceltas vismaz min_reblogs_hint: Neizdzēš nevienu no tavām ziņām, kas ir izceltas vismaz tik reižu. Atstāj tukšu, lai dzēstu ziņas neatkarīgi no to izcēlumu skaita stream_entries: @@ -1841,6 +1948,7 @@ lv: contrast: Mastodon (Augsts kontrasts) default: Mastodon (Tumšs) mastodon-light: Mastodon (Gaišs) + system: Automātisks (ievēro sistēmas izskatu) time: formats: default: "%b %d, %Y, %H:%M" @@ -1867,18 +1975,32 @@ lv: recovery_instructions_html: Ja kādreiz zaudēsi piekļuvi savam tālrunim, vari izmantot kādu no zemāk norādītajiem atkopes kodiem, lai atgūtu piekļuvi savam kontam. Atkpes kodi jātur drošībā. Piemēram, tos var izdrukāt un glabāt kopā ar citiem svarīgiem dokumentiem. webauthn: Drošības atslēgas user_mailer: + announcement_published: + description: "%{domain} pārvaldītāji veic paziņojumu:" + subject: Pakalpojuma paziņojums + title: "%{domain} pakalpojuma paziņojums" appeal_approved: action: Konta iestatījumi - explanation: Apelācija par brīdinājumu jūsu kontam %{strike_date}, ko iesniedzāt %{appeal_date}, ir apstiprināta. Jūsu konts atkal ir labā stāvoklī. - subject: Jūsu %{date} apelācija ir apstiprināta + explanation: Pārsūdzība par brīdinājumu Tavam kontam %{strike_date}, ko iesniedzi %{appeal_date}, ir apstiprināta. Tavs konts atkal ir labā stāvoklī. + subject: Tava %{date} iesniegtā pārsūdzība tika apstiprināta + subtitle: Tavs konts atkal ir labā stāvoklī. title: Apelācija apstiprināta appeal_rejected: - explanation: Apelācija par brīdinājumu jūsu kontam %{strike_date}, ko iesniedzāt %{appeal_date}, ir noraidīta. - subject: Jūsu %{date} apelācija ir noraidīta + explanation: Pārsūdzība par brīdinājumu Tavam kontam %{strike_date}, ko iesniedzi %{appeal_date}, tika noraidīta. + subject: Tava %{date} iesniegta pārsūdzība tika noraidīta + subtitle: Tava pārsūdzība tika noraidīta. title: Apelācija noraidīta backup_ready: + explanation: Tu pieprasīji pilnu sava Mastodon konta rezerves kopiju. + extra: Tā tagad ir gatava lejupielādei. subject: Tavs arhīvs ir gatavs lejupielādei title: Arhīva līdzņemšana + failed_2fa: + details: 'Šeit ir informācija par pieteikšanās mēģinājumu:' + explanation: Kāds mēģināja pieteikties Tavā kontā, bet norādīja nederīgu otro autentificēšanās soli. + further_actions_html: Ja tas nebiji Tu, mēs iesakām nekavējoties %{action}, jo var būt noticis drošības pārkāpums. + subject: Otrās pakāpes autentificēšanās atteice + title: Neizdevās otrās pakāpes autentificēšanās suspicious_sign_in: change_password: mainīt paroli details: 'Šeit ir pieteikšanās izvērsums:' @@ -1889,6 +2011,7 @@ lv: terms_of_service_changed: agreement: Ar %{domain} izmantošanas tuprināšanu tiek piekrists šiem noteikumiem. Ja ir iebildumi pret atjauninātajiem noteikumiem, savu piekrišanu var atcelt jebkurā laikā ar sava konta izdzēšanu. changelog: 'Šeit īsumā ir aprakstīts, ko šis atjauninājums nozīmē:' + description: 'Šis e-pasta ziņojums tika saņemts, jo mēs veicam dažas izmaiņas savos pakalpojuma izmantošanas noteikumos %{domain}. Šie atjauninājumi stāsies spēkā %{date}. Mēs aicinām pārskatīt pilnus atjauninātos noteikumus šeit:' sign_off: "%{domain} komanda" subject: Mūsu pakalpojuma izmantošanas noteikumu atjauninājumi subtitle: Mainās %{domain} pakalpojuma izmantošanas noteikumi diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 6a8fdda024..a2e142ab55 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -320,6 +320,7 @@ pt-BR: title: Novo anúncio preview: explanation_html: 'Esse e-mail será enviado a %{display_count} usuários. O texto a seguir será incluído ao e-mail:' + title: Visualizar anúncio publish: Publicar published_msg: Anúncio publicado! scheduled_for: Agendado para %{time} @@ -484,19 +485,30 @@ pt-BR: created_at: Criado em delete: Apagar ip: Endereço de IP + request_body: Corpo da solicitação + title: Depurar Callbacks providers: + active: Ativo base_url: URL Base + callback: Callback delete: Apagar + edit: Editar provedor finish_registration: Finalizar o cadastro name: Nome + providers: Provedores public_key_fingerprint: Impressão digital de chave pública registration_requested: Cadastro solicitado registrations: confirm: Confirmar + description: Você recebeu um registro de um FASP. Rejeite se você não tiver iniciado isso. Se você iniciou isso, compare cuidadosamente o nome e a impressão digital da chave antes de confirmar o registro. reject: Rejeitar + title: Confirmar o registro FASP save: Salvar + select_capabilities: Selecionar recursos sign_in: Entrar status: Estado + title: Provedores de serviços auxiliares do Fediverso + title: FASP follow_recommendations: description_html: "A recomendação de contas ajuda os novos usuários a encontrar rapidamente conteúdo interessante. Quando um usuário ainda não tiver interagido o suficiente para gerar recomendações de contas, essas contas serão recomendadas. Essas recomendações são recalculadas diariamente a partir de uma lista de contas com alto engajamento e maior número de seguidores locais em uma dada língua." language: Na língua @@ -1927,6 +1939,10 @@ pt-BR: recovery_instructions_html: Se você perder acesso ao seu celular, você pode usar um dos códigos de recuperação abaixo para acessar a sua conta. Mantenha os códigos de recuperação em um local seguro. Por exemplo, você pode imprimi-los e guardá-los junto a outros documentos importantes. webauthn: Chaves de segurança user_mailer: + announcement_published: + description: 'Os administradores do %{domain} estão fazendo um anúncio:' + subject: Anúncio de serviço + title: Anúncio de serviço de %{domain} appeal_approved: action: Configurações da conta explanation: A revisão da punição na sua conta em %{strike_date} que você enviou em %{appeal_date} foi aprovada. Sua conta está novamente em situação regular. diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 0501e8cb54..1f01f622f5 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -495,6 +495,10 @@ ru: new: title: Импорт доменных блокировок no_file: Файл не выбран + fasp: + providers: + sign_in: + status: Пост follow_recommendations: description_html: "Следуйте рекомендациям, чтобы помочь новым пользователям быстро находить интересный контент. Если пользователь не взаимодействовал с другими в достаточной степени, чтобы сформировать персонализированные рекомендации, вместо этого рекомендуется использовать эти учетные записи. Они пересчитываются на ежедневной основе на основе комбинации аккаунтов с наибольшим количеством недавних взаимодействий и наибольшим количеством местных подписчиков для данного языка." language: Для языка diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 016ed4b25a..342a1dbe1c 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -84,7 +84,7 @@ de: backups_retention_period: Nutzer*innen haben die Möglichkeit, Archive ihrer Beiträge zu erstellen, die sie später herunterladen können. Wenn ein positiver Wert gesetzt ist, werden diese Archive nach der festgelegten Anzahl von Tagen automatisch aus deinem Speicher gelöscht. bootstrap_timeline_accounts: Diese Konten werden bei den Follower-Empfehlungen für neu registrierte Nutzer*innen oben angeheftet. closed_registrations_message: Wird angezeigt, wenn Registrierungen deaktiviert sind - content_cache_retention_period: Sämtliche Beiträge von anderen Servern (einschließlich geteilte Beiträge und Antworten) werden, unabhängig von der Interaktion der lokalen Nutzer*innen mit diesen Beiträgen, nach der festgelegten Anzahl von Tagen gelöscht. Das betrifft auch Beiträge, die von lokalen Nutzer*innen favorisiert oder als Lesezeichen gespeichert wurden. Private Erwähnungen zwischen Nutzer*innen von verschiedenen Servern werden ebenfalls verloren gehen und können nicht wiederhergestellt werden. Das Verwenden dieser Option richtet sich ausschließlich an Server für spezielle Zwecke und wird die allgemeine Nutzungserfahrung beeinträchtigen, wenn sie für den allgemeinen Gebrauch aktiviert ist. + content_cache_retention_period: Sämtliche Beiträge von anderen Servern (einschließlich geteilte Beiträge und Antworten) werden, unabhängig von der Interaktion der lokalen Nutzer*innen mit diesen Beiträgen, nach der festgelegten Anzahl von Tagen gelöscht. Das betrifft auch Beiträge, die von lokalen Nutzer*innen favorisiert oder als Lesezeichen gespeichert wurden. Private Erwähnungen zwischen Nutzer*innen von verschiedenen Servern werden ebenfalls verloren gehen und können nicht wiederhergestellt werden. Diese Option richtet sich ausschließlich an Server mit speziellen Zwecken und wird die allgemeine Nutzungserfahrung beeinträchtigen, wenn sie für den allgemeinen Gebrauch aktiviert ist. custom_css: Du kannst benutzerdefinierte Stile auf die Web-Version von Mastodon anwenden. favicon: WEBP, PNG, GIF oder JPG. Überschreibt das Standard-Mastodon-Favicon mit einem eigenen Symbol. mascot: Überschreibt die Abbildung in der erweiterten Weboberfläche. diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index b768c6551f..b2f7daf500 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -51,7 +51,7 @@ es-MX: inbox_url: Copia la URL de la página principal del relé que deseas usar irreversible: Las publicaciones filtradas desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante locale: El idioma de la interfaz de usuario, correos y notificaciones push - password: Utiliza al menos 8 caracteres + password: Usa al menos 8 caracteres phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de una publicación scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales. setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a las publicaciones recibidas recientemente) @@ -68,10 +68,10 @@ es-MX: domain_allow: domain: Este dominio podrá obtener datos de este servidor y los datos entrantes serán procesados y archivados email_domain_block: - domain: Este puede ser el nombre de dominio que se muestra en al dirección de correo o el registro MX que utiliza. Se comprobarán al registrarse. + domain: Este puede ser el nombre de dominio que se muestra en la dirección de correo o el registro MX que utiliza. Se comprobarán al registrarse. with_dns_records: Se hará un intento de resolver los registros DNS del dominio dado y los resultados serán también puestos en lista negra featured_tag: - name: 'Aquí están algunas de las etiquetas que más has utilizado recientemente:' + name: 'Aquí están algunas de las etiquetas que más has usado recientemente:' filters: action: Elegir qué acción realizar cuando una publicación coincide con el filtro actions: @@ -90,14 +90,14 @@ es-MX: mascot: Reemplaza la ilustración en la interfaz web avanzada. media_cache_retention_period: Los archivos multimedia de las publicaciones realizadas por usuarios remotos se almacenan en caché en su servidor. Si se establece en un valor positivo, los archivos multimedia se eliminarán tras el número de días especificado. Si los datos multimedia se solicitan después de haber sido eliminados, se volverán a descargar, si el contenido de origen sigue estando disponible. Debido a las restricciones sobre la frecuencia con la que las tarjetas de previsualización de enlaces sondean sitios de terceros, se recomienda establecer este valor en al menos 14 días, o las tarjetas de previsualización de enlaces no se actualizarán bajo demanda antes de ese tiempo. min_age: Se pedirá a los usuarios que confirmen su fecha de nacimiento al registrarse - peers_api_enabled: Una lista de nombres de dominio que este servidor ha encontrado en el fediverso. Aquí no se incluye ningún dato sobre si usted federa con un servidor determinado, sólo que su servidor lo sabe. Esto es utilizado por los servicios que recopilan estadísticas sobre la federación en un sentido general. + peers_api_enabled: Una lista de nombres de dominio que este servidor ha encontrado en el fediverso. Aquí no se incluye ningún dato sobre si usted federa con un servidor determinado, solamente que su servidor lo sabe. Esto es usado por los servicios que recopilan estadísticas sobre la federación en un sentido general. profile_directory: El directorio de perfiles lista a todos los usuarios que han optado por que su cuenta pueda ser descubierta. require_invite_text: Cuando los registros requieren aprobación manual, hace obligatoria la entrada de texto "¿Por qué quieres unirte?" en lugar de opcional site_contact_email: Cómo la gente puede ponerse en contacto contigo para consultas legales o de ayuda. site_contact_username: Cómo puede contactarte la gente en Mastodon. site_extended_description: Cualquier información adicional que pueda ser útil para los visitantes y sus usuarios. Se puede estructurar con formato Markdown. site_short_description: Una breve descripción para ayudar a identificar su servidor de forma única. ¿Quién lo administra, a quién va dirigido? - site_terms: Utiliza tu propia política de privacidad o déjala en blanco para usar la predeterminada Puede estructurarse con formato Markdown. + site_terms: Usa tu propia política de privacidad o déjala en blanco para usar la predeterminada Puede estructurarse con formato Markdown. site_title: Cómo puede referirse la gente a tu servidor además de por el nombre de dominio. status_page_url: URL de una página donde las personas pueden ver el estado de este servidor durante una interrupción theme: El tema que los visitantes no registrados y los nuevos usuarios ven. @@ -138,11 +138,11 @@ es-MX: text: Puede estructurarse con la sintaxis Markdown. terms_of_service_generator: admin_email: Las notificaciones legales incluyen contraavisos, órdenes judiciales, solicitudes de retirada y solicitudes de aplicación de la ley. - arbitration_address: Puede ser la misma que la dirección física anterior, o "N/A" si utiliza correo electrónico. - arbitration_website: Puede ser un formulario web, o “N/A” si utiliza correo electrónico. + arbitration_address: Puede ser la misma que la dirección física anterior, o "N/A" si usa correo electrónico. + arbitration_website: Puede ser un formulario web, o “N/A” si usa correo electrónico. choice_of_law: Ciudad, región, territorio o estado de las leyes de sustancia interna de las que se regirán todas y cada una de las reclamaciones. dmca_address: Para los operadores de EE. UU., utilice la dirección registrada en el Directorio de Agentes Designados de la DMCA. Un listado de apartados de correos está disponible bajo petición directa, utilice la Solicitud de Renuncia de Apartado de Correos de Agente Designado de la DMCA para enviar un correo electrónico a la Oficina de Derechos de Autor y describa que usted es un moderador de contenidos desde su casa que teme venganza o represalias por sus acciones y necesita utilizar un apartado de correos para eliminar su dirección particular de la vista del público. - dmca_email: Puede ser el mismo correo electrónico utilizado para "Dirección de correo electrónico para avisos legales" de arriba. + dmca_email: Puede ser el mismo correo electrónico usado para “Dirección de correo electrónico para avisos legales” de arriba. domain: Identificación única del servicio en línea que presta. jurisdiction: Indique el país de residencia de quien paga las facturas. Si se trata de una empresa u otra entidad, indique el país en el que está constituida y la ciudad, región, territorio o estado, según proceda. min_age: No debe ser menor de la edad mínima exigida por las leyes de su jurisdicción. diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml index 520b684847..9f46cdec7d 100644 --- a/config/locales/simple_form.fa.yml +++ b/config/locales/simple_form.fa.yml @@ -349,6 +349,9 @@ fa: jurisdiction: صلاحیت قانونی min_age: کمینهٔ زمان user: + date_of_birth_1i: روز + date_of_birth_2i: ماه + date_of_birth_3i: سال role: نقش time_zone: منطقهٔ زمانی user_role: diff --git a/config/locales/simple_form.ga.yml b/config/locales/simple_form.ga.yml index af75401d3f..bd8dac2a36 100644 --- a/config/locales/simple_form.ga.yml +++ b/config/locales/simple_form.ga.yml @@ -75,6 +75,7 @@ ga: filters: action: Roghnaigh an gníomh ba cheart a dhéanamh nuair a mheaitseálann postáil an scagaire actions: + blur: Folaigh na meáin taobh thiar de rabhadh, gan an téacs féin a cheilt hide: Cuir an t-ábhar scagtha i bhfolach go hiomlán, ag iompar amhail is nach raibh sé ann warn: Folaigh an t-ábhar scagtha taobh thiar de rabhadh a luann teideal an scagaire form_admin_settings: @@ -88,6 +89,7 @@ ga: favicon: WEBP, PNG, GIF nó JPG. Sáraíonn sé an favicon Mastodon réamhshocraithe le deilbhín saincheaptha. mascot: Sáraíonn sé an léaráid san ardchomhéadan gréasáin. media_cache_retention_period: Déantar comhaid meán ó phoist a dhéanann cianúsáideoirí a thaisceadh ar do fhreastalaí. Nuair a bheidh luach dearfach socraithe, scriosfar na meáin tar éis an líon sonraithe laethanta. Má iarrtar na sonraí meán tar éis é a scriosadh, déanfar é a ath-íoslódáil, má tá an t-ábhar foinse fós ar fáil. Mar gheall ar shrianta ar cé chomh minic is atá cártaí réamhamhairc ag vótaíocht do shuíomhanna tríú páirtí, moltar an luach seo a shocrú go 14 lá ar a laghad, nó ní dhéanfar cártaí réamhamhairc naisc a nuashonrú ar éileamh roimh an am sin. + min_age: Iarrfar ar úsáideoirí a ndáta breithe a dhearbhú le linn clárúcháin peers_api_enabled: Liosta de na hainmneacha fearainn ar tháinig an freastalaí seo orthu sa choinbhleacht. Níl aon sonraí san áireamh anseo faoi cé acu an ndéanann tú cónascadh le freastalaí ar leith, díreach go bhfuil a fhios ag do fhreastalaí faoi. Úsáideann seirbhísí a bhailíonn staitisticí ar chónaidhm go ginearálta é seo. profile_directory: Liostaíonn an t-eolaire próifíle na húsáideoirí go léir a roghnaigh isteach le bheith in-aimsithe. require_invite_text: Nuair a bhíonn faomhadh láimhe ag teastáil le haghaidh clárúcháin, déan an "Cén fáth ar mhaith leat a bheith páirteach?" ionchur téacs éigeantach seachas roghnach @@ -146,6 +148,7 @@ ga: min_age: Níor chóir go mbeidís faoi bhun na haoise íosta a éilíonn dlíthe do dhlínse. user: chosen_languages: Nuair a dhéantar iad a sheiceáil, ní thaispeánfar ach postálacha i dteangacha roghnaithe in amlínte poiblí + date_of_birth: Ní mór dúinn a chinntiú go bhfuil tú ar a laghad %{age} chun Mastodon a úsáid. Ní stórálfaimid é seo. role: Rialaíonn an ról na ceadanna atá ag an úsáideoir. user_role: color: Dath le húsáid don ról ar fud an Chomhéadain, mar RGB i bhformáid heicsidheachúlach @@ -258,6 +261,7 @@ ga: name: Haischlib filters: actions: + blur: Folaigh na meáin le rabhadh hide: Cuir i bhfolach go hiomlán warn: Cuir i bhfolach le rabhadh form_admin_settings: @@ -271,6 +275,7 @@ ga: favicon: Favicon mascot: Mascóg saincheaptha (oidhreacht) media_cache_retention_period: Tréimhse choinneála taisce meán + min_age: Riachtanas aoise íosta peers_api_enabled: Foilsigh liosta de na freastalaithe aimsithe san API profile_directory: Cumasaigh eolaire próifíle registrations_mode: Cé atá in ann clárú @@ -349,6 +354,9 @@ ga: jurisdiction: Dlínse dhlíthiúil min_age: Aois íosta user: + date_of_birth_1i: Lá + date_of_birth_2i: Mí + date_of_birth_3i: Bliain role: Ról time_zone: Crios ama user_role: diff --git a/config/locales/simple_form.kab.yml b/config/locales/simple_form.kab.yml index c14f6376a0..c0ff7e598e 100644 --- a/config/locales/simple_form.kab.yml +++ b/config/locales/simple_form.kab.yml @@ -18,7 +18,7 @@ kab: bot: Smekti-d wiyaḍ dakken amiḍan-a ixeddem s wudem amezwer tigawin tiwurmanin yernu ur yezmir ara ad yettwaɛass email: Ad n-teṭṭfeḍ imayl i usentem irreversible: Tisuffaɣ i tessazedgeḍ ad ttwakksent i lebda, ula ma tekkseḍ imsizdeg-nni ar zdat - locale: Tutlayt n ugrudem, imaylen d walɣuten yettudemren + locale: Tutlayt n ugrudem, imaylen d yilɣa yettudemren password: Seqdec ma drus 8 n yisekkilen setting_always_send_emails: S umata, ilɣa s yimayl ur d-ttwaceyyεen ara mi ara tesseqdaceḍ Mastodon s wudem urmid setting_display_media_default: Ffer imidyaten yettwacreḍ d infariyen @@ -115,8 +115,8 @@ kab: theme: Asentel amezwer thumbnail: Tanfult n uqeddac interactions: - must_be_follower: Ssewḥel alɣuten sɣur wid akked tid ur yellin ara d imeḍfaren-ik·im - must_be_following: Ssewḥel alɣuten sɣur wid akked tid ur tettḍafareḍ ara + must_be_follower: Ssewḥel ilɣa sɣur wid akk d tid ur yellin ara d imeḍfaren-ik·im + must_be_following: Ssewḥel ilɣa sɣur wid akked tid ur tettḍafareḍ ara must_be_following_dm: Sewḥel iznan usriden sɣur wid akked tid ur tettḍafareḍ ara invite: comment: Awennit diff --git a/config/locales/simple_form.lad.yml b/config/locales/simple_form.lad.yml index 6fef9f7422..a3f70bd361 100644 --- a/config/locales/simple_form.lad.yml +++ b/config/locales/simple_form.lad.yml @@ -311,6 +311,9 @@ lad: terms_of_service_generator: domain: Domeno user: + date_of_birth_1i: Diya + date_of_birth_2i: Mez + date_of_birth_3i: Anyo role: Rolo time_zone: Zona de tiempo user_role: diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml index 0c362b0a30..287ce36a5d 100644 --- a/config/locales/simple_form.lv.yml +++ b/config/locales/simple_form.lv.yml @@ -25,7 +25,7 @@ lv: type_html: Izvēlies, ko darīt ar %{acct} types: disable: Neļauj lietotājam izmantot savu kontu, bet neizdzēs vai neslēp tā saturu. - none: Izmanto šo, lai nosūtītu lietotājam brīdinājumu, neradot nekādas citas darbības. + none: Šis ir izmantojams, lai nosūtītu lietotājam brīdinājumu bez jebkādu citu darbību izraisīšanas. sensitive: Visus šī lietotāja informācijas nesēju pielikumus uzspiesti atzīmēt kā jūtīgus. silence: Neļaut lietotājam veikt ierakstus ar publisku redzamību, paslēpt viņa ierakstus un paziņojumus no cilvēkiem, kas tam neseko. Tiek aizvērti visi ziņojumi par šo kontu. suspend: Novērs jebkādu mijiedarbību no šī konta vai uz to un dzēs tā saturu. Atgriežams 30 dienu laikā. Tiek aizvērti visi šī konta pārskati. @@ -45,8 +45,8 @@ lv: context: Viens vai vairāki konteksti, kur jāpiemēro filtrs current_password: Drošības nolūkos, lūdzu, ievadi pašreizējā konta paroli current_username: Lai apstiprinātu, lūdzu, ievadi pašreizējā konta paroli - digest: Tiek nosūtīts tikai pēc ilgstošas bezdarbības un tikai tad, ja savas prombūtnes laikā esi saņēmis jebkādas personīgas ziņas - email: Tev tiks nosūtīts apstiprinājuma e-pasts + digest: Tiek nosūtīts tikai pēc ilgstošas bezdarbības un tikai tad, ja savas prombūtnes laikā saņēmi jebkādas personīgas ziņas + email: Tev tiks nosūtīts apstiprinājuma e-pasta ziņojums header: WEBP, PNG, GIF vai JPG. Ne vairāk kā %{size}. Tiks samazināts līdz %{dimensions}px inbox_url: Nokopē URL no tā releja sākumlapas, kuru vēlies izmantot irreversible: Filtrētās ziņas neatgriezeniski pazudīs, pat ja filtrs vēlāk tiks noņemts @@ -125,7 +125,7 @@ lv: hint: Izvēles. Sniedz vairāk informācijas par noteikumu text: Jāapraksta nosacījums vai prasība šī servera lietotājiem. Jāmēģina to veidot īsu un vienkāršu sessions: - otp: 'Jāievada tālruņa lietotnes izveidots divpakāpju kods vai jāizmanto viens no saviem atkopes kodie:' + otp: 'Jāievada tālruņa lietotnes izveidots divpakāpju kods vai jāizmanto viens no saviem atkopes kodiem:' webauthn: Ja tā ir USB atslēga, noteikti ievieto to un, ja nepieciešams, pieskaries tai. settings: indexable: Tava profila lapa var tikt parādīta Google, Bing un citu meklēšanas dzinēju rezultātos. @@ -213,7 +213,7 @@ lv: max_uses: Maksimālais lietojumu skaits new_password: Jauna parole note: Par sevi - otp_attempt: Divfaktoru kods + otp_attempt: Divpakāpju kods password: Parole phrase: Atslēgvārds vai frāze setting_advanced_layout: Iespējot paplašināto tīmekļa saskarni diff --git a/config/locales/simple_form.th.yml b/config/locales/simple_form.th.yml index dfaa61e0cc..530bd60ad1 100644 --- a/config/locales/simple_form.th.yml +++ b/config/locales/simple_form.th.yml @@ -324,6 +324,9 @@ th: terms_of_service_generator: domain: โดเมน user: + date_of_birth_1i: วัน + date_of_birth_2i: เดือน + date_of_birth_3i: ปี role: บทบาท time_zone: โซนเวลา user_role: diff --git a/config/locales/th.yml b/config/locales/th.yml index 9b7ae7897d..19f9e4da1f 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -466,6 +466,12 @@ th: new: title: นำเข้าการปิดกั้นโดเมน no_file: ไม่ได้เลือกไฟล์ + fasp: + debug: + callbacks: + created_at: สร้างเมื่อ + delete: ลบ + ip: ที่อยู่ IP follow_recommendations: description_html: "คำแนะนำการติดตามช่วยให้ผู้ใช้ใหม่ค้นหาเนื้อหาที่น่าสนใจได้อย่างรวดเร็ว เมื่อผู้ใช้ไม่ได้โต้ตอบกับผู้อื่นมากพอที่จะสร้างคำแนะนำการติดตามเฉพาะบุคคล จะแนะนำบัญชีเหล่านี้แทน จะคำนวณคำแนะนำใหม่เป็นประจำทุกวันจากบัญชีต่าง ๆ ที่มีการมีส่วนร่วมล่าสุดสูงสุดและจำนวนผู้ติดตามในเซิร์ฟเวอร์สูงสุดสำหรับภาษาที่กำหนด" language: สำหรับภาษา @@ -1636,7 +1642,7 @@ th: last_active: ใช้งานล่าสุด most_recent: ล่าสุด moved: ย้ายแล้ว - mutual: ร่วมกัน + mutual: คนที่มีร่วมกัน primary: หลัก relationship: ความสัมพันธ์ remove_selected_domains: เอาผู้ติดตามทั้งหมดออกจากโดเมนที่เลือก diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index 7278058dce..43f4f3ecba 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -38,7 +38,7 @@ zh-HK: current_email: 現時電郵 label: 更改電郵 new_email: 新的電郵 - submit: 改變電郵 + submit: 更改電郵 title: 改變 %{username} 的電郵 change_role: changed_msg: 成功更改身份! @@ -182,6 +182,7 @@ zh-HK: destroy_domain_block: 刪除已封鎖的域名 destroy_instance: 清除網域 destroy_ip_block: 刪除 IP 規則 + destroy_relay: 刪除中繼 destroy_status: 刪除文章 destroy_unavailable_domain: 刪除無效域名 destroy_user_role: 刪除身份 @@ -425,6 +426,12 @@ zh-HK: new: title: 匯入封鎖的網域 no_file: 未選擇檔案 + fasp: + debug: + callbacks: + delete: 刪除 + providers: + delete: 刪除 follow_recommendations: description_html: "跟隨建議幫助新使用者快速找到有趣內容。 當使用者尚未和其他帳號足夠多的互動以產生個人化建議時,以下帳號將被推荐。這些是一句指定語言的近期參與度和本地粉絲數最高之帳戶組合每日重新計算。" language: 按語言 @@ -1549,6 +1556,7 @@ zh-HK: import: 匯入 import_and_export: 匯入及匯出 migrate: 帳戶遷移 + notifications: 電郵通知 preferences: 偏好設定 profile: 個人資料 relationships: 關注及追隨者 diff --git a/config/routes.rb b/config/routes.rb index ab6eeee73c..1f97ddaaa4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -133,6 +133,7 @@ Rails.application.routes.draw do constraints(username: %r{[^@/.]+}) do with_options to: 'accounts#show' do get '/@:username', as: :short_account + get '/@:username/featured' get '/@:username/with_replies', as: :short_account_with_replies get '/@:username/media', as: :short_account_media get '/@:username/tagged/:tag', as: :short_account_tag diff --git a/db/migrate/20250410144908_drop_imports.rb b/db/migrate/20250410144908_drop_imports.rb new file mode 100644 index 0000000000..7be9daf750 --- /dev/null +++ b/db/migrate/20250410144908_drop_imports.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class DropImports < ActiveRecord::Migration[7.1] + def up + drop_table :imports + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/schema.rb b/db/schema.rb index 172800edce..d9a082eb24 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2025_03_13_123400) do +ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -739,19 +739,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_13_123400) do t.index ["user_id"], name: "index_identities_on_user_id" end - create_table "imports", force: :cascade do |t| - t.integer "type", null: false - t.boolean "approved", default: false, null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "updated_at", precision: nil, null: false - t.string "data_file_name" - t.string "data_content_type" - t.datetime "data_updated_at", precision: nil - t.bigint "account_id", null: false - t.boolean "overwrite", default: false, null: false - t.integer "data_file_size" - end - create_table "instance_infos", force: :cascade do |t| t.string "domain", default: "", null: false t.string "software", default: "", null: false @@ -1720,7 +1707,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_03_13_123400) do add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade add_foreign_key "generated_annual_reports", "accounts" add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade - add_foreign_key "imports", "accounts", name: "fk_6db1b6e408", on_delete: :cascade add_foreign_key "invites", "users", on_delete: :cascade add_foreign_key "list_accounts", "accounts", on_delete: :cascade add_foreign_key "list_accounts", "follow_requests", on_delete: :cascade diff --git a/lib/mastodon/cli/media.rb b/lib/mastodon/cli/media.rb index 84ec13eaab..1059eb6066 100644 --- a/lib/mastodon/cli/media.rb +++ b/lib/mastodon/cli/media.rb @@ -293,7 +293,6 @@ module Mastodon::CLI Account Backup CustomEmoji - Import MediaAttachment PreviewCard SiteUpload @@ -309,7 +308,6 @@ module Mastodon::CLI [:headers, Account.sum(:header_file_size), Account.local.sum(:header_file_size)], [:preview_cards, PreviewCard.sum(:image_file_size), nil], [:backups, Backup.sum(:dump_file_size), nil], - [:imports, Import.sum(:data_file_size), nil], [:settings, SiteUpload.sum(:file_file_size), nil], ].map { |label, total, local| [label.to_s.titleize, number_to_human_size(total), local.present? ? number_to_human_size(local) : nil] } end diff --git a/spec/fabricators/import_fabricator.rb b/spec/fabricators/import_fabricator.rb deleted file mode 100644 index 4951bb9a4d..0000000000 --- a/spec/fabricators/import_fabricator.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -Fabricator(:import) do - account - type :following - data { attachment_fixture('imports.txt') } -end diff --git a/spec/models/import_spec.rb b/spec/models/import_spec.rb deleted file mode 100644 index 587e0a9d26..0000000000 --- a/spec/models/import_spec.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Import do - describe 'Validations' do - it { is_expected.to validate_presence_of(:type) } - it { is_expected.to validate_presence_of(:data) } - end -end diff --git a/spec/services/import_service_spec.rb b/spec/services/import_service_spec.rb deleted file mode 100644 index 2e1358c635..0000000000 --- a/spec/services/import_service_spec.rb +++ /dev/null @@ -1,242 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ImportService, :inline_jobs do - include RoutingHelper - - let!(:account) { Fabricate(:account, locked: false) } - let!(:bob) { Fabricate(:account, username: 'bob', locked: false) } - let!(:eve) { Fabricate(:account, username: 'eve', domain: 'example.com', locked: false, protocol: :activitypub, inbox_url: 'https://example.com/inbox') } - - before do - stub_request(:post, 'https://example.com/inbox').to_return(status: 200) - end - - context 'when importing old-style list of muted users' do - subject { described_class.new } - - let(:csv) { attachment_fixture('mute-imports.txt') } - - describe 'when no accounts are muted' do - let(:import) { Import.create(account: account, type: 'muting', data: csv) } - - it 'mutes the listed accounts, including notifications' do - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - end - end - - describe 'when some accounts are muted and overwrite is not set' do - let(:import) { Import.create(account: account, type: 'muting', data: csv) } - - it 'mutes the listed accounts, including notifications' do - account.mute!(bob, notifications: false) - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - end - end - - describe 'when some accounts are muted and overwrite is set' do - let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) } - - it 'mutes the listed accounts, including notifications' do - account.mute!(bob, notifications: false) - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - end - end - end - - context 'when importing new-style list of muted users' do - subject { described_class.new } - - let(:csv) { attachment_fixture('new-mute-imports.txt') } - - describe 'when no accounts are muted' do - let(:import) { Import.create(account: account, type: 'muting', data: csv) } - - it 'mutes the listed accounts, respecting notifications' do - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false - end - end - - describe 'when some accounts are muted and overwrite is not set' do - let(:import) { Import.create(account: account, type: 'muting', data: csv) } - - it 'mutes the listed accounts, respecting notifications' do - account.mute!(bob, notifications: true) - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false - end - end - - describe 'when some accounts are muted and overwrite is set' do - let(:import) { Import.create(account: account, type: 'muting', data: csv, overwrite: true) } - - it 'mutes the listed accounts, respecting notifications' do - account.mute!(bob, notifications: true) - subject.call(import) - expect(account.muting.count).to eq 2 - expect(Mute.find_by(account: account, target_account: bob).hide_notifications).to be true - expect(Mute.find_by(account: account, target_account: eve).hide_notifications).to be false - end - end - end - - context 'when importing old-style list of followed users' do - subject { described_class.new } - - let(:csv) { attachment_fixture('mute-imports.txt') } - - describe 'when no accounts are followed' do - let(:import) { Import.create(account: account, type: 'following', data: csv) } - - it 'follows the listed accounts, including boosts' do - subject.call(import) - - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - end - end - - describe 'when some accounts are already followed and overwrite is not set' do - let(:import) { Import.create(account: account, type: 'following', data: csv) } - - it 'follows the listed accounts, including notifications' do - account.follow!(bob, reblogs: false) - subject.call(import) - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - end - end - - describe 'when some accounts are already followed and overwrite is set' do - let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) } - - it 'mutes the listed accounts, including notifications' do - account.follow!(bob, reblogs: false) - subject.call(import) - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - end - end - end - - context 'when importing new-style list of followed users' do - subject { described_class.new } - - let(:csv) { attachment_fixture('new-following-imports.txt') } - - describe 'when no accounts are followed' do - let(:import) { Import.create(account: account, type: 'following', data: csv) } - - it 'follows the listed accounts, respecting boosts' do - subject.call(import) - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false - end - end - - describe 'when some accounts are already followed and overwrite is not set' do - let(:import) { Import.create(account: account, type: 'following', data: csv) } - - it 'mutes the listed accounts, respecting notifications' do - account.follow!(bob, reblogs: true) - subject.call(import) - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false - end - end - - describe 'when some accounts are already followed and overwrite is set' do - let(:import) { Import.create(account: account, type: 'following', data: csv, overwrite: true) } - - it 'mutes the listed accounts, respecting notifications' do - account.follow!(bob, reblogs: true) - subject.call(import) - expect(account.following.count).to eq 1 - expect(account.follow_requests.count).to eq 1 - expect(Follow.find_by(account: account, target_account: bob).show_reblogs).to be true - expect(FollowRequest.find_by(account: account, target_account: eve).show_reblogs).to be false - end - end - end - - # Based on the bug report 20571 where UTF-8 encoded domains were rejecting import of their users - # - # https://github.com/mastodon/mastodon/issues/20571 - context 'with a utf-8 encoded domains' do - subject { described_class.new } - - let!(:nare) { Fabricate(:account, username: 'nare', domain: 'թութ.հայ', locked: false, protocol: :activitypub, inbox_url: 'https://թութ.հայ/inbox') } - let(:csv) { attachment_fixture('utf8-followers.txt') } - let(:import) { Import.create(account: account, type: 'following', data: csv) } - - # Make sure to not actually go to the remote server - before do - stub_request(:post, nare.inbox_url).to_return(status: 200) - end - - it 'follows the listed account' do - expect(account.follow_requests.count).to eq 0 - subject.call(import) - expect(account.follow_requests.count).to eq 1 - end - end - - context 'when importing bookmarks' do - subject { described_class.new } - - let(:csv) { attachment_fixture('bookmark-imports.txt') } - let(:local_account) { Fabricate(:account, username: 'foo', domain: nil) } - let!(:remote_status) { Fabricate(:status, uri: 'https://example.com/statuses/1312') } - let!(:direct_status) { Fabricate(:status, uri: 'https://example.com/statuses/direct', visibility: :direct) } - - around do |example| - local_before = Rails.configuration.x.local_domain - web_before = Rails.configuration.x.web_domain - Rails.configuration.x.local_domain = 'local.com' - Rails.configuration.x.web_domain = 'local.com' - example.run - Rails.configuration.x.web_domain = web_before - Rails.configuration.x.local_domain = local_before - end - - before do - service = instance_double(ActivityPub::FetchRemoteStatusService) - allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service) - allow(service).to receive(:call).with('https://unknown-remote.com/users/bar/statuses/1') do - Fabricate(:status, uri: 'https://unknown-remote.com/users/bar/statuses/1') - end - end - - describe 'when no bookmarks are set' do - let(:import) { Import.create(account: account, type: 'bookmarks', data: csv) } - - it 'adds the toots the user has access to to bookmarks' do - local_status = Fabricate(:status, account: local_account, uri: 'https://local.com/users/foo/statuses/42', id: 42, local: true) - subject.call(import) - expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to include(local_status.id) - expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to include(remote_status.id) - expect(account.bookmarks.map { |bookmark| bookmark.status.id }).to_not include(direct_status.id) - expect(account.bookmarks.count).to eq 3 - end - end - end -end diff --git a/spec/support/system_helpers.rb b/spec/support/system_helpers.rb index ffbba177b3..44bbc64a59 100644 --- a/spec/support/system_helpers.rb +++ b/spec/support/system_helpers.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true module SystemHelpers + FRONTEND_TRANSLATIONS = JSON.parse Rails.root.join('app', 'javascript', 'mastodon', 'locales', 'en.json').read + def submit_button I18n.t('generic.save_changes') end @@ -16,4 +18,8 @@ module SystemHelpers def css_id(record) "##{dom_id(record)}" end + + def frontend_translations(key) + FRONTEND_TRANSLATIONS[key] + end end diff --git a/spec/system/account_notes_spec.rb b/spec/system/account_notes_spec.rb index c4054f204e..1d125e1984 100644 --- a/spec/system/account_notes_spec.rb +++ b/spec/system/account_notes_spec.rb @@ -18,7 +18,7 @@ RSpec.describe 'Account notes', :inline_jobs, :js, :streaming do visit_profile(other_account) note_text = 'This is a personal note' - fill_in 'Click to add note', with: note_text + fill_in frontend_translations('account_note.placeholder'), with: note_text # This is a bit awkward since there is no button to save the change # The easiest way is to send ctrl+enter ourselves diff --git a/spec/system/log_out_spec.rb b/spec/system/log_out_spec.rb index 2e52254ca0..ebbf5a5772 100644 --- a/spec/system/log_out_spec.rb +++ b/spec/system/log_out_spec.rb @@ -17,8 +17,9 @@ RSpec.describe 'Log out' do click_on 'Logout' end - expect(page).to have_title(I18n.t('auth.login')) - expect(page).to have_current_path('/auth/sign_in') + expect(page) + .to have_title(I18n.t('auth.login')) + .and have_current_path('/auth/sign_in') end end @@ -28,6 +29,8 @@ RSpec.describe 'Log out' do ignore_js_error(/Failed to load resource: the server responded with a status of 422/) visit root_path + expect(page) + .to have_css('body', class: 'app-body') within '.navigation-bar' do click_on 'Menu' @@ -39,8 +42,9 @@ RSpec.describe 'Log out' do click_on 'Log out' - expect(page).to have_title(I18n.t('auth.login')) - expect(page).to have_current_path('/auth/sign_in') + expect(page) + .to have_title(I18n.t('auth.login')) + .and have_current_path('/auth/sign_in') end end end diff --git a/spec/system/new_statuses_spec.rb b/spec/system/new_statuses_spec.rb index 01b1500bc3..87b29004b2 100644 --- a/spec/system/new_statuses_spec.rb +++ b/spec/system/new_statuses_spec.rb @@ -20,20 +20,7 @@ RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do status_text = 'This is a new status!' within('.compose-form') do - fill_in "What's on your mind?", with: status_text - click_on 'Post' - end - - expect(page) - .to have_css('.status__content__text', text: status_text) - end - - it 'can be posted again' do - visit_homepage - status_text = 'This is a second status!' - - within('.compose-form') do - fill_in "What's on your mind?", with: status_text + fill_in frontend_translations('compose_form.placeholder'), with: status_text click_on 'Post' end diff --git a/spec/system/share_entrypoint_spec.rb b/spec/system/share_entrypoint_spec.rb index 7ccfee599a..b55ea31657 100644 --- a/spec/system/share_entrypoint_spec.rb +++ b/spec/system/share_entrypoint_spec.rb @@ -23,24 +23,14 @@ RSpec.describe 'Share page', :js, :streaming do fill_in_form expect(page) - .to have_css('.notification-bar-message', text: translations['compose.published.body']) + .to have_css('.notification-bar-message', text: frontend_translations('compose.published.body')) end def fill_in_form within('.compose-form') do - fill_in translations['compose_form.placeholder'], + fill_in frontend_translations('compose_form.placeholder'), with: 'This is a new status!' - click_on translations['compose_form.publish'] + click_on frontend_translations('compose_form.publish') end end - - def translations - # TODO: Extract to system spec helper for re-use? - JSON.parse( - Rails - .root - .join('app', 'javascript', 'mastodon', 'locales', 'en.json') - .read - ) - end end diff --git a/spec/workers/import_worker_spec.rb b/spec/workers/import_worker_spec.rb deleted file mode 100644 index 1d34aafe86..0000000000 --- a/spec/workers/import_worker_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe ImportWorker do - let(:worker) { described_class.new } - let(:service) { instance_double(ImportService, call: true) } - - describe '#perform' do - before do - allow(ImportService).to receive(:new).and_return(service) - end - - let(:import) { Fabricate(:import) } - - it 'sends the import to the service' do - worker.perform(import.id) - - expect(service).to have_received(:call).with(import) - expect { import.reload }.to raise_error(ActiveRecord::RecordNotFound) - end - end -end diff --git a/spec/workers/unfilter_notifications_worker_spec.rb b/spec/workers/unfilter_notifications_worker_spec.rb index 464a4520ff..2fd130301f 100644 --- a/spec/workers/unfilter_notifications_worker_spec.rb +++ b/spec/workers/unfilter_notifications_worker_spec.rb @@ -5,6 +5,7 @@ require 'rails_helper' RSpec.describe UnfilterNotificationsWorker do let(:recipient) { Fabricate(:account) } let(:sender) { Fabricate(:account) } + let(:worker) { described_class.new } before do # Populate multiple kinds of filtered notifications @@ -67,23 +68,22 @@ RSpec.describe UnfilterNotificationsWorker do end describe '#perform' do - context 'with single argument (prerelease behavior)' do - subject { described_class.new.perform(notification_request.id) } - - let(:notification_request) { Fabricate(:notification_request, from_account: sender, account: recipient) } + context 'with recipient and sender' do + subject { worker.perform(recipient.id, sender.id) } it_behaves_like 'shared behavior' - - it 'destroys the notification request' do - expect { subject } - .to change { NotificationRequest.exists?(notification_request.id) }.to(false) - end end - context 'with two arguments' do - subject { described_class.new.perform(recipient.id, sender.id) } + context 'with missing records' do + it 'runs without error for missing sender' do + expect { worker.perform(recipient.id, nil) } + .to_not raise_error + end - it_behaves_like 'shared behavior' + it 'runs without error for missing recipient' do + expect { worker.perform(nil, sender.id) } + .to_not raise_error + end end end end diff --git a/yarn.lock b/yarn.lock index 6acbf03bf1..791fdf7c44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2235,14 +2235,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0": - version: 4.10.0 - resolution: "@eslint-community/regexpp@npm:4.10.0" - checksum: 10c0/c5f60ef1f1ea7649fa7af0e80a5a79f64b55a8a8fa5086de4727eb4c86c652aedee407a9c143b8995d2c0b2d75c1222bec9ba5d73dbfc1f314550554f0979ef4 - languageName: node - linkType: hard - -"@eslint-community/regexpp@npm:^4.12.1": +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1": version: 4.12.1 resolution: "@eslint-community/regexpp@npm:4.12.1" checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6