Merge pull request #3502 from glitch-soc/glitch-soc/merge-upstream

Merge upstream changes up to 8bbde181db0d6d79018fb1754a8296e753d47415
This commit is contained in:
Claire 2026-05-13 21:36:43 +02:00 committed by GitHub
commit 28b98143d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 1122 additions and 1162 deletions

View File

@ -87,8 +87,8 @@ jobs:
platforms: ${{ matrix.platform }}
provenance: false
push: ${{ inputs.push_to_images != '' }}
cache-from: ${{ inputs.cache && 'type=gha' || '' }}
cache-to: ${{ inputs.cache && 'type=gha,mode=max' || '' }}
cache-from: ${{ inputs.cache && format('type=gha,scope=build-{0}-{1}', hashFiles(inputs.file_to_build), env.PLATFORM_PAIR) || '' }}
cache-to: ${{ inputs.cache && format('type=gha,mode=max,scope=build-{0}-{1}', hashFiles(inputs.file_to_build), env.PLATFORM_PAIR) || '' }}
outputs: type=image,"name=${{ env.IMAGE_NAMES }}",push-by-digest=true,name-canonical=true,push=${{ inputs.push_to_images != '' }}
- name: Export digest

View File

@ -167,7 +167,7 @@ jobs:
- name: Upload coverage reports to Codecov
if: matrix.ruby-version == '.ruby-version'
uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
with:
files: coverage/lcov/*.lcov
env:

26
.simplecov Normal file
View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
SimpleCov.start 'rails' do
# During parallel runs, ensure unique names for post-run merge
command_name "job-#{ENV['TEST_ENV_NUMBER']}" if ENV['TEST_ENV_NUMBER']
if ENV['CI']
require 'simplecov-lcov'
formatter SimpleCov::Formatter::LcovFormatter
formatter.config.report_with_single_file = true
else
formatter SimpleCov::Formatter::HTMLFormatter
end
enable_coverage :branch
add_filter 'lib/linter'
add_group 'Libraries', 'lib'
add_group 'Policies', 'app/policies'
add_group 'Presenters', 'app/presenters'
add_group 'Search', 'app/chewy'
add_group 'Serializers', 'app/serializers'
add_group 'Services', 'app/services'
add_group 'Validators', 'app/validators'
end

View File

@ -4,13 +4,48 @@ module Admin
class CollectionsController < BaseController
before_action :set_account
before_action :set_collection, only: :show
before_action :set_collections, except: :show
PER_PAGE = 20
def index
authorize [:admin, :collection], :index?
@collection_batch_action = Admin::CollectionBatchAction.new
end
def show
authorize @collection, :show?
end
def batch
authorize [:admin, :collection], :index?
@collection_batch_action = Admin::CollectionBatchAction.new(admin_collection_batch_action_params.merge(current_account: current_account, report_id: params[:report_id], type: 'report'))
@collection_batch_action.save!
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.collections.no_collection_selected')
ensure
redirect_to after_create_redirect_path
end
private
def after_create_redirect_path
report_id = @collections_batch_action&.report_id || params[:report_id]
if report_id.present?
admin_report_path(report_id)
else
admin_account_collections_path(params[:account_id], params[:page])
end
end
def admin_collection_batch_action_params
params
.expect(admin_collection_batch_action: [collection_ids: []])
end
def set_account
@account = Account.find(params[:account_id])
end
@ -18,5 +53,9 @@ module Admin
def set_collection
@collection = @account.collections.includes(accepted_collection_items: :account).find(params[:id])
end
def set_collections
@collections = @account.collections.includes(accepted_collection_items: :account).page(params[:page]).per(PER_PAGE)
end
end
end

View File

@ -0,0 +1,145 @@
import { useCallback } from 'react';
import type { FC, ReactElement, ReactNode } from 'react';
import { FormattedMessage } from 'react-intl';
import {
authorizeFollowRequest,
rejectFollowRequest,
} from '@/flavours/glitch/actions/accounts';
import { useAccountVisibility } from '@/flavours/glitch/hooks/useAccountVisibility';
import { useRelationship } from '@/flavours/glitch/hooks/useRelationship';
import type { Account } from '@/flavours/glitch/models/account';
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { AvatarOverlay } from '../avatar_overlay';
import { Button } from '../button';
import { DisplayName } from '../display_name';
import { Icon } from '../icon';
import { Permalink } from '../permalink';
import classes from './styles.module.scss';
export const AccountBanners: FC<{ account: Account }> = ({ account }) => {
const { suspended, hidden } = useAccountVisibility(account.id);
const relationship = useRelationship(account.id);
if (hidden) {
return null;
}
let banner: ReactNode = null;
if (account.memorial) {
banner = (
<MessageText>
<FormattedMessage
id='account.in_memoriam'
defaultMessage='In Memoriam.'
/>
</MessageText>
);
}
if (account.moved) {
banner = <MovedNote account={account} targetAccountId={account.moved} />;
}
if (!suspended && relationship?.requested_by) {
banner = <FollowRequestNote account={account} />;
}
if (!banner) {
return null;
}
return <div className={classes.bannerWrapper}>{banner}</div>;
};
const FollowRequestNote: FC<{ account: Account }> = ({ account }) => {
const accountId = account.id;
const dispatch = useAppDispatch();
const handleAuthorize = useCallback(() => {
dispatch(authorizeFollowRequest(accountId));
}, [accountId, dispatch]);
const handleReject = useCallback(() => {
dispatch(rejectFollowRequest(accountId));
}, [accountId, dispatch]);
return (
<>
<MessageText>
<FormattedMessage
id='account.requested_follow'
defaultMessage='{name} has requested to follow you'
values={{ name: <DisplayName account={account} variant='simple' /> }}
/>
</MessageText>
<div className={classes.bannerActions}>
<Button secondary onClick={handleAuthorize}>
<Icon id='check' icon={CheckIcon} />
<FormattedMessage
id='follow_request.authorize'
defaultMessage='Authorize'
/>
</Button>
<Button secondary onClick={handleReject}>
<Icon id='times' icon={CloseIcon} />
<FormattedMessage
id='follow_request.reject'
defaultMessage='Reject'
/>
</Button>
</div>
</>
);
};
const MovedNote: React.FC<{
account: Account;
targetAccountId: string;
}> = ({ account: from, targetAccountId }) => {
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
return (
<>
<MessageText>
<FormattedMessage
id='account.moved_to'
defaultMessage='{name} has indicated that their new account is now:'
values={{
name: <DisplayName account={from} variant='simple' />,
}}
/>
</MessageText>
<div className={classes.bannerActions}>
<Permalink
to={`/@${to?.acct}`}
href={to?.url}
className={classes.bannerActionsDisplayName}
>
<div className='detailed-status__display-avatar'>
<AvatarOverlay account={to} friend={from} />
</div>
<DisplayName account={to} />
</Permalink>
<Permalink to={`/@${to?.acct}`} href={to?.url} className='button'>
<FormattedMessage
id='account.go_to_profile'
defaultMessage='Go to profile'
/>
</Permalink>
</div>
</>
);
};
const MessageText: React.FC<{ children: ReactElement }> = ({ children }) => (
<div className={classes.bannerText}>{children}</div>
);

View File

@ -5,7 +5,6 @@ import classNames from 'classnames';
import { Helmet } from '@unhead/react/helmet';
import { openModal } from '@/flavours/glitch/actions/modal';
import FollowRequestNoteContainer from '@/flavours/glitch/features/account/containers/follow_request_note_container';
import { useLayout } from '@/flavours/glitch/hooks/useLayout';
import { useVisibility } from '@/flavours/glitch/hooks/useVisibility';
import {
@ -22,10 +21,9 @@ import { Avatar } from '../avatar';
import { AnimateEmojiProvider } from '../emoji/context';
import { FamiliarFollowers } from '../familiar_followers';
import { AccountBanners } from './banners';
import { AccountButtons } from './buttons';
import { AccountHeaderFields } from './fields';
import { MemorialNote } from './memorial_note';
import { MovedNote } from './moved_note';
import { AccountName } from './name';
import { AccountNote } from './note';
import { AccountNumberFields } from './number_fields';
@ -51,9 +49,6 @@ export const AccountHeader: React.FC<{
}> = ({ accountId, hideTabs }) => {
const dispatch = useAppDispatch();
const account = useAppSelector((state) => state.accounts.get(accountId));
const relationship = useAppSelector((state) =>
state.relationships.get(accountId),
);
const hidden = useAppSelector((state) => getAccountHidden(state, accountId));
const handleOpenAvatar = useCallback(
@ -98,18 +93,11 @@ export const AccountHeader: React.FC<{
return (
<div>
{!hidden && account.memorial && <MemorialNote />}
{!hidden && account.moved && (
<MovedNote accountId={account.id} targetAccountId={account.moved} />
)}
<AccountBanners account={account} />
<AnimateEmojiProvider
className={classNames(!!account.moved && classes.moved)}
>
{!suspendedOrHidden && !account.moved && relationship?.requested_by && (
<FollowRequestNoteContainer account={account} />
)}
<div className={classes.header}>
{!suspendedOrHidden && (
<img

View File

@ -1,12 +0,0 @@
import { FormattedMessage } from 'react-intl';
export const MemorialNote: React.FC = () => (
<div className='account-memorial-banner'>
<div className='account-memorial-banner__message'>
<FormattedMessage
id='account.in_memoriam'
defaultMessage='In Memoriam.'
/>
</div>
</div>
);

View File

@ -1,49 +0,0 @@
import { FormattedMessage } from 'react-intl';
import { useAppSelector } from 'flavours/glitch/store';
import { AvatarOverlay } from '../avatar_overlay';
import { DisplayName } from '../display_name';
import { Permalink } from '../permalink';
export const MovedNote: React.FC<{
accountId: string;
targetAccountId: string;
}> = ({ accountId, targetAccountId }) => {
const from = useAppSelector((state) => state.accounts.get(accountId));
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
return (
<div className='moved-account-banner'>
<div className='moved-account-banner__message'>
<FormattedMessage
id='account.moved_to'
defaultMessage='{name} has indicated that their new account is now:'
values={{
name: <DisplayName account={from} variant='simple' />,
}}
/>
</div>
<div className='moved-account-banner__action'>
<Permalink
to={`/@${to?.acct}`}
href={to?.url}
className='detailed-status__display-name'
>
<div className='detailed-status__display-avatar'>
<AvatarOverlay account={to} friend={from} />
</div>
<DisplayName account={to} />
</Permalink>
<Permalink to={`/@${to?.acct}`} href={to?.url} className='button'>
<FormattedMessage
id='account.go_to_profile'
defaultMessage='Go to profile'
/>
</Permalink>
</div>
</div>
);
};

View File

@ -453,13 +453,24 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
border-bottom: 1px solid var(--color-border-primary);
}
// Banners
.bannerWrapper,
.bannerBase {
box-sizing: border-box;
padding: 16px;
border-radius: 12px;
background: var(--color-bg-secondary);
display: flex;
flex-direction: column;
}
.bannerWrapper {
background: var(--color-bg-tertiary);
padding: 16px;
align-items: center;
}
.bannerBase {
border-radius: 12px;
background: var(--color-bg-secondary);
gap: 12px;
justify-content: center;
align-items: flex-start;
@ -475,6 +486,13 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
}
}
.bannerText {
color: var(--color-text-secondary);
font-size: 14px;
font-weight: 500;
text-align: center;
}
.bannerTextAndActions {
display: flex;
flex-direction: column;
@ -489,6 +507,19 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
}
}
.bannerActions {
display: flex;
justify-content: space-between;
align-items: center;
gap: 15px;
width: 100%;
margin-top: 16px;
button {
width: 100%;
}
}
.bannerDisclaimer {
a {
color: inherit;
@ -519,6 +550,32 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
}
}
.bannerActionsDisplayName {
color: var(--color-text-secondary);
display: flex;
align-items: center;
gap: 10px;
font-size: 15px;
line-height: 22px;
overflow: hidden;
text-decoration: none;
&:hover strong {
text-decoration: underline;
}
strong,
span {
display: block;
text-overflow: ellipsis;
overflow: hidden;
}
strong {
color: var(--color-text-primary);
}
}
// Buttons
.followButton {

View File

@ -104,8 +104,6 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
.then(() => {
setSubmitting(false);
setSubmitted(true);
return '';
})
.catch((err: unknown) => {
setSubmitting(false);
@ -133,12 +131,11 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
className={classNames(classes.bannerBase, classes.bannerBaseCentered)}
>
<div className={classes.bannerTextAndActions}>
<h2>
<FormattedMessage
id='email_subscriptions.submitted.title'
defaultMessage='One more step'
/>
</h2>
<FormattedMessage
id='email_subscriptions.submitted.title'
defaultMessage='One more step'
tagName='h2'
/>
<FormattedMessage
id='email_subscriptions.submitted.lead'
defaultMessage='Check your inbox for an email to finish signing up for email updates.'
@ -151,15 +148,14 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
return (
<form onSubmit={handleSubmit} className={classes.bannerBase} noValidate>
<div className={classes.bannerTextAndActions}>
<h2>
<FormattedMessage
id='email_subscriptions.form.title'
defaultMessage='Sign up for email updates from {name}'
values={{
name: <DisplayName account={account} variant='simple' />,
}}
/>
</h2>
<FormattedMessage
id='email_subscriptions.form.title'
defaultMessage='Sign up for email updates from {name}'
tagName='h2'
values={{
name: <DisplayName account={account} variant='simple' />,
}}
/>
</div>
<div className={classes.bannerInputButton}>

View File

@ -1,131 +0,0 @@
import type { ChangeEventHandler, KeyboardEventHandler } from 'react';
import { useState, useRef, useCallback, useId } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import Textarea from 'react-textarea-autosize';
import { submitAccountNote } from '@/flavours/glitch/actions/account_notes';
import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator';
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
const messages = defineMessages({
placeholder: {
id: 'account_note.placeholder',
defaultMessage: 'Click to add a note',
},
});
const AccountNoteUI: React.FC<{
initialValue: string | undefined;
onSubmit: (newNote: string) => void;
wasSaved: boolean;
}> = ({ initialValue, onSubmit, wasSaved }) => {
const intl = useIntl();
const uniqueId = useId();
const [value, setValue] = useState(initialValue ?? '');
const isLoading = initialValue === undefined;
const canSubmitOnBlurRef = useRef(true);
const handleChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>(
(e) => {
setValue(e.target.value);
},
[],
);
const handleKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
(e) => {
if (e.key === 'Escape') {
e.preventDefault();
setValue(initialValue ?? '');
canSubmitOnBlurRef.current = false;
e.currentTarget.blur();
} else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
onSubmit(value);
canSubmitOnBlurRef.current = false;
e.currentTarget.blur();
}
},
[initialValue, onSubmit, value],
);
const handleBlur = useCallback(() => {
if (initialValue !== value && canSubmitOnBlurRef.current) {
onSubmit(value);
}
canSubmitOnBlurRef.current = true;
}, [initialValue, onSubmit, value]);
return (
<div className='account__header__account-note'>
<label htmlFor={`account-note-${uniqueId}`}>
<FormattedMessage
id='account.account_note_header'
defaultMessage='Personal note'
/>{' '}
<span
aria-live='polite'
role='status'
className='inline-alert'
style={{ opacity: wasSaved ? 1 : 0 }}
>
{wasSaved && (
<FormattedMessage id='generic.saved' defaultMessage='Saved' />
)}
</span>
</label>
{isLoading ? (
<div className='account__header__account-note__loading-indicator-wrapper'>
<LoadingIndicator />
</div>
) : (
<Textarea
id={`account-note-${uniqueId}`}
className='account__header__account-note__content'
placeholder={intl.formatMessage(messages.placeholder)}
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
/>
)}
</div>
);
};
export const AccountNote: React.FC<{
accountId: string;
}> = ({ accountId }) => {
const dispatch = useAppDispatch();
const initialValue = useAppSelector((state) =>
state.relationships.get(accountId)?.get('note'),
);
const [wasSaved, setWasSaved] = useState(false);
const handleSubmit = useCallback(
(note: string) => {
setWasSaved(true);
void dispatch(submitAccountNote({ accountId, note }));
setTimeout(() => {
setWasSaved(false);
}, 2000);
},
[dispatch, accountId],
);
return (
<AccountNoteUI
key={`${accountId}-${initialValue}`}
initialValue={initialValue}
wasSaved={wasSaved}
onSubmit={handleSubmit}
/>
);
};

View File

@ -1,210 +0,0 @@
import type { ReactNode } from 'react';
import { useState, useRef, useCallback, useId } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import Overlay from 'react-overlays/Overlay';
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
import BadgeIcon from '@/material-icons/400-24px/badge.svg?react';
import GlobeIcon from '@/material-icons/400-24px/globe.svg?react';
import { Icon } from 'flavours/glitch/components/icon';
export const DomainPill: React.FC<{
domain: string;
username: string;
isSelf: boolean;
children?: ReactNode;
className?: string;
}> = ({ domain, username, isSelf, children, className }) => {
const accessibilityId = useId();
const [open, setOpen] = useState(false);
const [expanded, setExpanded] = useState(false);
const triggerRef = useRef(null);
const handleClick = useCallback(() => {
setOpen(!open);
}, [open, setOpen]);
const handleExpandClick = useCallback(() => {
setExpanded(!expanded);
}, [expanded, setExpanded]);
return (
<>
<button
className={classNames('account__domain-pill', className, {
active: open,
})}
ref={triggerRef}
onClick={handleClick}
aria-expanded={open}
aria-controls={accessibilityId}
type='button'
>
{children ?? domain}
</button>
<Overlay
show={open}
rootClose
onHide={handleClick}
offset={[5, 5]}
target={triggerRef}
>
{({ props }) => (
<div
{...props}
role='region'
id={accessibilityId}
className='account__domain-pill__popout dropdown-animation'
>
<div className='account__domain-pill__popout__header'>
<div className='account__domain-pill__popout__header__icon'>
<Icon id='' icon={BadgeIcon} />
</div>
<h3>
<FormattedMessage
id='domain_pill.whats_in_a_handle'
defaultMessage="What's in a handle?"
/>
</h3>
</div>
<div className='account__domain-pill__popout__handle'>
<div className='account__domain-pill__popout__handle__label'>
{isSelf ? (
<FormattedMessage
id='domain_pill.your_handle'
defaultMessage='Your handle:'
/>
) : (
<FormattedMessage
id='domain_pill.their_handle'
defaultMessage='Their handle:'
/>
)}
</div>
<div className='account__domain-pill__popout__handle__handle'>
@{username}@{domain}
</div>
</div>
<div className='account__domain-pill__popout__parts'>
<div>
<div className='account__domain-pill__popout__parts__icon'>
<Icon id='' icon={AlternateEmailIcon} />
</div>
<div>
<h6>
<FormattedMessage
id='domain_pill.username'
defaultMessage='Username'
/>
</h6>
<p>
{isSelf ? (
<FormattedMessage
id='domain_pill.your_username'
defaultMessage='Your unique identifier on this server. Its possible to find users with the same username on different servers.'
/>
) : (
<FormattedMessage
id='domain_pill.their_username'
defaultMessage='Their unique identifier on their server. Its possible to find users with the same username on different servers.'
/>
)}
</p>
</div>
</div>
<div>
<div className='account__domain-pill__popout__parts__icon'>
<Icon id='' icon={GlobeIcon} />
</div>
<div>
<h6>
<FormattedMessage
id='domain_pill.server'
defaultMessage='Server'
/>
</h6>
<p>
{isSelf ? (
<FormattedMessage
id='domain_pill.your_server'
defaultMessage='Your digital home, where all of your posts live. Dont like this one? Transfer servers at any time and bring your followers, too.'
/>
) : (
<FormattedMessage
id='domain_pill.their_server'
defaultMessage='Their digital home, where all of their posts live.'
/>
)}
</p>
</div>
</div>
</div>
<p>
{isSelf ? (
<FormattedMessage
id='domain_pill.who_you_are'
defaultMessage='Because your handle says who you are and where you are, people can interact with you across the social web of <button>ActivityPub-powered platforms</button>.'
values={{
button: (x) => (
<button
onClick={handleExpandClick}
className='link-button'
type='button'
>
{x}
</button>
),
}}
/>
) : (
<FormattedMessage
id='domain_pill.who_they_are'
defaultMessage='Since handles say who someone is and where they are, you can interact with people across the social web of <button>ActivityPub-powered platforms</button>.'
values={{
button: (x) => (
<button
onClick={handleExpandClick}
className='link-button'
type='button'
>
{x}
</button>
),
}}
/>
)}
</p>
{expanded && (
<>
<p>
<FormattedMessage
id='domain_pill.activitypub_like_language'
defaultMessage='ActivityPub is like the language Mastodon speaks with other social networks.'
/>
</p>
<p>
<FormattedMessage
id='domain_pill.activitypub_lets_connect'
defaultMessage='It lets you connect and interact with people not just on Mastodon, but across different social apps too.'
/>
</p>
</>
)}
</div>
)}
</Overlay>
</>
);
};

View File

@ -1,41 +0,0 @@
import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { Icon } from 'flavours/glitch/components/icon';
import { DisplayName } from '@/flavours/glitch/components/display_name';
export default class FollowRequestNote extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.record.isRequired,
};
render () {
const { account, onAuthorize, onReject } = this.props;
return (
<div className='follow-request-banner'>
<div className='follow-request-banner__message'>
<FormattedMessage id='account.requested_follow' defaultMessage='{name} has requested to follow you' values={{ name: <DisplayName account={account} variant='simple' /> }} />
</div>
<div className='follow-request-banner__action'>
<button type='button' className='button button-secondary button--confirmation' onClick={onAuthorize}>
<Icon id='check' icon={CheckIcon} />
<FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' />
</button>
<button type='button' className='button button-secondary button--destructive' onClick={onReject}>
<Icon id='times' icon={CloseIcon} />
<FormattedMessage id='follow_request.reject' defaultMessage='Reject' />
</button>
</div>
</div>
);
}
}

View File

@ -1,17 +0,0 @@
import { connect } from 'react-redux';
import { authorizeFollowRequest, rejectFollowRequest } from 'flavours/glitch/actions/accounts';
import FollowRequestNote from '../components/follow_request_note';
const mapDispatchToProps = (dispatch, { account }) => ({
onAuthorize () {
dispatch(authorizeFollowRequest(account.get('id')));
},
onReject () {
dispatch(rejectFollowRequest(account.get('id')));
},
});
export default connect(null, mapDispatchToProps)(FollowRequestNote);

View File

@ -8556,47 +8556,6 @@ noscript {
}
}
.moved-account-banner,
.follow-request-banner,
.account-memorial-banner {
padding: 20px;
background: var(--color-bg-tertiary);
display: flex;
align-items: center;
flex-direction: column;
&__message {
color: var(--color-text-secondary);
padding: 8px 0;
padding-top: 0;
padding-bottom: 4px;
font-size: 14px;
font-weight: 500;
text-align: center;
margin-bottom: 16px;
}
&__action {
display: flex;
justify-content: space-between;
align-items: center;
gap: 15px;
width: 100%;
}
.detailed-status__display-name {
margin-bottom: 0;
}
}
.follow-request-banner .button {
width: 100%;
}
.account-memorial-banner__message {
margin-bottom: 0;
}
.column-inline-form {
padding: 15px;
display: flex;

View File

@ -0,0 +1,140 @@
import { useCallback } from 'react';
import type { FC, ReactElement, ReactNode } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import {
authorizeFollowRequest,
rejectFollowRequest,
} from '@/mastodon/actions/accounts';
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
import { useRelationship } from '@/mastodon/hooks/useRelationship';
import type { Account } from '@/mastodon/models/account';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { AvatarOverlay } from '../avatar_overlay';
import { Button } from '../button';
import { DisplayName } from '../display_name';
import { Icon } from '../icon';
import classes from './styles.module.scss';
export const AccountBanners: FC<{ account: Account }> = ({ account }) => {
const { suspended, hidden } = useAccountVisibility(account.id);
const relationship = useRelationship(account.id);
if (hidden) {
return null;
}
let banner: ReactNode = null;
if (account.memorial) {
banner = (
<MessageText>
<FormattedMessage
id='account.in_memoriam'
defaultMessage='In Memoriam.'
/>
</MessageText>
);
}
if (account.moved) {
banner = <MovedNote account={account} targetAccountId={account.moved} />;
}
if (!suspended && relationship?.requested_by) {
banner = <FollowRequestNote account={account} />;
}
if (!banner) {
return null;
}
return <div className={classes.bannerWrapper}>{banner}</div>;
};
const FollowRequestNote: FC<{ account: Account }> = ({ account }) => {
const accountId = account.id;
const dispatch = useAppDispatch();
const handleAuthorize = useCallback(() => {
dispatch(authorizeFollowRequest(accountId));
}, [accountId, dispatch]);
const handleReject = useCallback(() => {
dispatch(rejectFollowRequest(accountId));
}, [accountId, dispatch]);
return (
<>
<MessageText>
<FormattedMessage
id='account.requested_follow'
defaultMessage='{name} has requested to follow you'
values={{ name: <DisplayName account={account} variant='simple' /> }}
/>
</MessageText>
<div className={classes.bannerActions}>
<Button secondary onClick={handleAuthorize}>
<Icon id='check' icon={CheckIcon} />
<FormattedMessage
id='follow_request.authorize'
defaultMessage='Authorize'
/>
</Button>
<Button secondary onClick={handleReject}>
<Icon id='times' icon={CloseIcon} />
<FormattedMessage
id='follow_request.reject'
defaultMessage='Reject'
/>
</Button>
</div>
</>
);
};
const MovedNote: React.FC<{
account: Account;
targetAccountId: string;
}> = ({ account: from, targetAccountId }) => {
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
return (
<>
<MessageText>
<FormattedMessage
id='account.moved_to'
defaultMessage='{name} has indicated that their new account is now:'
values={{
name: <DisplayName account={from} variant='simple' />,
}}
/>
</MessageText>
<div className={classes.bannerActions}>
<Link to={`/@${to?.acct}`} className={classes.bannerActionsDisplayName}>
<AvatarOverlay account={to} friend={from} />
<DisplayName account={to} />
</Link>
<Link to={`/@${to?.acct}`} className='button'>
<FormattedMessage
id='account.go_to_profile'
defaultMessage='Go to profile'
/>
</Link>
</div>
</>
);
};
const MessageText: React.FC<{ children: ReactElement }> = ({ children }) => (
<div className={classes.bannerText}>{children}</div>
);

View File

@ -5,7 +5,6 @@ import classNames from 'classnames';
import { Helmet } from '@unhead/react/helmet';
import { openModal } from '@/mastodon/actions/modal';
import FollowRequestNoteContainer from '@/mastodon/features/account/containers/follow_request_note_container';
import { useLayout } from '@/mastodon/hooks/useLayout';
import { useVisibility } from '@/mastodon/hooks/useVisibility';
import {
@ -22,10 +21,9 @@ import { Avatar } from '../avatar';
import { AnimateEmojiProvider } from '../emoji/context';
import { FamiliarFollowers } from '../familiar_followers';
import { AccountBanners } from './banners';
import { AccountButtons } from './buttons';
import { AccountHeaderFields } from './fields';
import { MemorialNote } from './memorial_note';
import { MovedNote } from './moved_note';
import { AccountName } from './name';
import { AccountNote } from './note';
import { AccountNumberFields } from './number_fields';
@ -51,9 +49,6 @@ export const AccountHeader: React.FC<{
}> = ({ accountId, hideTabs }) => {
const dispatch = useAppDispatch();
const account = useAppSelector((state) => state.accounts.get(accountId));
const relationship = useAppSelector((state) =>
state.relationships.get(accountId),
);
const hidden = useAppSelector((state) => getAccountHidden(state, accountId));
const handleOpenAvatar = useCallback(
@ -98,18 +93,11 @@ export const AccountHeader: React.FC<{
return (
<div>
{!hidden && account.memorial && <MemorialNote />}
{!hidden && account.moved && (
<MovedNote accountId={account.id} targetAccountId={account.moved} />
)}
<AccountBanners account={account} />
<AnimateEmojiProvider
className={classNames(!!account.moved && classes.moved)}
>
{!suspendedOrHidden && !account.moved && relationship?.requested_by && (
<FollowRequestNoteContainer account={account} />
)}
<div className={classes.header}>
{!suspendedOrHidden && (
<img

View File

@ -1,12 +0,0 @@
import { FormattedMessage } from 'react-intl';
export const MemorialNote: React.FC = () => (
<div className='account-memorial-banner'>
<div className='account-memorial-banner__message'>
<FormattedMessage
id='account.in_memoriam'
defaultMessage='In Memoriam.'
/>
</div>
</div>
);

View File

@ -1,46 +0,0 @@
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { useAppSelector } from '@/mastodon/store';
import { AvatarOverlay } from '../avatar_overlay';
import { DisplayName } from '../display_name';
export const MovedNote: React.FC<{
accountId: string;
targetAccountId: string;
}> = ({ accountId, targetAccountId }) => {
const from = useAppSelector((state) => state.accounts.get(accountId));
const to = useAppSelector((state) => state.accounts.get(targetAccountId));
return (
<div className='moved-account-banner'>
<div className='moved-account-banner__message'>
<FormattedMessage
id='account.moved_to'
defaultMessage='{name} has indicated that their new account is now:'
values={{
name: <DisplayName account={from} variant='simple' />,
}}
/>
</div>
<div className='moved-account-banner__action'>
<Link to={`/@${to?.acct}`} className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'>
<AvatarOverlay account={to} friend={from} />
</div>
<DisplayName account={to} />
</Link>
<Link to={`/@${to?.acct}`} className='button'>
<FormattedMessage
id='account.go_to_profile'
defaultMessage='Go to profile'
/>
</Link>
</div>
</div>
);
};

View File

@ -453,13 +453,24 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
border-bottom: 1px solid var(--color-border-primary);
}
// Banners
.bannerWrapper,
.bannerBase {
box-sizing: border-box;
padding: 16px;
border-radius: 12px;
background: var(--color-bg-secondary);
display: flex;
flex-direction: column;
}
.bannerWrapper {
background: var(--color-bg-tertiary);
padding: 16px;
align-items: center;
}
.bannerBase {
border-radius: 12px;
background: var(--color-bg-secondary);
gap: 12px;
justify-content: center;
align-items: flex-start;
@ -475,6 +486,13 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
}
}
.bannerText {
color: var(--color-text-secondary);
font-size: 14px;
font-weight: 500;
text-align: center;
}
.bannerTextAndActions {
display: flex;
flex-direction: column;
@ -489,6 +507,19 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
}
}
.bannerActions {
display: flex;
justify-content: space-between;
align-items: center;
gap: 15px;
width: 100%;
margin-top: 16px;
button {
width: 100%;
}
}
.bannerDisclaimer {
a {
color: inherit;
@ -519,6 +550,32 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
}
}
.bannerActionsDisplayName {
color: var(--color-text-secondary);
display: flex;
align-items: center;
gap: 10px;
font-size: 15px;
line-height: 22px;
overflow: hidden;
text-decoration: none;
&:hover strong {
text-decoration: underline;
}
strong,
span {
display: block;
text-overflow: ellipsis;
overflow: hidden;
}
strong {
color: var(--color-text-primary);
}
}
// Buttons
.followButton {

View File

@ -104,8 +104,6 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
.then(() => {
setSubmitting(false);
setSubmitted(true);
return '';
})
.catch((err: unknown) => {
setSubmitting(false);
@ -133,12 +131,11 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
className={classNames(classes.bannerBase, classes.bannerBaseCentered)}
>
<div className={classes.bannerTextAndActions}>
<h2>
<FormattedMessage
id='email_subscriptions.submitted.title'
defaultMessage='One more step'
/>
</h2>
<FormattedMessage
id='email_subscriptions.submitted.title'
defaultMessage='One more step'
tagName='h2'
/>
<FormattedMessage
id='email_subscriptions.submitted.lead'
defaultMessage='Check your inbox for an email to finish signing up for email updates.'
@ -151,15 +148,14 @@ export const AccountSubscriptionForm: React.FC<{ accountId: string }> = ({
return (
<form onSubmit={handleSubmit} className={classes.bannerBase} noValidate>
<div className={classes.bannerTextAndActions}>
<h2>
<FormattedMessage
id='email_subscriptions.form.title'
defaultMessage='Sign up for email updates from {name}'
values={{
name: <DisplayName account={account} variant='simple' />,
}}
/>
</h2>
<FormattedMessage
id='email_subscriptions.form.title'
defaultMessage='Sign up for email updates from {name}'
tagName='h2'
values={{
name: <DisplayName account={account} variant='simple' />,
}}
/>
</div>
<div className={classes.bannerInputButton}>

View File

@ -1,131 +0,0 @@
import type { ChangeEventHandler, KeyboardEventHandler } from 'react';
import { useState, useRef, useCallback, useId } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import Textarea from 'react-textarea-autosize';
import { submitAccountNote } from '@/mastodon/actions/account_notes';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
const messages = defineMessages({
placeholder: {
id: 'account_note.placeholder',
defaultMessage: 'Click to add a note',
},
});
const AccountNoteUI: React.FC<{
initialValue: string | undefined;
onSubmit: (newNote: string) => void;
wasSaved: boolean;
}> = ({ initialValue, onSubmit, wasSaved }) => {
const intl = useIntl();
const uniqueId = useId();
const [value, setValue] = useState(initialValue ?? '');
const isLoading = initialValue === undefined;
const canSubmitOnBlurRef = useRef(true);
const handleChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>(
(e) => {
setValue(e.target.value);
},
[],
);
const handleKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
(e) => {
if (e.key === 'Escape') {
e.preventDefault();
setValue(initialValue ?? '');
canSubmitOnBlurRef.current = false;
e.currentTarget.blur();
} else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
onSubmit(value);
canSubmitOnBlurRef.current = false;
e.currentTarget.blur();
}
},
[initialValue, onSubmit, value],
);
const handleBlur = useCallback(() => {
if (initialValue !== value && canSubmitOnBlurRef.current) {
onSubmit(value);
}
canSubmitOnBlurRef.current = true;
}, [initialValue, onSubmit, value]);
return (
<div className='account__header__account-note'>
<label htmlFor={`account-note-${uniqueId}`}>
<FormattedMessage
id='account.account_note_header'
defaultMessage='Personal note'
/>{' '}
<span
aria-live='polite'
role='status'
className='inline-alert'
style={{ opacity: wasSaved ? 1 : 0 }}
>
{wasSaved && (
<FormattedMessage id='generic.saved' defaultMessage='Saved' />
)}
</span>
</label>
{isLoading ? (
<div className='account__header__account-note__loading-indicator-wrapper'>
<LoadingIndicator />
</div>
) : (
<Textarea
id={`account-note-${uniqueId}`}
className='account__header__account-note__content'
placeholder={intl.formatMessage(messages.placeholder)}
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
/>
)}
</div>
);
};
export const AccountNote: React.FC<{
accountId: string;
}> = ({ accountId }) => {
const dispatch = useAppDispatch();
const initialValue = useAppSelector((state) =>
state.relationships.get(accountId)?.get('note'),
);
const [wasSaved, setWasSaved] = useState(false);
const handleSubmit = useCallback(
(note: string) => {
setWasSaved(true);
void dispatch(submitAccountNote({ accountId, note }));
setTimeout(() => {
setWasSaved(false);
}, 2000);
},
[dispatch, accountId],
);
return (
<AccountNoteUI
key={`${accountId}-${initialValue}`}
initialValue={initialValue}
wasSaved={wasSaved}
onSubmit={handleSubmit}
/>
);
};

View File

@ -1,210 +0,0 @@
import type { ReactNode } from 'react';
import { useState, useRef, useCallback, useId } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import Overlay from 'react-overlays/Overlay';
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
import BadgeIcon from '@/material-icons/400-24px/badge.svg?react';
import GlobeIcon from '@/material-icons/400-24px/globe.svg?react';
import { Icon } from 'mastodon/components/icon';
export const DomainPill: React.FC<{
domain: string;
username: string;
isSelf: boolean;
children?: ReactNode;
className?: string;
}> = ({ domain, username, isSelf, children, className }) => {
const accessibilityId = useId();
const [open, setOpen] = useState(false);
const [expanded, setExpanded] = useState(false);
const triggerRef = useRef(null);
const handleClick = useCallback(() => {
setOpen(!open);
}, [open, setOpen]);
const handleExpandClick = useCallback(() => {
setExpanded(!expanded);
}, [expanded, setExpanded]);
return (
<>
<button
className={classNames('account__domain-pill', className, {
active: open,
})}
ref={triggerRef}
onClick={handleClick}
aria-expanded={open}
aria-controls={accessibilityId}
type='button'
>
{children ?? domain}
</button>
<Overlay
show={open}
rootClose
onHide={handleClick}
offset={[5, 5]}
target={triggerRef}
>
{({ props }) => (
<div
{...props}
role='region'
id={accessibilityId}
className='account__domain-pill__popout dropdown-animation'
>
<div className='account__domain-pill__popout__header'>
<div className='account__domain-pill__popout__header__icon'>
<Icon id='' icon={BadgeIcon} />
</div>
<h3>
<FormattedMessage
id='domain_pill.whats_in_a_handle'
defaultMessage="What's in a handle?"
/>
</h3>
</div>
<div className='account__domain-pill__popout__handle'>
<div className='account__domain-pill__popout__handle__label'>
{isSelf ? (
<FormattedMessage
id='domain_pill.your_handle'
defaultMessage='Your handle:'
/>
) : (
<FormattedMessage
id='domain_pill.their_handle'
defaultMessage='Their handle:'
/>
)}
</div>
<div className='account__domain-pill__popout__handle__handle'>
@{username}@{domain}
</div>
</div>
<div className='account__domain-pill__popout__parts'>
<div>
<div className='account__domain-pill__popout__parts__icon'>
<Icon id='' icon={AlternateEmailIcon} />
</div>
<div>
<h6>
<FormattedMessage
id='domain_pill.username'
defaultMessage='Username'
/>
</h6>
<p>
{isSelf ? (
<FormattedMessage
id='domain_pill.your_username'
defaultMessage='Your unique identifier on this server. Its possible to find users with the same username on different servers.'
/>
) : (
<FormattedMessage
id='domain_pill.their_username'
defaultMessage='Their unique identifier on their server. Its possible to find users with the same username on different servers.'
/>
)}
</p>
</div>
</div>
<div>
<div className='account__domain-pill__popout__parts__icon'>
<Icon id='' icon={GlobeIcon} />
</div>
<div>
<h6>
<FormattedMessage
id='domain_pill.server'
defaultMessage='Server'
/>
</h6>
<p>
{isSelf ? (
<FormattedMessage
id='domain_pill.your_server'
defaultMessage='Your digital home, where all of your posts live. Dont like this one? Transfer servers at any time and bring your followers, too.'
/>
) : (
<FormattedMessage
id='domain_pill.their_server'
defaultMessage='Their digital home, where all of their posts live.'
/>
)}
</p>
</div>
</div>
</div>
<p>
{isSelf ? (
<FormattedMessage
id='domain_pill.who_you_are'
defaultMessage='Because your handle says who you are and where you are, people can interact with you across the social web of <button>ActivityPub-powered platforms</button>.'
values={{
button: (x) => (
<button
onClick={handleExpandClick}
className='link-button'
type='button'
>
{x}
</button>
),
}}
/>
) : (
<FormattedMessage
id='domain_pill.who_they_are'
defaultMessage='Since handles say who someone is and where they are, you can interact with people across the social web of <button>ActivityPub-powered platforms</button>.'
values={{
button: (x) => (
<button
onClick={handleExpandClick}
className='link-button'
type='button'
>
{x}
</button>
),
}}
/>
)}
</p>
{expanded && (
<>
<p>
<FormattedMessage
id='domain_pill.activitypub_like_language'
defaultMessage='ActivityPub is like the language Mastodon speaks with other social networks.'
/>
</p>
<p>
<FormattedMessage
id='domain_pill.activitypub_lets_connect'
defaultMessage='It lets you connect and interact with people not just on Mastodon, but across different social apps too.'
/>
</p>
</>
)}
</div>
)}
</Overlay>
</>
);
};

View File

@ -1,41 +0,0 @@
import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { Icon } from 'mastodon/components/icon';
import { DisplayName } from '@/mastodon/components/display_name';
export default class FollowRequestNote extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.record.isRequired,
};
render () {
const { account, onAuthorize, onReject } = this.props;
return (
<div className='follow-request-banner'>
<div className='follow-request-banner__message'>
<FormattedMessage id='account.requested_follow' defaultMessage='{name} has requested to follow you' values={{ name: <DisplayName account={account} variant='simple' /> }} />
</div>
<div className='follow-request-banner__action'>
<button type='button' className='button button-secondary button--confirmation' onClick={onAuthorize}>
<Icon id='check' icon={CheckIcon} />
<FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' />
</button>
<button type='button' className='button button-secondary button--destructive' onClick={onReject}>
<Icon id='times' icon={CloseIcon} />
<FormattedMessage id='follow_request.reject' defaultMessage='Reject' />
</button>
</div>
</div>
);
}
}

View File

@ -1,17 +0,0 @@
import { connect } from 'react-redux';
import { authorizeFollowRequest, rejectFollowRequest } from 'mastodon/actions/accounts';
import FollowRequestNote from '../components/follow_request_note';
const mapDispatchToProps = (dispatch, { account }) => ({
onAuthorize () {
dispatch(authorizeFollowRequest(account.get('id')));
},
onReject () {
dispatch(rejectFollowRequest(account.get('id')));
},
});
export default connect(null, mapDispatchToProps)(FollowRequestNote);

View File

@ -55,6 +55,7 @@
"account.go_to_profile": "Gwelet ar profil",
"account.hide_reblogs": "Kuzh skignadennoù gant @{name}",
"account.in_memoriam": "E koun.",
"account.join_modal.day": "Deiz",
"account.join_modal.years": "{number, plural, two {vloavezh} many {a vloavezhioù} other {bloavezh}}",
"account.joined_short": "Amañ abaoe",
"account.languages": "Cheñch ar yezhoù koumanantet",
@ -100,6 +101,7 @@
"account_edit.image_edit.add_button": "Ouzhpennañ ur skeudenn",
"account_edit.image_edit.remove_button": "Dilemel ar skeudenn",
"account_edit.item_list.delete": "Dilemel {name}",
"account_edit.item_list.edit": "Cheñch {name}",
"account_edit.save": "Enrollañ",
"account_edit.upload_modal.back": "Distreiñ",
"account_edit.upload_modal.step_upload.header": "Dibab ur skeudenn",

View File

@ -257,7 +257,6 @@
"account_edit_tags.tag_status_count": "{count, plural, one {# post} other {# posts}}",
"account_list.hidden_notice": "This is only visible to you. To show this list to others, go to <link>{page} > {modal} > {field}</link>.",
"account_list.total": "{total, plural, one {# account} other {# accounts}}",
"account_note.placeholder": "Click to add note",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
"admin.dashboard.retention.average": "Average",
@ -596,19 +595,6 @@
"domain_block_modal.you_will_lose_num_followers": "You will lose {followersCount, plural, one {{followersCountDisplay} follower} other {{followersCountDisplay} followers}} and {followingCount, plural, one {{followingCountDisplay} person you follow} other {{followingCountDisplay} people you follow}}.",
"domain_block_modal.you_will_lose_relationships": "You will lose all followers and people you follow from this server.",
"domain_block_modal.you_wont_see_posts": "You won't see posts or notifications from users on this server.",
"domain_pill.activitypub_lets_connect": "It lets you connect and interact with people not just on Mastodon, but across different social apps too.",
"domain_pill.activitypub_like_language": "ActivityPub is like the language Mastodon speaks with other social networks.",
"domain_pill.server": "Server",
"domain_pill.their_handle": "Their handle:",
"domain_pill.their_server": "Their digital home, where all of their posts live.",
"domain_pill.their_username": "Their unique identifier on their server. Its possible to find users with the same username on different servers.",
"domain_pill.username": "Username",
"domain_pill.whats_in_a_handle": "What's in a handle?",
"domain_pill.who_they_are": "Since handles say who someone is and where they are, you can interact with people across the social web of <button>ActivityPub-powered platforms</button>.",
"domain_pill.who_you_are": "Because your handle says who you are and where you are, people can interact with you across the social web of <button>ActivityPub-powered platforms</button>.",
"domain_pill.your_handle": "Your handle:",
"domain_pill.your_server": "Your digital home, where all of your posts live. Dont like this one? Transfer servers at any time and bring your followers, too.",
"domain_pill.your_username": "Your unique identifier on this server. Its possible to find users with the same username on different servers.",
"dropdown.empty": "Select an option",
"email_subscriptions.email": "Email",
"email_subscriptions.form.action": "Subscribe",
@ -745,7 +731,6 @@
"footer.terms_of_service": "Terms of service",
"form_error.blank": "Field cannot be blank.",
"form_field.optional": "(optional)",
"generic.saved": "Saved",
"getting_started.heading": "Getting started",
"hashtag.admin_moderation": "Open moderation interface for #{name}",
"hashtag.browse": "Browse posts in #{hashtag}",

View File

@ -79,7 +79,7 @@
"account.join_modal.share.intro": "Compartí un mensaje introductorio",
"account.join_modal.share.welcome": "Compartí un mensaje de bienvenida",
"account.join_modal.years": "{number, plural, one {# año} other {# años}}",
"account.joined_short": "Se unió",
"account.joined_short": "En este servidor desde",
"account.languages": "Cambiar idiomas suscritos",
"account.last_active": "Última actividad",
"account.link_verified_on": "La propiedad de este enlace fue verificada el {date}",

View File

@ -605,6 +605,9 @@
"dropdown.empty": "انتخاب یک گزینه",
"email_subscriptions.email": "رایانامه",
"email_subscriptions.form.action": "اشتراک",
"email_subscriptions.submitted.title": "یک گام دیگر",
"email_subscriptions.validation.email.blocked": "فراهم کنندهٔ رایانامهٔ مسدود",
"email_subscriptions.validation.email.invalid": "نشانی رایانامه نامعتبر",
"embed.instructions": "جاسازی این فرسته روی پایگاهتان با رونوشت کردن کد زیر.",
"embed.preview": "این گونه دیده خواهد شد:",
"emoji_button.activity": "فعالیت",
@ -622,7 +625,10 @@
"emoji_button.search_results": "نتایج جست‌وجو",
"emoji_button.symbols": "نمادها",
"emoji_button.travel": "سفر و مکان",
"empty_column.account_featured.other": "{acct} هنوز چیزی را معرّفی نکرده.",
"empty_column.account_featured_self.no_collections_button": "ایجاد مجموعه",
"empty_column.account_featured_self.no_collections_hide_tab": "نهفتن این زبانه",
"empty_column.account_featured_self.pre_collections": "منتظر مجموعه‌ها باشید",
"empty_column.account_featured_self.showcase_accounts": "نمایشکاه حساب‌های محبوبتان",
"empty_column.account_featured_unknown.other": "این حساب هنوز چیزی را معرّفی نکرده.",
"empty_column.account_hides_collections": "کاربر خواسته که این اطّلاعات در دسترس نباشند",
@ -791,6 +797,7 @@
"keyboard_shortcuts.direct": "گشودن ستون اشاره‌های خصوصی",
"keyboard_shortcuts.down": "پایین بردن در سیاهه",
"keyboard_shortcuts.enter": "گشودن فرسته",
"keyboard_shortcuts.explore": "گشودن خط زمانی داغ",
"keyboard_shortcuts.favourite": "پسندیدن فرسته",
"keyboard_shortcuts.favourites": "گشودن فهرست برگزیده‌ها",
"keyboard_shortcuts.federated": "گشودن خط زمانی همگانی",
@ -1060,6 +1067,7 @@
"privacy.private.short": "پی‌گیرندگان",
"privacy.public.long": "هرکسی در و بیرون از ماستودون",
"privacy.public.short": "عمومی",
"privacy.quote.anyone": "{visibility}، نقل مجاز",
"privacy.quote.disabled": "{visibility}، نقل‌ها از کار افتاده",
"privacy.quote.limited": "{visibility}، نقل‌ها محدود شده",
"privacy.unlisted.additional": "درست مثل عمومی رفتار می‌کند؛ جز این که فرسته در برچسب‌ها یا خوراک‌های زنده، کشف یا جست‌وجوی ماستودون ظاهر نخواهد شد. حتا اگر کلیّت نمایه‌تان اجازه داده باشد.",
@ -1105,6 +1113,7 @@
"report.category.title_account": "نمایه",
"report.category.title_status": "فرسته",
"report.close": "انجام شد",
"report.collection_comment": "چرا می‌خواهید این مجموعه را گزارش دهید؟",
"report.comment.title": "آیا چیز دیگری هست که فکر می‌کنید باید بدانیم؟",
"report.forward": "فرستادن به {target}",
"report.forward_hint": "این حساب در کارساز دیگری ثبت شده. آیا می‌خواهید رونوشتی ناشناس از این گزارش به آن‌جا هم فرستاده شود؟",
@ -1126,6 +1135,8 @@
"report.rules.title": "کدام قوانین نقض شده‌اند؟",
"report.statuses.subtitle": "همهٔ موارد انجام شده را برگزینید",
"report.statuses.title": "آیا فرسته‌ای وجود دارد که از این گزارش پشتیبانی کند؟",
"report.submission_error": "گزارش نتوانست ثبت شود",
"report.submission_error_details": "لزفاً اتّصال شبکه‌تان را بررسی کرده و دوباره تلاش کنید.",
"report.submit": "فرستادن",
"report.target": "گزارش کردن {target}",
"report.thanks.take_action": "در اینجا گزینه‌هایی برای کنترل آنچه در ماستودون میبینید، وجود دارد:",
@ -1173,12 +1184,15 @@
"server_banner.active_users": "کاربر فعّال",
"server_banner.administered_by": "به مدیریت:",
"server_banner.is_one_of_many": "{domain} یکی از بسیاری از سرورهای مستقل ماستودون است که می توانید از آن برای شرکت در fediverse استفاده کنید.",
"server_banner.more_about_this_server": "بیش‌تر دربارهٔ این کارساز",
"server_banner.server_stats": "آمار کارساز:",
"sign_in_banner.create_account": "ایجاد حساب",
"sign_in_banner.follow_anyone": "هر کسی را در سراسر فدیورس دنبال کنید و همه را به ترتیب زمانی ببینید. هیچ الگوریتم، تبلیغات یا طعمه کلیکی در چشم نیست.",
"sign_in_banner.mastodon_is": "ماستودون بهترین راه برای پیگیری اتفاقات است.",
"sign_in_banner.sign_in": "ورود",
"sign_in_banner.sso_redirect": "ورود یا ثبت نام",
"skip_links.skip_to_content": "پرش به محتوای اصلی",
"skip_links.skip_to_navigation": "پرش به پیمایش اصلی",
"status.admin_account": "گشودن واسط مدیریت برای @{name}",
"status.admin_domain": "گشودن واسط مدیریت برای {domain}",
"status.admin_status": "گشودن این فرسته در واسط مدیریت",

View File

@ -328,11 +328,15 @@
"annual_report.summary.share_on_mastodon": "לשתף במסטודון",
"attachments_list.unprocessed": "(לא מעובד)",
"audio.hide": "השתק",
"block_modal.no_collections": "שתיכן/שניכם לא יכוליםות להוסיף זו את זה לאוספים. אתן תוסרו אוטומטית מהאוספים האחד של השניה, אם הופעתן בהם.",
"block_modal.remote_users_caveat": "אנו נבקש מהשרת {domain} לכבד את החלטתך. עם זאת, ציות למוסכמות איננו מובטח כיוון ששרתים מסויימים עשויים לטפל בחסימות בצורה אחרת. הודעות פומביות עדיין יהיו גלויות לעיני משתמשים שאינם מחוברים.",
"block_modal.show_less": "הצג פחות",
"block_modal.show_more": "הצג עוד",
"block_modal.they_cant_mention": "לא תוכלו לאזכר, לעקוב או לצטט האחת את השני.",
"block_modal.they_cant_see_posts": "הם לא יכולים לראות את תכניכן ואתם לא תוכלו לראות את שלהם.",
"block_modal.they_will_know": "הם יכולים לראות שהם חסומים.",
"block_modal.title": "לחסום משתמש?",
"block_modal.you_wont_see_mentions": "לא תראה הודעות מאחרים שמאזכרות אותם.",
"boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
"boost_modal.reblog": "להדהד הודעה?",
"boost_modal.undo_reblog": "להסיר הדהוד?",
@ -792,6 +796,7 @@
"info_button.label": "עזרה",
"info_button.what_is_alt_text": "<h1>מהו כיתוב חלופי?</h1> <p>כיתוב חלופי משמש תיאור מילולי של תמונות לסובלים ממגבלות ראיה, חיבורי רשת איטיים, או אלו הצריכים הקשר יותר מפורט לתוכן המולטימדיה המצורף.</p> <p>ניתן לשפר את הנגישות והבנת התוכן לכולם ע\"י כתיבת תיאור ברור, תמציתי ונטול פניות.</p> <ul> <li>כיסוי היסודות החשובים</li> <li>סיכום המלל שבתמונות</li> <li>שימוש במבנה משפטים רגיל</li> <li>יש להמנע מחזרה על מידע</li> <li>אם העזרים הויזואליים הם דיאגרמות או מפות, התמקדו במגמות וממצאים מרכזיים.</li> </ul>",
"interaction_modal.action": "כדי להגיב להודעה של {name}, עליך להזהות בשם המשתמש שלך בשרת המסטודון בו את.ה משתמש.ת.",
"interaction_modal.action_follow": "כדי לעקוב אחרי {name}, עליך להזהות בשם המשתמש שלך בשרת המסטודון בו את.ה משתמש.ת.",
"interaction_modal.go": "המשך",
"interaction_modal.no_account_yet": "אין לך עדיין חשבון?",
"interaction_modal.on_another_server": "על שרת אחר",

View File

@ -79,7 +79,7 @@
"account.join_modal.share.intro": "Condividi un post introduttivo",
"account.join_modal.share.welcome": "Condividi un post di benvenuto",
"account.join_modal.years": "{number, plural, one {anno} other {anni}}",
"account.joined_short": "Iscritto",
"account.joined_short": "Account iscritto",
"account.languages": "Modifica le lingue d'iscrizione",
"account.last_active": "Ultima attività",
"account.link_verified_on": "La proprietà di questo link è stata controllata il {date}",
@ -328,7 +328,7 @@
"annual_report.summary.share_on_mastodon": "Condividi su Mastodon",
"attachments_list.unprocessed": "(non elaborato)",
"audio.hide": "Nascondi audio",
"block_modal.no_collections": "Nessuno dei due può aggiungere l'altro alle rispettive raccolte. Verrete automaticamente rimossi dalle collezioni esistenti dell'altro, se presenti.",
"block_modal.no_collections": "Nessuno dei due può aggiungere l'altro alle rispettive collezioni. Verrete automaticamente rimossi dalle collezioni esistenti dell'altro, se presenti.",
"block_modal.remote_users_caveat": "Chiederemo al server {domain} di rispettare la tua decisione. Tuttavia, la conformità non è garantita poiché alcuni server potrebbero gestire i blocchi in modo diverso. I post pubblici potrebbero essere ancora visibili agli utenti che non hanno effettuato l'accesso.",
"block_modal.show_less": "Mostra meno",
"block_modal.show_more": "Mostra di più",
@ -336,7 +336,7 @@
"block_modal.they_cant_see_posts": "Non potrà vedere i tuoi contenuti e tu non potrai vedere i suoi.",
"block_modal.they_will_know": "Può vedere che è stato/a bloccato/a.",
"block_modal.title": "Bloccare l'utente?",
"block_modal.you_wont_see_mentions": "Non vedrai messaggi di altri che menzionano l'utente.",
"block_modal.you_wont_see_mentions": "Non vedrai post da altri che menzionano l'utente.",
"boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio, la prossima volta",
"boost_modal.reblog": "Condividere il post?",
"boost_modal.undo_reblog": "Annullare la condivisione del post?",

View File

@ -330,6 +330,7 @@
"column_search.cancel": "Semmet",
"combobox.close_results": "Mdel igmaḍ",
"combobox.loading": "Yessalay-d",
"combobox.no_results_found": "Ulac ugmaḍ yettwafen i unadi-a",
"combobox.open_results": "Ldi igmaḍ",
"community.column_settings.local_only": "Adigan kan",
"community.column_settings.media_only": "Imidyaten kan",

View File

@ -224,7 +224,9 @@
"account_edit.profile_tab.show_media.title": "Tabblad 'Media' tonen",
"account_edit.profile_tab.show_media_replies.description": "Wanneer dit is ingeschakeld, worden op het tabblad Media zowel jouw berichten en reacties op berichten van anderen weergegeven.",
"account_edit.profile_tab.show_media_replies.title": "Jouw reacties aan het tabblad 'Media' toevoegen",
"account_edit.profile_tab.show_relations.description": "Accounts die je volgt en jouw volgers aan andere gebruikers op je profiel tonen. Mensen kunnen nog steeds zien of je ze volgt.",
"account_edit.profile_tab.show_relations.title": "Volgers en Gevolgde accounts tonen",
"account_edit.profile_tab.subtitle": "Verander hoe je profiel wordt weergeven.",
"account_edit.profile_tab.title": "Weergave-instellingen profiel",
"account_edit.save": "Opslaan",
"account_edit.upload_modal.back": "Terug",
@ -253,6 +255,7 @@
"account_edit_tags.search_placeholder": "Voer een hashtag in…",
"account_edit_tags.suggestions": "Suggesties:",
"account_edit_tags.tag_status_count": "{count, plural, one {# bericht} other {# berichten}}",
"account_list.hidden_notice": "Dit is alleen voor jou zichtbaar. Om deze lijst aan anderen te tonen, ga naar <link>{page} > {modal} > {field}</link>.",
"account_list.total": "{total, plural, one {# account} other {# accounts}}",
"account_note.placeholder": "Klik om een opmerking toe te voegen",
"admin.dashboard.daily_retention": "Retentiegraad van gebruikers per dag, vanaf registratie",
@ -325,9 +328,11 @@
"annual_report.summary.share_on_mastodon": "Op Mastodon delen",
"attachments_list.unprocessed": "(niet verwerkt)",
"audio.hide": "Audio verbergen",
"block_modal.no_collections": "Jullie kunnen elkaar onderling niet aan verzamelingen toevoegen. Indien van toepassing worden jullie automatisch uit elkaars bestaande verzamelingen verwijderd.",
"block_modal.remote_users_caveat": "We vragen de server {domain} om je besluit te respecteren. Het naleven hiervan is echter niet gegarandeerd, omdat sommige servers blokkades anders kunnen interpreteren. Openbare berichten zijn mogelijk nog steeds zichtbaar voor niet-ingelogde gebruikers.",
"block_modal.show_less": "Minder tonen",
"block_modal.show_more": "Meer tonen",
"block_modal.they_cant_mention": "Je kunt elkaar niet vermelden, volgen of citeren.",
"block_modal.they_cant_see_posts": "De persoon kan jouw berichten niet zien en jij ook niet diens berichten.",
"block_modal.they_will_know": "De persoon kan zien dat die wordt geblokkeerd.",
"block_modal.title": "Gebruiker blokkeren?",
@ -644,6 +649,7 @@
"empty_column.account_unavailable": "Profiel is niet beschikbaar",
"empty_column.blocks": "Je hebt nog geen gebruikers geblokkeerd.",
"empty_column.bookmarked_statuses": "Jij hebt nog geen berichten aan je bladwijzers toegevoegd. Wanneer je er een aan jouw bladwijzers toevoegt, valt deze hier te zien.",
"empty_column.collections.featured_in": "Je bent aan nog geen enkele verzameling toegevoegd.",
"empty_column.community": "De lokale tijdlijn is nog leeg. Plaats een openbaar bericht om de spits af te bijten!",
"empty_column.direct": "Je hebt nog geen privéberichten. Wanneer je er een verstuurt of ontvangt, komen deze hier te staan.",
"empty_column.disabled_feed": "Deze tijdlijn is uitgeschakeld door je serverbeheerders.",

View File

@ -113,6 +113,7 @@
"account.name.copy": "Kopier brukaradressa",
"account.name.help.domain": "{domain} er tenaren som lagrar brukarprofilen og innlegga.",
"account.name.help.domain_self": "{domain} er tenaren som lagrar brukarprofilen og innlegga dine.",
"account.name.help.footer": "Du kan senda epost til folk som bruker ulike eposttenester, og nett slik er det med Mastodon og resten av Allheimen òg: Du kan samhandla med folk her eller andre stader uansett kva tenar dei bruker.",
"account.name.help.header": "Ei brukaradresse er som ei epostadresse",
"account.name.help.username": "{username} er brukarnamnet til denne kontoen på tenaren deira. Folk på andre tenarar kan ha same brukarnamnet.",
"account.name.help.username_self": "{username} er brukarnamnet ditt på denne tenaren. Folk på andre tenarar kan ha same brukarnamnet.",
@ -127,6 +128,7 @@
"account.note.edit_button": "Rediger",
"account.note.title": "Personleg notat (berre synleg for deg)",
"account.open_original_page": "Opne originalsida",
"account.pending": "Ventar",
"account.posts": "Tut",
"account.remove_from_followers": "Fjern {name} frå fylgjarane dine",
"account.report": "Rapporter @{name}",
@ -222,6 +224,10 @@
"account_edit.profile_tab.show_media.title": "Vis «Medium»-fana",
"account_edit.profile_tab.show_media_replies.description": "Når medium-fana er i bruk, syner ho både innlegga dine og svar på andre folk sine innlegg.",
"account_edit.profile_tab.show_media_replies.title": "Ta med svar i «Medium»-fana",
"account_edit.profile_tab.show_relations.description": "Syner fylgjarane dine og dei du fylgjer til andre brukarar til andre brukarar på profilen din. Folk vil framleis kunna sjå om du fylgjer dei.",
"account_edit.profile_tab.show_relations.title": "Vis «fylgjarar» og «dei du fylgjer»",
"account_edit.profile_tab.subtitle": "Tilpass korleis profilen din ser ut.",
"account_edit.profile_tab.title": "Innstillingar for profilvising",
"account_edit.save": "Lagre",
"account_edit.upload_modal.back": "Tilbake",
"account_edit.upload_modal.done": "Ferdig",
@ -249,6 +255,7 @@
"account_edit_tags.search_placeholder": "Skriv ein emneknagg…",
"account_edit_tags.suggestions": "Framlegg:",
"account_edit_tags.tag_status_count": "{count, plural, one {# innlegg} other {# innlegg}}",
"account_list.hidden_notice": "Berre du ser dette. Gå til <link>{page} > {modal} > {field}</link> for å syna lista til andre.",
"account_list.total": "{total, plural, one {# konto} other {# kontoar}}",
"account_note.placeholder": "Klikk for å leggja til merknad",
"admin.dashboard.daily_retention": "Mengda brukarar aktive ved dagar etter registrering",

View File

@ -150,11 +150,11 @@
"account_edit.advanced_settings.bot_hint": "Bu hesap temelde otomatik eylemler gerçekleştirir ve izlenmeyebiliri diğerlerine bildir",
"account_edit.advanced_settings.bot_label": "Otomatik hesap",
"account_edit.advanced_settings.title": "Gelişmiş ayarlar",
"account_edit.bio.add_label": "Kişisel bilgi ekle",
"account_edit.bio.edit_label": "Kişisel bilgiyi düzenle",
"account_edit.bio.add_label": "Biyografi ekle",
"account_edit.bio.edit_label": "Biyografiyi düzenle",
"account_edit.bio.placeholder": "Diğerlerinin sizi tanımasına yardımcı olmak için kısa bir tanıtım ekleyin.",
"account_edit.bio.title": "Kişisel bilgiler",
"account_edit.bio_modal.add_title": "Kişisel bilgi ekle",
"account_edit.bio.title": "Biyografi",
"account_edit.bio_modal.add_title": "Biyografi ekle",
"account_edit.bio_modal.edit_title": "Biyografiyi düzenle",
"account_edit.column_button": "Tamam",
"account_edit.column_title": "Profili Düzenle",
@ -184,7 +184,7 @@
"account_edit.field_edit_modal.edit_title": "Özel alanı düzenle",
"account_edit.field_edit_modal.length_warning": "Önerilen karakter sınırııldı. Mobil kullanıcılar sahayı tam olarak görmeyebilirler.",
"account_edit.field_edit_modal.link_emoji_warning": "Url'lerle birlikte özel emoji kullanmamanızı öneririz. Her ikisini de içeren özel alanlar, kullanıcıların kafasını karıştırmamak için bağlantı yerine yalnızca metin olarak görüntülenir.",
"account_edit.field_edit_modal.name_hint": "Örn. \"Kişisel web sitesi\"",
"account_edit.field_edit_modal.name_hint": "Ör. \"Kişisel web sitesi\"",
"account_edit.field_edit_modal.name_label": "Etiket",
"account_edit.field_edit_modal.url_warning": "Bağlantı eklemek için lütfen başlangıca {protocol} ekleyin.",
"account_edit.field_edit_modal.value_hint": "Ör. “https://example.me”",
@ -242,9 +242,9 @@
"account_edit.upload_modal.title_replace.avatar": "Profil fotoğrafını değiştir",
"account_edit.upload_modal.title_replace.header": "Kapak fotoğrafını değiştirin",
"account_edit.verified_modal.details": "Kişisel web sitelerine bağlantıları doğrulayarak Mastodon profilinize güvenilirlik katın. İşte böyle çalışıyor:",
"account_edit.verified_modal.invisible_link.details": "Bağlantıyı başlığınıza ekleyin. Önemli olan kısım, kullanıcı tarafından oluşturulan içeriğe sahip web sitelerinde kimlik sahtekarlığını önleyen rel=\"me\" özniteliğidir. {tag} yerine sayfanın başlığında bir bağlantı etiketi bile kullanabilirsiniz, ancak HTML, JavaScript çalıştırılmadan erişilebilir olmalıdır.",
"account_edit.verified_modal.invisible_link.details": "Bağlantıyı sitenizin header kısmına ekleyin. Önemli olan kısım, kullanıcı tarafından oluşturulan içeriğe sahip web sitelerinde kimlik sahtekarlığını önleyen rel=\"me\" özniteliğidir. {tag} yerine sayfanın başlığında bir bağlantı etiketi bile kullanabilirsiniz, ancak HTML, JavaScript çalıştırılmadan erişilebilir olmalıdır.",
"account_edit.verified_modal.invisible_link.summary": "Bağlantıyı nasıl görünmez hale getirebilirim?",
"account_edit.verified_modal.step1.header": "Aşağıdaki HTML kodunu kopyalayın ve web sitenizin başlığına yapıştırın",
"account_edit.verified_modal.step1.header": "Aşağıdaki HTML kodunu kopyalayın ve web sitenizin header kısmına yapıştırın",
"account_edit.verified_modal.step2.details": "Web sitenizi özel alan olarak zaten eklediyseniz, doğrulamayı tetiklemek için onu silip yeniden eklemeniz gerekir.",
"account_edit.verified_modal.step2.header": "Web sitenizi özel bir alan olarak ekleyin",
"account_edit.verified_modal.title": "Doğrulanmış bir bağlantı nasıl eklenir",
@ -346,7 +346,7 @@
"bundle_column_error.network.body": "Sayfayı yüklemeye çalışırken bir hata oluştu. Bu durum internet bağlantınızdaki veya bu sunucudaki geçici bir sorundan kaynaklanıyor olabilir.",
"bundle_column_error.network.title": "Ağ hatası",
"bundle_column_error.retry": "Tekrar deneyin",
"bundle_column_error.return": "Anasayfaya geri dön",
"bundle_column_error.return": "Ana sayfaya geri dön",
"bundle_column_error.routing.body": "İstenen sayfa bulunamadı. Adres çubuğundaki URL'nin doğru olduğundan emin misiniz?",
"bundle_column_error.routing.title": "404",
"bundle_modal_error.close": "Kapat",
@ -454,7 +454,7 @@
"column.firehose_local": "Bu sunucunun canlı akışı",
"column.firehose_singular": "Canlı akış",
"column.follow_requests": "Takip istekleri",
"column.home": "Anasayfa",
"column.home": "Ana Sayfa",
"column.list_members": "Liste üyelerini yönet",
"column.lists": "Listeler",
"column.mutes": "Sessize alınmış kullanıcılar",
@ -820,7 +820,7 @@
"keyboard_shortcuts.favourites": "Gözde listeni aç",
"keyboard_shortcuts.federated": "Federe akışı aç",
"keyboard_shortcuts.heading": "Klavye kısayolları",
"keyboard_shortcuts.home": "Ana akışı aç",
"keyboard_shortcuts.home": "Ana sayfa akışını aç",
"keyboard_shortcuts.hotkey": "Kısayol tuşu",
"keyboard_shortcuts.legend": "Bu efsaneyi görüntülemek için",
"keyboard_shortcuts.load_more": "\"Daha fazlası\" düğmesine odaklan",
@ -861,13 +861,13 @@
"lists.add_to_list": "Listeye ekle",
"lists.add_to_lists": "{name} kişisini listelere ekle",
"lists.create": "Oluştur",
"lists.create_a_list_to_organize": "Anasayfa akışınızı düzenlemek için yeni bir liste oluşturun",
"lists.create_a_list_to_organize": "Ana sayfa akışınızı düzenlemek için yeni bir liste oluşturun",
"lists.create_list": "Liste oluştur",
"lists.delete": "Listeyi sil",
"lists.done": "Tamamlandı",
"lists.edit": "Listeleri düzenle",
"lists.exclusive": "Anasayfada üyeleri gizle",
"lists.exclusive_hint": "Birisi bu listede yer alıyorsa, gönderilerini iki kez görmekten kaçınmak için onu anasayfa akışınızda gizleyin.",
"lists.exclusive": "Ana sayfada üyeleri gizle",
"lists.exclusive_hint": "Birisi bu listede yer alıyorsa, gönderilerini iki kez görmekten kaçınmak için onu ana sayfa akışınızda gizleyin.",
"lists.find_users_to_add": "Eklenecek kullanıcıları bul",
"lists.list_members_count": "{count, plural, one {# üye} other {# üye}}",
"lists.list_name": "Liste adı",
@ -941,7 +941,7 @@
"notification.collection_update": "{name} olduğunuz bir koleksiyonu düzenledi",
"notification.favourite": "{name} gönderinizi beğendi",
"notification.favourite.name_and_others_with_link": "{name} ve <a>{count, plural, one {# diğer kişi} other {# diğer kişi}}</a> gönderinizi beğendi",
"notification.favourite_pm": "{name} özel değininizi beğendi",
"notification.favourite_pm": "{name} özel bahsetmenizi beğendi",
"notification.favourite_pm.name_and_others_with_link": "{name} ve <a>{count, plural, one {# diğer kişi} other {# diğer kişi}}</a> özel değininizi beğendi",
"notification.follow": "{name} seni takip etti",
"notification.follow.name_and_others": "{name} ve <a>{count, plural, one {# diğer kişi} other {# diğer kişi}}</a> sizi takip etti",
@ -1060,7 +1060,7 @@
"onboarding.profile.display_name": "Görünen isim",
"onboarding.profile.display_name_hint": "Tam adınız veya kullanıcı adınız…",
"onboarding.profile.finish": "Tamamla",
"onboarding.profile.note": "Kişisel bilgiler",
"onboarding.profile.note": "Biyografi",
"onboarding.profile.note_hint": "Diğer insanlara @değinebilir veya #etiketler kullanabilirsiniz…",
"onboarding.profile.title": "Profilini ayarla",
"onboarding.profile.upload_avatar": "Profil resmi yükle",
@ -1308,7 +1308,7 @@
"subscribed_languages.lead": "Değişiklikten sonra ana akışınızda sadece seçili dillerdeki gönderiler görüntülenecek ve zaman akışları listelenecektir. Tüm dillerde gönderiler için hiçbirini seçin.",
"subscribed_languages.save": "Değişiklikleri kaydet",
"subscribed_languages.target": "{target} abone olduğu dilleri değiştir",
"tabs_bar.home": "Anasayfa",
"tabs_bar.home": "Ana sayfa",
"tabs_bar.menu": "Menü",
"tabs_bar.notifications": "Bildirimler",
"tabs_bar.publish": "Yeni Gönderi",

View File

@ -8222,47 +8222,6 @@ noscript {
}
}
.moved-account-banner,
.follow-request-banner,
.account-memorial-banner {
padding: 20px;
background: var(--color-bg-tertiary);
display: flex;
align-items: center;
flex-direction: column;
&__message {
color: var(--color-text-secondary);
padding: 8px 0;
padding-top: 0;
padding-bottom: 4px;
font-size: 14px;
font-weight: 500;
text-align: center;
margin-bottom: 16px;
}
&__action {
display: flex;
justify-content: space-between;
align-items: center;
gap: 15px;
width: 100%;
}
.detailed-status__display-name {
margin-bottom: 0;
}
}
.follow-request-banner .button {
width: 100%;
}
.account-memorial-banner__message {
margin-bottom: 0;
}
.column-inline-form {
padding: 15px;
display: flex;

View File

@ -265,8 +265,14 @@ class Account < ApplicationRecord
last_webfingered_at.nil? || last_webfingered_at <= STALE_THRESHOLD.ago
end
def needs_background_refresh?
return false if local?
last_webfingered_at.blank? || last_webfingered_at <= BACKGROUND_REFRESH_INTERVAL.ago
end
def schedule_refresh_if_stale!
return unless last_webfingered_at.present? && last_webfingered_at <= BACKGROUND_REFRESH_INTERVAL.ago
return unless needs_background_refresh?
AccountRefreshWorker.perform_in(rand(REFRESH_DEADLINE), id)
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
class Admin::CollectionBatchAction < Admin::BaseAction
TYPES = %w(
report
remove_from_report
).freeze
attr_accessor :collection_ids
private
def process_action!
return unless collection_ids
add_to_report!
end
def add_to_report!
@report = Report.new(report_params) unless with_report?
@report.collections = (@report.collections + selected_collections).uniq
@report.save!
@report_id = @report.id
end
def selected_collections
@report.target_account.collections.where(id: collection_ids)
end
def report_params
{ account: current_account, target_account: target_account }
end
def collection
@collection ||= Collection.where(id: collection_ids.first)
end
def target_account
@target_account ||= collection.first.account
end
end

View File

@ -0,0 +1,49 @@
- content_for :page_title do
= t('admin.collections.title', name: @account.pretty_acct)
.filters
.filter-subset
%ul
%li= filter_link_to t('generic.all'), id: nil
.back-link
- if params[:report_id]
= link_to admin_report_path(params[:report_id].to_i) do
= material_symbol 'chevron_left'
= t('admin.collections.back_to_report')
- else
= link_to admin_account_path(@account.id) do
= material_symbol 'chevron_left'
= t('admin.collections.back_to_account')
%hr.spacer/
= form_with model: @collection_batch_action, url: batch_admin_account_collections_path(@account.id) do |f|
= hidden_field_tag :page, params[:page] || 1
= hidden_field_tag :report_id, params[:report_id]
.batch-table
.batch-table__toolbar
%label.batch-table__toolbar__select.batch-checkbox-all
= check_box_tag :batch_checkbox_all, nil, false
.batch-table__toolbar__actions
- unless @collections.empty?
- if params[:report_id]
= f.button safe_join([material_symbol('add'), t('admin.collections.batch.add_to_report', id: params[:report_id])]),
class: 'table-action-link',
data: { confirm: t('admin.reports.are_you_sure') },
name: :report,
type: :submit
- else
= f.button safe_join([material_symbol('flag'), t('admin.collections.batch.report')]),
class: 'table-action-link',
data: { confirm: t('admin.reports.are_you_sure') },
name: :report,
type: :submit
.batch-table__body
- if @collections.empty?
= nothing_here 'nothing-here--under-tabs'
- else
= render partial: 'admin/shared/collection_batch_row', collection: @collections, as: :collection, locals: { f: f }
= paginate @collections

View File

@ -69,7 +69,7 @@
- if Mastodon::Feature.collections_enabled?
%span.report-card__summary__item__content__icon{ title: t('admin.accounts.collections') }
= material_symbol('groups-fill')
= material_symbol('category')
= report.collections.size
- if report.forwarded?

View File

@ -67,17 +67,19 @@
%summary
= t 'admin.reports.collections', count: @report.collections.size
%form
= form_with model: @form, url: batch_admin_account_collections_path(@report.target_account_id, report_id: @report.id) do |f|
.batch-table
.batch-table__toolbar
%label.batch-table__toolbar__select.batch-checkbox-all
-# = check_box_tag :batch_checkbox_all, nil, false
.batch-table__toolbar__actions
= link_to safe_join([material_symbol('add'), t('admin.reports.add_to_report')]),
admin_account_collections_path(@report.target_account_id, report_id: @report.id),
class: 'table-action-link'
.batch-table__body
- if @report.collections.empty?
= nothing_here 'nothing-here--under-tabs'
- else
= render partial: 'admin/shared/collection_batch_row', collection: @report.collections, as: :collection
= render partial: 'admin/shared/collection_batch_row', collection: @report.collections, as: :collection, locals: { f: f }
- if @report.unresolved?
%hr.spacer/

View File

@ -1,5 +1,5 @@
.batch-table__row
%label.batch-table__row__select.batch-checkbox
-# = f.check_box :collection_ids, { multiple: true, include_hidden: false }, collection.id
= f.check_box :collection_ids, { multiple: true, include_hidden: false }, collection.id
.batch-table__row__content
= render partial: 'admin/shared/collection', object: collection

View File

@ -7,7 +7,7 @@ class AccountRefreshWorker
def perform(account_id)
account = Account.find_by(id: account_id)
return if account.nil? || account.last_webfingered_at > Account::BACKGROUND_REFRESH_INTERVAL.ago
return unless account&.needs_background_refresh?
ResolveAccountService.new.call(account)
end

View File

@ -514,7 +514,7 @@ br:
guide_link: https://crowdin.com/project/mastodon
application_mailer:
salutation: "%{name},"
view: 'Sellet :'
view: 'Sellet:'
view_profile: Gwelet ar profil
view_status: Gwelet ar c'hannad
applications:

View File

@ -345,12 +345,19 @@ da:
updated_msg: Bekendtgørelsen er opdateret!
collections:
accounts: Konti
back_to_account: Tilbage til kontoside
back_to_report: Tilbage til anmeldelsesside
batch:
add_to_report: 'Føj til anmeldelse #%{id}'
report: Anmeldelse
collection_title: Indsamling af %{name}
contents: Indhold
no_collection_selected: Der blev ikke ændret nogen samlinger, da ingen var valgt
number_of_accounts:
one: 1 konto
other: "%{count} konti"
open: Åben
title: Kontosamlinger - @%{name}
view_publicly: Vis offentligt
critical_update_pending: Kritisk opdatering afventer
custom_emojis:

View File

@ -345,12 +345,19 @@ de:
updated_msg: Ankündigung erfolgreich aktualisiert!
collections:
accounts: Konten
back_to_account: Zurück zum Konto
back_to_report: Zurück zu den Meldungen
batch:
add_to_report: Der Meldung Nr. %{id} hinzufügen
report: Meldung
collection_title: Sammlung von %{name}
contents: Inhalte
no_collection_selected: Keine Sammlungen wurden geändert, da keine ausgewählt wurden
number_of_accounts:
one: 1 Konto
other: "%{count} Konten"
open: Öffnen
title: Sammlungen des Kontos @%{name}
view_publicly: Sammlung öffnen
critical_update_pending: Kritisches Update ausstehend
custom_emojis:

View File

@ -345,12 +345,19 @@ el:
updated_msg: Επιτυχής ενημέρωση ανακοίνωσης!
collections:
accounts: Λογαριασμοί
back_to_account: Πίσω στη σελίδα λογαριασμού
back_to_report: Πίσω στη σελίδα αναφοράς
batch:
add_to_report: 'Προσθήκη στην αναφορά #%{id}'
report: Αναφορά
collection_title: Συλλογή από %{name}
contents: Περιεχόμενα
no_collection_selected: Καμία συλλογή δεν άλλαξε αφού καμία δεν ήταν επιλεγμένη
number_of_accounts:
one: 1 λογαριασμός
other: "%{count} λογαριασμοί"
open: Άνοιγμα
title: Συλλογές λογαριασμού - @%{name}
view_publicly: Προβολή δημόσια
critical_update_pending: Κρίσιμη ενημέρωση σε αναμονή
custom_emojis:

View File

@ -345,12 +345,19 @@ en:
updated_msg: Announcement successfully updated!
collections:
accounts: Accounts
back_to_account: Back to account page
back_to_report: Back to report page
batch:
add_to_report: 'Add to report #%{id}'
report: Report
collection_title: Collection by %{name}
contents: Contents
no_collection_selected: No collections were changed as none were selected
number_of_accounts:
one: 1 account
other: "%{count} accounts"
open: Open
title: Account collections - @%{name}
view_publicly: View publicly
critical_update_pending: Critical update pending
custom_emojis:

View File

@ -345,12 +345,19 @@ es-AR:
updated_msg: "¡Anuncio actualizado exitosamente!"
collections:
accounts: Cuentas
back_to_account: Volver a la página de la cuenta
back_to_report: Volver a la página de la denuncia
batch:
add_to_report: Agregar a la denuncia N°%{id}
report: Denunciar
collection_title: Colección de %{name}
contents: Contenidos
no_collection_selected: No se cambió ninguna colección, ya que ninguna fue seleccionada
number_of_accounts:
one: 1 cuenta
other: "%{count} cuentas"
open: Abrir
title: Colecciones de cuentas - @%{name}
view_publicly: Ver públicamente
critical_update_pending: Actualización crítica pendiente
custom_emojis:

View File

@ -345,12 +345,19 @@ es-MX:
updated_msg: "¡Anuncio actualizado con éxito!"
collections:
accounts: Cuentas
back_to_account: Volver a la página de la cuenta
back_to_report: Volver a la página del informe
batch:
add_to_report: 'Añadir al informe #%{id}'
report: Informe
collection_title: Colección de %{name}
contents: Contenidos
no_collection_selected: No se modificó ninguna colección, ya que no se seleccionó ninguna
number_of_accounts:
one: 1 cuenta
other: "%{count} cuentas"
open: Abrir
title: Colecciones de la cuenta - @%{name}
view_publicly: Ver públicamente
critical_update_pending: Actualización crítica pendiente
custom_emojis:

View File

@ -345,12 +345,19 @@ es:
updated_msg: "¡Anuncio actualizado con éxito!"
collections:
accounts: Cuentas
back_to_account: Volver a la página de la cuenta
back_to_report: Volver a la página del informe
batch:
add_to_report: 'Añadir al informe #%{id}'
report: Informe
collection_title: Colección de %{name}
contents: Contenidos
no_collection_selected: No se ha cambiado ninguna colección ya que no se ha seleccionado ninguna
number_of_accounts:
one: 1 cuenta
other: "%{count} cuentas"
open: Abrir
title: Colecciones de la cuenta - @%{name}
view_publicly: Ver públicamente
critical_update_pending: Actualización crítica pendiente
custom_emojis:

View File

@ -12,6 +12,9 @@ fa:
followers:
one: پی‌گیرنده
other: پی‌گیرنده
following:
one: پی‌گرفته
other: پی‌گرفته
instance_actor_flash: این حساب عاملی مجازیست که به نمایندگی از خود کارساز استفاده می‌شود و نه کاربری جداگانه. این حساب به منظور اتصال به فدراسیون استفاده می‌شود و نباید معلق شود.
last_active: آخرین فعّالیت
link_verified_on: مالکیت این پیوند در %{date} بررسی شد
@ -53,6 +56,7 @@ fa:
label: تغییر نقش
no_role: بدون نقش
title: تغییر نقش برای %{username}
collections: مجموعه‌ها
confirm: تأیید
confirmed: تأیید شد
confirming: تأیید
@ -263,6 +267,7 @@ fa:
demote_user_html: "%{name} کاربر %{target} را تنزل داد"
destroy_announcement_html: "%{name} اعلامیهٔ %{target} را حذف کرد"
destroy_canonical_email_block_html: "%{name} رایانامه با درهم‌ریزی %{target} را نامسدود کرد"
destroy_collection_html: "%{name} مجموعهٔ %{target} را برداشت"
destroy_custom_emoji_html: "%{name} شکلک %{target} را حذف کرد"
destroy_domain_allow_html: "%{name} دامنهٔ %{target} را از فهرست مجاز برداشت"
destroy_domain_block_html: "%{name} انسداد دامنهٔ %{target} را رفع کرد"
@ -302,6 +307,7 @@ fa:
unsilence_account_html: "%{name} محدودیت حساب %{target} را برداشت"
unsuspend_account_html: "%{name} حساب %{target} را از تعلیق خارج کرد"
update_announcement_html: "%{name} اعلامیهٔ %{target} را به‌روز کرد"
update_collection_html: "%{name} مجموعهٔ %{target} را به‌روز کرد"
update_custom_emoji_html: "%{name} شکلک %{target} را به‌روز کرد"
update_domain_block_html: "%{name} مسدودسازی دامنه را برای %{target} به‌روزرسانی کرد"
update_ip_block_html: "%{name} قانون آی‌پی %{target} را تغییر داد"
@ -338,6 +344,7 @@ fa:
unpublished_msg: انتشار اعلامیه با موفقیت لغو شد!
updated_msg: اعلامیه با موفقیت به‌روز شد!
collections:
accounts: حساب‌ها
collection_title: محموعه به دست %{name}
contents: محتوا
number_of_accounts:
@ -477,9 +484,53 @@ fa:
title: مسدودسازی دامنهٔ رایانامهٔ جدید
no_email_domain_block_selected: هیچ انسداد دامنهٔ رایانامه‌ای تغییر نکرد زیرا هیچ‌کدامشان انتخاب نشده بودند
not_permitted: مجاز نیست
reset: بازنشانی
resolved_dns_records_hint_html: نام دامنه به دامنه‌های MX زیر منتقل می شود که در نهایت مسئولیت پذیرش رایانامه را بر عهده دارند. انسداد دامنهٔ MX، ثبت‌نام از هر نشانی رایانامه‌ای را که از همان دامنهٔ MX استفاده می‌کند را مسدود می‌کند؛ حتا اگر نام دامنهٔ نمایان متفاوت باشد. <strong>مراقب باشید ارائه‌دهندگان رایانامهٔ بزرگ را مسدود نکنید.</strong>
resolved_through_html: از طریق %{domain} حل شد
search: جست‌وجو
title: دامنه‌های رایانامهٔ مسدود شده
email_subscriptions:
accounts:
account: حساب
active: فعّال
inactive: غیرفعّال
status: وضعیت
subscribers: مشترکان
title: سیاهه‌های پستی
additional_footer_texts:
show:
title: متن پانویس اضافی
compliance_settings:
additional_footer_text:
action: مدیریت
title: متن پانویس اضافی
privacy_policy:
action: مدیریت
title: سیاست محرمانگی
danger_zone:
disable_feature:
action: از کار انداختن
title: از کار انداختن ویژگی
erase_all_data:
action: پاک کردن داده‌ها
title: پاک کردن تمام داده‌ها
title: منطقهٔ خطر
index:
disabled:
get_started: آغاز به کار
roles:
accounts: حساب ها
edit_role: ویرایش نقش
empty:
hint: هیچ‌کس اجازهٔ استفاده از این ویژگی را ندارد.
no_roles_added: هیچ نقشی اضافه نشد
manage_roles: مدیریت نقش‌ها
role_name: نام نقش
title: نقش‌ها
setups:
show:
enable_feature: به کار انداختن ویژگی
important_information: اطّلاعات مهم
export_domain_allows:
new:
title: درون‌ریزی اجازه‌های دامنه
@ -772,6 +823,7 @@ fa:
administrator_description: کاربرانی که این مجوز را دارند از هر مجوزی عبور می کنند
delete_user_data: حذف داده‌های کاربر
delete_user_data_description: به کاربران این امکان را می دهد که بدون تاخیر داده های سایر کاربران را حذف کنند
invite_bypass_approval: دعوت کاربر بدون بازبینی
invite_users: دعوت کاربران
invite_users_description: به کاربران اجازه می دهد افراد جدیدی را به سرور دعوت کنند
manage_announcements: مدیریت اعلامیه‌ها
@ -782,6 +834,7 @@ fa:
manage_blocks_description: می‌گذارد کاربران فراهم‌کنندگان رایانامه و نشانی‌های آی‌پی را مسدود کنند
manage_custom_emojis: مدیریت ایموجی‌های سفارشی
manage_custom_emojis_description: به کاربران اجازه می دهد تا ایموجی های سفارشی را روی سرور مدیریت کنند
manage_email_subscriptions: مدیریت اشتراک‌های رایانامه‌ای
manage_federation: مدیریت خودگردانی
manage_federation_description: به کاربران اجازه می‌دهد تا اتحاد با دامنه‌های دیگر را مسدود یا اجازه دهند و تحویل‌پذیری را کنترل کنند
manage_invites: مدیریت دعوت‌ها
@ -810,6 +863,7 @@ fa:
view_devops_description: به کاربران امکان دسترسی به داشبورد Sidekiq و pgHero را می دهد
view_feeds: دیدن خوراک‌های موضوع و زنده
view_feeds_description: می‌گذارد کاربران فارغ از تنظیمات کارساز به خوراک‌های موضوع و زنده دسترسی داشته باشند
requires_2fa: نیازمند هویت‌سنجی دو مرحله‌ای
title: نقش‌ها
rules:
add_new: افزودن قانون
@ -1271,6 +1325,7 @@ fa:
progress:
confirm: تأیید رایانامه
details: جزئیات شما
list: فرایند نام‌نویسی
review: بررسی ما
rules: پذیرش قوانین
providers:
@ -1286,6 +1341,7 @@ fa:
invited_by: 'با سپاس از دعوتی از این فرد دریافت کرده‌اید می‌توانید به %{domain} بپیوندید:'
preamble: اینها توسط گردانندگان %{domain} تنظیم و اجرا می شوند.
preamble_invited: قبل از ادامه، لطفاً قوانین اساسی تنظیم شده توسط مدیران %{domain} را در نظر بگیرید.
read_more: خواندن بیش‌تر
title: برخی از قوانین اساسی.
title_invited: شما دعوت شده اید.
security: امنیت
@ -1405,7 +1461,12 @@ fa:
your_appeal_rejected: درخواست تجدیدنظرتان رد شد
edit_profile:
other: سایر
redesign_button: آزمودن
redesign_title: تجربهٔ نماگر جدیدی آمده است
email_subscription_mailer:
confirmation:
action: تأیید نشانی رایانامه
subject: تأیید نشانی رایانامه‌تان
notification:
create_account: ایجاد حساب ماستودون
subject:
@ -1413,6 +1474,17 @@ fa:
singular: 'فرستهٔ جدید: «%{excerpt}»'
title:
plural: فرسته‌های جدید از %{name}
singular: 'فرستهٔ جدید: «%{excerpt}»'
email_subscriptions:
active: فعّال
confirmations:
show:
changed_your_mind: نظرتان عوض شد؟
title: نام نوشتید
unsubscribe: لغو اشتراک
inactive: غیرفعّال
status: وضعیت
subscribers: مشترکان
emoji_styles:
auto: خودکار
native: بومی
@ -1690,6 +1762,19 @@ fa:
copy_account_note_text: 'این کاربر از %{acct} جابه‌جا شده است. یادداشت‌های پیشینتان درباره‌اش این‌هاست:'
navigation:
toggle_menu: تغییر وضعیت فهرست
notification_fallbacks:
added_to_collection:
title_html: "%{name} به مجموعه‌ای افزودتان"
admin_report:
title_html: "%{name}، %{target} را گزارش داد"
admin_sign_up:
title_html: "%{name} نام نوشت"
collection_update:
title_html: "%{name} مجموعه‌ای که در آن هستید را به‌روز کرد"
generic:
sign_in: ورورد به کارهٔ وب ماستودون
severed_relationships:
title: قطع ارتباط با %{name}
notification_mailer:
admin:
report:
@ -1782,6 +1867,7 @@ fa:
posting_defaults: تنظیمات پیش‌فرض انتشار
public_timelines: خط زمانی‌های عمومی
privacy:
email_subscriptions: ارسال فرسته‌ها با رایانامه
hint_html: "<strong>شخصی‌سازی چگونگی پیدا شدن فرسته‌ها و نمایه‌تان.</strong> ویژگی‌های متعدّدی در ماستودون می‌توانند هنگام به کار افتادن در رسیدن به مخاطبینی گسترده‌تر یاریتان کنند. کمی وقت برای بازبینی این تنظیمات گذاشته تا مطمئن شوید برایتان مناسبند."
privacy: محرمانگی
privacy_hint_html: واپایش میزان باز شدن به نفع دیگران. افراد نمایه‌های جالب و کاره‌های باحال را با مرور پی‌گرفتگان دیگران و دیدن کاره‌هایی که از آن‌ها می‌فرستند پیدا می‌کنند. با این حال شاید بخواهید پنهان نگهشان دارید.
@ -2015,6 +2101,8 @@ fa:
past_preamble_html: شرایط خدماتمان را تغییر داده‌ایم. تشویقتان می‌کنیم که شرایط به‌روز شده را بازبینی کنید.
review_link: بازبینی شرایط استفاده
title: شرایط خدمات %{domain} در حال تغییر است
themes:
default: ماستودون
time:
formats:
default: "%d %b %Y, %H:%M"
@ -2039,7 +2127,26 @@ fa:
recovery_codes: پشتیبان‌گیری از کدهای بازیابی
recovery_codes_regenerated: کدهای بازیابی با موفقیت ساخته شدند
recovery_instructions_html: اگر تلفن خود را گم کردید، می‌توانید با یکی از کدهای بازیابی زیر کنترل حساب خود را به دست بگیرید. <strong>این کدها را در جای امنی نگه دارید.</strong> مثلاً آن‌ها را چاپ کنید و کنار سایر مدارک مهم خود قرار دهید.
resume_app_authorization: از سر گیری تأیید درخواست
webauthn: کلیدهای امنیتی
unsubscriptions:
create:
action: رفتن به صفحهٔ خانگی کارساز
email_subscription:
confirmation_html: دیگر از %{name} رایانامه نخواهید گرفت.
title: اشتراکتان لغو شد
notification_emails:
favourite: رایانامه‌های آگاهی برگزیدن
follow: رایانامه‌های آگاهی پی‌گیری
follow_request: رایانامه‌های درخواست پی‌گیری
mention: رایانامه‌های آگاهی اشاره
reblog: رایانامه‌های آگاهی تقویت
show:
action: لغو اشتراک
email_subscription:
title: لغو اشتراک %{name}؟
user:
title: لغو اشتراک %{type}؟
user_mailer:
announcement_published:
description: 'مدیران %{domain} اعلامیه‌ای داده‌اند:'
@ -2182,6 +2289,7 @@ fa:
error: حذف کلید امنیتیتان با مشکل مواجه شد. لطفاً دوباره تلاش کنید.
success: کلید امنیتیتان با موفّقیت حذف شد.
invalid_credential: کلید امنیتی نامعتبر
nickname: نام مستعار
nickname_hint: نام مستعار کلید امنیتی جدیدتان را وارد کنید
not_enabled: شما هنوز WebAuthn را فعال نکرده‌اید
not_supported: این مرورگر از کلیدهای امنیتی پشتیبانی نمی‌کند

View File

@ -345,12 +345,19 @@ fi:
updated_msg: Tiedotteen päivitys onnistui!
collections:
accounts: Tilit
back_to_account: Takaisin tilisivulle
back_to_report: Takaisin raporttisivulle
batch:
add_to_report: Lisää raporttiin nro %{id}
report: Raportoi
collection_title: Kokoelma, koonnut %{name}
contents: Sisältö
no_collection_selected: Kokoelmia ei muutettu, koska yhtään ei ollut valittuna
number_of_accounts:
one: 1 tili
other: "%{count} tiliä"
open: Avaa
title: Tilin kokoelmat @%{name}
view_publicly: Näytä julkisesti
critical_update_pending: Kriittinen päivitys odottaa
custom_emojis:

View File

@ -345,12 +345,19 @@ fr-CA:
updated_msg: Lannonce a été mise à jour avec succès !
collections:
accounts: Comptes
back_to_account: Retour à la page du compte
back_to_report: Retour à la page de signalement
batch:
add_to_report: 'Ajouter au signalement #%{id}'
report: Signaler
collection_title: Collection par %{name}
contents: Contenu
no_collection_selected: Aucune collection n'a était changé vu qu'aucune n'a était sélectionné
number_of_accounts:
one: 1 compte
other: "%{count} comptes"
open: Ouvrir
title: Collection du compte - @%{name}
view_publicly: Afficher publiquement
critical_update_pending: Mise à jour critique en attente
custom_emojis:

View File

@ -345,12 +345,19 @@ fr:
updated_msg: Lannonce a été mise à jour avec succès !
collections:
accounts: Comptes
back_to_account: Retour à la page du compte
back_to_report: Retour à la page de signalement
batch:
add_to_report: 'Ajouter au signalement #%{id}'
report: Signaler
collection_title: Collection par %{name}
contents: Contenu
no_collection_selected: Aucune collection n'a était changé vu qu'aucune n'a était sélectionné
number_of_accounts:
one: 1 compte
other: "%{count} comptes"
open: Ouvrir
title: Collection du compte - @%{name}
view_publicly: Afficher publiquement
critical_update_pending: Mise à jour critique en attente
custom_emojis:

View File

@ -345,12 +345,19 @@ gl:
updated_msg: Anuncio actualizado de xeito correcto!
collections:
accounts: Contas
back_to_account: Volver a páxina da conta
back_to_report: Volver á páxina da denuncia
batch:
add_to_report: 'Engadir á denuncia #%{id}'
report: Denunciar
collection_title: Colección de %{name}
contents: Contido
no_collection_selected: Sen cambios nas coleccións por que non había ningunha seleccionada
number_of_accounts:
one: 1 conta
other: "%{count} contas"
open: Abrir
title: Coleccións da conta - @%{name}
view_publicly: Ver publicamente
critical_update_pending: Actualización crítica pendente
custom_emojis:

View File

@ -504,9 +504,76 @@ he:
title: חסימת מתחם כתובות דואל חדש
no_email_domain_block_selected: לא בוצעו שינויים לחסימת מתחמי דוא"ל שכן לא נבחרו מתחמים
not_permitted: נאסר
reset: איפוס
resolved_dns_records_hint_html: שם הדומיין מוביל למתחמי ה-MX הבאים, שהם בסופו של דבר אחראיים לקבלת דוא"ל. חסימת שם MX תוביל לחסימת הרשמות מכל כתובת דוא"ל שעושה שימוש בכתובת MX זו, אפילו אם הדומיין הגלוי שונה. <strong>יש להמנע מלחסום ספקי דוא"ל מובילים.</strong>
resolved_through_html: נמצא דרך %{domain}
search: חיפוש
title: מתחמי כתובות דוא"ל חסומים
email_subscriptions:
accounts:
account: חשבון
active: פעילים
empty:
hint: עדיין אין מנוי לאף חשבון.
no_lists_yet: אין רשימות עדיין
inactive: לא פעילים
last_email: הודעת דואל אחרונה
lead: חשבונות שבהם הופעלה האפשרות ויש להם מנויים יופיעו להלן.
status: מצב
subscribers: מנויים
title: רשימות תפוצה
additional_footer_texts:
show:
title: מלל תחתית נוסף
compliance_settings:
additional_footer_text:
action: ניהול
hint: ניתן להוסיף מלל לתחתית מנשרי דואל
title: מלל תחתית נוסף
lead: מנשרי דואל עשויים להחשב לדואל שיווקי, בכפוף לחוקי תחום השיפוט בו אתם פועלים.
privacy_policy:
action: ניהול
hint: מדיניות זו מקושרת בתחתית כל דיוור דואל
title: מדיניות פרטיות
title: כיוונוני תאימות לחוק
danger_zone:
disable_feature:
action: השבתה
hint: כיבוי התכונה לכל החשבונות
title: כיבוי תכונה
erase_all_data:
action: מחיקת נתונים
hint: מחיקה לצמיתות של כל כתובות הדואל מכל רשימות התפוצה
title: מחק את כל הנתונים
title: אזור סכנה
disabled_msg: הרשמות דואל כובו בהצלחה.
index:
disabled:
cannot_be_enabled: הספק הטכני לא איפשר תכונה זו לשרת שלך.
description: התכונה מאפשרת לחשבונות שצוינו להוסיף כרטיסיה לפרופיל שלהם המאפשרת למבקרים חסרי חשבון מסטודון להרשם לקבלת הודעותיהם בדוא"ל.
get_started: בואו נתחיל
lead: לאפשר למבקרים לקבל הודעות משרת זה דרך הדואל בעזרת חשבונות יעודיים.
title: מנשרי דואל
purged_msg: כל מידע הרשמות הדואל עובר מחיקה.
roles:
accounts: חשבונות
edit_role: עריכת תפקיד
empty:
hint: לאף אחד אין רשות להשתמש בתכונה זו.
no_roles_added: לא הוספו תפקידים
lead: חשבונות עם התפקידים המפורטים יוכלו לאפשר תכונה זו בפרופילים שלהם.
manage_roles: ניהול תפקידים
role_name: שם תפקיד
title: תפקידים
setups:
show:
enable_feature: אפשר תכונה
important_information: מידע חשוב
list:
1_permission_explanation: כאשר תכונה זו מאופשרת, חשבונות עם ההרשאות היעודיות יוכלו להוסיף טופס אוסף דואל לפרופילים שלהם.
2_feature_explanation: כאשר מבקרים נרשמים בעמוד הפרופיל של חשבון ומאשרים את ההרשמה, הן יתחילו לקבל עידכוני דוא"ל כאשר החשבון ייצור הודעות פומביות חדשות.
3_privacy_policy_warning: למנהלי שרתים יש גישה למידע מזהה אישי שנאסף (כתובות דואל). בשל כך יש לעדכן את מדיניות הפרטיות שתכלול מידע זה לפני אפשור התכונה.
4_cost_warning: שליחת דואל עלולה לעלות כסף בכפוף לתצורת ניהול השרת. דונו עם ספק האירוח לפני שתאפשרו, שכן תכונה זו עשויה להעלות משמעותית את כמות הדואל הנשלח מהשרת.
export_domain_allows:
new:
title: יבוא רשימת שרתים מאושרים
@ -824,6 +891,7 @@ he:
manage_custom_emojis: ניהול סמלונים בהתאמה אישית
manage_custom_emojis_description: מאפשר למשתמשים לנהל סמלונים בהתאמה אישית של השרת
manage_email_subscriptions: ניהול הרשמות דוא"ל
manage_email_subscriptions_description: להתיר למתמשים עם הרשאה זו לאפשר תכונת מנשר הדואל לחשבונם
manage_federation: ניהול פדרציה
manage_federation_description: מאפשר למשתמשים לחסום או לאפשר התממשקות עם שמות מתחם אחרים
manage_invites: ניהול הזמנות
@ -2314,7 +2382,7 @@ he:
feature_audience_title: בנו את הקהל שלכםן בבטחה
feature_control: אתםן יודעיםות בדיוק מה תרצו לראות בפיד הבית. אין פה פרסומות ואלגוריתמים שיבזבזו את זמנכם. עקבו אחרי כל משתמשי מסטודון משלל שרתים מתוך חשבון אחד וקבלו את ההודעות שלהםן לפי סדר הפרסום, וכך תצרו לכם את פינת האינטרנט המתאימה לכן אישית.
feature_control_title: השארו בשליטה על ציר הזמן שלכם
feature_creativity: מסטודון תומך בהודעות עם שמע, חוזי ותמונות, עם תאורים למוגבלי ראייה, משאלים, אזהרות תוכן, אווטרים מונפשים, אמוג'י מותאמים אישית, שליטה בחיתוך תמונות מוקטנות ועוד, כדי שתוכלו לבטא את עצמכןם ברשת. בין אם תפרסמו אמנות, מוזיקה או פודקסטים אישיים, מסטודון פה בשבילכם.
feature_creativity: מסטודון תומך בהודעות עם שמע, חוזי ותמונות, עם תאורים ללקויי ראייה, משאלים, אזהרות תוכן, אווטרים מונפשים, אמוג'י מותאמים אישית, שליטה בחיתוך תמונות מוקטנות ועוד, כדי שתוכלו לבטא את עצמכןם ברשת. בין אם תפרסמו אמנות, מוזיקה או פודקסטים אישיים, מסטודון פה בשבילכם.
feature_creativity_title: יצירתיות ללא פשרות
feature_moderation: מסטודון מחזיר לידיכן את ההחלטות. כל שרת יוצר לעצמו את חוקיו, שנאכפים מקומית ולא "מלמעלה" כמו באתרים מסחריים, מה שהופך אותו לגמיש ביותר בשירות הרצונות והצרכים של קבוצות שונות. הצטרפו לשרת שאתםן מסכימותים עם חוקיו, או הקימו משלכןם.
feature_moderation_title: ניהול דיונים כמו שצריך

View File

@ -345,12 +345,19 @@ hu:
updated_msg: A közlemény sikeresen frissítve!
collections:
accounts: Fiókok
back_to_account: Vissza a fiók oldalára
back_to_report: Vissza a bejelentés oldalra
batch:
add_to_report: 'Hozzáadás ehhez a jelentéshez: #%{id}'
report: Jelentés
collection_title: "%{name} gyűjteménye"
contents: Tartalom
no_collection_selected: Nem változott meg egy gyűjtemény sem, mert semmi sem volt kiválasztva
number_of_accounts:
one: 1 fiók
other: "%{count} fiók"
open: Megnyitás
title: Fiók gyűjteményei @%{name}
view_publicly: Megtekintés nyilvánosan
critical_update_pending: Függőben levő kritikus frissítés
custom_emojis:

View File

@ -345,12 +345,19 @@ is:
updated_msg: Það tókst að uppfæra auglýsinguna!
collections:
accounts: Notandaaðgangar
back_to_account: Fara aftur á síðu notandaaðgangsins
back_to_report: Til baka á kærusíðu
batch:
add_to_report: 'Bæta við skýrslu #%{id}'
report: Kæra
collection_title: Safn frá %{name}
contents: Efni
no_collection_selected: Engum söfnum var breytt því engin voru valin
number_of_accounts:
one: 1 aðgangur
other: "%{count} aðgangar"
open: Opið
title: Söfn notandaaðgangs - @%{name}
view_publicly: Skoða opinberlega
critical_update_pending: Áríðandi uppfærsla í bið
custom_emojis:

View File

@ -85,7 +85,7 @@ it:
invite_request_text: Motivi d'iscrizione
invited_by: Invitato da
ip: IP
joined: Iscritto il
joined: Account iscritto il
location:
all: Tutto
local: Locale
@ -345,12 +345,19 @@ it:
updated_msg: Annuncio aggiornato!
collections:
accounts: Account
back_to_account: Torna alla pagina dell'account
back_to_report: Torna alla pagina di segnalazione
batch:
add_to_report: 'Aggiungi alla segnalazione #%{id}'
report: Segnala
collection_title: Collezione di %{name}
contents: Contenuti
no_collection_selected: Nessuna collezione è stata cambiata in quanto nessuna è stata selezionata
number_of_accounts:
one: 1 account
other: "%{count} account"
open: Apri
title: Collezioni dell'account - @%{name}
view_publicly: Visualizza pubblicamente
critical_update_pending: Aggiornamento critico in sospeso
custom_emojis:
@ -747,7 +754,7 @@ it:
actions_description_html: Decidi quale azione intraprendere per risolvere questa segnalazione. Se intraprendi un'azione punitiva nei confronti dell'account segnalato, gli verrà inviata una notifica via e-mail, tranne quando è selezionata la categoria <strong>Spam</strong>.
actions_description_remote_html: Decide quali azioni intraprendere per risolvere la relazione. Questo influenzerà solo come <strong>il tuo</strong> server comunica con questo account remoto e ne gestisce il contenuto.
actions_no_posts: Questa segnalazione non ha alcun post associato da eliminare
add_to_report: Aggiungi altro al report
add_to_report: Aggiungi altro alla segnalazione
already_suspended_badges:
local: Già sospeso su questo server
remote: Già sospeso sul loro server
@ -987,7 +994,7 @@ it:
account: Autore
application: Applicazione
back_to_account: Torna alla pagina dell'account
back_to_report: Torna alla pagina del report
back_to_report: Torna alla pagina di segnalazione
batch:
add_to_report: 'Aggiungi alla segnalazione #%{id}'
remove_from_report: Rimuovi dal report

View File

@ -135,6 +135,7 @@ kab:
create_domain_allow: Rnu-d taɣult yettusirgen
create_domain_block: Rnu-d asewḥel n taɣult
create_ip_block: Rnu alugen n IP
create_relay: Snulfu-d amedwel
create_unavailable_domain: Rnu-d taɣult ur nelli ara
create_user_role: Snulfu-d tamlilt
destroy_announcement: Kkes ulɣu
@ -285,6 +286,7 @@ kab:
domain: Taɣult
new:
create: Rnu taɣult
search: Nadi
email_subscriptions:
accounts:
account: Amiḍan
@ -293,13 +295,17 @@ kab:
last_email: Imayl aneggaru
status: Addad
compliance_settings:
additional_footer_text:
action: Sefrek
privacy_policy:
action: Sefrek
title: Tasertit n tbaḍnit
danger_zone:
disable_feature:
action: Kkes armad
erase_all_data:
action: Kkes isefka
title: Kkes akk isefka
index:
disabled:
get_started: Aha bdu tura

View File

@ -345,12 +345,19 @@ nl:
updated_msg: Bijwerken van mededeling geslaagd!
collections:
accounts: Accounts
back_to_account: Terug naar de accountpagina
back_to_report: Terug naar de rapportagepagina
batch:
add_to_report: 'Aan rapportage #%{id} toevoegen'
report: Rapportage
collection_title: Verzameling van %{name}
contents: Inhoud
no_collection_selected: Er werden geen verzamelingen gewijzigd, omdat er geen enkele werd geselecteerd
number_of_accounts:
one: 1 account
other: "%{count} accounts"
open: Openen
title: Accountverzamelingen - @%{name}
view_publicly: Openbare verzameling bekijken
critical_update_pending: Kritieke update in behandeling
custom_emojis:
@ -541,6 +548,7 @@ nl:
empty:
hint: Niemand heeft het recht om deze functionaliteit te gebruiken.
no_roles_added: Geen rollen toegevoegd
lead: Accounts met de volgende rollen kunnen deze functionaliteit op hun profiel inschakelen.
manage_roles: Rollen beheren
role_name: Naam rol
title: Rollen

View File

@ -60,6 +60,7 @@ fa:
setting_default_quote_policy_private: فرسته‌های فقط پی‌گیران روی ماستودون نمی‌توانند به دست دیگران نقل شوند.
setting_default_quote_policy_unlisted: هنگامی که کسی نقلتان می‌کند هم فرسته‌اش از خط زمانی‌های داغ پنهان خواهد بود.
setting_default_sensitive: تصاویر حساس به طور پیش‌فرض پنهان هستند و می‌توانند با یک کلیک آشکار شوند
setting_display_media_hide_all: هشدار پیش از نمایش همهٔ رسانه‌ها
setting_emoji_style: چگونگی نمایش شکلک‌ها. «خودکار» تلاش خواهد کرد از شکلک‌های بومی استفاده کند؛ ولی برای مرورگرهای قدیمی به توییموجی برخواهد گشت.
setting_quick_boosting_html: هنگام به کار افتادن، زدن روی %{boost_icon} نقشک تقویت به جای گشودنِ فهرست پایین افتادنی تقویت و نقل، بلافاصله تقویت خواهد کرد. کنشِ نقل قول را به فهرست %{options_icon} (گزینه‌ها) منتقل می‌کند.
setting_system_scrollbars_ui: فقط برای مرورگرهای دسکتاپ مبتنی بر سافاری و کروم اعمال می شود
@ -217,6 +218,7 @@ fa:
email: نشانی رایانامه
expires_in: تاریخ انقضا
fields: اطلاعات تکمیلی نمایه
filter_action: کنش پالایه
header: تصویر زمینه
honeypot: "%{label} (پر نکنید)"
inbox_url: نشانی صندوق ورودی رله
@ -283,6 +285,7 @@ fa:
closed_registrations_message: پیام سفارشی هنگام در دسترس نبودن ثبت‌نام‌ها
content_cache_retention_period: دوره نگهداری محتوا از راه دور
custom_css: سبک CSS سفارشی
email_footer_text: متن پانویس اضافی
favicon: نمادک
landing_page: صفحهٔ فرود برای بینندگان جدید
local_live_feed_access: دسترسی به خوراک‌های زندهٔ نمایانگر فرسته‌های محلی
@ -307,6 +310,7 @@ fa:
status_page_url: نشانی صفحهٔ وضعیت
theme: زمینهٔ پیش‌گزیده
thumbnail: بندانگشتی کارساز
thumbnail_description: متن جایگزین بندانگشتی
trendable_by_default: اجازهٔ پرطرفدار شدن بدون بازبینی پیشین
trends: به کار انداختن پرطرفدارها
wrapstodon: به کار انداختن خلاصهٔ ماستودون
@ -348,6 +352,7 @@ fa:
hint: اطّلاعات اضافی
text: قانون
settings:
email_subscriptions: به کار انداختن نام‌نویسی‌های رایانامه‌ای
indexable: بودن صفحهٔ نمایه در نتیجه‌های جست‌وجو
show_application: نمایش این که فرسته را از کدام کاره فرستاده‌اید
tag:
@ -376,11 +381,13 @@ fa:
role: نقش
time_zone: منطقهٔ زمانی
user_role:
collection_limit: بیشینهٔ تعداد مجموعهٔ هر کاربر
color: رنگ نشان
highlighted: نمایش نقش به شکل نشان روی نمایهً کاربری
name: نام
permissions_as_keys: اجازه‌ها
position: اولویت
require_2fa: نیازمند هویت‌سنجی دو مرحله‌ای
username_block:
allow_with_approval: اجازهٔ ثبت‌نام با تأیید
comparison: روش مقایسه

View File

@ -92,6 +92,7 @@ he:
closed_registrations_message: להציג כאשר הרשמות חדשות אינן מאופשרות
content_cache_retention_period: כל ההודעות משרתים אחרים (לרבות הדהודים ותגובות) ימחקו אחרי מספר ימים, ללא קשר לאינטראקציה של משתמשים מקומיים איתם. בכלל זה הודעות שהמתשתמשים המקומיים סימנו בסימניה או חיבוב. איזכורים פרטיים ("דיאם") בין משתמשים בין שרתים שונים יאבדו גם הם ולא תהיה אפשרות לשחזרם. השימוש באפשרות הזו מיועד לשרתים עם ייעוד מיוחד ושובר את ציפיותיהם של רב המשתמשים כאשר האפשרות מופעלת בשרת לשימוש כללי.
custom_css: ניתן לבחור ערכות סגנון אישיות בגרסת הדפדפן של מסטודון.
email_footer_text: ניתן להוסיף מלל לתחתית מנשרי דואל.
favicon: WEBP, PNG, GIF או JPG. גובר על "פאבאייקון" ברירת המחדל ומחליף אותו באייקון נבחר בדפדפן.
landing_page: בחירה בעמוד שיוצג ראשון למבקרים חדשים בביקור הראשון בשרת שלך. אם תבחרו "נושאים חמים", אזי הנושאים החמים צריכים להיות מאופשרים בהעדפות "תגליות". אם תבחרו "פיד מקומי", אז "גישה לפידים חיים המציגים הודעות מקומיות" חייב להיות מכוון למצב "כולם" בהעדפות תגליות.
mascot: בחירת ציור למנשק הווב המתקדם.
@ -296,6 +297,7 @@ he:
closed_registrations_message: הודעה מיוחדת כשההרשמה לא מאופשרת
content_cache_retention_period: תקופת השמירה על תוכן חיצוני
custom_css: CSS בהתאמה אישית
email_footer_text: מלל תחתית נוסף
favicon: סמל מועדפים (Favicon)
landing_page: דף נחיתה למבקרים חדשים
local_live_feed_access: גישה לפידים חיים המציגים הודעות מקומיות
@ -324,6 +326,9 @@ he:
trendable_by_default: הרשאה לפריטים להופיע בנושאים החמים ללא אישור מוקדם
trends: אפשר פריטים חמים (טרנדים)
wrapstodon: הפעלת סיכומודון
form_email_subscriptions_confirmation:
agreement_email_volume: אני מבין.ה כי אפשור תכונה זו עשוי להגדיל משמעותית את כמות הדואל הנשלחת מהשרת ושאני לבדי אחראי לעלויות הנלוות לכך.
agreement_privacy_and_terms: עדכנתי את מדיניות הפרטיות ותנאי השירות.
interactions:
must_be_follower: חסימת התראות משאינם עוקבים
must_be_following: חסימת התראות משאינם נעקבים

View File

@ -324,6 +324,8 @@ nl:
trendable_by_default: Trends goedkeuren zonder voorafgaande beoordeling
trends: Trends inschakelen
wrapstodon: Wrapstodon inschakelen
form_email_subscriptions_confirmation:
agreement_privacy_and_terms: Ik heb het Privacybeleid en de Gebruiksvoorwaarden bijgewerkt.
interactions:
must_be_follower: Meldingen van mensen die jou niet volgen blokkeren
must_be_following: Meldingen van mensen die jij niet volgt blokkeren

View File

@ -236,7 +236,7 @@ tr:
locale: Arayüz dili
max_uses: Maksimum kullanım sayısı
new_password: Yeni parola
note: Kişisel bilgiler
note: Biyografi
otp_attempt: İki adımlı doğrulama kodu
password: Parola
phrase: Anahtar kelime veya kelime öbeği

View File

@ -345,12 +345,19 @@ sq:
updated_msg: Lajmërimi u përditësua me sukses!
collections:
accounts: Llogari
back_to_account: Mbrapsht te faqja e llogarisë
back_to_report: Mbrapsht te faqja e raportimit
batch:
add_to_report: 'Shtoje te raportimi #%{id}'
report: Raportim
collection_title: Koleksion nga %{name}
contents: Lëndë
no_collection_selected: Su ndryshua ndonjë koleksion, ngaqë su përzgjodh ndonjë
number_of_accounts:
one: 1 llogari
other: "%{count} llogari"
open: Hape
title: Koleksione llogarish - @%{name}
view_publicly: Shiheni publikisht
critical_update_pending: Përditësim kritik pezull
custom_emojis:

View File

@ -2178,7 +2178,7 @@ tr:
webauthn: Güvenlik anahtarları
unsubscriptions:
create:
action: Sunucu anasayfasına git
action: Sunucu ana sayfasına git
email_subscription:
confirmation_html: "%{name} kişisinden artık e-posta almayacaksınız."
title: Abonelikten ayrıldınız
@ -2294,7 +2294,7 @@ tr:
feature_moderation_title: Olması gerektiği gibi moderasyon
follow_action: Takip et
follow_step: Kendi akışınızı düzenliyorsunuz. Hadi onu ilginç kullanıcılarla dolduralım.
follow_title: Anasayfa akışınızı kişiselleştirin
follow_title: Ana sayfa akışınızı kişiselleştirin
follows_subtitle: Tanınmış hesapları takip edin
follows_title: Takip edebileceklerin
follows_view_more: Takip edecek daha fazla kişi görüntüleyin

View File

@ -341,11 +341,18 @@ zh-CN:
updated_msg: 公告已成功更新!
collections:
accounts: 账号
back_to_account: 返回账号信息页
back_to_report: 返回举报页
batch:
add_to_report: '添加到举报 #%{id}'
report: 举报
collection_title: "%{name} 的收藏列表"
contents: 内容
no_collection_selected: 因为没有选中任何收藏列表,所以没有更改
number_of_accounts:
other: "%{count} 个账号"
open: 打开
title: 该账号的收藏列表 - @%{name}
view_publicly: 以公开身份查看
critical_update_pending: 紧急更新待处理
custom_emojis:

View File

@ -341,11 +341,18 @@ zh-TW:
updated_msg: 成功更新公告!
collections:
accounts: 帳號
back_to_account: 返回帳號資訊頁面
back_to_report: 回到檢舉報告頁面
batch:
add_to_report: '新增至報告 #%{id}'
report: 檢舉
collection_title: "%{name} 的收藏名單"
contents: 內容
no_collection_selected: 因未選取任何收藏名單,所以什麼事都沒發生
number_of_accounts:
other: "%{count} 個帳號"
open: 開啟
title: 收藏名單帳號 - @%{name}
view_publicly: 公開檢視
critical_update_pending: 重要更新待升級
custom_emojis:

View File

@ -158,7 +158,7 @@ namespace :admin do
resource :reset, only: [:create]
resource :action, only: [:new, :create], controller: 'account_actions'
resources :collections, only: [:show]
resources :collections, only: [:index, :show], concerns: :batch
resources :statuses, only: [:index, :show], concerns: :batch
resources :relationships, only: [:index]

View File

@ -840,4 +840,36 @@ RSpec.describe Account do
end
end
end
describe '#needs_background_refresh?' do
subject { account.needs_background_refresh? }
context 'when account is local' do
let(:account) { Fabricate(:account) }
it { is_expected.to be false }
end
context 'when account is remote' do
let(:account) { Fabricate(:remote_account, last_webfingered_at:) }
context 'when account has never been updated or last update is unknown' do
let(:last_webfingered_at) { nil }
it { is_expected.to be true }
end
context 'when account has not been updated for over a week' do
let(:last_webfingered_at) { 8.days.ago }
it { is_expected.to be true }
end
context 'when account has been updated in the last week' do
let(:last_webfingered_at) { 4.days.ago }
it { is_expected.to be false }
end
end
end
end

View File

@ -0,0 +1,50 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::CollectionBatchAction do
subject do
described_class.new(
current_account:,
collection_ids:,
report_id:,
type:
)
end
let(:account) { Fabricate(:account) }
let(:target_account) { Fabricate(:account) }
let(:current_account) { Fabricate(:admin_user).account }
let(:collection) { Fabricate(:collection, account: account) }
let(:collection_ids) { [collection.id] }
let(:report) { Fabricate(:report, account: target_account, target_account: account) }
let(:type) { 'report' }
before 'create collection report' do
report.collections << collection
report.save!
end
describe '#save!' do
context 'when with_report? is true' do
let(:other_collection) { Fabricate(:collection, account: account) }
let(:report_id) { report.id }
it 'creates no report and adds the collection to the existing report' do
collection_ids << other_collection.id
expect { subject.save! }.to_not change(Report, :count)
expect(report.collections).to include(other_collection)
end
end
context 'when with_report? is false' do
let(:report_id) { nil }
it 'creates a new report from the collection_ids' do
expect { subject.save! }.to change(Report, :count).by(1)
expect(Report.last.collections).to include(collection)
end
end
end
end

View File

@ -2,40 +2,7 @@
ENV['RAILS_ENV'] ||= 'test'
if ENV.fetch('COVERAGE', false)
require 'simplecov'
SimpleCov.start 'rails' do
# During parallel runs, ensure unique names for post-run merge
command_name "job-#{ENV['TEST_ENV_NUMBER']}" if ENV['TEST_ENV_NUMBER']
if ENV['CI']
require 'simplecov-lcov'
formatter SimpleCov::Formatter::LcovFormatter
formatter.config.report_with_single_file = true
else
formatter SimpleCov::Formatter::HTMLFormatter
end
enable_coverage :branch
add_filter 'lib/linter'
add_group 'Libraries', 'lib'
add_group 'Policies', 'app/policies'
add_group 'Presenters', 'app/presenters'
add_group 'Search', 'app/chewy'
add_group 'Serializers', 'app/serializers'
add_group 'Services', 'app/services'
add_group 'Validators', 'app/validators'
end
if defined?(Flatware)
Flatware.configure do |config|
config.after_fork { |test| SimpleCov.at_fork.call(test) } # Combines parallel coverage results
end
end
end
require 'simplecov' if ENV.fetch('COVERAGE', false)
# This needs to be defined before Rails is initialized
STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020')

View File

@ -3,13 +3,19 @@
require 'rails_helper'
RSpec.describe 'Admin Collections' do
let(:account) { Fabricate(:account) }
let(:other_account) { Fabricate(:account) }
let(:collection) { Fabricate(:collection, account: account) }
let!(:second_collection) { Fabricate(:collection, account: account) }
let(:report) { Fabricate(:report, account: other_account, target_account: account) }
before do
sign_in Fabricate(:admin_user)
end
describe 'GET /admin/accounts/:account_id/collections/:id' do
let(:collection) { Fabricate(:collection) }
before do
sign_in Fabricate(:admin_user)
end
it 'returns success' do
get admin_account_collection_path(collection.account_id, collection)
@ -17,4 +23,34 @@ RSpec.describe 'Admin Collections' do
.to have_http_status(200)
end
end
describe 'GET /admin/accounts/:account_id/collections/' do
it 'returns http success' do
get admin_account_collections_path(account_id: account.id, id: collection.id)
expect(response).to have_http_status(200)
end
end
describe 'POST /admin/accounts/:account_id/collections/batch' do
subject { post batch_admin_account_collections_path(account_id: account.id, report_id: report_id, admin_collection_batch_action: { collection_ids: [collection.id, second_collection.id] }) }
context 'with a valid report' do
let(:report_id) { report.id }
it 'redirects to the report page' do
report.collections << collection
subject
expect(response).to redirect_to(admin_report_path(report.id))
end
end
context 'with an invalid report' do
let(:report_id) { nil }
it 'redirects to the account collections page' do
subject
expect(response).to redirect_to(admin_account_collections_path(account.id))
end
end
end
end

View File

@ -19,7 +19,7 @@ RSpec.describe AccountRefreshWorker do
context 'when account exists' do
context 'when account does not need refreshing' do
let(:account) { Fabricate(:account, last_webfingered_at: recent_webfinger_at) }
let(:account) { Fabricate(:remote_account, last_webfingered_at: recent_webfinger_at) }
it 'returns immediately without processing' do
worker.perform(account.id)
@ -29,9 +29,9 @@ RSpec.describe AccountRefreshWorker do
end
context 'when account needs refreshing' do
let(:account) { Fabricate(:account, last_webfingered_at: outdated_webfinger_at) }
let(:account) { Fabricate(:remote_account, last_webfingered_at: outdated_webfinger_at) }
it 'schedules an account update' do
it 'performs an account update' do
worker.perform(account.id)
expect(service).to have_received(:call)