Merge commit '24ef8255b3f9b44cb54f49bc78fe3382a7070b1a' into glitch-soc/merge-upstream

Conflicts:
- `app/helpers/accounts_helper.rb`:
  Upstream removed a helper, textually adjacent to a glitch-soc-only one.
  Not really a conflict.
  Removed the helper as upstream did.
- `app/views/layouts/embedded.html.haml`:
  Conflicts due to theming system.
  Adapted upstream's change to our theming system.
- `app/views/statuses/_simple_status.html.haml`:
  Removed upstream, but we had local changes.
  Removed as upstream did.
This commit is contained in:
Claire 2024-09-12 20:05:08 +02:00
commit 3465d39494
126 changed files with 991 additions and 2169 deletions

View File

@ -19,14 +19,6 @@ module AccountsHelper
end
end
def account_action_button(account)
return if account.memorial? || account.moved?
link_to ActivityPub::TagManager.instance.url_for(account), class: 'button logo-button', target: '_new' do
safe_join([logo_as_symbol, t('accounts.follow')])
end
end
def hide_followers_count?(account)
Setting.hide_followers_count || account.user&.settings&.[]('hide_followers_count')
end

View File

@ -57,26 +57,6 @@ module MediaComponentHelper
end
end
def render_card_component(status, **options)
component_params = {
sensitive: sensitive_viewer?(status, current_account),
card: serialize_status_card(status).as_json,
}.merge(**options)
react_component :card, component_params
end
def render_poll_component(status, **options)
component_params = {
disabled: true,
poll: serialize_status_poll(status).as_json,
}.merge(**options)
react_component :poll, component_params do
render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: prefers_autoplay? }
end
end
private
def serialize_media_attachment(attachment)
@ -86,22 +66,6 @@ module MediaComponentHelper
)
end
def serialize_status_card(status)
ActiveModelSerializers::SerializableResource.new(
status.preview_card,
serializer: REST::PreviewCardSerializer
)
end
def serialize_status_poll(status)
ActiveModelSerializers::SerializableResource.new(
status.preloadable_poll,
serializer: REST::PollSerializer,
scope: current_user,
scope_name: :current_user
)
end
def sensitive_viewer?(status, account)
if !account.nil? && account.id == status.account_id
status.sensitive

View File

@ -0,0 +1,74 @@
import './public-path';
import { createRoot } from 'react-dom/client';
import { afterInitialRender } from 'mastodon/../hooks/useRenderSignal';
import { start } from '../mastodon/common';
import { Status } from '../mastodon/features/standalone/status';
import { loadPolyfills } from '../mastodon/polyfills';
import ready from '../mastodon/ready';
start();
function loaded() {
const mountNode = document.getElementById('mastodon-status');
if (mountNode) {
const attr = mountNode.getAttribute('data-props');
if (!attr) return;
const props = JSON.parse(attr) as { id: string; locale: string };
const root = createRoot(mountNode);
root.render(<Status {...props} />);
}
}
function main() {
ready(loaded).catch((error: unknown) => {
console.error(error);
});
}
loadPolyfills()
.then(main)
.catch((error: unknown) => {
console.error(error);
});
interface SetHeightMessage {
type: 'setHeight';
id: string;
height: number;
}
function isSetHeightMessage(data: unknown): data is SetHeightMessage {
if (
data &&
typeof data === 'object' &&
'type' in data &&
data.type === 'setHeight'
)
return true;
else return false;
}
window.addEventListener('message', (e) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typings are not correct, it can be null in very rare cases
if (!e.data || !isSetHeightMessage(e.data) || !window.parent) return;
const data = e.data;
// We use a timeout to allow for the React page to render before calculating the height
afterInitialRender(() => {
window.parent.postMessage(
{
type: 'setHeight',
id: data.id,
height: document.getElementsByTagName('html')[0]?.scrollHeight,
},
'*',
);
});
});

View File

@ -37,43 +37,6 @@ const messages = defineMessages({
},
});
interface SetHeightMessage {
type: 'setHeight';
id: string;
height: number;
}
function isSetHeightMessage(data: unknown): data is SetHeightMessage {
if (
data &&
typeof data === 'object' &&
'type' in data &&
data.type === 'setHeight'
)
return true;
else return false;
}
window.addEventListener('message', (e) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typings are not correct, it can be null in very rare cases
if (!e.data || !isSetHeightMessage(e.data) || !window.parent) return;
const data = e.data;
ready(() => {
window.parent.postMessage(
{
type: 'setHeight',
id: data.id,
height: document.getElementsByTagName('html')[0]?.scrollHeight,
},
'*',
);
}).catch((e: unknown) => {
console.error('Error in setHeightMessage postMessage', e);
});
});
function loaded() {
const { messages: localeData } = getLocale();

View File

@ -0,0 +1,32 @@
// This hook allows a component to signal that it's done rendering in a way that
// can be used by e.g. our embed code to determine correct iframe height
let renderSignalReceived = false;
type Callback = () => void;
let onInitialRender: Callback;
export const afterInitialRender = (callback: Callback) => {
if (renderSignalReceived) {
callback();
} else {
onInitialRender = callback;
}
};
export const useRenderSignal = () => {
return () => {
if (renderSignalReceived) {
return;
}
renderSignalReceived = true;
if (typeof onInitialRender !== 'undefined') {
window.requestAnimationFrame(() => {
onInitialRender();
});
}
};
};

View File

@ -49,11 +49,13 @@ export function fetchStatusRequest(id, skipLoading) {
};
}
export function fetchStatus(id, forceFetch = false) {
export function fetchStatus(id, forceFetch = false, alsoFetchContext = true) {
return (dispatch, getState) => {
const skipLoading = !forceFetch && getState().getIn(['statuses', id], null) !== null;
dispatch(fetchContext(id));
if (alsoFetchContext) {
dispatch(fetchContext(id));
}
if (skipLoading) {
return;

View File

@ -0,0 +1,90 @@
import { useRef, useState, useCallback } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
import { useTimeout } from 'mastodon/../hooks/useTimeout';
import { Icon } from 'mastodon/components/icon';
export const CopyPasteText: React.FC<{ value: string }> = ({ value }) => {
const inputRef = useRef<HTMLTextAreaElement>(null);
const [copied, setCopied] = useState(false);
const [focused, setFocused] = useState(false);
const [setAnimationTimeout] = useTimeout();
const handleInputClick = useCallback(() => {
setCopied(false);
if (inputRef.current) {
inputRef.current.focus();
inputRef.current.select();
inputRef.current.setSelectionRange(0, value.length);
}
}, [setCopied, value]);
const handleButtonClick = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
void navigator.clipboard.writeText(value);
inputRef.current?.blur();
setCopied(true);
setAnimationTimeout(() => {
setCopied(false);
}, 700);
},
[setCopied, setAnimationTimeout, value],
);
const handleKeyUp = useCallback(
(e: React.KeyboardEvent) => {
if (e.key !== ' ') return;
void navigator.clipboard.writeText(value);
setCopied(true);
setAnimationTimeout(() => {
setCopied(false);
}, 700);
},
[setCopied, setAnimationTimeout, value],
);
const handleFocus = useCallback(() => {
setFocused(true);
}, [setFocused]);
const handleBlur = useCallback(() => {
setFocused(false);
}, [setFocused]);
return (
<div
className={classNames('copy-paste-text', { copied, focused })}
tabIndex={0}
role='button'
onClick={handleInputClick}
onKeyUp={handleKeyUp}
>
<textarea
readOnly
value={value}
ref={inputRef}
onClick={handleInputClick}
onFocus={handleFocus}
onBlur={handleBlur}
/>
<button className='button' onClick={handleButtonClick}>
<Icon id='copy' icon={ContentCopyIcon} />{' '}
{copied ? (
<FormattedMessage id='copypaste.copied' defaultMessage='Copied' />
) : (
<FormattedMessage
id='copypaste.copy_to_clipboard'
defaultMessage='Copy to clipboard'
/>
)}
</button>
</div>
);
};

View File

@ -7,6 +7,13 @@ export const WordmarkLogo: React.FC = () => (
</svg>
);
export const IconLogo: React.FC = () => (
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
<title>Mastodon</title>
<use xlinkHref='#logo-symbol-icon' />
</svg>
);
export const SymbolLogo: React.FC = () => (
<img src={logo} alt='Mastodon' className='logo logo--icon' />
);

View File

@ -2,14 +2,12 @@ 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'>
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
<use xlinkHref='#logo-symbol-icon' />
</svg>
<IconLogo />
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <AuthorLink accountId={accountId} /> }} />
</div>
);

View File

