Merge commit 'a13756148d353c7479f68e65a210f6d88d26c785' into glitch-soc/merge-upstream

Conflicts:
- `app/views/layouts/embedded.html.haml`:
  Upstream made a change to javascript tags next to lines changed in glitch-soc
  because of the theming system.
  Added the javascript entrypoint upstream added.
- `app/views/layouts/error.html.haml`:
  Upstream made a change to javascript tags next to lines changed in glitch-soc
  because of the theming system.
  Added the javascript entrypoint upstream added.
This commit is contained in:
Claire
2025-05-25 15:11:58 +02:00
108 changed files with 1297 additions and 355 deletions

View File

@@ -28,6 +28,9 @@
// react-router: Requires manual upgrade
'history',
'react-router-dom',
// react-spring: Requires manual upgrade when upgrading react
'@react-spring/web',
],
matchUpdateTypes: ['major'],
dependencyDashboardApproval: true,

2
.nvmrc
View File

@@ -1 +1 @@
22.15
22.16

View File

@@ -70,13 +70,6 @@ Style/OptionalBooleanParameter:
- 'app/workers/domain_block_worker.rb'
- 'app/workers/unfollow_follow_worker.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: short, verbose
Style/PreferredHashMethods:
Exclude:
- 'config/initializers/paperclip.rb'
# This cop supports safe autocorrection (--autocorrect).
Style/RedundantConstantBase:
Exclude:

View File

@@ -62,6 +62,11 @@ Bug reports and feature suggestions must use descriptive and concise titles and
be submitted to [GitHub Issues]. Please use the search function to make sure
there are not duplicate bug reports or feature requests.
## Security Issues
If you believe you have identified a security issue in Mastodon or our own apps,
check [SECURITY].
## Translations
Translations are community contributed via [Crowdin]. They are periodically
@@ -124,3 +129,4 @@ and API docs. Improvements are made via PRs to the [documentation repository].
[GitHub Issues]: https://github.com/mastodon/mastodon/issues
[keepachangelog]: https://keepachangelog.com/en/1.0.0/
[Mastodon documentation]: https://docs.joinmastodon.org
[SECURITY]: SECURITY.md

View File

@@ -62,7 +62,7 @@ gem 'inline_svg'
gem 'irb', '~> 1.8'
gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0'
gem 'linzer', '~> 0.6.1'
gem 'linzer', '~> 0.7.2'
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'mime-types', '~> 3.7.0', require: 'mime/types/columnar'
gem 'mutex_m'

View File

@@ -148,6 +148,7 @@ GEM
case_transform (0.2)
activesupport
cbor (0.5.9.8)
cgi (0.4.2)
charlock_holmes (0.7.9)
chewy (7.6.0)
activesupport (>= 5.2)
@@ -262,6 +263,7 @@ GEM
fog-core (~> 2.1)
fog-json (>= 1.0)
formatador (1.1.0)
forwardable (1.3.3)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
@@ -396,7 +398,11 @@ GEM
rexml
link_header (0.0.8)
lint_roller (1.1.0)
linzer (0.6.5)
linzer (0.7.2)
cgi (~> 0.4.2)
forwardable (~> 1.3, >= 1.3.3)
logger (~> 1.7, >= 1.7.0)
net-http (~> 0.6.0)
openssl (~> 3.0, >= 3.0.0)
rack (>= 2.2, < 4.0)
starry (~> 0.2)
@@ -628,7 +634,7 @@ GEM
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.8.1)
rack (2.2.15)
rack (2.2.16)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
rack-cors (2.0.2)
@@ -742,7 +748,7 @@ GEM
rspec-mocks (~> 3.0)
sidekiq (>= 5, < 9)
rspec-support (3.13.3)
rubocop (1.75.6)
rubocop (1.75.7)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
@@ -779,7 +785,8 @@ GEM
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec (~> 3.5)
ruby-prof (1.7.1)
ruby-prof (1.7.2)
base64
ruby-progressbar (1.13.0)
ruby-saml (1.18.0)
nokogiri (>= 1.13.10)
@@ -993,7 +1000,7 @@ DEPENDENCIES
letter_opener (~> 1.8)
letter_opener_web (~> 3.0)
link_header (~> 0.0)
linzer (~> 0.6.1)
linzer (~> 0.7.2)
lograge (~> 0.12)
mail (~> 2.8)
mario-redis-lock (~> 1.2)

View File

