mirror of https://framagit.org/tykayn/mastodon
Merge branch 'main' into glitch-soc/merge-upstream
Conflicts: - `app/models/custom_emoji.rb`: Not a real conflict, just upstream changing a line too close to a glitch-soc-specific validation. Applied upstream changes. - `app/models/public_feed.rb`: Not a real conflict, just upstream changing a line too close to a glitch-soc-specific parameter documentation. Applied upstream changes.master
commit
c118918520
|
@ -276,7 +276,7 @@ Some of the features in this release have been funded through the [NGI0 Discover
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fix error resposes for `from` search prefix ([single-right-quote](https://github.com/mastodon/mastodon/pull/17963))
|
||||
- Fix error responses for `from` search prefix ([single-right-quote](https://github.com/mastodon/mastodon/pull/17963))
|
||||
- Fix dangling language-specific trends ([Gargron](https://github.com/mastodon/mastodon/pull/17997))
|
||||
- Fix extremely rare race condition when deleting a status or account ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17994))
|
||||
- Fix trends returning less results per page when filtered in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17996))
|
||||
|
@ -411,7 +411,7 @@ Some of the features in this release have been funded through the [NGI0 Discover
|
|||
- Remove profile directory link from main navigation panel in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17688))
|
||||
- **Remove language detection through cld3** ([Gargron](https://github.com/mastodon/mastodon/pull/17478), [ykzts](https://github.com/mastodon/mastodon/pull/17539), [Gargron](https://github.com/mastodon/mastodon/pull/17496), [Gargron](https://github.com/mastodon/mastodon/pull/17722))
|
||||
- cld3 is very inaccurate on short-form content even with unique alphabets
|
||||
- Post language can be overriden individually using `language` param
|
||||
- Post language can be overridden individually using `language` param
|
||||
- Otherwise, it defaults to the user's interface language
|
||||
- Remove support for `OAUTH_REDIRECT_AT_SIGN_IN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17287))
|
||||
- Use `OMNIAUTH_ONLY` instead
|
||||
|
|
|
@ -8,7 +8,7 @@ class Api::V1::Accounts::PinsController < Api::BaseController
|
|||
before_action :set_account
|
||||
|
||||
def create
|
||||
AccountPin.create!(account: current_account, target_account: @account)
|
||||
AccountPin.find_or_create_by!(account: current_account, target_account: @account)
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,10 @@ class Api::V1::ListsController < Api::BaseController
|
|||
before_action :require_user!
|
||||
before_action :set_list, except: [:index, :create]
|
||||
|
||||
rescue_from ArgumentError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
|
||||
def index
|
||||
@lists = List.where(account: current_account).all
|
||||
render json: @lists, each_serializer: REST::ListSerializer
|
||||
|
|
|
@ -24,7 +24,7 @@ class Api::V1::TagsController < Api::BaseController
|
|||
private
|
||||
|
||||
def set_or_create_tag
|
||||
return not_found unless /\A(#{Tag::HASHTAG_NAME_RE})\z/.match?(params[:id])
|
||||
return not_found unless Tag::HASHTAG_NAME_RE.match?(params[:id])
|
||||
@tag = Tag.find_normalized(params[:id]) || Tag.new(name: Tag.normalize(params[:id]), display_name: params[:id])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,7 +35,6 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
|||
def public_feed
|
||||
PublicFeed.new(
|
||||
current_account,
|
||||
locale: content_locale,
|
||||
local: truthy_param?(:local),
|
||||
remote: truthy_param?(:remote),
|
||||
only_media: truthy_param?(:only_media),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
# rubocop:disable Metrics/ModuleLength, Style/WordArray
|
||||
|
||||
module LanguagesHelper
|
||||
ISO_639_1 = {
|
||||
|
@ -189,8 +190,13 @@ module LanguagesHelper
|
|||
ISO_639_3 = {
|
||||
ast: ['Asturian', 'Asturianu'].freeze,
|
||||
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
|
||||
jbo: ['Lojban', 'la .lojban.'].freeze,
|
||||
kab: ['Kabyle', 'Taqbaylit'].freeze,
|
||||
kmr: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze,
|
||||
ldn: ['Láadan', 'Láadan'].freeze,
|
||||
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
|
||||
tok: ['Toki Pona', 'toki pona'].freeze,
|
||||
zba: ['Balaibalan', 'باليبلن'].freeze,
|
||||
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
||||
}.freeze
|
||||
|
||||
|
@ -259,3 +265,5 @@ module LanguagesHelper
|
|||
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:enable Metrics/ModuleLength, Style/WordArray
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedNumber } from 'react-intl';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { reduceMotion } from 'mastodon/initial_state';
|
||||
|
@ -51,7 +51,7 @@ export default class AnimatedNumber extends React.PureComponent {
|
|||
const { direction } = this.state;
|
||||
|
||||
if (reduceMotion) {
|
||||
return obfuscate ? obfuscatedCount(value) : <FormattedNumber value={value} />;
|
||||
return obfuscate ? obfuscatedCount(value) : <ShortNumber value={value} />;
|
||||
}
|
||||
|
||||
const styles = [{
|
||||
|
@ -65,7 +65,7 @@ export default class AnimatedNumber extends React.PureComponent {
|
|||
{items => (
|
||||
<span className='animated-number'>
|
||||
{items.map(({ key, data, style }) => (
|
||||
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <FormattedNumber value={data} />}</span>
|
||||
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
|
|
|
@ -152,7 +152,6 @@ class ColumnHeader extends React.PureComponent {
|
|||
className={collapsibleButtonClassName}
|
||||
title={formatMessage(collapsed ? messages.show : messages.hide)}
|
||||
aria-label={formatMessage(collapsed ? messages.show : messages.hide)}
|
||||
aria-pressed={collapsed ? 'false' : 'true'}
|
||||
onClick={this.handleToggleClick}
|
||||
>
|
||||
<i className='icon-with-badge'>
|
||||
|
|
|
@ -16,7 +16,6 @@ export default class IconButton extends React.PureComponent {
|
|||
onKeyPress: PropTypes.func,
|
||||
size: PropTypes.number,
|
||||
active: PropTypes.bool,
|
||||
pressed: PropTypes.bool,
|
||||
expanded: PropTypes.bool,
|
||||
style: PropTypes.object,
|
||||
activeStyle: PropTypes.object,
|
||||
|
@ -98,7 +97,6 @@ export default class IconButton extends React.PureComponent {
|
|||
icon,
|
||||
inverted,
|
||||
overlay,
|
||||
pressed,
|
||||
tabIndex,
|
||||
title,
|
||||
counter,
|
||||
|
@ -143,7 +141,6 @@ export default class IconButton extends React.PureComponent {
|
|||
<button
|
||||
type='button'
|
||||
aria-label={title}
|
||||
aria-pressed={pressed}
|
||||
aria-expanded={expanded}
|
||||
title={title}
|
||||
className={classes}
|
||||
|
|
|
@ -45,6 +45,7 @@ const messages = defineMessages({
|
|||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
filter: { id: 'status.filter', defaultMessage: 'Filter this post' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { status }) => ({
|
||||
|
@ -221,25 +222,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
handleCopy = () => {
|
||||
const url = this.props.status.get('url');
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
textarea.textContent = url;
|
||||
textarea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
} catch (e) {
|
||||
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
const url = this.props.status.get('url');
|
||||
navigator.clipboard.writeText(url);
|
||||
}
|
||||
|
||||
|
||||
handleHideClick = () => {
|
||||
this.props.onFilter();
|
||||
}
|
||||
|
@ -254,12 +240,17 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
const mutingConversation = status.get('muted');
|
||||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
|
||||
|
||||
let menu = [];
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
||||
|
||||
if (publicStatus) {
|
||||
if (isRemote) {
|
||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||
}
|
||||
|
@ -361,8 +352,8 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='status__action-bar'>
|
||||
<IconButton className='status__action-bar__button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
|
||||
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
|
||||
|
||||
{shareButton}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import configureStore from '../store/configureStore';
|
||||
import { hydrateStore } from '../actions/store';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { getLocale } from '../locales';
|
||||
import PublicTimeline from '../features/standalone/public_timeline';
|
||||
import HashtagTimeline from '../features/standalone/hashtag_timeline';
|
||||
import ModalContainer from '../features/ui/containers/modal_container';
|
||||
import initialState from '../initial_state';
|
||||
|
||||
const { localeData, messages } = getLocale();
|
||||
addLocaleData(localeData);
|
||||
|
||||
const store = configureStore();
|
||||
|
||||
if (initialState) {
|
||||
store.dispatch(hydrateStore(initialState));
|
||||
}
|
||||
|
||||
export default class TimelineContainer extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.string.isRequired,
|
||||
hashtag: PropTypes.string,
|
||||
local: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
local: !initialState.settings.known_fediverse,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { locale, hashtag, local } = this.props;
|
||||
|
||||
let timeline;
|
||||
|
||||
if (hashtag) {
|
||||
timeline = <HashtagTimeline hashtag={hashtag} local={local} />;
|
||||
} else {
|
||||
timeline = <PublicTimeline local={local} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<Provider store={store}>
|
||||
<Fragment>
|
||||
{timeline}
|
||||
|
||||
{ReactDOM.createPortal(
|
||||
<ModalContainer />,
|
||||
document.getElementById('modal-container'),
|
||||
)}
|
||||
</Fragment>
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -53,6 +53,7 @@ const messages = defineMessages({
|
|||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
});
|
||||
|
||||
const titleFromAccount = account => {
|
||||
|
@ -97,6 +98,7 @@ class Header extends ImmutablePureComponent {
|
|||
onEditAccountNote: PropTypes.func.isRequired,
|
||||
onChangeLanguages: PropTypes.func.isRequired,
|
||||
onInteractionModal: PropTypes.func.isRequired,
|
||||
onOpenAvatar: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
|
@ -140,6 +142,13 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
handleAvatarClick = e => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
this.props.onOpenAvatar();
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, hidden, intl, domain } = this.props;
|
||||
const { signedIn } = this.context.identity;
|
||||
|
@ -148,7 +157,9 @@ class Header extends ImmutablePureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
const suspended = account.get('suspended');
|
||||
const suspended = account.get('suspended');
|
||||
const isRemote = account.get('acct') !== account.get('username');
|
||||
const remoteDomain = isRemote ? account.get('acct').split('@')[1] : null;
|
||||
|
||||
let info = [];
|
||||
let actionBtn = '';
|
||||
|
@ -200,6 +211,11 @@ class Header extends ImmutablePureComponent {
|
|||
menu.push(null);
|
||||
}
|
||||
|
||||
if (isRemote) {
|
||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
|
||||
menu.push(null);
|
||||
}
|
||||
|
||||
if ('share' in navigator) {
|
||||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
||||
menu.push(null);
|
||||
|
@ -250,15 +266,13 @@ class Header extends ImmutablePureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
|
||||
}
|
||||
|
||||
if (signedIn && account.get('acct') !== account.get('username')) {
|
||||
const domain = account.get('acct').split('@')[1];
|
||||
|
||||
if (signedIn && isRemote) {
|
||||
menu.push(null);
|
||||
|
||||
if (account.getIn(['relationship', 'domain_blocking'])) {
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
|
||||
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain: remoteDomain }), action: this.props.onUnblockDomain });
|
||||
} else {
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
|
||||
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain: remoteDomain }), action: this.props.onBlockDomain });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,7 +310,7 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
<div className='account__header__bar'>
|
||||
<div className='account__header__tabs'>
|
||||
<a className='avatar' href={account.get('url')} rel='noopener noreferrer' target='_blank'>
|
||||
<a className='avatar' href={account.get('avatar')} rel='noopener noreferrer' target='_blank' onClick={this.handleAvatarClick}>
|
||||
<Avatar account={suspended || hidden ? undefined : account} size={90} />
|
||||
</a>
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
onAddToList: PropTypes.func.isRequired,
|
||||
onChangeLanguages: PropTypes.func.isRequired,
|
||||
onInteractionModal: PropTypes.func.isRequired,
|
||||
onOpenAvatar: PropTypes.func.isRequired,
|
||||
hideTabs: PropTypes.bool,
|
||||
domain: PropTypes.string.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
|
@ -101,6 +102,10 @@ export default class Header extends ImmutablePureComponent {
|
|||
this.props.onInteractionModal(this.props.account);
|
||||
}
|
||||
|
||||
handleOpenAvatar = () => {
|
||||
this.props.onOpenAvatar(this.props.account);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { account, hidden, hideTabs } = this.props;
|
||||
|
||||
|
@ -129,6 +134,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
onEditAccountNote={this.handleEditAccountNote}
|
||||
onChangeLanguages={this.handleChangeLanguages}
|
||||
onInteractionModal={this.handleInteractionModal}
|
||||
onOpenAvatar={this.handleOpenAvatar}
|
||||
domain={this.props.domain}
|
||||
hidden={hidden}
|
||||
/>
|
||||
|
|
|
@ -152,6 +152,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
}));
|
||||
},
|
||||
|
||||
onOpenAvatar (account) {
|
||||
dispatch(openModal('IMAGE', {
|
||||
src: account.get('avatar'),
|
||||
alt: account.get('acct'),
|
||||
}));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
|
||||
|
|
|
@ -194,7 +194,7 @@ class HashtagTimeline extends React.PureComponent {
|
|||
const following = tag.get('following');
|
||||
|
||||
followButton = (
|
||||
<button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-pressed={following ? 'true' : 'false'}>
|
||||
<button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}>
|
||||
<Icon id={following ? 'user-times' : 'user-plus'} fixedWidth className='column-header__icon' />
|
||||
</button>
|
||||
);
|
||||
|
|
|
@ -130,7 +130,6 @@ class HomeTimeline extends React.PureComponent {
|
|||
className={classNames('column-header__button', { 'active': showAnnouncements })}
|
||||
title={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
|
||||
aria-label={intl.formatMessage(showAnnouncements ? messages.hide_announcements : messages.show_announcements)}
|
||||
aria-pressed={showAnnouncements ? 'true' : 'false'}
|
||||
onClick={this.handleToggleAnnouncementsClick}
|
||||
>
|
||||
<IconWithBadge id='bullhorn' count={unreadAnnouncements} />
|
||||
|
|
|
@ -150,7 +150,7 @@ class InteractionModal extends React.PureComponent {
|
|||
|
||||
<div className='interaction-modal__choices__choice'>
|
||||
<h3><FormattedMessage id='interaction_modal.on_another_server' defaultMessage='On a different server' /></h3>
|
||||
<p><FormattedMessage id='interaction_modal.other_server_instructions' defaultMessage='Simply copy and paste this URL into the search bar of your favourite app or the web interface where you are signed in.' /></p>
|
||||
<p><FormattedMessage id='interaction_modal.other_server_instructions' defaultMessage='Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.' /></p>
|
||||
<Copypaste value={url} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -182,8 +182,8 @@ class Footer extends ImmutablePureComponent {
|
|||
return (
|
||||
<div className='picture-in-picture__footer'>
|
||||
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
||||
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} href={status.get('url')} />}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -39,6 +39,7 @@ const messages = defineMessages({
|
|||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { status }) => ({
|
||||
|
@ -174,22 +175,8 @@ class ActionBar extends React.PureComponent {
|
|||
}
|
||||
|
||||
handleCopy = () => {
|
||||
const url = this.props.status.get('url');
|
||||
const textarea = document.createElement('textarea');
|
||||
|
||||
textarea.textContent = url;
|
||||
textarea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
|
||||
try {
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
} catch (e) {
|
||||
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
const url = this.props.status.get('url');
|
||||
navigator.clipboard.writeText(url);
|
||||
}
|
||||
|
||||
render () {
|
||||
|
@ -201,10 +188,15 @@ class ActionBar extends React.PureComponent {
|
|||
const mutingConversation = status.get('muted');
|
||||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
|
||||
|
||||
let menu = [];
|
||||
|
||||
if (publicStatus) {
|
||||
if (isRemote) {
|
||||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: status.get('url') });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
|
||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||
menu.push(null);
|
||||
|
|
|
@ -619,7 +619,7 @@ class Status extends ImmutablePureComponent {
|
|||
showBackButton
|
||||
multiColumn={multiColumn}
|
||||
extraButton={(
|
||||
<button type='button' className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
|
||||
<button type='button' className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
|
||||
)}
|
||||
/>
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import IconButton from 'mastodon/components/icon_button';
|
||||
import ImageLoader from './image_loader';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class ImageModal extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
src: PropTypes.string.isRequired,
|
||||
alt: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
navigationHidden: false,
|
||||
};
|
||||
|
||||
toggleNavigation = () => {
|
||||
this.setState(prevState => ({
|
||||
navigationHidden: !prevState.navigationHidden,
|
||||
}));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl, src, alt, onClose } = this.props;
|
||||
const { navigationHidden } = this.state;
|
||||
|
||||
const navigationClassName = classNames('media-modal__navigation', {
|
||||
'media-modal__navigation--hidden': navigationHidden,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal media-modal'>
|
||||
<div className='media-modal__closer' role='presentation' onClick={onClose} >
|
||||
<ImageLoader
|
||||
src={src}
|
||||
width={400}
|
||||
height={400}
|
||||
alt={alt}
|
||||
onClick={this.toggleNavigation}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={navigationClassName}>
|
||||
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,7 @@ import BoostModal from './boost_modal';
|
|||
import AudioModal from './audio_modal';
|
||||
import ConfirmationModal from './confirmation_modal';
|
||||
import FocalPointModal from './focal_point_modal';
|
||||
import ImageModal from './image_modal';
|
||||
import {
|
||||
MuteModal,
|
||||
BlockModal,
|
||||
|
@ -31,6 +32,7 @@ const MODAL_COMPONENTS = {
|
|||
'MEDIA': () => Promise.resolve({ default: MediaModal }),
|
||||
'VIDEO': () => Promise.resolve({ default: VideoModal }),
|
||||
'AUDIO': () => Promise.resolve({ default: AudioModal }),
|
||||
'IMAGE': () => Promise.resolve({ default: ImageModal }),
|
||||
'BOOST': () => Promise.resolve({ default: BoostModal }),
|
||||
'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }),
|
||||
'MUTE': MuteModal,
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
|
||||
"account.follows.empty": "Die gebruiker volg nie tans iemand nie.",
|
||||
"account.follows_you": "Volg jou",
|
||||
"account.go_to_profile": "Go to profile",
|
||||
"account.go_to_profile": "Gaan na profiel",
|
||||
"account.hide_reblogs": "Versteek hupstoot vanaf @{name}",
|
||||
"account.joined_short": "Aangesluit",
|
||||
"account.languages": "Change subscribed languages",
|
||||
|
@ -74,7 +74,7 @@
|
|||
"admin.dashboard.retention.cohort_size": "Nuwe gebruikers",
|
||||
"alert.rate_limited.message": "Probeer asb. weer na {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Tempo-beperk",
|
||||
"alert.unexpected.message": "An unexpected error occurred.",
|
||||
"alert.unexpected.message": "'n Onverwagte fout het voorgekom.",
|
||||
"alert.unexpected.title": "Oeps!",
|
||||
"announcement.announcement": "Aankondiging",
|
||||
"attachments_list.unprocessed": "(unprocessed)",
|
||||
|
@ -146,18 +146,18 @@
|
|||
"compose_form.sensitive.unmarked": "{count, plural, one {Media is not marked as sensitive} other {Media is not marked as sensitive}}",
|
||||
"compose_form.spoiler.marked": "Text is hidden behind warning",
|
||||
"compose_form.spoiler.unmarked": "Text is not hidden",
|
||||
"compose_form.spoiler_placeholder": "Write your warning here",
|
||||
"compose_form.spoiler_placeholder": "Skryf jou waarskuwing hier",
|
||||
"confirmation_modal.cancel": "Kanselleer",
|
||||
"confirmations.block.block_and_report": "Block & Rapporteer",
|
||||
"confirmations.block.confirm": "Block",
|
||||
"confirmations.block.message": "Are you sure you want to block {name}?",
|
||||
"confirmations.cancel_follow_request.confirm": "Withdraw request",
|
||||
"confirmations.cancel_follow_request.message": "Are you sure you want to withdraw your request to follow {name}?",
|
||||
"confirmations.delete.confirm": "Delete",
|
||||
"confirmations.block.confirm": "Blokeer",
|
||||
"confirmations.block.message": "Is jy seker dat jy {name} wil blok?",
|
||||
"confirmations.cancel_follow_request.confirm": "Trek aanvaag terug",
|
||||
"confirmations.cancel_follow_request.message": "Is jy seker dat jy jou aanvraag om {name} te volg, terug wil trek?",
|
||||
"confirmations.delete.confirm": "Wis uit",
|
||||
"confirmations.delete.message": "Are you sure you want to delete this status?",
|
||||
"confirmations.delete_list.confirm": "Delete",
|
||||
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
|
||||
"confirmations.discard_edit_media.confirm": "Discard",
|
||||
"confirmations.delete_list.confirm": "Wis uit",
|
||||
"confirmations.delete_list.message": "Is jy seker dat jy hierdie lys permanent wil uitwis?",
|
||||
"confirmations.discard_edit_media.confirm": "Verwerp",
|
||||
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
||||
|
@ -173,17 +173,17 @@
|
|||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
|
||||
"conversation.delete": "Delete conversation",
|
||||
"conversation.mark_as_read": "Mark as read",
|
||||
"conversation.open": "View conversation",
|
||||
"conversation.with": "With {names}",
|
||||
"conversation.mark_as_read": "Merk as gelees",
|
||||
"conversation.open": "Besigtig gesprek",
|
||||
"conversation.with": "Met {names}",
|
||||
"copypaste.copied": "Copied",
|
||||
"copypaste.copy": "Copy",
|
||||
"directory.federated": "Vanaf bekende fediverse",
|
||||
"directory.local": "Slegs vanaf {domain}",
|
||||
"directory.new_arrivals": "New arrivals",
|
||||
"directory.recently_active": "Recently active",
|
||||
"disabled_account_banner.account_settings": "Account settings",
|
||||
"disabled_account_banner.text": "Your account {disabledAccount} is currently disabled.",
|
||||
"disabled_account_banner.account_settings": "Rekening instellings",
|
||||
"disabled_account_banner.text": "Jou rekening {disabledAccount} is tans onaktief.",
|
||||
"dismissable_banner.community_timeline": "These are the most recent public posts from people whose accounts are hosted by {domain}.",
|
||||
"dismissable_banner.dismiss": "Dismiss",
|
||||
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
|
||||
|
@ -293,7 +293,7 @@
|
|||
"interaction_modal.description.reply": "With an account on Mastodon, you can respond to this post.",
|
||||
"interaction_modal.on_another_server": "On a different server",
|
||||
"interaction_modal.on_this_server": "On this server",
|
||||
"interaction_modal.other_server_instructions": "Simply copy and paste this URL into the search bar of your favourite app or the web interface where you are signed in.",
|
||||
"interaction_modal.other_server_instructions": "Haak en plak hierdie URL in die soek area van jou gunseling toep of die web blaaier waar jy ingeteken is.",
|
||||
"interaction_modal.preamble": "Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one.",
|
||||
"interaction_modal.title.favourite": "Favourite {name}'s post",
|
||||
"interaction_modal.title.follow": "Follow {name}",
|
||||
|
@ -354,14 +354,14 @@
|
|||
"lists.replies_policy.list": "Members of the list",
|
||||
"lists.replies_policy.none": "No one",
|
||||
"lists.replies_policy.title": "Show replies to:",
|
||||
"lists.search": "Search among people you follow",
|
||||
"lists.search": "Soek tussen mense wat jy volg",
|
||||
"lists.subheading": "Your lists",
|
||||
"load_pending": "{count, plural, one {# new item} other {# new items}}",
|
||||
"loading_indicator.label": "Loading...",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Hide image} other {Hide images}}",
|
||||
"missing_indicator.label": "Not found",
|
||||
"missing_indicator.sublabel": "This resource could not be found",
|
||||
"moved_to_account_banner.text": "Your account {disabledAccount} is currently disabled because you moved to {movedToAccount}.",
|
||||
"moved_to_account_banner.text": "Jou rekening {disabledAccount} is tans onaktief omdat jy na {movedToAccount} verhuis het.",
|
||||
"mute_modal.duration": "Duration",
|
||||
"mute_modal.hide_notifications": "Hide notifications from this user?",
|
||||
"mute_modal.indefinite": "Indefinite",
|
||||
|
@ -521,7 +521,7 @@
|
|||
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
|
||||
"search_popout.tips.hashtag": "hits-etiket",
|
||||
"search_popout.tips.status": "status",
|
||||
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
|
||||
"search_popout.tips.text": "Eenvoudige teks bring name, gebruiker name en hits-etikette wat ooreenstem, terug",
|
||||
"search_popout.tips.user": "gebruiker",
|
||||
"search_results.accounts": "Mense",
|
||||
"search_results.all": "Alles",
|
||||
|
@ -530,7 +530,7 @@
|
|||
"search_results.statuses": "Toots",
|
||||
"search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
|
||||
"search_results.title": "Soek vir {q}",
|
||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||
"search_results.total": "{count, number} {count, plural, one {resultaat} other {resultate}}",
|
||||
"server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)",
|
||||
"server_banner.active_users": "active users",
|
||||
"server_banner.administered_by": "Administrasie deur:",
|
||||
|
|
|
@ -189,7 +189,7 @@
|
|||
"dismissable_banner.explore_links": "هذه القصص الإخبارية يتحدث عنها أشخاص على هذا الخوادم الأخرى للشبكة اللامركزية في الوقت الحالي.",
|
||||
"dismissable_banner.explore_statuses": "هذه المشاركات من هذا الخوادم الأخرى في الشبكة اللامركزية تجذب انتباه المستخدمين على هذا الخادم الآن.",
|
||||
"dismissable_banner.explore_tags": "هذه العلامات تكتسب جذب بين الناس على هذا الخوادم الأخرى للشبكة اللامركزية في الوقت الحالي.",
|
||||
"dismissable_banner.public_timeline": "These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.",
|
||||
"dismissable_banner.public_timeline": "هذه هي أحدث المشاركات العامة من الناس على هذا الخادم والخوادم الأخرى للشبكة اللامركزية التي يعرفها هذا الخادم.",
|
||||
"embed.instructions": "يمكنكم إدماج هذا المنشور على موقعكم الإلكتروني عن طريق نسخ الشفرة أدناه.",
|
||||
"embed.preview": "هكذا ما سوف يبدو عليه:",
|
||||
"emoji_button.activity": "الأنشطة",
|
||||
|
@ -534,7 +534,7 @@
|
|||
"server_banner.about_active_users": "الأشخاص الذين يستخدمون هذا الخادم خلال الأيام الثلاثين الأخيرة (المستخدمون النشطون شهريًا)",
|
||||
"server_banner.active_users": "مستخدم نشط",
|
||||
"server_banner.administered_by": "يُديره:",
|
||||
"server_banner.introduction": "{domain} هو جزء من الشبكة الاجتماعية اللامركزية المدعومة من {mastodon}.",
|
||||
"server_banner.introduction": "{domain} هو جزء من الشبكة الاجتماعية اللامركزية التي تعمل بواسطة {mastodon}.",
|
||||
"server_banner.learn_more": "تعلم المزيد",
|
||||
"server_banner.server_stats": "إحصائيات الخادم:",
|
||||
"sign_in_banner.create_account": "أنشئ حسابًا",
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
{
|
||||
"about.blocks": "Модерирани сървъри",
|
||||
"about.contact": "За контакти:",
|
||||
"about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.",
|
||||
"about.disclaimer": "Mastodon е безплатен софтуер с отворен изходен код и търговска марка Mastodon gGmbH.",
|
||||
"about.domain_blocks.comment": "Причина",
|
||||
"about.domain_blocks.domain": "Домейн",
|
||||
"about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the fediverse. These are the exceptions that have been made on this particular server.",
|
||||
"about.domain_blocks.severity": "Severity",
|
||||
"about.domain_blocks.severity": "Взискателност",
|
||||
"about.domain_blocks.silenced.explanation": "You will generally not see profiles and content from this server, unless you explicitly look it up or opt into it by following.",
|
||||
"about.domain_blocks.silenced.title": "Ограничено",
|
||||
"about.domain_blocks.suspended.explanation": "No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible.",
|
||||
"about.domain_blocks.suspended.title": "Suspended",
|
||||
"about.not_available": "This information has not been made available on this server.",
|
||||
"about.powered_by": "Decentralized social media powered by {mastodon}",
|
||||
"about.domain_blocks.suspended.explanation": "Никакви данни от този сървър няма да се обработват, съхранявани или обменяни, правещи невъзможно всяко взаимодействие или комуникация с потребители от тези сървъри.",
|
||||
"about.domain_blocks.suspended.title": "Спряно",
|
||||
"about.not_available": "Тази информация не е била направена налична на този сървър.",
|
||||
"about.powered_by": "Децентрализирана социална мрежа, захранвана от {mastodon}",
|
||||
"about.rules": "Правила на сървъра",
|
||||
"account.account_note_header": "Бележка",
|
||||
"account.add_or_remove_from_list": "Добави или премахни от списъците",
|
||||
"account.badges.bot": "Бот",
|
||||
"account.badges.group": "Група",
|
||||
"account.block": "Блокирай",
|
||||
"account.block_domain": "скрий всичко от (домейн)",
|
||||
"account.block": "Блокиране на @{name}",
|
||||
"account.block_domain": "Блокиране на домейн {domain}",
|
||||
"account.blocked": "Блокирани",
|
||||
"account.browse_more_on_origin_server": "Разглеждане на още в първообразния профил",
|
||||
"account.cancel_follow_request": "Withdraw follow request",
|
||||
|
@ -28,25 +28,25 @@
|
|||
"account.edit_profile": "Редактиране на профила",
|
||||
"account.enable_notifications": "Уведомявайте ме, когато @{name} публикува",
|
||||
"account.endorse": "Характеристика на профила",
|
||||
"account.featured_tags.last_status_at": "Last post on {date}",
|
||||
"account.featured_tags.last_status_at": "Последна публикация на {date}",
|
||||
"account.featured_tags.last_status_never": "Няма публикации",
|
||||
"account.featured_tags.title": "{name}'s featured hashtags",
|
||||
"account.follow": "Последване",
|
||||
"account.followers": "Последователи",
|
||||
"account.followers.empty": "Все още никой не следва този потребител.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} Последовател} other {{counter} Последователи}}",
|
||||
"account.followers.empty": "Още никой не следва потребителя.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} последовател} other {{counter} последователи}}",
|
||||
"account.following": "Последвани",
|
||||
"account.following_counter": "{count, plural, one {{counter} Последван} other {{counter} Последвани}}",
|
||||
"account.follows.empty": "Този потребител все още не следва никого.",
|
||||
"account.follows_you": "Твой последовател",
|
||||
"account.go_to_profile": "Go to profile",
|
||||
"account.following_counter": "{count, plural, one {{counter} последван} other {{counter} последвани}}",
|
||||
"account.follows.empty": "Потребителят още никого не следва.",
|
||||
"account.follows_you": "Ваш последовател",
|
||||
"account.go_to_profile": "Към профила",
|
||||
"account.hide_reblogs": "Скриване на споделяния от @{name}",
|
||||
"account.joined_short": "Joined",
|
||||
"account.joined_short": "Присъединени",
|
||||
"account.languages": "Change subscribed languages",
|
||||
"account.link_verified_on": "Собствеността върху тази връзка е проверена на {date}",
|
||||
"account.locked_info": "Този акаунт е поверително заключен. Собственикът преглежда ръчно кой може да го следва.",
|
||||
"account.locked_info": "Състоянието за поверителността на акаунта е зададено заключено. Собственикът преглежда ръчно от кого може да се следва.",
|
||||
"account.media": "Мултимедия",
|
||||
"account.mention": "Споменаване",
|
||||
"account.mention": "Споменаване на @{name}",
|
||||
"account.moved_to": "{name} has indicated that their new account is now:",
|
||||
"account.mute": "Заглушаване на @{name}",
|
||||
"account.mute_notifications": "Заглушаване на известия от @{name}",
|
||||
|
@ -55,24 +55,24 @@
|
|||
"account.posts_with_replies": "Публикации и отговори",
|
||||
"account.report": "Докладване на @{name}",
|
||||
"account.requested": "Чака се одобрение. Щракнете за отмяна на заявката за последване",
|
||||
"account.share": "Споделяне на @{name} профила",
|
||||
"account.share": "Споделяне на профила на @{name}",
|
||||
"account.show_reblogs": "Показване на споделяния от @{name}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} Публикация} other {{counter} Публикации}}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} публикация} other {{counter} публикации}}",
|
||||
"account.unblock": "Отблокиране на @{name}",
|
||||
"account.unblock_domain": "Unhide {domain}",
|
||||
"account.unblock_short": "Отблокирай",
|
||||
"account.unblock_domain": "Отблокиране на домейн {domain}",
|
||||
"account.unblock_short": "Отблокирване",
|
||||
"account.unendorse": "Не включвайте в профила",
|
||||
"account.unfollow": "Не следвай",
|
||||
"account.unfollow": "Стоп на следването",
|
||||
"account.unmute": "Без заглушаването на @{name}",
|
||||
"account.unmute_notifications": "Раззаглушаване на известия от @{name}",
|
||||
"account.unmute_short": "Unmute",
|
||||
"account.unmute_notifications": "Без заглушаване на известия от @{name}",
|
||||
"account.unmute_short": "Без заглушаването",
|
||||
"account_note.placeholder": "Щракнете, за да добавите бележка",
|
||||
"admin.dashboard.daily_retention": "Ниво на задържани на потребители след регистрация, в дни",
|
||||
"admin.dashboard.monthly_retention": "Ниво на задържани на потребители след регистрация, в месеци",
|
||||
"admin.dashboard.retention.average": "Средно",
|
||||
"admin.dashboard.retention.cohort": "Месец на регистрацията",
|
||||
"admin.dashboard.retention.cohort": "Регистрации за месец",
|
||||
"admin.dashboard.retention.cohort_size": "Нови потребители",
|
||||
"alert.rate_limited.message": "Моля, опитайте отново след {retry_time, time, medium}.",
|
||||
"alert.rate_limited.message": "Опитайте пак след {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Скоростта е ограничена",
|
||||
"alert.unexpected.message": "Възникна неочаквана грешка.",
|
||||
"alert.unexpected.title": "Опаа!",
|
||||
|
@ -81,28 +81,28 @@
|
|||
"audio.hide": "Скриване на звука",
|
||||
"autosuggest_hashtag.per_week": "{count} на седмица",
|
||||
"boost_modal.combo": "Можете да натиснете {combo}, за да пропуснете това следващия път",
|
||||
"bundle_column_error.copy_stacktrace": "Copy error report",
|
||||
"bundle_column_error.error.body": "The requested page could not be rendered. It could be due to a bug in our code, or a browser compatibility issue.",
|
||||
"bundle_column_error.error.title": "Oh, no!",
|
||||
"bundle_column_error.network.body": "There was an error when trying to load this page. This could be due to a temporary problem with your internet connection or this server.",
|
||||
"bundle_column_error.copy_stacktrace": "Копиране на доклада за грешката",
|
||||
"bundle_column_error.error.body": "Заявената страница не може да се изобрази. Това може да е заради грешка в кода ни или проблем със съвместимостта на браузъра.",
|
||||
"bundle_column_error.error.title": "О, не!",
|
||||
"bundle_column_error.network.body": "Възникна грешка, опитвайки зареждане на страницата. Това може да е заради временен проблем с интернет връзката ви или този сървър.",
|
||||
"bundle_column_error.network.title": "Мрежова грешка",
|
||||
"bundle_column_error.retry": "Нов опит",
|
||||
"bundle_column_error.return": "Обратно към началото",
|
||||
"bundle_column_error.routing.body": "The requested page could not be found. Are you sure the URL in the address bar is correct?",
|
||||
"bundle_column_error.routing.body": "Заявената страница не може да се намери. Сигурни ли сте, че URL адресът в адресната лента е правилен?",
|
||||
"bundle_column_error.routing.title": "404",
|
||||
"bundle_modal_error.close": "Затваряне",
|
||||
"bundle_modal_error.message": "Нещо се обърка, зареждайки компонента.",
|
||||
"bundle_modal_error.retry": "Нов опит",
|
||||
"closed_registrations.other_server_instructions": "Поради това че Mastodon е децентрализиран, можеш да създадеш акаунт на друг сървър, от който можеш да комуникираш с този.",
|
||||
"closed_registrations_modal.description": "Creating an account on {domain} is currently not possible, but please keep in mind that you do not need an account specifically on {domain} to use Mastodon.",
|
||||
"closed_registrations_modal.find_another_server": "Find another server",
|
||||
"closed_registrations_modal.preamble": "Mastodon is decentralized, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
|
||||
"closed_registrations_modal.title": "Signing up on Mastodon",
|
||||
"column.about": "About",
|
||||
"closed_registrations_modal.description": "Създаването на акаунт в {domain} сега не е възможно, но обърнете внимание, че нямате нужда от акаунт конкретно на {domain}, за да ползвате Mastodon.",
|
||||
"closed_registrations_modal.find_another_server": "Намиране на друг сървър",
|
||||
"closed_registrations_modal.preamble": "Mastodon е децентрализиран, така че няма значение къде създавате акаунта си, ще може да последвате и взаимодействате с всеки на този сървър. Може дори да стартирате свой собствен сървър!",
|
||||
"closed_registrations_modal.title": "Регистриране в Mastodon",
|
||||
"column.about": "Относно",
|
||||
"column.blocks": "Блокирани потребители",
|
||||
"column.bookmarks": "Отметки",
|
||||
"column.community": "Локална емисия",
|
||||
"column.direct": "Лични съобщения",
|
||||
"column.direct": "Директни съобщения",
|
||||
"column.directory": "Разглеждане на профили",
|
||||
"column.domain_blocks": "Блокирани домейни",
|
||||
"column.favourites": "Любими",
|
||||
|
@ -127,11 +127,11 @@
|
|||
"compose.language.change": "Смяна на езика",
|
||||
"compose.language.search": "Търсене на езици...",
|
||||
"compose_form.direct_message_warning_learn_more": "Още информация",
|
||||
"compose_form.encryption_warning": "Поставете в Мастодон не са криптирани от край до край. Не споделяйте никаква чувствителна информация.",
|
||||
"compose_form.encryption_warning": "Публикациите в Mastodon не са криптирани от край до край. Не споделяйте никаква чувствителна информация там.",
|
||||
"compose_form.hashtag_warning": "Тази публикация няма да бъде изброена под нито един хаштаг, тъй като е скрита. Само публични публикации могат да се търсят по хаштаг.",
|
||||
"compose_form.lock_disclaimer": "Вашият акаунт не е {locked}. Всеки може да ви последва, за да прегледа вашите публикации само за последователи.",
|
||||
"compose_form.lock_disclaimer.lock": "заключено",
|
||||
"compose_form.placeholder": "Какво си мислиш?",
|
||||
"compose_form.placeholder": "Какво мислите?",
|
||||
"compose_form.poll.add_option": "Добавяне на избор",
|
||||
"compose_form.poll.duration": "Времетраене на анкетата",
|
||||
"compose_form.poll.option_placeholder": "Избор {number}",
|
||||
|
@ -146,51 +146,51 @@
|
|||
"compose_form.sensitive.unmarked": "{count, plural, one {Мултимедията не е маркирана като деликатна} other {Мултимедиите не са маркирани като деликатни}}",
|
||||
"compose_form.spoiler.marked": "Текстът е скрит зад предупреждение",
|
||||
"compose_form.spoiler.unmarked": "Текстът не е скрит",
|
||||
"compose_form.spoiler_placeholder": "Content warning",
|
||||
"compose_form.spoiler_placeholder": "Тук напишете предупреждението си",
|
||||
"confirmation_modal.cancel": "Отказ",
|
||||
"confirmations.block.block_and_report": "Блокиране и докладване",
|
||||
"confirmations.block.confirm": "Блокиране",
|
||||
"confirmations.block.message": "Наистина ли искате да блокирате {name}?",
|
||||
"confirmations.cancel_follow_request.confirm": "Withdraw request",
|
||||
"confirmations.cancel_follow_request.message": "Are you sure you want to withdraw your request to follow {name}?",
|
||||
"confirmations.cancel_follow_request.confirm": "Оттегляне на заявката",
|
||||
"confirmations.cancel_follow_request.message": "Наистина ли искате да оттеглите заявката си да последвате {name}?",
|
||||
"confirmations.delete.confirm": "Изтриване",
|
||||
"confirmations.delete.message": "Наистина ли искате да изтриете публикацията?",
|
||||
"confirmations.delete_list.confirm": "Изтриване",
|
||||
"confirmations.delete_list.message": "Наистина ли искате да изтриете завинаги този списък?",
|
||||
"confirmations.discard_edit_media.confirm": "Отмени",
|
||||
"confirmations.discard_edit_media.message": "Не сте запазили промени на описанието или огледа на медията, отхвърляте ли ги въпреки това?",
|
||||
"confirmations.discard_edit_media.confirm": "Отхвърляне",
|
||||
"confirmations.discard_edit_media.message": "Не сте запазили промени на описанието или огледа на мултимедията, отхвърляте ли ги?",
|
||||
"confirmations.domain_block.confirm": "Блокиране на целия домейн",
|
||||
"confirmations.domain_block.message": "Наистина ли искате да блокирате целия {domain}? В повечето случаи няколко блокирания или заглушавания са достатъчно и за предпочитане. Няма да виждате съдържание от домейна из публичните места или известията си. Вашите последователи от този домейн ще се премахнат.",
|
||||
"confirmations.logout.confirm": "Излизане",
|
||||
"confirmations.logout.message": "Наистина ли искате да излезете?",
|
||||
"confirmations.mute.confirm": "Заглушаване",
|
||||
"confirmations.mute.explanation": "Това ще скрие публикации от тях и публикации, които ги споменават, но все пак ще им позволи да виждат вашите публикации и да ви следват.",
|
||||
"confirmations.mute.message": "Сигурни ли сте, че искате да заглушите {name}?",
|
||||
"confirmations.mute.message": "Наистина ли искате да заглушите {name}?",
|
||||
"confirmations.redraft.confirm": "Изтриване и преработване",
|
||||
"confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
|
||||
"confirmations.reply.confirm": "Отговор",
|
||||
"confirmations.reply.message": "Отговарянето сега ще замени съобщението, което в момента съставяте. Сигурни ли сте, че искате да продължите?",
|
||||
"confirmations.unfollow.confirm": "Отследване",
|
||||
"confirmations.unfollow.confirm": "Без следване",
|
||||
"confirmations.unfollow.message": "Наистина ли искате да не следвате {name}?",
|
||||
"conversation.delete": "Изтриване на разговора",
|
||||
"conversation.mark_as_read": "Маркиране като прочетено",
|
||||
"conversation.open": "Преглед на разговора",
|
||||
"conversation.with": "С {names}",
|
||||
"copypaste.copied": "Копирано",
|
||||
"copypaste.copy": "Copy",
|
||||
"copypaste.copy": "Копиране",
|
||||
"directory.federated": "От познат федивърс",
|
||||
"directory.local": "Само от {domain}",
|
||||
"directory.new_arrivals": "Новодошли",
|
||||
"directory.recently_active": "Наскоро активни",
|
||||
"disabled_account_banner.account_settings": "Account settings",
|
||||
"disabled_account_banner.text": "Your account {disabledAccount} is currently disabled.",
|
||||
"dismissable_banner.community_timeline": "These are the most recent public posts from people whose accounts are hosted by {domain}.",
|
||||
"dismissable_banner.dismiss": "Dismiss",
|
||||
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
|
||||
"dismissable_banner.explore_statuses": "These posts from this and other servers in the decentralized network are gaining traction on this server right now.",
|
||||
"dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
|
||||
"dismissable_banner.public_timeline": "These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.",
|
||||
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||
"disabled_account_banner.account_settings": "Настройки на акаунта",
|
||||
"disabled_account_banner.text": "Вашият акаунт {disabledAccount} сега е изключен.",
|
||||
"dismissable_banner.community_timeline": "Ето най-скорошните публични публикации от хора, чиито акаунти са разположени в {domain}.",
|
||||
"dismissable_banner.dismiss": "Отхвърляне",
|
||||
"dismissable_banner.explore_links": "Тези новини се разказват от хората в този и други сървъри на децентрализираната мрежа точно сега.",
|
||||
"dismissable_banner.explore_statuses": "Тези публикации от този и други сървъри в децентрализираната мрежа набират популярност сега на този сървър.",
|
||||
"dismissable_banner.explore_tags": "Тези хаштагове сега набират популярност сред хората в този и други сървъри на децентрализирата мрежа.",
|
||||
"dismissable_banner.public_timeline": "Ето най-скорошните публични публикации от хора в този и други сървъри на децентрализираната мрежа, за които този сървър познава.",
|
||||
"embed.instructions": "Вградете публикацията в уебсайта си, копирайки кода долу.",
|
||||
"embed.preview": "Ето как ще изглежда:",
|
||||
"emoji_button.activity": "Дейност",
|
||||
"emoji_button.clear": "Изчистване",
|
||||
|
@ -223,7 +223,7 @@
|
|||
"empty_column.hashtag": "Още няма нищо в този хаштаг.",
|
||||
"empty_column.home": "Вашата начална емисия е празна! Посетете {public} или използвайте търсене, за да започнете и да се запознаете с други потребители.",
|
||||
"empty_column.home.suggestions": "Преглед на някои предложения",
|
||||
"empty_column.list": "There is nothing in this list yet.",
|
||||
"empty_column.list": "Още няма нищо в този списък. Когато членовете на списъка публикуват нови публикации, то те ще се появят тук.",
|
||||
"empty_column.lists": "Все още нямате списъци. Когато създадете такъв, той ще се покаже тук.",
|
||||
"empty_column.mutes": "Още не сте заглушавали потребители.",
|
||||
"empty_column.notifications": "Все още нямате известия. Взаимодействайте с другите, за да започнете разговора.",
|
||||
|
@ -231,7 +231,7 @@
|
|||
"error.unexpected_crash.explanation": "Поради грешка в нашия код или проблем със съвместимостта на браузъра, тази страница не може да се покаже правилно.",
|
||||
"error.unexpected_crash.explanation_addons": "Тази страница не може да се покаже правилно. Тази грешка вероятно е причинена от добавка на браузъра или инструменти за автоматичен превод.",
|
||||
"error.unexpected_crash.next_steps": "Опитайте да опресните страницата. Ако това не помогне, все още можете да използвате Mastodon чрез различен браузър или приложение.",
|
||||
"error.unexpected_crash.next_steps_addons": "Опитайте да ги деактивирате и да опресните страницата. Ако това не помогне, може все още да използвате Mastodon чрез различен браузър или приложение.",
|
||||
"error.unexpected_crash.next_steps_addons": "Опитайте се да ги изключите и да опресните страницата. Ако това не помогне, то още може да използвате Mastodon чрез различен браузър или приложение.",
|
||||
"errors.unexpected_crash.copy_stacktrace": "Копиране на stacktrace-а в клипборда",
|
||||
"errors.unexpected_crash.report_issue": "Сигнал за проблем",
|
||||
"explore.search_results": "Резултати от търсенето",
|
||||
|
@ -243,32 +243,32 @@
|
|||
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
|
||||
"filter_modal.added.context_mismatch_title": "Несъвпадение на контекста!",
|
||||
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
|
||||
"filter_modal.added.expired_title": "Изтекал филтър!",
|
||||
"filter_modal.added.expired_title": "Изтекъл филтър!",
|
||||
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
|
||||
"filter_modal.added.review_and_configure_title": "Настройки на филтър",
|
||||
"filter_modal.added.review_and_configure_title": "Настройки на филтъра",
|
||||
"filter_modal.added.settings_link": "страница с настройки",
|
||||
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
|
||||
"filter_modal.added.title": "Филтърът е добавен!",
|
||||
"filter_modal.select_filter.context_mismatch": "не е приложимо за този контекст",
|
||||
"filter_modal.select_filter.expired": "изтекло",
|
||||
"filter_modal.select_filter.prompt_new": "Нова категория: {name}",
|
||||
"filter_modal.select_filter.search": "Търси или създай",
|
||||
"filter_modal.select_filter.search": "Търсене или създаване",
|
||||
"filter_modal.select_filter.subtitle": "Изберете съществуваща категория или създайте нова",
|
||||
"filter_modal.select_filter.title": "Филтриране на поста",
|
||||
"filter_modal.title.status": "Филтрирай пост",
|
||||
"filter_modal.select_filter.title": "Филтриране на публ.",
|
||||
"filter_modal.title.status": "Филтриране на публ.",
|
||||
"follow_recommendations.done": "Готово",
|
||||
"follow_recommendations.heading": "Следвайте хора, които харесвате, за да виждате техните съобщения! Ето някои предложения.",
|
||||
"follow_recommendations.lead": "Съобщения от хора, които следвате, ще се показват в хронологичен ред на вашата главна страница. Не се страхувайте, че ще сгрешите, по всяко време много лесно можете да спрете да ги следвате!",
|
||||
"follow_recommendations.heading": "Следвайте хора, от които харесвате да виждате публикации! Ето някои предложения.",
|
||||
"follow_recommendations.lead": "Публикациите от хората, които следвате, ще се показват в хронологично в началния ви инфопоток. Не се страхувайте, че ще сгрешите, по всяко време много лесно може да спрете да ги следвате!",
|
||||
"follow_request.authorize": "Упълномощаване",
|
||||
"follow_request.reject": "Отхвърляне",
|
||||
"follow_requests.unlocked_explanation": "Въпреки че акаунтът ви не е заключен, служителите на {domain} помислиха, че може да искате да преглеждате ръчно заявките за последване на тези профили.",
|
||||
"footer.about": "About",
|
||||
"footer.directory": "Profiles directory",
|
||||
"footer.get_app": "Get the app",
|
||||
"footer.invite": "Invite people",
|
||||
"footer.about": "Относно",
|
||||
"footer.directory": "Директория на профилите",
|
||||
"footer.get_app": "Вземане на приложението",
|
||||
"footer.invite": "Поканване на хора",
|
||||
"footer.keyboard_shortcuts": "Клавишни съчетания",
|
||||
"footer.privacy_policy": "Политика за поверителност",
|
||||
"footer.source_code": "View source code",
|
||||
"footer.source_code": "Преглед на изходния код",
|
||||
"generic.saved": "Запазено",
|
||||
"getting_started.heading": "Първи стъпки",
|
||||
"hashtag.column_header.tag_mode.all": "и {additional}",
|
||||
|
@ -291,33 +291,33 @@
|
|||
"interaction_modal.description.follow": "With an account on Mastodon, you can follow {name} to receive their posts in your home feed.",
|
||||
"interaction_modal.description.reblog": "With an account on Mastodon, you can boost this post to share it with your own followers.",
|
||||
"interaction_modal.description.reply": "With an account on Mastodon, you can respond to this post.",
|
||||
"interaction_modal.on_another_server": "On a different server",
|
||||
"interaction_modal.on_this_server": "On this server",
|
||||
"interaction_modal.other_server_instructions": "Simply copy and paste this URL into the search bar of your favourite app or the web interface where you are signed in.",
|
||||
"interaction_modal.preamble": "Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one.",
|
||||
"interaction_modal.title.favourite": "Favourite {name}'s post",
|
||||
"interaction_modal.on_another_server": "На различен сървър",
|
||||
"interaction_modal.on_this_server": "На този сървър",
|
||||
"interaction_modal.other_server_instructions": "Просто копипействате URL адреса в лентата за търсене на любимото си приложение или уеб интерфейс, където сте влезли.",
|
||||
"interaction_modal.preamble": "Откак Mastodon е децентрализиран, може да употребявате съществуващ акаунт, разположен на друг сървър на Mastodon или съвместима платформа, ако нямате акаунт на този сървър.",
|
||||
"interaction_modal.title.favourite": "Любими публикации на {name}",
|
||||
"interaction_modal.title.follow": "Последване на {name}",
|
||||
"interaction_modal.title.reblog": "Boost {name}'s post",
|
||||
"interaction_modal.title.reply": "Reply to {name}'s post",
|
||||
"interaction_modal.title.reply": "Отговаряне на публикацията на {name}",
|
||||
"intervals.full.days": "{number, plural, one {# ден} other {# дни}}",
|
||||
"intervals.full.hours": "{number, plural, one {# час} other {# часа}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# минута} other {# минути}}",
|
||||
"keyboard_shortcuts.back": "за придвижване назад",
|
||||
"keyboard_shortcuts.blocked": "за отваряне на списъка с блокирани потребители",
|
||||
"keyboard_shortcuts.back": "Навигиране назад",
|
||||
"keyboard_shortcuts.blocked": "Отваряне на списъка с блокирани потребители",
|
||||
"keyboard_shortcuts.boost": "за споделяне",
|
||||
"keyboard_shortcuts.column": "Съсредоточение на колона",
|
||||
"keyboard_shortcuts.compose": "за фокусиране на текстовото пространство за композиране",
|
||||
"keyboard_shortcuts.description": "Описание",
|
||||
"keyboard_shortcuts.direct": "to open direct messages column",
|
||||
"keyboard_shortcuts.down": "за придвижване надолу в списъка",
|
||||
"keyboard_shortcuts.enter": "to open status",
|
||||
"keyboard_shortcuts.direct": "за отваряне на колоната с директни съобщения",
|
||||
"keyboard_shortcuts.down": "Преместване надолу в списъка",
|
||||
"keyboard_shortcuts.enter": "Отваряне на публикация",
|
||||
"keyboard_shortcuts.favourite": "Любима публикация",
|
||||
"keyboard_shortcuts.favourites": "Отваряне на списъка с любими",
|
||||
"keyboard_shortcuts.federated": "да отвори обединена хронология",
|
||||
"keyboard_shortcuts.heading": "Клавишни съчетания",
|
||||
"keyboard_shortcuts.home": "за отваряне на началната емисия",
|
||||
"keyboard_shortcuts.hotkey": "Бърз клавиш",
|
||||
"keyboard_shortcuts.legend": "за показване на тази легенда",
|
||||
"keyboard_shortcuts.legend": "Показване на тази легенда",
|
||||
"keyboard_shortcuts.local": "за отваряне на локалната емисия",
|
||||
"keyboard_shortcuts.mention": "Споменаване на автор",
|
||||
"keyboard_shortcuts.muted": "Отваряне на списъка със заглушени потребители",
|
||||
|
@ -332,17 +332,17 @@
|
|||
"keyboard_shortcuts.spoilers": "за показване/скриване на ПС полето",
|
||||
"keyboard_shortcuts.start": "за отваряне на колоната \"първи стъпки\"",
|
||||
"keyboard_shortcuts.toggle_hidden": "за показване/скриване на текст зад ПС",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Показване/скриване на мултимедия",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Показване/скриване на мултимедията",
|
||||
"keyboard_shortcuts.toot": "Начало на нова публикация",
|
||||
"keyboard_shortcuts.unfocus": "за дефокусиране на текстовото поле за композиране/търсене",
|
||||
"keyboard_shortcuts.up": "за придвижване нагоре в списъка",
|
||||
"keyboard_shortcuts.up": "Преместване нагоре в списъка",
|
||||
"lightbox.close": "Затваряне",
|
||||
"lightbox.compress": "Компресиране на полето за преглед на изображение",
|
||||