diff --git a/.rubocop/metrics.yml b/.rubocop/metrics.yml index 89532af42a..bb15e6ff31 100644 --- a/.rubocop/metrics.yml +++ b/.rubocop/metrics.yml @@ -1,17 +1,21 @@ --- Metrics/AbcSize: - Exclude: - - lib/mastodon/cli/*.rb + Enabled: false Metrics/BlockLength: Enabled: false +Metrics/BlockNesting: + Enabled: false + Metrics/ClassLength: Enabled: false +Metrics/CollectionLiteralLength: + Enabled: false + Metrics/CyclomaticComplexity: - Exclude: - - lib/mastodon/cli/*.rb + Enabled: false Metrics/MethodLength: Enabled: false @@ -20,4 +24,7 @@ Metrics/ModuleLength: Enabled: false Metrics/ParameterLists: - CountKeywordArgs: false + Enabled: false + +Metrics/PerceivedComplexity: + Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 625fbf17ab..68ed0ac6c8 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,23 +6,6 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. -Metrics/AbcSize: - Max: 82 - -# Configuration parameters: CountBlocks, CountModifierForms, Max. -Metrics/BlockNesting: - Exclude: - - 'lib/tasks/mastodon.rake' - -# Configuration parameters: AllowedMethods, AllowedPatterns. -Metrics/CyclomaticComplexity: - Max: 25 - -# Configuration parameters: AllowedMethods, AllowedPatterns. -Metrics/PerceivedComplexity: - Max: 27 - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedVars, DefaultToNil. Style/FetchEnvVar: diff --git a/Gemfile.lock b/Gemfile.lock index 85114a3f1e..e99d5f7cba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -806,10 +806,10 @@ GEM ruby-saml (1.18.1) nokogiri (>= 1.13.10) rexml - ruby-vips (2.2.4) + ruby-vips (2.2.5) ffi (~> 1.12) logger - rubyzip (3.0.1) + rubyzip (3.0.2) rufus-scheduler (3.9.2) fugit (~> 1.1, >= 1.11.1) safety_net_attestation (0.4.0) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 33d4bf6d02..65f803b101 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -102,6 +102,16 @@ module ApplicationHelper policy(record).public_send(:"#{action}?") end + def conditional_link_to(condition, name, options = {}, html_options = {}, &block) + if condition && !current_page?(block_given? ? name : options) + link_to(name, options, html_options, &block) + elsif block_given? + content_tag(:span, options, html_options, &block) + else + content_tag(:span, name, html_options) + end + end + def material_symbol(icon, attributes = {}) safe_join( [ diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 9dfa4041bd..41b2336bc2 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -84,6 +84,7 @@ export const COMPOSE_FOCUS = 'COMPOSE_FOCUS'; const messages = defineMessages({ uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' }, uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, + uploadQuote: { id: 'upload_error.quote', defaultMessage: 'File upload not allowed with quotes.' }, open: { id: 'compose.published.open', defaultMessage: 'Open' }, published: { id: 'compose.published.body', defaultMessage: 'Post published.' }, saved: { id: 'compose.saved.body', defaultMessage: 'Post saved.' }, @@ -146,7 +147,7 @@ export function resetCompose() { }; } -export const focusCompose = (defaultText) => (dispatch, getState) => { +export const focusCompose = (defaultText = '') => (dispatch, getState) => { dispatch({ type: COMPOSE_FOCUS, defaultText, @@ -303,6 +304,11 @@ export function submitComposeFail(error) { export function uploadCompose(files) { return function (dispatch, getState) { + // Exit if there's a quote. + if (getState().compose.get('quoted_status_id')) { + dispatch(showAlert({ message: messages.uploadQuote })); + return; + } const uploadLimit = getState().getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']); const media = getState().getIn(['compose', 'media_attachments']); const pending = getState().getIn(['compose', 'pending_media_attachments']); diff --git a/app/javascript/mastodon/actions/compose_typed.ts b/app/javascript/mastodon/actions/compose_typed.ts index 7b1f5e688c..3c6a264993 100644 --- a/app/javascript/mastodon/actions/compose_typed.ts +++ b/app/javascript/mastodon/actions/compose_typed.ts @@ -1,3 +1,5 @@ +import { defineMessages } from 'react-intl'; + import { createAction } from '@reduxjs/toolkit'; import type { List as ImmutableList, Map as ImmutableMap } from 'immutable'; @@ -12,7 +14,27 @@ import { import type { ApiQuotePolicy } from '../api_types/quotes'; import type { Status } from '../models/status'; -import { ensureComposeIsVisible } from './compose'; +import { showAlert } from './alerts'; +import { focusCompose } from './compose'; + +const messages = defineMessages({ + quoteErrorUpload: { + id: 'quote_error.upload', + defaultMessage: 'Quoting is not allowed with media attachments.', + }, + quoteErrorPoll: { + id: 'quote_error.poll', + defaultMessage: 'Quoting is not allowed with polls.', + }, + quoteErrorQuote: { + id: 'quote_error.quote', + defaultMessage: 'Only one quote at a time is allowed.', + }, + quoteErrorUnauthorized: { + id: 'quote_error.unauthorized', + defaultMessage: 'You are not authorized to quote this post.', + }, +}); type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & { unattached?: boolean; @@ -78,14 +100,43 @@ export const changeUploadCompose = createDataLoadingThunk( }, ); -export const quoteComposeByStatus = createAppThunk( +export const quoteCompose = createAppThunk( 'compose/quoteComposeStatus', - (status: Status, { getState }) => { - ensureComposeIsVisible(getState); + (status: Status, { dispatch }) => { + dispatch(focusCompose()); return status; }, ); +export const quoteComposeByStatus = createAppThunk( + (status: Status, { dispatch, getState }) => { + const composeState = getState().compose; + const mediaAttachments = composeState.get('media_attachments'); + + if (composeState.get('poll')) { + dispatch(showAlert({ message: messages.quoteErrorPoll })); + } else if ( + composeState.get('is_uploading') || + (mediaAttachments && + typeof mediaAttachments !== 'string' && + typeof mediaAttachments !== 'number' && + typeof mediaAttachments !== 'boolean' && + mediaAttachments.size !== 0) + ) { + dispatch(showAlert({ message: messages.quoteErrorUpload })); + } else if (composeState.get('quoted_status_id')) { + dispatch(showAlert({ message: messages.quoteErrorQuote })); + } else if ( + status.getIn(['quote_approval', 'current_user']) !== 'automatic' && + status.getIn(['quote_approval', 'current_user']) !== 'manual' + ) { + dispatch(showAlert({ message: messages.quoteErrorUnauthorized })); + } else { + dispatch(quoteCompose(status)); + } + }, +); + export const quoteComposeById = createAppThunk( (statusId: string, { dispatch, getState }) => { const status = getState().statuses.get(statusId); @@ -97,6 +148,6 @@ export const quoteComposeById = createAppThunk( export const quoteComposeCancel = createAction('compose/quoteComposeCancel'); -export const setQuotePolicy = createAction( +export const setComposeQuotePolicy = createAction( 'compose/setQuotePolicy', ); diff --git a/app/javascript/mastodon/api_types/quotes.ts b/app/javascript/mastodon/api_types/quotes.ts index 981c047c13..58cce1e257 100644 --- a/app/javascript/mastodon/api_types/quotes.ts +++ b/app/javascript/mastodon/api_types/quotes.ts @@ -2,6 +2,7 @@ import type { ApiStatusJSON } from './statuses'; export type ApiQuoteState = 'accepted' | 'pending' | 'revoked' | 'unauthorized'; export type ApiQuotePolicy = 'public' | 'followers' | 'nobody' | 'unknown'; +export type ApiUserQuotePolicy = 'automatic' | 'manual' | 'denied' | 'unknown'; interface ApiQuoteEmptyJSON { state: Exclude; @@ -25,7 +26,7 @@ export type ApiQuoteJSON = ApiQuoteAcceptedJSON | ApiQuoteEmptyJSON; export interface ApiQuotePolicyJSON { automatic: ApiQuotePolicy[]; manual: ApiQuotePolicy[]; - current_user: ApiQuotePolicy; + current_user: ApiUserQuotePolicy; } export function isQuotePolicy(policy: string): policy is ApiQuotePolicy { diff --git a/app/javascript/mastodon/api_types/statuses.ts b/app/javascript/mastodon/api_types/statuses.ts index 0127f6334b..d588950118 100644 --- a/app/javascript/mastodon/api_types/statuses.ts +++ b/app/javascript/mastodon/api_types/statuses.ts @@ -133,3 +133,9 @@ export interface ApiStatusSourceJSON { text: string; spoiler_text: string; } + +export function isStatusVisibility( + visibility: string, +): visibility is StatusVisibility { + return ['public', 'unlisted', 'private', 'direct'].includes(visibility); +} diff --git a/app/javascript/mastodon/components/dropdown_menu.tsx b/app/javascript/mastodon/components/dropdown_menu.tsx index d9c87e93a7..27af0ba6c0 100644 --- a/app/javascript/mastodon/components/dropdown_menu.tsx +++ b/app/javascript/mastodon/components/dropdown_menu.tsx @@ -41,13 +41,16 @@ import { IconButton } from './icon_button'; let id = 0; -type RenderItemFn = ( +export interface RenderItemFnHandlers { + onClick: React.MouseEventHandler; + onKeyUp: React.KeyboardEventHandler; +} + +export type RenderItemFn = ( item: Item, index: number, - handlers: { - onClick: (e: React.MouseEvent) => void; - onKeyUp: (e: React.KeyboardEvent) => void; - }, + handlers: RenderItemFnHandlers, + focusRefCallback?: (c: HTMLAnchorElement | HTMLButtonElement | null) => void, ) => React.ReactNode; type ItemClickFn = (item: Item, index: number) => void; @@ -173,7 +176,7 @@ export const DropdownMenu = ({ onItemClick(item, i); } else if (isActionItem(item)) { e.preventDefault(); - item.action(); + item.action(e); } }, [onClose, onItemClick, items], @@ -277,10 +280,15 @@ export const DropdownMenu = ({ })} > {items.map((option, i) => - renderItemMethod(option, i, { - onClick: handleItemClick, - onKeyUp: handleItemKeyUp, - }), + renderItemMethod( + option, + i, + { + onClick: handleItemClick, + onKeyUp: handleItemKeyUp, + }, + i === 0 ? handleFocusedItemRef : undefined, + ), )} )} @@ -307,7 +315,9 @@ interface DropdownProps { forceDropdown?: boolean; renderItem?: RenderItemFn; renderHeader?: RenderHeaderFn; - onOpen?: () => void; + onOpen?: // Must use a union type for the full function as a union with void is not allowed. + | ((event: React.MouseEvent | React.KeyboardEvent) => void) + | ((event: React.MouseEvent | React.KeyboardEvent) => boolean); onItemClick?: ItemClickFn; } @@ -376,7 +386,7 @@ export const Dropdown = ({ onItemClick(item, i); } else if (isActionItem(item)) { e.preventDefault(); - item.action(); + item.action(e); } }, [handleClose, onItemClick, items], @@ -389,7 +399,10 @@ export const Dropdown = ({ if (open) { handleClose(); } else { - onOpen?.(); + const allow = onOpen?.(e); + if (allow === false) { + return; + } if (prefetchAccountId) { dispatch(fetchRelationships([prefetchAccountId])); diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index fe66de9d16..7271e1d626 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -110,6 +110,7 @@ class Status extends ImmutablePureComponent { onToggleCollapsed: PropTypes.func, onTranslate: PropTypes.func, onInteractionModal: PropTypes.func, + onQuoteCancel: PropTypes.func, muted: PropTypes.bool, hidden: PropTypes.bool, unread: PropTypes.bool, @@ -583,7 +584,7 @@ class Status extends ImmutablePureComponent { - {this.props.contextType === 'compose' && isQuotedPost && ( + {isQuotedPost && !!this.props.onQuoteCancel && ( ( + 0} + /> + ), +} satisfies Meta; + +export default meta; + +function argsToStatus({ + reblogCount, + visibility, + quoteAllowed, + alreadyBoosted, +}: StoryProps) { + return statusFactoryState({ + reblogs_count: reblogCount, + visibility, + reblogged: alreadyBoosted, + quote_approval: { + automatic: [], + manual: [], + current_user: quoteAllowed ? 'automatic' : 'denied', + }, + }); +} + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Mine: Story = { + parameters: { + state: { + meta: { + me: '1', + }, + }, + }, +}; + +export const Legacy: Story = { + render: (args) => ( + 0} + /> + ), +}; diff --git a/app/javascript/mastodon/components/status/reblog_button.tsx b/app/javascript/mastodon/components/status/reblog_button.tsx new file mode 100644 index 0000000000..936d5506e7 --- /dev/null +++ b/app/javascript/mastodon/components/status/reblog_button.tsx @@ -0,0 +1,373 @@ +import { useCallback, useMemo } from 'react'; +import type { + FC, + KeyboardEvent, + MouseEvent, + MouseEventHandler, + SVGProps, +} from 'react'; + +import type { MessageDescriptor } from 'react-intl'; +import { defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import { quoteComposeById } from '@/mastodon/actions/compose_typed'; +import { toggleReblog } from '@/mastodon/actions/interactions'; +import { openModal } from '@/mastodon/actions/modal'; +import type { ActionMenuItem } from '@/mastodon/models/dropdown_menu'; +import type { Status, StatusVisibility } from '@/mastodon/models/status'; +import { + createAppSelector, + useAppDispatch, + useAppSelector, +} from '@/mastodon/store'; +import { isFeatureEnabled } from '@/mastodon/utils/environment'; +import FormatQuote from '@/material-icons/400-24px/format_quote.svg?react'; +import FormatQuoteOff from '@/material-icons/400-24px/format_quote_off.svg?react'; +import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; +import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; +import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; +import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; +import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; + +import type { RenderItemFn, RenderItemFnHandlers } from '../dropdown_menu'; +import { Dropdown } from '../dropdown_menu'; +import { Icon } from '../icon'; +import { IconButton } from '../icon_button'; + +const messages = defineMessages({ + all_disabled: { + id: 'status.all_disabled', + defaultMessage: 'Boosts and quotes are disabled', + }, + quote: { id: 'status.quote', defaultMessage: 'Quote' }, + quote_cannot: { + id: 'status.cannot_quote', + defaultMessage: 'Author has disabled quoting on this post', + }, + quote_private: { + id: 'status.quote_private', + defaultMessage: 'Private posts cannot be quoted', + }, + reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, + reblog_cancel: { + id: 'status.cancel_reblog_private', + defaultMessage: 'Unboost', + }, + reblog_private: { + id: 'status.reblog_private', + defaultMessage: 'Boost with original visibility', + }, + reblog_cannot: { + id: 'status.cannot_reblog', + defaultMessage: 'This post cannot be boosted', + }, +}); + +interface ReblogButtonProps { + status: Status; + counters?: boolean; +} + +export const StatusReblogButton: FC = ({ + status, + counters, +}) => { + const intl = useIntl(); + + const statusState = useAppSelector((state) => + selectStatusState(state, status), + ); + const { isLoggedIn, isReblogged, isReblogAllowed, isQuoteAllowed } = + statusState; + const { iconComponent } = useMemo( + () => reblogIconText(statusState), + [statusState], + ); + const disabled = !isQuoteAllowed && !isReblogAllowed; + + const dispatch = useAppDispatch(); + const statusId = status.get('id') as string; + const items: ActionMenuItem[] = useMemo( + () => [ + { + text: 'reblog', + action: (event) => { + if (isLoggedIn) { + dispatch(toggleReblog(statusId, event.shiftKey)); + } + }, + }, + { + text: 'quote', + action: () => { + if (isLoggedIn) { + dispatch(quoteComposeById(statusId)); + } + }, + }, + ], + [dispatch, isLoggedIn, statusId], + ); + + const handleDropdownOpen = useCallback( + (event: MouseEvent | KeyboardEvent) => { + if (!isLoggedIn) { + dispatch( + openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'reblog', + accountId: status.getIn(['account', 'id']), + url: status.get('uri'), + }, + }), + ); + } else if (event.shiftKey) { + dispatch(toggleReblog(status.get('id'), true)); + return false; + } + return true; + }, + [dispatch, isLoggedIn, status], + ); + + const renderMenuItem: RenderItemFn = useCallback( + (item, index, handlers, focusRefCallback) => ( + + ), + [status], + ); + + return ( + + + + ); +}; + +interface ReblogMenuItemProps { + status: Status; + item: ActionMenuItem; + index: number; + handlers: RenderItemFnHandlers; + focusRefCallback?: (c: HTMLAnchorElement | HTMLButtonElement | null) => void; +} + +const ReblogMenuItem: FC = ({ + status, + index, + item: { text }, + handlers, + focusRefCallback, +}) => { + const intl = useIntl(); + const statusState = useAppSelector((state) => + selectStatusState(state, status), + ); + const { title, meta, iconComponent, disabled } = useMemo( + () => + text === 'quote' + ? quoteIconText(statusState) + : reblogIconText(statusState), + [statusState, text], + ); + const active = useMemo( + () => text === 'reblog' && !!status.get('reblogged'), + [status, text], + ); + + return ( +
  • + +
  • + ); +}; + +// Legacy helpers + +// Switch between the legacy and new reblog button based on feature flag. +export const ReblogButton: FC = (props) => { + if (isFeatureEnabled('outgoing_quotes')) { + return ; + } + return ; +}; + +export const LegacyReblogButton: FC = ({ + status, + counters, +}) => { + const intl = useIntl(); + const statusState = useAppSelector((state) => + selectStatusState(state, status), + ); + + const { title, meta, iconComponent, disabled } = useMemo( + () => reblogIconText(statusState), + [statusState], + ); + + const dispatch = useAppDispatch(); + const handleClick: MouseEventHandler = useCallback( + (event) => { + if (statusState.isLoggedIn) { + dispatch(toggleReblog(status.get('id') as string, event.shiftKey)); + } else { + dispatch( + openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'reblog', + accountId: status.getIn(['account', 'id']), + url: status.get('uri'), + }, + }), + ); + } + }, + [dispatch, status, statusState.isLoggedIn], + ); + + return ( + + ); +}; + +// Helpers for copy and state for status. +const selectStatusState = createAppSelector( + [ + (state) => state.meta.get('me') as string | undefined, + (_, status: Status) => status, + ], + (userId, status) => { + const isPublic = ['public', 'unlisted'].includes( + status.get('visibility') as StatusVisibility, + ); + const isMineAndPrivate = + userId === status.getIn(['account', 'id']) && + status.get('visibility') === 'private'; + return { + isLoggedIn: !!userId, + isPublic, + isMine: userId === status.getIn(['account', 'id']), + isPrivateReblog: + userId === status.getIn(['account', 'id']) && + status.get('visibility') === 'private', + isReblogged: !!status.get('reblogged'), + isReblogAllowed: isPublic || isMineAndPrivate, + isQuoteAllowed: + status.getIn(['quote_approval', 'current_user']) === 'automatic' && + (isPublic || isMineAndPrivate), + }; + }, +); +type StatusState = ReturnType; + +interface IconText { + title: MessageDescriptor; + meta?: MessageDescriptor; + iconComponent: FC>; + disabled?: boolean; +} + +function reblogIconText({ + isPublic, + isPrivateReblog, + isReblogged, +}: StatusState): IconText { + if (isReblogged) { + return { + title: messages.reblog_cancel, + iconComponent: isPublic ? RepeatActiveIcon : RepeatPrivateActiveIcon, + }; + } + const iconText: IconText = { + title: messages.reblog, + iconComponent: RepeatIcon, + }; + + if (isPrivateReblog) { + iconText.meta = messages.reblog_private; + iconText.iconComponent = RepeatPrivateIcon; + } else if (!isPublic) { + iconText.meta = messages.reblog_cannot; + iconText.iconComponent = RepeatDisabledIcon; + iconText.disabled = true; + } + return iconText; +} + +function quoteIconText({ + isMine, + isQuoteAllowed, + isPublic, +}: StatusState): IconText { + const iconText: IconText = { + title: messages.quote, + iconComponent: FormatQuote, + }; + + if (!isQuoteAllowed || (!isPublic && !isMine)) { + iconText.meta = !isQuoteAllowed + ? messages.quote_cannot + : messages.quote_private; + iconText.iconComponent = FormatQuoteOff; + iconText.disabled = true; + } + return iconText; +} diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index 69ca9817a2..143407193b 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -2,7 +2,6 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; -import classNames from 'classnames'; import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -12,15 +11,10 @@ import { connect } from 'react-redux'; import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; -import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; -import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; -import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; -import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; -import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -30,6 +24,7 @@ import { me } from '../initial_state'; import { IconButton } from './icon_button'; import { isFeatureEnabled } from '../utils/environment'; +import { ReblogButton } from './status/reblog_button'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -43,10 +38,6 @@ const messages = defineMessages({ share: { id: 'status.share', defaultMessage: 'Share' }, more: { id: 'status.more', defaultMessage: 'More' }, replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' }, - reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, - reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' }, - cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, - cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, favourite: { id: 'status.favourite', defaultMessage: 'Favorite' }, removeFavourite: { id: 'status.remove_favourite', defaultMessage: 'Remove from favorites' }, bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' }, @@ -85,10 +76,9 @@ class StatusActionBar extends ImmutablePureComponent { identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, relationship: ImmutablePropTypes.record, - quotedAccountId: ImmutablePropTypes.string, + quotedAccountId: PropTypes.string, onReply: PropTypes.func, onFavourite: PropTypes.func, - onReblog: PropTypes.func, onDelete: PropTypes.func, onRevokeQuote: PropTypes.func, onQuotePolicyChange: PropTypes.func, @@ -152,16 +142,6 @@ class StatusActionBar extends ImmutablePureComponent { } }; - handleReblogClick = e => { - const { signedIn } = this.props.identity; - - if (signedIn) { - this.props.onReblog(this.props.status, e); - } else { - this.props.onInteractionModal('reblog', this.props.status); - } - }; - handleBookmarkClick = () => { this.props.onBookmark(this.props.status); }; @@ -377,25 +357,6 @@ class StatusActionBar extends ImmutablePureComponent { replyTitle = intl.formatMessage(messages.replyAll); } - const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private'; - - let reblogTitle, reblogIconComponent; - - if (status.get('reblogged')) { - reblogTitle = intl.formatMessage(messages.cancel_reblog_private); - reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon; - } else if (publicStatus) { - reblogTitle = intl.formatMessage(messages.reblog); - reblogIconComponent = RepeatIcon; - } else if (reblogPrivate) { - reblogTitle = intl.formatMessage(messages.reblog_private); - reblogIconComponent = RepeatPrivateIcon; - } else { - reblogTitle = intl.formatMessage(messages.cannot_reblog); - reblogIconComponent = RepeatDisabledIcon; - } - - const bookmarkTitle = intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark); const favouriteTitle = intl.formatMessage(status.get('favourited') ? messages.removeFavourite : messages.favourite); const isReply = status.get('in_reply_to_account_id') === status.getIn(['account', 'id']); @@ -406,7 +367,7 @@ class StatusActionBar extends ImmutablePureComponent {
    - +
    diff --git a/app/javascript/mastodon/components/status_quoted.tsx b/app/javascript/mastodon/components/status_quoted.tsx index 8d43ea1819..7f5f51977c 100644 --- a/app/javascript/mastodon/components/status_quoted.tsx +++ b/app/javascript/mastodon/components/status_quoted.tsx @@ -63,12 +63,21 @@ type GetStatusSelector = ( props: { id?: string | null; contextType?: string }, ) => Status | null; -export const QuotedStatus: React.FC<{ +interface QuotedStatusProps { quote: QuoteMap; contextType?: string; variant?: 'full' | 'link'; nestingLevel?: number; -}> = ({ quote, contextType, nestingLevel = 1, variant = 'full' }) => { + onQuoteCancel?: () => void; // Used for composer. +} + +export const QuotedStatus: React.FC = ({ + quote, + contextType, + nestingLevel = 1, + variant = 'full', + onQuoteCancel, +}) => { const dispatch = useAppDispatch(); const quotedStatusId = quote.get('quoted_status'); const quoteState = quote.get('state'); @@ -160,6 +169,7 @@ export const QuotedStatus: React.FC<{ id={quotedStatusId} contextType={contextType} avatarSize={32} + onQuoteCancel={onQuoteCancel} > {canRenderChildQuote && ( { const getStatus = makeGetStatus(); @@ -112,18 +112,18 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({ } }, - onQuoteCancel() { - if (contextType === 'compose') { - dispatch(quoteComposeCancel()); - } - }, - onRevokeQuote (status) { dispatch(openModal({ modalType: 'CONFIRM_REVOKE_QUOTE', modalProps: { statusId: status.get('id'), quotedStatusId: status.getIn(['quote', 'quoted_status']) }})); }, onQuotePolicyChange(status) { - dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId: status.get('id') } })); + const statusId = status.get('id'); + const handleChange = (_, quotePolicy) => { + dispatch( + setStatusQuotePolicy({ policy: quotePolicy, statusId }), + ); + } + dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId, onChange: handleChange } })); }, onEdit (status) { diff --git a/app/javascript/mastodon/features/compose/components/compose_form.jsx b/app/javascript/mastodon/features/compose/components/compose_form.jsx index 08d111b064..ac0a496939 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.jsx +++ b/app/javascript/mastodon/features/compose/components/compose_form.jsx @@ -15,10 +15,8 @@ import { missingAltTextModal } from 'mastodon/initial_state'; import AutosuggestInput from 'mastodon/components/autosuggest_input'; import AutosuggestTextarea from 'mastodon/components/autosuggest_textarea'; import { Button } from 'mastodon/components/button'; -import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import PollButtonContainer from '../containers/poll_button_container'; -import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import SpoilerButtonContainer from '../containers/spoiler_button_container'; import UploadButtonContainer from '../containers/upload_button_container'; import { countableText } from '../util/counter'; @@ -32,6 +30,7 @@ import { ReplyIndicator } from './reply_indicator'; import { UploadForm } from './upload_form'; import { Warning } from './warning'; import { ComposeQuotedStatus } from './quoted_post'; +import { VisibilityButton } from './visibility_button'; const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d'; @@ -260,7 +259,7 @@ class ComposeForm extends ImmutablePureComponent {
    - +
    diff --git a/app/javascript/mastodon/features/compose/components/quoted_post.tsx b/app/javascript/mastodon/features/compose/components/quoted_post.tsx index ee12e4ae75..335e7ce610 100644 --- a/app/javascript/mastodon/features/compose/components/quoted_post.tsx +++ b/app/javascript/mastodon/features/compose/components/quoted_post.tsx @@ -1,15 +1,17 @@ -import { useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import type { FC } from 'react'; import { Map } from 'immutable'; +import { quoteComposeCancel } from '@/mastodon/actions/compose_typed'; import { QuotedStatus } from '@/mastodon/components/status_quoted'; -import { useAppSelector } from '@/mastodon/store'; +import { useAppDispatch, useAppSelector } from '@/mastodon/store'; export const ComposeQuotedStatus: FC = () => { const quotedStatusId = useAppSelector( (state) => state.compose.get('quoted_status_id') as string | null, ); + const isEditing = useAppSelector((state) => !!state.compose.get('id')); const quote = useMemo( () => quotedStatusId @@ -20,8 +22,17 @@ export const ComposeQuotedStatus: FC = () => { : null, [quotedStatusId], ); + const dispatch = useAppDispatch(); + const handleQuoteCancel = useCallback(() => { + dispatch(quoteComposeCancel()); + }, [dispatch]); if (!quote) { return null; } - return ; + return ( + + ); }; diff --git a/app/javascript/mastodon/features/compose/components/visibility_button.tsx b/app/javascript/mastodon/features/compose/components/visibility_button.tsx new file mode 100644 index 0000000000..203a569bcc --- /dev/null +++ b/app/javascript/mastodon/features/compose/components/visibility_button.tsx @@ -0,0 +1,148 @@ +import { useCallback, useMemo } from 'react'; +import type { FC } from 'react'; + +import { defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import { changeComposeVisibility } from '@/mastodon/actions/compose'; +import { setComposeQuotePolicy } from '@/mastodon/actions/compose_typed'; +import { openModal } from '@/mastodon/actions/modal'; +import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes'; +import type { StatusVisibility } from '@/mastodon/api_types/statuses'; +import { Icon } from '@/mastodon/components/icon'; +import { useAppSelector, useAppDispatch } from '@/mastodon/store'; +import { isFeatureEnabled } from '@/mastodon/utils/environment'; +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; +import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react'; + +import type { VisibilityModalCallback } from '../../ui/components/visibility_modal'; +import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; + +import { messages as privacyMessages } from './privacy_dropdown'; + +const messages = defineMessages({ + anyone_quote: { + id: 'privacy.quote.anyone', + defaultMessage: '{visibility}, anyone can quote', + }, + limited_quote: { + id: 'privacy.quote.limited', + defaultMessage: '{visibility}, quotes limited', + }, + disabled_quote: { + id: 'privacy.quote.disabled', + defaultMessage: '{visibility}, quotes disabled', + }, +}); + +interface PrivacyDropdownProps { + disabled?: boolean; +} + +export const VisibilityButton: FC = (props) => { + if (!isFeatureEnabled('outgoing_quotes')) { + return ; + } + return ; +}; + +const visibilityOptions = { + public: { + icon: 'globe', + iconComponent: PublicIcon, + value: 'public', + text: privacyMessages.public_short, + }, + unlisted: { + icon: 'unlock', + iconComponent: QuietTimeIcon, + value: 'unlisted', + text: privacyMessages.unlisted_short, + }, + private: { + icon: 'lock', + iconComponent: LockIcon, + value: 'private', + text: privacyMessages.private_short, + }, + direct: { + icon: 'at', + iconComponent: AlternateEmailIcon, + value: 'direct', + text: privacyMessages.direct_short, + }, +}; + +const PrivacyModalButton: FC = ({ disabled = false }) => { + const intl = useIntl(); + + const { visibility, quotePolicy } = useAppSelector((state) => ({ + visibility: state.compose.get('privacy') as StatusVisibility, + quotePolicy: state.compose.get('quote_policy') as ApiQuotePolicy, + })); + + const { icon, iconComponent } = useMemo(() => { + const option = visibilityOptions[visibility]; + return { icon: option.icon, iconComponent: option.iconComponent }; + }, [visibility]); + const text = useMemo(() => { + const visibilityText = intl.formatMessage( + visibilityOptions[visibility].text, + ); + if (visibility === 'private' || visibility === 'direct') { + return visibilityText; + } + if (quotePolicy === 'nobody') { + return intl.formatMessage(messages.disabled_quote, { + visibility: visibilityText, + }); + } + if (quotePolicy !== 'public') { + return intl.formatMessage(messages.limited_quote, { + visibility: visibilityText, + }); + } + return intl.formatMessage(messages.anyone_quote, { + visibility: visibilityText, + }); + }, [quotePolicy, visibility, intl]); + + const dispatch = useAppDispatch(); + + const handleChange: VisibilityModalCallback = useCallback( + (newVisibility, newQuotePolicy) => { + if (newVisibility !== visibility) { + dispatch(changeComposeVisibility(newVisibility)); + } + if (newQuotePolicy !== quotePolicy) { + dispatch(setComposeQuotePolicy(newQuotePolicy)); + } + }, + [dispatch, quotePolicy, visibility], + ); + + const handleOpen = useCallback(() => { + dispatch( + openModal({ + modalType: 'COMPOSE_PRIVACY', + modalProps: { onChange: handleChange }, + }), + ); + }, [dispatch, handleChange]); + + return ( + + ); +}; diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx index 15f193510d..f5dea36cc6 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.jsx +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -3,23 +3,16 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; -import classNames from 'classnames'; - import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react'; import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react'; import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; -import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; -import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; -import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; -import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; -import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; @@ -27,6 +20,7 @@ import { IconButton } from '../../../components/icon_button'; import { Dropdown } from 'mastodon/components/dropdown_menu'; import { me } from '../../../initial_state'; import { isFeatureEnabled } from '@/mastodon/utils/environment'; +import { ReblogButton } from '@/mastodon/components/status/reblog_button'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -35,10 +29,6 @@ const messages = defineMessages({ direct: { id: 'status.direct', defaultMessage: 'Privately mention @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, reply: { id: 'status.reply', defaultMessage: 'Reply' }, - reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, - reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' }, - cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, - cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, favourite: { id: 'status.favourite', defaultMessage: 'Favorite' }, removeFavourite: { id: 'status.remove_favourite', defaultMessage: 'Remove from favorites' }, bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' }, @@ -313,31 +303,15 @@ class ActionBar extends PureComponent { replyIconComponent = ReplyAllIcon; } - const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private'; - - let reblogTitle, reblogIconComponent; - - if (status.get('reblogged')) { - reblogTitle = intl.formatMessage(messages.cancel_reblog_private); - reblogIconComponent = publicStatus ? RepeatActiveIcon : RepeatPrivateActiveIcon; - } else if (publicStatus) { - reblogTitle = intl.formatMessage(messages.reblog); - reblogIconComponent = RepeatIcon; - } else if (reblogPrivate) { - reblogTitle = intl.formatMessage(messages.reblog_private); - reblogIconComponent = RepeatPrivateIcon; - } else { - reblogTitle = intl.formatMessage(messages.cannot_reblog); - reblogIconComponent = RepeatDisabledIcon; - } - const bookmarkTitle = intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark); const favouriteTitle = intl.formatMessage(status.get('favourited') ? messages.removeFavourite : messages.favourite); return (
    -
    +
    + +
    diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 3cfda6e837..36a67226f8 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; @@ -15,7 +15,6 @@ import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?reac import { Hotkeys } from 'mastodon/components/hotkeys'; import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; -import { TimelineHint } from 'mastodon/components/timeline_hint'; import ScrollContainer from 'mastodon/containers/scroll_container'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; @@ -57,6 +56,7 @@ import { translateStatus, undoStatusTranslation, } from '../../actions/statuses'; +import { setStatusQuotePolicy } from '../../actions/statuses_typed'; import ColumnHeader from '../../components/column_header'; import { textForScreenReader, defaultMediaVisibility } from '../../components/status'; import { StatusQuoteManager } from '../../components/status_quoted'; @@ -266,8 +266,14 @@ class Status extends ImmutablePureComponent { }; handleQuotePolicyChange = (status) => { + const statusId = status.get('id'); const { dispatch } = this.props; - dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId: status.get('id') } })); + const handleChange = (_, quotePolicy) => { + dispatch( + setStatusQuotePolicy({ policy: quotePolicy, statusId }), + ); + } + dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId, onChange: handleChange } })); }; handleEditClick = (status) => { diff --git a/app/javascript/mastodon/features/ui/components/visibility_modal.tsx b/app/javascript/mastodon/features/ui/components/visibility_modal.tsx index 82a1a482a3..8e681ea5c5 100644 --- a/app/javascript/mastodon/features/ui/components/visibility_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/visibility_modal.tsx @@ -1,25 +1,31 @@ -import { forwardRef, useCallback, useId, useMemo } from 'react'; +import { + forwardRef, + useCallback, + useId, + useImperativeHandle, + useMemo, + useState, +} from 'react'; import type { FC } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import classNames from 'classnames'; -import { changeComposeVisibility } from '@/mastodon/actions/compose'; -import { setStatusQuotePolicy } from '@/mastodon/actions/statuses_typed'; import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes'; import { isQuotePolicy } from '@/mastodon/api_types/quotes'; +import { isStatusVisibility } from '@/mastodon/api_types/statuses'; import type { StatusVisibility } from '@/mastodon/api_types/statuses'; import { Dropdown } from '@/mastodon/components/dropdown'; import type { SelectItem } from '@/mastodon/components/dropdown_selector'; import { IconButton } from '@/mastodon/components/icon_button'; import { messages as privacyMessages } from '@/mastodon/features/compose/components/privacy_dropdown'; -import { - createAppSelector, - useAppDispatch, - useAppSelector, -} from '@/mastodon/store'; +import { createAppSelector, useAppSelector } from '@/mastodon/store'; +import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import LockIcon from '@/material-icons/400-24px/lock.svg?react'; +import PublicIcon from '@/material-icons/400-24px/public.svg?react'; +import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react'; import type { BaseConfirmationModalProps } from './confirmation_modals/confirmation_modal'; @@ -43,13 +49,26 @@ const messages = defineMessages({ }, }); +export type VisibilityModalCallback = ( + visibility: StatusVisibility, + quotePolicy: ApiQuotePolicy, +) => void; + interface VisibilityModalProps extends BaseConfirmationModalProps { - statusId: string; + statusId?: string; + onChange: VisibilityModalCallback; } const selectStatusPolicy = createAppSelector( - [(state) => state.statuses, (_state, statusId: string) => statusId], - (statuses, statusId) => { + [ + (state) => state.statuses, + (_state, statusId?: string) => statusId, + (state) => state.compose.get('quote_policy') as ApiQuotePolicy, + ], + (statuses, statusId, composeQuotePolicy) => { + if (!statusId) { + return composeQuotePolicy; + } const status = statuses.get(statusId); if (!status) { return 'public'; @@ -77,24 +96,25 @@ const selectStatusPolicy = createAppSelector( ); export const VisibilityModal: FC = forwardRef( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ({ onClose, statusId }, ref) => { + ({ onClose, onChange, statusId }, ref) => { const intl = useIntl(); - const currentVisibility = useAppSelector( - (state) => - (state.statuses.getIn([statusId, 'visibility'], 'public') as - | StatusVisibility - | undefined) ?? 'public', + const currentVisibility = useAppSelector((state) => + statusId + ? ((state.statuses.getIn([statusId, 'visibility'], 'public') as + | StatusVisibility + | undefined) ?? 'public') + : (state.compose.get('privacy') as StatusVisibility), ); const currentQuotePolicy = useAppSelector((state) => selectStatusPolicy(state, statusId), ); + + const [visibility, setVisibility] = useState(currentVisibility); + const [quotePolicy, setQuotePolicy] = useState(currentQuotePolicy); + + const disableVisibility = !!statusId; const disableQuotePolicy = - currentVisibility === 'private' || currentVisibility === 'direct'; - const isSaving = useAppSelector( - (state) => - state.statuses.getIn([statusId, 'isSavingQuotePolicy']) === true, - ); + visibility === 'private' || visibility === 'direct'; const visibilityItems = useMemo[]>( () => [ @@ -102,21 +122,30 @@ export const VisibilityModal: FC = forwardRef( value: 'public', text: intl.formatMessage(privacyMessages.public_short), meta: intl.formatMessage(privacyMessages.public_long), + icon: 'globe', + iconComponent: PublicIcon, }, { value: 'unlisted', text: intl.formatMessage(privacyMessages.unlisted_short), meta: intl.formatMessage(privacyMessages.unlisted_long), + extra: intl.formatMessage(privacyMessages.unlisted_extra), + icon: 'unlock', + iconComponent: QuietTimeIcon, }, { value: 'private', text: intl.formatMessage(privacyMessages.private_short), meta: intl.formatMessage(privacyMessages.private_long), + icon: 'lock', + iconComponent: LockIcon, }, { value: 'direct', text: intl.formatMessage(privacyMessages.direct_short), meta: intl.formatMessage(privacyMessages.direct_long), + icon: 'at', + iconComponent: AlternateEmailIcon, }, ], [intl], @@ -133,24 +162,27 @@ export const VisibilityModal: FC = forwardRef( [intl], ); - const dispatch = useAppDispatch(); - const handleVisibilityChange = useCallback( - (value: string) => { - // Published statuses cannot change visibility. - if (statusId) { - return; - } - dispatch(changeComposeVisibility(value)); - }, - [dispatch, statusId], - ); - const handleQuotePolicyChange = useCallback( - (value: string) => { - if (isQuotePolicy(value) && !disableQuotePolicy) { - void dispatch(setStatusQuotePolicy({ policy: value, statusId })); - } - }, - [disableQuotePolicy, dispatch, statusId], + const handleVisibilityChange = useCallback((value: string) => { + if (isStatusVisibility(value)) { + setVisibility(value); + } + }, []); + const handleQuotePolicyChange = useCallback((value: string) => { + if (isQuotePolicy(value)) { + setQuotePolicy(value); + } + }, []); + + // Save on close + useImperativeHandle( + ref, + () => ({ + getCloseConfirmationMessage() { + onChange(visibility, quotePolicy); + return null; + }, + }), + [onChange, quotePolicy, visibility], ); const privacyDropdownId = useId(); @@ -192,7 +224,7 @@ export const VisibilityModal: FC = forwardRef(
    diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 29ad29c4f5..c6326096d0 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -836,22 +836,24 @@ "search_results.see_all": "Праглядзець усе", "search_results.statuses": "Допісы", "search_results.title": "Шукаць \"{q}\"", - "server_banner.about_active_users": "Людзі, якія карыстаюцца гэтым сервера на працягу апошніх 30 дзён (Штомесячна Актыўныя Карыстальнікі)", + "server_banner.about_active_users": "Людзі, якія карысталіся гэтым серверам на працягу апошніх 30 дзён (штомесячна актыўныя карыстальнікі)", "server_banner.active_users": "актыўныя карыстальнікі", "server_banner.administered_by": "Адміністратар:", "server_banner.is_one_of_many": "{domain} - гэта адзін з многіх незалежных сервераў Mastodon, які Вы можаце выкарыстоўваць для ўдзелу ў федэральным сусвеце.", "server_banner.server_stats": "Статыстыка сервера:", "sign_in_banner.create_account": "Стварыць уліковы запіс", "sign_in_banner.follow_anyone": "Падпісвайцеся на каго захочаце ва ўсім федэральным сусвеце і глядзіце ўсё ў храналагічным парадку. Ніякіх алгарытмаў, рэкламы або клікбэйту.", - "sign_in_banner.mastodon_is": "Mastodon - лепшы спосаб быць у курсе ўсяго, што адбываецца.", + "sign_in_banner.mastodon_is": "Mastodon - найлепшы спосаб быць у курсе ўсяго, што адбываецца.", "sign_in_banner.sign_in": "Увайсці", "sign_in_banner.sso_redirect": "Уваход ці рэгістрацыя", "status.admin_account": "Адкрыць інтэрфейс мадэратара для @{name}", "status.admin_domain": "Адкрыць інтэрфейс мадэратара для {domain}", "status.admin_status": "Адкрыць гэты допіс у інтэрфейсе мадэрацыі", + "status.all_disabled": "Пашырэнні і цытаванні адключаны", "status.block": "Заблакаваць @{name}", "status.bookmark": "Дадаць закладку", "status.cancel_reblog_private": "Прыбраць", + "status.cannot_quote": "Аўтар допісу адключыў цытаванне", "status.cannot_reblog": "Гэты допіс нельга пашырыць", "status.context.load_new_replies": "Даступныя новыя адказы", "status.context.loading": "Правяраюцца новыя адказы", @@ -880,6 +882,8 @@ "status.mute_conversation": "Ігнараваць размову", "status.open": "Разгарнуць гэты допіс", "status.pin": "Замацаваць у профілі", + "status.quote": "Цытаваць", + "status.quote.cancel": "Адмяніць цытаванне", "status.quote_error.filtered": "Схавана адным з Вашых фільтраў", "status.quote_error.not_available": "Допіс недаступны", "status.quote_error.pending_approval": "Допіс чакае пацвярджэння", @@ -887,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "Цытаваны допіс чакае пацвярджэння? Захоўвайце спакой", "status.quote_policy_change": "Змяніць, хто можа цытаваць", "status.quote_post_author": "Цытаваў допіс @{name}", + "status.quote_private": "Прыватныя допісы нельга цытаваць", "status.read_more": "Чытаць болей", "status.reblog": "Пашырыць", "status.reblog_private": "Пашырыць з першапачатковай бачнасцю", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 0f87dacc1b..f090b1bb0d 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -848,9 +848,11 @@ "status.admin_account": "Obre la interfície de moderació per a @{name}", "status.admin_domain": "Obre la interfície de moderació per a @{domain}", "status.admin_status": "Obre aquest tut a la interfície de moderació", + "status.all_disabled": "S'han deshabilitat impulsos i cites", "status.block": "Bloca @{name}", "status.bookmark": "Marca", "status.cancel_reblog_private": "Desfés l'impuls", + "status.cannot_quote": "L'autor no permet que se citi aquesta publicació", "status.cannot_reblog": "No es pot impulsar aquest tut", "status.context.load_new_replies": "Hi ha respostes noves", "status.context.loading": "Comprovació de més respostes", @@ -879,6 +881,7 @@ "status.mute_conversation": "Silencia la conversa", "status.open": "Amplia el tut", "status.pin": "Fixa en el perfil", + "status.quote": "Cita", "status.quote.cancel": "Canceŀlar la citació", "status.quote_error.filtered": "No es mostra a causa d'un dels vostres filtres", "status.quote_error.not_available": "Publicació no disponible", @@ -887,6 +890,7 @@ "status.quote_error.pending_approval_popout.title": "Publicació pendent? Mantinguem la calma", "status.quote_policy_change": "Canvieu qui us pot citar", "status.quote_post_author": "S'ha citat una publicació de @{name}", + "status.quote_private": "No es poden citar les publicacions privades", "status.read_more": "Més informació", "status.reblog": "Impulsa", "status.reblog_private": "Impulsa amb la visibilitat original", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index dc314244a3..d69b66aa4d 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -849,9 +849,11 @@ "status.admin_account": "Otevřít moderátorské rozhraní pro @{name}", "status.admin_domain": "Otevřít moderátorské rozhraní pro {domain}", "status.admin_status": "Otevřít tento příspěvek v moderátorském rozhraní", + "status.all_disabled": "Boosty a citace jsou zakázány", "status.block": "Blokovat @{name}", "status.bookmark": "Přidat do záložek", "status.cancel_reblog_private": "Zrušit boostnutí", + "status.cannot_quote": "Autor zakázal citování tohoto příspěvku", "status.cannot_reblog": "Tento příspěvek nemůže být boostnutý", "status.context.load_new_replies": "K dispozici jsou nové odpovědi", "status.context.loading": "Hledání dalších odpovědí", @@ -880,6 +882,8 @@ "status.mute_conversation": "Skrýt konverzaci", "status.open": "Rozbalit tento příspěvek", "status.pin": "Připnout na profil", + "status.quote": "Citovat", + "status.quote.cancel": "Zrušit citování", "status.quote_error.filtered": "Skryté kvůli jednomu z vašich filtrů", "status.quote_error.not_available": "Příspěvek není dostupný", "status.quote_error.pending_approval": "Příspěvek čeká na schválení", @@ -887,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "Příspěvek čeká na schválení? Buďte klidní", "status.quote_policy_change": "Změňte, kdo může citovat", "status.quote_post_author": "Citovali příspěvek od @{name}", + "status.quote_private": "Soukromé příspěvky nelze citovat", "status.read_more": "Číst více", "status.reblog": "Boostnout", "status.reblog_private": "Boostnout s původní viditelností", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 16d31652a9..4ea43aa06a 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -849,9 +849,11 @@ "status.admin_account": "@{name} moderieren", "status.admin_domain": "{domain} moderieren", "status.admin_status": "Beitrag moderieren", + "status.all_disabled": "Teilen und Zitieren von Beiträgen ist deaktiviert", "status.block": "@{name} blockieren", "status.bookmark": "Lesezeichen setzen", "status.cancel_reblog_private": "Beitrag nicht mehr teilen", + "status.cannot_quote": "Autor*in hat das Zitieren dieses Beitrags deaktiviert", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", "status.context.load_new_replies": "Neue Antworten verfügbar", "status.context.loading": "Weitere Antworten werden abgerufen", @@ -880,6 +882,7 @@ "status.mute_conversation": "Unterhaltung stummschalten", "status.open": "Beitrag öffnen", "status.pin": "Im Profil anheften", + "status.quote": "Zitieren", "status.quote.cancel": "Zitat abbrechen", "status.quote_error.filtered": "Ausgeblendet wegen eines deiner Filter", "status.quote_error.not_available": "Beitrag nicht verfügbar", @@ -888,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "Zitierter Beitrag noch nicht freigegeben? Immer mit der Ruhe", "status.quote_policy_change": "Ändern, wer zitieren darf", "status.quote_post_author": "Zitierte einen Beitrag von @{name}", + "status.quote_private": "Private Beiträge können nicht zitiert werden", "status.read_more": "Gesamten Beitrag anschauen", "status.reblog": "Teilen", "status.reblog_private": "Mit der ursprünglichen Zielgruppe teilen", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 6f25078bf6..787c6a400c 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -849,9 +849,11 @@ "status.admin_account": "Άνοιγμα διεπαφής συντονισμού για τον/την @{name}", "status.admin_domain": "Άνοιγμα λειτουργίας διαμεσολάβησης για {domain}", "status.admin_status": "Άνοιγμα αυτής της ανάρτησης σε διεπαφή συντονισμού", + "status.all_disabled": "Ενισχύσεις και παραθέσεις είναι απενεργοποιημένες", "status.block": "Αποκλεισμός @{name}", "status.bookmark": "Σελιδοδείκτης", "status.cancel_reblog_private": "Ακύρωση ενίσχυσης", + "status.cannot_quote": "Ο συντάκτης έχει απενεργοποιήσει την παράθεση σε αυτή την ανάρτηση", "status.cannot_reblog": "Αυτή η ανάρτηση δεν μπορεί να ενισχυθεί", "status.context.load_new_replies": "Νέες απαντήσεις διαθέσιμες", "status.context.loading": "Γίνεται έλεγχος για περισσότερες απαντήσεις", @@ -880,6 +882,8 @@ "status.mute_conversation": "Σίγαση συνομιλίας", "status.open": "Επέκταση ανάρτησης", "status.pin": "Καρφίτσωσε στο προφίλ", + "status.quote": "Παράθεση", + "status.quote.cancel": "Ακύρωση παράθεσης", "status.quote_error.filtered": "Κρυφό λόγω ενός από τα φίλτρα σου", "status.quote_error.not_available": "Ανάρτηση μη διαθέσιμη", "status.quote_error.pending_approval": "Ανάρτηση σε αναμονή", @@ -887,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "Παράθεση σε εκκρεμότητα; Μείνετε ψύχραιμοι", "status.quote_policy_change": "Αλλάξτε ποιός μπορεί να κάνει παράθεση", "status.quote_post_author": "Παρατίθεται μια ανάρτηση από @{name}", + "status.quote_private": "Ιδιωτικές αναρτήσεις δεν μπορούν να παρατεθούν", "status.read_more": "Διάβασε περισότερα", "status.reblog": "Ενίσχυση", "status.reblog_private": "Ενίσχυση με αρχική ορατότητα", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 6362d0b462..01eb56c72c 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -738,11 +738,18 @@ "privacy.private.short": "Followers", "privacy.public.long": "Anyone on and off Mastodon", "privacy.public.short": "Public", + "privacy.quote.anyone": "{visibility}, anyone can quote", + "privacy.quote.disabled": "{visibility}, quotes disabled", + "privacy.quote.limited": "{visibility}, quotes limited", "privacy.unlisted.additional": "This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.", "privacy.unlisted.long": "Fewer algorithmic fanfares", "privacy.unlisted.short": "Quiet public", "privacy_policy.last_updated": "Last updated {date}", "privacy_policy.title": "Privacy Policy", + "quote_error.poll": "Quoting is not allowed with polls.", + "quote_error.quote": "Only one quote at a time is allowed.", + "quote_error.unauthorized": "You are not authorized to quote this post.", + "quote_error.upload": "Quoting is not allowed with media attachments.", "recommended": "Recommended", "refresh": "Refresh", "regeneration_indicator.please_stand_by": "Please stand by.", @@ -849,9 +856,11 @@ "status.admin_account": "Open moderation interface for @{name}", "status.admin_domain": "Open moderation interface for {domain}", "status.admin_status": "Open this post in the moderation interface", + "status.all_disabled": "Boosts and quotes are disabled", "status.block": "Block @{name}", "status.bookmark": "Bookmark", "status.cancel_reblog_private": "Unboost", + "status.cannot_quote": "Author has disabled quoting on this post", "status.cannot_reblog": "This post cannot be boosted", "status.context.load_new_replies": "New replies available", "status.context.loading": "Checking for more replies", @@ -880,6 +889,7 @@ "status.mute_conversation": "Mute conversation", "status.open": "Expand this post", "status.pin": "Pin on profile", + "status.quote": "Quote", "status.quote.cancel": "Cancel quote", "status.quote_error.filtered": "Hidden due to one of your filters", "status.quote_error.not_available": "Post unavailable", @@ -888,6 +898,7 @@ "status.quote_error.pending_approval_popout.title": "Pending quote? Remain calm", "status.quote_policy_change": "Change who can quote", "status.quote_post_author": "Quoted a post by @{name}", + "status.quote_private": "Private posts cannot be quoted", "status.read_more": "Read more", "status.reblog": "Boost", "status.reblog_private": "Boost with original visibility", @@ -940,6 +951,7 @@ "upload_button.label": "Add images, a video or an audio file", "upload_error.limit": "File upload limit exceeded.", "upload_error.poll": "File upload not allowed with polls.", + "upload_error.quote": "File upload not allowed with quotes.", "upload_form.drag_and_drop.instructions": "To pick up a media attachment, press space or enter. While dragging, use the arrow keys to move the media attachment in any given direction. Press space or enter again to drop the media attachment in its new position, or press escape to cancel.", "upload_form.drag_and_drop.on_drag_cancel": "Dragging was cancelled. Media attachment {item} was dropped.", "upload_form.drag_and_drop.on_drag_end": "Media attachment {item} was dropped.", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index c93cacf3aa..1468f742d9 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -568,6 +568,8 @@ "navigation_bar.preferences": "Preferoj", "navigation_bar.privacy_and_reach": "Privateco kaj atingo", "navigation_bar.search": "Serĉi", + "navigation_panel.expand_followed_tags": "Malfermi la menuon de sekvataj haŝetikedoj", + "navigation_panel.expand_lists": "Malfermi la listmenuon", "not_signed_in_indicator.not_signed_in": "Necesas saluti por aliri tiun rimedon.", "notification.admin.report": "{name} raportis {target}", "notification.admin.report_account": "{name} raportis {count, plural, one {afiŝon} other {# afiŝojn}} de {target} por {category}", @@ -837,6 +839,7 @@ "status.block": "Bloki @{name}", "status.bookmark": "Aldoni al la legosignoj", "status.cancel_reblog_private": "Ne plu diskonigi", + "status.cannot_quote": "La aŭtoro malŝaltis citadon en ĉi tiu afiŝo", "status.cannot_reblog": "Ĉi tiun afiŝon ne eblas diskonigi", "status.context.load_new_replies": "Disponeblaj novaj respondoj", "status.context.loading": "Serĉante pliajn respondojn", @@ -865,9 +868,16 @@ "status.mute_conversation": "Silentigi konversacion", "status.open": "Pligrandigu ĉi tiun afiŝon", "status.pin": "Alpingli al la profilo", + "status.quote": "Citaĵo", + "status.quote.cancel": "Nuligi citaĵon", + "status.quote_error.filtered": "Kaŝita pro unu el viaj filtriloj", "status.quote_error.not_available": "Afiŝo ne disponebla", "status.quote_error.pending_approval": "Pritraktata afiŝo", + "status.quote_error.pending_approval_popout.body": "Citaĵoj diskonigitaj tra la Fediverso povas bezoni tempon por montriĝi, ĉar malsamaj serviloj havas malsamajn protokolojn.", "status.quote_error.pending_approval_popout.title": "Ĉu pritraktata citaĵo? Restu trankvila", + "status.quote_policy_change": "Ŝanĝi kiu povas citi", + "status.quote_post_author": "Citis afiŝon de @{name}", + "status.quote_private": "Privataj afiŝoj ne povas esti cititaj", "status.read_more": "Legi pli", "status.reblog": "Diskonigi", "status.reblog_private": "Diskonigi kun la sama videbleco", @@ -882,6 +892,7 @@ "status.reply": "Respondi", "status.replyAll": "Respondi al la fadeno", "status.report": "Raporti @{name}", + "status.revoke_quote": "Forigu mian afiŝon el la afiŝo de @{name}", "status.sensitive_warning": "Tikla enhavo", "status.share": "Kundividi", "status.show_less_all": "Montri malpli ĉiun", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index c137bdfa24..31181f60b1 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -849,9 +849,11 @@ "status.admin_account": "Abrir interface de moderación para @{name}", "status.admin_domain": "Abrir interface de moderación para {domain}", "status.admin_status": "Abrir este mensaje en la interface de moderación", + "status.all_disabled": "Las adhesiones y citas están deshabilitadas", "status.block": "Bloquear a @{name}", "status.bookmark": "Marcar", "status.cancel_reblog_private": "Quitar adhesión", + "status.cannot_quote": "El autor deshabilitó las citas en este mensaje", "status.cannot_reblog": "No se puede adherir a este mensaje", "status.context.load_new_replies": "Hay nuevas respuestas", "status.context.loading": "Buscando más respuestas", @@ -880,6 +882,7 @@ "status.mute_conversation": "Silenciar conversación", "status.open": "Expandir este mensaje", "status.pin": "Fijar en el perfil", + "status.quote": "Citar", "status.quote.cancel": "Cancelar cita", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", "status.quote_error.not_available": "Mensaje no disponible", @@ -888,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "¿Cita pendiente? Esperá un momento", "status.quote_policy_change": "Cambiá quién puede citar", "status.quote_post_author": "Se citó un mensaje de @{name}", + "status.quote_private": "No se pueden citar los mensajes privados", "status.read_more": "Leé más", "status.reblog": "Adherir", "status.reblog_private": "Adherir a la audiencia original", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index c35b5b34ef..5dcb113351 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -849,9 +849,11 @@ "status.admin_account": "Abrir interfaz de moderación para @{name}", "status.admin_domain": "Abrir interfaz de moderación para {domain}", "status.admin_status": "Abrir este estado en la interfaz de moderación", + "status.all_disabled": "Los impulsos y las citas están desactivadas", "status.block": "Bloquear a @{name}", "status.bookmark": "Añadir marcador", "status.cancel_reblog_private": "Deshacer impulso", + "status.cannot_quote": "El autor ha desactivado las citas de esta publicación", "status.cannot_reblog": "Esta publicación no puede ser impulsada", "status.context.load_new_replies": "Nuevas respuestas disponibles", "status.context.loading": "Comprobando si hay más respuestas", @@ -880,6 +882,7 @@ "status.mute_conversation": "Silenciar conversación", "status.open": "Expandir estado", "status.pin": "Fijar", + "status.quote": "Cita", "status.quote.cancel": "Cancelar cita", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", "status.quote_error.not_available": "Publicación no disponible", @@ -888,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "¿Cita pendiente? Mantén la calma", "status.quote_policy_change": "Cambia quién puede citarte", "status.quote_post_author": "Ha citado una publicación de @{name}", + "status.quote_private": "Las publicaciones privadas no pueden citarse", "status.read_more": "Leer más", "status.reblog": "Impulsar", "status.reblog_private": "Implusar a la audiencia original", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 6762bb546f..0c50e5fc6a 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -849,9 +849,11 @@ "status.admin_account": "Abrir interfaz de moderación para @{name}", "status.admin_domain": "Abrir interfaz de moderación para {domain}", "status.admin_status": "Abrir esta publicación en la interfaz de moderación", + "status.all_disabled": "Los impulsos y las citas están deshabilitados", "status.block": "Bloquear a @{name}", "status.bookmark": "Añadir marcador", "status.cancel_reblog_private": "Deshacer impulso", + "status.cannot_quote": "El autor ha deshabilitado las citas en esta publicación", "status.cannot_reblog": "Esta publicación no se puede impulsar", "status.context.load_new_replies": "Hay nuevas respuestas", "status.context.loading": "Buscando más respuestas", @@ -880,6 +882,7 @@ "status.mute_conversation": "Silenciar conversación", "status.open": "Expandir publicación", "status.pin": "Fijar", + "status.quote": "Cita", "status.quote.cancel": "Cancelar cita", "status.quote_error.filtered": "Oculto debido a uno de tus filtros", "status.quote_error.not_available": "Publicación no disponible", @@ -888,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "¿Cita pendiente? Mantén la calma", "status.quote_policy_change": "Cambia quién puede citarte", "status.quote_post_author": "Ha citado una publicación de @{name}", + "status.quote_private": "Las publicaciones privadas no pueden ser citadas", "status.read_more": "Leer más", "status.reblog": "Impulsar", "status.reblog_private": "Impulsar a la audiencia original", @@ -966,7 +970,7 @@ "visibility_modal.button_title": "Configura la visibilidad", "visibility_modal.header": "Visibilidad e interacciones", "visibility_modal.helper.direct_quoting": "Las menciones privadas no se pueden citar.", - "visibility_modal.helper.privacy_editing": "Las publicaciones no pueden cambiar su visibilidad.", + "visibility_modal.helper.privacy_editing": "Una vez publicada, no se puede cambiar su visibilidad.", "visibility_modal.helper.private_quoting": "Las publicaciones para seguidores no pueden citarse.", "visibility_modal.helper.unlisted_quoting": "Cuando la gente te cite, su publicación tampoco se mostrará en las cronologías públicas.", "visibility_modal.instructions": "Controla quién puede interactuar con esta publicación. Puedes encontrar los ajustes globales en Preferencias > Otros.", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index b2ab96a37f..afd75c9905 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -43,7 +43,7 @@ "account.followers": "پی‌گیرندگان", "account.followers.empty": "هنوز کسی پی‌گیر این کاربر نیست.", "account.followers_counter": "{count, plural, one {{counter} پی‌گیرنده} other {{counter} پی‌گیرنده}}", - "account.followers_you_know_counter": "{counter} را می‌شناسید", + "account.followers_you_know_counter": "{counter} نفر را می‌شناسید", "account.following": "پی می‌گیرید", "account.following_counter": "{count, plural, one {{counter} پی‌گرفته} other {{counter} پی‌گرفته}}", "account.follows.empty": "این کاربر هنوز پی‌گیر کسی نیست.", @@ -880,6 +880,7 @@ "status.mute_conversation": "خموشاندن گفت‌وگو", "status.open": "گسترش این فرسته", "status.pin": "سنجاق به نمایه", + "status.quote.cancel": "لغو نقل", "status.quote_error.filtered": "نهفته بنا بر یکی از پالایه‌هایتان", "status.quote_error.not_available": "فرسته در دسترس نیست", "status.quote_error.pending_approval": "فرسته منتظر", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 0db6e6db2b..34f14ab03d 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -849,9 +849,11 @@ "status.admin_account": "Avaa tilin @{name} moderointinäkymä", "status.admin_domain": "Avaa palvelimen {domain} moderointinäkymä", "status.admin_status": "Avaa julkaisu moderointinäkymässä", + "status.all_disabled": "Tehostukset ja lainaukset ovat poissa käytöstä", "status.block": "Estä @{name}", "status.bookmark": "Lisää kirjanmerkki", "status.cancel_reblog_private": "Peru tehostus", + "status.cannot_quote": "Julkaisun tekijä on poistanut lainaamisen käytöstä", "status.cannot_reblog": "Tätä julkaisua ei voi tehostaa", "status.context.load_new_replies": "Uusia vastauksia saatavilla", "status.context.loading": "Tarkistetaan lisävastauksia", @@ -880,6 +882,7 @@ "status.mute_conversation": "Mykistä keskustelu", "status.open": "Laajenna julkaisu", "status.pin": "Kiinnitä profiiliin", + "status.quote": "Lainaa", "status.quote.cancel": "Peruuta lainaus", "status.quote_error.filtered": "Piilotettu jonkin asettamasi suodattimen takia", "status.quote_error.not_available": "Julkaisu ei saatavilla", @@ -888,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "Odottava lainaus? Pysy rauhallisena", "status.quote_policy_change": "Vaihda, kuka voi lainata", "status.quote_post_author": "Lainaa käyttäjän @{name} julkaisua", + "status.quote_private": "Yksityisiä julkaisuja ei voi lainata", "status.read_more": "Näytä enemmän", "status.reblog": "Tehosta", "status.reblog_private": "Tehosta alkuperäiselle yleisölle", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 734fa3052a..6fdb9f25e4 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -849,9 +849,11 @@ "status.admin_account": "Lat kjakleiðaramarkamót upp fyri @{name}", "status.admin_domain": "Lat umsjónarmarkamót upp fyri {domain}", "status.admin_status": "Lat hendan postin upp í kjakleiðaramarkamótinum", + "status.all_disabled": "Stimbranir og sitatir eru gjørd óvirkin", "status.block": "Blokera @{name}", "status.bookmark": "Goym", "status.cancel_reblog_private": "Strika stimbran", + "status.cannot_quote": "Høvundurin hevur gjørt sitering óvirkna fyri hendan postin", "status.cannot_reblog": "Tað ber ikki til at stimbra hendan postin", "status.context.load_new_replies": "Nýggj svar tøk", "status.context.loading": "Kanni um tað eru fleiri svar", @@ -880,6 +882,7 @@ "status.mute_conversation": "Doyv samrøðu", "status.open": "Víðka henda postin", "status.pin": "Ger fastan í vangan", + "status.quote": "Sitat", "status.quote.cancel": "Ógilda sitat", "status.quote_error.filtered": "Eitt av tínum filtrum fjalir hetta", "status.quote_error.not_available": "Postur ikki tøkur", @@ -888,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "Bíðar eftir sitati? Tak tað róligt", "status.quote_policy_change": "Broyt hvør kann sitera", "status.quote_post_author": "Siteraði ein post hjá @{name}", + "status.quote_private": "Privatir postar kunnu ikki siterast", "status.read_more": "Les meira", "status.reblog": "Stimbra", "status.reblog_private": "Stimbra við upprunasýni", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index a2997220c9..5c0c1235b4 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -849,9 +849,11 @@ "status.admin_account": "פתח/י ממשק פיקוח דיון עבור @{name}", "status.admin_domain": "פתיחת ממשק פיקוח דיון עבור {domain}", "status.admin_status": "לפתוח הודעה זו במסך ניהול הדיונים", + "status.all_disabled": "הדהודים וציטוטים כובו", "status.block": "חסימת @{name}", "status.bookmark": "סימניה", "status.cancel_reblog_private": "הסרת הדהוד", + "status.cannot_quote": "יוצר\\ת ההודעה חסמו את האפשרות לצטט אותה", "status.cannot_reblog": "לא ניתן להדהד חצרוץ זה", "status.context.load_new_replies": "הגיעו תגובות חדשות", "status.context.loading": "מחפש תגובות חדשות", @@ -880,6 +882,7 @@ "status.mute_conversation": "השתקת שיחה", "status.open": "הרחבת הודעה זו", "status.pin": "הצמדה לפרופיל שלי", + "status.quote": "ציטוט", "status.quote.cancel": "ביטול הודעת ציטוט", "status.quote_error.filtered": "מוסתר בהתאם לסננים שלך", "status.quote_error.not_available": "ההודעה לא זמינה", @@ -888,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "ההודעה בהמתנה? המתינו ברוגע", "status.quote_policy_change": "הגדרת הרשאה לציטוט הודעותיך", "status.quote_post_author": "ההודעה צוטטה על ידי @{name}", + "status.quote_private": "הודעות פרטיות לא ניתנות לציטוט", "status.read_more": "לקרוא עוד", "status.reblog": "הדהוד", "status.reblog_private": "להדהד ברמת הנראות המקורית", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index 2edb77b7f3..c6bba3bac9 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -1,6 +1,7 @@ { "about.blocks": "Servitores moderate", "about.contact": "Contacto:", + "about.default_locale": "Default", "about.disclaimer": "Mastodon es software libere, de codice aperte, e un marca de Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "Ration non disponibile", "about.domain_blocks.preamble": "Mastodon generalmente permitte vider le contento de, e interager con, usatores de qualcunque altere servitor in le fediverso. Istes es le exceptiones que ha essite facite sur iste servitor particular.", @@ -8,6 +9,7 @@ "about.domain_blocks.silenced.title": "Limitate", "about.domain_blocks.suspended.explanation": "Nulle datos de iste servitor essera processate, immagazinate o excambiate, rendente omne interaction o communication con usatores de iste servitor impossibile.", "about.domain_blocks.suspended.title": "Suspendite", + "about.language_label": "Lingua", "about.not_available": "Iste information non ha essite rendite disponibile sur iste servitor.", "about.powered_by": "Rete social decentralisate, actionate per {mastodon}", "about.rules": "Regulas del servitor", @@ -41,6 +43,7 @@ "account.followers": "Sequitores", "account.followers.empty": "Necuno seque ancora iste usator.", "account.followers_counter": "{count, plural, one {{counter} sequitor} other {{counter} sequitores}}", + "account.followers_you_know_counter": "{counter} que tu cognosce", "account.following": "Sequente", "account.following_counter": "{count, plural, one {{counter} sequite} other {{counter} sequites}}", "account.follows.empty": "Iste usator non seque ancora alcuno.", @@ -216,6 +219,13 @@ "confirmations.delete_list.confirm": "Deler", "confirmations.delete_list.message": "Es tu secur que tu vole deler permanentemente iste lista?", "confirmations.delete_list.title": "Deler lista?", + "confirmations.discard_draft.confirm": "Discartar e continuar", + "confirmations.discard_draft.edit.cancel": "Reprender le modification", + "confirmations.discard_draft.edit.message": "Si tu continua, tu perdera tote le modificatione que tu ha apportate al message que tu modifica in iste momento.", + "confirmations.discard_draft.edit.title": "Discartar le modificationes de tu message?", + "confirmations.discard_draft.post.cancel": "Retornar al esbosso", + "confirmations.discard_draft.post.message": "Si tu continua, tu perdera le message que tu compone in iste momento.", + "confirmations.discard_draft.post.title": "Discartar tu esbosso de message?", "confirmations.discard_edit_media.confirm": "Abandonar", "confirmations.discard_edit_media.message": "Tu ha cambiamentos non salvate in le description o previsualisation del objecto multimedial. Abandonar los?", "confirmations.follow_to_list.confirm": "Sequer e adder al lista", @@ -235,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Remover sequitor", "confirmations.remove_from_followers.message": "{name} non plus te sequera. Es tu secur de voler continuar?", "confirmations.remove_from_followers.title": "Remover sequitor?", + "confirmations.revoke_quote.confirm": "Remover message", + "confirmations.revoke_quote.message": "Iste action non pote esser disfacite.", + "confirmations.revoke_quote.title": "Remover message?", "confirmations.unfollow.confirm": "Non plus sequer", "confirmations.unfollow.message": "Es tu secur que tu vole cessar de sequer {name}?", "confirmations.unfollow.title": "Cessar de sequer le usator?", @@ -279,6 +292,7 @@ "domain_pill.your_handle": "Tu pseudonymo:", "domain_pill.your_server": "Tu casa digital, ubi vive tote tu messages. Non te place? Cambia de servitor a omne momento e porta tu sequitores con te.", "domain_pill.your_username": "Tu identificator unic sur iste servitor. Es possibile trovar usatores con le mesme nomine de usator sur servitores differente.", + "dropdown.empty": "Selige un option", "embed.instructions": "Incorpora iste message sur tu sito web con le codice sequente.", "embed.preview": "Ecce como illlo parera:", "emoji_button.activity": "Activitate", @@ -296,6 +310,8 @@ "emoji_button.search_results": "Resultatos de recerca", "emoji_button.symbols": "Symbolos", "emoji_button.travel": "Viages e locos", + "empty_column.account_featured.me": "Tu non ha ancora mittite alcun cosa in evidentia. Sapeva tu que tu pote mitter in evidentia sur tu profilo le hashtags que tu usa le plus e mesmo le contos de tu amicos?", + "empty_column.account_featured.other": "{acct} non ha ancora mittite alcun cosa in evidentia. Sapeva tu que tu pote mitter in evidentia sur tu profilo le hashtags que tu usa le plus e mesmo le contos de tu amicos?", "empty_column.account_featured_other.unknown": "Iste conto non ha ancora mittite alcun cosa in evidentia.", "empty_column.account_hides_collections": "Le usator non ha rendite iste information disponibile", "empty_column.account_suspended": "Conto suspendite", @@ -325,9 +341,15 @@ "errors.unexpected_crash.copy_stacktrace": "Copiar le traciamento del pila al area de transferentia", "errors.unexpected_crash.report_issue": "Reportar problema", "explore.suggested_follows": "Personas", + "explore.title": "In tendentia", "explore.trending_links": "Novas", "explore.trending_statuses": "Messages", "explore.trending_tags": "Hashtags", + "featured_carousel.header": "{count, plural, one {Message fixate} other {Messages fixate}}", + "featured_carousel.next": "Sequente", + "featured_carousel.post": "Message", + "featured_carousel.previous": "Precedente", + "featured_carousel.slide": "{index} de {total}", "filter_modal.added.context_mismatch_explanation": "Iste categoria de filtros non se applica al contexto in le qual tu ha accedite a iste message. Pro filtrar le message in iste contexto tamben, modifica le filtro.", "filter_modal.added.context_mismatch_title": "Contexto incoherente!", "filter_modal.added.expired_explanation": "Iste categoria de filtros ha expirate. Tu debe modificar le data de expiration pro applicar lo.", @@ -380,6 +402,8 @@ "generic.saved": "Salvate", "getting_started.heading": "Prime passos", "hashtag.admin_moderation": "Aperir le interfacie de moderation pro #{name}", + "hashtag.browse": "Percurrer messages in #{hashtag}", + "hashtag.browse_from_account": "Percurrer le messages de @{name} in #{hashtag}", "hashtag.column_header.tag_mode.all": "e {additional}", "hashtag.column_header.tag_mode.any": "o {additional}", "hashtag.column_header.tag_mode.none": "sin {additional}", @@ -404,6 +428,7 @@ "hints.profiles.see_more_followers": "Vider plus de sequitores sur {domain}", "hints.profiles.see_more_follows": "Vider plus de sequites sur {domain}", "hints.profiles.see_more_posts": "Vider plus de messages sur {domain}", + "home.column_settings.show_quotes": "Monstrar citationes", "home.column_settings.show_reblogs": "Monstrar impulsos", "home.column_settings.show_replies": "Monstrar responsas", "home.hide_announcements": "Celar annuncios", @@ -477,6 +502,8 @@ "keyboard_shortcuts.translate": "a traducer un message", "keyboard_shortcuts.unfocus": "Disfocalisar le area de composition de texto/de recerca", "keyboard_shortcuts.up": "Displaciar in alto in le lista", + "learn_more_link.got_it": "Comprendite", + "learn_more_link.learn_more": "Pro saper plus", "lightbox.close": "Clauder", "lightbox.next": "Sequente", "lightbox.previous": "Precedente", @@ -526,8 +553,10 @@ "mute_modal.you_wont_see_mentions": "Tu non videra le messages que mentiona iste persona.", "mute_modal.you_wont_see_posts": "Iste persona pote totevia vider tu messages, ma tu non videra le sues.", "navigation_bar.about": "A proposito", + "navigation_bar.account_settings": "Contrasigno e securitate", "navigation_bar.administration": "Administration", "navigation_bar.advanced_interface": "Aperir in le interfacie web avantiate", + "navigation_bar.automated_deletion": "Deletion automatic del message", "navigation_bar.blocks": "Usatores blocate", "navigation_bar.bookmarks": "Marcapaginas", "navigation_bar.direct": "Mentiones private", @@ -537,13 +566,23 @@ "navigation_bar.follow_requests": "Requestas de sequimento", "navigation_bar.followed_tags": "Hashtags sequite", "navigation_bar.follows_and_followers": "Sequites e sequitores", + "navigation_bar.import_export": "Importar e exportar", "navigation_bar.lists": "Listas", + "navigation_bar.live_feed_local": "Canal in directo (local)", + "navigation_bar.live_feed_public": "Canal in directo (public)", "navigation_bar.logout": "Clauder session", "navigation_bar.moderation": "Moderation", + "navigation_bar.more": "Plus", "navigation_bar.mutes": "Usatores silentiate", "navigation_bar.opened_in_classic_interface": "Messages, contos e altere paginas specific es aperite per predefinition in le interfacie web classic.", "navigation_bar.preferences": "Preferentias", + "navigation_bar.privacy_and_reach": "Confidentialitate e portata", "navigation_bar.search": "Cercar", + "navigation_bar.search_trends": "Cercar / In tendentia", + "navigation_panel.collapse_followed_tags": "Contraher le menu de hashtags sequite", + "navigation_panel.collapse_lists": "Contraher le menu de listas", + "navigation_panel.expand_followed_tags": "Expander le menu de hashtags sequite", + "navigation_panel.expand_lists": "Expander le menu de listas", "not_signed_in_indicator.not_signed_in": "Es necessari aperir session pro acceder a iste ressource.", "notification.admin.report": "{name} ha reportate {target}", "notification.admin.report_account": "{name} ha reportate {count, plural, one {un message} other {# messages}} de {target} per {category}", @@ -565,6 +604,7 @@ "notification.label.mention": "Mention", "notification.label.private_mention": "Mention private", "notification.label.private_reply": "Responsa private", + "notification.label.quote": "{name} ha citate tu message", "notification.label.reply": "Responder", "notification.mention": "Mention", "notification.mentioned_you": "{name} te ha mentionate", @@ -622,6 +662,7 @@ "notifications.column_settings.mention": "Mentiones:", "notifications.column_settings.poll": "Resultatos del sondage:", "notifications.column_settings.push": "Notificationes push", + "notifications.column_settings.quote": "Citationes:", "notifications.column_settings.reblog": "Impulsos:", "notifications.column_settings.show": "Monstrar in columna", "notifications.column_settings.sound": "Reproducer sono", @@ -770,6 +811,7 @@ "report_notification.categories.violation": "Violation del regulas", "report_notification.categories.violation_sentence": "violation del regulas", "report_notification.open": "Aperir reporto", + "search.clear": "Rader le recerca", "search.no_recent_searches": "Nulle recercas recente", "search.placeholder": "Cercar", "search.quick_action.account_search": "Profilos correspondente a {x}", @@ -811,6 +853,8 @@ "status.bookmark": "Adder al marcapaginas", "status.cancel_reblog_private": "Disfacer impulso", "status.cannot_reblog": "Iste message non pote esser impulsate", + "status.context.load_new_replies": "Nove responsas disponibile", + "status.context.loading": "Cercante plus responsas", "status.continued_thread": "Continuation del discussion", "status.copy": "Copiar ligamine a message", "status.delete": "Deler", @@ -836,6 +880,14 @@ "status.mute_conversation": "Silentiar conversation", "status.open": "Expander iste message", "status.pin": "Fixar sur profilo", + "status.quote.cancel": "Cancellar le citation", + "status.quote_error.filtered": "Celate a causa de un de tu filtros", + "status.quote_error.not_available": "Message indisponibile", + "status.quote_error.pending_approval": "Message pendente", + "status.quote_error.pending_approval_popout.body": "Le citationes distribuite a transverso le Fediverso pote tardar in apparer, perque differente servitores ha differente protocollos.", + "status.quote_error.pending_approval_popout.title": "Citation pendente? Resta calme", + "status.quote_policy_change": "Cambiar qui pote citar", + "status.quote_post_author": "Ha citate un message de @{name}", "status.read_more": "Leger plus", "status.reblog": "Impulsar", "status.reblog_private": "Impulsar con visibilitate original", @@ -850,6 +902,7 @@ "status.reply": "Responder", "status.replyAll": "Responder al discussion", "status.report": "Reportar @{name}", + "status.revoke_quote": "Remover mi message del message de @{name}", "status.sensitive_warning": "Contento sensibile", "status.share": "Compartir", "status.show_less_all": "Monstrar minus pro totes", @@ -865,9 +918,13 @@ "subscribed_languages.save": "Salvar le cambiamentos", "subscribed_languages.target": "Cambiar le linguas subscribite pro {target}", "tabs_bar.home": "Initio", + "tabs_bar.menu": "Menu", "tabs_bar.notifications": "Notificationes", + "tabs_bar.publish": "Nove message", + "tabs_bar.search": "Cercar", "terms_of_service.effective_as_of": "In vigor a partir de {date}", "terms_of_service.title": "Conditiones de servicio", + "terms_of_service.upcoming_changes_on": "Cambiamentos a venir le {date}", "time_remaining.days": "{number, plural, one {# die} other {# dies}} restante", "time_remaining.hours": "{number, plural, one {# hora} other {# horas}} restante", "time_remaining.minutes": "{number, plural, one {# minuta} other {# minutas}} restante", @@ -875,7 +932,7 @@ "time_remaining.seconds": "{number, plural, one {# secunda} other {# secundas}} restante", "trends.counter_by_accounts": "{count, plural, one {{counter} persona} other {{counter} personas}} in le passate {days, plural, one {die} other {{days} dies}}", "trends.trending_now": "Ora in tendentias", - "ui.beforeunload": "Tu esbosso essera predite si tu exi de Mastodon.", + "ui.beforeunload": "Tu esbosso essera perdite si tu exi de Mastodon.", "units.short.billion": "{count}B", "units.short.million": "{count}M", "units.short.thousand": "{count}K", @@ -902,7 +959,20 @@ "video.pause": "Pausa", "video.play": "Reproducer", "video.skip_backward": "Saltar a retro", + "video.skip_forward": "Avantiar", "video.unmute": "Non plus silentiar", "video.volume_down": "Abassar le volumine", - "video.volume_up": "Augmentar le volumine" + "video.volume_up": "Augmentar le volumine", + "visibility_modal.button_title": "Definir visibilitate", + "visibility_modal.header": "Visibilitate e interaction", + "visibility_modal.helper.direct_quoting": "Le mentiones private non pote esser citate.", + "visibility_modal.helper.privacy_editing": "Le messages ja publicate non pote cambiar de visibilitate.", + "visibility_modal.helper.private_quoting": "Le messages reservate al sequitores non pote esser citate.", + "visibility_modal.helper.unlisted_quoting": "Quando un persona te cita, su message essera tamben celate del chronologia \"In tendentia\".", + "visibility_modal.instructions": "Controla qui pote interager con iste message. Le parametros global se trova sub Preferentias > Alteres.", + "visibility_modal.privacy_label": "Confidentialitate", + "visibility_modal.quote_followers": "Solmente sequitores", + "visibility_modal.quote_label": "Cambiar qui pote citar", + "visibility_modal.quote_nobody": "Necuno", + "visibility_modal.quote_public": "Omnes" } diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 86e2e6542e..f2abc7c4d2 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -849,9 +849,11 @@ "status.admin_account": "Opna umsjónarviðmót fyrir @{name}", "status.admin_domain": "Opna umsjónarviðmót fyrir @{domain}", "status.admin_status": "Opna þessa færslu í umsjónarviðmótinu", + "status.all_disabled": "Endurbirtingar og tilvitnanir eru óvirkar", "status.block": "Útiloka @{name}", "status.bookmark": "Bókamerki", "status.cancel_reblog_private": "Taka úr endurbirtingu", + "status.cannot_quote": "Höfundurinn hefur gert tilvitnanir óvirkar fyrir þessa færslu", "status.cannot_reblog": "Þessa færslu er ekki hægt að endurbirta", "status.context.load_new_replies": "Ný svör hafa borist", "status.context.loading": "Athuga með fleiri svör", @@ -880,6 +882,7 @@ "status.mute_conversation": "Þagga niður í samtali", "status.open": "Opna þessa færslu", "status.pin": "Festa á notandasnið", + "status.quote": "Tilvitnun", "status.quote.cancel": "Hætta við tilvitnun", "status.quote_error.filtered": "Falið vegna einnar síu sem er virk", "status.quote_error.not_available": "Færsla ekki tiltæk", @@ -888,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "Færsla í bið? Verum róleg", "status.quote_policy_change": "Breyttu því hver getur tilvitnað", "status.quote_post_author": "Vitnaði í færslu frá @{name}", + "status.quote_private": "Ekki er hægt að vitna í einkafærslur", "status.read_more": "Lesa meira", "status.reblog": "Endurbirting", "status.reblog_private": "Endurbirta til upphaflegra lesenda", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 072ee445b5..1caa373d62 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -245,6 +245,9 @@ "confirmations.remove_from_followers.confirm": "Rimuovi il seguace", "confirmations.remove_from_followers.message": "{name} smetterà di seguirti. Si è sicuri di voler procedere?", "confirmations.remove_from_followers.title": "Rimuovi il seguace?", + "confirmations.revoke_quote.confirm": "Elimina il post", + "confirmations.revoke_quote.message": "Questa azione non può essere annullata.", + "confirmations.revoke_quote.title": "Rimuovere il post?", "confirmations.unfollow.confirm": "Smetti di seguire", "confirmations.unfollow.message": "Sei sicuro di voler smettere di seguire {name}?", "confirmations.unfollow.title": "Smettere di seguire l'utente?", @@ -289,6 +292,7 @@ "domain_pill.your_handle": "Il tuo nome univoco:", "domain_pill.your_server": "La tua casa digitale, dove vivono tutti i tuoi post. Non ti piace questa? Cambia server in qualsiasi momento e porta con te anche i tuoi seguaci.", "domain_pill.your_username": "Il tuo identificatore univoco su questo server. È possibile trovare utenti con lo stesso nome utente su server diversi.", + "dropdown.empty": "Seleziona un'opzione", "embed.instructions": "Incorpora questo post sul tuo sito web, copiando il seguente codice.", "embed.preview": "Ecco come apparirà:", "emoji_button.activity": "Attività", @@ -845,9 +849,11 @@ "status.admin_account": "Apri interfaccia di moderazione per @{name}", "status.admin_domain": "Apri l'interfaccia di moderazione per {domain}", "status.admin_status": "Apri questo post nell'interfaccia di moderazione", + "status.all_disabled": "Condivisioni e citazioni sono disabilitate", "status.block": "Blocca @{name}", "status.bookmark": "Aggiungi segnalibro", "status.cancel_reblog_private": "Annulla reblog", + "status.cannot_quote": "L'autore ha disabilitato le citazioni su questo post", "status.cannot_reblog": "Questo post non può essere condiviso", "status.context.load_new_replies": "Nuove risposte disponibili", "status.context.loading": "Controllo per altre risposte", @@ -876,12 +882,16 @@ "status.mute_conversation": "Silenzia conversazione", "status.open": "Espandi questo post", "status.pin": "Fissa in cima sul profilo", + "status.quote": "Cita", + "status.quote.cancel": "Annulla la citazione", "status.quote_error.filtered": "Nascosto a causa di uno dei tuoi filtri", "status.quote_error.not_available": "Post non disponibile", "status.quote_error.pending_approval": "Post in attesa", "status.quote_error.pending_approval_popout.body": "Le citazioni condivise in tutto il Fediverso possono richiedere del tempo per la visualizzazione, poiché server diversi hanno protocolli diversi.", "status.quote_error.pending_approval_popout.title": "Citazione in attesa? Resta calmo", + "status.quote_policy_change": "Cambia chi può citare", "status.quote_post_author": "Citato un post di @{name}", + "status.quote_private": "I post privati non possono essere citati", "status.read_more": "Leggi di più", "status.reblog": "Reblog", "status.reblog_private": "Reblog con visibilità originale", @@ -896,6 +906,7 @@ "status.reply": "Rispondi", "status.replyAll": "Rispondi alla conversazione", "status.report": "Segnala @{name}", + "status.revoke_quote": "Rimuovi la mia citazione dal post di @{name}", "status.sensitive_warning": "Contenuto sensibile", "status.share": "Condividi", "status.show_less_all": "Mostra meno per tutti", @@ -955,5 +966,17 @@ "video.skip_forward": "Vai avanti", "video.unmute": "Muta", "video.volume_down": "Abbassa volume", - "video.volume_up": "Alza volume" + "video.volume_up": "Alza volume", + "visibility_modal.button_title": "Imposta la visibilità", + "visibility_modal.header": "Visibilità e interazione", + "visibility_modal.helper.direct_quoting": "Le menzioni private non possono essere citate.", + "visibility_modal.helper.privacy_editing": "La visibilità dei post pubblicati non può essere modificata.", + "visibility_modal.helper.private_quoting": "I post riservati ai seguaci non possono essere citati.", + "visibility_modal.helper.unlisted_quoting": "Quando le persone ti citano, il loro post verrà nascosto anche dalle timeline di tendenza.", + "visibility_modal.instructions": "Controlla chi può interagire con questo post. Le impostazioni globali si trovano in Preferenze > Altro.", + "visibility_modal.privacy_label": "Privacy", + "visibility_modal.quote_followers": "Solo i seguaci", + "visibility_modal.quote_label": "Cambia chi può citare", + "visibility_modal.quote_nobody": "Nessuno", + "visibility_modal.quote_public": "Chiunque" } diff --git a/app/javascript/mastodon/locales/ne.json b/app/javascript/mastodon/locales/ne.json index 2d949afa66..ff2032e371 100644 --- a/app/javascript/mastodon/locales/ne.json +++ b/app/javascript/mastodon/locales/ne.json @@ -23,11 +23,14 @@ "account.edit_profile": "प्रोफाइल सम्पादन गर्नुहोस्", "account.enable_notifications": "@{name} ले पोस्ट गर्दा मलाई सूचित गर्नुहोस्", "account.endorse": "प्रोफाइलमा फिचर गर्नुहोस्", + "account.featured": "फिचर गरिएको", + "account.featured.accounts": "प्रोफाइलहरू", + "account.featured.hashtags": "ह्यासट्यागहरू", "account.featured_tags.last_status_never": "कुनै पोस्ट छैन", "account.follow": "फलो गर्नुहोस", "account.follow_back": "फलो ब्याक गर्नुहोस्", "account.followers": "फलोअरहरु", - "account.followers.empty": "यस प्रयोगकर्तालाई अहिलेसम्म कसैले फलो गर्दैन।", + "account.followers.empty": "यस प्रयोगकर्तालाई अहिलेसम्म कसैले फलो गरेको छैन।", "account.followers_counter": "{count, plural, one {{counter} फलोअर} other {{counter} फलोअरहरू}}", "account.following": "फलो गर्दै", "account.following_counter": "{count, plural, one {{counter} फलो गर्दै} other {{counter} फलो गर्दै}}", @@ -42,6 +45,7 @@ "account.mute_notifications_short": "सूचनाहरू म्यूट गर्नुहोस्", "account.mute_short": "म्युट", "account.muted": "म्युट गरिएको", + "account.mutual": "तपाईंहरु एकअर्कालाई फलो गर्नुहुन्छ", "account.no_bio": "कुनै विवरण प्रदान गरिएको छैन।", "account.posts": "पोस्टहरू", "account.posts_with_replies": "पोस्ट र जवाफहरू", @@ -53,6 +57,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": "अनफलो गर्नुहोस्", @@ -64,6 +69,7 @@ "admin.dashboard.retention.cohort_size": "नयाँ प्रयोगकर्ताहरू", "alert.rate_limited.message": "कृपया {retry_time, time, medium} पछि पुन: प्रयास गर्नुहोस्।", "alert.unexpected.message": "एउटा अनपेक्षित त्रुटि भयो।", + "alt_text_modal.cancel": "रद्द गर्नुहोस्", "announcement.announcement": "घोषणा", "annual_report.summary.followers.followers": "फलोअरहरु", "annual_report.summary.highlighted_post.by_reblogs": "सबैभन्दा बढि बूस्ट गरिएको पोस्ट", @@ -156,8 +162,13 @@ "notification_requests.confirm_accept_multiple.title": "सूचना अनुरोधहरू स्वीकार गर्ने?", "notification_requests.confirm_dismiss_multiple.title": "सूचना अनुरोधहरू खारेज गर्ने?", "notifications.clear_title": "सूचनाहरू खाली गर्ने?", + "notifications.column_settings.follow": "नयाँ फलोअरहरु:", + "notifications.column_settings.follow_request": "नयाँ फलोअर अनुरोधहरु", "notifications.column_settings.reblog": "बूस्टहरू:", "notifications.filter.boosts": "बूस्टहरू", + "privacy.private.long": "मात्र तपाईंको फलोअरहरु", + "privacy.private.short": "फलोअरहरु", + "reply_indicator.cancel": "रद्द गर्नुहोस्", "report.comment.title": "के हामीले थाहा पाउनुपर्ने अरू केही छ जस्तो लाग्छ?", "report.forward_hint": "यो खाता अर्को सर्भरबाट हो। त्यहाँ पनि रिपोर्टको गुमनाम प्रतिलिपि पठाउने हो?", "report.rules.title": "कुन नियमहरू उल्लङ्घन भइरहेका छन्?", @@ -172,5 +183,6 @@ "status.reblog": "बूस्ट गर्नुहोस्", "status.reblogged_by": "{name} ले बूस्ट गर्नुभएको", "status.reblogs": "{count, plural, one {बूस्ट} other {बूस्टहरू}}", - "status.unmute_conversation": "कुराकानी अनम्यूट गर्नुहोस्" + "status.unmute_conversation": "कुराकानी अनम्यूट गर्नुहोस्", + "visibility_modal.quote_followers": "फलोअरहरु मात्र" } diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index f113e5c6b2..fb14c022d0 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -350,14 +350,14 @@ "featured_carousel.previous": "Предыдущий", "featured_carousel.slide": "{index} из {total}", "filter_modal.added.context_mismatch_explanation": "Этот фильтр не применяется в том контексте, в котором вы видели этот пост. Если вы хотите, чтобы пост был отфильтрован в текущем контексте, необходимо редактировать фильтр.", - "filter_modal.added.context_mismatch_title": "Несоответствие контекста!", + "filter_modal.added.context_mismatch_title": "Несоответствие контекста", "filter_modal.added.expired_explanation": "Этот фильтр истёк. Чтобы он был применён, вам нужно изменить срок действия фильтра.", - "filter_modal.added.expired_title": "Истёкший фильтр!", + "filter_modal.added.expired_title": "Истёкший фильтр", "filter_modal.added.review_and_configure": "Для просмотра или редактирования этого фильтра перейдите в {settings_link}.", "filter_modal.added.review_and_configure_title": "Настройки фильтра", "filter_modal.added.settings_link": "настройки", "filter_modal.added.short_explanation": "Этот пост был добавлен к фильтру «{title}».", - "filter_modal.added.title": "Фильтр добавлен!", + "filter_modal.added.title": "Фильтр добавлен", "filter_modal.select_filter.context_mismatch": "не применяется в этом контексте", "filter_modal.select_filter.expired": "истёкший", "filter_modal.select_filter.prompt_new": "Новый фильтр: {name}", @@ -433,7 +433,7 @@ "home.hide_announcements": "Скрыть объявления", "home.pending_critical_update.body": "Пожалуйста, обновите свой сервер Mastodon как можно скорее!", "home.pending_critical_update.link": "Посмотреть обновления", - "home.pending_critical_update.title": "Доступно критическое обновление безопасности!", + "home.pending_critical_update.title": "Доступно критическое обновление безопасности", "home.show_announcements": "Показать объявления", "ignore_notifications_modal.disclaimer": "Mastodon не может сообщить пользователям, что вы игнорируете их уведомления. Игнорирование уведомлений не остановит отправку самих сообщений.", "ignore_notifications_modal.filter_instead": "Фильтровать", @@ -590,7 +590,7 @@ "notification.admin.report_statuses_other": "{name} пожаловался (-лась) на {target}", "notification.admin.sign_up": "{name} зарегистрировался (-лась) на сервере", "notification.admin.sign_up.name_and_others": "{name} и ещё {count, plural, one {# пользователь} few {# пользователя} other {# пользователей}} зарегистрировались на сервере", - "notification.annual_report.message": "#Wrapstodon за {year} год ждёт вас! Откройте для себя итоги и памятные моменты этого года в Mastodon!", + "notification.annual_report.message": "#Wrapstodon за {year} год ждёт вас! Откройте для себя итоги и памятные моменты этого года в Mastodon.", "notification.annual_report.view": "Перейти к #Wrapstodon", "notification.favourite": "{name} добавил(а) ваш пост в избранное", "notification.favourite.name_and_others_with_link": "{name} и ещё {count, plural, one {# пользователь} few {# пользователя} other {# пользователей}} добавили ваш пост в избранное", @@ -861,7 +861,7 @@ "status.direct": "Упомянуть @{name} лично", "status.direct_indicator": "Личное упоминание", "status.edit": "Редактировать", - "status.edited": "Дата последнего изменения: {date}", + "status.edited": "Последнее изменение: {date}", "status.edited_x_times": "{count, plural, one {{count} изменение} many {{count} изменений} other {{count} изменения}}", "status.embed": "Встроить на свой сайт", "status.favourite": "Добавить в избранное", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index c1643ab9be..883920af75 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -246,6 +246,7 @@ "confirmations.remove_from_followers.message": "{name} kommer att sluta följa dig. Är du säker på att du vill fortsätta?", "confirmations.remove_from_followers.title": "Ta bort följare?", "confirmations.revoke_quote.confirm": "Ta bort inlägg", + "confirmations.revoke_quote.message": "Denna åtgärd kan inte ångras.", "confirmations.revoke_quote.title": "Ta bort inlägg?", "confirmations.unfollow.confirm": "Avfölj", "confirmations.unfollow.message": "Är du säker på att du vill avfölja {name}?", @@ -603,6 +604,7 @@ "notification.label.mention": "Nämn", "notification.label.private_mention": "Privat omnämnande", "notification.label.private_reply": "Privata svar", + "notification.label.quote": "{name} citerade ditt inlägg", "notification.label.reply": "Svar", "notification.mention": "Nämn", "notification.mentioned_you": "{name} nämnde dig", @@ -660,6 +662,7 @@ "notifications.column_settings.mention": "Omnämningar:", "notifications.column_settings.poll": "Omröstningsresultat:", "notifications.column_settings.push": "Push-aviseringar", + "notifications.column_settings.quote": "Citerat:", "notifications.column_settings.reblog": "Boostar:", "notifications.column_settings.show": "Visa i kolumnen", "notifications.column_settings.sound": "Spela upp ljud", @@ -877,8 +880,14 @@ "status.mute_conversation": "Tysta konversation", "status.open": "Utvidga detta inlägg", "status.pin": "Fäst i profil", + "status.quote.cancel": "Återkalla citering", "status.quote_error.filtered": "Dolt på grund av ett av dina filter", "status.quote_error.not_available": "Inlägg ej tillgängligt", + "status.quote_error.pending_approval": "Väntande inlägg", + "status.quote_error.pending_approval_popout.body": "Citat som delas över Fediverse kan ta tid att visa, eftersom olika servrar har olika protokoll.", + "status.quote_error.pending_approval_popout.title": "Väntande citat? Förbli lugn", + "status.quote_policy_change": "Ändra vem som kan citera", + "status.quote_post_author": "Citerade ett inlägg av @{name}", "status.read_more": "Läs mer", "status.reblog": "Boosta", "status.reblog_private": "Boosta med ursprunglig synlighet", @@ -893,6 +902,7 @@ "status.reply": "Svara", "status.replyAll": "Svara på tråden", "status.report": "Rapportera @{name}", + "status.revoke_quote": "Ta bort mitt inlägg från @{name}s inlägg", "status.sensitive_warning": "Känsligt innehåll", "status.share": "Dela", "status.show_less_all": "Visa mindre för alla", @@ -953,9 +963,16 @@ "video.unmute": "Avtysta", "video.volume_down": "Volym ned", "video.volume_up": "Volym upp", + "visibility_modal.button_title": "Ange synlighet", "visibility_modal.header": "Synlighet och interaktion", + "visibility_modal.helper.direct_quoting": "Privata omnämnanden kan inte bli citerade.", + "visibility_modal.helper.privacy_editing": "Publicerade inlägg kan inte ändra deras synlighet.", + "visibility_modal.helper.private_quoting": "Inlägg som endast är synliga för följare kan inte citeras.", + "visibility_modal.helper.unlisted_quoting": "När folk citerar dig, deras inlägg kommer också att döljas från trendiga tidslinjer.", + "visibility_modal.instructions": "Kontrollera vem som kan interagera med det här inlägget. Globala inställningar kan hittas under Inställningar > Andra.", "visibility_modal.privacy_label": "Integritet", "visibility_modal.quote_followers": "Endast följare", + "visibility_modal.quote_label": "Ändra vem som kan citera", "visibility_modal.quote_nobody": "Ingen", "visibility_modal.quote_public": "Alla" } diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 4043695a16..b1de3fe39c 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -292,6 +292,7 @@ "domain_pill.your_handle": "Tanıtıcınız:", "domain_pill.your_server": "Dijital anasayfanız, tüm gönderilerinizin yaşadığı yerdir. Bunu beğenmediniz mi? İstediğiniz zaman sunucularınızı değiştirin ve takipçilerinizi de getirin.", "domain_pill.your_username": "Bu sunucudaki tekil tanımlayıcınız. Farklı sunucularda aynı kullanıcı adına sahip kullanıcıları bulmak mümkündür.", + "dropdown.empty": "Bir seçenek seçin", "embed.instructions": "Aşağıdaki kodu kopyalayarak bu durumu sitenize gömün.", "embed.preview": "İşte nasıl görüneceği:", "emoji_button.activity": "Aktivite", @@ -848,9 +849,11 @@ "status.admin_account": "@{name} için denetim arayüzünü açın", "status.admin_domain": "{domain} için denetim arayüzünü açın", "status.admin_status": "Denetim arayüzünde bu gönderiyi açın", + "status.all_disabled": "Yükseltmeler ve alıntılar devre dışı bırakıldı", "status.block": "@{name} adlı kişiyi engelle", "status.bookmark": "Yer işareti ekle", "status.cancel_reblog_private": "Yeniden paylaşımı geri al", + "status.cannot_quote": "Yazar bu gönderide alıntılamayı devre dışı bıraktı", "status.cannot_reblog": "Bu gönderi yeniden paylaşılamaz", "status.context.load_new_replies": "Yeni yanıtlar mevcut", "status.context.loading": "Daha fazla yanıt için kontrol ediliyor", @@ -879,12 +882,16 @@ "status.mute_conversation": "Sohbeti sessize al", "status.open": "Bu gönderiyi genişlet", "status.pin": "Profile sabitle", + "status.quote": "Teklif", + "status.quote.cancel": "Teklifi iptal et", "status.quote_error.filtered": "Bazı filtrelerinizden dolayı gizlenmiştir", "status.quote_error.not_available": "Gönderi kullanılamıyor", "status.quote_error.pending_approval": "Gönderi beklemede", "status.quote_error.pending_approval_popout.body": "Fediverse genelinde paylaşılan alıntıların görüntülenmesi zaman alabilir, çünkü farklı sunucuların farklı protokolleri vardır.", "status.quote_error.pending_approval_popout.title": "Bekleyen bir teklif mi var? Sakin olun.", + "status.quote_policy_change": "Kimin alıntı yapabileceğini değiştirin", "status.quote_post_author": "@{name} adlı kullanıcının bir gönderisini alıntıladı", + "status.quote_private": "Özel gönderiler alıntılanamaz", "status.read_more": "Devamını okuyun", "status.reblog": "Yeniden paylaş", "status.reblog_private": "Özgün görünürlük ile yeniden paylaş", @@ -959,5 +966,17 @@ "video.skip_forward": "İleriye atla", "video.unmute": "Sesi aç", "video.volume_down": "Sesi kıs", - "video.volume_up": "Sesi yükselt" + "video.volume_up": "Sesi yükselt", + "visibility_modal.button_title": "Görünürlüğü ayarla", + "visibility_modal.header": "Görünürlük ve etkileşim", + "visibility_modal.helper.direct_quoting": "Özel gönderiler alıntılanamaz.", + "visibility_modal.helper.privacy_editing": "Yayınlanan gönderilerin görünürlüğü değiştirilemez.", + "visibility_modal.helper.private_quoting": "Sadece takipçilere özel paylaşımlar alıntılanamaz.", + "visibility_modal.helper.unlisted_quoting": "İnsanlar sizden alıntı yaptığında, onların gönderileri de trend zaman tünellerinden gizlenecektir.", + "visibility_modal.instructions": "Bu gönderiyle kimlerin etkileşimde bulunabileceğini kontrol edin. Genel ayarlara Tercihler > Diğer bölümünden ulaşabilirsiniz.", + "visibility_modal.privacy_label": "Gizlilik", + "visibility_modal.quote_followers": "Sadece takipçiler", + "visibility_modal.quote_label": "Kimin alıntı yapabileceğini değiştirin", + "visibility_modal.quote_nobody": "Kimseden", + "visibility_modal.quote_public": "Herkesten" } diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index f0a158c1fb..7608cfef46 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -849,9 +849,11 @@ "status.admin_account": "Mở giao diện quản trị @{name}", "status.admin_domain": "Mở giao diện quản trị @{domain}", "status.admin_status": "Mở tút này trong giao diện quản trị", + "status.all_disabled": "Đăng lại và trích dẫn bị tắt", "status.block": "Chặn @{name}", "status.bookmark": "Lưu", "status.cancel_reblog_private": "Bỏ đăng lại", + "status.cannot_quote": "Tác giả không cho phép trích dẫn tút này", "status.cannot_reblog": "Không thể đăng lại tút này", "status.context.load_new_replies": "Có những trả lời mới", "status.context.loading": "Kiểm tra nhiều trả lời hơn", @@ -880,6 +882,7 @@ "status.mute_conversation": "Tắt thông báo", "status.open": "Mở tút", "status.pin": "Ghim lên hồ sơ", + "status.quote": "Trích dẫn", "status.quote.cancel": "Bỏ trích dẫn", "status.quote_error.filtered": "Bị ẩn vì một bộ lọc của bạn", "status.quote_error.not_available": "Tút không khả dụng", @@ -888,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "Đang chờ trích dẫn? Hãy bình tĩnh", "status.quote_policy_change": "Thay đổi người có thể trích dẫn", "status.quote_post_author": "Trích dẫn từ tút của @{name}", + "status.quote_private": "Không thể trích dẫn nhắn riêng", "status.read_more": "Đọc tiếp", "status.reblog": "Đăng lại", "status.reblog_private": "Đăng lại (Riêng tư)", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 88aaa80b25..ac144ab849 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -849,9 +849,11 @@ "status.admin_account": "開啟 @{name} 的管理介面", "status.admin_domain": "開啟 {domain} 的管理介面", "status.admin_status": "於管理介面開啟此嘟文", + "status.all_disabled": "已停用轉嘟與引用", "status.block": "封鎖 @{name}", "status.bookmark": "書籤", "status.cancel_reblog_private": "取消轉嘟", + "status.cannot_quote": "作者已停用引用此嘟文", "status.cannot_reblog": "這則嘟文無法被轉嘟", "status.context.load_new_replies": "有新回嘟", "status.context.loading": "正在檢查更多回嘟", @@ -880,6 +882,7 @@ "status.mute_conversation": "靜音對話", "status.open": "展開此嘟文", "status.pin": "釘選至個人檔案頁面", + "status.quote": "引用", "status.quote.cancel": "取消引用嘟文", "status.quote_error.filtered": "由於您的過濾器,該嘟文被隱藏", "status.quote_error.not_available": "無法取得該嘟文", @@ -888,6 +891,7 @@ "status.quote_error.pending_approval_popout.title": "引用嘟文正在發送中?別著急,請稍候片刻", "status.quote_policy_change": "變更可以引用的人", "status.quote_post_author": "已引用 @{name} 之嘟文", + "status.quote_private": "無法引用私人嘟文", "status.read_more": "閱讀更多", "status.reblog": "轉嘟", "status.reblog_private": "依照原嘟可見性轉嘟", diff --git a/app/javascript/mastodon/models/dropdown_menu.ts b/app/javascript/mastodon/models/dropdown_menu.ts index c02f205023..963b5071c9 100644 --- a/app/javascript/mastodon/models/dropdown_menu.ts +++ b/app/javascript/mastodon/models/dropdown_menu.ts @@ -1,10 +1,12 @@ +import type { KeyboardEvent, MouseEvent, TouchEvent } from 'react'; + interface BaseMenuItem { text: string; dangerous?: boolean; } export interface ActionMenuItem extends BaseMenuItem { - action: () => void; + action: (event: MouseEvent | KeyboardEvent | TouchEvent) => void; } export interface LinkMenuItem extends BaseMenuItem { diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 0aec0f5406..bb4ce9ce11 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -2,9 +2,9 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrde import { changeUploadCompose, - quoteComposeByStatus, + quoteCompose, quoteComposeCancel, - setQuotePolicy, + setComposeQuotePolicy, } from 'mastodon/actions/compose_typed'; import { timelineDelete } from 'mastodon/actions/timelines_typed'; @@ -329,22 +329,15 @@ export const composeReducer = (state = initialState, action) => { return state.set('is_changing_upload', true); } else if (changeUploadCompose.rejected.match(action)) { return state.set('is_changing_upload', false); - } else if (quoteComposeByStatus.match(action)) { + } else if (quoteCompose.match(action)) { const status = action.payload; - if ( - status.getIn(['quote_approval', 'current_user']) === 'automatic' && - state.get('media_attachments').size === 0 && - !state.get('is_uploading') && - !state.get('poll') - ) { - return state - .set('quoted_status_id', status.get('id')) - .set('spoiler', status.get('sensitive')) - .set('spoiler_text', status.get('spoiler_text')); - } + return state + .set('quoted_status_id', status.get('id')) + .set('spoiler', status.get('sensitive')) + .set('spoiler_text', status.get('spoiler_text')); } else if (quoteComposeCancel.match(action)) { return state.set('quoted_status_id', null); - } else if (setQuotePolicy.match(action)) { + } else if (setComposeQuotePolicy.match(action)) { return state.set('quote_policy', action.payload); } @@ -520,6 +513,7 @@ export const composeReducer = (state = initialState, action) => { map.set('sensitive', action.status.get('sensitive')); map.set('language', action.status.get('language')); map.set('id', null); + map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status'])); // Mastodon-authored posts can be expected to have at most one automatic approval policy map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody'); @@ -551,6 +545,7 @@ export const composeReducer = (state = initialState, action) => { map.set('idempotencyKey', uuid()); map.set('sensitive', action.status.get('sensitive')); map.set('language', action.status.get('language')); + map.set('quoted_status_id', action.status.getIn(['quote', 'quoted_status'])); // Mastodon-authored posts can be expected to have at most one automatic approval policy map.set('quote_policy', action.status.getIn(['quote_approval', 'automatic', 0]) || 'nobody'); diff --git a/app/javascript/mastodon/selectors/filters.ts b/app/javascript/mastodon/selectors/filters.ts index 2eea785335..f84d01216a 100644 --- a/app/javascript/mastodon/selectors/filters.ts +++ b/app/javascript/mastodon/selectors/filters.ts @@ -15,7 +15,7 @@ export const getFilters = createSelector( (_, { contextType }: { contextType: string }) => contextType, ], (filters, contextType) => { - if (!contextType || contextType === 'compose') { + if (!contextType) { return null; } diff --git a/app/javascript/material-icons/400-24px/format_quote_off.svg b/app/javascript/material-icons/400-24px/format_quote_off.svg new file mode 100644 index 0000000000..e7b70bf793 --- /dev/null +++ b/app/javascript/material-icons/400-24px/format_quote_off.svg @@ -0,0 +1 @@ + diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index e89859f058..fe1689b325 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1482,18 +1482,19 @@ body > [data-popper-placement] { border-bottom: 0; - .status__content, - .status__action-bar, - .media-gallery, - .video-player, - .audio-player, - .attachment-list, - .picture-in-picture-placeholder, - .more-from-author, - .status-card, - .hashtag-bar, - .content-warning, - .filter-warning { + & > .status__content, + & > .status__action-bar, + & > .media-gallery, + & > .video-player, + & > .audio-player, + & > .attachment-list, + & > .picture-in-picture-placeholder, + & > .more-from-author, + & > .status-card, + & > .hashtag-bar, + & > .content-warning, + & > .filter-warning, + & > .status__quote { margin-inline-start: var(--thread-margin); width: calc(100% - var(--thread-margin)); } @@ -2383,6 +2384,7 @@ a .account__avatar { .detailed-status__display-name, .detailed-status__datetime, .detailed-status__application, +.detailed-status__link, .account__display-name { text-decoration: none; } @@ -2415,7 +2417,8 @@ a.account__display-name { } .detailed-status__application, -.detailed-status__datetime { +.detailed-status__datetime, +.detailed-status__link { color: inherit; } @@ -2601,8 +2604,9 @@ a.account__display-name { } .status__relative-time, -.detailed-status__datetime { - &:hover { +.detailed-status__datetime, +.detailed-status__link { + &:is(a):hover { text-decoration: underline; } } @@ -2856,10 +2860,43 @@ a.account__display-name { &:focus, &:hover, &:active { - background: var(--dropdown-border-color); - outline: 0; + &:not(:disabled) { + background: var(--dropdown-border-color); + outline: 0; + } } } + + button:disabled { + color: $dark-text-color; + cursor: default; + } +} + +.reblog-button { + &__item { + width: 280px; + + button { + display: flex; + align-items: center; + gap: 8px; + white-space: inherit; + } + + div { + display: flex; + flex-direction: column; + } + + &.active:not(.disabled) { + color: $highlight-text-color; + } + } + + &__meta { + font-weight: 400; + } } .inline-account { @@ -5478,7 +5515,10 @@ a.status-card { .privacy-dropdown__option__content, .privacy-dropdown__option__content strong, - .privacy-dropdown__option__additional { + .privacy-dropdown__option__additional, + .visibility-dropdown__option__content, + .visibility-dropdown__option__content strong, + .visibility-dropdown__option__additional { color: $primary-text-color; } } @@ -5492,13 +5532,15 @@ a.status-card { } } -.privacy-dropdown__option__icon { +.privacy-dropdown__option__icon, +.visibility-dropdown__option__icon { display: flex; align-items: center; justify-content: center; } -.privacy-dropdown__option__content { +.privacy-dropdown__option__content, +.visibility-dropdown__option__content { flex: 1 1 auto; color: $darker-text-color; diff --git a/app/javascript/testing/factories.ts b/app/javascript/testing/factories.ts index cd5f72a06f..c379d55c0d 100644 --- a/app/javascript/testing/factories.ts +++ b/app/javascript/testing/factories.ts @@ -1,9 +1,13 @@ +import { Map as ImmutableMap } from 'immutable'; + import type { ApiRelationshipJSON } from '@/mastodon/api_types/relationships'; +import type { ApiStatusJSON } from '@/mastodon/api_types/statuses'; import type { CustomEmojiData, UnicodeEmojiData, } from '@/mastodon/features/emoji/types'; import { createAccountFromServerJSON } from '@/mastodon/models/account'; +import type { Status } from '@/mastodon/models/status'; import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; type FactoryOptions = { @@ -51,6 +55,36 @@ export const accountFactoryState = ( options: FactoryOptions = {}, ) => createAccountFromServerJSON(accountFactory(options)); +export const statusFactory: FactoryFunction = ({ + id, + ...data +} = {}) => ({ + id: id ?? '1', + created_at: '2023-01-01T00:00:00.000Z', + sensitive: false, + visibility: 'public', + language: 'en', + uri: 'https://example.com/status/1', + url: 'https://example.com/status/1', + replies_count: 0, + reblogs_count: 0, + favorites_count: 0, + account: accountFactory(), + media_attachments: [], + mentions: [], + tags: [], + emojis: [], + content: '

    This is a test status.

    ', + ...data, +}); + +export const statusFactoryState = ( + options: FactoryOptions = {}, +) => + ImmutableMap( + statusFactory(options) as unknown as Record, + ) as unknown as Status; + export const relationshipsFactory: FactoryFunction = ({ id, ...data diff --git a/app/models/status_edit.rb b/app/models/status_edit.rb index 25e2228873..2c2306bb75 100644 --- a/app/models/status_edit.rb +++ b/app/models/status_edit.rb @@ -45,6 +45,10 @@ class StatusEdit < ApplicationRecord delegate :local?, :application, :edited?, :edited_at, :discarded?, :visibility, :language, to: :status + def with_media? + ordered_media_attachments.any? + end + def emojis return @emojis if defined?(@emojis) diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 7926cc54be..495b543e3d 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -12,7 +12,7 @@ class InitialStateSerializer < ActiveModel::Serializer has_one :push_subscription, serializer: REST::WebPushSubscriptionSerializer has_one :role, serializer: REST::RoleSerializer - def meta # rubocop:disable Metrics/AbcSize + def meta store = default_meta_store if object.current_account diff --git a/app/views/admin/reports/_media_attachments.html.haml b/app/views/admin/reports/_media_attachments.html.haml deleted file mode 100644 index aa82ec09a8..0000000000 --- a/app/views/admin/reports/_media_attachments.html.haml +++ /dev/null @@ -1,6 +0,0 @@ -- if status.ordered_media_attachments.first.video? - = render_video_component(status, visible: false) -- elsif status.ordered_media_attachments.first.audio? - = render_audio_component(status) -- else - = render_media_gallery_component(status, visible: false) diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml deleted file mode 100644 index c1e47d5b32..0000000000 --- a/app/views/admin/reports/_status.html.haml +++ /dev/null @@ -1,53 +0,0 @@ -.batch-table__row - %label.batch-table__row__select.batch-checkbox - = f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id - .batch-table__row__content - .status__card - - if status.reblog? - .status__prepend - = material_symbol('repeat') - = t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account, path: admin_account_status_path(status.proper.account.id, status.proper.id))) - - elsif status.reply? && status.in_reply_to_id.present? - .status__prepend - = material_symbol('reply') - = t('admin.statuses.replied_to_html', acct_link: admin_account_inline_link_to(status.in_reply_to_account, path: status.thread.present? ? admin_account_status_path(status.thread.account_id, status.in_reply_to_id) : nil)) - .status__content>< - - if status.proper.spoiler_text.blank? - = prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis) - - else - %details< - %summary>< - %strong> Content warning: #{prerender_custom_emojis(h(status.proper.spoiler_text), status.proper.emojis)} - = prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis) - - - unless status.proper.ordered_media_attachments.empty? - = render partial: 'admin/reports/media_attachments', locals: { status: status.proper } - - .detailed-status__meta - - if status.application - = status.application.name - · - - = link_to admin_account_status_path(status.account.id, status), class: 'detailed-status__datetime' do - %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) - - if status.edited? - · - = link_to t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'formatted')), - admin_account_status_path(status.account_id, status), - class: 'detailed-status__datetime' - - if status.discarded? - · - %span.negative-hint= t('admin.statuses.deleted') - · - - = material_symbol visibility_icon(status) - = t("statuses.visibilities.#{status.visibility}") - · - - = link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__link', rel: 'noopener' do - = t('admin.statuses.view_publicly') - - - if status.proper.sensitive? - · - = material_symbol('visibility_off') - = t('stream_entries.sensitive_content') diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 69e9c02921..516d2f5d2c 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -57,7 +57,7 @@ - if @statuses.empty? = nothing_here 'nothing-here--under-tabs' - else - = render partial: 'admin/reports/status', collection: @statuses, locals: { f: f } + = render partial: 'admin/shared/status_batch_row', collection: @statuses, as: :status, locals: { f: f } - if @report.unresolved? %hr.spacer/ diff --git a/app/views/admin/shared/_status.html.haml b/app/views/admin/shared/_status.html.haml new file mode 100644 index 0000000000..c042fd7a2c --- /dev/null +++ b/app/views/admin/shared/_status.html.haml @@ -0,0 +1,40 @@ +-# locals: (status:) + +.status__card>< + - if status.reblog? + .status__prepend + = material_symbol('repeat') + = t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(status.proper.account, path: admin_account_status_path(status.proper.account.id, status.proper.id))) + - elsif status.reply? && status.in_reply_to_id.present? + .status__prepend + = material_symbol('reply') + = t('admin.statuses.replied_to_html', acct_link: admin_account_inline_link_to(status.in_reply_to_account, path: status.thread.present? ? admin_account_status_path(status.thread.account_id, status.in_reply_to_id) : nil)) + + = render partial: 'admin/shared/status_content', locals: { status: status.proper } + + .detailed-status__meta + - if status.application + = status.application.name + · + = conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status), class: 'detailed-status__datetime' do + %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }><= l(status.created_at) + - if status.edited? +  · + = conditional_link_to can?(:show, status), admin_account_status_path(status.account.id, status, { anchor: 'history' }), class: 'detailed-status__datetime' do + %span><= t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'relative-formatted')) + - if status.discarded? +  · + %span.negative-hint= t('admin.statuses.deleted') + - unless status.reblog? +  · + %span< + = material_symbol(visibility_icon(status)) + = t("statuses.visibilities.#{status.visibility}") + - if status.proper.sensitive? +  · + = material_symbol('visibility_off') + = t('stream_entries.sensitive_content') + - unless status.direct_visibility? +  · + = link_to ActivityPub::TagManager.instance.url_for(status.proper), class: 'detailed-status__link', target: 'blank', rel: 'noopener' do + = t('admin.statuses.view_publicly') diff --git a/app/views/admin/shared/_status_attachments.html.haml b/app/views/admin/shared/_status_attachments.html.haml new file mode 100644 index 0000000000..92cc696453 --- /dev/null +++ b/app/views/admin/shared/_status_attachments.html.haml @@ -0,0 +1,7 @@ +- if status.with_media? + - if status.ordered_media_attachments.first.video? + = render_video_component(status, visible: false) + - elsif status.ordered_media_attachments.first.audio? + = render_audio_component(status) + - else + = render_media_gallery_component(status, visible: false) diff --git a/app/views/admin/shared/_status_batch_row.html.haml b/app/views/admin/shared/_status_batch_row.html.haml new file mode 100644 index 0000000000..53d8a56e60 --- /dev/null +++ b/app/views/admin/shared/_status_batch_row.html.haml @@ -0,0 +1,5 @@ +.batch-table__row + %label.batch-table__row__select.batch-checkbox + = f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id + .batch-table__row__content + = render partial: 'admin/shared/status', object: status diff --git a/app/views/admin/shared/_status_content.html.haml b/app/views/admin/shared/_status_content.html.haml new file mode 100644 index 0000000000..aedd84bdd6 --- /dev/null +++ b/app/views/admin/shared/_status_content.html.haml @@ -0,0 +1,10 @@ +.status__content>< + - if status.spoiler_text.present? + %details< + %summary>< + %strong> Content warning: #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)} + = prerender_custom_emojis(status_content_format(status), status.emojis) + = render partial: 'admin/shared/status_attachments', locals: { status: status.proper } + - else + = prerender_custom_emojis(status_content_format(status), status.emojis) + = render partial: 'admin/shared/status_attachments', locals: { status: status.proper } diff --git a/app/views/admin/status_edits/_status_edit.html.haml b/app/views/admin/status_edits/_status_edit.html.haml index 0bec0159ee..d4cc4f5ea2 100644 --- a/app/views/admin/status_edits/_status_edit.html.haml +++ b/app/views/admin/status_edits/_status_edit.html.haml @@ -9,17 +9,7 @@ %time.formatted{ datetime: status_edit.created_at.iso8601, title: l(status_edit.created_at) }= l(status_edit.created_at) .status - .status__content>< - - if status_edit.spoiler_text.blank? - = prerender_custom_emojis(status_content_format(status_edit), status_edit.emojis) - - else - %details< - %summary>< - %strong> Content warning: #{prerender_custom_emojis(h(status_edit.spoiler_text), status_edit.emojis)} - = prerender_custom_emojis(status_content_format(status_edit), status_edit.emojis) - - - unless status_edit.ordered_media_attachments.empty? - = render partial: 'admin/reports/media_attachments', locals: { status: status_edit } + = render partial: 'admin/shared/status_content', locals: { status: status_edit } .detailed-status__meta %time.formatted{ datetime: status_edit.created_at.iso8601, title: l(status_edit.created_at) }= l(status_edit.created_at) diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml index 57b9fe0e15..e914585db6 100644 --- a/app/views/admin/statuses/index.html.haml +++ b/app/views/admin/statuses/index.html.haml @@ -47,6 +47,6 @@ - if @statuses.empty? = nothing_here 'nothing-here--under-tabs' - else - = render partial: 'admin/reports/status', collection: @statuses, locals: { f: f } + = render partial: 'admin/shared/status_batch_row', collection: @statuses, as: :status, locals: { f: f } = paginate @statuses diff --git a/app/views/admin/statuses/show.html.haml b/app/views/admin/statuses/show.html.haml index 7328eeb0a7..ba5ba81987 100644 --- a/app/views/admin/statuses/show.html.haml +++ b/app/views/admin/statuses/show.html.haml @@ -53,52 +53,11 @@ %h3= t('admin.statuses.contents') -.status__card - - if @status.reblog? - .status__prepend - = material_symbol('repeat') - = t('statuses.boosted_from_html', acct_link: admin_account_inline_link_to(@status.proper.account, path: admin_account_status_path(@status.proper.account.id, @status.proper.id))) - - elsif @status.reply? && @status.in_reply_to_id.present? - .status__prepend - = material_symbol('reply') - = t('admin.statuses.replied_to_html', acct_link: admin_account_inline_link_to(@status.in_reply_to_account, path: @status.thread.present? ? admin_account_status_path(@status.thread.account_id, @status.in_reply_to_id) : nil)) - .status__content>< - - if @status.proper.spoiler_text.blank? - = prerender_custom_emojis(status_content_format(@status.proper), @status.proper.emojis) - - else - %details< - %summary>< - %strong> Content warning: #{prerender_custom_emojis(h(@status.proper.spoiler_text), @status.proper.emojis)} - = prerender_custom_emojis(status_content_format(@status.proper), @status.proper.emojis) - - - unless @status.proper.ordered_media_attachments.empty? - = render partial: 'admin/reports/media_attachments', locals: { status: @status.proper } - - .detailed-status__meta - - if @status.application - = @status.application.name - · - %span.detailed-status__datetime - %time.formatted{ datetime: @status.created_at.iso8601, title: l(@status.created_at) }= l(@status.created_at) - - if @status.edited? - · - %span.detailed-status__datetime - = t('statuses.edited_at_html', date: content_tag(:time, l(@status.edited_at), datetime: @status.edited_at.iso8601, title: l(@status.edited_at), class: 'formatted')) - - if @status.discarded? - · - %span.negative-hint= t('admin.statuses.deleted') - - unless @status.reblog? - · - = material_symbol(visibility_icon(@status)) - = t("statuses.visibilities.#{@status.visibility}") - - if @status.proper.sensitive? - · - = material_symbol('visibility_off') - = t('stream_entries.sensitive_content') += render partial: 'admin/shared/status', object: @status %hr.spacer/ -%h3= t('admin.statuses.history') +%h3#history= t('admin.statuses.history') - if @status.edits.empty? %p= t('admin.statuses.no_history') - else diff --git a/config/initializers/httplog.rb b/config/initializers/httplog.rb index 7a009e84d1..ea4e32c7b0 100644 --- a/config/initializers/httplog.rb +++ b/config/initializers/httplog.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -# Disable httplog in production unless log_level is `debug` -if !Rails.env.production? || Rails.configuration.log_level == :debug +# Disable in production unless log level is `debug` +if Rails.env.local? || Rails.logger.debug? require 'httplog' HttpLog.configure do |config| diff --git a/config/locales/activerecord.ia.yml b/config/locales/activerecord.ia.yml index db0eb9c9ab..8d588ceec2 100644 --- a/config/locales/activerecord.ia.yml +++ b/config/locales/activerecord.ia.yml @@ -49,6 +49,10 @@ ia: attributes: reblog: taken: del message jam existe + terms_of_service: + attributes: + effective_date: + too_soon: es troppo tosto, debe esser plus tarde que %{date} user: attributes: date_of_birth: diff --git a/config/locales/doorkeeper.fa.yml b/config/locales/doorkeeper.fa.yml index e36694dbbb..be748880d9 100644 --- a/config/locales/doorkeeper.fa.yml +++ b/config/locales/doorkeeper.fa.yml @@ -72,7 +72,7 @@ fa: revoke: آیا مطمئن هستید؟ index: authorized_at: تایید شده در %{date} - description_html: اینها نرم‌افزار هایی هستند که می‌توانند به حساب کاربری شما با استفاده از رابط نرم‌افزاری دسترسی پیدا کنند. اگر نرم‌افزار های در اینجا هستند که نمی‌شناسید، یا نرم‌افزاری که رفتار مشکوک دارد، می‌توانید دسترسی اش را باطل کنید. + description_html: این‌ها برنامه‌هاییند که می‌توانند با استفاده از میانای برنامه‌نویسی به حسابتان دسترسی پیدا کنند. اگر برنامه‌ای هست که نمی‌شناسیدش یا رفتار بدی دارد می‌توانید دسترسیش را باطل کنید. last_used_at: آخرین استفاده در %{date} never_used: هرگز استفاده نشده scopes: اجازه‌ها diff --git a/config/locales/en.yml b/config/locales/en.yml index 06db5e3cfc..1c56fcfd82 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1915,7 +1915,7 @@ en: public: Everyone title: '%{name}: "%{quote}"' visibilities: - direct: Direct + direct: Private mention private: Followers-only private_long: Only show to followers public: Public diff --git a/config/locales/eo.yml b/config/locales/eo.yml index f3e0b8186c..09598182cf 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -2090,4 +2090,4 @@ eo: not_enabled: Vi ankoraŭ ne ŝaltis WebAuth not_supported: Ĉi tiu legilo ne povas uzi sekurecŝlosilojn otp_required: Por uzi sekurecŝlosilojn, ebligu 2-faktoran autentigon unue. - registered_on: Registrigita je %{date} + registered_on: Registrita je %{date} diff --git a/config/locales/ia.yml b/config/locales/ia.yml index 1c7de3f382..3e0274cd0b 100644 --- a/config/locales/ia.yml +++ b/config/locales/ia.yml @@ -190,6 +190,7 @@ ia: create_relay: Crear repetitor create_unavailable_domain: Crear dominio indisponibile create_user_role: Crear un rolo + create_username_block: Crear regula de nomine de usator demote_user: Degradar usator destroy_announcement: Deler annuncio destroy_canonical_email_block: Deler blocada de e-mail @@ -203,6 +204,7 @@ ia: destroy_status: Deler message destroy_unavailable_domain: Deler dominio indisponibile destroy_user_role: Destruer rolo + destroy_username_block: Deler regula de nomine de usator disable_2fa_user: Disactivar A2F disable_custom_emoji: Disactivar emoji personalisate disable_relay: Disactivar repetitor @@ -237,6 +239,7 @@ ia: update_report: Actualisar le reporto update_status: Actualisar le message update_user_role: Actualisar rolo + update_username_block: Actualisar regula de nomine de usator actions: approve_appeal_html: "%{name} approbava appello del decision de moderation de %{target}" approve_user_html: "%{name} approbava inscription de %{target}" @@ -255,6 +258,7 @@ ia: create_relay_html: "%{name} ha create un repetitor %{target}" create_unavailable_domain_html: "%{name} stoppava livration al dominio %{target}" create_user_role_html: "%{name} creava rolo de %{target}" + create_username_block_html: "%{name} ha addite un regula pro nomines de usator que contine %{target}" demote_user_html: "%{name} degradava usator %{target}" destroy_announcement_html: "%{name} deleva annuncio %{target}" destroy_canonical_email_block_html: "%{name} disblocava le e-mail con le hash %{target}" @@ -268,6 +272,7 @@ ia: destroy_status_html: "%{name} removeva un message de %{target}" destroy_unavailable_domain_html: "%{name} reprendeva le livration al dominio %{target}" destroy_user_role_html: "%{name} deleva le rolo %{target}" + destroy_username_block_html: "%{name} ha removite un regula pro nomines de usator que contine %{target}" disable_2fa_user_html: "%{name} disactivava le authentication a duo factores pro le usator %{target}" disable_custom_emoji_html: "%{name} disactivava le emoji %{target}" disable_relay_html: "%{name} ha disactivate le repetitor %{target}" @@ -302,6 +307,7 @@ ia: update_report_html: "%{name} actualisava reporto %{target}" update_status_html: "%{name} actualisava message de %{target}" update_user_role_html: "%{name} cambiava rolo de %{target}" + update_username_block_html: "%{name} ha actualisate un regula pro nomines de usator que contine %{target}" deleted_account: conto delite empty: Nulle registros trovate. filter_by_action: Filtrar per action @@ -321,7 +327,7 @@ ia: preview: disclaimer: Proque le usatores non pote optar pro non reciper los, le notificationes per e-mail debe limitar se a annuncios importante tal como le notificationes de violation de datos personal o de clausura de servitores. explanation_html: 'Le e-mail essera inviate a %{display_count} usatores. Le sequente texto essera includite in le e-mail:' - title: Previder le notification de annuncio + title: Previsualisar le notification de annuncio publish: Publicar published_msg: Annuncio publicate con successo! scheduled_for: Programmate pro %{time} @@ -480,6 +486,36 @@ ia: new: title: Importar blocadas de dominio no_file: Necun file seligite + fasp: + debug: + callbacks: + created_at: Data de creation + delete: Deler + ip: Adresse IP + request_body: Corpore del requesta + title: Depurar le appellos de retorno + providers: + active: Active + base_url: URL de base + callback: Appello de retorno + delete: Deler + edit: Modificar fornitor + finish_registration: Terminar registration + name: Nomine + providers: Fornitores + public_key_fingerprint: Dactylogramma del clave public + registration_requested: Registration requestate + registrations: + confirm: Confirmar + description: Tu ha recipite un registration de un FSAF. Rejecta lo si tu non lo ha initiate. Si tu lo ha initiate, compara attentemente le nomine e le dactylogramma del clave ante de confirmar le registration. + reject: Rejectar + title: Confirmar registration FSAF + save: Salvar + select_capabilities: Selige capacitates + sign_in: Aperir session + status: Stato + title: Fornitores de Servicios Auxiliar del Fediverso + title: FSAF follow_recommendations: description_html: "Le recommendationes de sequimento adjuta le nove usatores a trovar rapidemente contento interessante. Quando un usator non ha un historia sufficiente de interactiones con alteres pro formar recommendationes personalisate de sequimento, iste contos es recommendate. Illos se recalcula cata die a partir de un mixtura de contos con le plus grande numero de ingagiamentos recente e le numero de sequitores local le plus alte pro un lingua date." language: Pro le lingua @@ -548,6 +584,13 @@ ia: all: Toto limited: Limitate title: Moderation + moderation_notes: + create: Adder nota de moderation + created_msg: Le nota de moderation del servitor ha essite create. + description_html: Vider e lassar notas pro altere moderatores e pro referentia futur + destroyed_msg: Le nota de moderation del servitor ha essite delite. + placeholder: Informationes sur iste servitor, actiones prendite o altere cosas que pote adjutar te a moderar iste servitor in le futuro. + title: Notas de moderation private_comment: Commento private public_comment: Commento public purge: Purgar @@ -756,11 +799,17 @@ ia: title: Rolos rules: add_new: Adder regula + add_translation: Adder traduction delete: Deler description_html: Ben que multes affirma de haber legite e acceptate le conditiones de servicio, generalmente le gente non los lege completemente usque un problema surge. Facilita le visibilitate del regulas de tu servitor in un colpo de oculo forniente los in un lista a punctos. Tenta mantener le regulas individual curte e simple, ma sin divider los in multe punctos separate. edit: Modificar regula empty: Necun regula del servitor ha essite definite ancora. + move_down: Displaciar a basso + move_up: Displaciar in alto title: Regulas del servitor + translation: Traduction + translations: Traductiones + translations_explanation: Tu ha le option de adder traductiones al regulas. Le valor predefinite apparera si necun version traducite es disponibile. Per favor sempre assecura te que omne traduction fornite es ben synchronisate con le valor predefinite. settings: about: manage_rules: Gerer le regulas del servitor @@ -877,7 +926,7 @@ ia: database_schema_check: message_html: Il ha migrationes de base de datos pendente. Per favor exeque los pro assecurar que le application se comporta como expectate elasticsearch_analysis_index_mismatch: - message_html: Le parametros del analisator del indice de Elasticsearch es obsolete. Executa tootctl search deploy --only-mapping --only=%{value} + message_html: Le parametros del analysator del indice de Elasticsearch es obsolete. Executa tootctl search deploy --only-mapping --only=%{value} elasticsearch_health_red: message_html: Le aggregation Elasticsearch es malsan (stato rubie), le functiones de recerca es indisponibile elasticsearch_health_yellow: @@ -942,7 +991,7 @@ ia: changelog: Lo que ha cambiate create: Usar tu proprie current: Actual - draft: Provisori + draft: Esbosso generate: Usar modello generates: action: Generar @@ -965,7 +1014,7 @@ ia: title: Previsualisar le notification sur le conditiones de servicio publish: Publicar published_on_html: Publicate le %{date} - save_draft: Salvar version provisori + save_draft: Salvar esbosso title: Conditiones de servicio title: Administration trends: @@ -1043,8 +1092,17 @@ ia: title: Recommendationes e tendentias trending: In tendentia username_blocks: + add_new: Adder nove + block_registrations: Blocar registrationes + comparison: + contains: Contine + equals: Es equal a + contains_html: Contine %{string} + created_msg: Regula de nomine de usator create con successo + delete: Deler edit: title: Modificar regula de nomine de usator + matches_exactly_html: Es equal a %{string} new: create: Crear regula title: Crear nove regula de nomine de usator @@ -1316,6 +1374,10 @@ ia: basic_information: Information basic hint_html: "Personalisa lo que le personas vide sur tu profilo public e presso tu messages. Il es plus probabile que altere personas te seque e interage con te quando tu ha un profilo complete e un photo." other: Alteres + emoji_styles: + auto: Automatic + native: Native + twemoji: Twemoji errors: '400': Le requesta que tu ha inviate non es valide o es mal formate. '403': Tu non ha le permission de acceder a iste pagina. @@ -1625,6 +1687,10 @@ ia: title: Nove mention poll: subject: Un sondage de %{name} ha finite + quote: + body: 'Tu message ha essite citate per %{name}:' + subject: "%{name} ha citate tu message" + title: Nove citation reblog: body: "%{name} ha impulsate tu message:" subject: "%{name} ha impulsate tu message" @@ -1835,6 +1901,7 @@ ia: edited_at_html: Modificate le %{date} errors: in_reply_not_found: Le message a que tu tenta responder non pare exister. + quoted_status_not_found: Le message que tu tenta citar non pare exister. over_character_limit: limite de characteres de %{max} excedite pin_errors: direct: Messages que es solo visibile a usatores mentionate non pote esser appunctate @@ -1842,6 +1909,8 @@ ia: ownership: Le message de alcuno altere non pote esser appunctate reblog: Un impulso non pote esser affixate quote_policies: + followers: Solmente tu sequitores + nobody: Necuno public: Omnes title: "%{name}: “%{quote}”" visibilities: @@ -1897,7 +1966,7 @@ ia: terms_of_service: title: Conditiones de servicio terms_of_service_interstitial: - future_preamble_html: Nos face alcun cambios a nostre conditiones de servicio, que entrara in vigor in %{date}. Nos te invita a revider le conditiones actualisate. + future_preamble_html: Nos apporta alcun modificationes a nostre conditiones de servicio, le quales entrara in vigor le %{date}. Nos te invita a revider le conditiones actualisate. past_preamble_html: Nos ha cambiate nostre conditiones de servicio desde tu ultime visita. Nos te invita a revider le conditiones actualisate. review_link: Revider le conditiones de servicio title: Le conditiones de servicio de %{domain} cambia @@ -1969,7 +2038,7 @@ ia: agreement: Si tu continua a usar %{domain}, tu accepta iste conditiones. Si tu non es de accordo con le conditiones actualisate, tu pote sempre eliminar tu conto pro terminar tu accordo con %{domain}. changelog: 'In summario, ecce lo que iste actualisation significa pro te:' description: 'Tu recipe iste e-mail proque nos face alcun cambios al nostre conditiones de servicio a %{domain}. Iste actualisationes entrara in vigor in %{date}. Nos te invita a revider integralmente le conditiones actualisate hic:' - description_html: Tu recipe iste e-mail proque nos face alcun cambios al nostre conditiones de servicio a %{domain}. Iste actualisationes entrara in vigor in %{date}. Nos te invita a revider integralmente le conditiones actualisate hic. + description_html: Tu recipe iste message perque nos apporta alcun modificationes a nostre conditiones de servicio sur %{domain}. Iste actualisationes entrara in vigor le %{date}. Nos te invita a leger integralmente le conditiones actualisate. sign_off: Le equipa de %{domain} subject: Actualisationes de nostre conditiones de servicio subtitle: Le conditiones de servicio de %{domain} ha cambiate diff --git a/config/locales/ru.yml b/config/locales/ru.yml index e2ed99d404..cc97175be2 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1,7 +1,7 @@ --- ru: about: - about_mastodon_html: 'Социальная сеть будущего: никакой рекламы или слежки со стороны корпораций, этичный дизайн и децентрализация! С Mastodon ваши данные находятся только под вашим контролем!' + about_mastodon_html: 'Социальная сеть будущего: никакой рекламы или слежки со стороны корпораций, этичный дизайн и децентрализация. С Mastodon ваши данные находятся только под вашим контролем!' contact_missing: Не указано contact_unavailable: N/A hosted_on: Сервер Mastodon на сайте %{domain} @@ -350,9 +350,9 @@ ru: copy: Копировать copy_failed_msg: Не удалось создать локальную копию эмодзи create_new_category: Создать новую категорию - created_msg: Эмодзи добавлен! + created_msg: Эмодзи добавлен delete: Удалить - destroyed_msg: Эмодзи удалён! + destroyed_msg: Эмодзи удалён disable: Отключить disabled: Отключён disabled_msg: Эмодзи отключён @@ -375,7 +375,7 @@ ru: unlist: Из списка unlisted: Не в списке update_failed_msg: Не удалось обновить эмодзи - updated_msg: Эмодзи обновлён! + updated_msg: Эмодзи обновлён upload: Загрузить dashboard: active_users: активные пользователи @@ -1247,7 +1247,7 @@ ru: delete_account: Удалить учётную запись delete_account_html: Вы можете удалить свою учётную запись. Перед удалением у вас будет запрошено подтверждение. description: - prefix_invited_by_user: Вы получили приглашение на сервер Mastodon от @%{name}! + prefix_invited_by_user: Вы получили приглашение на сервер Mastodon от @%{name}. prefix_sign_up: Зарегистрируйтесь в Mastodon прямо сейчас! suffix: С учётной записью вы сможете подписываться на людей, публиковать посты и обмениваться сообщениями с пользователями любого сервера Mastodon — и не только! didnt_get_confirmation: Не получили письмо со ссылкой для подтверждения? @@ -1497,7 +1497,7 @@ ru: one: Выбран %{count} элемент, соответствующий вашему запросу. other: Выбраны все %{count} элементов, соответствующих вашему запросу. cancel: Отмена - changes_saved_msg: Изменения сохранены! + changes_saved_msg: Изменения сохранены confirm: Подтвердить copy: Копировать delete: Удалить @@ -1731,7 +1731,7 @@ ru: subject: "%{name} добавил(а) ваш пост в избранное" title: Ваш пост добавили в избранное follow: - body: "%{name} теперь подписан(а) на вас!" + body: "%{name} теперь подписан(а) на вас" subject: "%{name} теперь подписан(а) на вас" title: Новый подписчик follow_request: @@ -1965,7 +1965,7 @@ ru: many: 'содержались запрещённые хэштеги: %{tags}' one: 'содержался запрещённый хэштег: %{tags}' other: 'содержались запрещённые хэштеги: %{tags}' - edited_at_html: 'Дата последнего изменения: %{date}' + edited_at_html: 'Последнее изменение: %{date}' errors: in_reply_not_found: Пост, на который вы собирались ответить, был удалён или не существует. quoted_status_not_found: Пост, который вы собирались процитировать, был удалён или не существует. diff --git a/config/locales/simple_form.ia.yml b/config/locales/simple_form.ia.yml index 78af1c1192..914257a89b 100644 --- a/config/locales/simple_form.ia.yml +++ b/config/locales/simple_form.ia.yml @@ -56,10 +56,12 @@ ia: scopes: Le APIs al quales le application habera accesso. Si tu selige un ambito de nivello superior, non es necessari seliger ambitos individual. setting_aggregate_reblogs: Non monstrar nove impulsos pro messages que ha essite recentemente impulsate (affecta solmente le impulsos novemente recipite) setting_always_send_emails: Normalmente, le notificationes de e-mail non es inviate quando tu activemente usa Mastodon + setting_default_quote_policy: Iste parametro solo habera effecto pro messages create con le proxime version de Mastodon, ma tu pote seliger tu preferentia anticipatemente. setting_default_sensitive: Le medios sensibile es celate de ordinario e pote esser revelate con un clic setting_display_media_default: Celar le medios marcate como sensibile setting_display_media_hide_all: Sempre celar contento multimedial setting_display_media_show_all: Sempre monstrar contento multimedial + setting_emoji_style: Como monstrar emojis. “Automatic” tentara usar emojis native, ma recurre al Twemojis pro navigatores ancian. setting_system_scrollbars_ui: Se applica solmente al navigatores de scriptorio basate sur Safari e Chrome setting_use_blurhash: Le imagines degradate se basa sur le colores del visuales celate, ma illos offusca tote le detalios setting_use_pending_items: Requirer un clic pro monstrar nove messages in vice de rolar automaticamente le fluxo @@ -134,16 +136,23 @@ ia: name: Tu pote solmente cambiar le litteras inter majusculas e minusculas, per exemplo, pro render lo plus legibile terms_of_service: changelog: Pote esser structurate con le syntaxe Markdown. + effective_date: Un periodo rationabile pote variar inter 10 e 30 dies post le data al qual tu notifica tu usatores. text: Pote esser structurate con le syntaxe Markdown. terms_of_service_generator: admin_email: Le avisos juridic include le contra-avisos, ordinantias judiciari, demandas de retiro e demandas de application del lege. + arbitration_address: Pote esser le mesme que le adresse physic supra, o “N/A” si se usa e-mail. + arbitration_website: Pote esser un formulario web, o “N/A” si se usa e-mail. choice_of_law: Citate, region, territorio o stato cuje leges substantive interne governa omne disputa juridic. dmca_address: Pro operatores in le SUA, usa le adresse registrate in le Directorio de Agentes Designate pro le DMCA (DMCA Designated Agent Directory). Un adresse de cassa postal es disponibile per requesta directe; usa le Requesta de Exemption de Cassa Postal pro Agentes Designate del DMCA (DMCA Designated Agent Post Office Box Waiver Request) pro inviar un message electronic al Officio del Derecto de Autor (Copyright Office) e describer que tu es un moderator de contento que travalia de casa e qui time vengiantias o represalias pro tu actiones, necessitante le uso un cassa postal pro remover tu adresse personal del vista public. + dmca_email: Pote esser le mesme adresse de e-mail usate pro “Adresse de e-mail pro avisos juridic” supra. domain: Identification unic del servicio in linea que tu forni. jurisdiction: Indica le pais ubi vive le persona qui paga le facturas. Si se tracta de un interprisa o altere organisation, indica le pais ubi illo es incorporate, e le citate, region, territorio o stato del maniera appropriate pro le pais. min_age: Non deberea esser infra le etate minime requirite per le leges de tu jurisdiction. user: chosen_languages: Si marcate, solmente le messages in le linguas seligite apparera in chronologias public + date_of_birth: + one: Nos debe assecurar que tu ha al minus %{count} anno pro usar %{domain}. Nos non va immagazinar isto. + other: Nos debe assecurar que tu ha al minus %{count} annos pro usar %{domain}. Nos non va immagazinar isto. role: Le rolo controla qual permissos le usator ha. user_role: color: Color a esser usate pro le rolo in omne parte del UI, como RGB in formato hexadecimal @@ -151,6 +160,10 @@ ia: name: Nomine public del rolo, si rolo es definite a esser monstrate como insignia permissions_as_keys: Usatores con iste rolo habera accesso a... position: Rolo superior decide resolution de conflicto in certe situationes. Certe actiones pote solo esser exequite sur rolos con un prioritate inferior + username_block: + allow_with_approval: In loco de impedir totalmente le inscription, le inscriptiones correspondente requirera tu approbation + comparison: Tene in conto le “problema Scunthorpe” si tu bloca correspondentias partial + username: Correspondera independentemente de majusculas e minusculas e de homoglyphos commun como "4" pro "a" or "3" pro "e" webhook: events: Selige le eventos a inviar template: Compone tu proprie carga utile JSON per interpolation de variabile. Lassar blanc pro JSON predefinite. @@ -233,6 +246,7 @@ ia: setting_display_media_default: Predefinite setting_display_media_hide_all: Celar toto setting_display_media_show_all: Monstrar toto + setting_emoji_style: Stilo de emojis setting_expand_spoilers: Sempre expander messages marcate con avisos de contento setting_hide_network: Cela tu rete social setting_missing_alt_text_modal: Monstrar un dialogo de confirmation ante de publicar multimedia sin texto alternative @@ -271,6 +285,7 @@ ia: favicon: Favicon mascot: Personalisar le mascotte (hereditage) media_cache_retention_period: Periodo de retention del cache multimedial + min_age: Etate minime requirite peers_api_enabled: Publicar le lista de servitores discoperite in le API profile_directory: Activar directorio de profilos registrations_mode: Qui pote inscriber se @@ -314,6 +329,7 @@ ia: follow_request: Alcuno ha demandate de sequer te mention: Alcuno te ha mentionate pending_account: Nove conto besonia de revision + quote: Alcuno te ha citate reblog: Alcuno ha impulsate tu message report: Un nove reporto es inviate software_updates: @@ -360,6 +376,10 @@ ia: name: Nomine permissions_as_keys: Permissiones position: Prioritate + username_block: + allow_with_approval: Permitter registrationes con approbation + comparison: Methodo de comparation + username: Parola a comparar webhook: events: Eventos activate template: Modello de carga utile diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index 598617efdc..ee8fae37f1 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -56,6 +56,7 @@ sv: scopes: 'Vilka API: er applikationen kommer tillåtas åtkomst till. Om du väljer en omfattning på högstanivån behöver du inte välja individuella sådana.' setting_aggregate_reblogs: Visa inte nya boostar för inlägg som nyligen blivit boostade (påverkar endast nymottagna boostar) setting_always_send_emails: E-postnotiser kommer vanligtvis inte skickas när du aktivt använder Mastodon + setting_default_quote_policy: Denna inställning kommer bara att träda i kraft för inlägg som skapats med nästa Mastodon-version, men du kan förbereda dina önskemål. setting_default_sensitive: Känslig media döljs som standard och kan visas med ett klick setting_display_media_default: Dölj media markerad som känslig setting_display_media_hide_all: Dölj alltid all media @@ -149,6 +150,9 @@ sv: min_age: Bör inte vara lägre än den minimiålder som krävs enligt lagarna i din jurisdiktion. user: chosen_languages: Vid aktivering visas bara inlägg på dina valda språk i offentliga tidslinjer + date_of_birth: + one: Vi måste se till att du är minst %{count} för att använda %{domain}. Vi sparar inte denna information. + other: Vi måste se till att du är minst %{count} för att använda %{domain}. Vi lagrar inte denna information. role: Rollen styr vilka behörigheter användaren har. user_role: color: Färgen som ska användas för rollen i användargränssnittet, som RGB i hex-format @@ -156,6 +160,10 @@ sv: name: Offentligt namn på rollen, om rollen är inställd på att visas som ett emblem permissions_as_keys: Användare med denna roll kommer ha tillgång till... position: Högre roll avgör konfliktlösning i vissa situationer. Vissa åtgärder kan endast utföras på roller med lägre prioritet + username_block: + allow_with_approval: I stället för att förhindra direkt registrering kräver matchning av registreringar ditt godkännande + comparison: Var uppmärksam på Scunthorpe Problem vid blockering av partiella matcher + username: Matchas oavsett versaler och gemena bokstäver som ”4” för ”a” eller ’3’ för ”e” webhook: events: Välj händelser att skicka template: Skriv din egen JSON-nyttolast med variabel interpolation. Lämna tomt för förvald JSON. @@ -321,6 +329,7 @@ sv: follow_request: Någon begärt att följa dig mention: Någon nämnt dig pending_account: Ett nytt konto behöver granskas + quote: Någon citerade dig reblog: Någon boostade ditt inlägg report: En ny rapport har skickats software_updates: @@ -369,6 +378,7 @@ sv: position: Prioritet username_block: allow_with_approval: Tillåt registreringar med godkännande + comparison: Jämförelsesätt username: Ord att matcha webhook: events: Aktiverade händelser diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 594f4b9740..1be0ca2671 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -258,6 +258,7 @@ sv: create_relay_html: "%{name} skapade ombudet %{target}" create_unavailable_domain_html: "%{name} stoppade leverans till domänen %{target}" create_user_role_html: "%{name} skapade rollen %{target}" + create_username_block_html: "%{name} lade till regel för användarnamn som innehåller %{target}" demote_user_html: "%{name} nedgraderade användare %{target}" destroy_announcement_html: "%{name} raderade kungörelsen %{target}" destroy_canonical_email_block_html: "%{name} avblockerade e-post med hash%{target}" @@ -271,6 +272,7 @@ sv: destroy_status_html: "%{name} tog bort inlägget av %{target}" destroy_unavailable_domain_html: "%{name} återupptog leverans till domänen %{target}" destroy_user_role_html: "%{name} raderade rollen %{target}" + destroy_username_block_html: "%{name} borttagna regler för användarnamn som innehåller %{target}" disable_2fa_user_html: "%{name} inaktiverade tvåfaktorsautentiseringskrav för användaren %{target}" disable_custom_emoji_html: "%{name} inaktiverade emoji %{target}" disable_relay_html: "%{name} inaktiverade ombudet %{target}" @@ -305,6 +307,7 @@ sv: update_report_html: "%{name} uppdaterade rapporten %{target}" update_status_html: "%{name} uppdaterade inlägget av %{target}" update_user_role_html: "%{name} ändrade rollen %{target}" + update_username_block_html: "%{name} uppdaterade regel för användarnamn som innehåller %{target}" deleted_account: raderat konto empty: Inga loggar hittades. filter_by_action: Filtrera efter åtgärd @@ -1090,15 +1093,23 @@ sv: trending: Trendande username_blocks: add_new: Lägg till ny + block_registrations: Registrerade blockeringar comparison: contains: Innehåller + equals: Lika + contains_html: Innehåller %{string} + created_msg: Användarnamnsregel har skapats delete: Ta bort edit: title: Redigera användarnamnsregel + matches_exactly_html: Lika med %{string} new: create: Skapa regel title: Skapa ny användarnamnsregel + no_username_block_selected: Inga användarnamn regler ändrades eftersom ingen valdes not_permitted: Ej tillåtet + title: Regler för användarnamn + updated_msg: Uppdaterade användarnamnsregel warning_presets: add_new: Lägg till ny delete: Radera @@ -1676,6 +1687,10 @@ sv: title: Ny omnämning poll: subject: En undersökning av %{name} har avslutats + quote: + body: 'Ditt inlägg citerades av %{name}:' + subject: "%{name} citerade ditt inlägg" + title: Nytt citat reblog: body: 'Ditt inlägg boostades av %{name}:' subject: "%{name} boostade ditt inlägg" diff --git a/yarn.lock b/yarn.lock index 793a79ea54..674741de08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5945,9 +5945,9 @@ __metadata: linkType: hard "core-js@npm:^3.30.2, core-js@npm:^3.45.0": - version: 3.45.0 - resolution: "core-js@npm:3.45.0" - checksum: 10c0/118350f9f1d81f42a1276590d6c217dca04c789fdb8074c82e53056b1a784948769a62b16b98493fd73e8a988545432f302bca798571e56ad881b9c039a5a83c + version: 3.45.1 + resolution: "core-js@npm:3.45.1" + checksum: 10c0/c38e5fae5a05ee3a129c45e10056aafe61dbb15fd35d27e0c289f5490387541c89741185e0aeb61acb558559c6697e016c245cca738fa169a73f2b06cd30e6b6 languageName: node linkType: hard @@ -13818,8 +13818,8 @@ __metadata: linkType: hard "vite-plugin-pwa@npm:^1.0.2": - version: 1.0.2 - resolution: "vite-plugin-pwa@npm:1.0.2" + version: 1.0.3 + resolution: "vite-plugin-pwa@npm:1.0.3" dependencies: debug: "npm:^4.3.6" pretty-bytes: "npm:^6.1.1" @@ -13834,13 +13834,13 @@ __metadata: peerDependenciesMeta: "@vite-pwa/assets-generator": optional: true - checksum: 10c0/e4f2f4dfff843ee2585a0d89e74187168ba20da77cd0d127ce7ad7eebcf5a68b2bf09000afb6bb86d43a2034fea9f568cd6db2a2d4b47a72e175d999a5e07eb1 + checksum: 10c0/03fc24bd12ae4a4130979da4877e3dabddf13d7d6ff2bd68e5e8497a2643b8874a78e6c2502874277ddf2f28593d0a3b025d78af2335bdcc5d2966295784fd46 languageName: node linkType: hard "vite-plugin-static-copy@npm:^3.1.1": - version: 3.1.1 - resolution: "vite-plugin-static-copy@npm:3.1.1" + version: 3.1.2 + resolution: "vite-plugin-static-copy@npm:3.1.2" dependencies: chokidar: "npm:^3.6.0" fs-extra: "npm:^11.3.0" @@ -13849,7 +13849,7 @@ __metadata: tinyglobby: "npm:^0.2.14" peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/a4dd5d31212b037d4902d55c2ee83866e496857bf948f258599dc24ec61b4628cf0dd23e4a7d7dc189d33ad1489427e976fa95e4db61b394d0be4f63077ef92c + checksum: 10c0/1a65f4c9d291cc27483a5b225b1ac5610edc3aa2f13fa3a76a77327874c83bbee52e1011ee0bf5b0168b9b7b974213d49fe800e44af398cfbcb6607814b45c5b languageName: node linkType: hard