Merge commit '8e7c3973dc67835e0ce9c7073b9344ec19d3910a' into glitch-soc/merge-upstream

This commit is contained in:
Claire
2026-02-19 21:52:23 +01:00
151 changed files with 1519 additions and 687 deletions

View File

@@ -187,7 +187,7 @@ GEM
irb (~> 1.10)
reline (>= 0.3.8)
debug_inspector (1.2.0)
devise (5.0.1)
devise (5.0.2)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 7.0)

View File

@@ -0,0 +1,22 @@
# frozen_string_literal: true
module Admin
class CollectionsController < BaseController
before_action :set_account
before_action :set_collection, only: :show
def show
authorize @collection, :show?
end
private
def set_account
@account = Account.find(params[:account_id])
end
def set_collection
@collection = @account.collections.includes(accepted_collection_items: :account).find(params[:id])
end
end
end

View File

@@ -50,7 +50,7 @@ module Admin
private
def filtered_reports
ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account)
ReportFilter.new(filter_params).results.order(id: :desc).includes(:account, :target_account, :collections)
end
def filter_params
@@ -58,7 +58,7 @@ module Admin
end
def set_report
@report = Report.find(params[:id])
@report = Report.includes(collections: :accepted_collection_items).find(params[:id])
end
end
end

View File

@@ -12,6 +12,26 @@ export interface ApiAccountRoleJSON {
name: string;
}
type ApiFeaturePolicy =
| 'public'
| 'followers'
| 'following'
| 'disabled'
| 'unsupported_policy';
type ApiUserFeaturePolicy =
| 'automatic'
| 'manual'
| 'denied'
| 'missing'
| 'unknown';
interface ApiFeaturePolicyJSON {
automatic: ApiFeaturePolicy[];
manual: ApiFeaturePolicy[];
current_user: ApiUserFeaturePolicy;
}
// See app/serializers/rest/account_serializer.rb
export interface BaseApiAccountJSON {
acct: string;
@@ -23,6 +43,7 @@ export interface BaseApiAccountJSON {
indexable: boolean;
display_name: string;
emojis: ApiCustomEmojiJSON[];
feature_approval: ApiFeaturePolicyJSON;
fields: ApiAccountFieldJSON[];
followers_count: number;
following_count: number;

View File

@@ -6,7 +6,7 @@ import { EmojiHTML } from './emoji/html';
import { useElementHandledLink } from './status/handled_link';
interface AccountBioProps {
className: string;
className?: string;
accountId: string;
showDropdown?: boolean;
}

View File

@@ -42,6 +42,13 @@ export const WithError: Story = {
},
};
export const AutoSize: Story = {
args: {
autoSize: true,
defaultValue: 'This textarea will grow as you type more lines.',
},
};
export const Plain: Story = {
render(args) {
return <TextArea {...args} />;

View File

@@ -3,12 +3,16 @@ import { forwardRef, useCallback } from 'react';
import classNames from 'classnames';
import type { TextareaAutosizeProps } from 'react-textarea-autosize';
import TextAreaAutosize from 'react-textarea-autosize';
import { FormFieldWrapper } from './form_field_wrapper';
import type { CommonFieldWrapperProps } from './form_field_wrapper';
import classes from './text_input.module.scss';
interface Props
extends ComponentPropsWithoutRef<'textarea'>, CommonFieldWrapperProps {}
type TextAreaProps =
| ({ autoSize?: false } & ComponentPropsWithoutRef<'textarea'>)
| ({ autoSize: true } & TextareaAutosizeProps);
/**
* A simple form field for multi-line text.
@@ -17,45 +21,56 @@ interface Props
* or optional (by explicitly setting `required={false}`)
*/
export const TextAreaField = forwardRef<HTMLTextAreaElement, Props>(
({ id, label, hint, required, hasError, ...otherProps }, ref) => (
<FormFieldWrapper
label={label}
hint={hint}
required={required}
hasError={hasError}
inputId={id}
>
{(inputProps) => <TextArea {...otherProps} {...inputProps} ref={ref} />}
</FormFieldWrapper>
),
);
export const TextAreaField = forwardRef<
HTMLTextAreaElement,
TextAreaProps & CommonFieldWrapperProps
>(({ id, label, hint, required, hasError, ...otherProps }, ref) => (
<FormFieldWrapper
label={label}
hint={hint}
required={required}
hasError={hasError}
inputId={id}
>
{(inputProps) => <TextArea {...otherProps} {...inputProps} ref={ref} />}
</FormFieldWrapper>
));
TextAreaField.displayName = 'TextAreaField';
export const TextArea = forwardRef<
HTMLTextAreaElement,
ComponentPropsWithoutRef<'textarea'>
>(({ className, onKeyDown, ...otherProps }, ref) => {
const handleSubmitHotkey = useCallback(
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
onKeyDown?.(e);
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
const targetForm = e.currentTarget.form;
targetForm?.requestSubmit();
}
},
[onKeyDown],
);
export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
({ className, onKeyDown, autoSize, ...otherProps }, ref) => {
const handleSubmitHotkey = useCallback(
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
onKeyDown?.(e);
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
const targetForm = e.currentTarget.form;
targetForm?.requestSubmit();
}
},
[onKeyDown],
);
return (
<textarea
{...otherProps}
onKeyDown={handleSubmitHotkey}
className={classNames(className, classes.input)}
ref={ref}
/>
);
});
if (autoSize) {
return (
<TextAreaAutosize
{...(otherProps as TextareaAutosizeProps)}
onKeyDown={handleSubmitHotkey}
className={classNames(className, classes.input)}
ref={ref}
/>
);
}
return (
<textarea
{...otherProps}
onKeyDown={handleSubmitHotkey}
className={classNames(className, classes.input)}
ref={ref}
/>
);
},
);
TextArea.displayName = 'TextArea';

View File

@@ -0,0 +1,94 @@
import { useCallback, useId, useRef, useState } from 'react';
import type { ChangeEventHandler, FC } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { TextArea } from '@/mastodon/components/form_fields';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { insertEmojiAtPosition } from '@/mastodon/features/emoji/utils';
import type { BaseConfirmationModalProps } from '@/mastodon/features/ui/components/confirmation_modals';
import { ConfirmationModal } from '@/mastodon/features/ui/components/confirmation_modals';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import classes from '../styles.module.scss';
import { CharCounter } from './char_counter';
import { EmojiPicker } from './emoji_picker';
const messages = defineMessages({
addTitle: {
id: 'account_edit.bio_modal.add_title',
defaultMessage: 'Add bio',
},
editTitle: {
id: 'account_edit.bio_modal.edit_title',
defaultMessage: 'Edit bio',
},
save: {
id: 'account_edit.save',
defaultMessage: 'Save',
},
});
const MAX_BIO_LENGTH = 500;
export const BioModal: FC<BaseConfirmationModalProps> = ({ onClose }) => {
const intl = useIntl();
const titleId = useId();
const counterId = useId();
const textAreaRef = useRef<HTMLTextAreaElement>(null);
const accountId = useCurrentAccountId();
const account = useAccount(accountId);
const [newBio, setNewBio] = useState(account?.note_plain ?? '');
const handleChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
(event) => {
setNewBio(event.currentTarget.value);
},
[],
);
const handlePickEmoji = useCallback((emoji: string) => {
setNewBio((prev) => {
const position = textAreaRef.current?.selectionStart ?? prev.length;
return insertEmojiAtPosition(prev, emoji, position);
});
}, []);
if (!account) {
return <LoadingIndicator />;
}
return (
<ConfirmationModal
title={intl.formatMessage(
account.note_plain ? messages.editTitle : messages.addTitle,
)}
titleId={titleId}
confirm={intl.formatMessage(messages.save)}
onConfirm={onClose} // To be implemented
onClose={onClose}
noFocusButton
>
<div className={classes.inputWrapper}>
<TextArea
value={newBio}
ref={textAreaRef}
onChange={handleChange}
className={classes.inputText}
aria-labelledby={titleId}
aria-describedby={counterId}
// eslint-disable-next-line jsx-a11y/no-autofocus -- This is a modal, it's fine.
autoFocus
autoSize
/>
<EmojiPicker onPick={handlePickEmoji} />
</div>
<CharCounter
currentLength={newBio.length}
maxLength={MAX_BIO_LENGTH}
id={counterId}
/>
</ConfirmationModal>
);
};

View File

@@ -0,0 +1,27 @@
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { polymorphicForwardRef } from '@/types/polymorphic';
import classes from '../styles.module.scss';
export const CharCounter = polymorphicForwardRef<
'p',
{ currentLength: number; maxLength: number }
>(({ currentLength, maxLength, as: Component = 'p' }, ref) => (
<Component
ref={ref}
className={classNames(
classes.counter,
currentLength > maxLength && classes.counterError,
)}
>
<FormattedMessage
id='account_edit.char_counter'
defaultMessage='{currentLength}/{maxLength} characters'
values={{ currentLength, maxLength }}
/>
</Component>
));
CharCounter.displayName = 'CharCounter';

View File

@@ -0,0 +1,27 @@
import { useCallback } from 'react';
import type { FC } from 'react';
import { isPlainObject } from '@reduxjs/toolkit';
import EmojiPickerDropdown from '../../compose/containers/emoji_picker_dropdown_container';
export const EmojiPicker: FC<{ onPick: (emoji: string) => void }> = ({
onPick,
}) => {
const handlePick = useCallback(
(emoji: unknown) => {
if (isPlainObject(emoji)) {
if ('native' in emoji && typeof emoji.native === 'string') {
onPick(emoji.native);
} else if (
'shortcode' in emoji &&
typeof emoji.shortcode === 'string'
) {
onPick(`:${emoji.shortcode}:`);
}
}
},
[onPick],
);
return <EmojiPickerDropdown onPickEmoji={handlePick} />;
};

View File

@@ -0,0 +1,87 @@
import { useCallback, useId, useRef, useState } from 'react';
import type { ChangeEventHandler, FC } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { TextInput } from '@/mastodon/components/form_fields';
import { insertEmojiAtPosition } from '@/mastodon/features/emoji/utils';
import type { BaseConfirmationModalProps } from '@/mastodon/features/ui/components/confirmation_modals';
import { ConfirmationModal } from '@/mastodon/features/ui/components/confirmation_modals';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import classes from '../styles.module.scss';
import { CharCounter } from './char_counter';
import { EmojiPicker } from './emoji_picker';
const messages = defineMessages({
addTitle: {
id: 'account_edit.name_modal.add_title',
defaultMessage: 'Add display name',
},
editTitle: {
id: 'account_edit.name_modal.edit_title',
defaultMessage: 'Edit display name',
},
save: {
id: 'account_edit.save',
defaultMessage: 'Save',
},
});
const MAX_NAME_LENGTH = 30;
export const NameModal: FC<BaseConfirmationModalProps> = ({ onClose }) => {
const intl = useIntl();
const titleId = useId();
const counterId = useId();
const inputRef = useRef<HTMLInputElement>(null);
const accountId = useCurrentAccountId();
const account = useAccount(accountId);
const [newName, setNewName] = useState(account?.display_name ?? '');
const handleChange: ChangeEventHandler<HTMLInputElement> = useCallback(
(event) => {
setNewName(event.currentTarget.value);
},
[],
);
const handlePickEmoji = useCallback((emoji: string) => {
setNewName((prev) => {
const position = inputRef.current?.selectionStart ?? prev.length;
return insertEmojiAtPosition(prev, emoji, position);
});
}, []);
return (
<ConfirmationModal
title={intl.formatMessage(messages.editTitle)}
titleId={titleId}
confirm={intl.formatMessage(messages.save)}
onConfirm={onClose} // To be implemented
onClose={onClose}
noCloseOnConfirm
noFocusButton
>
<div className={classes.inputWrapper}>
<TextInput
value={newName}
ref={inputRef}
onChange={handleChange}
className={classes.inputText}
aria-labelledby={titleId}
aria-describedby={counterId}
// eslint-disable-next-line jsx-a11y/no-autofocus -- This is a modal, it's fine.
autoFocus
/>
<EmojiPicker onPick={handlePickEmoji} />
</div>
<CharCounter
currentLength={newName.length}
maxLength={MAX_NAME_LENGTH}
id={counterId}
/>
</ConfirmationModal>
);
};

View File

@@ -0,0 +1,62 @@
import type { FC, ReactNode } from 'react';
import type { MessageDescriptor } from 'react-intl';
import { defineMessage, FormattedMessage, useIntl } from 'react-intl';
import classNames from 'classnames';
import { IconButton } from '@/mastodon/components/icon_button';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import classes from '../styles.module.scss';
const buttonMessage = defineMessage({
id: 'account_edit.section_edit_button',
defaultMessage: 'Edit',
});
interface AccountEditSectionProps {
title: MessageDescriptor;
description?: MessageDescriptor;
showDescription?: boolean;
onEdit?: () => void;
children?: ReactNode;
className?: string;
extraButtons?: ReactNode;
}
export const AccountEditSection: FC<AccountEditSectionProps> = ({
title,
description,
showDescription,
onEdit,
children,
className,
extraButtons,
}) => {
const intl = useIntl();
return (
<section className={classNames(className, classes.section)}>
<header className={classes.sectionHeader}>
<h3 className={classes.sectionTitle}>
<FormattedMessage {...title} />
</h3>
{onEdit && (
<IconButton
icon='pencil'
iconComponent={EditIcon}
onClick={onEdit}
title={`${intl.formatMessage(buttonMessage)} ${intl.formatMessage(title)}`}
/>
)}
{extraButtons}
</header>
{showDescription && (
<p className={classes.sectionSubtitle}>
<FormattedMessage {...description} />
</p>
)}
{children}
</section>
);
};