@@ -93,12 +93,12 @@ accepted into Mastodon, you can request to be paid through our [OpenCollective].
## License
Copyright (c) 2016-2024 Eugen Rochko (+ [`mastodon authors`](AUTHORS.md))
Copyright (c) 2016-2025 Eugen Rochko (+ [`mastodon authors`](AUTHORS.md))
Licensed under GNU Affero General Public License as stated in the [LICENSE](LICENSE):
```
Copyright (c) 2016-2024 Eugen Rochko & other Mastodon contributors
Copyright (c) 2016-2025 Eugen Rochko & other Mastodon contributors
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free

View File

@@ -42,37 +42,22 @@ class Api::Fasp::BaseController < ApplicationController
end
def validate_signature!
signature_input = request.headers['signature-input']&.encode('UTF-8')
raise Error, 'signature-input is missing' if signature_input.blank?
raise Error, 'signature-input is missing' if request.headers['signature-input'].blank?
provider = nil
Linzer.verify!(request.rack_request, no_older_than: 5.minutes) do |keyid|
provider = Fasp::Provider.find(keyid)
Linzer.new_ed25519_public_key(provider.provider_public_key_pem, keyid)
end
keyid = signature_input.match(KEYID_PATTERN)[1]
provider = Fasp::Provider.find(keyid)
linzer_request = Linzer.new_request(
request.method,
request.original_url,
{},
{
'content-digest' => request.headers['content-digest'],
'signature-input' => signature_input,
'signature' => request.headers['signature'],
}
)
message = Linzer::Message.new(linzer_request)
key = Linzer.new_ed25519_public_key(provider.provider_public_key_pem, keyid)
signature = Linzer::Signature.build(message.headers)
Linzer.verify(key, message, signature)
@current_provider = provider
end
def sign_response
response.headers['content-digest'] = "sha-256=:#{OpenSSL::Digest.base64digest('sha256', response.body || '')}:"
linzer_response = Linzer.new_response(response.body, response.status, { 'content-digest' => response.headers['content-digest'] })
message = Linzer::Message.new(linzer_response)
key = Linzer.new_ed25519_key(current_provider.server_private_key_pem)
signature = Linzer.sign(key, message, %w(@status content-digest))
response.headers.merge!(signature.to_h)
Linzer.sign!(response, key:, components: %w(@status content-digest))
end
def check_fasp_enabled

View File

@@ -69,6 +69,10 @@ export function importFetchedStatuses(statuses) {
processStatus(status.reblog);
}
if (status.quote?.quoted_status) {
processStatus(status.quote.quoted_status);
}
if (status.poll?.id) {
pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id]));
}

View File

@@ -23,12 +23,20 @@ export function normalizeFilterResult(result) {
export function normalizeStatus(status, normalOldStatus) {
const normalStatus = { ...status };
normalStatus.account = status.account.id;
if (status.reblog && status.reblog.id) {
normalStatus.reblog = status.reblog.id;
}
if (status.quote?.quoted_status ?? status.quote?.quoted_status_id) {
normalStatus.quote = {
...status.quote,
quoted_status: status.quote.quoted_status?.id ?? status.quote?.quoted_status_id,
};
}
if (status.poll && status.poll.id) {
normalStatus.poll = status.poll.id;
}

View File

@@ -27,6 +27,7 @@ import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
import { VerifiedBadge } from 'mastodon/components/verified_badge';
import { useIdentity } from 'mastodon/identity_context';
import { me } from 'mastodon/initial_state';
import type { MenuItem } from 'mastodon/models/dropdown_menu';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
@@ -70,10 +71,12 @@ export const Account: React.FC<{
withBio?: boolean;
}> = ({ id, size = 46, hidden, minimal, defaultAction, withBio }) => {
const intl = useIntl();
const { signedIn } = useIdentity();
const account = useAppSelector((state) => state.accounts.get(id));
const relationship = useAppSelector((state) => state.relationships.get(id));
const dispatch = useAppDispatch();
const accountUrl = account?.url;
const isRemote = account?.acct !== account?.username;
const handleBlock = useCallback(() => {
if (relationship?.blocking) {
@@ -116,66 +119,74 @@ export const Account: React.FC<{
},
];
} else if (defaultAction !== 'block') {
const handleAddToLists = () => {
const openAddToListModal = () => {
dispatch(
openModal({
modalType: 'LIST_ADDER',
modalProps: {
accountId: id,
},
}),
);
};
if (relationship?.following || relationship?.requested || id === me) {
openAddToListModal();
} else {
dispatch(
openModal({
modalType: 'CONFIRM_FOLLOW_TO_LIST',
modalProps: {
accountId: id,
onConfirm: () => {
apiFollowAccount(id)
.then((relationship) => {
dispatch(
followAccountSuccess({
relationship,
alreadyFollowing: false,
}),
);
openAddToListModal();
})
.catch((err: unknown) => {
dispatch(showAlertForError(err));
});
},
},
}),
);
}
};
arr = [];
arr = [
{
if (isRemote && accountUrl) {
arr.push({
text: intl.formatMessage(messages.openOriginalPage),
href: accountUrl,
});
}
if (signedIn) {
const handleAddToLists = () => {
const openAddToListModal = () => {
dispatch(
openModal({
modalType: 'LIST_ADDER',
modalProps: {
accountId: id,
},
}),
);
};
if (relationship?.following || relationship?.requested || id === me) {
openAddToListModal();
} else {
dispatch(
openModal({
modalType: 'CONFIRM_FOLLOW_TO_LIST',
modalProps: {
accountId: id,
onConfirm: () => {
apiFollowAccount(id)
.then((relationship) => {
dispatch(
followAccountSuccess({
relationship,
alreadyFollowing: false,
}),
);
openAddToListModal();
})
.catch((err: unknown) => {
dispatch(showAlertForError(err));
});
},
},
}),
);
}
};
arr.push({
text: intl.formatMessage(messages.addToLists),
action: handleAddToLists,
},
];
if (accountUrl) {
arr.unshift(
{
text: intl.formatMessage(messages.openOriginalPage),
href: accountUrl,
},
null,
);
});
}
}
return arr;
}, [dispatch, intl, id, accountUrl, relationship, defaultAction]);
}, [
dispatch,
intl,
id,
accountUrl,
relationship,
defaultAction,
isRemote,
signedIn,
]);
if (hidden) {
return (

View File

@@ -7,10 +7,16 @@ import classNames from 'classnames';
export const AvatarGroup: React.FC<{
compact?: boolean;
avatarHeight?: number;
children: React.ReactNode;
}> = ({ children, compact = false }) => (
}> = ({ children, compact = false, avatarHeight }) => (
<div
className={classNames('avatar-group', { 'avatar-group--compact': compact })}
style={
avatarHeight
? ({ '--avatar-height': `${avatarHeight}px` } as React.CSSProperties)
: undefined
}
>
{children}
</div>

View File

@@ -43,3 +43,17 @@ export const FollowersCounter = (
}}
/>
);
export const FollowersYouKnowCounter = (
displayNumber: React.ReactNode,
pluralReady: number,
) => (
<FormattedMessage
id='account.followers_you_know_counter'
defaultMessage='{counter} you know'
values={{
count: pluralReady,
counter: <strong>{displayNumber}</strong>,
}}
/>
);

View File

@@ -9,11 +9,16 @@ import { fetchAccount } from 'mastodon/actions/accounts';
import { AccountBio } from 'mastodon/components/account_bio';
import { AccountFields } from 'mastodon/components/account_fields';
import { Avatar } from 'mastodon/components/avatar';
import { FollowersCounter } from 'mastodon/components/counters';
import { AvatarGroup } from 'mastodon/components/avatar_group';
import {
FollowersCounter,
FollowersYouKnowCounter,
} from 'mastodon/components/counters';
import { DisplayName } from 'mastodon/components/display_name';
import { FollowButton } from 'mastodon/components/follow_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { ShortNumber } from 'mastodon/components/short_number';
import { useFetchFamiliarFollowers } from 'mastodon/features/account_timeline/hooks/familiar_followers';
import { domain } from 'mastodon/initial_state';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
@@ -38,6 +43,8 @@ export const HoverCardAccount = forwardRef<
}
}, [dispatch, accountId, account]);
const { familiarFollowers } = useFetchFamiliarFollowers({ accountId });
return (
<div
ref={ref}
@@ -73,11 +80,27 @@ export const HoverCardAccount = forwardRef<
)}
</div>
<div className='hover-card__number'>
<div className='hover-card__numbers'>
<ShortNumber
value={account.followers_count}
renderer={FollowersCounter}
/>
{familiarFollowers.length > 0 && (
<>
&middot;
<div className='hover-card__familiar-followers'>
<ShortNumber
value={familiarFollowers.length}
renderer={FollowersYouKnowCounter}
/>
<AvatarGroup compact>
{familiarFollowers.slice(0, 3).map((account) => (
<Avatar key={account.id} account={account} size={22} />
))}
</AvatarGroup>
</div>
</>
)}
</div>
<FollowButton accountId={accountId} />

View File

@@ -1,17 +0,0 @@
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { IconLogo } from 'mastodon/components/logo';
import { AuthorLink } from 'mastodon/features/explore/components/author_link';
export const MoreFromAuthor = ({ accountId }) => (
<div className='more-from-author'>
<IconLogo />
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <AuthorLink accountId={accountId} /> }} />
</div>
);
MoreFromAuthor.propTypes = {
accountId: PropTypes.string.isRequired,
};

View File

@@ -0,0 +1,21 @@
import { FormattedMessage } from 'react-intl';
import { IconLogo } from 'mastodon/components/logo';
import { AuthorLink } from 'mastodon/features/explore/components/author_link';
export const MoreFromAuthor: React.FC<{ accountId: string }> = ({
accountId,
}) => (
<FormattedMessage
id='link_preview.more_from_author'
defaultMessage='More from {name}'
values={{ name: <AuthorLink accountId={accountId} /> }}
>
{(chunks) => (
<div className='more-from-author'>
<IconLogo />
{chunks}
</div>
)}
</FormattedMessage>
);

View File

@@ -5,14 +5,12 @@ import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react';
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
import { ContentWarning } from 'mastodon/components/content_warning';
import { FilterWarning } from 'mastodon/components/filter_warning';
@@ -88,6 +86,7 @@ class Status extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map,
account: ImmutablePropTypes.record,
children: PropTypes.node,
previousId: PropTypes.string,
nextInReplyToId: PropTypes.string,
rootId: PropTypes.string,
@@ -115,6 +114,7 @@ class Status extends ImmutablePureComponent {
onMoveUp: PropTypes.func,
onMoveDown: PropTypes.func,
showThread: PropTypes.bool,
isQuotedPost: PropTypes.bool,
getScrollPosition: PropTypes.func,
updateScrollBottom: PropTypes.func,
cacheMediaWidth: PropTypes.func,
@@ -372,7 +372,7 @@ class Status extends ImmutablePureComponent {
};
render () {
const { intl, hidden, featured, unfocusable, unread, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46 } = this.props;
const { intl, hidden, featured, unfocusable, unread, showThread, isQuotedPost = false, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46, children } = this.props;
let { status, account, ...other } = this.props;
@@ -519,7 +519,7 @@ class Status extends ImmutablePureComponent {
</Bundle>
);
}
} else if (status.get('card')) {
} else if (status.get('card') && !status.get('quote')) {
media = (
<Card
onOpenMedia={this.handleOpenMedia}
@@ -543,7 +543,19 @@ class Status extends ImmutablePureComponent {
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted || unfocusable ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
{!skipPrepend && prepend}
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
<div
className={
classNames('status', `status-${status.get('visibility')}`,
{
'status-reply': !!status.get('in_reply_to_id'),
'status--in-thread': !!rootId,
'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted,
'status--is-quote': isQuotedPost,
'status--has-quote': !!status.get('quote'),
})
}
data-id={status.get('id')}
>
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
<div onClick={this.handleHeaderClick} onAuxClick={this.handleHeaderClick} className='status__info'>
@@ -576,12 +588,16 @@ class Status extends ImmutablePureComponent {
{...statusContentProps}
/>
{children}
{media}
{hashtagBar}
</>
)}
<StatusActionBar scrollKey={scrollKey} status={status} account={account} {...other} />
{!isQuotedPost &&
<StatusActionBar scrollKey={scrollKey} status={status} account={account} {...other} />
}
</div>
</div>
</HotKeys>

View File

@@ -9,7 +9,7 @@ import { TIMELINE_GAP, TIMELINE_SUGGESTIONS } from 'mastodon/actions/timelines';
import { RegenerationIndicator } from 'mastodon/components/regeneration_indicator';
import { InlineFollowSuggestions } from 'mastodon/features/home_timeline/components/inline_follow_suggestions';
import StatusContainer from '../containers/status_container';
import { StatusQuoteManager } from '../components/status_quoted';
import { LoadGap } from './load_gap';
import ScrollableList from './scrollable_list';
@@ -113,7 +113,7 @@ export default class StatusList extends ImmutablePureComponent {
);
default:
return (
<StatusContainer
<StatusQuoteManager
key={statusId}
id={statusId}
onMoveUp={this.handleMoveUp}
@@ -130,7 +130,7 @@ export default class StatusList extends ImmutablePureComponent {
if (scrollableContent && featuredStatusIds) {
scrollableContent = featuredStatusIds.map(statusId => (
<StatusContainer
<StatusQuoteManager
key={`f-${statusId}`}
id={statusId}
featured

View File

@@ -0,0 +1,208 @@
import { useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import type { Map as ImmutableMap } from 'immutable';
import ArticleIcon from '@/material-icons/400-24px/article.svg?react';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
import { Icon } from 'mastodon/components/icon';
import StatusContainer from 'mastodon/containers/status_container';
import type { Status } from 'mastodon/models/status';
import type { RootState } from 'mastodon/store';
import { useAppSelector } from 'mastodon/store';
import QuoteIcon from '../../images/quote.svg?react';
import { makeGetStatus } from '../selectors';
const MAX_QUOTE_POSTS_NESTING_LEVEL = 1;
const QuoteWrapper: React.FC<{
isError?: boolean;
children: React.ReactElement;
}> = ({ isError, children }) => {
return (
<div
className={classNames('status__quote', {
'status__quote--error': isError,
})}
>
<Icon id='quote' icon={QuoteIcon} className='status__quote-icon' />
{children}
</div>
);
};
const QuoteLink: React.FC<{
status: Status;
}> = ({ status }) => {
const accountId = status.get('account') as string;
const account = useAppSelector((state) =>
accountId ? state.accounts.get(accountId) : undefined,
);
const quoteAuthorName = account?.display_name_html;
if (!quoteAuthorName) {
return null;
}
const quoteAuthorElement = (
<span dangerouslySetInnerHTML={{ __html: quoteAuthorName }} />
);
const quoteUrl = `/@${account.get('acct')}/${status.get('id') as string}`;
return (
<Link to={quoteUrl} className='status__quote-author-button'>
<FormattedMessage
id='status.quote_post_author'
defaultMessage='Post by {name}'
values={{ name: quoteAuthorElement }}
/>
<Icon id='chevron_right' icon={ChevronRightIcon} />
<Icon id='article' icon={ArticleIcon} />
</Link>
);
};
type QuoteMap = ImmutableMap<'state' | 'quoted_status', string | null>;
type GetStatusSelector = (
state: RootState,
props: { id?: string | null; contextType?: string },
) => Status | null;
export const QuotedStatus: React.FC<{
quote: QuoteMap;
contextType?: string;
variant?: 'full' | 'link';
nestingLevel?: number;
}> = ({ quote, contextType, nestingLevel = 1, variant = 'full' }) => {
const quotedStatusId = quote.get('quoted_status');
const quoteState = quote.get('state');
const status = useAppSelector((state) =>
quotedStatusId ? state.statuses.get(quotedStatusId) : undefined,
);
let quoteError: React.ReactNode = null;
// In order to find out whether the quoted post should be completely hidden
// due to a matching filter, we run it through the selector used by `status_container`.
// If this returns null even though `status` exists, it's because it's filtered.
const getStatus = useMemo(() => makeGetStatus(), []) as GetStatusSelector;
const statusWithExtraData = useAppSelector((state) =>
getStatus(state, { id: quotedStatusId, contextType }),
);
const isFilteredAndHidden = status && statusWithExtraData === null;
if (isFilteredAndHidden) {
quoteError = (
<FormattedMessage
id='status.quote_error.filtered'
defaultMessage='Hidden due to one of your filters'
/>
);
} else if (quoteState === 'deleted') {
quoteError = (
<FormattedMessage
id='status.quote_error.removed'
defaultMessage='This post was removed by its author.'
/>
);
} else if (quoteState === 'unauthorized') {
quoteError = (
<FormattedMessage
id='status.quote_error.unauthorized'
defaultMessage='This post cannot be displayed as you are not authorized to view it.'
/>
);
} else if (quoteState === 'pending') {
quoteError = (
<FormattedMessage
id='status.quote_error.pending_approval'
defaultMessage='This post is pending approval from the original author.'
/>
);
} else if (quoteState === 'rejected' || quoteState === 'revoked') {
quoteError = (
<FormattedMessage
id='status.quote_error.rejected'
defaultMessage='This post cannot be displayed as the original author does not allow it to be quoted.'
/>
);
} else if (!status || !quotedStatusId) {
quoteError = (
<FormattedMessage
id='status.quote_error.not_found'
defaultMessage='This post cannot be displayed.'
/>
);
}
if (quoteError) {
return <QuoteWrapper isError>{quoteError}</QuoteWrapper>;
}
if (variant === 'link' && status) {
return <QuoteLink status={status} />;
}
const childQuote = status?.get('quote') as QuoteMap | undefined;
const canRenderChildQuote =
childQuote && nestingLevel <= MAX_QUOTE_POSTS_NESTING_LEVEL;
return (
<QuoteWrapper>
{/* @ts-expect-error Status is not yet typed */}
<StatusContainer
isQuotedPost
id={quotedStatusId}
contextType={contextType}
avatarSize={40}
>
{canRenderChildQuote && (
<QuotedStatus
quote={childQuote}
contextType={contextType}
variant={
nestingLevel === MAX_QUOTE_POSTS_NESTING_LEVEL ? 'link' : 'full'
}
nestingLevel={nestingLevel + 1}
/>
)}
</StatusContainer>
</QuoteWrapper>
);
};
interface StatusQuoteManagerProps {
id: string;
contextType?: string;
[key: string]: unknown;
}
/**
* This wrapper component takes a status ID and, if the associated status
* is a quote post, it renders the quote into `StatusContainer` as a child.
* It passes all other props through to `StatusContainer`.
*/
export const StatusQuoteManager = (props: StatusQuoteManagerProps) => {
const status = useAppSelector((state) => {
const status = state.statuses.get(props.id);
const reblogId = status?.get('reblog') as string | undefined;
return reblogId ? state.statuses.get(reblogId) : status;
});
const quote = status?.get('quote') as QuoteMap | undefined;
if (quote) {
return (
<StatusContainer {...props}>
<QuotedStatus quote={quote} contextType={props.contextType} />
</StatusContainer>
);
}
return <StatusContainer {...props} />;
};

View File

@@ -171,8 +171,8 @@ class About extends PureComponent {
) : (
<ol className='rules-list'>
{server.get('rules').map(rule => {
const text = rule.getIn(['translations', locale, 'text']) || rule.get('text');
const hint = rule.getIn(['translations', locale, 'hint']) || rule.get('hint');
const text = rule.getIn(['translations', locale, 'text']) || rule.getIn(['translations', locale.split('-')[0], 'text']) || rule.get('text');
const hint = rule.getIn(['translations', locale, 'hint']) || rule.getIn(['translations', locale.split('-')[0], 'hint']) || rule.get('hint');
return (
<li key={rule.get('id')}>
<div className='rules-list__text'>{text}</div>

View File

@@ -14,7 +14,7 @@ import { Account } from 'mastodon/components/account';
import { ColumnBackButton } from 'mastodon/components/column_back_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { RemoteHint } from 'mastodon/components/remote_hint';
import StatusContainer from 'mastodon/containers/status_container';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import Column from 'mastodon/features/ui/components/column';
@@ -142,9 +142,8 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
/>
</h4>
{featuredStatusIds.map((statusId) => (
<StatusContainer
<StatusQuoteManager
key={`f-${statusId}`}
// @ts-expect-error inferred props are wrong
id={statusId}
contextType='account'
/>

View File

@@ -1,15 +1,12 @@
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { fetchAccountsFamiliarFollowers } from '@/mastodon/actions/accounts_familiar_followers';
import { Avatar } from '@/mastodon/components/avatar';
import { AvatarGroup } from '@/mastodon/components/avatar_group';
import type { Account } from '@/mastodon/models/account';
import { getAccountFamiliarFollowers } from '@/mastodon/selectors/accounts';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { useFetchFamiliarFollowers } from '../hooks/familiar_followers';
const AccountLink: React.FC<{ account?: Account }> = ({ account }) => {
if (!account) {
@@ -64,20 +61,11 @@ const FamiliarFollowersReadout: React.FC<{ familiarFollowers: Account[] }> = ({
export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
accountId,
}) => {
const dispatch = useAppDispatch();
const familiarFollowers = useAppSelector((state) =>
getAccountFamiliarFollowers(state, accountId),
);
const { familiarFollowers, isLoading } = useFetchFamiliarFollowers({
accountId,
});
const hasNoData = familiarFollowers === null;
useEffect(() => {
if (hasNoData) {
void dispatch(fetchAccountsFamiliarFollowers({ id: accountId }));
}
}, [dispatch, accountId, hasNoData]);
if (hasNoData || familiarFollowers.length === 0) {
if (isLoading || familiarFollowers.length === 0) {
return null;
}

View File

@@ -0,0 +1,30 @@
import { useEffect } from 'react';
import { fetchAccountsFamiliarFollowers } from '@/mastodon/actions/accounts_familiar_followers';
import { getAccountFamiliarFollowers } from '@/mastodon/selectors/accounts';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
import { me } from 'mastodon/initial_state';
export const useFetchFamiliarFollowers = ({
accountId,
}: {
accountId?: string;
}) => {
const dispatch = useAppDispatch();
const familiarFollowers = useAppSelector((state) =>
accountId ? getAccountFamiliarFollowers(state, accountId) : null,
);
const hasNoData = familiarFollowers === null;
useEffect(() => {
if (hasNoData && accountId && accountId !== me) {
void dispatch(fetchAccountsFamiliarFollowers({ id: accountId }));
}
}, [dispatch, accountId, hasNoData]);
return {
familiarFollowers: hasNoData ? [] : familiarFollowers,
isLoading: hasNoData,
};
};

View File

@@ -20,7 +20,7 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
import { Account } from 'mastodon/components/account';
import { Icon } from 'mastodon/components/icon';
import StatusContainer from 'mastodon/containers/status_container';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
import { me } from 'mastodon/initial_state';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@@ -175,7 +175,7 @@ class Notification extends ImmutablePureComponent {
renderMention (notification) {
return (
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
withDismiss
hidden={this.props.hidden}
@@ -205,7 +205,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
account={notification.get('account')}
muted
@@ -235,7 +235,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
account={notification.get('account')}
muted
@@ -269,7 +269,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
account={notification.get('account')}
contextType='notifications'
@@ -304,7 +304,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
account={notification.get('account')}
contextType='notifications'
@@ -345,7 +345,7 @@ class Notification extends ImmutablePureComponent {
</span>
</div>
<StatusContainer
<StatusQuoteManager
id={notification.get('status')}
account={account}
contextType='notifications'

View File

@@ -18,12 +18,14 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { DisplayedName } from './displayed_name';
import { EmbeddedStatus } from './embedded_status';
const AVATAR_SIZE = 28;
export const AvatarById: React.FC<{ accountId: string }> = ({ accountId }) => {
const account = useAppSelector((state) => state.accounts.get(accountId));
if (!account) return null;
return <Avatar withLink account={account} size={28} />;
return <Avatar withLink account={account} size={AVATAR_SIZE} />;
};
export type LabelRenderer = (
@@ -108,7 +110,7 @@ export const NotificationGroupWithStatus: React.FC<{
<div className='notification-group__main'>
<div className='notification-group__main__header'>
<div className='notification-group__main__header__wrapper'>
<AvatarGroup>
<AvatarGroup avatarHeight={AVATAR_SIZE}>
{accountIds
.slice(0, NOTIFICATIONS_GROUP_MAX_AVATARS)
.map((id) => (
@@ -123,7 +125,14 @@ export const NotificationGroupWithStatus: React.FC<{
<div className='notification-group__main__header__label'>
{label}
{timestamp && <RelativeTimestamp timestamp={timestamp} />}
{timestamp && (
<>
<span className='notification-group__main__header__label-separator'>
&middot;
</span>
<RelativeTimestamp timestamp={timestamp} />
</>
)}
</div>
</div>

View File

@@ -12,7 +12,7 @@ import {
} from 'mastodon/actions/statuses';
import type { IconProp } from 'mastodon/components/icon';
import { Icon } from 'mastodon/components/icon';
import Status from 'mastodon/containers/status_container';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
import { getStatusHidden } from 'mastodon/selectors/filters';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
@@ -102,8 +102,7 @@ export const NotificationWithStatus: React.FC<{
{label}
</div>
<Status
// @ts-expect-error -- <Status> is not yet typed
<StatusQuoteManager
id={statusId}
contextType='notifications'
withDismiss

View File

@@ -51,7 +51,7 @@ class Rules extends PureComponent {
value={item.get('id')}
checked={selectedRuleIds.includes(item.get('id'))}
onToggle={this.handleRulesToggle}
label={item.getIn(['translations', locale, 'text']) || item.get('text')}
label={item.getIn(['translations', locale, 'text']) || item.getIn(['translations', locale.split('-')[0], 'text']) || item.get('text')}
multiple
/>
))}

View File

@@ -17,7 +17,7 @@ import { ColumnHeader } from 'mastodon/components/column_header';
import { CompatibilityHashtag as Hashtag } from 'mastodon/components/hashtag';
import { Icon } from 'mastodon/components/icon';
import ScrollableList from 'mastodon/components/scrollable_list';
import Status from 'mastodon/containers/status_container';
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
import { Search } from 'mastodon/features/compose/components/search';
import { useSearchParam } from 'mastodon/hooks/useSearchParam';
import type { Hashtag as HashtagType } from 'mastodon/models/tags';
@@ -53,8 +53,7 @@ const renderHashtags = (hashtags: HashtagType[]) =>
const renderStatuses = (statusIds: string[]) =>
hidePeek<string>(statusIds).map((id) => (
// @ts-expect-error inferred props are wrong
<Status key={id} id={id} />
<StatusQuoteManager key={id} id={id} />
));
type SearchType = 'all' | ApiSearchType;
@@ -190,8 +189,7 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
onClickMore={handleSelectStatuses}
>
{results.statuses.slice(0, INITIAL_DISPLAY).map((id) => (
// @ts-expect-error inferred props are wrong
<Status key={id} id={id} />
<StatusQuoteManager key={id} id={id} />
))}
</SearchSection>
)}

View File

@@ -26,6 +26,7 @@ import { IconLogo } from 'mastodon/components/logo';
import MediaGallery from 'mastodon/components/media_gallery';
import { PictureInPicturePlaceholder } from 'mastodon/components/picture_in_picture_placeholder';
import StatusContent from 'mastodon/components/status_content';
import { QuotedStatus } from 'mastodon/components/status_quoted';
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
import { Audio } from 'mastodon/features/audio';
import scheduleIdleTask from 'mastodon/features/ui/util/schedule_idle_task';
@@ -226,7 +227,7 @@ export const DetailedStatus: React.FC<{
/>
);
}
} else if (status.get('card')) {
} else if (status.get('card') && !status.get('quote')) {
media = (
<Card
sensitive={status.get('sensitive')}
@@ -306,7 +307,12 @@ export const DetailedStatus: React.FC<{
return (
<div style={outerStyle}>
<div ref={handleRef} className={classNames('detailed-status')}>
<div
ref={handleRef}
className={classNames('detailed-status', {
'status--has-quote': !!status.get('quote'),
})}
>
{status.get('visibility') === 'direct' && (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'>
@@ -371,6 +377,10 @@ export const DetailedStatus: React.FC<{
{...(statusContentProps as any)}
/>
{status.get('quote') && (
<QuotedStatus quote={status.get('quote')} />
)}
{media}
{hashtagBar}
</>

View File

@@ -6,8 +6,6 @@ import classNames from 'classnames';
import { Helmet } from 'react-helmet';
import { withRouter } from 'react-router-dom';
import { createSelector } from '@reduxjs/toolkit';
import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
@@ -62,7 +60,7 @@ import {
} from '../../actions/statuses';
import ColumnHeader from '../../components/column_header';
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
import StatusContainer from '../../containers/status_container';
import { StatusQuoteManager } from '../../components/status_quoted';
import { deleteModal } from '../../initial_state';
import { makeGetStatus, makeGetPictureInPicture } from '../../selectors';
import { getAncestorsIds, getDescendantsIds } from 'mastodon/selectors/contexts';
@@ -477,7 +475,7 @@ class Status extends ImmutablePureComponent {
const { params: { statusId } } = this.props;
return list.map((id, i) => (
<StatusContainer
<StatusQuoteManager
key={id}
id={id}
onMoveUp={this.handleMoveUp}

View File

@@ -24,9 +24,7 @@ export const HotkeyIndicator: React.FC<{
enter: [{ opacity: 1 }],
leave: [{ opacity: 0 }],
onRest: (_result, _ctrl, item) => {
if (item) {
onDismiss(item);
}
onDismiss(item);
},
});

View File

@@ -23,9 +23,11 @@
"account.copy": "Profil linkini kopyala",
"account.direct": "@{name} istifadəçisini fərdi olaraq etiketlə",
"account.disable_notifications": "@{name} paylaşım edəndə mənə bildiriş göndərməyi dayandır",
"account.domain_blocking": "Domenin bloklanması",
"account.edit_profile": "Profili redaktə et",
"account.enable_notifications": "@{name} paylaşım edəndə mənə bildiriş göndər",
"account.endorse": "Profildə seçilmişlərə əlavə et",
"account.featured.hashtags": "Etiketler",
"account.featured_tags.last_status_at": "Son paylaşım {date} tarixində olub",
"account.featured_tags.last_status_never": "Paylaşım yoxdur",
"account.follow": "İzlə",

View File

@@ -34,6 +34,7 @@
"account.followers": "Tud koumanantet",
"account.followers.empty": "Den na heul an implijer·ez-mañ c'hoazh.",
"account.followers_counter": "{count, plural, one {{counter} heulier} two {{counter} heulier} few {{counter} heulier} many {{counter} heulier} other {{counter} heulier}}",
"account.followers_you_know_counter": "{counter} a anavezit",
"account.following": "Koumanantoù",
"account.follows.empty": "An implijer·ez-mañ na heul den ebet.",
"account.go_to_profile": "Gwelet ar profil",
@@ -577,6 +578,7 @@
"status.mute": "Kuzhat @{name}",
"status.mute_conversation": "Kuzhat ar gaozeadenn",
"status.open": "Digeriñ ar c'hannad-mañ",
"status.quote_post_author": "Embannadenn gant {name}",
"status.read_more": "Lenn muioc'h",
"status.reblog": "Skignañ",
"status.reblog_private": "Skignañ gant ar weledenn gentañ",

View File

@@ -42,6 +42,7 @@
"account.followers": "Sledující",
"account.followers.empty": "Tohoto uživatele zatím nikdo nesleduje.",
"account.followers_counter": "{count, plural, one {{counter} sledující} few {{counter} sledující} many {{counter} sledujících} other {{counter} sledujících}}",
"account.followers_you_know_counter": "{count, one {{counter}, kterého znáte}, few {{counter}, které znáte}, many {{counter}, kterých znáte} other {{counter}, kterých znáte}}",
"account.following": "Sledujete",
"account.following_counter": "{count, plural, one {{counter} sledovaný} few {{counter} sledovaní} many {{counter} sledovaných} other {{counter} sledovaných}}",
"account.follows.empty": "Tento uživatel zatím nikoho nesleduje.",
@@ -683,7 +684,7 @@
"notifications.policy.filter_not_followers_title": "Lidé, kteří vás nesledují",
"notifications.policy.filter_not_following_hint": "Dokud je ručně neschválíte",
"notifications.policy.filter_not_following_title": "Lidé, které nesledujete",
"notifications.policy.filter_private_mentions_hint": "Vyfiltrováno, pokud to není odpověď na vaši zmínku nebo pokud sledujete odesílatele",
"notifications.policy.filter_private_mentions_hint": "Filtrováno, pokud to není v odpovědi na vaši vlastní zmínku nebo pokud nesledujete odesílatele",
"notifications.policy.filter_private_mentions_title": "Nevyžádané soukromé zmínky",
"notifications.policy.title": "Spravovat oznámení od…",
"notifications_permission_banner.enable": "Povolit oznámení na ploše",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Skrýt konverzaci",
"status.open": "Rozbalit tento příspěvek",
"status.pin": "Zvýraznit na profilu",
"status.quote_error.not_found": "Tento příspěvek nelze zobrazit.",
"status.quote_error.pending_approval": "Tento příspěvek čeká na schválení od původního autora.",
"status.quote_error.rejected": "Tento příspěvek nemůže být zobrazen, protože původní autor neumožňuje, aby byl citován.",
"status.quote_error.removed": "Tento příspěvek byl odstraněn jeho autorem.",
"status.quote_error.unauthorized": "Tento příspěvek nelze zobrazit, protože nemáte oprávnění k jeho zobrazení.",
"status.quote_post_author": "Příspěvek od {name}",
"status.read_more": "Číst více",
"status.reblog": "Boostnout",
"status.reblog_private": "Boostnout s původní viditelností",

View File

@@ -42,6 +42,7 @@
"account.followers": "Dilynwyr",
"account.followers.empty": "Does neb yn dilyn y defnyddiwr hwn eto.",
"account.followers_counter": "{count, plural, one {{counter} dilynwr} two {{counter} ddilynwr} other {{counter} dilynwyr}}",
"account.followers_you_know_counter": "{counter} rydych chi'n adnabod",
"account.following": "Yn dilyn",
"account.following_counter": "{count, plural, one {Yn dilyn {counter}} other {Yn dilyn {counter} arall}}",
"account.follows.empty": "Dyw'r defnyddiwr hwn ddim yn dilyn unrhyw un eto.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Anwybyddu sgwrs",
"status.open": "Ehangu'r post hwn",
"status.pin": "Dangos ar y proffil",
"status.quote_error.not_found": "Does dim modd dangos y postiad hwn.",
"status.quote_error.pending_approval": "Mae'r postiad hwn yn aros am gymeradwyaeth yr awdur gwreiddiol.",
"status.quote_error.rejected": "Does dim modd dangos y postiad hwn gan nad yw'r awdur gwreiddiol yn caniatáu iddo gael ei ddyfynnu.",
"status.quote_error.removed": "Cafodd y postiad hwn ei ddileu gan ei awdur.",
"status.quote_error.unauthorized": "Does dim modd dangos y postiad hwn gan nad oes gennych awdurdod i'w weld.",
"status.quote_post_author": "Postiad gan {name}",
"status.read_more": "Darllen rhagor",
"status.reblog": "Hybu",
"status.reblog_private": "Hybu i'r gynulleidfa wreiddiol",

View File

@@ -863,6 +863,12 @@
"status.mute_conversation": "Skjul samtale",
"status.open": "Udvid dette indlæg",
"status.pin": "Fremhæv på profil",
"status.quote_error.not_found": "Dette indlæg kan ikke vises.",
"status.quote_error.pending_approval": "Dette indlæg afventer godkendelse fra den oprindelige forfatter.",
"status.quote_error.rejected": "Dette indlæg kan ikke vises, da den oprindelige forfatter ikke tillader citering heraf.",
"status.quote_error.removed": "Dette indlæg er fjernet af forfatteren.",
"status.quote_error.unauthorized": "Dette indlæg kan ikke vises, da man ikke har tilladelse til at se det.",
"status.quote_post_author": "Indlæg fra {name}",
"status.read_more": "Læs mere",
"status.reblog": "Fremhæv",
"status.reblog_private": "Fremhæv med oprindelig synlighed",

View File

@@ -42,6 +42,7 @@
"account.followers": "Follower",
"account.followers.empty": "Diesem Profil folgt noch niemand.",
"account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}",
"account.followers_you_know_counter": "{counter} bekannt",
"account.following": "Folge ich",
"account.following_counter": "{count, plural, one {{counter} Folge ich} other {{counter} Folge ich}}",
"account.follows.empty": "Dieses Profil folgt noch niemandem.",
@@ -360,7 +361,7 @@
"filter_modal.select_filter.title": "Diesen Beitrag filtern",
"filter_modal.title.status": "Beitrag per Filter ausblenden",
"filter_warning.matches_filter": "Übereinstimmend mit dem Filter „<span>{title}</span>“",
"filtered_notifications_banner.pending_requests": "Von {count, plural, =0 {keinem, den} one {einer Person, die} other {# Personen, die}} du möglicherweise kennst",
"filtered_notifications_banner.pending_requests": "Von {count, plural, =0 {keinem Profil, das dir möglicherweise bekannt ist} one {einem Profil, das dir möglicherweise bekannt ist} other {# Profilen, die dir möglicherweise bekannt sind}}",
"filtered_notifications_banner.title": "Gefilterte Benachrichtigungen",
"firehose.all": "Alle Server",
"firehose.local": "Dieser Server",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Unterhaltung stummschalten",
"status.open": "Beitrag öffnen",
"status.pin": "Im Profil vorstellen",
"status.quote_error.not_found": "Dieser Beitrag kann nicht angezeigt werden.",
"status.quote_error.pending_approval": "Dieser Beitrag muss noch durch das ursprüngliche Profil genehmigt werden.",
"status.quote_error.rejected": "Dieser Beitrag kann nicht angezeigt werden, weil das ursprüngliche Profil das Zitieren nicht erlaubt.",
"status.quote_error.removed": "Dieser Beitrag wurde durch das Profil entfernt.",
"status.quote_error.unauthorized": "Dieser Beitrag kann nicht angezeigt werden, weil du zum Ansehen nicht berechtigt bist.",
"status.quote_post_author": "Beitrag von {name}",
"status.read_more": "Gesamten Beitrag anschauen",
"status.reblog": "Teilen",
"status.reblog_private": "Mit der ursprünglichen Zielgruppe teilen",

View File

@@ -42,6 +42,7 @@
"account.followers": "Followers",
"account.followers.empty": "No one follows this user yet.",
"account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}",
"account.followers_you_know_counter": "{counter} you know",
"account.following": "Following",
"account.following_counter": "{count, plural, one {{counter} following} other {{counter} following}}",
"account.follows.empty": "This user doesn't follow anyone yet.",
@@ -863,6 +864,13 @@
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this post",
"status.pin": "Feature on profile",
"status.quote_error.filtered": "Hidden due to one of your filters",
"status.quote_error.not_found": "This post cannot be displayed.",
"status.quote_error.pending_approval": "This post is pending approval from the original author.",
"status.quote_error.rejected": "This post cannot be displayed as the original author does not allow it to be quoted.",
"status.quote_error.removed": "This post was removed by its author.",
"status.quote_error.unauthorized": "This post cannot be displayed as you are not authorized to view it.",
"status.quote_post_author": "Post by {name}",
"status.read_more": "Read more",
"status.reblog": "Boost",
"status.reblog_private": "Boost with original visibility",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seguidores",
"account.followers.empty": "Todavía nadie sigue a este usuario.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.followers_you_know_counter": "{counter} seguidores que conocés",
"account.following": "Siguiendo",
"account.following_counter": "{count, plural, one {siguiendo a {counter}} other {siguiendo a {counter}}}",
"account.follows.empty": "Todavía este usuario no sigue a nadie.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Silenciar conversación",
"status.open": "Expandir este mensaje",
"status.pin": "Destacar en el perfil",
"status.quote_error.not_found": "No se puede mostrar este mensaje.",
"status.quote_error.pending_approval": "Este mensaje está pendiente de aprobación del autor original.",
"status.quote_error.rejected": "No se puede mostrar este mensaje, ya que el autor original no permite que se cite.",
"status.quote_error.removed": "Este mensaje fue eliminado por su autor.",
"status.quote_error.unauthorized": "No se puede mostrar este mensaje, ya que no tenés autorización para verlo.",
"status.quote_post_author": "Mensaje de @{name}",
"status.read_more": "Leé más",
"status.reblog": "Adherir",
"status.reblog_private": "Adherir a la audiencia original",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seguidores",
"account.followers.empty": "Nadie sigue a este usuario todavía.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.followers_you_know_counter": "{counter} que conoces",
"account.following": "Siguiendo",
"account.following_counter": "{count, plural, one {{counter} siguiendo} other {{counter} siguiendo}}",
"account.follows.empty": "Este usuario no sigue a nadie todavía.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Silenciar conversación",
"status.open": "Expandir estado",
"status.pin": "Destacar en el perfil",
"status.quote_error.not_found": "No se puede mostrar esta publicación.",
"status.quote_error.pending_approval": "Esta publicación está pendiente de aprobación del autor original.",
"status.quote_error.rejected": "No se puede mostrar esta publicación, puesto que el autor original no permite que sea citado.",
"status.quote_error.removed": "Esta publicación fue eliminada por su autor.",
"status.quote_error.unauthorized": "No se puede mostrar esta publicación, puesto que no estás autorizado a verla.",
"status.quote_post_author": "Publicado por {name}",
"status.read_more": "Leer más",
"status.reblog": "Impulsar",
"status.reblog_private": "Implusar a la audiencia original",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seguidores",
"account.followers.empty": "Todavía nadie sigue a este usuario.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.followers_you_know_counter": "{counter} seguidores que conoces",
"account.following": "Siguiendo",
"account.following_counter": "{count, plural, one {{counter} siguiendo} other {{counter} siguiendo}}",
"account.follows.empty": "Este usuario todavía no sigue a nadie.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Silenciar conversación",
"status.open": "Expandir publicación",
"status.pin": "Destacar en el perfil",
"status.quote_error.not_found": "No se puede mostrar esta publicación.",
"status.quote_error.pending_approval": "Esta publicación está pendiente de aprobación del autor original.",
"status.quote_error.rejected": "Esta publicación no puede mostrarse porque el autor original no permite que se cite.",
"status.quote_error.removed": "Esta publicación fue eliminada por su autor.",
"status.quote_error.unauthorized": "Esta publicación no puede mostrarse, ya que no estás autorizado a verla.",
"status.quote_post_author": "Publicación de {name}",
"status.read_more": "Leer más",
"status.reblog": "Impulsar",
"status.reblog_private": "Impulsar a la audiencia original",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seuraajat",
"account.followers.empty": "Kukaan ei seuraa tätä käyttäjää vielä.",
"account.followers_counter": "{count, plural, one {{counter} seuraaja} other {{counter} seuraajaa}}",
"account.followers_you_know_counter": "{count, plural, one {{counter} tuntemasi} other {{counter} tuntemaasi}}",
"account.following": "Seurattavat",
"account.following_counter": "{count, plural, one {{counter} seurattava} other {{counter} seurattavaa}}",
"account.follows.empty": "Tämä käyttäjä ei vielä seuraa ketään.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Mykistä keskustelu",
"status.open": "Laajenna julkaisu",
"status.pin": "Suosittele profiilissa",
"status.quote_error.not_found": "Tätä julkaisua ei voi näyttää.",
"status.quote_error.pending_approval": "Tämä julkaisu odottaa alkuperäisen tekijänsä hyväksyntää.",
"status.quote_error.rejected": "Tätä julkaisua ei voi näyttää, sillä sen alkuperäinen tekijä ei salli lainattavan julkaisua.",
"status.quote_error.removed": "Tekijä on poistanut julkaisun.",
"status.quote_error.unauthorized": "Tätä julkaisua ei voi näyttää, koska sinulla ei ole oikeutta tarkastella sitä.",
"status.quote_post_author": "Julkaisu käyttäjältä {name}",
"status.read_more": "Näytä enemmän",
"status.reblog": "Tehosta",
"status.reblog_private": "Tehosta alkuperäiselle yleisölle",

View File

@@ -42,6 +42,7 @@
"account.followers": "Fylgjarar",
"account.followers.empty": "Ongar fylgjarar enn.",
"account.followers_counter": "{count, plural, one {{counter} fylgjari} other {{counter} fylgjarar}}",
"account.followers_you_know_counter": "{counter} tú kennir",
"account.following": "Fylgir",
"account.following_counter": "{count, plural, one {{counter} fylgir} other {{counter} fylgja}}",
"account.follows.empty": "Hesin brúkari fylgir ongum enn.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Doyv samrøðu",
"status.open": "Víðka henda postin",
"status.pin": "Vís á vanga",
"status.quote_error.not_found": "Tað ber ikki til at vísa hendan postin.",
"status.quote_error.pending_approval": "Hesin posturin bíðar eftir góðkenning frá upprunahøvundinum.",
"status.quote_error.rejected": "Hesin posturin kann ikki vísast, tí upprunahøvundurin loyvir ikki at posturin verður siteraður.",
"status.quote_error.removed": "Hesin posturin var strikaður av høvundinum.",
"status.quote_error.unauthorized": "Hesin posturin kann ikki vísast, tí tú hevur ikki rættindi at síggja hann.",
"status.quote_post_author": "Postur hjá @{name}",
"status.read_more": "Les meira",
"status.reblog": "Stimbra",
"status.reblog_private": "Stimbra við upprunasýni",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seguidoras",
"account.followers.empty": "Aínda ninguén segue esta usuaria.",
"account.followers_counter": "{count, plural, one {{counter} seguidora} other {{counter} seguidoras}}",
"account.followers_you_know_counter": "{counter} que coñeces",
"account.following": "Seguindo",
"account.following_counter": "{count, plural, one {{counter} seguimento} other {{counter} seguimentos}}",
"account.follows.empty": "Esta usuaria aínda non segue a ninguén.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Silenciar conversa",
"status.open": "Estender esta publicación",
"status.pin": "Destacar no perfil",
"status.quote_error.not_found": "Non se pode mostrar a publicación.",
"status.quote_error.pending_approval": "A publicación está pendente da aprobación pola autora orixinal.",
"status.quote_error.rejected": "Non se pode mostrar esta publicación xa que a autora orixinal non permite que se cite.",
"status.quote_error.removed": "Publicación eliminada pola autora.",
"status.quote_error.unauthorized": "Non se pode mostrar esta publicación porque non tes permiso para vela.",
"status.quote_post_author": "Publicación de {name}",
"status.read_more": "Ler máis",
"status.reblog": "Promover",
"status.reblog_private": "Compartir coa audiencia orixinal",

View File

@@ -863,6 +863,12 @@
"status.mute_conversation": "השתקת שיחה",
"status.open": "הרחבת הודעה זו",
"status.pin": "מובלט בפרופיל",
"status.quote_error.not_found": "לא ניתן להציג הודעה זו.",
"status.quote_error.pending_approval": "הודעה זו מחכה לאישור מידי היוצר המקורי.",
"status.quote_error.rejected": "לא ניתן להציג הודעה זו שכן המחבר.ת המקוריים לא הרשו לצטט אותה.",
"status.quote_error.removed": "הודעה זו הוסרה על ידי השולחים המקוריים.",
"status.quote_error.unauthorized": "הודעה זו לא מוצגת כיוון שאין לך רשות לראותה.",
"status.quote_post_author": "פרסום מאת {name}",
"status.read_more": "לקרוא עוד",
"status.reblog": "הדהוד",
"status.reblog_private": "להדהד ברמת הנראות המקורית",

View File

@@ -42,6 +42,7 @@
"account.followers": "Követő",
"account.followers.empty": "Ezt a felhasználót még senki sem követi.",
"account.followers_counter": "{count, plural, one {{counter} követő} other {{counter} követő}}",
"account.followers_you_know_counter": "{counter} ismerős",
"account.following": "Követve",
"account.following_counter": "{count, plural, one {{counter} követett} other {{counter} követett}}",
"account.follows.empty": "Ez a felhasználó még senkit sem követ.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Beszélgetés némítása",
"status.open": "Bejegyzés kibontása",
"status.pin": "Kiemelés a profilodon",
"status.quote_error.not_found": "Ez a bejegyzés nem jeleníthető meg.",
"status.quote_error.pending_approval": "Ez a bejegyzés az eredeti szerző jóváhagyására vár.",
"status.quote_error.rejected": "Ez a bejegyzés nem jeleníthető meg, mert az eredeti szerzője nem engedélyezi az idézését.",
"status.quote_error.removed": "Ezt a bejegyzés eltávolította a szerzője.",
"status.quote_error.unauthorized": "Ez a bejegyzés nem jeleníthető meg, mert nem jogosult a megtekintésére.",
"status.quote_post_author": "Szerző: {name}",
"status.read_more": "Bővebben",
"status.reblog": "Megtolás",
"status.reblog_private": "Megtolás az eredeti közönségnek",

View File

@@ -42,6 +42,7 @@
"account.followers": "Fylgjendur",
"account.followers.empty": "Ennþá fylgist enginn með þessum notanda.",
"account.followers_counter": "{count, plural, one {Fylgjandi: {counter}} other {Fylgjendur: {counter}}}",
"account.followers_you_know_counter": "{counter} sem þú þekkir",
"account.following": "Fylgist með",
"account.following_counter": "{count, plural, one {Fylgist með: {counter}} other {Fylgist með: {counter}}}",
"account.follows.empty": "Þessi notandi fylgist ennþá ekki með neinum.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Þagga niður í samtali",
"status.open": "Opna þessa færslu",
"status.pin": "Birta á notandasniði",
"status.quote_error.not_found": "Þessa færslu er ekki hægt að birta.",
"status.quote_error.pending_approval": "Þessi færsla bíður eftir samþykki frá upprunalegum höfundi hennar.",
"status.quote_error.rejected": "Þessa færslu er ekki hægt að birta þar sem upphaflegur höfundur hennar leyfir ekki að vitnað sé til hennar.",
"status.quote_error.removed": "Þessi færsla var fjarlægð af höfundi hennar.",
"status.quote_error.unauthorized": "Þessa færslu er ekki hægt að birta þar sem þú hefur ekki heimild til að skoða hana.",
"status.quote_post_author": "Færsla frá {name}",
"status.read_more": "Lesa meira",
"status.reblog": "Endurbirting",
"status.reblog_private": "Endurbirta til upphaflegra lesenda",

View File

@@ -28,6 +28,9 @@
"account.edit_profile": "프로필 편집",
"account.enable_notifications": "@{name} 의 게시물 알림 켜기",
"account.endorse": "프로필에 추천하기",
"account.familiar_followers_many": "{name1}, {name2} 님 외 내가 아는 {othersCount, plural, other {#}} 명이 팔로우함",
"account.familiar_followers_one": "{name1} 님이 팔로우함",
"account.familiar_followers_two": "{name1}, {name2} 님이 팔로우함",
"account.featured": "추천",
"account.featured.accounts": "프로필",
"account.featured.hashtags": "해시태그",
@@ -406,8 +409,10 @@
"hashtag.counter_by_accounts": "{count, plural, other {참여자 {counter}명}}",
"hashtag.counter_by_uses": "{count, plural, other {게시물 {counter}개}}",
"hashtag.counter_by_uses_today": "오늘 {count, plural, other {{counter} 개의 게시물}}",
"hashtag.feature": "프로필에 추천하기",
"hashtag.follow": "해시태그 팔로우",
"hashtag.mute": "#{hashtag} 뮤트",
"hashtag.unfeature": "프로필에 추천하지 않기",
"hashtag.unfollow": "해시태그 팔로우 해제",
"hashtags.and_other": "…및 {count, plural,other {#개}}",
"hints.profiles.followers_may_be_missing": "이 프로필의 팔로워 목록은 일부 누락되었을 수 있습니다.",
@@ -858,6 +863,11 @@
"status.mute_conversation": "대화 뮤트",
"status.open": "상세 정보 표시",
"status.pin": "고정",
"status.quote_error.not_found": "이 게시물은 표시할 수 없습니다.",
"status.quote_error.pending_approval": "이 게시물은 원작자의 승인을 기다리고 있습니다.",
"status.quote_error.rejected": "이 게시물은 원작자가 인용을 허용하지 않았기 때문에 표시할 수 없습니다.",
"status.quote_error.removed": "이 게시물은 작성자에 의해 삭제되었습니다.",
"status.quote_error.unauthorized": "이 게시물은 권한이 없기 때문에 볼 수 없습니다.",
"status.read_more": "더 보기",
"status.reblog": "부스트",
"status.reblog_private": "원래의 수신자들에게 부스트",

View File

@@ -41,6 +41,7 @@
"account.followers": "Sekotāji",
"account.followers.empty": "Šim lietotājam vēl nav sekotāju.",
"account.followers_counter": "{count, plural, zero {{count} sekotāju} one {{count} sekotājs} other {{count} sekotāji}}",
"account.followers_you_know_counter": "{counter} jūs pazīstiet",
"account.following": "Seko",
"account.following_counter": "{count, plural, one {seko {counter}} other {seko {counter}}}",
"account.follows.empty": "Šis lietotājs pagaidām nevienam neseko.",
@@ -75,6 +76,7 @@
"account.statuses_counter": "{count, plural, zero {{counter} ierakstu} one {{counter} ieraksts} other {{counter} ieraksti}}",
"account.unblock": "Atbloķēt @{name}",
"account.unblock_domain": "Atbloķēt domēnu {domain}",
"account.unblock_domain_short": "Atbloķēt",
"account.unblock_short": "Atbloķēt",
"account.unendorse": "Neizcelt profilā",
"account.unfollow": "Pārstāt sekot",
@@ -367,6 +369,8 @@
"generic.saved": "Saglabāts",
"getting_started.heading": "Darba sākšana",
"hashtag.admin_moderation": "Atvērt #{name} satura pārraudzības saskarni",
"hashtag.browse": "Pārlūkot #{hashtag} ierakstus",
"hashtag.browse_from_account": "Pārlūkot @{name} #{hashtag} ierakstus",
"hashtag.column_header.tag_mode.all": "un {additional}",
"hashtag.column_header.tag_mode.any": "vai {additional}",
"hashtag.column_header.tag_mode.none": "bez {additional}",
@@ -381,6 +385,7 @@
"hashtag.counter_by_uses_today": "{count, plural, zero {{counter} ierakstu} one {{counter} ieraksts} other {{counter} ieraksti}} šodien",
"hashtag.feature": "Attēlot profilā",
"hashtag.follow": "Sekot tēmturim",
"hashtag.mute": "Apklusināt #{hashtag}",
"hashtag.unfeature": "Neattēlot profilā",
"hashtag.unfollow": "Pārstāt sekot tēmturim",
"hashtags.and_other": "… un {count, plural, other {vēl #}}",
@@ -448,6 +453,7 @@
"keyboard_shortcuts.toggle_hidden": "Rādīt/slēpt tekstu aiz satura brīdinājuma",
"keyboard_shortcuts.toggle_sensitivity": "Rādīt/slēpt multividi",
"keyboard_shortcuts.toot": "Uzsākt jaunu ierakstu",
"keyboard_shortcuts.translate": "tulkot ierakstu",
"keyboard_shortcuts.unfocus": "Atfokusēt veidojamā teksta/meklēšanas lauku",
"keyboard_shortcuts.up": "Pārvietoties augšup sarakstā",
"lightbox.close": "Aizvērt",
@@ -515,6 +521,7 @@
"notification.favourite": "{name} pievienoja izlasei Tavu ierakstu",
"notification.follow": "{name} uzsāka Tev sekot",
"notification.follow_request": "{name} nosūtīja Tev sekošanas pieprasījumu",
"notification.mentioned_you": "{name} pieminēja jūs",
"notification.moderation-warning.learn_more": "Uzzināt vairāk",
"notification.moderation_warning": "Ir saņemts satura pārraudzības brīdinājums",
"notification.moderation_warning.action_delete_statuses": "Daži no Taviem ierakstiem tika noņemti.",
@@ -741,6 +748,12 @@
"status.mute_conversation": "Apklusināt sarunu",
"status.open": "Izvērst šo ierakstu",
"status.pin": "Attēlot profilā",
"status.quote_error.not_found": "Šo ierakstu nevar parādīt.",
"status.quote_error.pending_approval": "Šis ieraksts gaida apstiprinājumu no tā autora.",
"status.quote_error.rejected": "Šo ierakstu nevar parādīt, jo tā autors neļauj to citēt.",
"status.quote_error.removed": "Šo ierakstu noņēma tā autors.",
"status.quote_error.unauthorized": "Šo ierakstu nevar parādīt, jo jums nav atļaujas to skatīt.",
"status.quote_post_author": "Publicēja {name}",
"status.read_more": "Lasīt vairāk",
"status.reblog": "Pastiprināt",
"status.reblog_private": "Pastiprināt ar sākotnējo redzamību",

View File

@@ -42,6 +42,7 @@
"account.followers": "Volgers",
"account.followers.empty": "Deze gebruiker heeft nog geen volgers of heeft deze verborgen.",
"account.followers_counter": "{count, plural, one {{counter} volger} other {{counter} volgers}}",
"account.followers_you_know_counter": "{counter} die je kent",
"account.following": "Volgend",
"account.following_counter": "{count, plural, one {{counter} volgend} other {{counter} volgend}}",
"account.follows.empty": "Deze gebruiker volgt nog niemand of heeft deze verborgen.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Gesprek negeren",
"status.open": "Volledig bericht tonen",
"status.pin": "Op profiel uitlichten",
"status.quote_error.not_found": "Dit bericht kan niet worden weergegeven.",
"status.quote_error.pending_approval": "Dit bericht is in afwachting van goedkeuring door de oorspronkelijke auteur.",
"status.quote_error.rejected": "Dit bericht kan niet worden weergegeven omdat de oorspronkelijke auteur niet toestaat dat het wordt geciteerd.",
"status.quote_error.removed": "Dit bericht is verwijderd door de auteur.",
"status.quote_error.unauthorized": "Dit bericht kan niet worden weergegeven omdat je niet bevoegd bent om het te bekijken.",
"status.quote_post_author": "Bericht van {name}",
"status.read_more": "Meer lezen",
"status.reblog": "Boosten",
"status.reblog_private": "Boost naar oorspronkelijke ontvangers",

View File

@@ -42,6 +42,7 @@
"account.followers": "Seguidores",
"account.followers.empty": "Ainda ninguém segue este utilizador.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.followers_you_know_counter": "{counter} que conhece",
"account.following": "A seguir",
"account.following_counter": "{count, plural, one {A seguir {counter}} other {A seguir {counter}}}",
"account.follows.empty": "Este utilizador ainda não segue ninguém.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Ocultar conversa",
"status.open": "Expandir esta publicação",
"status.pin": "Destacar no perfil",
"status.quote_error.not_found": "Esta publicação não pode ser exibida.",
"status.quote_error.pending_approval": "Esta publicação está a aguardar a aprovação do autor original.",
"status.quote_error.rejected": "Esta publicação não pode ser exibida porque o autor original não permite que seja citada.",
"status.quote_error.removed": "Esta publicação foi removida pelo seu autor.",
"status.quote_error.unauthorized": "Esta publicação não pode ser exibida porque o utilizador não está autorizado a visualizá-la.",
"status.quote_post_author": "Publicação de {name}",
"status.read_more": "Ler mais",
"status.reblog": "Impulsionar",
"status.reblog_private": "Impulsionar com a visibilidade original",

View File

@@ -86,10 +86,10 @@
"admin.dashboard.retention.average": "В среднем",
"admin.dashboard.retention.cohort": "Месяц регистрации",
"admin.dashboard.retention.cohort_size": "Новые пользователи",
"admin.impact_report.instance_accounts": "Профили учетных записей, которые будут удалены",
"admin.impact_report.instance_followers": "Подписчики, которых потеряют наши пользователи",
"admin.impact_report.instance_follows": "Подписчики, которых потеряют их пользователи",
"admin.impact_report.title": "Резюме воздействия",
"admin.impact_report.instance_accounts": "Число профилей, которые будут удалены",
"admin.impact_report.instance_followers": "Число подписчиков, которых лишатся наши пользователи",
"admin.impact_report.instance_follows": "Число подписчиков, которых лишатся их пользователи",
"admin.impact_report.title": "Сводка последствий",
"alert.rate_limited.message": "Подождите до {retry_time, time, medium}, прежде чем делать что-либо ещё.",
"alert.rate_limited.title": "Слишком много запросов",
"alert.unexpected.message": "Произошла непредвиденная ошибка.",
@@ -305,8 +305,8 @@
"emoji_button.search_results": "Результаты поиска",
"emoji_button.symbols": "Символы",
"emoji_button.travel": "Путешествия и места",
"empty_column.account_featured.me": "Вы ещё ничего не закрепили в своём профиле. Знаете ли вы, что вы можете рекомендовать в этом разделе свои посты, часто используемые вами хэштеги и даже профили друзей?",
"empty_column.account_featured.other": "{acct} ещё ничего не закрепил(а) в своём профиле. Знаете ли вы, что вы можете рекомендовать в этом разделе свои посты, часто используемые вами хэштеги и даже профили друзей?",
"empty_column.account_featured.me": "Вы ещё ничего не закрепили в своём профиле. Знаете ли вы, что вы можете рекомендовать в этом разделе свои посты, часто используемые вами хештеги и даже профили друзей?",
"empty_column.account_featured.other": "{acct} ещё ничего не закрепил(а) в своём профиле. Знаете ли вы, что вы можете рекомендовать в этом разделе свои посты, часто используемые вами хештеги и даже профили друзей?",
"empty_column.account_featured_other.unknown": "Этот пользователь ещё ничего не закрепил в своём профиле.",
"empty_column.account_hides_collections": "Пользователь предпочёл не раскрывать эту информацию",
"empty_column.account_suspended": "Учётная запись заблокирована",
@@ -357,7 +357,7 @@
"filter_modal.select_filter.title": "Фильтровать этот пост",
"filter_modal.title.status": "Фильтровать пост",
"filter_warning.matches_filter": "Соответствует фильтру «<span>{title}</span>»",
"filtered_notifications_banner.pending_requests": "От {count, plural, =0 {не известных вам людей} one {# возможно вам известного человека} other {# возможно вам известных человек}}",
"filtered_notifications_banner.pending_requests": "От {count, plural, =0 {не знакомых вам людей} one {# человека, которого вы можете знать} other {# человек, которых вы можете знать}}",
"filtered_notifications_banner.title": "Отфильтрованные уведомления",
"firehose.all": "Всё вместе",
"firehose.local": "Этот сервер",
@@ -392,8 +392,8 @@
"generic.saved": "Сохранено",
"getting_started.heading": "Добро пожаловать",
"hashtag.admin_moderation": "Открыть интерфейс модератора для #{name}",
"hashtag.browse": "Обзор постов с хэштегом #{hashtag}",
"hashtag.browse_from_account": "Обзор постов от @{name} с хэштегом #{hashtag}",
"hashtag.browse": "Обзор постов с хештегом #{hashtag}",
"hashtag.browse_from_account": "Обзор постов от @{name} с хештегом #{hashtag}",
"hashtag.column_header.tag_mode.all": "и {additional}",
"hashtag.column_header.tag_mode.any": "или {additional}",
"hashtag.column_header.tag_mode.none": "без {additional}",
@@ -454,7 +454,7 @@
"interaction_modal.title.reblog": "Продвинуть пост {name}",
"interaction_modal.title.reply": "Ответить на пост {name}",
"interaction_modal.title.vote": "Голосовать в опросе {name}",
"interaction_modal.username_prompt": "Например {example}",
"interaction_modal.username_prompt": "Например, {example}",
"intervals.full.days": "{number, plural, one {# день} few {# дня} other {# дней}}",
"intervals.full.hours": "{number, plural, one {# час} few {# часа} other {# часов}}",
"intervals.full.minutes": "{number, plural, one {# минута} few {# минуты} other {# минут}}",

View File

@@ -42,6 +42,7 @@
"account.followers": "Ndjekës",
"account.followers.empty": "Këtë përdorues ende se ndjek kush.",
"account.followers_counter": "{count, plural, one {{counter} ndjekës} other {{counter} ndjekës}}",
"account.followers_you_know_counter": "{counter} që njihni",
"account.following": "Ndjekje",
"account.following_counter": "{count, plural, one {{counter} i ndjekur} other {{counter} të ndjekur}}",
"account.follows.empty": "Ky përdorues ende sndjek kënd.",
@@ -858,6 +859,12 @@
"status.mute_conversation": "Heshtoje bisedën",
"status.open": "Zgjeroje këtë mesazh",
"status.pin": "Pasqyrojeni në profil",
"status.quote_error.not_found": "Ky postim smund të shfaqet.",
"status.quote_error.pending_approval": "Ky postim është në pritje të miratimit nga autori origjinal.",
"status.quote_error.rejected": "Ky postim smund të shfaqet, ngaqë autori origjinal nuk lejon citim të tij.",
"status.quote_error.removed": "Ky postim u hoq nga autori i tij.",
"status.quote_error.unauthorized": "Ky postim smund të shfaqet, ngaqë sjeni i autorizuar ta shihni.",
"status.quote_post_author": "Postim nga {name}",
"status.read_more": "Lexoni më tepër",
"status.reblog": "Përforcojeni",
"status.reblog_private": "Përforcim për publikun origjinal",

View File

@@ -42,6 +42,7 @@
"account.followers": "Takipçi",
"account.followers.empty": "Henüz kimse bu kullanıcıyı takip etmiyor.",
"account.followers_counter": "{count, plural, one {{counter} takipçi} other {{counter} takipçi}}",
"account.followers_you_know_counter": "bildiğiniz {counter}",
"account.following": "Takip Ediliyor",
"account.following_counter": "{count, plural, one {{counter} takip edilen} other {{counter} takip edilen}}",
"account.follows.empty": "Bu kullanıcı henüz kimseyi takip etmiyor.",
@@ -863,6 +864,12 @@
"status.mute_conversation": "Sohbeti sessize al",
"status.open": "Bu gönderiyi genişlet",
"status.pin": "Profilimde öne çıkar",
"status.quote_error.not_found": "Bu gönderi görüntülenemez.",
"status.quote_error.pending_approval": "Bu gönderi özgün yazarın onayını bekliyor.",
"status.quote_error.rejected": "Bu gönderi, özgün yazar alıntılanmasına izin vermediği için görüntülenemez.",
"status.quote_error.removed": "Bu gönderi yazarı tarafından kaldırıldı.",
"status.quote_error.unauthorized": "Bu gönderiyi, yetkiniz olmadığı için görüntüleyemiyorsunuz.",
"status.quote_post_author": "{name} gönderisi",
"status.read_more": "Devamını okuyun",
"status.reblog": "Yeniden paylaş",
"status.reblog_private": "Özgün görünürlük ile yeniden paylaş",

View File

@@ -843,6 +843,7 @@
"status.mute": "Приховати @{name}",
"status.mute_conversation": "Ігнорувати розмову",
"status.open": "Розгорнути допис",
"status.quote_post_author": "@{name} опублікував допис",
"status.read_more": "Дізнатися більше",
"status.reblog": "Поширити",
"status.reblog_private": "Поширити для початкової аудиторії",

View File

@@ -42,6 +42,7 @@
"account.followers": "跟隨者",
"account.followers.empty": "尚未有人跟隨這位使用者。",
"account.followers_counter": "被 {count, plural, other {{count} 人}}跟隨",
"account.followers_you_know_counter": "{counter} 位您知道的跟隨者",
"account.following": "跟隨中",
"account.following_counter": "正在跟隨 {count,plural,other {{count} 人}}",
"account.follows.empty": "這位使用者尚未跟隨任何人。",
@@ -863,6 +864,12 @@
"status.mute_conversation": "靜音對話",
"status.open": "展開此嘟文",
"status.pin": "於個人檔案推薦",
"status.quote_error.not_found": "這則嘟文無法被顯示。",
"status.quote_error.pending_approval": "此嘟文正在等待原作者審核。",
"status.quote_error.rejected": "由於原作者不允許引用,此嘟文無法被顯示。",
"status.quote_error.removed": "此嘟文已被其作者移除。",
"status.quote_error.unauthorized": "由於您未被授權檢視,此嘟文無法被顯示。",
"status.quote_post_author": "由 {name} 發嘟",
"status.read_more": "閱讀更多",
"status.reblog": "轉嘟",
"status.reblog_private": "依照原嘟可見性轉嘟",

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm80-160h280v-80H280v80Zm0-160h400v-80H280v80Zm0-160h400v-80H280v80Z"/></svg>

After

Width:  |  Height:  |  Size: 292 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-280h280v-80H280v80Zm0-160h400v-80H280v80Zm0-160h400v-80H280v80Zm-80 480q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"/></svg>

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -1 +1,12 @@
Files in this directory are Material Symbols icons fetched using the `icons:download` task.
Files in this directory are Material Symbols icons fetched using the `icons:download` rake task (see `/lib/tasks/icons.rake`).
To add another icon, follow these steps:
- Determine the name of the Material Symbols icon you want to download.
You can find a searchable overview of all icons on [https://fonts.google.com/icons].
Click on the icon you want to use and find the icon name towards the bottom of the slide-out panel (it'll be something like `icon_name`)
- Import the icon in your React component using the following format:
`import IconName from '@/material-icons/400-24px/icon_name.svg?react';`
- Run `RAILS_ENV=development rails icons:download` to download any newly imported icons.
The import should now work and the icon should appear when passed to the `<Icon icon={IconName} /> component