@ -55,7 +55,7 @@ const messages = defineMessages({
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
embed: { id: 'status.embed', defaultMessage: 'Embed' },
embed: { id: 'status.embed', defaultMessage: 'Get embed code' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },

View File

@ -6,7 +6,6 @@ import {
unmuteAccount,
unblockAccount,
} from '../actions/accounts';
import { showAlertForError } from '../actions/alerts';
import { initBlockModal } from '../actions/blocks';
import {
replyCompose,
@ -100,10 +99,7 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
onEmbed (status) {
dispatch(openModal({
modalType: 'EMBED',
modalProps: {
id: status.get('id'),
onError: error => dispatch(showAlertForError(error)),
},
modalProps: { id: status.get('id') },
}));
},

View File

@ -10,8 +10,8 @@ import { Link } from 'react-router-dom';
import SwipeableViews from 'react-swipeable-views';
import ArrowRightAltIcon from '@/material-icons/400-24px/arrow_right_alt.svg?react';
import ContentCopyIcon from '@/material-icons/400-24px/content_copy.svg?react';
import { ColumnBackButton } from 'mastodon/components/column_back_button';
import { CopyPasteText } from 'mastodon/components/copy_paste_text';
import { Icon } from 'mastodon/components/icon';
import { me, domain } from 'mastodon/initial_state';
import { useAppSelector } from 'mastodon/store';
@ -20,67 +20,6 @@ const messages = defineMessages({
shareableMessage: { id: 'onboarding.share.message', defaultMessage: 'I\'m {username} on #Mastodon! Come follow me at {url}' },
});
class CopyPasteText extends PureComponent {
static propTypes = {
value: PropTypes.string,
};
state = {
copied: false,
focused: false,
};
setRef = c => {
this.input = c;
};
handleInputClick = () => {
this.setState({ copied: false });
this.input.focus();
this.input.select();
this.input.setSelectionRange(0, this.props.value.length);
};
handleButtonClick = e => {
e.stopPropagation();
const { value } = this.props;
navigator.clipboard.writeText(value);
this.input.blur();
this.setState({ copied: true });
this.timeout = setTimeout(() => this.setState({ copied: false }), 700);
};
handleFocus = () => {
this.setState({ focused: true });
};
handleBlur = () => {
this.setState({ focused: false });
};
componentWillUnmount () {
if (this.timeout) clearTimeout(this.timeout);
}
render () {
const { value } = this.props;
const { copied, focused } = this.state;
return (
<div className={classNames('copy-paste-text', { copied, focused })} tabIndex='0' role='button' onClick={this.handleInputClick}>
<textarea readOnly value={value} ref={this.setRef} onClick={this.handleInputClick} onFocus={this.handleFocus} onBlur={this.handleBlur} />
<button className='button' onClick={this.handleButtonClick}>
<Icon id='copy' icon={ContentCopyIcon} /> {copied ? <FormattedMessage id='copypaste.copied' defaultMessage='Copied' /> : <FormattedMessage id='copypaste.copy_to_clipboard' defaultMessage='Copy to clipboard' />}
</button>
</div>
);
}
}
class TipCarousel extends PureComponent {
static propTypes = {

View File

@ -0,0 +1,87 @@
/* eslint-disable @typescript-eslint/no-unsafe-return,
@typescript-eslint/no-explicit-any,
@typescript-eslint/no-unsafe-assignment */
import { useEffect, useCallback } from 'react';
import { Provider } from 'react-redux';
import { useRenderSignal } from 'mastodon/../hooks/useRenderSignal';
import { fetchStatus, toggleStatusSpoilers } from 'mastodon/actions/statuses';
import { hydrateStore } from 'mastodon/actions/store';
import { Router } from 'mastodon/components/router';
import { DetailedStatus } from 'mastodon/features/status/components/detailed_status';
import initialState from 'mastodon/initial_state';
import { IntlProvider } from 'mastodon/locales';
import { makeGetStatus, makeGetPictureInPicture } from 'mastodon/selectors';
import { store, useAppSelector, useAppDispatch } from 'mastodon/store';
const getStatus = makeGetStatus() as unknown as (arg0: any, arg1: any) => any;
const getPictureInPicture = makeGetPictureInPicture() as unknown as (
arg0: any,
arg1: any,
) => any;
const Embed: React.FC<{ id: string }> = ({ id }) => {
const status = useAppSelector((state) => getStatus(state, { id }));
const pictureInPicture = useAppSelector((state) =>
getPictureInPicture(state, { id }),
);
const domain = useAppSelector((state) => state.meta.get('domain'));
const dispatch = useAppDispatch();
const dispatchRenderSignal = useRenderSignal();
useEffect(() => {
dispatch(fetchStatus(id, false, false));
}, [dispatch, id]);
const handleToggleHidden = useCallback(() => {
dispatch(toggleStatusSpoilers(id));
}, [dispatch, id]);
// This allows us to calculate the correct page height for embeds
if (status) {
dispatchRenderSignal();
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
const permalink = status?.get('url') as string;
return (
<div className='embed'>
<DetailedStatus
status={status}
domain={domain}
pictureInPicture={pictureInPicture}
onToggleHidden={handleToggleHidden}
withLogo
/>
<a
className='embed__overlay'
href={permalink}
target='_blank'
rel='noreferrer noopener'
aria-label=''
/>
</div>
);
};
export const Status: React.FC<{ id: string }> = ({ id }) => {
useEffect(() => {
if (initialState) {
store.dispatch(hydrateStore(initialState));
}
}, []);
return (
<IntlProvider>
<Provider store={store}>
<Router>
<Embed id={id} />
</Router>
</Provider>
</IntlProvider>
);
};

View File

@ -49,7 +49,7 @@ const messages = defineMessages({
share: { id: 'status.share', defaultMessage: 'Share' },
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
embed: { id: 'status.embed', defaultMessage: 'Embed' },
embed: { id: 'status.embed', defaultMessage: 'Get embed code' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },

View File

@ -1,322 +0,0 @@
import PropTypes from 'prop-types';
import { FormattedDate, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link, withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
import { AnimatedNumber } from 'mastodon/components/animated_number';
import { ContentWarning } from 'mastodon/components/content_warning';
import EditedTimestamp from 'mastodon/components/edited_timestamp';
import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar';
import { Icon } from 'mastodon/components/icon';
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { Avatar } from '../../../components/avatar';
import { DisplayName } from '../../../components/display_name';
import MediaGallery from '../../../components/media_gallery';
import StatusContent from '../../../components/status_content';
import Audio from '../../audio';
import scheduleIdleTask from '../../ui/util/schedule_idle_task';
import Video from '../../video';
import Card from './card';
class DetailedStatus extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map,
onOpenMedia: PropTypes.func.isRequired,
onOpenVideo: PropTypes.func.isRequired,
onToggleHidden: PropTypes.func.isRequired,
onTranslate: PropTypes.func.isRequired,
measureHeight: PropTypes.bool,
onHeightChange: PropTypes.func,
domain: PropTypes.string.isRequired,
compact: PropTypes.bool,
showMedia: PropTypes.bool,
pictureInPicture: ImmutablePropTypes.contains({
inUse: PropTypes.bool,
available: PropTypes.bool,
}),
onToggleMediaVisibility: PropTypes.func,
...WithRouterPropTypes,
};
state = {
height: null,
};
handleAccountClick = (e) => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.props.history) {
e.preventDefault();
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
}
e.stopPropagation();
};
handleOpenVideo = (options) => {
this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options);
};
handleExpandedToggle = () => {
this.props.onToggleHidden(this.props.status);
};
_measureHeight (heightJustChanged) {
if (this.props.measureHeight && this.node) {
scheduleIdleTask(() => this.node && this.setState({ height: Math.ceil(this.node.scrollHeight) + 1 }));
if (this.props.onHeightChange && heightJustChanged) {
this.props.onHeightChange();
}
}
}
setRef = c => {
this.node = c;
this._measureHeight();
};
componentDidUpdate (prevProps, prevState) {
this._measureHeight(prevState.height !== this.state.height);
}
handleModalLink = e => {
e.preventDefault();
let href;
if (e.target.nodeName !== 'A') {
href = e.target.parentNode.href;
} else {
href = e.target.href;
}
window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
};
handleTranslate = () => {
const { onTranslate, status } = this.props;
onTranslate(status);
};
_properStatus () {
const { status } = this.props;
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
return status.get('reblog');
} else {
return status;
}
}
getAttachmentAspectRatio () {
const attachments = this._properStatus().get('media_attachments');
if (attachments.getIn([0, 'type']) === 'video') {
return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`;
} else if (attachments.getIn([0, 'type']) === 'audio') {
return '16 / 9';
} else {
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2';
}
}
render () {
const status = this._properStatus();
const outerStyle = { boxSizing: 'border-box' };
const { compact, pictureInPicture } = this.props;
if (!status) {
return null;
}
let media = '';
let applicationLink = '';
let reblogLink = '';
let favouriteLink = '';
if (this.props.measureHeight) {
outerStyle.height = `${this.state.height}px`;
}
const language = status.getIn(['translation', 'language']) || status.get('language');
if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
} else if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const attachment = status.getIn(['media_attachments', 0]);
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
media = (
<Audio
src={attachment.get('url')}
alt={description}
lang={language}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
sensitive={status.get('sensitive')}
visible={this.props.showMedia}
blurhash={attachment.get('blurhash')}
height={150}
onToggleVisibility={this.props.onToggleMediaVisibility}
/>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const attachment = status.getIn(['media_attachments', 0]);
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
media = (
<Video
preview={attachment.get('preview_url')}
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={description}
lang={language}
width={300}
height={150}
onOpenVideo={this.handleOpenVideo}
sensitive={status.get('sensitive')}
visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility}
/>
);
} else {
media = (
<MediaGallery
standalone
sensitive={status.get('sensitive')}
media={status.get('media_attachments')}
lang={language}
height={300}
onOpenMedia={this.props.onOpenMedia}
visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility}
/>
);
}
} else if (status.get('spoiler_text').length === 0) {
media = <Card sensitive={status.get('sensitive')} onOpenMedia={this.props.onOpenMedia} card={status.get('card', null)} />;
}
if (status.get('application')) {
applicationLink = <>·<a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></>;
}
const visibilityLink = <>·<VisibilityIcon visibility={status.get('visibility')} /></>;
if (['private', 'direct'].includes(status.get('visibility'))) {
reblogLink = '';
} else if (this.props.history) {
reblogLink = (
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`} className='detailed-status__link'>
<span className='detailed-status__reblogs'>
<AnimatedNumber value={status.get('reblogs_count')} />
</span>
<FormattedMessage id='status.reblogs' defaultMessage='{count, plural, one {boost} other {boosts}}' values={{ count: status.get('reblogs_count') }} />
</Link>
);
} else {
reblogLink = (
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
<span className='detailed-status__reblogs'>
<AnimatedNumber value={status.get('reblogs_count')} />
</span>
<FormattedMessage id='status.reblogs' defaultMessage='{count, plural, one {boost} other {boosts}}' values={{ count: status.get('reblogs_count') }} />
</a>
);
}
if (this.props.history) {
favouriteLink = (
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} className='detailed-status__link'>
<span className='detailed-status__favorites'>
<AnimatedNumber value={status.get('favourites_count')} />
</span>
<FormattedMessage id='status.favourites' defaultMessage='{count, plural, one {favorite} other {favorites}}' values={{ count: status.get('favourites_count') }} />
</Link>
);
} else {
favouriteLink = (
<a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}>
<span className='detailed-status__favorites'>
<AnimatedNumber value={status.get('favourites_count')} />
</span>
<FormattedMessage id='status.favourites' defaultMessage='{count, plural, one {favorite} other {favorites}}' values={{ count: status.get('favourites_count') }} />
</a>
);
}
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
const expanded = !status.get('hidden') || status.get('spoiler_text').length === 0;
return (
<div style={outerStyle}>
<div ref={this.setRef} className={classNames('detailed-status', { compact })}>
{status.get('visibility') === 'direct' && (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='at' icon={AlternateEmailIcon} className='status__prepend-icon' /></div>
<FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' />
</div>
)}
<a href={`/@${status.getIn(['account', 'acct'])}`} data-hover-card-account={status.getIn(['account', 'id'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={46} /></div>
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
</a>
{status.get('spoiler_text').length > 0 && <ContentWarning text={status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml')} expanded={expanded} onClick={this.handleExpandedToggle} />}
{expanded && (
<>
<StatusContent
status={status}
onTranslate={this.handleTranslate}
{...statusContentProps}
/>
{media}
{hashtagBar}
</>
)}
<div className='detailed-status__meta'>
<div className='detailed-status__meta__line'>
<a className='detailed-status__datetime' href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} target='_blank' rel='noopener noreferrer'>
<FormattedDate value={new Date(status.get('created_at'))} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
</a>
{visibilityLink}
{applicationLink}
</div>
{status.get('edited_at') && <div className='detailed-status__meta__line'><EditedTimestamp statusId={status.get('id')} timestamp={status.get('edited_at')} /></div>}
<div className='detailed-status__meta__line'>
{reblogLink}
{reblogLink && <>·</>}
{favouriteLink}
</div>
</div>
</div>
</div>
);
}
}
export default withRouter(DetailedStatus);

View File

@ -0,0 +1,390 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access,
@typescript-eslint/no-unsafe-call,
@typescript-eslint/no-explicit-any,
@typescript-eslint/no-unsafe-assignment */
import type { CSSProperties } from 'react';
import { useState, useRef, useCallback } from 'react';
import { FormattedDate, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
import { AnimatedNumber } from 'mastodon/components/animated_number';
import { ContentWarning } from 'mastodon/components/content_warning';
import EditedTimestamp from 'mastodon/components/edited_timestamp';
import type { StatusLike } from 'mastodon/components/hashtag_bar';
import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar';
import { Icon } from 'mastodon/components/icon';
import { IconLogo } from 'mastodon/components/logo';
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
import { Avatar } from '../../../components/avatar';
import { DisplayName } from '../../../components/display_name';
import MediaGallery from '../../../components/media_gallery';
import StatusContent from '../../../components/status_content';
import Audio from '../../audio';
import scheduleIdleTask from '../../ui/util/schedule_idle_task';
import Video from '../../video';
import Card from './card';
interface VideoModalOptions {
startTime: number;
autoPlay?: boolean;
defaultVolume: number;
componentIndex: number;
}
export const DetailedStatus: React.FC<{
status: any;
onOpenMedia?: (status: any, index: number, lang: string) => void;
onOpenVideo?: (status: any, lang: string, options: VideoModalOptions) => void;
onTranslate?: (status: any) => void;
measureHeight?: boolean;
onHeightChange?: () => void;
domain: string;
showMedia?: boolean;
withLogo?: boolean;
pictureInPicture: any;
onToggleHidden?: (status: any) => void;
onToggleMediaVisibility?: () => void;
}> = ({
status,
onOpenMedia,
onOpenVideo,
onTranslate,
measureHeight,
onHeightChange,
domain,
showMedia,
withLogo,
pictureInPicture,
onToggleMediaVisibility,
onToggleHidden,
}) => {
const properStatus = status?.get('reblog') ?? status;
const [height, setHeight] = useState(0);
const nodeRef = useRef<HTMLDivElement>();
const handleOpenVideo = useCallback(
(options: VideoModalOptions) => {
const lang = (status.getIn(['translation', 'language']) ||
status.get('language')) as string;
if (onOpenVideo)
onOpenVideo(status.getIn(['media_attachments', 0]), lang, options);
},
[onOpenVideo, status],
);
const handleExpandedToggle = useCallback(() => {
if (onToggleHidden) onToggleHidden(status);
}, [onToggleHidden, status]);
const _measureHeight = useCallback(
(heightJustChanged?: boolean) => {
if (measureHeight && nodeRef.current) {
scheduleIdleTask(() => {
if (nodeRef.current)
setHeight(Math.ceil(nodeRef.current.scrollHeight) + 1);
});
if (onHeightChange && heightJustChanged) {
onHeightChange();
}
}
},
[onHeightChange, measureHeight, setHeight],
);
const handleRef = useCallback(
(c: HTMLDivElement) => {
nodeRef.current = c;
_measureHeight();
},
[_measureHeight],
);
const handleTranslate = useCallback(() => {
if (onTranslate) onTranslate(status);
}, [onTranslate, status]);
if (!properStatus) {
return null;
}
let media;
let applicationLink;
let reblogLink;
let attachmentAspectRatio;
if (properStatus.get('media_attachments').getIn([0, 'type']) === 'video') {
attachmentAspectRatio = `${properStatus.get('media_attachments').getIn([0, 'meta', 'original', 'width'])} / ${properStatus.get('media_attachments').getIn([0, 'meta', 'original', 'height'])}`;
} else if (
properStatus.get('media_attachments').getIn([0, 'type']) === 'audio'
) {
attachmentAspectRatio = '16 / 9';
} else {
attachmentAspectRatio =
properStatus.get('media_attachments').size === 1 &&
properStatus
.get('media_attachments')
.getIn([0, 'meta', 'small', 'aspect'])
? properStatus
.get('media_attachments')
.getIn([0, 'meta', 'small', 'aspect'])
: '3 / 2';
}
const outerStyle = { boxSizing: 'border-box' } as CSSProperties;
if (measureHeight) {
outerStyle.height = height;
}
const language =
status.getIn(['translation', 'language']) || status.get('language');
if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder aspectRatio={attachmentAspectRatio} />;
} else if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const attachment = status.getIn(['media_attachments', 0]);
const description =
attachment.getIn(['translation', 'description']) ||
attachment.get('description');
media = (
<Audio
src={attachment.get('url')}
alt={description}
lang={language}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
poster={
attachment.get('preview_url') ||
status.getIn(['account', 'avatar_static'])
}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
sensitive={status.get('sensitive')}
visible={showMedia}
blurhash={attachment.get('blurhash')}
height={150}
onToggleVisibility={onToggleMediaVisibility}
/>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const attachment = status.getIn(['media_attachments', 0]);
const description =
attachment.getIn(['translation', 'description']) ||
attachment.get('description');
media = (
<Video
preview={attachment.get('preview_url')}
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={description}
lang={language}
width={300}
height={150}
onOpenVideo={handleOpenVideo}
sensitive={status.get('sensitive')}
visible={showMedia}
onToggleVisibility={onToggleMediaVisibility}
/>
);
} else {
media = (
<MediaGallery
standalone
sensitive={status.get('sensitive')}
media={status.get('media_attachments')}
lang={language}
height={300}
onOpenMedia={onOpenMedia}
visible={showMedia}
onToggleVisibility={onToggleMediaVisibility}
/>
);
}
} else if (status.get('spoiler_text').length === 0) {
media = (
<Card
sensitive={status.get('sensitive')}
onOpenMedia={onOpenMedia}
card={status.get('card', null)}
/>
);
}
if (status.get('application')) {
applicationLink = (
<>
·
<a
className='detailed-status__application'
href={status.getIn(['application', 'website'])}
target='_blank'
rel='noopener noreferrer'
>
{status.getIn(['application', 'name'])}
</a>
</>
);
}
const visibilityLink = (
<>
·<VisibilityIcon visibility={status.get('visibility')} />
</>
);
if (['private', 'direct'].includes(status.get('visibility') as string)) {
reblogLink = '';
} else {
reblogLink = (
<Link
to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/reblogs`}
className='detailed-status__link'
>
<span className='detailed-status__reblogs'>
<AnimatedNumber value={status.get('reblogs_count')} />
</span>
<FormattedMessage
id='status.reblogs'
defaultMessage='{count, plural, one {boost} other {boosts}}'
values={{ count: status.get('reblogs_count') }}
/>
</Link>
);
}
const favouriteLink = (
<Link
to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`}
className='detailed-status__link'
>
<span className='detailed-status__favorites'>
<AnimatedNumber value={status.get('favourites_count')} />
</span>
<FormattedMessage
id='status.favourites'
defaultMessage='{count, plural, one {favorite} other {favorites}}'
values={{ count: status.get('favourites_count') }}
/>
</Link>
);
const { statusContentProps, hashtagBar } = getHashtagBarForStatus(
status as StatusLike,
);
const expanded =
!status.get('hidden') || status.get('spoiler_text').length === 0;
return (
<div style={outerStyle}>
<div ref={handleRef} className={classNames('detailed-status')}>
{status.get('visibility') === 'direct' && (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'>
<Icon
id='at'
icon={AlternateEmailIcon}
className='status__prepend-icon'
/>
</div>
<FormattedMessage
id='status.direct_indicator'
defaultMessage='Private mention'
/>
</div>
)}
<Link
to={`/@${status.getIn(['account', 'acct'])}`}
data-hover-card-account={status.getIn(['account', 'id'])}
className='detailed-status__display-name'
>
<div className='detailed-status__display-avatar'>
<Avatar account={status.get('account')} size={46} />
</div>
<DisplayName account={status.get('account')} localDomain={domain} />
{withLogo && (
<>
<div className='spacer' />
<IconLogo />
</>
)}
</Link>
{status.get('spoiler_text').length > 0 && (
<ContentWarning
text={
status.getIn(['translation', 'spoilerHtml']) ||
status.get('spoilerHtml')
}
expanded={expanded}
onClick={handleExpandedToggle}
/>
)}
{expanded && (
<>
<StatusContent
status={status}
onTranslate={handleTranslate}
{...(statusContentProps as any)}
/>
{media}
{hashtagBar}
</>
)}
<div className='detailed-status__meta'>
<div className='detailed-status__meta__line'>
<a
className='detailed-status__datetime'
href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`}
target='_blank'
rel='noopener noreferrer'
>
<FormattedDate
value={new Date(status.get('created_at') as string)}
year='numeric'
month='short'
day='2-digit'
hour='2-digit'
minute='2-digit'
/>
</a>
{visibilityLink}
{applicationLink}
</div>
{status.get('edited_at') && (
<div className='detailed-status__meta__line'>
<EditedTimestamp
statusId={status.get('id')}
timestamp={status.get('edited_at')}
/>
</div>
)}
<div className='detailed-status__meta__line'>
{reblogLink}
{reblogLink && <>·</>}
{favouriteLink}
</div>
</div>
</div>
</div>
);
};

View File

@ -1,140 +0,0 @@
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { showAlertForError } from '../../../actions/alerts';
import { initBlockModal } from '../../../actions/blocks';
import {
replyCompose,
mentionCompose,
directCompose,
} from '../../../actions/compose';
import {
toggleReblog,
toggleFavourite,
pin,
unpin,
} from '../../../actions/interactions';
import { openModal } from '../../../actions/modal';
import { initMuteModal } from '../../../actions/mutes';
import { initReport } from '../../../actions/reports';
import {
muteStatus,
unmuteStatus,
deleteStatus,
toggleStatusSpoilers,
} from '../../../actions/statuses';
import { deleteModal } from '../../../initial_state';
import { makeGetStatus, makeGetPictureInPicture } from '../../../selectors';
import DetailedStatus from '../components/detailed_status';
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const getPictureInPicture = makeGetPictureInPicture();
const mapStateToProps = (state, props) => ({
status: getStatus(state, props),
domain: state.getIn(['meta', 'domain']),
pictureInPicture: getPictureInPicture(state, props),
});
return mapStateToProps;
};
const mapDispatchToProps = (dispatch) => ({
onReply (status) {
dispatch((_, getState) => {
let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } }));
} else {
dispatch(replyCompose(status));
}
});
},
onReblog (status, e) {
dispatch(toggleReblog(status.get('id'), e.shiftKey));
},
onFavourite (status) {
dispatch(toggleFavourite(status.get('id')));
},
onPin (status) {
if (status.get('pinned')) {
dispatch(unpin(status));
} else {
dispatch(pin(status));
}
},
onEmbed (status) {
dispatch(openModal({
modalType: 'EMBED',
modalProps: {
id: status.get('id'),
onError: error => dispatch(showAlertForError(error)),
},
}));
},
onDelete (status, withRedraft = false) {
if (!deleteModal) {
dispatch(deleteStatus(status.get('id'), withRedraft));
} else {
dispatch(openModal({ modalType: 'CONFIRM_DELETE_STATUS', modalProps: { statusId: status.get('id'), withRedraft } }));
}
},
onDirect (account) {
dispatch(directCompose(account));
},
onMention (account) {
dispatch(mentionCompose(account));
},
onOpenMedia (media, index, lang) {
dispatch(openModal({
modalType: 'MEDIA',
modalProps: { media, index, lang },
}));
},
onOpenVideo (media, lang, options) {
dispatch(openModal({
modalType: 'VIDEO',
modalProps: { media, lang, options },
}));
},
onBlock (status) {
const account = status.get('account');
dispatch(initBlockModal(account));
},
onReport (status) {
dispatch(initReport(status.get('account'), status));
},
onMute (account) {
dispatch(initMuteModal(account));
},
onMuteConversation (status) {
if (status.get('muted')) {
dispatch(unmuteStatus(status.get('id')));
} else {
dispatch(muteStatus(status.get('id')));
}
},
onToggleHidden (status) {
dispatch(toggleStatusSpoilers(status.get('id')));
},
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus));

View File

@ -69,7 +69,7 @@ import Column from '../ui/components/column';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
import ActionBar from './components/action_bar';
import DetailedStatus from './components/detailed_status';
import { DetailedStatus } from './components/detailed_status';
const messages = defineMessages({

View File

@ -1,101 +0,0 @@
import PropTypes from 'prop-types';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import api from 'mastodon/api';
import { IconButton } from 'mastodon/components/icon_button';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
});
class EmbedModal extends ImmutablePureComponent {
static propTypes = {
id: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onError: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
state = {
loading: false,
oembed: null,
};
componentDidMount () {
const { id } = this.props;
this.setState({ loading: true });
api().get(`/api/web/embeds/${id}`).then(res => {
this.setState({ loading: false, oembed: res.data });
const iframeDocument = this.iframe.contentWindow.document;
iframeDocument.open();
iframeDocument.write(res.data.html);
iframeDocument.close();
iframeDocument.body.style.margin = 0;
this.iframe.width = iframeDocument.body.scrollWidth;
this.iframe.height = iframeDocument.body.scrollHeight;
}).catch(error => {
this.props.onError(error);
});
}
setIframeRef = c => {
this.iframe = c;
};
handleTextareaClick = (e) => {
e.target.select();
};
render () {
const { intl, onClose } = this.props;
const { oembed } = this.state;
return (
<div className='modal-root__modal report-modal embed-modal'>
<div className='report-modal__target'>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' iconComponent={CloseIcon} onClick={onClose} size={16} />
<FormattedMessage id='status.embed' defaultMessage='Embed' />
</div>
<div className='report-modal__container embed-modal__container' style={{ display: 'block' }}>
<p className='hint'>
<FormattedMessage id='embed.instructions' defaultMessage='Embed this status on your website by copying the code below.' />
</p>
<input
type='text'
className='embed-modal__html'
readOnly
value={oembed && oembed.html || ''}
onClick={this.handleTextareaClick}
/>
<p className='hint'>
<FormattedMessage id='embed.preview' defaultMessage='Here is what it will look like:' />
</p>
<iframe
className='embed-modal__iframe'
frameBorder='0'
ref={this.setIframeRef}
sandbox='allow-scripts allow-same-origin'
title='preview'
/>
</div>
</div>
);
}
}
export default injectIntl(EmbedModal);

View File

@ -0,0 +1,116 @@
import { useRef, useState, useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { showAlertForError } from 'mastodon/actions/alerts';
import api from 'mastodon/api';
import { Button } from 'mastodon/components/button';
import { CopyPasteText } from 'mastodon/components/copy_paste_text';
import { useAppDispatch } from 'mastodon/store';
interface OEmbedResponse {
html: string;
}
const EmbedModal: React.FC<{
id: string;
onClose: () => void;
}> = ({ id, onClose }) => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const intervalRef = useRef<ReturnType<typeof setInterval>>();
const [oembed, setOembed] = useState<OEmbedResponse | null>(null);
const dispatch = useAppDispatch();
useEffect(() => {
api()
.get(`/api/web/embeds/${id}`)
.then((res) => {
const data = res.data as OEmbedResponse;
setOembed(data);
const iframeDocument = iframeRef.current?.contentWindow?.document;
if (!iframeDocument) {
return '';
}
iframeDocument.open();
iframeDocument.write(data.html);
iframeDocument.close();
iframeDocument.body.style.margin = '0px';
// This is our best chance to ensure the parent iframe has the correct height...
intervalRef.current = setInterval(
() =>
window.requestAnimationFrame(() => {
if (iframeRef.current) {
iframeRef.current.width = `${iframeDocument.body.scrollWidth}px`;
iframeRef.current.height = `${iframeDocument.body.scrollHeight}px`;
}
}),
100,
);
return '';
})
.catch((error: unknown) => {
dispatch(showAlertForError(error));
});
}, [dispatch, id, setOembed]);
useEffect(
() => () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
},
[],
);
return (
<div className='modal-root__modal dialog-modal'>
<div className='dialog-modal__header'>
<Button onClick={onClose}>
<FormattedMessage id='report.close' defaultMessage='Done' />
</Button>
<span className='dialog-modal__header__title'>
<FormattedMessage id='status.embed' defaultMessage='Get embed code' />
</span>
<Button secondary onClick={onClose}>
<FormattedMessage
id='confirmation_modal.cancel'
defaultMessage='Cancel'
/>
</Button>
</div>
<div className='dialog-modal__content'>
<div className='dialog-modal__content__form'>
<FormattedMessage
id='embed.instructions'
defaultMessage='Embed this status on your website by copying the code below.'
/>
<CopyPasteText value={oembed?.html ?? ''} />
<FormattedMessage
id='embed.preview'
defaultMessage='Here is what it will look like:'
/>
<iframe
frameBorder='0'
ref={iframeRef}
sandbox='allow-scripts allow-same-origin'
title='Preview'
/>
</div>
</div>
</div>
);
};
// eslint-disable-next-line import/no-default-export
export default EmbedModal;

View File

@ -789,7 +789,7 @@
"status.edit": "Edit",
"status.edited": "Last edited {date}",
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
"status.embed": "Embed",
"status.embed": "Get embed code",
"status.favourite": "Favorite",
"status.favourites": "{count, plural, one {favorite} other {favorites}}",
"status.filter": "Filter this post",

View File

@ -11,7 +11,6 @@
@import 'mastodon/widgets';
@import 'mastodon/forms';
@import 'mastodon/accounts';
@import 'mastodon/statuses';
@import 'mastodon/components';
@import 'mastodon/polls';
@import 'mastodon/modal';

View File

@ -1677,18 +1677,6 @@ body > [data-popper-placement] {
padding: 16px;
border-top: 1px solid var(--background-border-color);
&--flex {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-start;
.status__content,
.detailed-status__meta {
flex: 100%;
}
}
.status__content {
font-size: 19px;
line-height: 24px;
@ -1723,6 +1711,29 @@ body > [data-popper-placement] {
margin-bottom: 0;
}
}
.logo {
width: 40px;
height: 40px;
color: $dark-text-color;
}
}
.embed {
position: relative;
&__overlay {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.detailed-status {
border-top: 0;
}
}
.scrollable > div:first-child .detailed-status {
@ -6282,6 +6293,50 @@ a.status-card {
}
}
.dialog-modal {
width: 588px;
max-height: 80vh;
flex-direction: column;
background: var(--modal-background-color);
backdrop-filter: var(--background-filter);
border: 1px solid var(--modal-border-color);
border-radius: 16px;
&__header {
border-bottom: 1px solid var(--modal-border-color);
display: flex;
align-items: center;
justify-content: space-between;
flex-direction: row-reverse;
padding: 12px 24px;
&__title {
font-size: 16px;
line-height: 24px;
font-weight: 500;
letter-spacing: 0.15px;
}
}
&__content {
font-size: 14px;
line-height: 20px;
letter-spacing: 0.25px;
overflow-y: auto;
&__form {
display: flex;
flex-direction: column;
gap: 16px;
padding: 24px;
}
}
.copy-paste-text {
margin-bottom: 0;
}
}
.hotkey-combination {
display: inline-flex;
align-items: center;
@ -7730,69 +7785,6 @@ noscript {
}
}
.embed-modal {
width: auto;
max-width: 80vw;
max-height: 80vh;
h4 {
padding: 30px;
font-weight: 500;
font-size: 16px;
text-align: center;
}
.embed-modal__container {
padding: 10px;
.hint {
margin-bottom: 15px;
}
.embed-modal__html {
outline: 0;
box-sizing: border-box;
display: block;
width: 100%;
border: 0;
padding: 10px;
font-family: $font-monospace, monospace;
background: $ui-base-color;
color: $primary-text-color;
font-size: 14px;
margin: 0;
margin-bottom: 15px;
border-radius: 4px;
&::-moz-focus-inner {
border: 0;
}
&::-moz-focus-inner,
&:focus,
&:active {
outline: 0 !important;
}
&:focus {
background: lighten($ui-base-color, 4%);
}
@media screen and (width <= 600px) {
font-size: 16px;
}
}
.embed-modal__iframe {
width: 400px;
max-width: 100%;
overflow: hidden;
border: 0;
border-radius: 4px;
}
}
}
.moved-account-banner,
.follow-request-banner,
.account-memorial-banner {

View File

@ -1,152 +0,0 @@
.activity-stream {
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
border-radius: 4px;
overflow: hidden;
margin-bottom: 10px;
&--under-tabs {
border-radius: 0 0 4px 4px;
}
@media screen and (max-width: $no-gap-breakpoint) {
margin-bottom: 0;
border-radius: 0;
box-shadow: none;
}
&--headless {
border-radius: 0;
margin: 0;
box-shadow: none;
.detailed-status,
.status {
border-radius: 0 !important;
}
}
div[data-component] {
width: 100%;
}
.entry {
background: $ui-base-color;
.detailed-status,
.status,
.load-more {
animation: none;
}
&:last-child {
.detailed-status,
.status,
.load-more {
border-bottom: 0;
border-radius: 0 0 4px 4px;
}
}
&:first-child {
.detailed-status,
.status,
.load-more {
border-radius: 4px 4px 0 0;
}
&:last-child {
.detailed-status,
.status,
.load-more {
border-radius: 4px;
}
}
}
@media screen and (width <= 740px) {
.detailed-status,
.status,
.load-more {
border-radius: 0 !important;
}
}
}
&--highlighted .entry {
background: lighten($ui-base-color, 8%);
}
}
.button.logo-button svg {
width: 20px;
height: auto;
vertical-align: middle;
margin-inline-end: 5px;
fill: $primary-text-color;
@media screen and (max-width: $no-gap-breakpoint) {
display: none;
}
}
.embed {
.status__content[data-spoiler='folded'] {
.e-content {
display: none;
}
p:first-child {
margin-bottom: 0;
}
}
.detailed-status {
padding: 15px;
.detailed-status__display-avatar .account__avatar {
width: 48px;
height: 48px;
}
}
.status {
padding: 15px 15px 15px (48px + 15px * 2);
min-height: 48px + 2px;
&__avatar {
inset-inline-start: 15px;
top: 17px;
.account__avatar {
width: 48px;
height: 48px;
}
}
&__content {
padding-top: 5px;
}
&__prepend {
margin-inline-start: 48px + 15px * 2;
padding-top: 15px;
}
&__prepend-icon-wrapper {
inset-inline-start: -32px;
}
.media-gallery,
&__action-bar,
.video-player {
margin-top: 10px;
}
&__action-bar-button {
font-size: 18px;
width: 23.1429px;
height: 23.1429px;
line-height: 23.15px;
}
}
}

View File

@ -225,7 +225,7 @@ class LinkDetailsExtractor
end
def valid_url_or_nil(str, same_origin_only: false)
return if str.blank? || str == 'null'
return if str.blank? || str == 'null' || str == 'undefined'
url = @original_url + Addressable::URI.parse(str)

View File

@ -1,6 +1,13 @@
# frozen_string_literal: true
class OEmbedSerializer < ActiveModel::Serializer
INLINE_STYLES = {
blockquote: 'max-width: 540px; min-width: 270px; background:#FCF8FF; border: 1px solid #C9C4DA; border-radius: 8px; overflow: hidden; margin: 0; padding: 0;',
a: "color: #1C1A25; text-decoration: none; display: flex; align-items: center; justify-content: center; flex-direction: column; padding: 24px; font-size: 14px; line-height: 20px; letter-spacing: 0.25px; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', Roboto, sans-serif;", # rubocop:disable Layout/LineLength
div0: 'margin-top: 16px; color: #787588;',
div1: 'font-weight: 500;',
}.freeze
include RoutingHelper
include ActionView::Helpers::TagHelper
@ -37,16 +44,16 @@ class OEmbedSerializer < ActiveModel::Serializer
end
def html
attributes = {
src: embed_short_account_status_url(object.account, object),
class: 'mastodon-embed',
style: 'max-width: 100%; border: 0',
width: width,
height: height,
allowfullscreen: true,
}
content_tag(:iframe, nil, attributes) + content_tag(:script, nil, src: full_asset_url('embed.js', skip_pipeline: true), async: true)
<<~HTML.squish
<blockquote class="mastodon-embed" data-embed-url="#{embed_short_account_status_url(object.account, object)}" style="#{INLINE_STYLES[:blockquote]}">
<a href="#{short_account_status_url(object.account, object)}" target="_blank" style="#{INLINE_STYLES[:a]}">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 79 75"><path d="M74.7135 16.6043C73.6199 8.54587 66.5351 2.19527 58.1366 0.964691C56.7196 0.756754 51.351 0 38.9148 0H38.822C26.3824 0 23.7135 0.756754 22.2966 0.964691C14.1319 2.16118 6.67571 7.86752 4.86669 16.0214C3.99657 20.0369 3.90371 24.4888 4.06535 28.5726C4.29578 34.4289 4.34049 40.275 4.877 46.1075C5.24791 49.9817 5.89495 53.8251 6.81328 57.6088C8.53288 64.5968 15.4938 70.4122 22.3138 72.7848C29.6155 75.259 37.468 75.6697 44.9919 73.971C45.8196 73.7801 46.6381 73.5586 47.4475 73.3063C49.2737 72.7302 51.4164 72.086 52.9915 70.9542C53.0131 70.9384 53.0308 70.9178 53.0433 70.8942C53.0558 70.8706 53.0628 70.8445 53.0637 70.8179V65.1661C53.0634 65.1412 53.0574 65.1167 53.0462 65.0944C53.035 65.0721 53.0189 65.0525 52.9992 65.0371C52.9794 65.0218 52.9564 65.011 52.9318 65.0056C52.9073 65.0002 52.8819 65.0003 52.8574 65.0059C48.0369 66.1472 43.0971 66.7193 38.141 66.7103C29.6118 66.7103 27.3178 62.6981 26.6609 61.0278C26.1329 59.5842 25.7976 58.0784 25.6636 56.5486C25.6622 56.5229 25.667 56.4973 25.6775 56.4738C25.688 56.4502 25.7039 56.4295 25.724 56.4132C25.7441 56.397 25.7678 56.3856 25.7931 56.3801C25.8185 56.3746 25.8448 56.3751 25.8699 56.3816C30.6101 57.5151 35.4693 58.0873 40.3455 58.086C41.5183 58.086 42.6876 58.086 43.8604 58.0553C48.7647 57.919 53.9339 57.6701 58.7591 56.7361C58.8794 56.7123 58.9998 56.6918 59.103 56.6611C66.7139 55.2124 73.9569 50.665 74.6929 39.1501C74.7204 38.6967 74.7892 34.4016 74.7892 33.9312C74.7926 32.3325 75.3085 22.5901 74.7135 16.6043ZM62.9996 45.3371H54.9966V25.9069C54.9966 21.8163 53.277 19.7302 49.7793 19.7302C45.9343 19.7302 44.0083 22.1981 44.0083 27.0727V37.7082H36.0534V27.0727C36.0534 22.1981 34.124 19.7302 30.279 19.7302C26.8019 19.7302 25.0651 21.8163 25.0617 25.9069V45.3371H17.0656V25.3172C17.0656 21.2266 18.1191 17.9769 20.2262 15.568C22.3998 13.1648 25.2509 11.9308 28.7898 11.9308C32.8859 11.9308 35.9812 13.492 38.0447 16.6111L40.036 19.9245L42.0308 16.6111C44.0943 13.492 47.1896 11.9308 51.2788 11.9308C54.8143 11.9308 57.6654 13.1648 59.8459 15.568C61.9529 17.9746 63.0065 21.2243 63.0065 25.3172L62.9996 45.3371Z" fill="currentColor"/></svg>
<div style="#{INLINE_STYLES[:div0]}">Post by @#{object.account.pretty_acct}@#{provider_name}</div>
<div style="#{INLINE_STYLES[:div1]}">View on Mastodon</div>
</a>
</blockquote>
<script data-allowed-prefixes="#{root_url}" async src="#{full_asset_url('embed.js', skip_pipeline: true)}"></script>
HTML
end
def width

View File

@ -15,8 +15,7 @@
= javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
= preload_locale_pack
= render_initial_state
= flavoured_javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
= flavoured_javascript_pack_tag 'embed', integrity: true, crossorigin: 'anonymous'
%body.embed
= yield

View File

@ -1,80 +0,0 @@
.detailed-status.detailed-status--flex{ class: "detailed-status-#{status.visibility}" }
.p-author.h-card
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'detailed-status__display-name u-url', target: stream_link_target, rel: 'noopener' do
.detailed-status__display-avatar
- if prefers_autoplay?
= image_tag status.account.avatar_original_url, alt: '', class: 'account__avatar u-photo'
- else
= image_tag status.account.avatar_static_url, alt: '', class: 'account__avatar u-photo'
%span.display-name
%bdi
%strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: prefers_autoplay?)
%span.display-name__account
= acct(status.account)
= material_symbol('lock') if status.account.locked?
= account_action_button(status.account)
.status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
- if status.spoiler_text?
%p<
%span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}&nbsp;
%button.status__content__spoiler-link= t('statuses.show_more')
.e-content{ lang: status.language }
= prerender_custom_emojis(status_content_format(status), status.emojis)
- if status.preloadable_poll
= render_poll_component(status)
- if !status.ordered_media_attachments.empty?
- if status.ordered_media_attachments.first.video?
= render_video_component(status, width: 670, height: 380, detailed: true)
- elsif status.ordered_media_attachments.first.audio?
= render_audio_component(status, width: 670, height: 380)
- else
= render_media_gallery_component(status, height: 380, standalone: true)
- elsif status.preview_card
= render_card_component(status)
.detailed-status__meta
%data.dt-published{ value: status.created_at.to_time.iso8601 }
- if status.edited?
%data.dt-updated{ value: status.edited_at.to_time.iso8601 }
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener noreferrer' do
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
·
- if status.edited?
= t('statuses.edited_at_html', date: content_tag(:time, l(status.edited_at), datetime: status.edited_at.iso8601, title: l(status.edited_at), class: 'formatted'))
·
%span.detailed-status__visibility-icon
= visibility_icon status
·
- if status.application && status.account.user&.setting_show_application
- if status.application.website.blank?
%strong.detailed-status__application= status.application.name
- else
= link_to status.application.name, status.application.website, class: 'detailed-status__application', target: '_blank', rel: 'noopener noreferrer'
·
%span.detailed-status__link
- if status.in_reply_to_id.nil?
= material_symbol('reply')
- else
= material_symbol('reply_all')
%span.detailed-status__reblogs>= friendly_number_to_human status.replies_count
&nbsp;
·
- if status.public_visibility? || status.unlisted_visibility?
%span.detailed-status__link
= material_symbol('repeat')
%span.detailed-status__reblogs>= friendly_number_to_human status.reblogs_count
&nbsp;
·
%span.detailed-status__link
= material_symbol('star')
%span.detailed-status__favorites>= friendly_number_to_human status.favourites_count
&nbsp;
- if user_signed_in?
·
= link_to t('statuses.open_in_web'), web_url("@#{status.account.pretty_acct}/#{status.id}"), class: 'detailed-status__application', target: '_blank', rel: 'noopener noreferrer'

View File

@ -1,36 +0,0 @@
:ruby
show_results = (user_signed_in? && poll.voted?(current_account)) || poll.expired?
total_votes_count = poll.voters_count || poll.votes_count
.poll
%ul
- poll.loaded_options.each do |option|
%li
- if show_results
- percent = total_votes_count.positive? ? 100 * option.votes_count / total_votes_count : 0
%label.poll__option><
%span.poll__number><
#{percent.round}%
%span.poll__option__text
= prerender_custom_emojis(h(option.title), status.emojis)
%progress{ max: 100, value: [percent, 1].max, 'aria-hidden': 'true' }
%span.poll__chart
- else
%label.poll__option><
%span.poll__input{ class: poll.multiple? ? 'checkbox' : nil }><
%span.poll__option__text
= prerender_custom_emojis(h(option.title), status.emojis)
.poll__footer
- unless show_results
%button.button.button-secondary{ disabled: true }
= t('statuses.poll.vote')
- if poll.voters_count.nil?
%span= t('statuses.poll.total_votes', count: poll.votes_count)
- else
%span= t('statuses.poll.total_people', count: poll.voters_count)
- unless poll.expires_at.nil?
·
%span= l poll.expires_at

View File

@ -1,70 +0,0 @@
:ruby
hide_show_thread ||= false
.status{ class: "status-#{status.visibility}" }
.status__info
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', target: stream_link_target, rel: 'noopener noreferrer' do
%span.status__visibility-icon><
= visibility_icon status
%time.time-ago{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
- if status.edited?
%abbr{ title: t('statuses.edited_at_html', date: l(status.edited_at.to_date)) }
*
%data.dt-published{ value: status.created_at.to_time.iso8601 }
.p-author.h-card
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener noreferrer' do
.status__avatar
%div
- if prefers_autoplay?
= image_tag status.account.avatar_original_url, alt: '', class: 'u-photo account__avatar'
- else
= image_tag status.account.avatar_static_url, alt: '', class: 'u-photo account__avatar'
%span.display-name
%bdi
%strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: prefers_autoplay?)
&nbsp;
%span.display-name__account
= acct(status.account)
= material_symbol('lock') if status.account.locked?
.status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
- if status.spoiler_text?
%p<
%span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}&nbsp;
%button.status__content__spoiler-link= t('statuses.show_more')
.e-content{ lang: status.language }<
= prerender_custom_emojis(status_content_format(status), status.emojis)
- if status.preloadable_poll
= render_poll_component(status)
- if !status.ordered_media_attachments.empty?
- if status.ordered_media_attachments.first.video?
= render_video_component(status, width: 610, height: 343)
- elsif status.ordered_media_attachments.first.audio?
= render_audio_component(status, width: 610, height: 343)
- else
= render_media_gallery_component(status, height: 343)
- elsif status.preview_card
= render_card_component(status)
- if !status.in_reply_to_id.nil? && status.in_reply_to_account_id == status.account.id && !hide_show_thread
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'status__content__read-more-button', target: stream_link_target, rel: 'noopener noreferrer' do
= t 'statuses.show_thread'
.status__action-bar
%span.status__action-bar-button.icon-button.icon-button--with-counter
- if status.in_reply_to_id.nil?
= material_symbol 'reply'
- else
= material_symbol 'reply_all'
%span.icon-button__counter= obscured_counter status.replies_count
%span.status__action-bar-button.icon-button
- if status.distributable?
= material_symbol 'repeat'
- elsif status.private_visibility? || status.limited_visibility?
= material_symbol 'lock'
- else
= material_symbol 'alternate_email'
%span.status__action-bar-button.icon-button
= material_symbol 'star'

View File

@ -1,2 +0,0 @@
.entry
= render (centered ? 'statuses/detailed_status' : 'statuses/simple_status'), status: status.proper, hide_show_thread: false

View File

@ -1,2 +1 @@
.activity-stream.activity-stream--headless
= render 'status', status: @status, centered: true
#mastodon-status{ data: { props: Oj.dump(default_props.merge(id: @status.id.to_s)) } }

View File

@ -38,17 +38,16 @@ Rails.application.config.content_security_policy do |p|
p.img_src :self, :data, :blob, *media_hosts
p.style_src :self, assets_host
p.media_src :self, :data, *media_hosts
p.frame_src :self, :https
p.manifest_src :self, assets_host
if sso_host.present?
p.form_action :self, sso_host
p.form_action :self, sso_host
else
p.form_action :self
p.form_action :self
end
p.child_src :self, :blob, assets_host
p.worker_src :self, :blob, assets_host
p.child_src :self, :blob, assets_host
p.worker_src :self, :blob, assets_host
if Rails.env.development?
webpacker_public_host = ENV.fetch('WEBPACKER_DEV_SERVER_PUBLIC', Webpacker.config.dev_server[:public])
@ -56,9 +55,11 @@ Rails.application.config.content_security_policy do |p|
p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url, *front_end_build_urls
p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host
p.frame_src :self, :https, :http
else
p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url
p.script_src :self, assets_host, "'wasm-unsafe-eval'"
p.frame_src :self, :https
end
end

View File

@ -6,7 +6,6 @@ af:
hosted_on: Mastodon gehuisves op %{domain}
title: Aangaande
accounts:
follow: Volg
followers:
one: Volgeling
other: Volgelinge

View File

@ -7,7 +7,6 @@ an:
hosted_on: Mastodon alochau en %{domain}
title: Sobre
accounts:
follow: Seguir
followers:
one: Seguidor
other: Seguidores
@ -1410,23 +1409,12 @@ an:
edited_at_html: Editau %{date}
errors:
in_reply_not_found: Lo estau a lo qual intentas responder no existe.
open_in_web: Ubrir en web
over_character_limit: Limite de caracters de %{max} superau
pin_errors:
direct: Las publicacions que son visibles solo pa los usuarios mencionaus no pueden fixar-se
limit: Ya has fixau lo numero maximo de publicacions
ownership: La publicación d'unatra persona no puede fixar-se
reblog: Un boost no puede fixar-se
poll:
total_people:
one: "%{count} persona"
other: "%{count} chent"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Vota
show_more: Amostrar mas
show_thread: Amostrar discusión
title: "%{name}: «%{quote}»"
visibilities:
direct: Directa

View File

@ -7,7 +7,6 @@ ar:
hosted_on: ماستدون مُستضاف على %{domain}
title: عن
accounts:
follow: متابَعة
followers:
few: متابِعون
many: متابِعون
@ -1772,31 +1771,12 @@ ar:
edited_at_html: عُدّل في %{date}
errors:
in_reply_not_found: إنّ المنشور الذي تحاول الرد عليه غير موجود على ما يبدو.
open_in_web: افتح في الويب
over_character_limit: تم تجاوز حد الـ %{max} حرف المسموح بها
pin_errors:
direct: لا يمكن تثبيت المنشورات التي يراها فقط المتسخدمون المشار إليهم
limit: لقد بلغت الحد الأقصى للمنشورات المثبتة
ownership: لا يمكن تثبيت منشور نشره شخص آخر
reblog: لا يمكن تثبيت إعادة نشر
poll:
total_people:
few: "%{count} أشخاص"
many: "%{count} أشخاص"
one: "%{count} شخص واحد"
other: "%{count} شخصا"
two: "%{count} شخصين"
zero: "%{count} شخص"
total_votes:
few: "%{count} أصوات"
many: "%{count} أصوات"
one: صوت واحد %{count}
other: "%{count} صوتا"
two: صوتين %{count}
zero: بدون صوت %{count}
vote: صوّت
show_more: أظهر المزيد
show_thread: اعرض خيط المحادثة
title: '%{name}: "%{quote}"'
visibilities:
direct: مباشرة

View File

@ -800,20 +800,11 @@ ast:
default_language: La mesma que la de la interfaz
errors:
in_reply_not_found: L'artículu al que tentes de responder paez que nun esiste.
open_in_web: Abrir na web
pin_errors:
direct: Nun se puen fixar los artículos que son visibles namás pa los usuarios mentaos
limit: Yá fixesti'l númberu máximu d'artículos
ownership: Nun se pue fixar l'artículu d'otru perfil
reblog: Nun se pue fixar un artículu compartíu
poll:
total_people:
one: "%{count} persona"
other: "%{count} persones"
total_votes:
one: "%{count} votu"
other: "%{count} votos"
show_more: Amosar más
title: "%{name}: «%{quote}»"
visibilities:
direct: Mensaxe direutu

View File

@ -7,7 +7,6 @@ be:
hosted_on: Mastodon месціцца на %{domain}
title: Пра нас
accounts:
follow: Падпісацца
followers:
few: Падпісчыка
many: Падпісчыкаў
@ -1778,27 +1777,12 @@ be:
edited_at_html: Адрэдагавана %{date}
errors:
in_reply_not_found: Здаецца, допіс, на які вы спрабуеце адказаць, не існуе.
open_in_web: Адчыніць у вэб-версіі
over_character_limit: перавышаная колькасць сімвалаў у %{max}
pin_errors:
direct: Допісы, бачныя толькі згаданым карыстальнікам, не могуць быць замацаваныя
limit: Вы ўжо замацавалі максімальную колькасць допісаў
ownership: Немагчыма замацаваць чужы допіс
reblog: Немагчыма замацаваць пашырэнне
poll:
total_people:
few: "%{count} чалавекі"
many: "%{count} чалавек"
one: "%{count} чалавек"
other: "%{count} чалавека"
total_votes:
few: "%{count} галасы"
many: "%{count} галасоў"
one: "%{count} голас"
other: "%{count} голасу"
vote: Прагаласаваць
show_more: Паказаць больш
show_thread: Паказаць ланцуг
title: '%{name}: "%{quote}"'
visibilities:
direct: Асабіста

View File

@ -7,7 +7,6 @@ bg:
hosted_on: Mastodon е разположен на хост %{domain}
title: Относно
accounts:
follow: Последване
followers:
one: Последовател
other: Последователи
@ -1664,23 +1663,12 @@ bg:
edited_at_html: Редактирано на %{date}
errors:
in_reply_not_found: Изглежда, че публикацията, на която се опитвате да отговорите, не съществува.
open_in_web: Отвори в уеб
over_character_limit: прехвърлен лимит от %{max} символа
pin_errors:
direct: Публикациите, които са видими само за потребители споменати в тях, не могат да бъдат закачани
limit: Вече сте закачили максималния брой публикации
ownership: Публикация на някого другиго не може да бъде закачена
reblog: Раздуване не може да бъде закачано
poll:
total_people:
one: "%{count} човек"
other: "%{count} души"
total_votes:
one: "%{count} глас"
other: "%{count} гласа"
vote: Гласуване
show_more: Покажи повече
show_thread: Показване на нишката
title: "%{name}: „%{quote}“"
visibilities:
direct: Директно

View File

@ -7,7 +7,6 @@ bn:
hosted_on: এই মাস্টাডনটি আছে %{domain} এ
title: পরিচিতি
accounts:
follow: যুক্ত
followers:
one: যুক্ত আছে
other: যারা যুক্ত হয়েছে

View File

@ -6,7 +6,6 @@ br:
hosted_on: Servijer Mastodon herberc'hiet war %{domain}
title: Diwar-benn
accounts:
follow: Heuliañ
followers:
few: Heulier·ez
many: Heulier·ez
@ -519,9 +518,6 @@ br:
two: "%{count} skeudenn"
pin_errors:
ownership: N'hallit ket spilhennañ embannadurioù ar re all
poll:
vote: Mouezhiañ
show_more: Diskouez muioc'h
visibilities:
direct: War-eeun
public: Publik

View File

@ -7,7 +7,6 @@ ca:
hosted_on: Mastodon allotjat a %{domain}
title: Quant a
accounts:
follow: Segueix
followers:
one: Seguidor
other: Seguidors
@ -1734,23 +1733,12 @@ ca:
edited_at_html: Editat %{date}
errors:
in_reply_not_found: El tut al qual intentes respondre sembla que no existeix.
open_in_web: Obre en la web
over_character_limit: Límit de caràcters de %{max} superat
pin_errors:
direct: Els tuts que només són visibles per als usuaris mencionats no poden ser fixats
limit: Ja has fixat el màxim nombre de tuts
ownership: No es pot fixar el tut d'algú altre
reblog: No es pot fixar un impuls
poll:
total_people:
one: "%{count} persona"
other: "%{count} persones"
total_votes:
one: "%{count} vot"
other: "%{count} vots"
vote: Vota
show_more: Mostra'n més
show_thread: Mostra el fil
title: '%{name}: "%{quote}"'
visibilities:
direct: Directe

View File

@ -7,7 +7,6 @@ ckb:
hosted_on: مەستودۆن میوانداری کراوە لە %{domain}
title: دەربارە
accounts:
follow: شوێن کەوە
followers:
one: شوێنکەوتوو
other: شوێن‌کەوتووان
@ -938,22 +937,11 @@ ckb:
other: 'هاشتاگەکانی ڕێگەپێنەدراوەی تێدابوو: %{tags}'
errors:
in_reply_not_found: ئەو دۆخەی کە تۆ هەوڵی وەڵامدانەوەی دەدەیت وادەرناکەوێت کە هەبێت.
open_in_web: کردنەوە لە وێب
over_character_limit: سنووری نووسەی %{max} تێپەڕێنرا
pin_errors:
limit: تۆ پێشتر زۆرترین ژمارەی توتتی چەسپیوەت هەیە
ownership: نووسراوەکانی تر ناتوانرێ بسەلمێت
reblog: بەهێزکردن ناتوانرێت بچەسپێ
poll:
total_people:
one: "%{count} کەس"
other: "%{count} خەڵک"
total_votes:
one: "%{count} دەنگ"
other: "%{count} دەنگەکان"
vote: دەنگ
show_more: زیاتر پیشان بدە
show_thread: نیشاندانی ڕشتە
visibilities:
private: شوێنکەوتوانی تەنها
private_long: تەنها بۆ شوێنکەوتوانی پیشان بدە

View File

@ -6,7 +6,6 @@ co:
contact_unavailable: Micca dispunibule
hosted_on: Mastodon allughjatu nantà %{domain}
accounts:
follow: Siguità
followers:
one: Abbunatu·a
other: Abbunati
@ -922,22 +921,11 @@ co:
other: 'cuntene lhashtag disattivati: %{tags}'
errors:
in_reply_not_found: U statutu à quellu avete pruvatu di risponde ùn sembra micca esiste.
open_in_web: Apre nantà u web
over_character_limit: site soprà a limita di %{max} caratteri
pin_errors:
limit: Avete digià puntarulatu u numeru massimale di statuti
ownership: Pudete puntarulà solu unu di i vostri propii statuti
reblog: Ùn pudete micca puntarulà una spartera
poll:
total_people:
one: "%{count} persona"
other: "%{count} persone"
total_votes:
one: "%{count} votu"
other: "%{count} voti"
vote: Vutà
show_more: Vede di più
show_thread: Vede u filu
title: '%{name}: "%{quote}"'
visibilities:
direct: Direttu

View File

@ -7,7 +7,6 @@ cs:
hosted_on: Mastodon na doméně %{domain}
title: O aplikaci
accounts:
follow: Sledovat
followers:
few: Sledující
many: Sledujících
@ -1721,27 +1720,12 @@ cs:
edited_at_html: Upraven %{date}
errors:
in_reply_not_found: Příspěvek, na který se pokoušíte odpovědět, neexistuje.
open_in_web: Otevřít na webu
over_character_limit: byl překročen limit %{max} znaků
pin_errors:
direct: Příspěvky viditelné pouze zmíněným uživatelům nelze připnout
limit: Už jste si připnuli maximální počet příspěvků
ownership: Nelze připnout příspěvek někoho jiného
reblog: Boosty nelze připnout
poll:
total_people:
few: "%{count} lidé"
many: "%{count} lidí"
one: "%{count} člověk"
other: "%{count} lidí"
total_votes:
few: "%{count} hlasy"
many: "%{count} hlasů"
one: "%{count} hlas"
other: "%{count} hlasů"
vote: Hlasovat
show_more: Zobrazit více
show_thread: Zobrazit vlákno
title: "%{name}: „%{quote}“"
visibilities:
direct: Přímé

View File

@ -7,7 +7,6 @@ cy:
hosted_on: Mastodon wedi ei weinyddu ar %{domain}
title: Ynghylch
accounts:
follow: Dilyn
followers:
few: Dilynwyr
many: Dilynwyr
@ -1860,31 +1859,12 @@ cy:
edited_at_html: Wedi'i olygu %{date}
errors:
in_reply_not_found: Nid yw'n ymddangos bod y postiad rydych chi'n ceisio ei ateb yn bodoli.
open_in_web: Agor yn y we
over_character_limit: wedi mynd y tu hwnt i'r terfyn nodau o %{max}
pin_errors:
direct: Nid oes modd pinio postiadau sy'n weladwy i ddefnyddwyr a grybwyllwyd yn unig
limit: Rydych chi eisoes wedi pinio uchafswm nifer y postiadau
ownership: Nid oes modd pinio postiad rhywun arall
reblog: Nid oes modd pinio hwb
poll:
total_people:
few: "%{count} person"
many: "%{count} person"
one: "%{count} berson"
other: "%{count} person"
two: "%{count} person"
zero: "%{count} o bersonau"
total_votes:
few: "%{count} pleidlais"
many: "%{count} pleidlais"
one: "%{count} bleidlais"
other: "%{count} pleidlais"
two: "%{count} pleidlais"
zero: "%{count} o bleidleisiau"
vote: Pleidlais
show_more: Dangos mwy
show_thread: Dangos edefyn
title: '%{name}: "%{quote}"'
visibilities:
direct: Uniongyrchol

View File

@ -7,7 +7,6 @@ da:
hosted_on: Mastodon hostet på %{domain}
title: Om
accounts:
follow: Følg
followers:
one: Følger
other: tilhængere
@ -1740,23 +1739,12 @@ da:
edited_at_html: Redigeret %{date}
errors:
in_reply_not_found: Indlægget, der forsøges besvaret, ser ikke ud til at eksistere.
open_in_web: Åbn i webbrowser
over_character_limit: grænsen på %{max} tegn overskredet
pin_errors:
direct: Indlæg, som kun kan ses af omtalte brugere, kan ikke fastgøres
limit: Maksimalt antal indlæg allerede fastgjort
ownership: Andres indlæg kan ikke fastgøres
reblog: Et boost kan ikke fastgøres
poll:
total_people:
one: "%{count} person"
other: "%{count} personer"
total_votes:
one: "%{count} stemme"
other: "%{count} stemmer"
vote: Stem
show_more: Vis flere
show_thread: Vis tråd
title: '%{name}: "%{quote}"'
visibilities:
direct: Direkte

View File

@ -7,7 +7,6 @@ de:
hosted_on: Mastodon, gehostet auf %{domain}
title: Über
accounts:
follow: Folgen
followers:
one: Follower
other: Follower
@ -1740,23 +1739,12 @@ de:
edited_at_html: 'Bearbeitet: %{date}'
errors:
in_reply_not_found: Der Beitrag, auf den du antworten möchtest, scheint nicht zu existieren.
open_in_web: Im Webinterface öffnen
over_character_limit: Begrenzung von %{max} Zeichen überschritten
pin_errors:
direct: Beiträge, die nur für erwähnte Profile sichtbar sind, können nicht angeheftet werden
limit: Du hast bereits die maximale Anzahl an Beiträgen angeheftet
ownership: Du kannst nur eigene Beiträge anheften
reblog: Du kannst keine geteilten Beiträge anheften
poll:
total_people:
one: "%{count} Stimme"
other: "%{count} Stimmen"
total_votes:
one: "%{count} Stimme"
other: "%{count} Stimmen"
vote: Abstimmen
show_more: Mehr anzeigen
show_thread: Thread anzeigen
title: "%{name}: „%{quote}“"
visibilities:
direct: Direktnachricht

View File

@ -7,7 +7,6 @@ el:
hosted_on: Το Mastodon φιλοξενείται στο %{domain}
title: Σχετικά
accounts:
follow: Ακολούθησε
followers:
one: Ακόλουθος
other: Ακόλουθοι
@ -1636,23 +1635,12 @@ el:
edited_at_html: Επεξεργάστηκε στις %{date}
errors:
in_reply_not_found: Η ανάρτηση στην οποία προσπαθείς να απαντήσεις δεν φαίνεται να υπάρχει.
open_in_web: Άνοιγμα στο διαδίκτυο
over_character_limit: υπέρβαση μέγιστου ορίου %{max} χαρακτήρων
pin_errors:
direct: Αναρτήσεις που είναι ορατές μόνο στους αναφερόμενους χρήστες δεν μπορούν να καρφιτσωθούν
limit: Έχεις ήδη καρφιτσώσει το μέγιστο αριθμό επιτρεπτών αναρτήσεων
ownership: Δεν μπορείς να καρφιτσώσεις ανάρτηση κάποιου άλλου
reblog: Οι ενισχύσεις δεν καρφιτσώνονται
poll:
total_people:
one: "%{count} άτομο"
other: "%{count} άτομα"
total_votes:
one: "%{count} ψήφος"
other: "%{count} ψήφοι"
vote: Ψήφισε
show_more: Δείξε περισσότερα
show_thread: Εμφάνιση νήματος
title: '%{name}: "%{quote}"'
visibilities:
direct: Άμεση

View File

@ -7,7 +7,6 @@ en-GB:
hosted_on: Mastodon hosted on %{domain}
title: About
accounts:
follow: Follow
followers:
one: Follower
other: Followers
@ -1740,23 +1739,12 @@ en-GB:
edited_at_html: Edited %{date}
errors:
in_reply_not_found: The post you are trying to reply to does not appear to exist.
open_in_web: Open in web
over_character_limit: character limit of %{max} exceeded
pin_errors:
direct: Posts that are only visible to mentioned users cannot be pinned
limit: You have already pinned the maximum number of posts
ownership: Someone else's post cannot be pinned
reblog: A boost cannot be pinned
poll:
total_people:
one: "%{count} person"
other: "%{count} people"
total_votes:
one: "%{count} vote"
other: "%{count} votes"
vote: Vote
show_more: Show more
show_thread: Show thread
title: '%{name}: "%{quote}"'
visibilities:
direct: Direct

View File

@ -7,7 +7,6 @@ en:
hosted_on: Mastodon hosted on %{domain}
title: About
accounts:
follow: Follow
followers:
one: Follower
other: Followers
@ -1741,23 +1740,12 @@ en:
edited_at_html: Edited %{date}
errors:
in_reply_not_found: The post you are trying to reply to does not appear to exist.
open_in_web: Open in web
over_character_limit: character limit of %{max} exceeded
pin_errors:
direct: Posts that are only visible to mentioned users cannot be pinned
limit: You have already pinned the maximum number of posts
ownership: Someone else's post cannot be pinned
reblog: A boost cannot be pinned
poll:
total_people:
one: "%{count} person"
other: "%{count} people"
total_votes:
one: "%{count} vote"
other: "%{count} votes"
vote: Vote
show_more: Show more
show_thread: Show thread
title: '%{name}: "%{quote}"'
visibilities:
direct: Direct

View File

@ -7,7 +7,6 @@ eo:
hosted_on: "%{domain} estas nodo de Mastodon"
title: Pri
accounts:
follow: Sekvi
followers:
one: Sekvanto
other: Sekvantoj
@ -1553,23 +1552,12 @@ eo:
edited_at_html: Redaktis je %{date}
errors:
in_reply_not_found: Mesaĝo kiun vi provas respondi ŝajnas ne ekzisti.
open_in_web: Malfermi retumile
over_character_limit: limo de %{max} signoj transpasita
pin_errors:
direct: Mesaĝoj kiu videbla nun al la uzantoj ne povas alpinglitis
limit: Vi jam atingis la maksimuman nombron de alpinglitaj mesaĝoj
ownership: Mesaĝo de iu alia ne povas esti alpinglita
reblog: Diskonigo ne povas esti alpinglita
poll:
total_people:
one: "%{count} persono"
other: "%{count} personoj"
total_votes:
one: "%{count} voĉdono"
other: "%{count} voĉdonoj"
vote: Voĉdoni
show_more: Montri pli
show_thread: Montri la mesaĝaron
title: "%{name}: “%{quote}”"
visibilities:
direct: Rekta

View File

@ -7,7 +7,6 @@ es-AR:
hosted_on: Mastodon alojado en %{domain}
title: Información
accounts:
follow: Seguir
followers:
one: Seguidor
other: Seguidores
@ -1740,23 +1739,12 @@ es-AR:
edited_at_html: Editado el %{date}
errors:
in_reply_not_found: El mensaje al que intentás responder no existe.
open_in_web: Abrir en la web
over_character_limit: se excedió el límite de %{max} caracteres
pin_errors:
direct: Los mensajes que sólo son visibles para los usuarios mencionados no pueden ser fijados
limit: Ya fijaste el número máximo de mensajes
ownership: No se puede fijar el mensaje de otra cuenta
reblog: No se puede fijar una adhesión
poll:
total_people:
one: "%{count} persona"
other: "%{count} personas"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Votar
show_more: Mostrar más
show_thread: Mostrar hilo
title: '%{name}: "%{quote}"'
visibilities:
direct: Directo

View File

@ -7,7 +7,6 @@ es-MX:
hosted_on: Mastodon alojado en %{domain}
title: Acerca de
accounts:
follow: Seguir
followers:
one: Seguidor
other: Seguidores
@ -1730,23 +1729,12 @@ es-MX:
edited_at_html: Editado %{date}
errors:
in_reply_not_found: El estado al que intentas responder no existe.
open_in_web: Abrir en web
over_character_limit: Límite de caracteres de %{max} superado
pin_errors:
direct: Las publicaciones que son visibles solo para los usuarios mencionados no pueden fijarse
limit: Ya has fijado el número máximo de publicaciones
ownership: El toot de alguien más no puede fijarse
reblog: Un boost no puede fijarse
poll:
total_people:
one: persona %{count}
other: "%{count} gente"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Vota
show_more: Mostrar más
show_thread: Mostrar discusión
title: "%{name}: «%{quote}»"
visibilities:
direct: Directa

View File

@ -7,7 +7,6 @@ es:
hosted_on: Mastodon alojado en %{domain}
title: Acerca de
accounts:
follow: Seguir
followers:
one: Seguidor
other: Seguidores
@ -1730,23 +1729,12 @@ es:
edited_at_html: Editado %{date}
errors:
in_reply_not_found: La publicación a la que intentas responder no existe.
open_in_web: Abrir en web
over_character_limit: Límite de caracteres de %{max} superado
pin_errors:
direct: Las publicaciones que son visibles solo para los usuarios mencionados no pueden fijarse
limit: Ya has fijado el número máximo de publicaciones
ownership: La publicación de otra persona no puede fijarse
reblog: Un boost no puede fijarse
poll:
total_people:
one: "%{count} persona"
other: "%{count} personas"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Vota
show_more: Mostrar más
show_thread: Mostrar discusión
title: "%{name}: «%{quote}»"
visibilities:
direct: Directa

View File

@ -7,7 +7,6 @@ et:
hosted_on: Mastodon majutatud %{domain}-is
title: Teave
accounts:
follow: Jälgi
followers:
one: Jälgija
other: Jälgijaid
@ -1701,23 +1700,12 @@ et:
edited_at_html: Muudetud %{date}
errors:
in_reply_not_found: Postitus, millele üritad vastata, ei näi enam eksisteerivat.
open_in_web: Ava veebis
over_character_limit: tähtmärkide limiit %{max} ületatud
pin_errors:
direct: Ei saa kinnitada postitusi, mis on nähtavad vaid mainitud kasutajatele
limit: Kinnitatud on juba maksimaalne arv postitusi
ownership: Kellegi teise postitust ei saa kinnitada
reblog: Jagamist ei saa kinnitada
poll:
total_people:
one: "%{count} inimene"
other: "%{count} inimest"
total_votes:
one: "%{count} hääl"
other: "%{count} häält"
vote: Hääleta
show_more: Näita rohkem
show_thread: Kuva lõim
title: '%{name}: "%{quote}"'
visibilities:
direct: Otsene

View File

@ -7,7 +7,6 @@ eu:
hosted_on: Mastodon %{domain} domeinuan ostatatua
title: Honi buruz
accounts:
follow: Jarraitu
followers:
one: Jarraitzaile
other: jarraitzaile
@ -1639,23 +1638,12 @@ eu:
edited_at_html: Editatua %{date}
errors:
in_reply_not_found: Erantzuten saiatu zaren bidalketa antza ez da existitzen.
open_in_web: Ireki web-ean
over_character_limit: "%{max}eko karaktere muga gaindituta"
pin_errors:
direct: Aipatutako erabiltzaileentzat soilik ikusgai dauden bidalketak ezin dira finkatu
limit: Gehienez finkatu daitekeen bidalketa kopurua finkatu duzu jada
ownership: Ezin duzu beste norbaiten bidalketa bat finkatu
reblog: Bultzada bat ezin da finkatu
poll:
total_people:
one: pertsona %{count}
other: "%{count} pertsona"
total_votes:
one: Boto %{count}
other: "%{count} boto"
vote: Bozkatu
show_more: Erakutsi gehiago
show_thread: Erakutsi haria
title: '%{name}: "%{quote}"'
visibilities:
direct: Zuzena

View File

@ -7,7 +7,6 @@ fa:
hosted_on: ماستودون، میزبانی‌شده روی %{domain}
title: درباره
accounts:
follow: پیگیری
followers:
one: پیگیر
other: پیگیر
@ -1404,23 +1403,12 @@ fa:
edited_at_html: ویراسته در %{date}
errors:
in_reply_not_found: به نظر نمی‌رسد وضعیتی که می‌خواهید به آن پاسخ دهید، وجود داشته باشد.
open_in_web: گشودن در وب
over_character_limit: از حد مجاز %{max} حرف فراتر رفتید
pin_errors:
direct: فرسته‌هایی که فقط برای کاربران اشاره شده نمایانند نمی‌توانند سنجاق شوند
limit: از این بیشتر نمی‌شود نوشته‌های ثابت داشت
ownership: نوشته‌های دیگران را نمی‌توان ثابت کرد
reblog: تقویت نمی‌تواند سنجاق شود
poll:
total_people:
one: "%{count} نفر"
other: "%{count} نفر"
total_votes:
one: "%{count} رأی"
other: "%{count} رأی"
vote: رأی
show_more: نمایش
show_thread: نمایش رشته
title: "%{name}: «%{quote}»"
visibilities:
direct: مستقیم

View File

@ -7,7 +7,6 @@ fi:
hosted_on: Mastodon palvelimella %{domain}
title: Tietoja
accounts:
follow: Seuraa
followers:
one: seuraaja
other: seuraajaa
@ -1738,23 +1737,12 @@ fi:
edited_at_html: Muokattu %{date}
errors:
in_reply_not_found: Julkaisua, johon yrität vastata, ei näytä olevan olemassa.
open_in_web: Avaa selaimessa
over_character_limit: merkkimäärän rajoitus %{max} ylitetty
pin_errors:
direct: Vain mainituille käyttäjille näkyviä julkaisuja ei voi kiinnittää
limit: Olet jo kiinnittänyt enimmäismäärän julkaisuja
ownership: Muiden julkaisuja ei voi kiinnittää
reblog: Tehostusta ei voi kiinnittää
poll:
total_people:
one: "%{count} käyttäjä"
other: "%{count} käyttäjää"
total_votes:
one: "%{count} ääni"
other: "%{count} ääntä"
vote: Äänestä
show_more: Näytä lisää
show_thread: Näytä ketju
title: "%{name}: ”%{quote}”"
visibilities:
direct: Suoraan

View File

@ -7,7 +7,6 @@ fo:
hosted_on: Mastodon hýst á %{domain}
title: Um
accounts:
follow: Fylg
followers:
one: Fylgjari
other: Fylgjarar
@ -1740,23 +1739,12 @@ fo:
edited_at_html: Rættað %{date}
errors:
in_reply_not_found: Posturin, sum tú roynir at svara, sýnist ikki at finnast.
open_in_web: Lat upp á vevinum
over_character_limit: mesta tal av teknum, %{max}, rokkið
pin_errors:
direct: Postar, sum einans eru sjónligir hjá nevndum brúkarum, kunnu ikki festast
limit: Tú hevur longu fest loyvda talið av postum
ownership: Postar hjá øðrum kunnu ikki festast
reblog: Ein stimbran kann ikki festast
poll:
total_people:
one: "%{count} fólk"
other: "%{count} fólk"
total_votes:
one: "%{count} atkvøða"
other: "%{count} atkvøður"
vote: Atkvøð
show_more: Vís meira
show_thread: Vís tráð
title: '%{name}: "%{quote}"'
visibilities:
direct: Beinleiðis

View File

@ -7,7 +7,6 @@ fr-CA:
hosted_on: Serveur Mastodon hébergé sur %{domain}
title: À propos
accounts:
follow: Suivre
followers:
one: Abonné·e
other: Abonné·e·s
@ -1711,23 +1710,12 @@ fr-CA:
edited_at_html: Édité le %{date}
errors:
in_reply_not_found: Le message auquel vous essayez de répondre ne semble pas exister.
open_in_web: Ouvrir sur le web
over_character_limit: limite de %{max} caractères dépassée
pin_errors:
direct: Les messages qui ne sont visibles que pour les utilisateur·rice·s mentionné·e·s ne peuvent pas être épinglés
limit: Vous avez déjà épinglé le nombre maximum de messages
ownership: Vous ne pouvez pas épingler un message ne vous appartenant pas
reblog: Un partage ne peut pas être épinglé
poll:
total_people:
one: "%{count} personne"
other: "%{count} personnes"
total_votes:
one: "%{count} vote"
other: "%{count} votes"
vote: Voter
show_more: Déplier
show_thread: Afficher le fil de discussion
title: "%{name}: « %{quote} »"
visibilities:
direct: Direct

View File

@ -7,7 +7,6 @@ fr:
hosted_on: Serveur Mastodon hébergé sur %{domain}
title: À propos
accounts:
follow: Suivre
followers:
one: Abonné·e
other: Abonné·e·s
@ -1711,23 +1710,12 @@ fr:
edited_at_html: Modifié le %{date}
errors:
in_reply_not_found: Le message auquel vous essayez de répondre ne semble pas exister.
open_in_web: Ouvrir sur le web
over_character_limit: limite de %{max} caractères dépassée
pin_errors:
direct: Les messages qui ne sont visibles que pour les utilisateur·rice·s mentionné·e·s ne peuvent pas être épinglés
limit: Vous avez déjà épinglé le nombre maximum de messages
ownership: Vous ne pouvez pas épingler un message ne vous appartenant pas
reblog: Un partage ne peut pas être épinglé
poll:
total_people:
one: "%{count} personne"
other: "%{count} personnes"
total_votes:
one: "%{count} vote"
other: "%{count} votes"
vote: Voter
show_more: Déplier
show_thread: Afficher le fil de discussion
title: "%{name}: « %{quote} »"
visibilities:
direct: Direct

View File

@ -7,7 +7,6 @@ fy:
hosted_on: Mastodon op %{domain}
title: Oer
accounts:
follow: Folgje
followers:
one: Folger
other: Folgers
@ -1730,23 +1729,12 @@ fy:
edited_at_html: Bewurke op %{date}
errors:
in_reply_not_found: It berjocht wêrop jo probearje te reagearjen liket net te bestean.
open_in_web: Yn de webapp iepenje
over_character_limit: Oer de limyt fan %{max} tekens
pin_errors:
direct: Berjochten dyt allinnich sichtber binne foar fermelde brûkers kinne net fêstset wurde
limit: Jo hawwe it maksimaal tal berjochten al fêstmakke
ownership: In berjocht fan in oar kin net fêstmakke wurde
reblog: In boost kin net fêstset wurde
poll:
total_people:
one: "%{count} persoan"
other: "%{count} persoanen"
total_votes:
one: "%{count} stim"
other: "%{count} stimmen"
vote: Stimme
show_more: Mear toane
show_thread: Petear toane
title: '%{name}: "%{quote}"'
visibilities:
direct: Direkt

View File

@ -7,7 +7,6 @@ ga:
hosted_on: Mastodon arna óstáil ar %{domain}
title: Maidir le
accounts:
follow: Lean
followers:
few: Leantóirí
many: Leantóirí
@ -1823,29 +1822,12 @@ ga:
edited_at_html: "%{date} curtha in eagar"
errors:
in_reply_not_found: Is cosúil nach ann don phostáil a bhfuil tú ag iarraidh freagra a thabhairt air.
open_in_web: Oscail i ngréasán
over_character_limit: teorainn carachtar %{max} sáraithe
pin_errors:
direct: Ní féidir postálacha nach bhfuil le feiceáil ach ag úsáideoirí luaite a phinnáil
limit: Tá uaslíon na bpostálacha pinn agat cheana féin
ownership: Ní féidir postáil duine éigin eile a phionnáil
reblog: Ní féidir treisiú a phinnáil
poll:
total_people:
few: "%{count} daoine"
many: "%{count} daoine"
one: "%{count} duine"
other: "%{count} daoine"
two: "%{count} daoine"
total_votes:
few: "%{count} vótaí"
many: "%{count} vótaí"
one: "%{count} vóta"
other: "%{count} vótaí"
two: "%{count} vótaí"
vote: Vótáil
show_more: Taispeáin níos mó
show_thread: Taispeáin snáithe
title: '%{name}: "%{quote}"'
visibilities:
direct: Díreach

View File

@ -7,7 +7,6 @@ gd:
hosted_on: Mastodon ga òstadh air %{domain}
title: Mu dhèidhinn
accounts:
follow: Lean
followers:
few: Luchd-leantainn
one: Neach-leantainn
@ -1793,27 +1792,12 @@ gd:
edited_at_html: Air a dheasachadh %{date}
errors:
in_reply_not_found: Tha coltas nach eil am post dhan a tha thu airson freagairt ann.
open_in_web: Fosgail air an lìon
over_character_limit: chaidh thu thar crìoch charactaran de %{max}
pin_errors:
direct: Chan urrainn dhut post a phrìneachadh nach fhaic ach na cleachdaichean le iomradh orra
limit: Tha an àireamh as motha de phostaichean prìnichte agad a tha ceadaichte
ownership: Chan urrainn dhut post càich a phrìneachadh
reblog: Chan urrainn dhut brosnachadh a phrìneachadh
poll:
total_people:
few: "%{count} daoine"
one: "%{count} neach"
other: "%{count} duine"
two: "%{count} neach"
total_votes:
few: "%{count} bhòtaichean"
one: "%{count} bhòt"
other: "%{count} bhòt"
two: "%{count} bhòt"
vote: Bhòt
show_more: Seall barrachd dheth
show_thread: Seall an snàithlean
title: "%{name}: “%{quote}”"
visibilities:
direct: Dìreach

View File

@ -7,7 +7,6 @@ gl:
hosted_on: Mastodon aloxado en %{domain}
title: Sobre
accounts:
follow: Seguir
followers:
one: Seguidora
other: Seguidoras
@ -1740,23 +1739,12 @@ gl:
edited_at_html: Editado %{date}
errors:
in_reply_not_found: A publicación á que tentas responder semella que non existe.
open_in_web: Abrir na web
over_character_limit: Excedeu o límite de caracteres %{max}
pin_errors:
direct: As publicacións que só son visibles para as usuarias mencionadas non se poden fixar
limit: Xa fixaches o número máximo permitido de publicacións
ownership: Non podes fixar a publicación doutra usuaria
reblog: Non se poden fixar as mensaxes promovidas
poll:
total_people:
one: "%{count} persoa"
other: "%{count} persoas"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Votar
show_more: Mostrar máis
show_thread: Amosar fío
title: '%{name}: "%{quote}"'
visibilities:
direct: Directa

View File

@ -7,7 +7,6 @@ he:
hosted_on: מסטודון שיושב בכתובת %{domain}
title: אודות
accounts:
follow: לעקוב
followers:
many: עוקבים
one: עוקב
@ -1800,27 +1799,12 @@ he:
edited_at_html: נערך ב-%{date}
errors:
in_reply_not_found: נראה שההודעה שאת/ה מנסה להגיב לה לא קיימת.
open_in_web: פתח ברשת
over_character_limit: חריגה מגבול התווים של %{max}
pin_errors:
direct: לא ניתן לקבע הודעות שנראותן מוגבלת למכותבים בלבד
limit: הגעת למספר המירבי של ההודעות המוצמדות
ownership: הודעות של אחרים לא יכולות להיות מוצמדות
reblog: אין אפשרות להצמיד הדהודים
poll:
total_people:
many: "%{count} אנשים"
one: איש/ה %{count}
other: "%{count} אנשים"
two: "%{count} אנשים"
total_votes:
many: "%{count} קולות"
one: קול %{count}
other: "%{count} קולות"
two: "%{count} קולות"
vote: הצבעה
show_more: עוד
show_thread: הצג שרשור
title: '%{name}: "%{quote}"'
visibilities:
direct: ישיר

View File

@ -5,7 +5,6 @@ hi:
contact_unavailable: लागू नहीं है
title: के बारे में
accounts:
follow: अनुसरे
following: फ़ॉलो कर रहे हैं
instance_actor_flash: यह खाता आभासी है जो सर्वर को दिखाने के लिये है और ये किसी व्यक्तिका प्रतिनिधित्व नहि करता। यह सिर्फ देखरेख के हेतु से कार्यरत है और इसको निलंबित करने कि आवश्यकता नहि है।
last_active: आखिरि बार इस वक्त सक्रिय थे

View File

@ -5,7 +5,6 @@ hr:
contact_missing: Nije postavljeno
title: O aplikaciji
accounts:
follow: Prati
following: Praćenih
last_active: posljednja aktivnost
nothing_here: Ovdje nema ničeg!
@ -215,20 +214,7 @@ hr:
statuses_cleanup: Automatsko brisanje postova
two_factor_authentication: Dvofaktorska autentifikacija
statuses:
open_in_web: Otvori na webu
over_character_limit: prijeđeno je ograničenje od %{max} znakova
poll:
total_people:
few: "%{count} osobe"
one: "%{count} osoba"
other: "%{count} ljudi"
total_votes:
few: "%{count} glasa"
one: "%{count} glas"
other: "%{count} glasova"
vote: Glasaj
show_more: Prikaži više
show_thread: Prikaži nit
visibilities:
private: Samo pratitelji
public: Javno

View File

@ -7,7 +7,6 @@ hu:
hosted_on: "%{domain} Mastodon-kiszolgáló"
title: Névjegy
accounts:
follow: Követés
followers:
one: Követő
other: Követő
@ -1740,23 +1739,12 @@ hu:
edited_at_html: 'Szerkesztve: %{date}'
errors:
in_reply_not_found: Már nem létezik az a bejegyzés, melyre válaszolni szeretnél.
open_in_web: Megnyitás a weben
over_character_limit: túllépted a maximális %{max} karakteres keretet
pin_errors:
direct: A csak a megemlített felhasználók számára látható bejegyzések nem tűzhetők ki
limit: Elérted a kitűzhető bejegyzések maximális számát
ownership: Nem tűzheted ki valaki más bejegyzését
reblog: Megtolt bejegyzést nem tudsz kitűzni
poll:
total_people:
one: "%{count} személy"
other: "%{count} személy"
total_votes:
one: "%{count} szavazat"
other: "%{count} szavazat"
vote: Szavazás
show_more: Több megjelenítése
show_thread: Szál mutatása
title: "%{name}: „%{quote}”"
visibilities:
direct: Közvetlen

View File

@ -7,7 +7,6 @@ hy:
hosted_on: Մաստոդոնը տեղակայուած է %{domain}ում
title: Մասին
accounts:
follow: Հետևել
followers:
one: Հետեւորդ
other: Հետևորդներ
@ -782,18 +781,7 @@ hy:
other: "%{count} վիդեո"
content_warning: Նախազգուշացում։ %{warning}
edited_at_html: Խմբագրուած՝ %{date}
open_in_web: Բացել վէբում
over_character_limit: "%{max} նիշի սահմանը գերազանցուած է"
poll:
total_people:
one: "%{count} մարդ"
other: "%{count} մարդիկ"
total_votes:
one: "%{count} ձայն"
other: "%{count} ձայներ"
vote: Քուէարկել
show_more: Աւելին
show_thread: Բացել շղթան
title: '%{name}: "%{quote}"'
visibilities:
direct: Հասցէագրուած

View File

@ -7,7 +7,6 @@ ia:
hosted_on: Mastodon albergate sur %{domain}
title: A proposito
accounts:
follow: Sequer
followers:
one: Sequitor
other: Sequitores
@ -1723,23 +1722,12 @@ ia:
edited_at_html: Modificate le %{date}
errors:
in_reply_not_found: Le message a que tu tenta responder non pare exister.
open_in_web: Aperir sur le web
over_character_limit: limite de characteres de %{max} excedite
pin_errors:
direct: Messages que es solo visibile a usatores mentionate non pote esser appunctate
limit: Tu ha jam appunctate le maxime numero de messages
ownership: Le message de alcuno altere non pote esser appunctate
reblog: Un impulso non pote esser affixate
poll:
total_people:
one: "%{count} persona"
other: "%{count} personas"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Votar
show_more: Monstrar plus
show_thread: Monstrar discussion
title: "%{name}: “%{quote}”"
visibilities:
direct: Directe

View File

@ -7,7 +7,6 @@ id:
hosted_on: Mastodon dihosting di %{domain}
title: Tentang
accounts:
follow: Ikuti
followers:
other: Pengikut
following: Mengikuti
@ -1378,21 +1377,12 @@ id:
edited_at_html: Diedit %{date}
errors:
in_reply_not_found: Status yang ingin Anda balas sudah tidak ada.
open_in_web: Buka di web
over_character_limit: melebihi %{max} karakter
pin_errors:
direct: Kiriman yang hanya terlihat oleh pengguna yang disebutkan tidak dapat disematkan
limit: Anda sudah mencapai jumlah maksimum kiriman yang dapat disematkan
ownership: Kiriman orang lain tidak bisa disematkan
reblog: Boost tidak bisa disematkan
poll:
total_people:
other: "%{count} orang"
total_votes:
other: "%{count} memilih"
vote: Pilih
show_more: Tampilkan selengkapnya
show_thread: Tampilkan utas
title: '%{name}: "%{quote}"'
visibilities:
direct: Langsung

View File

@ -7,7 +7,6 @@ ie:
hosted_on: Mastodon logiat che %{domain}
title: Pri
accounts:
follow: Sequer
followers:
one: Sequitor
other: Sequitores
@ -1637,23 +1636,12 @@ ie:
edited_at_html: Modificat ye %{date}
errors:
in_reply_not_found: Li posta a quel tu prova responder ne sembla exister.
open_in_web: Aperter in web
over_character_limit: límite de carácteres de %{max} transpassat
pin_errors:
direct: On ne posse pinglar postas queles es visibil solmen a mentionat usatores
limit: Tu ja ha pinglat li maxim númere de postas
ownership: On ne posse pinglar li posta de un altri person
reblog: On ne posse pinglar un boost
poll:
total_people:
one: "%{count} person"
other: "%{count} persones"
total_votes:
one: "%{count} vote"
other: "%{count} votes"
vote: Votar
show_more: Monstrar plu
show_thread: Monstrar fil
title: "%{name}: «%{quote}»"
visibilities:
direct: Direct

View File

@ -7,7 +7,6 @@ io:
hosted_on: Mastodon hostigesas che %{domain}
title: Pri co
accounts:
follow: Sequar
followers:
one: Sequanto
other: Sequanti
@ -1590,23 +1589,12 @@ io:
edited_at_html: Modifikesis ye %{date}
errors:
in_reply_not_found: Posto quon vu probas respondar semblas ne existas.
open_in_web: Apertar retnavigile
over_character_limit: limito de %{max} signi ecesita
pin_errors:
direct: Posti quo povas videsar nur mencionita uzanti ne povas pinglagesar
limit: Vu ja pinglagis maxima posti
ownership: Posto di altra persono ne povas pinglagesar
reblog: Repeto ne povas pinglizesar
poll:
total_people:
one: "%{count} persono"
other: "%{count} personi"
total_votes:
one: "%{count} voto"
other: "%{count} voti"
vote: Votez
show_more: Montrar plue
show_thread: Montrez postaro
title: '%{name}: "%{quote}"'
visibilities:
direct: Direta

View File

@ -7,7 +7,6 @@ is:
hosted_on: Mastodon hýst á %{domain}
title: Um hugbúnaðinn
accounts:
follow: Fylgjast með
followers:
one: fylgjandi
other: fylgjendur
@ -1744,23 +1743,12 @@ is:
edited_at_html: Breytt %{date}
errors:
in_reply_not_found: Færslan sem þú ert að reyna að svara að er líklega ekki til.
open_in_web: Opna í vafra
over_character_limit: hámarksfjölda stafa (%{max}) náð
pin_errors:
direct: Ekki er hægt að festa færslur sem einungis eru sýnilegar þeim notendum sem minnst er á
limit: Þú hefur þegar fest leyfilegan hámarksfjölda færslna
ownership: Færslur frá einhverjum öðrum er ekki hægt að festa
reblog: Ekki er hægt að festa endurbirtingu
poll:
total_people:
one: "%{count} aðili"
other: "%{count} aðilar"
total_votes:
one: "%{count} atkvæði"
other: "%{count} atkvæði"
vote: Greiða atkvæði
show_more: Sýna meira
show_thread: Birta þráð
title: "%{name}: „%{quote}‟"
visibilities:
direct: Beint

View File

@ -7,7 +7,6 @@ it:
hosted_on: Mastodon ospitato su %{domain}
title: Info
accounts:
follow: Segui
followers:
one: Seguace
other: Seguaci
@ -1742,23 +1741,12 @@ it:
edited_at_html: Modificato il %{date}
errors:
in_reply_not_found: Il post a cui stai tentando di rispondere non sembra esistere.
open_in_web: Apri sul Web
over_character_limit: Limite caratteri superato di %{max}
pin_errors:
direct: I messaggi visibili solo agli utenti citati non possono essere fissati in cima
limit: Hai già fissato in cima il massimo numero di post
ownership: Non puoi fissare in cima un post di qualcun altro
reblog: Un toot condiviso non può essere fissato in cima
poll:
total_people:
one: "%{count} persona"
other: "%{count} persone"
total_votes:
one: "%{count} voto"
other: "%{count} voti"
vote: Vota
show_more: Mostra di più
show_thread: Mostra thread
title: '%{name}: "%{quote}"'
visibilities:
direct: Diretto

View File

@ -7,7 +7,6 @@ ja:
hosted_on: Mastodon hosted on %{domain}
title: このサーバーについて
accounts:
follow: フォロー
followers:
other: フォロワー
following: フォロー中
@ -1700,21 +1699,12 @@ ja:
edited_at_html: "%{date} 編集済み"
errors:
in_reply_not_found: あなたが返信しようとしている投稿は存在しないようです。
open_in_web: Webで開く
over_character_limit: 上限は%{max}文字です
pin_errors:
direct: 返信したユーザーのみに表示される投稿はピン留めできません
limit: 固定できる投稿数の上限に達しました
ownership: 他人の投稿を固定することはできません
reblog: ブーストを固定することはできません
poll:
total_people:
other: "%{count}人"
total_votes:
other: "%{count}票"
vote: 投票
show_more: もっと見る
show_thread: スレッドを表示
title: '%{name}: "%{quote}"'
visibilities:
direct: ダイレクト

View File

@ -6,7 +6,6 @@ ka:
contact_unavailable: მიუწ.
hosted_on: მასტოდონს მასპინძლობს %{domain}
accounts:
follow: გაყევი
following: მიჰყვება
nothing_here: აქ არაფერია!
pin_errors:
@ -430,13 +429,11 @@ ka:
disallowed_hashtags:
one: 'მოიცავდა აკრძალულ ჰეშტეგს: %{tags}'
other: 'მოიცავს აკრძალულ ჰეშტეგს: %{tags}'
open_in_web: ვებში გახნსა
over_character_limit: ნიშნების ლიმიტი გადასცდა %{max}-ს
pin_errors:
limit: ტუტების მაქსიმალური რაოდენობა უკვე აპინეთ
ownership: სხვისი ტუტი ვერ აიპინება
reblog: ბუსტი ვერ აიპინება
show_more: მეტის ჩვენება
visibilities:
private: მხოლოდ-მიმდევრები
private_long: აჩვენე მხოლოდ მიმდევრებს

View File

@ -7,7 +7,6 @@ kab:
hosted_on: Maṣṭudun yersen deg %{domain}
title: Ɣef
accounts:
follow: Ḍfeṛ
followers:
one: Umeḍfaṛ
other: Imeḍfaṛen
@ -798,17 +797,6 @@ kab:
one: "%{count} n tbidyutt"
other: "%{count} n tbidyutin"
edited_at_html: Tettwaẓreg ass n %{date}
open_in_web: Ldi deg Web
poll:
total_people:
one: "%{count} n wemdan"
other: "%{count} n yemdanen"
total_votes:
one: "%{count} n wedɣar"
other: "%{count} n yedɣaren"
vote: Dɣeṛ
show_more: Ssken-d ugar
show_thread: Ssken-d lxiḍ
title: '%{name}: "%{quote}"'
visibilities:
direct: Usrid

View File

@ -6,7 +6,6 @@ kk:
contact_unavailable: Белгісіз
hosted_on: Mastodon орнатылған %{domain} доменінде
accounts:
follow: Жазылу
followers:
one: Оқырман
other: Оқырман
@ -647,22 +646,11 @@ kk:
disallowed_hashtags:
one: 'рұқсат етілмеген хэштег: %{tags}'
other: 'рұқсат етілмеген хэштегтер: %{tags}'
open_in_web: Вебте ашу
over_character_limit: "%{max} максимум таңбадан асып кетті"
pin_errors:
limit: Жабыстырылатын жазба саны максимумынан асты
ownership: Біреудің жазбасы жабыстырылмайды
reblog: Бөлісілген жазба жабыстырылмайды
poll:
total_people:
one: "%{count} адам"
other: "%{count} адам"
total_votes:
one: "%{count} дауыс"
other: "%{count} дауыс"
vote: Дауыс беру
show_more: Тағы әкел
show_thread: Тақырыпты көрсет
visibilities:
private: Тек оқырмандарға
private_long: Тек оқырмандарға ғана көрінеді

View File

@ -7,7 +7,6 @@ ko:
hosted_on: "%{domain}에서 호스팅 되는 마스토돈"
title: 정보
accounts:
follow: 팔로우
followers:
other: 팔로워
following: 팔로잉
@ -1705,21 +1704,12 @@ ko:
edited_at_html: "%{date}에 편집됨"
errors:
in_reply_not_found: 답장하려는 게시물이 존재하지 않습니다.
open_in_web: Web으로 열기
over_character_limit: 최대 %{max}자까지 입력할 수 있습니다
pin_errors:
direct: 멘션된 사용자들에게만 보이는 게시물은 고정될 수 없습니다
limit: 이미 너무 많은 게시물을 고정했습니다
ownership: 다른 사람의 게시물은 고정될 수 없습니다
reblog: 부스트는 고정될 수 없습니다
poll:
total_people:
other: "%{count}명"
total_votes:
other: "%{count} 명 투표함"
vote: 투표
show_more: 더 보기
show_thread: 글타래 보기
title: '%{name}: "%{quote}"'
visibilities:
direct: 다이렉트

View File

@ -7,7 +7,6 @@ ku:
hosted_on: Mastodon li ser %{domain} tê pêşkêşkirin
title: Derbar
accounts:
follow: Bişopîne
followers:
one: Şopîner
other: Şopîner
@ -1404,23 +1403,12 @@ ku:
edited_at_html: Di %{date} de hate serrastkirin
errors:
in_reply_not_found: Ew şandiya ku tu dikî nakî bersivê bide xuya nake an jî hatiye jêbirin.
open_in_web: Di tevnê de veke
over_character_limit: sînorê karakterê %{max} derbas kir
pin_errors:
direct: Şandiyên ku tenê ji bikarhênerên qalkirî re têne xuyangkirin, nayê derzîkirin
limit: Jixwe te mezintirîn hejmara şandîyên xwe derzî kir
ownership: Şandiya kesekî din nay derzî kirin
reblog: Ev şandî nayê derzî kirin
poll:
total_people:
one: "%{count} kes"
other: "%{count} kes"
total_votes:
one: "%{count} deng"
other: "%{count} deng"
vote: Deng bide
show_more: Bêtir nîşan bide
show_thread: Mijarê nîşan bide
title: "%{name}%{quote}"
visibilities:
direct: Rasterast

View File

@ -7,7 +7,6 @@ la:
hosted_on: Mastodon in %{domain} hospitātum
title: De
accounts:
follow: Sequere
followers:
one: Sectātor
other: Sectātōrēs

View File

@ -7,7 +7,6 @@ lad:
hosted_on: Mastodon balabayado en %{domain}
title: Sovre mozotros
accounts:
follow: Sige
followers:
one: Suivante
other: Suivantes
@ -1677,23 +1676,12 @@ lad:
edited_at_html: Editado %{date}
errors:
in_reply_not_found: La publikasion a la ke aprovas arispondir no egziste.
open_in_web: Avre en web
over_character_limit: limito de karakteres de %{max} superado
pin_errors:
direct: Las publikasyones ke son vizivles solo para los utilizadores enmentados no pueden fiksarse
limit: Ya tienes fiksado el numero maksimo de publikasyones
ownership: La publikasyon de otra persona no puede fiksarse
reblog: No se puede fixar una repartajasyon
poll:
total_people:
one: "%{count} persona"
other: "%{count} personas"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Vota
show_more: Amostra mas
show_thread: Amostra diskusyon
title: '%{name}: "%{quote}"'
visibilities:
direct: Direkto

View File

@ -7,7 +7,6 @@ lt:
hosted_on: Mastodon talpinamas %{domain}
title: Apie
accounts:
follow: Sekti
followers:
few: Sekėjai
many: Sekėjo
@ -1107,16 +1106,11 @@ lt:
other: "%{count} vaizdų"
boosted_from_html: Pakelta iš %{acct_link}
content_warning: 'Turinio įspėjimas: %{warning}'
open_in_web: Atidaryti naudojan Web
over_character_limit: pasiektas %{max} simbolių limitas
pin_errors:
limit: Jūs jau prisegėte maksimalų toot'ų skaičų
ownership: Kitų vartotojų toot'ai negali būti prisegti
reblog: Pakeltos žinutės negali būti prisegtos
poll:
vote: Balsuoti
show_more: Rodyti daugiau
show_thread: Rodyti giją
visibilities:
private: Tik sekėjams
private_long: rodyti tik sekėjams

View File

@ -7,7 +7,6 @@ lv:
hosted_on: Mastodon mitināts %{domain}
title: Par
accounts:
follow: Sekot
followers:
one: Sekotājs
other: Sekotāji
@ -1645,25 +1644,12 @@ lv:
edited_at_html: Labots %{date}
errors:
in_reply_not_found: Šķiet, ka ziņa, uz kuru tu mēģini atbildēt, nepastāv.
open_in_web: Atvērt webā
over_character_limit: pārsniegts %{max} rakstzīmju ierobežojums
pin_errors:
direct: Ziņojumus, kas ir redzami tikai minētajiem lietotājiem, nevar piespraust
limit: Tu jau esi piespraudis maksimālo ziņu skaitu
ownership: Kāda cita ierakstu nevar piespraust
reblog: Izceltu ierakstu nevar piespraust
poll:
total_people:
one: "%{count} cilvēks"
other: "%{count} cilvēki"
zero: "%{count} cilvēku"
total_votes:
one: "%{count} balss"
other: "%{count} balsis"
zero: "%{count} balsu"
vote: Balsu skaits
show_more: Rādīt vairāk
show_thread: Rādīt tematu
title: "%{name}: “%{quote}”"
visibilities:
direct: Tiešs

View File

@ -4,7 +4,6 @@ ml:
contact_missing: സജ്ജമാക്കിയിട്ടില്ല
contact_unavailable: ലഭ്യമല്ല
accounts:
follow: പിന്തുടരുക
following: പിന്തുടരുന്നു
last_active: അവസാനം സജീവമായിരുന്നത്
link_verified_on: സന്ധിയുടെ ഉടമസ്ഥാവസ്‌കാശം %{date} ൽ പരിശോധിക്കപ്പെട്ടു

View File

@ -7,7 +7,6 @@ ms:
hosted_on: Mastodon dihoskan di %{domain}
title: Perihal
accounts:
follow: Ikut
followers:
other: Pengikut
following: Mengikuti
@ -1560,21 +1559,12 @@ ms:
edited_at_html: Disunting %{date}
errors:
in_reply_not_found: Pos yang anda cuba balas nampaknya tidak wujud.
open_in_web: Buka dalam web
over_character_limit: had aksara %{max} melebihi
pin_errors:
direct: Pos yang hanya boleh dilihat oleh pengguna yang disebut tidak boleh disematkan
limit: Anda telah menyematkan bilangan maksimum pos
ownership: Siaran orang lain tidak boleh disematkan
reblog: Rangsangan tidak boleh disematkan
poll:
total_people:
other: "%{count} orang"
total_votes:
other: "%{count} undi"
vote: Undi
show_more: Tunjuk lebih banyak
show_thread: Tunjuk bebenang
title: '%{name}: "%{quote}"'
visibilities:
direct: Terus

View File

@ -7,7 +7,6 @@ my:
hosted_on: "%{domain} မှ လက်ခံဆောင်ရွက်ထားသော Mastodon"
title: အကြောင်း
accounts:
follow: စောင့်ကြည့်မယ်
followers:
other: စောင့်ကြည့်သူ
following: စောင့်ကြည့်နေသည်
@ -1560,21 +1559,12 @@ my:
edited_at_html: "%{date} ကို ပြင်ဆင်ပြီးပါပြီ"
errors:
in_reply_not_found: သင် စာပြန်နေသည့်ပို့စ်မှာ မရှိတော့ပါ။
open_in_web: ဝဘ်တွင် ဖွင့်ပါ
over_character_limit: စာလုံးကန့်သတ်ချက် %{max} ကို ကျော်လွန်သွားပါပြီ
pin_errors:
direct: အမည်ဖော်ပြထားသည့် ပို့စ်များကို ပင်တွဲ၍မရပါ
limit: သင်သည် ပို့စ်အရေအတွက်အများဆုံးကို ပင်တွဲထားပြီးဖြစ်သည်
ownership: အခြားသူ၏ပို့စ်ကို ပင်တွဲ၍မရပါ
reblog: Boost လုပ်ထားသောပို့စ်ကို ပင်ထား၍မရပါ
poll:
total_people:
other: "%{count} ယောက်"
total_votes:
other: မဲအရေအတွက် %{count} မဲ
vote: မဲပေးမည်
show_more: ပိုမိုပြရန်
show_thread: Thread ကို ပြပါ
title: '%{name}: "%{quote}"'
visibilities:
direct: တိုက်ရိုက်

View File

@ -7,7 +7,6 @@ nl:
hosted_on: Mastodon op %{domain}
title: Over
accounts:
follow: Volgen
followers:
one: Volger
other: Volgers
@ -1740,23 +1739,12 @@ nl:
edited_at_html: Bewerkt op %{date}
errors:
in_reply_not_found: Het bericht waarop je probeert te reageren lijkt niet te bestaan.
open_in_web: In de webapp openen
over_character_limit: Limiet van %{max} tekens overschreden
pin_errors:
direct: Berichten die alleen zichtbaar zijn voor vermelde gebruikers, kunnen niet worden vastgezet
limit: Je hebt het maximaal aantal bericht al vastgemaakt
ownership: Een bericht van iemand anders kan niet worden vastgemaakt
reblog: Een boost kan niet worden vastgezet
poll:
total_people:
one: "%{count} persoon"
other: "%{count} personen"
total_votes:
one: "%{count} stem"
other: "%{count} stemmen"
vote: Stemmen
show_more: Meer tonen
show_thread: Gesprek tonen
title: '%{name}: "%{quote}"'
visibilities:
direct: Privébericht

View File

@ -7,7 +7,6 @@ nn:
hosted_on: "%{domain} er vert for Mastodon"
title: Om
accounts:
follow: Fylg
followers:
one: Fylgjar
other: Fylgjarar
@ -1740,23 +1739,12 @@ nn:
edited_at_html: Redigert %{date}
errors:
in_reply_not_found: Det ser ut til at tutet du freistar å svara ikkje finst.
open_in_web: Opn på nett
over_character_limit: øvregrensa for teikn, %{max}, er nådd
pin_errors:
direct: Innlegg som bare er synlige for nevnte brukere kan ikke festes
limit: Du har allereie festa så mange tut som det går an å festa
ownership: Du kan ikkje festa andre sine tut
reblog: Ei framheving kan ikkje festast
poll:
total_people:
one: "%{count} person"
other: "%{count} folk"
total_votes:
one: "%{count} røyst"
other: "%{count} røyster"
vote: Røyst
show_more: Vis meir
show_thread: Vis tråden
title: "%{name}: «%{quote}»"
visibilities:
direct: Direkte

View File

@ -7,7 +7,6 @@
hosted_on: Mastodon driftet på %{domain}
title: Om
accounts:
follow: Følg
followers:
one: Følger
other: Følgere
@ -1619,23 +1618,12 @@
edited_at_html: Redigert %{date}
errors:
in_reply_not_found: Posten du prøver å svare ser ikke ut til eksisterer.
open_in_web: Åpne i nettleser
over_character_limit: grensen på %{max} tegn overskredet
pin_errors:
direct: Innlegg som bare er synlige for nevnte brukere kan ikke festes
limit: Du har allerede festet det maksimale antall innlegg
ownership: Kun egne innlegg kan festes
reblog: En fremheving kan ikke festes
poll:
total_people:
one: "%{count} person"
other: "%{count} personer"
total_votes:
one: "%{count} stemme"
other: "%{count} stemmer"
vote: Stem
show_more: Vis mer
show_thread: Vis tråden
title: "%{name}: «%{quote}»"
visibilities:
direct: Direkte

View File

@ -7,7 +7,6 @@ oc:
hosted_on: Mastodon albergat sus %{domain}
title: A prepaus
accounts:
follow: Sègre
followers:
one: Seguidor
other: Seguidors
@ -846,22 +845,11 @@ oc:
edited_at_html: Modificat %{date}
errors:
in_reply_not_found: La publicacion que respondètz sembla pas mai exisitir.
open_in_web: Dobrir sul web
over_character_limit: limit de %{max} caractèrs passat
pin_errors:
limit: Avètz ja lo maximum de tuts penjats
ownership: Se pòt pas penjar lo tut de qualquun mai
reblog: Se pòt pas penjar un tut partejat
poll:
total_people:
one: "%{count} persona"
other: "%{count} personas"
total_votes:
one: "%{count} vòte"
other: "%{count} vòtes"
vote: Votar
show_more: Ne veire mai
show_thread: Mostrar lo fil
title: '%{name}: "%{quote}"'
visibilities:
direct: Dirècte

View File

@ -7,7 +7,6 @@ pa:
hosted_on: "%{domain} ਉੱਤੇ ਹੋਸਟ ਕੀਤਾ ਮਸਟਾਡੋਨ"
title: ਇਸ ਬਾਰੇ
accounts:
follow: ਫ਼ਾਲੋ
following: ਫ਼ਾਲੋ ਕੀਤੇ ਜਾ ਰਹੇ
posts_tab_heading: ਪੋਸਟਾਂ
admin:

View File

@ -7,7 +7,6 @@ pl:
hosted_on: Mastodon prowadzony na %{domain}
title: O nas
accounts:
follow: Obserwuj
followers:
few: śledzących
many: śledzących
@ -1800,27 +1799,12 @@ pl:
edited_at_html: Edytowane %{date}
errors:
in_reply_not_found: Post, na który próbujesz odpowiedzieć, nie istnieje.
open_in_web: Otwórz w przeglądarce
over_character_limit: limit %{max} znaków przekroczony
pin_errors:
direct: Nie możesz przypiąć wpisu, który jest widoczny tylko dla wspomnianych użytkowników
limit: Przekroczyłeś maksymalną liczbę przypiętych wpisów
ownership: Nie możesz przypiąć cudzego wpisu
reblog: Nie możesz przypiąć podbicia wpisu
poll:
total_people:
few: "%{count} osoby"
many: "%{count} osób"
one: "%{count} osoba"
other: "%{count} osoby"
total_votes:
few: "%{count} głosy"
many: "%{count} głosy"
one: "%{count} głos"
other: "%{count} głosy"
vote: Głosuj
show_more: Pokaż więcej
show_thread: Pokaż wątek
title: '%{name}: "%{quote}"'
visibilities:
direct: Bezpośredni

View File

@ -7,7 +7,6 @@ pt-BR:
hosted_on: Mastodon hospedado em %{domain}
title: Sobre
accounts:
follow: Seguir
followers:
one: Seguidor
other: Seguidores
@ -1740,23 +1739,12 @@ pt-BR:
edited_at_html: Editado em %{date}
errors:
in_reply_not_found: A publicação que você quer responder parece não existir.
open_in_web: Abrir no navegador
over_character_limit: limite de caracteres de %{max} excedido
pin_errors:
direct: Publicações visíveis apenas para usuários mencionados não podem ser fixadas
limit: Você alcançou o número limite de publicações fixadas
ownership: As publicações dos outros não podem ser fixadas
reblog: Um impulso não pode ser fixado
poll:
total_people:
one: "%{count} pessoa"
other: "%{count} pessoas"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Votar
show_more: Mostrar mais
show_thread: Mostrar conversa
title: '%{name}: "%{quote}"'
visibilities:
direct: Direto

View File

@ -7,7 +7,6 @@ pt-PT:
hosted_on: Mastodon alojado em %{domain}
title: Sobre
accounts:
follow: Seguir
followers:
one: Seguidor
other: Seguidores
@ -1683,23 +1682,12 @@ pt-PT:
edited_at_html: Editado em %{date}
errors:
in_reply_not_found: A publicação a que está a tentar responder parece não existir.
open_in_web: Abrir na web
over_character_limit: limite de caracter excedeu %{max}
pin_errors:
direct: Publicações visíveis apenas para utilizadores mencionados não podem ser afixadas
limit: Já afixaste a quantidade máxima de publicações
ownership: Não podem ser afixadas publicações doutras pessoas
reblog: Não pode afixar um reforço
poll:
total_people:
one: "%{count} pessoa"
other: "%{count} pessoas"
total_votes:
one: "%{count} voto"
other: "%{count} votos"
vote: Votar
show_more: Mostrar mais
show_thread: Mostrar conversa
title: '%{name}: "%{quote}"'
visibilities:
direct: Direto

View File

@ -7,7 +7,6 @@ ro:
hosted_on: Mastodon găzduit de %{domain}
title: Despre
accounts:
follow: Urmărește
followers:
few: Urmăritori
one: Urmăritor
@ -681,24 +680,11 @@ ro:
other: 'conținea aceste hashtag-uri nepermise: %{tags}'
errors:
in_reply_not_found: Postarea la care încercați să răspundeți nu pare să existe.
open_in_web: Deschide pe web
over_character_limit: s-a depășit limita de caracter %{max}
pin_errors:
limit: Deja ai fixat numărul maxim de postări
ownership: Postarea altcuiva nu poate fi fixată
reblog: Un impuls nu poate fi fixat
poll:
total_people:
few: "%{count} persoane"
one: "%{count} persoană"
other: "%{count} de persoane"
total_votes:
few: "%{count} voturi"
one: "%{count} vot"
other: "%{count} de voturi"
vote: Votează
show_more: Arată mai mult
show_thread: Arată discuția
visibilities:
private: Doar urmăritorii
private_long: Arată doar urmăritorilor

View File

@ -7,7 +7,6 @@ ru:
hosted_on: Вы получили это сообщение, так как зарегистрированы на %{domain}
title: О проекте
accounts:
follow: Подписаться
followers:
few: подписчика
many: подписчиков
@ -1692,27 +1691,12 @@ ru:
edited_at_html: Редактировано %{date}
errors:
in_reply_not_found: Пост, на который вы пытаетесь ответить, не существует или удалён.
open_in_web: Открыть в веб-версии
over_character_limit: превышен лимит символов (%{max})
pin_errors:
direct: Сообщения, видимые только упомянутым пользователям, не могут быть закреплены
limit: Вы закрепили максимально возможное число постов
ownership: Нельзя закрепить чужой пост
reblog: Нельзя закрепить продвинутый пост
poll:
total_people:
few: "%{count} человека"
many: "%{count} человек"
one: "%{count} человек"
other: "%{count} человек"
total_votes:
few: "%{count} голоса"
many: "%{count} голосов"
one: "%{count} голос"
other: "%{count} голосов"
vote: Голосовать
show_more: Развернуть
show_thread: Открыть обсуждение
title: '%{name}: "%{quote}"'
visibilities:
direct: Адресованный

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