View File

@@ -1,23 +1,92 @@
import { useCallback } from 'react';
import type { FC } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import type { ModalType } from '@/mastodon/actions/modal';
import { openModal } from '@/mastodon/actions/modal';
import { AccountBio } from '@/mastodon/components/account_bio';
import { Avatar } from '@/mastodon/components/avatar';
import { Column } from '@/mastodon/components/column';
import { ColumnHeader } from '@/mastodon/components/column_header';
import { DisplayNameSimple } from '@/mastodon/components/display_name/simple';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import { autoPlayGif } from '@/mastodon/initial_state';
import { useAppDispatch } from '@/mastodon/store';
import { AccountEditSection } from './components/section';
import classes from './styles.module.scss';
const messages = defineMessages({
displayNameTitle: {
id: 'account_edit.display_name.title',
defaultMessage: 'Display name',
},
displayNamePlaceholder: {
id: 'account_edit.display_name.placeholder',
defaultMessage:
'Your display name is how your name appears on your profile and in timelines.',
},
bioTitle: {
id: 'account_edit.bio.title',
defaultMessage: 'Bio',
},
bioPlaceholder: {
id: 'account_edit.bio.placeholder',
defaultMessage: 'Add a short introduction to help others identify you.',
},
customFieldsTitle: {
id: 'account_edit.custom_fields.title',
defaultMessage: 'Custom fields',
},
customFieldsPlaceholder: {
id: 'account_edit.custom_fields.placeholder',
defaultMessage:
'Add your pronouns, external links, or anything else youd like to share.',
},
featuredHashtagsTitle: {
id: 'account_edit.featured_hashtags.title',
defaultMessage: 'Featured hashtags',
},
featuredHashtagsPlaceholder: {
id: 'account_edit.featured_hashtags.placeholder',
defaultMessage:
'Help others identify, and have quick access to, your favorite topics.',
},
profileTabTitle: {
id: 'account_edit.profile_tab.title',
defaultMessage: 'Profile tab settings',
},
profileTabSubtitle: {
id: 'account_edit.profile_tab.subtitle',
defaultMessage: 'Customize the tabs on your profile and what they display.',
},
});
export const AccountEdit: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
const accountId = useCurrentAccountId();
const account = useAccount(accountId);
const intl = useIntl();
const dispatch = useAppDispatch();
const handleOpenModal = useCallback(
(type: ModalType, props?: Record<string, unknown>) => {
dispatch(openModal({ modalType: type, modalProps: props ?? {} }));
},
[dispatch],
);
const handleNameEdit = useCallback(() => {
handleOpenModal('ACCOUNT_EDIT_NAME');
}, [handleOpenModal]);
const handleBioEdit = useCallback(() => {
handleOpenModal('ACCOUNT_EDIT_BIO');
}, [handleOpenModal]);
if (!accountId) {
return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
}
@@ -30,6 +99,8 @@ export const AccountEdit: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
);
}
const headerSrc = autoPlayGif ? account.header : account.header_static;
return (
<Column bindToDocument={!multiColumn} className={classes.column}>
<ColumnHeader
@@ -37,7 +108,7 @@ export const AccountEdit: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
id: 'account_edit.column_title',
defaultMessage: 'Edit Profile',
})}
className={classes.header}
className={classes.columnHeader}
showBackButton
extraButton={
<Link to={`/@${account.acct}`} className='button'>
@@ -48,6 +119,48 @@ export const AccountEdit: FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
</Link>
}
/>
<header>
<div className={classes.profileImage}>
{headerSrc && <img src={headerSrc} alt='' />}
</div>
<Avatar account={account} size={80} className={classes.avatar} />
</header>
<AccountEditSection
title={messages.displayNameTitle}
description={messages.displayNamePlaceholder}
showDescription={account.display_name.length === 0}
onEdit={handleNameEdit}
>
<DisplayNameSimple account={account} />
</AccountEditSection>
<AccountEditSection
title={messages.bioTitle}
description={messages.bioPlaceholder}
showDescription={!account.note_plain}
onEdit={handleBioEdit}
>
<AccountBio accountId={accountId} />
</AccountEditSection>
<AccountEditSection
title={messages.customFieldsTitle}
description={messages.customFieldsPlaceholder}
showDescription
/>
<AccountEditSection
title={messages.featuredHashtagsTitle}
description={messages.featuredHashtagsPlaceholder}
showDescription
/>
<AccountEditSection
title={messages.profileTabTitle}
description={messages.profileTabSubtitle}
showDescription
/>
</Column>
);
};

View File