View File

@@ -1421,6 +1421,10 @@ body > [data-popper-placement] {
}
}
.status--has-quote .quote-inline {
display: none;
}
.status {
padding: 16px;
min-height: 54px;
@@ -1491,8 +1495,12 @@ body > [data-popper-placement] {
}
}
&--is-quote {
border: none;
}
&--in-thread {
$thread-margin: 46px + 10px;
--thread-margin: calc(46px + 8px);
border-bottom: 0;
@@ -1508,16 +1516,16 @@ body > [data-popper-placement] {
.hashtag-bar,
.content-warning,
.filter-warning {
margin-inline-start: $thread-margin;
width: calc(100% - $thread-margin);
margin-inline-start: var(--thread-margin);
width: calc(100% - var(--thread-margin));
}
.more-from-author {
width: calc(100% - $thread-margin + 2px);
width: calc(100% - var(--thread-margin) + 2px);
}
.status__content__read-more-button {
margin-inline-start: $thread-margin;
margin-inline-start: var(--thread-margin);
}
}
@@ -1873,6 +1881,81 @@ body > [data-popper-placement] {
}
}
.status__quote {
position: relative;
margin-block-start: 16px;
margin-inline-start: 36px;
border-radius: 8px;
color: var(--nested-card-text);
background: var(--nested-card-background);
border: var(--nested-card-border);
@media screen and (min-width: $mobile-breakpoint) {
margin-inline-start: 56px;
}
}
.status__quote--error {
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
font-size: 15px;
}
.status__quote-author-button {
position: relative;
overflow: hidden;
display: inline-flex;
width: auto;
margin-block-start: 10px;
padding: 5px 12px;
align-items: center;
gap: 6px;
font-family: inherit;
font-size: 14px;
font-weight: 700;
line-height: normal;
letter-spacing: 0;
text-decoration: none;
color: $highlight-text-color;
background: var(--nested-card-background);
border: var(--nested-card-border);
border-radius: 4px;
&:active,
&:focus,
&:hover {
border-color: lighten($highlight-text-color, 4%);
color: lighten($highlight-text-color, 4%);
}
&:focus-visible {
outline: $ui-button-icon-focus-outline;
}
}
.status__quote-icon {
position: absolute;
inset-block-start: 18px;
inset-inline-start: -40px;
display: block;
width: 26px;
height: 26px;
padding: 5px;
color: #6a49ba;
z-index: 10;
.status__quote--error & {
inset-block-start: 50%;
transform: translateY(-50%);
}
@media screen and (min-width: $mobile-breakpoint) {
inset-inline-start: -50px;
}
}
.detailed-status__link {
display: inline-flex;
align-items: center;
@@ -2170,14 +2253,18 @@ a .account__avatar {
.avatar-group {
display: flex;
gap: 8px;
flex-wrap: wrap;
--avatar-height: 28px;
&:not(.avatar-group--compact) {
gap: 8px;
flex-wrap: wrap;
height: var(--avatar-height);
overflow-y: clip;
}
}
.avatar-group--compact {
gap: 0;
flex-wrap: nowrap;
& > :not(:first-child) {
margin-inline-start: -12px;
}
@@ -2306,11 +2393,6 @@ a.account__display-name {
}
}
.status__avatar {
width: 46px;
height: 46px;
}
.muted {
.status__content,
.status__content p,
@@ -10334,7 +10416,8 @@ noscript {
padding: 15px;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
gap: 4px 8px;
.logo {
width: 16px;
@@ -10428,12 +10511,6 @@ noscript {
overflow: hidden;
container-type: inline-size;
@container (width < 350px) {
&__header time {
display: none;
}
}
&__header {
display: flex;
flex-direction: column;
@@ -10446,7 +10523,8 @@ noscript {
&__label {
display: flex;
gap: 8px;
flex-wrap: wrap;
gap: 2px 8px;
font-size: 15px;
line-height: 22px;
color: $darker-text-color;
@@ -10464,6 +10542,13 @@ noscript {
time {
color: $dark-text-color;
}
@container (width < 350px) {
time,
&-separator {
display: none;
}
}
}
}
@@ -10515,6 +10600,7 @@ noscript {
line-height: 22px;
color: $darker-text-color;
-webkit-line-clamp: 4;
line-clamp: 4;
-webkit-box-orient: vertical;
max-height: none;
overflow: hidden;
@@ -10670,7 +10756,15 @@ noscript {
color: inherit;
}
&__number {
&__numbers,
&__familiar-followers {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
&__numbers {
font-size: 15px;
line-height: 22px;
color: $secondary-text-color;
@@ -10818,9 +10912,9 @@ noscript {
.content-warning {
display: block;
box-sizing: border-box;
background: rgba($ui-highlight-color, 0.05);
color: $secondary-text-color;
border: 1px solid rgba($ui-highlight-color, 0.15);
background: var(--nested-card-background);
color: var(--nested-card-text);
border: var(--nested-card-border);
border-radius: 8px;
padding: 8px (5px + 8px);
position: relative;

View File

@@ -27,6 +27,10 @@
--rich-text-container-color: rgba(87, 24, 60, 100%);
--rich-text-text-color: rgba(255, 175, 212, 100%);
--rich-text-decorations-color: rgba(128, 58, 95, 100%);
--nested-card-background: color(from #{$ui-highlight-color} srgb r g b / 5%);
--nested-card-text: #{$secondary-text-color};
--nested-card-border: 1px solid
color(from #{$ui-highlight-color} srgb r g b / 15%);
--input-placeholder-color: #{$dark-text-color};
--input-background-color: var(--surface-variant-background-color);
--on-input-color: #{$secondary-text-color};

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Fasp::Request
COVERED_COMPONENTS = %w(@method @target-uri content-digest).freeze
def initialize(provider)
@provider = provider
end
@@ -23,55 +25,36 @@ class Fasp::Request
url = @provider.url(path)
body = body.present? ? body.to_json : ''
headers = request_headers(verb, url, body)
response = HTTP.headers(headers).send(verb, url, body:)
key = Linzer.new_ed25519_key(@provider.server_private_key_pem, @provider.remote_identifier)
response = HTTP
.headers(headers)
.use(http_signature: { key:, covered_components: COVERED_COMPONENTS })
.send(verb, url, body:)
validate!(response)
response.parse if response.body.present?
end
def request_headers(verb, url, body = '')
result = {
def request_headers(_verb, _url, body = '')
{
'accept' => 'application/json',
'content-type' => 'application/json',
'content-digest' => content_digest(body),
}
result.merge(signature_headers(verb, url, result))
end
def content_digest(body)
"sha-256=:#{OpenSSL::Digest.base64digest('sha256', body || '')}:"
end
def signature_headers(verb, url, headers)
linzer_request = Linzer.new_request(verb, url, {}, headers)
message = Linzer::Message.new(linzer_request)
key = Linzer.new_ed25519_key(@provider.server_private_key_pem, @provider.remote_identifier)
signature = Linzer.sign(key, message, %w(@method @target-uri content-digest))
Linzer::Signer.send(:populate_parameters, key, {})
signature.to_h
end
def validate!(response)
content_digest_header = response.headers['content-digest']
raise Mastodon::SignatureVerificationError, 'content-digest missing' if content_digest_header.blank?
raise Mastodon::SignatureVerificationError, 'content-digest does not match' if content_digest_header != content_digest(response.body)
raise Mastodon::SignatureVerificationError, 'signature-input is missing' if response.headers['signature-input'].blank?
signature_input = response.headers['signature-input']&.encode('UTF-8')
raise Mastodon::SignatureVerificationError, 'signature-input is missing' if signature_input.blank?
linzer_response = Linzer.new_response(
response.body,
response.status,
{
'content-digest' => content_digest_header,
'signature-input' => signature_input,
'signature' => response.headers['signature'],
}
)
message = Linzer::Message.new(linzer_response)
key = Linzer.new_ed25519_public_key(@provider.provider_public_key_pem)
signature = Linzer::Signature.build(message.headers)
Linzer.verify(key, message, signature)
Linzer.verify!(response, key:)
end
end

View File

@@ -42,6 +42,6 @@ class Rule < ApplicationRecord
def translation_for(locale)
@cached_translations ||= {}
@cached_translations[locale] ||= translations.find_by(language: locale) || RuleTranslation.new(language: locale, text: text, hint: hint)
@cached_translations[locale] ||= translations.where(language: [locale, locale.to_s.split('-').first]).order('length(language) desc').first || RuleTranslation.new(language: locale, text: text, hint: hint)
end
end

View File

@@ -73,7 +73,6 @@ class User < ApplicationRecord
ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days.freeze
devise :two_factor_authenticatable,
otp_secret_encryption_key: Rails.configuration.x.otp_secret,
otp_secret_length: 32
devise :two_factor_backupable,

View File

@@ -4,13 +4,11 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService
include JsonLdHelper
def call(account, **options)
return if account.featured_collection_url.blank? || account.suspended? || account.local?
return if (account.featured_collection_url.blank? && options[:collection].blank?) || account.suspended? || account.local?
@account = account
@options = options
@json = fetch_resource(@account.featured_collection_url, true, local_follower)
return unless supported_context?(@json)
@json = fetch_collection(options[:collection].presence || @account.featured_collection_url)
process_items(collection_items(@json))
end

View File

@@ -57,7 +57,7 @@ class ActivityPub::ProcessAccountService < BaseService
after_suspension_change! if suspension_changed?
unless @options[:only_key] || @account.suspended?
check_featured_collection! if @account.featured_collection_url.present?
check_featured_collection! if @json['featured'].present?
check_featured_tags_collection! if @json['featuredTags'].present?
check_links! if @account.fields.any?(&:requires_verification?)
end
@@ -121,7 +121,7 @@ class ActivityPub::ProcessAccountService < BaseService
end
def set_immediate_attributes!
@account.featured_collection_url = @json['featured'] || ''
@account.featured_collection_url = valid_collection_uri(@json['featured'])
@account.display_name = @json['name'] || ''
@account.note = @json['summary'] || ''
@account.locked = @json['manuallyApprovesFollowers'] || false
@@ -186,7 +186,7 @@ class ActivityPub::ProcessAccountService < BaseService
end
def check_featured_collection!
ActivityPub::SynchronizeFeaturedCollectionWorker.perform_async(@account.id, { 'hashtag' => @json['featuredTags'].blank?, 'request_id' => @options[:request_id] })
ActivityPub::SynchronizeFeaturedCollectionWorker.perform_async(@account.id, { 'hashtag' => @json['featuredTags'].blank?, 'collection' => @json['featured'], 'request_id' => @options[:request_id] })
end
def check_featured_tags_collection!

View File

@@ -19,7 +19,7 @@ class FetchLinkCardService < BaseService
@status = status
@original_url = parse_urls
return if @original_url.nil? || @status.with_preview_card?
return if @original_url.nil? || @status.with_preview_card? || @status.with_media? || @status.quote.present?
@url = @original_url.to_s

View File

@@ -28,6 +28,7 @@
= theme_style_tags current_theme
= vite_client_tag
= vite_react_refresh_tag
= vite_polyfills_tag
-# Needed for the wicg-inert polyfill. It needs to be on it's own <style> tag, with this `id`
= vite_stylesheet_tag 'styles/entrypoints/inert.scss', media: 'all', id: 'inert-style' # TODO: flavour
= flavoured_vite_typescript_tag 'common.ts', crossorigin: 'anonymous'

View File

@@ -13,6 +13,7 @@
= vite_client_tag
= vite_react_refresh_tag
= vite_polyfills_tag
= theme_style_tags current_theme
= vite_preload_file_tag "mastodon/locales/#{I18n.locale}.json" # TODO: fix preload for flavour
= render_initial_state

View File

@@ -7,6 +7,7 @@
%meta{ content: 'width=device-width,initial-scale=1', name: 'viewport' }/
= vite_client_tag
= vite_react_refresh_tag
= vite_polyfills_tag
= theme_style_tags current_theme
= flavoured_vite_typescript_tag 'error.ts', crossorigin: 'anonymous'
%body.error

View File

@@ -6,7 +6,7 @@ class ActivityPub::SynchronizeFeaturedCollectionWorker
sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.day.to_i
def perform(account_id, options = {})
options = { note: true, hashtag: false }.deep_merge(options.deep_symbolize_keys)
options = { note: true, hashtag: false }.deep_merge(options.symbolize_keys)
ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id), **options)
rescue ActiveRecord::RecordNotFound

View File

@@ -84,9 +84,6 @@ Rails.application.configure do
# Otherwise, use letter_opener, which launches a browser window to view sent mail.
config.action_mailer.delivery_method = ENV['HEROKU'] || ENV['VAGRANT'] || ENV['REMOTE_DEV'] ? :letter_opener_web : :letter_opener
# TODO: Remove once devise-two-factor data migration complete
config.x.otp_secret = ENV.fetch('OTP_SECRET', '1fc2b87989afa6351912abeebe31ffc5c476ead9bf8b3d74cbc4a302c7b69a45b40b1bbef3506ddad73e942e15ed5ca4b402bf9a66423626051104f4b5f05109')
# Raise error when a before_action's only/except options reference missing actions.
config.action_controller.raise_on_missing_callback_actions = true

View File

@@ -0,0 +1,32 @@
# frozen_string_literal: true
require 'linzer/http/signature_feature'
require 'linzer/message/adapter/http_gem/response'
module Linzer::Message::Adapter
module ActionDispatch
class Response < Linzer::Message::Adapter::Abstract
def initialize(operation, **_options) # rubocop:disable Lint/MissingSuper
@operation = operation
end
def header(name)
@operation.headers[name]
end
def attach!(signature)
signature.to_h.each { |h, v| @operation.headers[h] = v }
end
# Incomplete, but sufficient for FASP
def [](field_name)
return @operation.status if field_name == '@status'
@operation.headers[field_name]
end
end
end
end
Linzer::Message.register_adapter(HTTP::Response, Linzer::Message::Adapter::HTTPGem::Response)
Linzer::Message.register_adapter(ActionDispatch::Response, Linzer::Message::Adapter::ActionDispatch::Response)

View File

@@ -42,7 +42,7 @@ if ENV['S3_ENABLED'] == 'true'
s3_protocol = ENV.fetch('S3_PROTOCOL') { 'https' }
s3_hostname = ENV.fetch('S3_HOSTNAME') { "s3-#{s3_region}.amazonaws.com" }
Paperclip::Attachment.default_options[:path] = ENV.fetch('S3_KEY_PREFIX') + "/#{PATH}" if ENV.has_key?('S3_KEY_PREFIX')
Paperclip::Attachment.default_options[:path] = ENV.fetch('S3_KEY_PREFIX') + "/#{PATH}" if ENV.key?('S3_KEY_PREFIX')
Paperclip::Attachment.default_options.merge!(
storage: :s3,
@@ -74,7 +74,7 @@ if ENV['S3_ENABLED'] == 'true'
Paperclip::Attachment.default_options[:s3_permissions] = ->(*) {} if ENV['S3_PERMISSION'] == ''
if ENV.has_key?('S3_ENDPOINT')
if ENV.key?('S3_ENDPOINT')
Paperclip::Attachment.default_options[:s3_options].merge!(
endpoint: ENV['S3_ENDPOINT'],
force_path_style: ENV['S3_OVERRIDE_PATH_STYLE'] != 'true'
@@ -83,14 +83,14 @@ if ENV['S3_ENABLED'] == 'true'
Paperclip::Attachment.default_options[:url] = ':s3_path_url'
end
if ENV.has_key?('S3_ALIAS_HOST') || ENV.has_key?('S3_CLOUDFRONT_HOST')
if ENV.key?('S3_ALIAS_HOST') || ENV.key?('S3_CLOUDFRONT_HOST')
Paperclip::Attachment.default_options.merge!(
url: ':s3_alias_url',
s3_host_alias: ENV['S3_ALIAS_HOST'] || ENV['S3_CLOUDFRONT_HOST']
)
end
Paperclip::Attachment.default_options[:s3_headers]['X-Amz-Storage-Class'] = ENV['S3_STORAGE_CLASS'] if ENV.has_key?('S3_STORAGE_CLASS')
Paperclip::Attachment.default_options[:s3_headers]['X-Amz-Storage-Class'] = ENV['S3_STORAGE_CLASS'] if ENV.key?('S3_STORAGE_CLASS')
# Some S3-compatible providers might not actually be compatible with some APIs
# used by kt-paperclip, see https://github.com/mastodon/mastodon/issues/16822
@@ -153,7 +153,7 @@ elsif ENV['AZURE_ENABLED'] == 'true'
container: ENV['AZURE_CONTAINER_NAME'],
}
)
if ENV.has_key?('AZURE_ALIAS_HOST')
if ENV.key?('AZURE_ALIAS_HOST')
Paperclip::Attachment.default_options.merge!(
url: ':azure_alias_url',
azure_host_alias: ENV['AZURE_ALIAS_HOST']

View File

@@ -271,10 +271,13 @@ br:
view_devops: DevOps
title: Rolloù
rules:
add_translation: Ouzhpennañ un droidigezh
delete: Dilemel
edit: Kemmañ ar reolenn
move_down: D'an traoñ
move_up: D'ar c'hrec'h
translation: Troidigezh
translations: Troidigezhioù
settings:
about:
title: Diwar-benn

View File

@@ -5,7 +5,7 @@ cs:
contact_missing: Nenastaveno
contact_unavailable: Neuvedeno
hosted_on: Mastodon na doméně %{domain}
title: O aplikaci
title: O službě
accounts:
followers:
few: Sledující
@@ -814,11 +814,17 @@ cs:
title: Role
rules:
add_new: Přidat pravidlo
add_translation: Přidat překlad
delete: Smazat
description_html: Přestože většina tvrdí, že četla a souhlasí s podmínkami služby, lidé je obvykle nepročtou dříve, než vznikne problém. <strong>Usnadněte prohlížení pravidel vašeho serveru jejich poskytnutím v odrážkovém seznamu.</strong> Snažte se držet jednotlivá pravidla krátká a jednoduchá, ale zároveň je nerozdělovat do mnoha samostatných položek.
edit: Upravit pravidlo
empty: Zatím nebyla definována žádná pravidla serveru.
move_down: Posunout dolů
move_up: Přesunout nahoru
title: Pravidla serveru
translation: Překlad
translations: Překlady
translations_explanation: Volitelně můžete též přidat překlady pravidel. Výchozí hodnota bude zobrazena, pokud není k dispozici žádná přeložená verze. Vždy se ujistěte, že jakýkoliv překlad je synchronizován s výchozí hodnotou.
settings:
about:
manage_rules: Spravovat pravidla serveru

View File

@@ -842,11 +842,17 @@ cy:
title: Rolau
rules:
add_new: Ychwanegu rheol
add_translation: Ychwanegu cyfieithiad
delete: Dileu
description_html: Er bod y rhan fwyaf yn honni eu bod wedi darllen ac yn cytuno i'r telerau gwasanaeth, fel arfer nid yw pobl yn darllen y cyfan tan ar ôl i broblem godi. <strong>Gwnewch hi'n haws i bobl weld rheolau eich gweinydd yn fras trwy eu darparu mewn rhestr pwyntiau bwled fflat.</strong> Ceisiwch gadw'r rheolau unigol yn fyr ac yn syml, ond ceisiwch beidio â'u rhannu'n nifer o eitemau ar wahân chwaith.
edit: Golygu rheol
empty: Nid oes unrhyw reolau gweinydd wedi'u diffinio eto.
move_down: Symud i lawr
move_up: Symud i fyny
title: Rheolau'r gweinydd
translation: Cyfieithiad
translations: Cyfieithiadau
translations_explanation: Gallwch ychwanegu cyfieithiadau ar gyfer y rheolau yn ôl eich dewis. Bydd y gwerth rhagosodedig yn cael ei ddangos os nad oes fersiwn wedi'i chyfieithu ar gael. Gwnewch yn siŵr bob amser bod unrhyw gyfieithiad sy'n cael ei ddarparu'n gyson â'r gwerth rhagosodedig.
settings:
about:
manage_rules: Rheoli rheolau gweinydd

View File

@@ -786,6 +786,7 @@ da:
title: Roller
rules:
add_new: Tilføj regel
add_translation: Tilføj oversættelse
delete: Slet
description_html: Mens de fleste hævder at have læst og accepteret tjenestevilkårene, så læser folk normalt ikke disse, før et problem er opstået. <strong>Gør det lettere med ét blik at se din servers regler ved at opliste disse på en punktliste.</strong> Forsøg at holde individuelle regler korte og enkle, men undgå også at opdele dem i mange separate underpunkter.
edit: Redigér regel
@@ -793,6 +794,9 @@ da:
move_down: Flyt ned
move_up: Flyt op
title: Serverregler
translation: Oversættelse
translations: Oversættelser
translations_explanation: Man kan valgfrit tilføje oversættelser af reglerne. Standardindholdet vil fremgå, såfremt ingen oversat version findes. Tilsikr venligst, at enhver given oversættelse er synket med standardindholdet.
settings:
about:
manage_rules: Håndtér serverregler

View File

@@ -783,11 +783,17 @@ de:
title: Rollen
rules:
add_new: Regel hinzufügen
add_translation: Übersetzung hinzufügen
delete: Löschen
description_html: Während die meisten behaupten, die Nutzungsbedingungen tatsächlich gelesen zu haben, bekommen viele sie erst nach einem Problem mit. <strong>Vereinfache und reduziere daher die Serverregeln mit Stichpunkten.</strong> Versuche dabei, die einzelnen Vorgaben kurz und einfach zu halten, aber vermeide, sie in viele verschiedene Elemente aufzuteilen.
edit: Regel bearbeiten
empty: Es wurden bisher keine Serverregeln definiert.
move_down: Abwärts
move_up: Aufwärts
title: Serverregeln
translation: Übersetzung
translations: Übersetzungen
translations_explanation: Du kannst Serverregeln übersetzen. Der Standardwert wird angezeigt, wenn keine übersetzte Version verfügbar ist. Bitte vergewissere dich, dass jede Übersetzung mit dem Standardwert übereinstimmt.
settings:
about:
manage_rules: Serverregeln verwalten
@@ -796,7 +802,7 @@ de:
title: Über
allow_referrer_origin:
desc: Klicken Nutzer*innen auf Links zu externen Seiten, kann der Browser die Adresse deines Mastodon-Servers als Referrer (Verweis) übermitteln. Diese Option sollte deaktiviert werden, wenn Nutzer*innen dadurch eindeutig identifiziert würden z. B. wenn es sich um einen privaten Mastodon-Server handelt.
title: Externen Seiten erlauben, diesen Mastodon-Server als Traffic-Quelle zu sehen
title: Externen Seiten erlauben, diesen Mastodon-Server als Quelle zu sehen
appearance:
preamble: Passe das Webinterface von Mastodon an.
title: Design

View File

@@ -786,6 +786,7 @@ es-AR:
title: Roles
rules:
add_new: Agregar regla
add_translation: Agregar traducción
delete: Eliminar
description_html: Aunque la mayoría afirma haber leído y aceptado los términos del servicio, normalmente la gente no los revisa hasta después de que surge un problema. <strong>Hacé que sea más fácil ver las reglas de tu servidor, de un vistazo, disponiéndolas en una lista por puntos.</strong> Tratá de hacer cada regla corta y sencilla, pero no de dividirlas en muchos temas individuales.
edit: Editar regla
@@ -793,6 +794,9 @@ es-AR:
move_down: Bajar
move_up: Subir
title: Reglas del servidor
translation: Traducción
translations: Traducciones
translations_explanation: Opcionalmente, podés agregar traducciones para las reglas. El valor predeterminado se mostrará si no hay una versión traducida disponible. Por favor, asegurate siempre de que cualquier traducción proporcionada esté sincronizada con el valor predeterminado.
settings:
about:
manage_rules: Administrar reglas del servidor

View File

@@ -786,11 +786,17 @@ es-MX:
title: Roles
rules:
add_new: Añadir norma
add_translation: Añadir traducción
delete: Eliminar
description_html: Aunque la mayoría afirma haber leído y estar de acuerdo con los términos de servicio, la gente normalmente no los lee hasta después de que surja algún problema. <strong>Haz que sea más fácil ver las normas de tu servidor de un vistazo estipulándolas en una lista de puntos.</strong> Intenta que cada norma sea corta y sencilla, pero sin estar divididas en muchos puntos.
edit: Editar norma
empty: Aún no se han definido las normas del servidor.
move_down: Mover hacia abajo
move_up: Mover hacia arriba
title: Normas del servidor
translation: Traducción
translations: Traducciones
translations_explanation: Opcionalmente, puedes añadir traducciones para las reglas. Se mostrará el valor por defecto si no hay ninguna versión traducida disponible. Por favor, asegúrate siempre de que cualquier traducción proporcionada esté sincronizada con el valor por defecto.
settings:
about:
manage_rules: Administrar reglas del servidor
@@ -799,6 +805,7 @@ es-MX:
title: Acerca de
allow_referrer_origin:
desc: Cuando tus usuarios cliquen en enlaces a sitios externos, su navegador podría enviar la dirección de su servidor de Mastodon como referencia. Deshabilita esto si identifica a tus usuarios unívocamente, por ejemplo, si este es un servidor de Mastodon personal.
title: Permitir que sitios externos vean su servidor Mastodon como fuente de tráfico
appearance:
preamble: Personalizar la interfaz web de Mastodon.
title: Apariencia

View File

@@ -786,11 +786,17 @@ es:
title: Roles
rules:
add_new: Añadir norma
add_translation: Añadir traducción
delete: Eliminar
description_html: Aunque la mayoría afirma haber leído y estar de acuerdo con los términos de servicio, la gente normalmente no los lee hasta después de que surja algún problema. <strong>Haz que sea más fácil ver las normas de tu servidor de un vistazo estipulándolas en una lista de puntos.</strong> Intenta que cada norma sea corta y sencilla, pero sin estar divididas en muchos puntos.
edit: Editar norma
empty: Aún no se han definido las normas del servidor.
move_down: Mover hacia abajo
move_up: Mover hacia arriba
title: Normas del servidor
translation: Traducción
translations: Traducciones
translations_explanation: Opcionalmente, puedes añadir traducciones para las reglas. Se mostrará el valor por defecto si no hay ninguna versión traducida disponible. Por favor, asegúrate siempre de que cualquier traducción proporcionada esté sincronizada con el valor por defecto.
settings:
about:
manage_rules: Administrar reglas del servidor
@@ -799,6 +805,7 @@ es:
title: Acerca de
allow_referrer_origin:
desc: Cuando tus usuarios cliquen en enlaces a sitios externos, su navegador podría enviar la dirección de su servidor de Mastodon como referencia. Deshabilita esto si identifica a tus usuarios unívocamente, por ejemplo, si este es un servidor de Mastodon personal.
title: Permitir que sitios externos vean su servidor Mastodon como fuente de tráfico
appearance:
preamble: Personalizar la interfaz web de Mastodon.
title: Apariencia

View File

@@ -486,13 +486,17 @@ fi:
created_at: Luotu
delete: Poista
ip: IP-osoite
request_body: Pyynnön sisältö
title: Aloita takaisinkutsujen vianetsintä
providers:
active: Käytössä
base_url: Perus-URL
callback: Takaisinkutsu
delete: Poista
edit: Muokkaa palveluntarjoajaa
finish_registration: Viimeistele rekisteröinti
name: Nimi
providers: Palveluntarjoajat
public_key_fingerprint: Julkisen avaimen sormenjälki
registration_requested: Rekisteröintiä pyydetty
registrations:
@@ -780,6 +784,7 @@ fi:
title: Roolit
rules:
add_new: Lisää sääntö
add_translation: Lisää käännös
delete: Poista
description_html: Vaikka useimmat väittävät, että ovat lukeneet ja hyväksyneet käyttöehdot, niin yleensä ihmiset eivät lue niitä läpi ennen kuin ilmenee ongelma. <strong>Helpota palvelimen sääntöjen näkemistä yhdellä silmäyksellä tarjoamalla ne tiiviissä luettelossa.</strong> Yritä pitää säännöt lyhyinä ja yksinkertaisina, mutta yritä olla jakamatta niitä useisiin erillisiin kohtiin.
edit: Muokkaa sääntöä
@@ -787,6 +792,9 @@ fi:
move_down: Siirrä alaspäin
move_up: Siirrä ylöspäin
title: Palvelimen säännöt
translation: Käännös
translations: Käännökset
translations_explanation: Voit halutessasi lisätä säännöille käännöksiä. Jos käännettyä versiota ei ole saatavilla, näytetään oletusarvo. Varmista aina, että tarjoamasi käännös on linjassa oletusarvon kanssa.
settings:
about:
manage_rules: Hallitse palvelimen sääntöjä

View File

@@ -786,6 +786,7 @@ fo:
title: Leiklutir
rules:
add_new: Ger nýggja reglu
add_translation: Legg umseting afturat
delete: Strika
description_html: Sjálvt um tey flestu vilja vera við, at tey hava lisið og tikið undir við tænastutreytunum, so lesa tey flestu tær ikki fyrr enn eftir at ein trupulleiki stingur seg upp. <strong>Ger tað lættari at skimma ígjøgnum ambætarareglurnar við at veita tær í einum fløtum punkt-lista.</strong> Royn at gera einstøku reglurnar stuttar og einfaldar, samstundis sum at tær ikki blíva pettaðar ov nógv sundur.
edit: Broyt reglur
@@ -793,6 +794,9 @@ fo:
move_down: Flyt niður
move_up: Flyt upp
title: Ambætarareglur
translation: Umseting
translations: Umsetingar
translations_explanation: Tað er valfrítt at leggja umsetingar afturat fyri reglurnar. Sjálvsetta svarið verður víst, um eingin umsett útgáva er tøk. Vinarliga tryggja, at umsetingar samsvara við sjálvsetta svarið.
settings:
about:
manage_rules: Stýr ambætarareglum

View File

@@ -832,6 +832,8 @@ ga:
description_html: Cé go maíonn a bhformhór gur léigh siad agus go n-aontaíonn siad leis na téarmaí seirbhíse, de ghnáth ní léann daoine tríd go dtí go dtagann fadhb chun cinn. <strong>Déan rialacha do fhreastalaí a fheiceáil go sracfhéachaint trí iad a sholáthar i liosta comhréidh de phointe urchair.</strong> Déan iarracht rialacha aonair a choinneáil gearr simplí, ach déan iarracht gan iad a roinnt ina go leor míreanna ar leith ach an oiread.
edit: Cuir riail in eagar
empty: Níl aon rialacha freastalaí sainmhínithe fós.
move_down: Bog síos
move_up: Bog suas
title: Rialacha freastalaí
settings:
about:
@@ -839,6 +841,9 @@ ga:
preamble: Cuir eolas domhain ar fáil faoin gcaoi a n-oibrítear, a ndéantar modhnóireacht agus maoiniú ar an bhfreastalaí.
rules_hint: Tá réimse tiomnaithe rialacha ann a bhfuiltear ag súil go gcloífidh dúsáideoirí leis.
title: Faoi
allow_referrer_origin:
desc: Nuair a chliceálann dúsáideoirí ar naisc chuig suíomhanna seachtracha, féadfaidh a mbrabhsálaí seoladh do fhreastalaí Mastodon a sheoladh mar an tagairt. Díchumasaigh é seo má shainaithníonn sé seo dúsáideoirí go huathúil, e.g. más freastalaí pearsanta Mastodon é seo.
title: Lig do shuíomhanna seachtracha do fhreastalaí Mastodon a fheiceáil mar fhoinse tráchta
appearance:
preamble: Saincheap comhéadan gréasáin Mastodon.
title: Cuma
@@ -858,6 +863,7 @@ ga:
discovery:
follow_recommendations: Lean na moltaí
preamble: Tá sé ríthábhachtach dromchla a chur ar ábhar suimiúil chun úsáideoirí nua a chur ar bord nach bhfuil aithne acu ar dhuine ar bith Mastodon. Rialú conas a oibríonn gnéithe fionnachtana éagsúla ar do fhreastalaí.
privacy: Príobháideacht
profile_directory: Eolaire próifíle
public_timelines: Amlínte poiblí
publish_statistics: Staitisticí a fhoilsiú

View File

@@ -786,6 +786,7 @@ gl:
title: Roles
rules:
add_new: Engadir regra
add_translation: Engadir tradución
delete: Eliminar
description_html: Aínda que a maioría di que leu e acepta as condicións do servizo, normalmente non as lemos ata que xurde un problema. <strong>Facilita a visualización das regras do servidor mostrándoas nunha lista de puntos.</strong> Intenta manter as regras individuais curtas e simples, mais non dividilas en demasiados elementos separados.
edit: Editar regra
@@ -793,6 +794,9 @@ gl:
move_down: Baixar
move_up: Subir
title: Regras do servidor
translation: Tradución
translations: Traducións
translations_explanation: De xeito optativo podes engadir traducións das normas. Mostrarase o valor por defecto se non hai unha tradución dispoñible. Pon coidado en que a versión traducida estea ao día respecto da versión por defecto.
settings:
about:
manage_rules: Xestionar regras do servidor

View File

@@ -814,6 +814,7 @@ he:
title: תפקידים
rules:
add_new: הוספת כלל
add_translation: הוספת תרגום
delete: מחיקה
description_html: בעוד הרוב טוען שקרא והסכים לתנאי השימוש, אנשים לא נוטים לקרוא אותם עד הסוף עד שמתעוררת בעיה. <strong>כדי שיקל לראות את כללי השרת במבט, יש לספקם כרשימת נקודות.</strong> כדאי לשמור על הכללים קצרים ופשוטים, אבל מאידך גם לא לפצל אותם ליותר מדי נקודות נפרדות.
edit: עריכת כלל
@@ -821,6 +822,9 @@ he:
move_down: הזזה למטה
move_up: הזזה למעלה
title: כללי שרת
translation: תרגום
translations: תרגומים
translations_explanation: באפשרותך להוסיף גרסאות מתורגמות לשפות נוספות של תנאי השימוש. ברירת המחדף תופיע אם גרסאות מתורגמות אינן בנמצא. עליך לוודא כי הגרסאות המתורגמות מעודכנות יחד עם שפת ברירת המחדל.
settings:
about:
manage_rules: ניהול כללי שרת

View File

@@ -786,6 +786,7 @@ hu:
title: Szerepek
rules:
add_new: Szabály hozzáadása
add_translation: Fordítás hozzáadása
delete: Törlés
description_html: Bár a többség azt állítja, hogy elolvasták és egyetértenek a felhasználói feltételekkel, általában ez nem teljesül, amíg egy probléma elő nem jön. <strong>Tedd könnyebbé a kiszolgálód szabályainak áttekintését azzal, hogy pontokba foglalod azt egy listában.</strong> Az egyes szabályok legyenek rövidek és egyszerűek. Próbáld meg nem túl sok önálló pontra darabolni őket.
edit: Szabály szerkesztése
@@ -793,6 +794,9 @@ hu:
move_down: Mozgás lefelé
move_up: Mozgatás felfelé
title: Kiszolgáló szabályai
translation: Fordítás
translations: Fordítások
translations_explanation: Fordításokat is hozzáadhatóak a szabályokhoz. Az alapértelmezett érték lesz megjelenítve, ha nem érhető el lefordított változat. Győződj meg arról, hogy a megadott fordítás szinkronban van az alapértelmezett értékkel.
settings:
about:
manage_rules: Kiszolgáló szabályainak kezelése

View File

@@ -786,6 +786,7 @@ is:
title: Hlutverk
rules:
add_new: Skrá reglu
add_translation: Bæta við þýðingu
delete: Eyða
description_html: Þó að flestir segist hafa lesið og samþykkt þjónustuskilmála, er fólk samt gjarnt á að lesa slíkar upplýsingar ekki til enda fyrr en upp koma einhver vandamál. <strong>Gerðu fólki auðvelt að sjá mikilvægustu reglurnar með því að setja þær fram í flötum punktalista.</strong> Reyndu að hafa hverja reglu stutta og skýra, en ekki vera heldur að skipta þeim upp í mörg aðskilin atriði.
edit: Breyta reglu
@@ -793,6 +794,9 @@ is:
move_down: Færa niður
move_up: Færa upp
title: Reglur netþjónsins
translation: Þýðing
translations: Þýðingar
translations_explanation: Þú getur valið að bæta við þýðingum fyrir reglurnar. Sjálfgefna gildið verður birt ef engin þýðing er fyrir hendi. Gakktu úr skugga um að uppgefnar þýðingar sé alltaf í samræmi við sjálfgefna gildið.
settings:
about:
manage_rules: Sýsla með reglur netþjónsins

View File

@@ -774,17 +774,26 @@ ko:
title: 역할
rules:
add_new: 규칙 추가
add_translation: 번역 추가
delete: 삭제
description_html: 대부분의 사람들은 서비스 약관을 읽고 동의한다고 밝힙니다만, 대개 문제가 발생하기 전까지는 약관을 자세히 읽어보지 않습니다. <strong>서버의 운영원칙을 단일한 글머리 기호 목록으로 제공하여 한 눈에 쉽게 확인할 수 있도록 하세요.</strong> 각 규칙을 짧고 간결하게 유지하되, 많은 항목으로 나누지 않도록 하세요.
edit: 규칙 수정
empty: 아직 정의된 서버 규칙이 없습니다.
move_down: 아래로 이동
move_up: 위로 이동
title: 서버 규칙
translation: 번역
translations: 번역
translations_explanation: 규칙에 대한 번역을 추가할 수 있습니다. 번역된 버전이 없다면 원문이 보여질 것입니다. 항상 원문과 번역본의 내용이 일치하는지 확인하세요.
settings:
about:
manage_rules: 서버 규칙 관리
preamble: 이 서버가 어떻게 운영되고, 중재되고, 자금을 조달하는지 등에 관한 자세한 정보를 기입하세요.
rules_hint: 사용자들이 준수해야 할 규칙들을 위한 전용 공간입니다.
title: 정보
allow_referrer_origin:
desc: 사용자가 외부 사이트 링크를 클릭한 경우 브라우저는 마스토돈 서버 주소를 리퍼러로 보내게 됩니다. 이 서버가 개인 서버인 경우 등 사용자를 식별할 수 있는 경우 이 기능을 비활성화하세요.
title: 외부 사이트가 이 마스토돈 서버를 트래픽 소스로 볼 수 있도록 허용
appearance:
preamble: 마스토돈의 웹 인터페이스를 변경
title: 외관
@@ -804,6 +813,7 @@ ko:
discovery:
follow_recommendations: 팔로우 추천
preamble: 흥미로운 콘텐츠를 노출하는 것은 마스토돈을 알지 못할 수도 있는 신규 사용자를 유입시키는 데 중요합니다. 이 서버에서 작동하는 다양한 발견하기 기능을 제어합니다.
privacy: 개인정보
profile_directory: 프로필 책자
public_timelines: 공개 타임라인
publish_statistics: 통계 발행
@@ -890,6 +900,8 @@ ko:
system_checks:
database_schema_check:
message_html: 대기 중인 데이터베이스 마이그레이션이 있습니다. 애플리케이션이 예상대로 동작할 수 있도록 마이그레이션을 실행해 주세요
elasticsearch_analysis_index_mismatch:
message_html: Elasticsearch 인덱스 아날라이저 설정이 업데이트되지 않았습니다. <code>tootctl search deploy --only-mapping --only=%{value}</code> 명령을 실행해주세요
elasticsearch_health_red:
message_html: Elasticsearch 클러스터가 비정상(red)상태입니다. 검색 기능이 동작하지 않습니다
elasticsearch_health_yellow:
@@ -1814,6 +1826,10 @@ ko:
limit: 이미 너무 많은 게시물을 고정했습니다
ownership: 다른 사람의 게시물은 고정될 수 없습니다
reblog: 부스트는 고정될 수 없습니다
quote_policies:
followers: 팔로워와 멘션된 사람들만
nobody: 멘션된 사람들만
public: 모두
title: '%{name}: "%{quote}"'
visibilities:
direct: 다이렉트
@@ -1867,6 +1883,9 @@ ko:
does_not_match_previous_name: 이전 이름과 맞지 않습니다
terms_of_service:
title: 이용 약관
terms_of_service_interstitial:
review_link: 이용 약관 검토하기
title: "%{domain} 도메인의 이용 약관에 변경 있음"
themes:
contrast: 마스토돈 (고대비)
default: 마스토돈 (어두움)

View File

@@ -794,17 +794,26 @@ lv:
title: Lomas
rules:
add_new: Pievienot noteikumu
add_translation: Pievienot tulkojumu
delete: Dzēst
description_html: Kaut arī lielākā daļa apgalvo, ka ir lasījuši un piekrīt pakalpojuma izmantošanas noteikumiem, parasti cilvēki tos neizlasa, līdz rodas sarežģījumi. <strong>Padari vienkāršāku sava servera noteikumu pārskatīšanu, sniedzot tos vienkāršā uzsvēruma punktu sarakstā!</strong> Jāmēģina atsevišķus noteikumus veidot īsus un vienkāršus, bet jāmēģina arī tos nesadalīt daudzos atsevišķos vienumos.
edit: Labot nosacījumu
empty: Vēl nav pievienots neviens servera noteikums.
move_down: Pārvietot lejup
move_up: Pārvietot augšup
title: Servera noteikumi
translation: Tulkojums
translations: Tulkojumi
translations_explanation: Jūs variet pēc izvēles pievienot noteikumu tulkojumus. Ja nav iztulkots, rādīs noklusējuma vērtību. Lūdzu, vienmēr pārliecinieties, ka sniegtais tulkojums atbilst, ir konkrētai noklusējuma vērtībai.
settings:
about:
manage_rules: Pārvaldīt servera noteikumus
preamble: Sniedz padziļinātu informāciju par to, kā serveris tiek darbināts, moderēts un finansēts.
rules_hint: Noteikumiem, kas taviem lietotājiem ir jāievēro, ir īpaša sadaļa.
title: Par
allow_referrer_origin:
desc: Kad jūsu lietotāji noklikšķina uz ārējām saitēm, viņu pārlūks var nosūtīt jūsu Mastodon servera adresi kā novirzītāju. Atspējojiet šo iestatījumu, ja tas unikāli identificē jūsu lietotājus, piemēram, ja tas ir personīgais Mastodon serveris.
title: Ļaut ārējām vietnēm redzēt jūsu Mastodon serveri kā avotu, no kura nāk apmeklētāji
appearance:
preamble: Pielāgo Mastodon tīmekļa saskarni.
title: Izskats
@@ -824,6 +833,7 @@ lv:
discovery:
follow_recommendations: Sekotšanas rekomendācijas
preamble: Interesanta satura parādīšana palīdz piesaistīt jaunus lietotājus, kuri, iespējams, nepazīst nevienu Mastodon. Kontrolē, kā tavā serverī darbojas dažādi atklāšanas līdzekļi.
privacy: Konfidencialitāte
profile_directory: Profila direktorija
public_timelines: Publiskās ziņu lentas
publish_statistics: Publicēt statistiku

View File

@@ -786,6 +786,7 @@ nl:
title: Rollen
rules:
add_new: Regel toevoegen
add_translation: Vertaling toevoegen
delete: Verwijderen
description_html: Hoewel de meeste mensen zeggen dat ze de gebruiksvoorwaarden hebben gelezen en er mee akkoord gaan, lezen mensen deze meestal niet totdat er een probleem optreedt. <strong>Maak het eenvoudiger om de regels van deze server in één oogopslag te zien, door ze puntsgewijs in een lijst te zetten.</strong> Probeer de verschillende regels kort en simpel te houden, maar probeer ze ook niet in verschillende items onder te verdelen.
edit: Regel bewerken
@@ -793,6 +794,9 @@ nl:
move_down: Omlaag verplaatsen
move_up: Omhoog verplaatsen
title: Serverregels
translation: Vertaling
translations: Vertalingen
translations_explanation: Je kunt eventueel vertalingen toevoegen voor de regels. De standaardwaarde wordt getoond als er geen vertaalde versie beschikbaar is. Zorg er altijd voor dat aanvullende vertalingen bijgewerkt blijven.
settings:
about:
manage_rules: Serverregels beheren

View File

@@ -319,6 +319,7 @@ pt-PT:
create: Criar mensagem de manutenção
title: Nova mensagem de manutenção
preview:
disclaimer: Dado que os utilizadores não podem opor-se a elas, as notificações por e-mail devem ser limitadas a anúncios importantes, como notificações de violação de dados pessoais ou de encerramento de servidores.
explanation_html: 'O e-mail será enviado para <strong>%{display_count} utilizadores</strong>. O texto seguinte será incluído na mensagem de e-mail:'
title: Pré-visualização da mensagem de manutenção
publish: Publicar
@@ -479,6 +480,36 @@ pt-PT:
new:
title: Importar bloqueios de domínio
no_file: Nenhum ficheiro selecionado
fasp:
debug:
callbacks:
created_at: Criado em
delete: Eliminar
ip: Endereço de IP
request_body: Corpo do pedido
title: Depurar Callbacks
providers:
active: Ativo
base_url: URL base
callback: Callback
delete: Eliminar
edit: Editar Fornecedor
finish_registration: Terminar inscrição
name: Nome
providers: Fornecedores
public_key_fingerprint: Impressão digital da chave pública
registration_requested: Requerida inscrição
registrations:
confirm: Confirmar
description: Recebeu uma inscrição de um FASP. Rejeite-a se não foi você que a iniciou. Se foi você que a iniciou, compare cuidadosamente o nome e a impressão digital da chave antes de confirmar a inscrição.
reject: Rejeitar
title: Confirmar Inscrição no FASP
save: Guardar
select_capabilities: Selecionar Capacidades
sign_in: Iniciar Sessão
status: Estado
title: Fornecedores de Serviços Auxiliares no Fediverso
title: FASP
follow_recommendations:
description_html: "<strong>Recomendações de quem seguir ajudam novos utilizadores a encontrar conteúdo interessante rapidamente.</strong>. Quando um utilizador não interage com outros o suficiente para formar recomendações personalizadas, estas contas são recomendadas. Elas são recalculadas diariamente a partir de uma mistura de contas com mais atividade recente e maior número de seguidores locais para um determinado idioma."
language: Para o idioma
@@ -755,17 +786,26 @@ pt-PT:
title: Funções
rules:
add_new: Adicionar regra
add_translation: Adicionar tradução
delete: Eliminar
description_html: Embora a maioria afirme ter lido e concordado com os termos de serviço, geralmente as pessoas só os leem depois de lhes surgir um problema. <strong>Torne fácil a leitura rápida das regras do seu servidor, apresentando-as numa lista de tópicos.</strong> Tente que cada regra seja sucinta e simples, mas tente também não dividi-las num número excessivo de tópicos separados.
edit: Editar regra
empty: Ainda não foi definida nenhuma regra do servidor.
move_down: Mover para baixo
move_up: Mover para cima
title: Regras do servidor
translation: Tradução
translations: Traduções
translations_explanation: Opcionalmente, pode adicionar traduções para as regras do servidor. A versão predefinida será apresentada se não estiver disponível uma versão traduzida. Certifique-se sempre de que qualquer tradução fornecida está em sincronia com a versão predefinida.
settings:
about:
manage_rules: Gerir regras do servidor
preamble: Forneça informações aprofundadas sobre como o servidor é operado, moderado, financiado.
rules_hint: Existe uma área dedicada às regras a que os teus utilizadores devem aderir.
title: Sobre
allow_referrer_origin:
desc: Quando os seus utilizadores clicam em links para sites externos, o navegador deles pode enviar o endereço do seu servidor Mastodon como referenciador. Desative esta opção se isso identificar inequivocamente os seus utilizadores, por exemplo, se este for um servidor Mastodon pessoal.
title: Permitir que sites externos vejam o seu servidor Mastodon como uma fonte de tráfego
appearance:
preamble: Personaliza a interface web do Mastodon.
title: Aparência
@@ -785,6 +825,7 @@ pt-PT:
discovery:
follow_recommendations: Recomendações de contas
preamble: Revelar conteúdos interessantes é fundamental para a entrada de novos utilizadores que podem não conhecer ninguém no Mastodon. Controla como os vários recursos de descoberta funcionam no teu servidor.
privacy: Privacidade
profile_directory: Diretório de perfis
public_timelines: Cronologias públicas
publish_statistics: Publicar estatísticas
@@ -871,6 +912,8 @@ pt-PT:
system_checks:
database_schema_check:
message_html: Existem migrações de bases de dados pendentes. Execute-as para garantir que a aplicação se comporta como esperado
elasticsearch_analysis_index_mismatch:
message_html: As definições do analisador de índices do Elasticsearch estão desactualizadas. Por favor, execute <code>tootctl search deploy --only-mapping --only=%{value}</code>
elasticsearch_health_red:
message_html: O cluster elasticsearch não está de boa saúde (estado vermelho), as funcionalidades de pesquisa não estão disponíveis
elasticsearch_health_yellow:
@@ -1824,6 +1867,10 @@ pt-PT:
limit: Já fixaste a quantidade máxima de publicações
ownership: Não podem ser fixadas publicações de outras pessoas
reblog: Não é possível fixar um impulso
quote_policies:
followers: Seguidores e utilizadores mencionados
nobody: Apenas utilizadores mencionados
public: Todos
title: '%{name}: "%{quote}"'
visibilities:
direct: Direto
@@ -1877,6 +1924,11 @@ pt-PT:
does_not_match_previous_name: não coincide com o nome anterior
terms_of_service:
title: Termos de Serviço
terms_of_service_interstitial:
future_preamble_html: Estamos a fazer algumas alterações aos nossos termos de serviço, que entrarão em vigor a partir de <strong>%{date}</strong>. Recomendamos que reveja os termos atualizados.
past_preamble_html: Alterámos os nossos termos de serviço desde a sua última visita. Recomendamos que reveja os termos atualizados.
review_link: Rever termos de serviço
title: Os termos de serviço de %{domain} estão a ser alterados
themes:
contrast: Mastodon (alto contraste)
default: Mastodon (escuro)

View File

@@ -226,6 +226,7 @@ ko:
setting_boost_modal: 부스트 전 확인창을 띄웁니다
setting_default_language: 게시물 언어
setting_default_privacy: 게시물 프라이버시
setting_default_quote_policy: 인용할 수 있는 사람
setting_default_sensitive: 미디어를 언제나 민감한 콘텐츠로 설정
setting_delete_modal: 게시물 삭제 전 확인창을 띄웁니다
setting_disable_hover_cards: 호버시 프로필 미리보기를 비활성화

View File

@@ -56,6 +56,7 @@ pt-PT:
scopes: Quais as API a que a aplicação terá permissão para aceder. Se selecionar um âmbito de nível superior, não precisa de selecionar âmbitos individuais.
setting_aggregate_reblogs: Não mostrar os novos impulsos para publicações que tenham sido recentemente impulsionadas (apenas afeta os impulsos recentemente recebidos)
setting_always_send_emails: Normalmente as notificações por e-mail não serão enviadas quando estiver a utilizar ativamente o Mastodon
setting_default_quote_policy: Os utilizadores mencionados têm sempre permissão para citar. Esta definição só terá efeito para publicações criadas com a próxima versão do Mastodon, mas pode selecionar a sua preferência em antecipação
setting_default_sensitive: Os multimédia sensíveis são ocultados por predefinição e podem ser revelados com um clique/toque
setting_display_media_default: Esconder multimédia marcada como sensível
setting_display_media_hide_all: Esconder sempre toda a multimédia
@@ -148,6 +149,9 @@ pt-PT:
min_age: Não deve ter menos do que a idade mínima exigida pela legislação da sua jurisdição.
user:
chosen_languages: Quando selecionado, só serão mostradas nas cronologias públicas as publicações nos idiomas escolhidos
date_of_birth:
one: Temos de nos certificar que tem pelo menos %{count} para utilizar o Mastodon. Não vamos guardar esta informação.
other: Temos de nos certificar que tem pelo menos %{count} para utilizar o Mastodon. Não vamos guardar esta informação.
role: A função controla as permissões que o utilizador tem.
user_role:
color: Cor a ser utilizada para a função em toda a interface de utilizador, como RGB no formato hexadecimal
@@ -228,6 +232,7 @@ pt-PT:
setting_boost_modal: Mostrar caixa de diálogo de confirmação antes de impulsionar
setting_default_language: Idioma de publicação
setting_default_privacy: Privacidade da publicação
setting_default_quote_policy: Quem pode citar
setting_default_sensitive: Marcar sempre os multimédia como sensíveis
setting_delete_modal: Solicitar confirmação antes de eliminar uma publicação
setting_disable_hover_cards: Desativar visualização de perfil ao passar o cursor

View File

@@ -779,11 +779,17 @@ sq:
title: Role
rules:
add_new: Shtoni rregull
add_translation: Shtoni përkthim
delete: Fshije
description_html: Edhe pse shumica pretendon se kanë lexuar dhe pajtohen me kushtet e shërbimit, zakonisht njerëzit nuk e lexojnë nga fillimi në fund, deri kur del një problem. <strong>Bëjeni më të lehtë parjen e rregullave të shërbyesit tuaj me një vështrim, duke i dhënë në një listë të thjeshtë me pika.</strong> Përpiquni që rregullat të jenë secili të shkurtër dhe të thjeshtë, por as mos u përpiqni ti ndani në shumë zëra të veçantë.
edit: Përpunoni rregull
empty: Sjanë përcaktuar ende rregulla shërbyesi.
move_down: Lëvize poshtë
move_up: Lëvize sipër
title: Rregulla shërbyesi
translation: Përkthim
translations: Përkthime
translations_explanation: Në daçi, mund të shtoni përkthime për rregullat. Nëse ska version të përkthyer, do të shfaqet vlera parazgjedhje. Ju lutemi, garantoni përherë se përkthimi i dhënë është i njëkohësuar me vlerën parazgjedhje.
settings:
about:
manage_rules: Administroni rregulla shërbyesi

View File

@@ -1,7 +1,7 @@
---
tr:
about:
about_mastodon_html: Mastodon <em>ücretsiz ve açık kaynaklı</em> bir sosyal ağdır. <em>Merkezi olmayan</em> yapısı sayesinde diğer ticari sosyal platformların aksine iletişimininizin tek bir firmada tutulmasının/yönetilmesinin önüne geçer. Güvendiğiniz bir sunucuyu seçerek oradaki kişilerle etkileşimde bulunabilirsiniz. Herkes kendi Mastodon sunucusunu kurabilir ve sorunsuz bir şekilde Mastodon <em>sosyal ağına</em> dahil edebilir!
about_mastodon_html: Jetub Max<em>ücretsiz ve açık kaynaklı</em> bir sosyal ağdır. <em>Merkezi olmayan</em> yapısı sayesinde diğer ticari sosyal platformların aksine iletişimininizin tek bir firmada tutulmasının/yönetilmesinin önüne geçer. Güvendiğiniz bir sunucuyu seçerek oradaki kişilerle etkileşimde bulunabilirsiniz. Herkes kendi Jetub Max sunucusunu kurabilir ve sorunsuz bir şekilde Jetub Max<em>sosyal ağına</em> dahil edebilir!
contact_missing: Ayarlanmadı
contact_unavailable: Bulunamadı
hosted_on: Mastodon %{domain} üzerinde barındırılıyor
@@ -786,6 +786,7 @@ tr:
title: Roller
rules:
add_new: Kural ekle
add_translation: Çeviri ekle
delete: Sil
description_html: Her ne kadar çoğu hizmet kullanım şartlarını okuyup kabul ettiğini söylese de, insanlar onu ancak bir sorun çıktığında gözden geçiriyorlar. <strong>Sunucunuzun kurallarını bir bakışta kolayca görülecek şekilde düz bir madde listesi şeklinde sunun</strong>. Tekil kuralları kısa ve yalın tutmaya çalışan ama onları çok sayıda maddeye bölmemeye de çalışın.
edit: Kuralı düzenle
@@ -793,6 +794,9 @@ tr:
move_down: Aşağı taşı
move_up: Yukarı taşı
title: Sunucu kuralları
translation: Çeviri
translations: Çeviriler
translations_explanation: İsteğe bağlı olarak kurallar için çeviriler ekleyebilirsiniz. Çevrilmiş bir sürüm mevcut değilse varsayılan değer gösterilecektir. Lütfen her zaman sağlanan çevirinin varsayılan değerle senkronize olduğundan emin olun.
settings:
about:
manage_rules: Sunucu kurallarını yönet

View File

@@ -772,6 +772,7 @@ zh-TW:
title: 角色
rules:
add_new: 新增規則
add_translation: 新增翻譯
delete: 刪除
description_html: 雖然大多數人皆宣稱已閱讀並同意服務條款,通常直到某些問題發生時人們從未讀過。<strong>以透過提供條列式規則的方式使您的伺服器規則可以一目了然。</strong>試著維持各項條款簡短而明瞭,但也試著不要將條款切割為許多分開的項目。
edit: 編輯規則
@@ -779,6 +780,9 @@ zh-TW:
move_down: 向下移
move_up: 向上移
title: 伺服器規則
translation: 翻譯
translations: 翻譯
translations_explanation: 您可以選擇性地替規則添加翻譯。若沒有翻譯版本,預設值將被顯示。請確保任何提供之翻譯與預設值內容同步。
settings:
about:
manage_rules: 管理伺服器規則

View File

@@ -31,6 +31,7 @@ class Sanitize
next true if /^(h|p|u|dt|e)-/.match?(e) # microformats classes
next true if /^(mention|hashtag)$/.match?(e) # semantic classes
next true if /^(ellipsis|invisible)$/.match?(e) # link formatting classes
next true if e == 'quote-inline'
end
node['class'] = class_list.join(' ')
@@ -122,6 +123,7 @@ class Sanitize
'blockquote' => %w(cite),
'ol' => %w(start reversed),
'li' => %w(value),
'p' => %w(class),
},
add_attributes: {

View File

@@ -351,5 +351,42 @@ namespace :dev do
display_name: 'Mastodon test/showcase account',
note: 'Test account to showcase many Mastodon features. Most of its posts are public, but some are private!'
)
remote_quote = Status.create_with(
text: <<~HTML,
<p>This is a self-quote of a remote formatted post</p>
<p class="quote-inline">RE: <a href="https://example.org/foo/bar/baz">https://example.org/foo/bar/baz</a></p>
HTML
account: remote_account,
uri: 'https://example.org/foo/bar/quote',
url: 'https://example.org/foo/bar/quote'
).find_or_create_by!(id: 10_000_023)
Quote.create_with(
status: remote_quote,
quoted_status: remote_formatted_post,
state: :accepted
).find_or_create_by!(id: 10_000_010)
Status.create_with(
account: showcase_account,
reblog: remote_quote
).find_or_create_by!(id: 10_000_024)
media_attachment = MediaAttachment.create_with(
account: showcase_account,
file: File.open('spec/fixtures/files/attachment.jpg')
).find_or_create_by!(id: 10_000_010)
quote_post_with_media = Status.create_with(
text: "This is a status with a picture and tags which also quotes a status with a picture.\n\n#Mastodon #Test",
ordered_media_attachment_ids: [media_attachment.id],
account: showcase_account,
visibility: :public
).find_or_create_by!(id: 10_000_025)
media_attachment.update(status_id: quote_post_with_media.id)
ProcessHashtagsService.new.call(quote_post_with_media)
Quote.create_with(
status: quote_post_with_media,
quoted_status: status_with_media,
state: :accepted
).find_or_create_by!(id: 10_000_011)
end
end

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