@@ -3,7 +3,7 @@
border-top-width: 0;
}
.header {
.columnHeader {
:global(.column-header__buttons) {
align-items: center;
padding-inline-end: 16px;
@@ -11,16 +11,100 @@
}
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 24px 24px 12px;
.profileImage {
height: 120px;
background: var(--color-bg-secondary);
border-bottom: 1px solid var(--color-border-primary);
overflow: hidden;
> h1 {
flex-grow: 1;
font-weight: 600;
font-size: 15px;
@container (width >= 500px) {
height: 160px;
}
> img {
object-fit: cover;
object-position: top center;
width: 100%;
height: 100%;
}
}
.avatar {
margin-top: -64px;
margin-left: 18px;
border: 1px solid var(--color-border-primary);
}
.section {
padding: 20px;
border-bottom: 1px solid var(--color-border-primary);
font-size: 15px;
}
.sectionHeader {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
> button {
border: 1px solid var(--color-border-primary);
border-radius: 8px;
box-sizing: border-box;
padding: 4px;
svg {
width: 20px;
height: 20px;
}
}
}
.sectionTitle {
flex-grow: 1;
font-size: 17px;
font-weight: 600;
}
.sectionSubtitle {
color: var(--color-text-secondary);
}
.inputWrapper {
position: relative;
}
// Override input styles
.inputWrapper .inputText {
font-size: 15px;
padding-right: 32px;
}
textarea.inputText {
min-height: 82px;
height: 100%;
// 160px is approx the height of the modal header and footer
max-height: calc(80vh - 160px);
}
.inputWrapper :global(.emoji-picker-dropdown) {
position: absolute;
bottom: 10px;
right: 8px;
height: 24px;
z-index: 1;
:global(.icon-button) {
color: var(--color-text-secondary);
}
}
.counter {
margin-top: 4px;
font-size: 13px;
}
.counterError {
color: var(--color-text-error);
}

View File

@@ -153,7 +153,7 @@ const InnerNodeModal: FC<{
onConfirm={handleSave}
updating={state === 'saving'}
disabled={!isDirty}
closeWhenConfirm={false}
noCloseOnConfirm
noFocusButton
/>
);

View File

@@ -86,11 +86,9 @@ const InnerTimeline: FC<{ accountId: string; multiColumn: boolean }> = ({
const dispatch = useAppDispatch();
useEffect(() => {
if (accountId) {
if (!timeline) {
dispatch(expandTimelineByKey({ key }));
}
dispatch(expandTimelineByKey({ key }));
}
}, [accountId, dispatch, key, timeline]);
}, [accountId, dispatch, key]);
const handleLoadMore = useCallback(
(maxId: number) => {

View File

@@ -13,8 +13,9 @@ import { CollectionMenu } from './collection_menu';
export const CollectionMetaData: React.FC<{
collection: ApiCollectionJSON;
extended?: boolean;
className?: string;
}> = ({ collection, className }) => {
}> = ({ collection, extended, className }) => {
return (
<ul className={classNames(classes.metaList, className)}>
<FormattedMessage
@@ -23,6 +24,30 @@ export const CollectionMetaData: React.FC<{
values={{ count: collection.item_count }}
tagName='li'
/>
{extended && (
<>
{collection.discoverable ? (
<FormattedMessage
id='collections.visibility_public'
defaultMessage='Public'
tagName='li'
/>
) : (
<FormattedMessage
id='collections.visibility_unlisted'
defaultMessage='Unlisted'
tagName='li'
/>
)}
{collection.sensitive && (
<FormattedMessage
id='collections.sensitive'
defaultMessage='Sensitive'
tagName='li'
/>
)}
</>
)}
<FormattedMessage
id='collections.last_updated_at'
defaultMessage='Last updated: {date}'

View File

@@ -55,10 +55,6 @@ export const CollectionMenu: React.FC<{
text: intl.formatMessage(editorMessages.editDetails),
to: `/collections/${id}/edit/details`,
},
{
text: intl.formatMessage(editorMessages.editSettings),
to: `/collections/${id}/edit/settings`,
},
null,
{
text: intl.formatMessage(messages.delete),

View File

@@ -114,6 +114,7 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
{description && <p className={classes.description}>{description}</p>}
<AuthorNote id={collection.account_id} />
<CollectionMetaData
extended
collection={collection}
className={classes.metaData}
/>

View File

@@ -129,7 +129,11 @@ export const CollectionAccounts: React.FC<{
accountIds: suggestedAccountIds,
isLoading: isLoadingSuggestions,
searchAccounts,
} = useSearchAccounts();
} = useSearchAccounts({
filterResults: (account) =>
// Only suggest accounts who allow being featured/recommended
account.feature_approval.current_user === 'automatic',
});
const suggestedItems = suggestedAccountIds.map((id) => ({
id,

View File

@@ -4,15 +4,26 @@ import { FormattedMessage } from 'react-intl';
import { useHistory, useLocation } from 'react-router-dom';
import { isFulfilled } from '@reduxjs/toolkit';
import type {
ApiCollectionJSON,
ApiCreateCollectionPayload,
ApiUpdateCollectionPayload,
} from 'mastodon/api_types/collections';
import { Button } from 'mastodon/components/button';
import { FormStack, TextAreaField } from 'mastodon/components/form_fields';
import {
CheckboxField,
Fieldset,
FormStack,
RadioButtonField,
TextAreaField,
} from 'mastodon/components/form_fields';
import { TextInputField } from 'mastodon/components/form_fields/text_input_field';
import { updateCollection } from 'mastodon/reducers/slices/collections';
import {
createCollection,
updateCollection,
} from 'mastodon/reducers/slices/collections';
import { useAppDispatch } from 'mastodon/store';
import type { TempCollectionState } from './state';
@@ -27,12 +38,21 @@ export const CollectionDetails: React.FC<{
const history = useHistory();
const location = useLocation<TempCollectionState>();
const { id, initialName, initialDescription, initialTopic, initialItemIds } =
getCollectionEditorState(collection, location.state);
const {
id,
initialName,
initialDescription,
initialTopic,
initialItemIds,
initialDiscoverable,
initialSensitive,
} = getCollectionEditorState(collection, location.state);
const [name, setName] = useState(initialName);
const [description, setDescription] = useState(initialDescription);
const [topic, setTopic] = useState(initialTopic);
const [discoverable, setDiscoverable] = useState(initialDiscoverable);
const [sensitive, setSensitive] = useState(initialSensitive);
const handleNameChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
@@ -55,6 +75,20 @@ export const CollectionDetails: React.FC<{
[],
);
const handleDiscoverableChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setDiscoverable(event.target.value === 'public');
},
[],
);
const handleSensitiveChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setSensitive(event.target.checked);
},
[],
);
const handleSubmit = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
@@ -65,108 +99,208 @@ export const CollectionDetails: React.FC<{
name,
description,
tag_name: topic || null,
discoverable,
sensitive,
};
void dispatch(updateCollection({ payload })).then(() => {
history.push(`/collections/${id}`);
history.goBack();
});
} else {
const payload: Partial<ApiCreateCollectionPayload> = {
const payload: ApiCreateCollectionPayload = {
name,
description,
tag_name: topic || null,
discoverable,
sensitive,
account_ids: initialItemIds,
};
if (topic) {
payload.tag_name = topic;
}
history.replace('/collections/new', payload);
history.push('/collections/new/settings', payload);
void dispatch(
createCollection({
payload,
}),
).then((result) => {
if (isFulfilled(result)) {
history.replace(
`/collections/${result.payload.collection.id}/edit/details`,
);
history.push(`/collections/${result.payload.collection.id}`);
}
});
}
},
[id, name, description, topic, dispatch, history, initialItemIds],
[
id,
name,
description,
topic,
discoverable,
sensitive,
dispatch,
history,
initialItemIds,
],
);
return (
<FormStack as='form' onSubmit={handleSubmit}>
{!id && (
<WizardStepHeader
step={2}
title={
<form onSubmit={handleSubmit} className={classes.form}>
<FormStack className={classes.formFieldStack}>
{!id && (
<WizardStepHeader
step={2}
title={
<FormattedMessage
id='collections.create.basic_details_title'
defaultMessage='Basic details'
/>
}
/>
)}
<TextInputField
required
label={
<FormattedMessage
id='collections.create.basic_details_title'
defaultMessage='Basic details'
id='collections.collection_name'
defaultMessage='Name'
/>
}
/>
)}
<TextInputField
required
label={
<FormattedMessage
id='collections.collection_name'
defaultMessage='Name'
/>
}
hint={
<FormattedMessage
id='collections.name_length_hint'
defaultMessage='40 characters limit'
/>
}
value={name}
onChange={handleNameChange}
maxLength={40}
/>
<TextAreaField
required
label={
<FormattedMessage
id='collections.collection_description'
defaultMessage='Description'
/>
}
hint={
<FormattedMessage
id='collections.description_length_hint'
defaultMessage='100 characters limit'
/>
}
value={description}
onChange={handleDescriptionChange}
maxLength={100}
/>
<TextInputField
required={false}
label={
<FormattedMessage
id='collections.collection_topic'
defaultMessage='Topic'
/>
}
hint={
<FormattedMessage
id='collections.topic_hint'
defaultMessage='Add a hashtag that helps others understand the main topic of this collection.'
/>
}
value={topic}
onChange={handleTopicChange}
maxLength={40}
/>
<div className={classes.actionWrapper}>
<Button type='submit'>
{id ? (
<FormattedMessage id='lists.save' defaultMessage='Save' />
) : (
hint={
<FormattedMessage
id='collections.continue'
defaultMessage='Continue'
id='collections.name_length_hint'
defaultMessage='40 characters limit'
/>
)}
</Button>
}
value={name}
onChange={handleNameChange}
maxLength={40}
/>
<TextAreaField
required
label={
<FormattedMessage
id='collections.collection_description'
defaultMessage='Description'
/>
}
hint={
<FormattedMessage
id='collections.description_length_hint'
defaultMessage='100 characters limit'
/>
}
value={description}
onChange={handleDescriptionChange}
maxLength={100}
/>
<TextInputField
required={false}
label={
<FormattedMessage
id='collections.collection_topic'
defaultMessage='Topic'
/>
}
hint={
<FormattedMessage
id='collections.topic_hint'
defaultMessage='Add a hashtag that helps others understand the main topic of this collection.'
/>
}
value={topic}
onChange={handleTopicChange}
maxLength={40}
/>
<Fieldset
legend={
<FormattedMessage
id='collections.visibility_title'
defaultMessage='Visibility'
/>
}
>
<RadioButtonField
label={
<FormattedMessage
id='collections.visibility_public'
defaultMessage='Public'
/>
}
hint={
<FormattedMessage
id='collections.visibility_public_hint'
defaultMessage='Discoverable in search results and other areas where recommendations appear.'
/>
}
value='public'
checked={discoverable}
onChange={handleDiscoverableChange}
/>
<RadioButtonField
label={
<FormattedMessage
id='collections.visibility_unlisted'
defaultMessage='Unlisted'
/>
}
hint={
<FormattedMessage
id='collections.visibility_unlisted_hint'
defaultMessage='Visible to anyone with a link. Hidden from search results and recommendations.'
/>
}
value='unlisted'
checked={!discoverable}
onChange={handleDiscoverableChange}
/>
</Fieldset>
<Fieldset
legend={
<FormattedMessage
id='collections.content_warning'
defaultMessage='Content warning'
/>
}
>
<CheckboxField
label={
<FormattedMessage
id='collections.mark_as_sensitive'
defaultMessage='Mark as sensitive'
/>
}
hint={
<FormattedMessage
id='collections.mark_as_sensitive_hint'
defaultMessage="Hides the collection's description and accounts behind a content warning. The collection name will still be visible."
/>
}
checked={sensitive}
onChange={handleSensitiveChange}
/>
</Fieldset>
</FormStack>
<div className={classes.stickyFooter}>
<div className={classes.actionWrapper}>
<Button type='submit'>
{id ? (
<FormattedMessage id='lists.save' defaultMessage='Save' />
) : (
<FormattedMessage
id='collections.create_collection'
defaultMessage='Create collection'
/>
)}
</Button>
</div>
</div>
</FormStack>
</form>
);
};

View File

@@ -21,7 +21,6 @@ import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { CollectionAccounts } from './accounts';
import { CollectionDetails } from './details';
import { CollectionSettings } from './settings';
export const messages = defineMessages({
create: {
@@ -34,20 +33,12 @@ export const messages = defineMessages({
},
editDetails: {
id: 'collections.edit_details',
defaultMessage: 'Edit basic details',
defaultMessage: 'Edit details',
},
manageAccounts: {
id: 'collections.manage_accounts',
defaultMessage: 'Manage accounts',
},
manageAccountsLong: {
id: 'collections.manage_accounts_in_collection',
defaultMessage: 'Manage accounts in this collection',
},
editSettings: {
id: 'collections.edit_settings',
defaultMessage: 'Edit settings',
},
});
function usePageTitle(id: string | undefined) {
@@ -62,8 +53,6 @@ function usePageTitle(id: string | undefined) {
return messages.manageAccounts;
} else if (matchPath(location.pathname, { path: `${path}/details` })) {
return messages.editDetails;
} else if (matchPath(location.pathname, { path: `${path}/settings` })) {
return messages.editSettings;
} else {
throw new Error('No page title defined for route');
}
@@ -117,11 +106,6 @@ export const CollectionEditorPage: React.FC<{
// eslint-disable-next-line react/jsx-no-bind
render={() => <CollectionDetails collection={collection} />}
/>
<Route
path={`${path}/settings`}
// eslint-disable-next-line react/jsx-no-bind
render={() => <CollectionSettings collection={collection} />}
/>
</Switch>
)}
</div>

View File

@@ -1,199 +0,0 @@
import { useCallback, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { useHistory, useLocation } from 'react-router-dom';
import { isFulfilled } from '@reduxjs/toolkit';
import type {
ApiCollectionJSON,
ApiCreateCollectionPayload,
ApiUpdateCollectionPayload,
} from 'mastodon/api_types/collections';
import { Button } from 'mastodon/components/button';
import {
Fieldset,
FormStack,
CheckboxField,
RadioButtonField,
} from 'mastodon/components/form_fields';
import {
createCollection,
updateCollection,
} from 'mastodon/reducers/slices/collections';
import { useAppDispatch } from 'mastodon/store';
import type { TempCollectionState } from './state';
import { getCollectionEditorState } from './state';
import classes from './styles.module.scss';
import { WizardStepHeader } from './wizard_step_header';
export const CollectionSettings: React.FC<{
collection?: ApiCollectionJSON | null;
}> = ({ collection }) => {
const dispatch = useAppDispatch();
const history = useHistory();
const location = useLocation<TempCollectionState>();
const { id, initialDiscoverable, initialSensitive, ...editorState } =
getCollectionEditorState(collection, location.state);
const [discoverable, setDiscoverable] = useState(initialDiscoverable);
const [sensitive, setSensitive] = useState(initialSensitive);
const handleDiscoverableChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setDiscoverable(event.target.value === 'public');
},
[],
);
const handleSensitiveChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setSensitive(event.target.checked);
},
[],
);
const handleSubmit = useCallback(
(e: React.FormEvent) => {
e.preventDefault();
if (id) {
const payload: ApiUpdateCollectionPayload = {
id,
discoverable,
sensitive,
};
void dispatch(updateCollection({ payload })).then(() => {
history.push(`/collections/${id}`);
});
} else {
const payload: ApiCreateCollectionPayload = {
name: editorState.initialName,
description: editorState.initialDescription,
discoverable,
sensitive,
account_ids: editorState.initialItemIds,
};
if (editorState.initialTopic) {
payload.tag_name = editorState.initialTopic;
}
void dispatch(
createCollection({
payload,
}),
).then((result) => {
if (isFulfilled(result)) {
history.replace(
`/collections/${result.payload.collection.id}/edit/settings`,
);
history.push(`/collections`);
}
});
}
},
[id, discoverable, sensitive, dispatch, history, editorState],
);
return (
<FormStack as='form' onSubmit={handleSubmit}>
{!id && (
<WizardStepHeader
step={3}
title={
<FormattedMessage
id='collections.create.settings_title'
defaultMessage='Settings'
/>
}
/>
)}
<Fieldset
legend={
<FormattedMessage
id='collections.visibility_title'
defaultMessage='Visibility'
/>
}
>
<RadioButtonField
label={
<FormattedMessage
id='collections.visibility_public'
defaultMessage='Public'
/>
}
hint={
<FormattedMessage
id='collections.visibility_public_hint'
defaultMessage='Discoverable in search results and other areas where recommendations appear.'
/>
}
value='public'
checked={discoverable}
onChange={handleDiscoverableChange}
/>
<RadioButtonField
label={
<FormattedMessage
id='collections.visibility_unlisted'
defaultMessage='Unlisted'
/>
}
hint={
<FormattedMessage
id='collections.visibility_unlisted_hint'
defaultMessage='Visible to anyone with a link. Hidden from search results and recommendations.'
/>
}
value='unlisted'
checked={!discoverable}
onChange={handleDiscoverableChange}
/>
</Fieldset>
<Fieldset
legend={
<FormattedMessage
id='collections.content_warning'
defaultMessage='Content warning'
/>
}
>
<CheckboxField
label={
<FormattedMessage
id='collections.mark_as_sensitive'
defaultMessage='Mark as sensitive'
/>
}
hint={
<FormattedMessage
id='collections.mark_as_sensitive_hint'
defaultMessage="Hides the collection's description and accounts behind a content warning. The collection name will still be visible."
/>
}
checked={sensitive}
onChange={handleSensitiveChange}
/>
</Fieldset>
<div className={classes.actionWrapper}>
<Button type='submit'>
{id ? (
<FormattedMessage id='lists.save' defaultMessage='Save' />
) : (
<FormattedMessage
id='collections.create_collection'
defaultMessage='Create collection'
/>
)}
</Button>
</div>
</FormStack>
);
};

View File

@@ -12,7 +12,7 @@ export const WizardStepHeader: React.FC<{
<FormattedMessage
id='collections.create.steps'
defaultMessage='Step {step}/{total}'
values={{ step, total: 3 }}
values={{ step, total: 2 }}
>
{(content) => <p className={classes.step}>{content}</p>}
</FormattedMessage>

View File

@@ -67,6 +67,24 @@ export function emojiToUnicodeHex(emoji: string): string {
return codes.join('-');
}
const CHARS_ALLOWED_AROUND_EMOJI =
// eslint-disable-next-line no-control-regex
/[>< …\u0009-\u000d\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]/;
// TODO: Move to picker file when that's being built out.
export function insertEmojiAtPosition(
text: string,
emoji: string,
position = text.length,
): string {
const isShortcode = isCustomEmoji(emoji);
const needsSpace =
isShortcode &&
position > 0 &&
!CHARS_ALLOWED_AROUND_EMOJI.test(text[position - 1] ?? '');
return `${text.slice(0, position)}${needsSpace ? ' ' : ''}${emoji} ${text.slice(position)}`;
}
function supportsRegExpSets() {
return 'unicodeSets' in RegExp.prototype;
}

View File

@@ -10,8 +10,10 @@ import { useAppDispatch } from 'mastodon/store';
export function useSearchAccounts({
resetOnInputClear = true,
onSettled,
filterResults,
}: {
onSettled?: (value: string) => void;
filterResults?: (account: ApiAccountJSON) => boolean;
resetOnInputClear?: boolean;
} = {}) {
const dispatch = useAppDispatch();
@@ -49,8 +51,9 @@ export function useSearchAccounts({
},
})
.then((data) => {
dispatch(importFetchedAccounts(data));
setAccountIds(data.map((a) => a.id));
const accounts = filterResults ? data.filter(filterResults) : data;
dispatch(importFetchedAccounts(accounts));
setAccountIds(accounts.map((a) => a.id));
setLoadingState('idle');
onSettled?.(value);
})

View File

@@ -11,20 +11,23 @@ export interface BaseConfirmationModalProps {
export const ConfirmationModal: React.FC<
{
title: React.ReactNode;
titleId?: string;
message?: React.ReactNode;
confirm: React.ReactNode;
cancel?: React.ReactNode;
secondary?: React.ReactNode;
onSecondary?: () => void;
onConfirm: () => void;
closeWhenConfirm?: boolean;
noCloseOnConfirm?: boolean;
extraContent?: React.ReactNode;
children?: React.ReactNode;
updating?: boolean;
disabled?: boolean;
noFocusButton?: boolean;
} & BaseConfirmationModalProps
> = ({
title,
titleId,
message,
confirm,
cancel,
@@ -32,19 +35,20 @@ export const ConfirmationModal: React.FC<
onConfirm,
secondary,
onSecondary,
closeWhenConfirm = true,
extraContent,
children,
updating,
disabled,
noCloseOnConfirm = false,
noFocusButton = false,
}) => {
const handleClick = useCallback(() => {
if (closeWhenConfirm) {
if (!noCloseOnConfirm) {
onClose();
}
onConfirm();
}, [onClose, onConfirm, closeWhenConfirm]);
}, [onClose, onConfirm, noCloseOnConfirm]);
const handleSecondary = useCallback(() => {
onClose();
@@ -55,10 +59,10 @@ export const ConfirmationModal: React.FC<
<div className='modal-root__modal safety-action-modal'>
<div className='safety-action-modal__top'>
<div className='safety-action-modal__confirmation'>
<h1>{title}</h1>
<h1 id={titleId}>{title}</h1>
{message && <p>{message}</p>}
{extraContent}
{extraContent ?? children}
</div>
</div>

View File

@@ -1,3 +1,4 @@
export type { BaseConfirmationModalProps } from './confirmation_modal';
export { ConfirmationModal } from './confirmation_modal';
export { ConfirmDeleteStatusModal } from './delete_status';
export { ConfirmDeleteListModal } from './delete_list';

View File

@@ -33,7 +33,7 @@ const messages = defineMessages({
/**
* [1] Since we only want this modal to have two buttons "Don't ask again" and
* "Got it" , we have to use the `onClose` handler to handle the "Don't ask again"
* functionality. Because of this, we need to set `closeWhenConfirm` to false and
* functionality. Because of this, we need to set `noCloseOnConfirm` to true and
* close the modal manually.
* This prevents the modal from being dismissed permanently when just confirming.
*/
@@ -65,13 +65,13 @@ export const QuietPostQuoteInfoModal: React.FC<{ status: Status }> = ({
return (
<ConfirmationModal
closeWhenConfirm={false} // [1]
title={intl.formatMessage(messages.title)}
message={intl.formatMessage(messages.message)}
confirm={intl.formatMessage(messages.got_it)}
cancel={intl.formatMessage(messages.dismiss)}
onConfirm={confirm}
onClose={dismiss}
noCloseOnConfirm
/>
);
};

View File

@@ -88,6 +88,8 @@ export const MODAL_COMPONENTS = {
'ANNUAL_REPORT': AnnualReportModal,
'COMPOSE_PRIVACY': () => Promise.resolve({ default: VisibilityModal }),
'ACCOUNT_NOTE': () => import('@/mastodon/features/account_timeline/modals/note_modal').then(module => ({ default: module.AccountNoteModal })),
'ACCOUNT_EDIT_NAME': () => import('@/mastodon/features/account_edit/components/name_modal').then(module => ({ default: module.NameModal })),
'ACCOUNT_EDIT_BIO': () => import('@/mastodon/features/account_edit/components/bio_modal').then(module => ({ default: module.BioModal })),
};
export default class ModalRoot extends PureComponent {

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "Стварыць калекцыю",
"collections.delete_collection": "Выдаліць калекцыю",
"collections.description_length_hint": "Максімум 100 сімвалаў",
"collections.detail.accounts_heading": "Уліковыя запісы",
"collections.detail.curated_by_author": "Курыруе {author}",
"collections.detail.curated_by_you": "Курыруеце Вы",
"collections.detail.loading": "Загружаецца калекцыя…",
"collections.detail.share": "Падзяліцца гэтай калекцыяй",
"collections.edit_details": "Змяніць асноўныя звесткі",
"collections.edit_settings": "Змяніць налады",
"collections.error_loading_collections": "Адбылася памылка падчас загрузкі Вашых калекцый.",

View File

@@ -13,11 +13,18 @@
"about.not_available": "Tato informace nebyla zpřístupněna na tomto serveru.",
"about.powered_by": "Decentralizovaná sociální média poháněná {mastodon}",
"about.rules": "Pravidla serveru",
"account.about": "O účtu",
"account.account_note_header": "Osobní poznámka",
"account.activity": "Aktivita",
"account.add_note": "Přidat vlastní poznámku",
"account.add_or_remove_from_list": "Přidat nebo odstranit ze seznamů",
"account.badges.admin": "Admin",
"account.badges.blocked": "Zablokovaný",
"account.badges.bot": "Bot",
"account.badges.domain_blocked": "Zablokovaná doména",
"account.badges.group": "Skupina",
"account.badges.muted": "Ztišeno",
"account.badges.muted_until": "Ztišen do {until}",
"account.block": "Blokovat @{name}",
"account.block_domain": "Blokovat doménu {domain}",
"account.block_short": "Zablokovat",
@@ -28,6 +35,7 @@
"account.direct": "Soukromě zmínit @{name}",
"account.disable_notifications": "Přestat mě upozorňovat, když @{name} zveřejní příspěvek",
"account.domain_blocking": "Blokované domény",
"account.edit_note": "Upravit vlastní poznámku",
"account.edit_profile": "Upravit profil",
"account.edit_profile_short": "Upravit",
"account.enable_notifications": "Oznamovat mi příspěvky @{name}",
@@ -40,6 +48,12 @@
"account.featured.hashtags": "Hashtagy",
"account.featured_tags.last_status_at": "Poslední příspěvek {date}",
"account.featured_tags.last_status_never": "Žádné příspěvky",
"account.filters.all": "Veškerá aktivita",
"account.filters.boosts_toggle": "Zobrazit boosty",
"account.filters.posts_boosts": "Příspěvky a boosty",
"account.filters.posts_only": "Příspěvky",
"account.filters.posts_replies": "Příspěvky a odpovědi",
"account.filters.replies_toggle": "Zobrazit odpovědi",
"account.follow": "Sledovat",
"account.follow_back": "Také sledovat",
"account.follow_back_short": "Také sledovat",
@@ -65,6 +79,24 @@
"account.locked_info": "Stav soukromí tohoto účtu je nastaven na zamčeno. Jeho vlastník ručně posuzuje, kdo ho může sledovat.",
"account.media": "Média",
"account.mention": "Zmínit @{name}",
"account.menu.add_to_list": "Přidat do seznamu…",
"account.menu.block": "Blokovat účet",
"account.menu.block_domain": "Blokovat {domain}",
"account.menu.copied": "Odkaz účtu byl zkopírován do schránky",
"account.menu.copy": "Zkopírovat odkaz",
"account.menu.direct": "Soukromě zmínit",
"account.menu.hide_reblogs": "Skrýt boosty na časové ose",
"account.menu.mention": "Zmínit",
"account.menu.mute": "Ztlumit účet",
"account.menu.note.description": "Viditelné pouze pro vás",
"account.menu.open_original_page": "Zobrazit na {domain}",
"account.menu.remove_follower": "Odstranit sledujícího",
"account.menu.report": "Nahlásit účet",
"account.menu.share": "Sdílet…",
"account.menu.show_reblogs": "Zobrazit boosty na časové ose",
"account.menu.unblock": "Odblokovat účet",
"account.menu.unblock_domain": "Odblokovat {domain}",
"account.menu.unmute": "Zrušit ztlumení účtu",
"account.moved_to": "Uživatel {name} uvedl, že jeho nový účet je nyní:",
"account.mute": "Skrýt @{name}",
"account.mute_notifications_short": "Ztlumit upozornění",
@@ -72,7 +104,14 @@
"account.muted": "Skrytý",
"account.muting": "Ztlumení",
"account.mutual": "Sledujete se navzájem",
"account.name.help.domain": "{domain} je server, který hostuje profily a příspěvky uživatelů.",
"account.name.help.domain_self": "{domain} je váš server, který hostuje váš profil a příspěvky.",
"account.no_bio": "Nebyl poskytnut žádný popis.",
"account.node_modal.field_label": "Vlastní poznámka",
"account.node_modal.save": "Uložit",
"account.node_modal.title": "Přidat vlastní poznámku",
"account.note.edit_button": "Upravit",
"account.note.title": "Vlastní poznámka (viditelná pouze pro vás)",
"account.open_original_page": "Otevřít původní stránku",
"account.posts": "Příspěvky",
"account.posts_with_replies": "Příspěvky a odpovědi",
@@ -83,6 +122,8 @@
"account.share": "Sdílet profil @{name}",
"account.show_reblogs": "Zobrazit boosty od @{name}",
"account.statuses_counter": "{count, plural, one {{counter} příspěvek} few {{counter} příspěvky} many {{counter} příspěvků} other {{counter} příspěvků}}",
"account.timeline.pinned": "Připnuto",
"account.timeline.pinned.view_all": "Zobrazit všechny připnuté příspěvky",
"account.unblock": "Odblokovat @{name}",
"account.unblock_domain": "Odblokovat doménu {domain}",
"account.unblock_domain_short": "Odblokovat",
@@ -92,6 +133,8 @@
"account.unmute": "Zrušit skrytí @{name}",
"account.unmute_notifications_short": "Zrušit ztlumení oznámení",
"account.unmute_short": "Zrušit skrytí",
"account_edit.column_button": "Hotovo",
"account_edit.column_title": "Upravit profil",
"account_note.placeholder": "Klikněte pro přidání poznámky",
"admin.dashboard.daily_retention": "Míra udržení uživatelů podle dne po registraci",
"admin.dashboard.monthly_retention": "Míra udržení uživatelů podle měsíce po registraci",
@@ -186,6 +229,7 @@
"bundle_modal_error.close": "Zavřít",
"bundle_modal_error.message": "Něco se pokazilo při načítání této obrazovky.",
"bundle_modal_error.retry": "Zkusit znovu",
"callout.dismiss": "Zamítnout",
"carousel.current": "<sr>Snímek</sr> {current, number}/{max, number}",
"carousel.slide": "Snímek {current, number} z {max, number}",
"closed_registrations.other_server_instructions": "Protože Mastodon je decentralizovaný, můžete si vytvořit účet na jiném serveru a přesto komunikovat s tímto serverem.",
@@ -193,12 +237,24 @@
"closed_registrations_modal.find_another_server": "Najít jiný server",
"closed_registrations_modal.preamble": "Mastodon je decentralizovaný, takže bez ohledu na to, kde vytvoříte svůj účet, budete moci sledovat a komunikovat s kýmkoli na tomto serveru. Můžete ho dokonce hostovat!",
"closed_registrations_modal.title": "Registrace na Mastodon",
"collections.continue": "Pokračovat",
"collections.mark_as_sensitive_hint": "Skryje popis kolekce a účty za varováním obsahu. Název kolekce bude stále viditelný.",
"collections.name_length_hint": "Max. 100 znaků",
"collections.new_collection": "Nová sbírka",
"collections.no_collections_yet": "Ještě nemáte žádné sbírky.",
"collections.remove_account": "Odstranit tento účet",
"collections.search_accounts_label": "Hledat účty pro přidání…",
"collections.search_accounts_max_reached": "Přidali jste maximální počet účtů",
"collections.topic_hint": "Přidat štítek, který pomůže ostatním pochopit hlavní téma této kolekce.",
"collections.view_collection": "Zobrazit sbírku",
"collections.visibility_public": "Veřejné",
"collections.visibility_public_hint": "Objevitelné ve výsledcích vyhledávání a dalších místech, kde se objevují doporučení.",
"collections.visibility_title": "Viditelnost",
"collections.visibility_unlisted": "Neveřejný",
"column.about": "O aplikaci",
"column.blocks": "Blokovaní uživatelé",
"column.bookmarks": "Záložky",
"column.collections": "Mé sbírky",
"column.community": "Místní časová osa",
"column.create_list": "Vytvořit seznam",
"column.direct": "Soukromé zmínky",
@@ -225,6 +281,10 @@
"column_header.show_settings": "Zobrazit nastavení",
"column_header.unpin": "Odepnout",
"column_search.cancel": "Zrušit",
"combobox.close_results": "Zavřít výsledky",
"combobox.loading": "Načítání",
"combobox.no_results_found": "Žádné výsledky pro toto vyhledávání",
"combobox.open_results": "Otevřít výsledky",
"community.column_settings.local_only": "Pouze místní",
"community.column_settings.media_only": "Pouze média",
"community.column_settings.remote_only": "Pouze vzdálené",
@@ -258,6 +318,7 @@
"confirmations.delete.confirm": "Smazat",
"confirmations.delete.message": "Opravdu chcete smazat tento příspěvek?",
"confirmations.delete.title": "Smazat příspěvek?",
"confirmations.delete_collection.title": "Smazat „{name}“?",
"confirmations.delete_list.confirm": "Smazat",
"confirmations.delete_list.message": "Opravdu chcete tento seznam navždy smazat?",
"confirmations.delete_list.title": "Smazat seznam?",
@@ -364,6 +425,8 @@
"emoji_button.search_results": "Výsledky hledání",
"emoji_button.symbols": "Symboly",
"emoji_button.travel": "Cestování a místa",
"empty_column.account_about.me": "Zatím jste o sobě nepřidali žádné informace.",
"empty_column.account_about.other": "{acct} zatím o sobě nepřidali žádné informace.",
"empty_column.account_featured.me": "Zatím jste nic nezvýraznili. Věděli jste, že na svém profilu můžete zvýraznit hashtagy, které používáte nejvíce, a dokonce účty vašich přátel?",
"empty_column.account_featured.other": "{acct} zatím nic nezvýraznili. Věděli jste, že na svém profilu můžete zvýraznit hashtagy, které používáte nejvíce, a dokonce účty vašich přátel?",
"empty_column.account_featured_other.unknown": "Tento účet zatím nemá nic zvýrazněného.",
@@ -389,6 +452,7 @@
"empty_column.notification_requests": "Vyčištěno! Nic tu není. Jakmile obdržíš nové notifikace, objeví se zde podle tvého nastavení.",
"empty_column.notifications": "Zatím nemáte žádná oznámení. Až s vámi někdo bude interagovat, uvidíte to zde.",
"empty_column.public": "Tady nic není! Napište něco veřejně, nebo začněte ručně sledovat uživatele z jiných serverů, aby tu něco přibylo",
"empty_state.no_results": "Žádné výsledky",
"error.no_hashtag_feed_access": "Zaregistrujte se nebo se přihlaste k zobrazení a sledování tohoto hashtagu.",
"error.unexpected_crash.explanation": "Kvůli chybě v našem kódu nebo problému s kompatibilitou prohlížeče nemohla být tato stránka správně zobrazena.",
"error.unexpected_crash.explanation_addons": "Tuto stránku nelze správně zobrazit. Takovou chybu obvykle způsobuje doplněk prohlížeče nebo nástroje pro automatický překlad.",
@@ -404,6 +468,7 @@
"featured_carousel.current": "<sr>Příspěvek</sr> {current, number}/{max, number}",
"featured_carousel.header": "{count, plural, one {{counter} zvýrazněný příspěvek} few {{counter} zvýrazněné příspěvky} many {{counter} zvýrazněných příspěvků} other {{counter} zvýrazněných příspěvků}}",
"featured_carousel.slide": "Příspěvek {current, number} z {max, number}",
"featured_tags.more_items": "+{count}",
"filter_modal.added.context_mismatch_explanation": "Tato kategorie filtrů se nevztahuje na kontext, ve kterém jste tento příspěvek otevřeli. Pokud chcete, aby byl příspěvek filtrován i v tomto kontextu, budete muset filtr upravit.",
"filter_modal.added.context_mismatch_title": "Kontext se neshoduje!",
"filter_modal.added.expired_explanation": "Tato kategorie filtrů vypršela, budete muset změnit datum vypršení platnosti, aby mohla být použita.",
@@ -445,6 +510,7 @@
"follow_suggestions.view_all": "Zobrazit vše",
"follow_suggestions.who_to_follow": "Koho sledovat",
"followed_tags": "Sledované hashtagy",
"followers.hide_other_followers": "Tento uživatel se rozhodl nezveřejnit své další sledující",
"footer.about": "O aplikaci",
"footer.about_mastodon": "O Mastodonu",
"footer.about_server": "O {domain}",
@@ -456,6 +522,7 @@
"footer.source_code": "Zobrazit zdrojový kód",
"footer.status": "Stav",
"footer.terms_of_service": "Obchodní podmínky",
"form_field.optional": "(volitelné)",
"generic.saved": "Uloženo",
"getting_started.heading": "Začínáme",
"hashtag.admin_moderation": "Otevřít moderátorské rozhraní pro #{name}",
@@ -791,6 +858,7 @@
"privacy.private.short": "Sledující",
"privacy.public.long": "Kdokoliv na Mastodonu i mimo něj",
"privacy.public.short": "Veřejné",
"privacy.quote.anyone": "{visibility}, citování povoleno",
"privacy.quote.disabled": "{visibility}, citování je zakázáno",
"privacy.quote.limited": "{visibility}, citování je omezené",
"privacy.unlisted.additional": "Chová se stejně jako veřejný, až na to, že se příspěvek neobjeví v živých kanálech nebo hashtazích, v objevování nebo vyhledávání na Mastodonu, a to i když je účet nastaven tak, aby se zde všude tyto příspěvky zobrazovaly.",

View File

@@ -20,7 +20,7 @@
"account.add_or_remove_from_list": "Tilføj eller fjern fra lister",
"account.badges.admin": "Admin",
"account.badges.blocked": "Blokeret",
"account.badges.bot": "Automatisert",
"account.badges.bot": "Automatiseret",
"account.badges.domain_blocked": "Blokeret domæne",
"account.badges.group": "Gruppe",
"account.badges.muted": "Skjult",
@@ -263,6 +263,11 @@
"collections.create_collection": "Opret samling",
"collections.delete_collection": "Slet samling",
"collections.description_length_hint": "Begrænset til 100 tegn",
"collections.detail.accounts_heading": "Konti",
"collections.detail.curated_by_author": "Kurateret af {author}",
"collections.detail.curated_by_you": "Kurateret af dig",
"collections.detail.loading": "Indlæser samling…",
"collections.detail.share": "Del denne samling",
"collections.edit_details": "Rediger grundlæggende oplysninger",
"collections.edit_settings": "Rediger indstillinger",
"collections.error_loading_collections": "Der opstod en fejl under indlæsning af dine samlinger.",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "Sammlung erstellen",
"collections.delete_collection": "Sammlung löschen",
"collections.description_length_hint": "Maximal 100 Zeichen",
"collections.detail.accounts_heading": "Konten",
"collections.detail.curated_by_author": "Kuratiert von {author}",
"collections.detail.curated_by_you": "Kuratiert von dir",
"collections.detail.loading": "Sammlung wird geladen …",
"collections.detail.share": "Sammlung teilen",
"collections.edit_details": "Allgemeine Informationen bearbeiten",
"collections.edit_settings": "Einstellungen bearbeiten",
"collections.error_loading_collections": "Beim Laden deiner Sammlungen ist ein Fehler aufgetreten.",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "Δημιουργία συλλογής",
"collections.delete_collection": "Διαγραφή συλλογής",
"collections.description_length_hint": "Όριο 100 χαρακτήρων",
"collections.detail.accounts_heading": "Λογαριασμοί",
"collections.detail.curated_by_author": "Επιμέλεια από {author}",
"collections.detail.curated_by_you": "Επιμέλεια από εσάς",
"collections.detail.loading": "Γίνεται φόρτωση της συλλογής…",
"collections.detail.share": "Κοινοποιήστε αυτήν τη συλλογή",
"collections.edit_details": "Επεξεργασία βασικών στοιχείων",
"collections.edit_settings": "Επεξεργασία ρυθμίσεων",
"collections.error_loading_collections": "Παρουσιάστηκε σφάλμα κατά την προσπάθεια φόρτωσης των συλλογών σας.",

View File

@@ -263,8 +263,12 @@
"collections.create_collection": "Create collection",
"collections.delete_collection": "Delete collection",
"collections.description_length_hint": "100 characters limit",
"collections.edit_details": "Edit basic details",
"collections.edit_settings": "Edit settings",
"collections.detail.accounts_heading": "Accounts",
"collections.detail.curated_by_author": "Curated by {author}",
"collections.detail.curated_by_you": "Curated by you",
"collections.detail.loading": "Loading collection…",
"collections.detail.share": "Share this collection",
"collections.edit_details": "Edit details",
"collections.error_loading_collections": "There was an error when trying to load your collections.",
"collections.hints.accounts_counter": "{count} / {max} accounts",
"collections.hints.add_more_accounts": "Add at least {count, plural, one {# account} other {# accounts}} to continue",
@@ -274,7 +278,7 @@
"collections.manage_accounts_in_collection": "Manage accounts in this collection",
"collections.mark_as_sensitive": "Mark as sensitive",
"collections.mark_as_sensitive_hint": "Hides the collection's description and accounts behind a content warning. The collection name will still be visible.",
"collections.name_length_hint": "100 characters limit",
"collections.name_length_hint": "40 characters limit",
"collections.new_collection": "New collection",
"collections.no_collections_yet": "No collections yet.",
"collections.remove_account": "Remove this account",

View File

@@ -141,8 +141,25 @@
"account.unmute": "Unmute @{name}",
"account.unmute_notifications_short": "Unmute notifications",
"account.unmute_short": "Unmute",
"account_edit.bio.placeholder": "Add a short introduction to help others identify you.",
"account_edit.bio.title": "Bio",
"account_edit.bio_modal.add_title": "Add bio",
"account_edit.bio_modal.edit_title": "Edit bio",
"account_edit.char_counter": "{currentLength}/{maxLength} characters",
"account_edit.column_button": "Done",
"account_edit.column_title": "Edit Profile",
"account_edit.custom_fields.placeholder": "Add your pronouns, external links, or anything else youd like to share.",
"account_edit.custom_fields.title": "Custom fields",
"account_edit.display_name.placeholder": "Your display name is how your name appears on your profile and in timelines.",
"account_edit.display_name.title": "Display name",
"account_edit.featured_hashtags.placeholder": "Help others identify, and have quick access to, your favorite topics.",
"account_edit.featured_hashtags.title": "Featured hashtags",
"account_edit.name_modal.add_title": "Add display name",
"account_edit.name_modal.edit_title": "Edit display name",
"account_edit.profile_tab.subtitle": "Customize the tabs on your profile and what they display.",
"account_edit.profile_tab.title": "Profile tab settings",
"account_edit.save": "Save",
"account_edit.section_edit_button": "Edit",
"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",
@@ -257,7 +274,6 @@
"collections.create.accounts_subtitle": "Only accounts you follow who have opted into discovery can be added.",
"collections.create.accounts_title": "Who will you feature in this collection?",
"collections.create.basic_details_title": "Basic details",
"collections.create.settings_title": "Settings",
"collections.create.steps": "Step {step}/{total}",
"collections.create_a_collection_hint": "Create a collection to recommend or share your favourite accounts with others.",
"collections.create_collection": "Create collection",
@@ -268,23 +284,22 @@
"collections.detail.curated_by_you": "Curated by you",
"collections.detail.loading": "Loading collection…",
"collections.detail.share": "Share this collection",
"collections.edit_details": "Edit basic details",
"collections.edit_settings": "Edit settings",
"collections.edit_details": "Edit details",
"collections.error_loading_collections": "There was an error when trying to load your collections.",
"collections.hints.accounts_counter": "{count} / {max} accounts",
"collections.hints.add_more_accounts": "Add at least {count, plural, one {# account} other {# accounts}} to continue",
"collections.hints.can_not_remove_more_accounts": "Collections must contain at least {count, plural, one {# account} other {# accounts}}. Removing more accounts is not possible.",
"collections.last_updated_at": "Last updated: {date}",
"collections.manage_accounts": "Manage accounts",
"collections.manage_accounts_in_collection": "Manage accounts in this collection",
"collections.mark_as_sensitive": "Mark as sensitive",
"collections.mark_as_sensitive_hint": "Hides the collection's description and accounts behind a content warning. The collection name will still be visible.",
"collections.name_length_hint": "100 characters limit",
"collections.name_length_hint": "40 characters limit",
"collections.new_collection": "New collection",
"collections.no_collections_yet": "No collections yet.",
"collections.remove_account": "Remove this account",
"collections.search_accounts_label": "Search for accounts to add…",
"collections.search_accounts_max_reached": "You have added the maximum number of accounts",
"collections.sensitive": "Sensitive",
"collections.topic_hint": "Add a hashtag that helps others understand the main topic of this collection.",
"collections.view_collection": "View collection",
"collections.visibility_public": "Public",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.description_length_hint": "Límite de 100 caracteres",
"collections.detail.accounts_heading": "Cuentas",
"collections.detail.curated_by_author": "Curado por {author}",
"collections.detail.curated_by_you": "Curado por vos",
"collections.detail.loading": "Cargando colección…",
"collections.detail.share": "Compartir esta colección",
"collections.edit_details": "Editar detalles básicos",
"collections.edit_settings": "Editar configuración",
"collections.error_loading_collections": "Hubo un error al intentar cargar tus colecciones.",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.description_length_hint": "Limitado a 100 caracteres",
"collections.detail.accounts_heading": "Cuentas",
"collections.detail.curated_by_author": "Seleccionado por {author}",
"collections.detail.curated_by_you": "Seleccionado por ti",
"collections.detail.loading": "Cargando colección…",
"collections.detail.share": "Compartir esta colección",
"collections.edit_details": "Editar detalles básicos",
"collections.edit_settings": "Editar configuración",
"collections.error_loading_collections": "Se produjo un error al intentar cargar tus colecciones.",

View File

@@ -141,6 +141,8 @@
"account.unmute": "Dejar de silenciar a @{name}",
"account.unmute_notifications_short": "Dejar de silenciar notificaciones",
"account.unmute_short": "Dejar de silenciar",
"account_edit.column_button": "Hecho",
"account_edit.column_title": "Editar perfil",
"account_note.placeholder": "Haz clic para añadir nota",
"admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después del registro",
"admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después del registro",
@@ -261,6 +263,11 @@
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.description_length_hint": "Limitado a 100 caracteres",
"collections.detail.accounts_heading": "Cuentas",
"collections.detail.curated_by_author": "Seleccionado por {author}",
"collections.detail.curated_by_you": "Seleccionado por ti",
"collections.detail.loading": "Cargando colección…",
"collections.detail.share": "Compartir esta colección",
"collections.edit_details": "Editar datos básicos",
"collections.edit_settings": "Cambiar ajustes",
"collections.error_loading_collections": "Se ha producido un error al intentar cargar tus colecciones.",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "Luo kokoelma",
"collections.delete_collection": "Poista kokoelma",
"collections.description_length_hint": "100 merkin rajoitus",
"collections.detail.accounts_heading": "Tilit",
"collections.detail.curated_by_author": "Koonnut {author}",
"collections.detail.curated_by_you": "Itse kokoamasi",
"collections.detail.loading": "Ladataan kokoelmaa…",
"collections.detail.share": "Jaa tämä kokoelma",
"collections.edit_details": "Muokkaa perustietoja",
"collections.edit_settings": "Muokkaa asetuksia",
"collections.error_loading_collections": "Kokoelmien latauksessa tapahtui virhe.",

View File

@@ -141,6 +141,8 @@
"account.unmute": "Ne plus masquer @{name}",
"account.unmute_notifications_short": "Ne plus masquer les notifications",
"account.unmute_short": "Ne plus masquer",
"account_edit.column_button": "Terminé",
"account_edit.column_title": "Modifier le profil",
"account_note.placeholder": "Cliquez pour ajouter une note",
"admin.dashboard.daily_retention": "Taux de rétention des comptes par jour après inscription",
"admin.dashboard.monthly_retention": "Taux de rétention des comptes par mois après inscription",
@@ -261,6 +263,11 @@
"collections.create_collection": "Créer une collection",
"collections.delete_collection": "Supprimer la collection",
"collections.description_length_hint": "Maximum 100 caractères",
"collections.detail.accounts_heading": "Comptes",
"collections.detail.curated_by_author": "Organisée par {author}",
"collections.detail.curated_by_you": "Organisée par vous",
"collections.detail.loading": "Chargement de la collection…",
"collections.detail.share": "Partager la collection",
"collections.edit_details": "Modifier les informations générales",
"collections.edit_settings": "Modifier les paramètres",
"collections.error_loading_collections": "Une erreur s'est produite durant le chargement de vos collections.",

View File

@@ -141,6 +141,8 @@
"account.unmute": "Ne plus masquer @{name}",
"account.unmute_notifications_short": "Réactiver les notifications",
"account.unmute_short": "Ne plus masquer",
"account_edit.column_button": "Terminé",
"account_edit.column_title": "Modifier le profil",
"account_note.placeholder": "Cliquez pour ajouter une note",
"admin.dashboard.daily_retention": "Taux de rétention des utilisateur·rice·s par jour après inscription",
"admin.dashboard.monthly_retention": "Taux de rétention des utilisateur·rice·s par mois après inscription",
@@ -261,6 +263,11 @@
"collections.create_collection": "Créer une collection",
"collections.delete_collection": "Supprimer la collection",
"collections.description_length_hint": "Maximum 100 caractères",
"collections.detail.accounts_heading": "Comptes",
"collections.detail.curated_by_author": "Organisée par {author}",
"collections.detail.curated_by_you": "Organisée par vous",
"collections.detail.loading": "Chargement de la collection…",
"collections.detail.share": "Partager la collection",
"collections.edit_details": "Modifier les informations générales",
"collections.edit_settings": "Modifier les paramètres",
"collections.error_loading_collections": "Une erreur s'est produite durant le chargement de vos collections.",

View File

@@ -141,6 +141,8 @@
"account.unmute": "Díbhalbhaigh @{name}",
"account.unmute_notifications_short": "Díbhalbhaigh fógraí",
"account.unmute_short": "Díbhalbhaigh",
"account_edit.column_button": "Déanta",
"account_edit.column_title": "Cuir Próifíl in Eagar",
"account_note.placeholder": "Cliceáil chun nóta a chuir leis",
"admin.dashboard.daily_retention": "Ráta coinneála an úsáideora de réir an lae tar éis clárú",
"admin.dashboard.monthly_retention": "Ráta coinneála na n-úsáideoirí de réir na míosa tar éis dóibh clárú",
@@ -261,6 +263,11 @@
"collections.create_collection": "Cruthaigh bailiúchán",
"collections.delete_collection": "Scrios bailiúchán",
"collections.description_length_hint": "Teorainn 100 carachtar",
"collections.detail.accounts_heading": "Cuntais",
"collections.detail.curated_by_author": "Curtha i dtoll a chéile ag {author}",
"collections.detail.curated_by_you": "Curtha i dtoll a chéile agatsa",
"collections.detail.loading": "Ag lódáil an bhailiúcháin…",
"collections.detail.share": "Comhroinn an bailiúchán seo",
"collections.edit_details": "Cuir sonraí bunúsacha in eagar",
"collections.edit_settings": "Socruithe a chur in eagar",
"collections.error_loading_collections": "Tharla earráid agus iarracht á déanamh do bhailiúcháin a luchtú.",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "Crear colección",
"collections.delete_collection": "Eliminar colección",
"collections.description_length_hint": "Límite de 100 caracteres",
"collections.detail.accounts_heading": "Contas",
"collections.detail.curated_by_author": "Seleccionadas por {author}",
"collections.detail.curated_by_you": "Seleccionadas por ti",
"collections.detail.loading": "Cargando colección…",
"collections.detail.share": "Compartir esta colección",
"collections.edit_details": "Editar detalles básicos",
"collections.edit_settings": "Editar axustes",
"collections.error_loading_collections": "Houbo un erro ao intentar cargar as túas coleccións.",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "יצירת אוסף",
"collections.delete_collection": "מחיקת האוסף",
"collections.description_length_hint": "מגבלה של 100 תווים",
"collections.detail.accounts_heading": "חשבונות",
"collections.detail.curated_by_author": "נאצר על ידי {author}",
"collections.detail.curated_by_you": "נאצר על ידיך",
"collections.detail.loading": "טוען אוסף…",
"collections.detail.share": "שיתוף אוסף",
"collections.edit_details": "עריכת פרטים בסיסיים",
"collections.edit_settings": "עריכת הגדרות",
"collections.error_loading_collections": "חלה שגיאה בנסיון לטעון את אוספיך.",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "Búa til safn",
"collections.delete_collection": "Eyða safni",
"collections.description_length_hint": "100 stafa takmörk",
"collections.detail.accounts_heading": "Aðgangar",
"collections.detail.curated_by_author": "Safnað saman af {author}",
"collections.detail.curated_by_you": "Safnað saman af þér",
"collections.detail.loading": "Hleð inn safni…",
"collections.detail.share": "Deila þessu safni",
"collections.edit_details": "Breyta grunnupplýsingum",
"collections.edit_settings": "Breyta stillingum",
"collections.error_loading_collections": "Villa kom upp þegar reynt var að hlaða inn söfnunum þínum.",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "Crea la collezione",
"collections.delete_collection": "Cancella la collezione",
"collections.description_length_hint": "Limite di 100 caratteri",
"collections.detail.accounts_heading": "Account",
"collections.detail.curated_by_author": "Curata da {author}",
"collections.detail.curated_by_you": "Curata da te",
"collections.detail.loading": "Caricamento della collezione…",
"collections.detail.share": "Condividi questa collezione",
"collections.edit_details": "Modifica i dettagli di base",
"collections.edit_settings": "Modifica impostazioni",
"collections.error_loading_collections": "Si è verificato un errore durante il tentativo di caricare le tue collezioni.",

View File

@@ -16,7 +16,9 @@
"account.about": "정보",
"account.account_note_header": "개인 메모",
"account.activity": "활동",
"account.add_note": "개인 메모 추가",
"account.add_or_remove_from_list": "리스트에 추가 혹은 삭제",
"account.badges.admin": "관리자",
"account.badges.blocked": "차단함",
"account.badges.bot": "자동화됨",
"account.badges.domain_blocked": "차단한 도메인",
@@ -33,6 +35,7 @@
"account.direct": "@{name} 님에게 개인 멘션",
"account.disable_notifications": "@{name} 의 게시물 알림 끄기",
"account.domain_blocking": "도메인 차단함",
"account.edit_note": "개인 메모 편집",
"account.edit_profile": "프로필 편집",
"account.edit_profile_short": "수정",
"account.enable_notifications": "@{name} 의 게시물 알림 켜기",
@@ -45,6 +48,7 @@
"account.featured.hashtags": "해시태그",
"account.featured_tags.last_status_at": "{date}에 마지막으로 게시",
"account.featured_tags.last_status_never": "게시물 없음",
"account.filters.all": "모든 활동",
"account.filters.boosts_toggle": "부스트 보기",
"account.filters.replies_toggle": "답글 보기",
"account.follow": "팔로우",

View File

@@ -141,6 +141,8 @@
"account.unmute": "@{name} niet langer negeren",
"account.unmute_notifications_short": "Meldingen niet langer negeren",
"account.unmute_short": "Niet langer negeren",
"account_edit.column_button": "Klaar",
"account_edit.column_title": "Profiel bewerken",
"account_note.placeholder": "Klik om een opmerking toe te voegen",
"admin.dashboard.daily_retention": "Retentiegraad van gebruikers per dag, vanaf registratie",
"admin.dashboard.monthly_retention": "Retentiegraad van gebruikers per maand, vanaf registratie",
@@ -244,9 +246,12 @@
"closed_registrations_modal.preamble": "Mastodon is gedecentraliseerd. Op welke server je ook een account hebt, je kunt overal vandaan mensen op deze server volgen en er mee interactie hebben. Je kunt zelfs zelf een Mastodon-server hosten!",
"closed_registrations_modal.title": "Registreren op Mastodon",
"collections.account_count": "{count, plural, one {# account} other {# accounts}}",
"collections.accounts.empty_description": "Voeg tot {count} accounts toe die je volgt",
"collections.accounts.empty_title": "Deze verzameling is leeg",
"collections.collection_description": "Omschrijving",
"collections.collection_name": "Naam",
"collections.collection_topic": "Onderwerp",
"collections.confirm_account_removal": "Weet je zeker dat je dit account uit deze verzameling wilt verwijderen?",
"collections.content_warning": "Inhoudswaarschuwing",
"collections.continue": "Doorgaan",
"collections.create.accounts_subtitle": "Alleen accounts die je volgt en ontdekt willen worden, kunnen worden toegevoegd.",
@@ -258,9 +263,17 @@
"collections.create_collection": "Verzameling aanmaken",
"collections.delete_collection": "Verzameling verwijderen",
"collections.description_length_hint": "Maximaal 100 karakters",
"collections.detail.accounts_heading": "Accounts",
"collections.detail.curated_by_author": "Samengesteld door {author}",
"collections.detail.curated_by_you": "Samengesteld door jou",
"collections.detail.loading": "Verzameling laden…",
"collections.detail.share": "Deze verzameling delen",
"collections.edit_details": "Basisgegevens bewerken",
"collections.edit_settings": "Instellingen bewerken",
"collections.error_loading_collections": "Er is een fout opgetreden bij het laden van je verzamelingen.",
"collections.hints.accounts_counter": "{count} / {max} accounts",
"collections.hints.add_more_accounts": "Voeg ten minste {count, plural, one {# account} other {# accounts}} toe om door te gaan",
"collections.hints.can_not_remove_more_accounts": "Verzamelingen moeten ten minste {count, plural, one {# account} other {# accounts}} bevatten. Meer accounts verwijderen is niet mogelijk.",
"collections.last_updated_at": "Laatst bijgewerkt: {date}",
"collections.manage_accounts": "Accounts beheren",
"collections.manage_accounts_in_collection": "Accounts in deze verzameling beheren",
@@ -269,6 +282,9 @@
"collections.name_length_hint": "100 tekens limiet",
"collections.new_collection": "Nieuwe verzameling",
"collections.no_collections_yet": "Nog geen verzamelingen.",
"collections.remove_account": "Deze account verwijderen",
"collections.search_accounts_label": "Zoek naar accounts om toe te voegen…",
"collections.search_accounts_max_reached": "Je hebt het maximum aantal accounts toegevoegd",
"collections.topic_hint": "Voeg een hashtag toe die anderen helpt het hoofdonderwerp van deze collectie te begrijpen.",
"collections.view_collection": "Verzameling bekijken",
"collections.visibility_public": "Openbaar",

View File

@@ -260,6 +260,11 @@
"collections.create_collection": "Krijoni koleksion",
"collections.delete_collection": "Fshije koleksionin",
"collections.description_length_hint": "Kufi prej 100 shenjash",
"collections.detail.accounts_heading": "Llogari",
"collections.detail.curated_by_author": "Në kujdesin e {author}",
"collections.detail.curated_by_you": "Nën kujdesin tuaj",
"collections.detail.loading": "Po ngarkohet koleksion…",
"collections.detail.share": "Ndajeni këtë koleksion me të tjerë",
"collections.edit_details": "Përpunoni hollësi bazë",
"collections.edit_settings": "Përpunoni rregullime",
"collections.error_loading_collections": "Pati një gabim teksa provohej të ngarkoheshin koleksionet tuaj.",

View File

@@ -200,6 +200,7 @@
"collections.create_a_collection_hint": "Skapa en samling för att rekommendera eller dela dina favoritkonton med andra.",
"collections.create_collection": "Skapa samling",
"collections.delete_collection": "Radera samling",
"collections.detail.accounts_heading": "Konton",
"collections.error_loading_collections": "Det uppstod ett fel när dina samlingar skulle laddas.",
"collections.hints.accounts_counter": "{count} / {max} konton",
"collections.no_collections_yet": "Inga samlingar än.",

View File

@@ -141,6 +141,8 @@
"account.unmute": "@{name} adlı kişinin sesini aç",
"account.unmute_notifications_short": "Bildirimlerin sesini aç",
"account.unmute_short": "Susturmayı kaldır",
"account_edit.column_button": "Tamamlandı",
"account_edit.column_title": "Profili Düzenle",
"account_note.placeholder": "Not eklemek için tıklayın",
"admin.dashboard.daily_retention": "Kayıttan sonra günlük kullanıcı saklama oranı",
"admin.dashboard.monthly_retention": "Kayıttan sonra aylık kullanıcı saklama oranı",
@@ -261,6 +263,11 @@
"collections.create_collection": "Koleksiyon oluştur",
"collections.delete_collection": "Koleksiyonu sil",
"collections.description_length_hint": "100 karakterle sınırlı",
"collections.detail.accounts_heading": "Hesaplar",
"collections.detail.curated_by_author": "{author} tarafından derlenen",
"collections.detail.curated_by_you": "Sizin derledikleriniz",
"collections.detail.loading": "Koleksiyon yükleniyor…",
"collections.detail.share": "Bu koleksiyonu paylaş",
"collections.edit_details": "Temel bilgileri düzenle",
"collections.edit_settings": "Ayarları düzenle",
"collections.error_loading_collections": "Koleksiyonlarınızı yüklemeye çalışırken bir hata oluştu.",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "Tạo collection",
"collections.delete_collection": "Xóa collection",
"collections.description_length_hint": "Giới hạn 100 ký tự",
"collections.detail.accounts_heading": "Tài khoản",
"collections.detail.curated_by_author": "Tuyển chọn bởi {author}",
"collections.detail.curated_by_you": "Tuyển chọn bởi bạn",
"collections.detail.loading": "Đang tải collection…",
"collections.detail.share": "Chia sẻ collection này",
"collections.edit_details": "Sửa thông tin cơ bản",
"collections.edit_settings": "Sửa cài đặt",
"collections.error_loading_collections": "Đã xảy ra lỗi khi tải những collection của bạn.",

View File

@@ -141,6 +141,8 @@
"account.unmute": "不再隐藏 @{name}",
"account.unmute_notifications_short": "恢复通知",
"account.unmute_short": "取消隐藏",
"account_edit.column_button": "完成",
"account_edit.column_title": "修改个人资料",
"account_note.placeholder": "点击添加备注",
"admin.dashboard.daily_retention": "注册后用户留存率(按日计算)",
"admin.dashboard.monthly_retention": "注册后用户留存率(按月计算)",
@@ -261,6 +263,11 @@
"collections.create_collection": "创建收藏列表",
"collections.delete_collection": "删除收藏列表",
"collections.description_length_hint": "100字限制",
"collections.detail.accounts_heading": "账号",
"collections.detail.curated_by_author": "由 {author} 精心挑选",
"collections.detail.curated_by_you": "由你精心挑选",
"collections.detail.loading": "正在加载收藏列表…",
"collections.detail.share": "分享此收藏列表",
"collections.edit_details": "编辑基本信息",
"collections.edit_settings": "编辑设置",
"collections.error_loading_collections": "加载你的收藏列表时发生错误。",

View File

@@ -263,6 +263,11 @@
"collections.create_collection": "建立收藏名單",
"collections.delete_collection": "刪除收藏名單",
"collections.description_length_hint": "100 字限制",
"collections.detail.accounts_heading": "帳號",
"collections.detail.curated_by_author": "由 {author} 精選",
"collections.detail.curated_by_you": "由您精選",
"collections.detail.loading": "讀取收藏名單中...",
"collections.detail.share": "分享此收藏名單",
"collections.edit_details": "編輯基本資料",
"collections.edit_settings": "編輯設定",
"collections.error_loading_collections": "讀取您的收藏名單時發生錯誤。",

View File

@@ -69,6 +69,11 @@ export const accountDefaultValues: AccountShape = {
display_name: '',
display_name_html: '',
emojis: ImmutableList<CustomEmoji>(),
feature_approval: {
automatic: [],
manual: [],
current_user: 'missing',
},
fields: ImmutableList<AccountField>(),
group: false,
header: '',

View File

@@ -15,6 +15,7 @@ import type {
ApiCreateCollectionPayload,
ApiUpdateCollectionPayload,
} from '@/mastodon/api_types/collections';
import { me } from '@/mastodon/initial_state';
import {
createAppSelector,
createDataLoadingThunk,
@@ -111,6 +112,14 @@ const collectionSlice = createSlice({
const { collectionId } = action.meta.arg;
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete state.collections[collectionId];
if (me) {
let accountCollectionIds = state.accountCollections[me]?.collectionIds;
if (accountCollectionIds) {
accountCollectionIds = accountCollectionIds.filter(
(id) => id !== collectionId,
);
}
}
});
/**

View File

@@ -31,6 +31,11 @@ export const accountFactory: FactoryFunction<ApiAccountJSON> = ({
created_at: '2023-01-01T00:00:00.000Z',
discoverable: true,
emojis: [],
feature_approval: {
automatic: [],
manual: [],
current_user: 'missing',
},
fields: [],
followers_count: 0,
following_count: 0,

View File

@@ -26,6 +26,7 @@ class Collection < ApplicationRecord
belongs_to :tag, optional: true
has_many :collection_items, dependent: :delete_all
has_many :accepted_collection_items, -> { accepted }, class_name: 'CollectionItem', inverse_of: :collection # rubocop:disable Rails/HasManyOrHasOneDependent
has_many :collection_reports, dependent: :delete_all
validates :name, presence: true

View File

@@ -1,6 +1,7 @@
.batch-table__row{ class: [!account.unavailable? && account.user_pending? && 'batch-table__row--attention', (account.unavailable? || account.user_unconfirmed?) && 'batch-table__row--muted'] }
%label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
= f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
- if local_assigns[:f].present?
= f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
.batch-table__row__content.batch-table__row__content--unpadded
%table.accounts-table
%tbody

View File

@@ -0,0 +1,21 @@
- content_for :page_title do
= t('admin.collections.collection_title', name: @account.pretty_acct)
- content_for :heading_actions do
= link_to t('admin.collections.open'), account_collection_path(@account, @collection), class: 'button', target: '_blank', rel: 'noopener'
%h3= t('admin.collections.contents')
= render 'admin/shared/collection', collection: @collection
%hr.spacer/
%h3= t('admin.collections.accounts')
.batch-table
.batch-table__toolbar
.batch-table__body
- if @collection.accepted_collection_items.none?
= nothing_here 'nothing-here--under-tabs'
- else
= render partial: 'admin/accounts/account', collection: @collection.accepted_collection_items.map(&:account)

View File

@@ -67,6 +67,11 @@
= material_symbol('photo_camera')
= report.media_attachments_count
- if Mastodon::Feature.collections_enabled?
%span.report-card__summary__item__content__icon{ title: t('admin.accounts.collections') }
= material_symbol('groups-fill')
= report.collections.size
- if report.forwarded?
·
= t('admin.reports.forwarded_to', domain: target_account.domain)

View File

@@ -32,7 +32,7 @@
%hr.spacer/
%h3
= t 'admin.reports.statuses'
= t 'admin.reports.reported_content'
%small.section-skip-link
= link_to '#actions' do
= material_symbol 'keyboard_double_arrow_down'
@@ -41,6 +41,9 @@
%p
= t 'admin.reports.statuses_description_html'
%h4
= t 'admin.reports.statuses'
= form_with model: @form, url: batch_admin_account_statuses_path(@report.target_account_id, report_id: @report.id) do |f|
.batch-table
.batch-table__toolbar
@@ -58,6 +61,22 @@
- else
= render partial: 'admin/shared/status_batch_row', collection: @statuses, as: :status, locals: { f: f }
- if Mastodon::Feature.collections_enabled?
%h4
= t 'admin.reports.collections'
%form
.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
.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
- if @report.unresolved?
%hr.spacer/

View File

@@ -0,0 +1,22 @@
.status__card
- if collection.tag.present?
.status__prepend
= link_to collection.tag.formatted_name, admin_tag_path(collection.tag_id)
.status__content
%h6= collection.name
%p= collection.description
.detailed-status__meta
= conditional_link_to can?(:show, collection), admin_account_collection_path(collection.account.id, collection), class: 'detailed-status__datetime' do
%time.formatted{ datetime: collection.created_at.iso8601, title: l(collection.created_at) }><= l(collection.created_at)
- if collection.sensitive?
&nbsp;·
= material_symbol('visibility_off')
= t('stream_entries.sensitive_content')
&nbsp;·
= t('admin.collections.number_of_accounts', count: collection.accepted_collection_items.size)
&nbsp;·
= link_to account_collection_path(collection.account, collection), class: 'detailed-status__link', target: 'blank', rel: 'noopener' do
= t('admin.collections.view_publicly')

View File

@@ -0,0 +1,5 @@
.batch-table__row
%label.batch-table__row__select.batch-checkbox
-# = 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

@@ -585,7 +585,6 @@ an:
resolved_msg: La denuncia s'ha resuelto correctament!
skip_to_actions: Ir dreitament a las accions
status: Estau
statuses: Conteniu denunciau
statuses_description_html: Lo conteniu ofensivo se citará en a comunicación con a cuenta denunciada
target_origin: Orichen d'a cuenta denunciada
title: Reportes

View File

@@ -752,7 +752,6 @@ ar:
resolved_msg: تمت معالجة الشكوى بنجاح!
skip_to_actions: تخطي إلى الإجراءات
status: الحالة
statuses: المحتوى المبلغ عنه
statuses_description_html: سيشار إلى المحتوى المخالف في الاتصال بالحساب المبلغ عنه
summary:
action_preambles:

View File

@@ -261,7 +261,6 @@ ast:
resolved_msg: "¡L'informe resolvióse correutamente!"
skip_to_actions: Saltar a les aiciones
status: Estáu
statuses: Conteníu del que s'informó
statuses_description_html: El conteníu ofensivu cítase na comunicación cola cuenta de la que s'informó
target_origin: Orixe de la cuenta de la que s'infomó
title: Informes

View File

@@ -739,7 +739,6 @@ be:
resolved_msg: Скарга была паспяхова вырашана!
skip_to_actions: Прапусціць дзеянні
status: Стан
statuses: Змесціва, на якое паскардзіліся
statuses_description_html: Крыўднае змесціва будзе згадвацца ў зносінах з уліковым запісам, на які пададзена скарга
summary:
action_preambles:

View File

@@ -696,7 +696,6 @@ bg:
resolved_msg: Успешно разрешен доклад!
skip_to_actions: Прескок към действия
status: Състояние
statuses: Докладвано съдържание
statuses_description_html: Обидно съдържание ще се цитира в общуването с докладвания акаунт
summary:
action_preambles:

View File

@@ -707,7 +707,6 @@ ca:
resolved_msg: Informe resolt correctament!
skip_to_actions: Salta a les accions
status: Estat
statuses: Contingut reportat
statuses_description_html: El contingut ofensiu se citarà en comunicació amb el compte denunciat
summary:
action_preambles:

View File

@@ -482,7 +482,6 @@ ckb:
resolved_msg: گوزارشتکردن بە سەرکەوتوویی چارەسەر کرا!
skip_to_actions: باز بدە بۆ کردارەکان
status: دۆخ
statuses: ناوەڕۆکی ڕاپۆرتکراو
statuses_description_html: ناوەڕۆکی توڕەکەر لە پەیوەندی لەگەڵ ئەکاونتی ڕاپۆرتکراودا ئاماژەی پێدەکرێت
target_origin: سەرچاوەی ئەکاونتی ڕاپۆرتکراو
title: گوزارشتکرا

View File

@@ -14,6 +14,11 @@ cs:
many: Sledujících
one: Sledující
other: Sledujících
following:
few: Sleduje
many: Sleduje
one: Sleduje
other: Sleduje
instance_actor_flash: Tento účet je virtuální aktér, který představuje server samotný, nikoliv jednotlivého uživatele. Používá se pro účely federace a neměl by být pozastaven.
last_active: naposledy aktivní
link_verified_on: Vlastnictví tohoto odkazu bylo zkontrolováno %{date}
@@ -734,7 +739,6 @@ cs:
resolved_msg: Hlášení úspěšně vyřešeno!
skip_to_actions: Přeskočit k akcím
status: Stav
statuses: Nahlášený obsah
statuses_description_html: Obsah porušující pravidla bude uveden v komunikaci s nahlášeným účtem
summary:
action_preambles:
@@ -827,6 +831,7 @@ cs:
view_devops_description: Umožňuje uživatelům přístup k ovládacím panelům Sidekiq a pgHero
view_feeds: Zobrazit živé a tematické kanály
view_feeds_description: Umožňuje uživatelům přístup k živým a tematickým kanálům bez ohledu na nastavení serveru
requires_2fa: Vyžaduje dvoufázové ověření
title: Role
rules:
add_new: Přidat pravidlo
@@ -2103,6 +2108,8 @@ cs:
past_preamble_html: Od vaší poslední návštěvy jsme změnili podmínky služby. Doporučujeme vám zkontrolovat aktualizované podmínky.
review_link: Zkontrolovat podmínky užívání služby
title: Podmínky služby %{domain} se mění
themes:
default: Mastodon
time:
formats:
default: "%d. %b %Y, %H:%M"
@@ -2127,6 +2134,8 @@ cs:
recovery_codes: Záložní kódy pro obnovu
recovery_codes_regenerated: Záložní kódy byly úspěšně znovu vygenerovány
recovery_instructions_html: Ztratíte-li někdy přístup ke svému telefonu, můžete k získání přístupu k účtu použít jeden ze záložních kódů. <strong>Uchovejte tyto kódy v bezpečí</strong>. Můžete si je například vytisknout a uložit je mezi jiné důležité dokumenty.
resume_app_authorization: Pokračovat v autorizaci aplikace
role_requirement: "%{domain} vyžaduje nastavení dvoufázového ověření, než budete moci použít Mastodon."
webauthn: Bezpečnostní klíče
user_mailer:
announcement_published:

View File

@@ -765,7 +765,6 @@ cy:
resolved_msg: Llwyddwyd i ddatrys yr adroddiad!
skip_to_actions: Mynd i gamau gweithredu
status: Statws
statuses: Cynnwys wedi'i adrodd
statuses_description_html: Bydd cynnwys tramgwyddus yn cael ei ddyfynnu wrth gyfathrebu â'r cyfrif a adroddwyd
summary:
action_preambles:

View File

@@ -56,6 +56,7 @@ da:
label: Ændr rolle
no_role: Ingen rolle
title: Ændr rolle for %{username}
collections: Samlinger
confirm: Bekræft
confirmed: Bekræftet
confirming: Bekræfter
@@ -340,6 +341,15 @@ da:
unpublish: Afpublicér
unpublished_msg: Bekendtgørelsen er afpubliceret!
updated_msg: Bekendtgørelsen er opdateret!
collections:
accounts: Konti
collection_title: Indsamling af %{name}
contents: Indhold
number_of_accounts:
one: 1 konto
other: "%{count} konti"
open: Åben
view_publicly: Vis offentligt
critical_update_pending: Kritisk opdatering afventer
custom_emojis:
assign_category: Tildel kategori
@@ -679,6 +689,7 @@ da:
cancel: Afbryd
category: Kategori
category_description_html: Årsagen til anmeldelsen af denne konto og/eller indhold refereres i kommunikationen med den anmeldte konto
collections: Samlinger
comment:
none: Ingen
comment_description_html: 'For at give mere information, skrev %{name}:'
@@ -708,12 +719,13 @@ da:
report: 'Anmeldelse #%{id}'
reported_account: Anmeldt konto
reported_by: Anmeldt af
reported_content: Anmeldt indhold
reported_with_application: Rapporteret via applikation
resolved: Løst
resolved_msg: Anmeldelse løst!
skip_to_actions: Overspring til foranstaltninger
status: Status
statuses: Anmeld indhold
statuses: Indlæg
statuses_description_html: Krænkende indhold citeres i kommunikationen med den anmeldte konto
summary:
action_preambles:
@@ -1499,7 +1511,7 @@ da:
copy: Kopier
delete: Slet
deselect: Afmarkér alle
none: Intet
none: Ingen
order_by: Sortér efter
save_changes: Gem ændringer
select_all_matching_items:
@@ -1918,8 +1930,8 @@ da:
account_suspension: Kontosuspendering (%{target_name})
domain_block: Serversuspendering (%{target_name})
user_domain_block: "%{target_name} blev blokeret"
lost_followers: Tabte følgere
lost_follows: Mistet følger
lost_followers: Mistet følgere
lost_follows: Mistet fulgte
preamble: Du kan miste fulgte og følgere, når du blokerer et domæne, eller når dine moderatorer beslutter at suspendere en fjernserver. Når det sker, kan du downloade lister over afbrudte forhold til inspektion og eventuelt import til en anden server.
purged: Oplysninger om denne server er blevet renset af serveradministratoreren.
type: Begivenhed

View File

@@ -713,7 +713,6 @@ de:
resolved_msg: Meldung erfolgreich geklärt!
skip_to_actions: Zur Maßnahme springen
status: Status
statuses: Gemeldeter Inhalt
statuses_description_html: Beanstandete Inhalte werden in der Kommunikation mit dem gemeldeten Konto erwähnt
summary:
action_preambles:

View File

@@ -83,6 +83,10 @@ cs:
access_denied: Vlastník zdroje či autorizační server žádost zamítl.
credential_flow_not_configured: Proud Resource Owner Password Credentials selhal, protože Doorkeeper.configure.resource_owner_from_credentials nebylo nakonfigurováno.
invalid_client: Ověření klienta selhalo kvůli neznámému klientovi, chybějící klientské autentizaci či nepodporované autentizační metodě.
invalid_code_challenge_method:
one: code_challenge_method musí být %{challenge_methods}.
other: code_challenge_method musí být jedna z %{challenge_methods}.
zero: Autorizační server nepodporuje PKCE, protože neexistují žádné akceptované hodnoty kode_challenge_method.
invalid_grant: Poskytnuté oprávnění je neplatné, vypršela jeho platnost, bylo odvoláno, neshoduje se s URI přesměrování použitým v požadavku o autorizaci, nebo bylo uděleno jinému klientu.
invalid_redirect_uri: Zahrnutá přesměrovací URI není platná.
invalid_request:

View File

@@ -83,6 +83,10 @@ es-AR:
access_denied: El propietario del recurso o servidor de autorización denegó la petición.
credential_flow_not_configured: Las credenciales de contraseña del propietario del recurso fallaron debido a que "Doorkeeper.configure.resource_owner_from_credentials" está sin configurar.
invalid_client: La autenticación del cliente falló debido a que es un cliente desconocido, o no está incluída la autenticación del cliente, o el método de autenticación no está soportado.
invalid_code_challenge_method:
one: El code_challenge_method debe ser %{challenge_methods}.
other: El code_challenge_method debe ser uno de %{challenge_methods}.
zero: El servidor de autorización no soporta PKCE, ya que no hay valores aceptados de code_challenge_method.
invalid_grant: La concesión de autorización ofrecida no es válida, venció, se revocó, no coincide con la dirección web de redireccionamiento usada en la petición de autorización, o fue emitida para otro cliente.
invalid_redirect_uri: La dirección web de redireccionamiento incluida no es válida.
invalid_request:

View File

@@ -83,6 +83,10 @@ es:
access_denied: El propietario del recurso o servidor de autorización denegó la petición.
credential_flow_not_configured: Las credenciales de contraseña del propietario del recurso falló debido a que Doorkeeper.configure.resource_owner_from_credentials está sin configurar.
invalid_client: La autentificación del cliente falló debido o a que es un cliente desconocido o no está incluída la autentificación del cliente o el método de autentificación no está confirmado.
invalid_code_challenge_method:
one: El code_challenge_method debe ser %{challenge_methods}.
other: El code_challenge_method debe ser uno de %{challenge_methods}.
zero: El servidor de autorización no soporta PKCE, ya que no hay valores aceptados de code_challenge_method.
invalid_grant: La concesión de autorización ofrecida es inválida, venció, se revocó, no coincide con la URI de redirección utilizada en la petición de autorización, o fue emitida para otro cliente.
invalid_redirect_uri: La URI de redirección incluida no es válida.
invalid_request:

View File

@@ -83,8 +83,6 @@ fr-CA:
access_denied: Le/la propriétaire de la ressource ou le serveur dautorisation a refusé la requête.
credential_flow_not_configured: Le flux des identifiants du mot de passe du/de la propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_from_credentials nest pas configuré.
invalid_client: Lauthentification du client a échoué à cause dun client inconnu, daucune authentification de client incluse ou dune méthode dauthentification non prise en charge.
invalid_code_challenge_method:
one: The code_challenge_method must be %{challenge_methods}.
invalid_grant: Lautorisation accordée est invalide, expirée, révoquée, ne concorde pas avec lURI de redirection utilisée dans la requête dautorisation, ou a été délivrée à un autre client.
invalid_redirect_uri: LURI de redirection nest pas valide.
invalid_request:

View File

@@ -83,8 +83,6 @@ fr:
access_denied: Le propriétaire de la ressource ou le serveur dautorisation a refusé la requête.
credential_flow_not_configured: Le flux des identifiants du mot de passe du propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_from_credentials nest pas configuré.
invalid_client: Lauthentification du client a échoué à cause dun client inconnu, daucune authentification de client incluse ou dune méthode dauthentification non prise en charge.
invalid_code_challenge_method:
one: The code_challenge_method must be %{challenge_methods}.
invalid_grant: Lautorisation accordée est invalide, expirée, annulée, ne concorde pas avec lURL de redirection utilisée dans la requête dautorisation, ou a été délivrée à un autre client.
invalid_redirect_uri: LURL de redirection nest pas valide.
invalid_request:

View File

@@ -83,6 +83,10 @@ nl:
access_denied: De resource-eigenaar of autorisatie-server weigerde het verzoek.
credential_flow_not_configured: De wachtwoordgegevens-flow van de resource-eigenaar is mislukt omdat Doorkeeper.configure.resource_owner_from_credentials niet is ingesteld.
invalid_client: Clientverificatie is mislukt door een onbekende client, ontbrekende client-authenticatie of een niet ondersteunde authenticatie-methode.
invalid_code_challenge_method:
one: De code_challenge_method moet %{challenge_methods} zijn.
other: De code_challenge_method moet een van %{challenge_methods} zijn.
zero: De autorisatieserver ondersteunt PKCE niet, aangezien er geen geaccepteerde code_challenge_method waarden zijn.
invalid_grant: De verstrekte autorisatie is ongeldig, verlopen, ingetrokken, komt niet overeen met de redirect-URI die is opgegeven of werd uitgegeven aan een andere client.
invalid_redirect_uri: De opgegeven redirect-URI is ongeldig.
invalid_request:

View File

@@ -83,6 +83,10 @@ tr:
access_denied: Kaynak sahibi veya yetkilendirme sunucusu isteği reddetti.
credential_flow_not_configured: Kaynak Sahibi Parolası Kimlik Bilgileri akışı Doorkeeper.configure.resource_owner_from_credentials 'ın yapılandırılmamış olması nedeniyle başarısız oldu.
invalid_client: İstemcinin kimlik doğrulaması bilinmeyen istemci, istemci kimlik doğrulamasının dahil olmaması veya desteklenmeyen kimlik doğrulama yöntemi nedeniyle başarısız oldu.
invalid_code_challenge_method:
one: code_challenge_method %{challenge_methods} olmalıdır.
other: code_challenge_method %{challenge_methods} seçeneklerinden biri olmalıdır.
zero: Yetkilendirme sunucusu kabul edilen code_challenge_method değerleri olmadığı için PKCE'yi desteklemiyor.
invalid_grant: Sağlanan yetkilendirme izni geçersiz, süresi dolmuş, iptal edilmiş, yetkilendirme isteğinde kullanılan yönlendirme URL'siyle eşleşmiyor veya başka bir istemciye verilmiş.
invalid_redirect_uri: Dahil edilmiş yönlendirme uri'si geçersiz.
invalid_request:

View File

@@ -713,7 +713,6 @@ el:
resolved_msg: Η αναφορά επιλύθηκε επιτυχώς!
skip_to_actions: Μετάβαση στις ενέργειες
status: Κατάσταση
statuses: Αναφερόμενο περιεχόμενο
statuses_description_html: Το προσβλητικό περιεχόμενο θα εσωκλείεται στην επικοινωνία με τον αναφερόμενο λογαριασμό
summary:
action_preambles:

View File

@@ -713,7 +713,6 @@ en-GB:
resolved_msg: Report successfully resolved!
skip_to_actions: Skip to actions
status: Status
statuses: Reported content
statuses_description_html: Offending content will be cited in communication with the reported account
summary:
action_preambles:

View File

@@ -56,6 +56,7 @@ en:
label: Change role
no_role: No role
title: Change role for %{username}
collections: Collections
confirm: Confirm
confirmed: Confirmed
confirming: Confirming
@@ -340,6 +341,15 @@ en:
unpublish: Unpublish
unpublished_msg: Announcement successfully unpublished!
updated_msg: Announcement successfully updated!
collections:
accounts: Accounts
collection_title: Collection by %{name}
contents: Contents
number_of_accounts:
one: 1 account
other: "%{count} accounts"
open: Open
view_publicly: View publicly
critical_update_pending: Critical update pending
custom_emojis:
assign_category: Assign category
@@ -679,6 +689,7 @@ en:
cancel: Cancel
category: Category
category_description_html: The reason this account and/or content was reported will be cited in communication with the reported account
collections: Collections
comment:
none: None
comment_description_html: 'To provide more information, %{name} wrote:'
@@ -708,12 +719,13 @@ en:
report: 'Report #%{id}'
reported_account: Reported account
reported_by: Reported by
reported_content: Reported content
reported_with_application: Reported with application
resolved: Resolved
resolved_msg: Report successfully resolved!
skip_to_actions: Skip to actions
status: Status
statuses: Reported content
statuses: Posts
statuses_description_html: Offending content will be cited in communication with the reported account
summary:
action_preambles:

View File

@@ -704,7 +704,6 @@ eo:
resolved_msg: Signalo sukcese solvita!
skip_to_actions: Salti al agoj
status: Afiŝo
statuses: Raportita enhavo
statuses_description_html: Sentema enhavo referencitas kun la raportita konto
summary:
action_preambles:

View File

@@ -713,7 +713,6 @@ es-AR:
resolved_msg: "¡Denuncia exitosamente resuelta!"
skip_to_actions: Ir directamente a las acciones
status: Estado
statuses: Contenido denunciado
statuses_description_html: El contenido ofensivo se citará en la comunicación con la cuenta denunciada
summary:
action_preambles:

View File

@@ -713,7 +713,6 @@ es-MX:
resolved_msg: "¡La denuncia se ha resuelto correctamente!"
skip_to_actions: Ir directamente a las acciones
status: Estado
statuses: Contenido reportado
statuses_description_html: El contenido ofensivo se citará en comunicación con la cuenta reportada
summary:
action_preambles:

View File

@@ -713,7 +713,6 @@ es:
resolved_msg: "¡La denuncia se ha resuelto correctamente!"
skip_to_actions: Ir directamente a las acciones
status: Estado
statuses: Contenido reportado
statuses_description_html: El contenido ofensivo se citará en la comunicación con la cuenta reportada
summary:
action_preambles:

View File

@@ -713,7 +713,6 @@ et:
resolved_msg: Teavituse lahendamine õnnestus!
skip_to_actions: Otsuste juurde
status: Olek
statuses: Raporteeritud sisu
statuses_description_html: Sobimatu sisu kaasatakse suhtlusse raporteeritud kontoga
summary:
action_preambles:

View File

@@ -701,7 +701,6 @@ eu:
resolved_msg: Salaketa ongi konpondu da!
skip_to_actions: Salto ekintzetara
status: Mezua
statuses: Salatutako edukia
statuses_description_html: Salatutako edukia salatutako kontuarekiko komunikazioan aipatuko da
summary:
action_preambles:

View File

@@ -710,7 +710,6 @@ fa:
resolved_msg: گزارش با موفقیت حل شد!
skip_to_actions: پرش به کنش‌ها
status: نوشته
statuses: محتوای گزارش شده
statuses_description_html: محتوای توهین آمیز در ارتباط با حساب گزارش شده ذکر می شود
summary:
action_preambles:

View File

@@ -713,7 +713,6 @@ fi:
resolved_msg: Raportin ratkaisu onnistui!
skip_to_actions: Siirry toimiin
status: Tila
statuses: Raportoitu sisältö
statuses_description_html: Loukkaava sisältö mainitaan raportoidun tilin yhteydessä
summary:
action_preambles:

View File

@@ -710,7 +710,6 @@ fo:
resolved_msg: Melding avgreidd!
skip_to_actions: Leyp til atgerðir
status: Støða
statuses: Meldað innihald
statuses_description_html: Tilfarið, sum brotið viðvíkur, fer at vera siterað í samskifti við meldaðu kontuni
summary:
action_preambles:

View File

@@ -716,7 +716,6 @@ fr-CA:
resolved_msg: Signalement résolu avec succès!
skip_to_actions: Passer aux actions
status: Statut
statuses: Contenu signalé
statuses_description_html: Le contenu offensant sera cité dans la communication avec le compte signalé
summary:
action_preambles:

View File

@@ -716,7 +716,6 @@ fr:
resolved_msg: Signalement résolu avec succès!
skip_to_actions: Passer aux actions
status: Statut
statuses: Contenu signalé
statuses_description_html: Le contenu offensant sera cité dans la communication avec le compte signalé
summary:
action_preambles:

View File

@@ -708,7 +708,6 @@ fy:
resolved_msg: Rapportaazje oplosse slagge!
skip_to_actions: Gean daliks nei de maatregels
status: Steat
statuses: Rapportearre ynhâld
statuses_description_html: De problematyske ynhâld wurdt oan it rapportearre account meidield
summary:
action_preambles:

View File

@@ -752,7 +752,6 @@ ga:
resolved_msg: D'éirigh le réiteach an tuairisc!
skip_to_actions: Léim ar ghníomhartha
status: Stádas
statuses: Ábhar tuairiscithe
statuses_description_html: Luafar ábhar ciontach i gcumarsáid leis an gcuntas tuairiscithe
summary:
action_preambles:

Some files were not shown because too many files have changed in this diff Show More