Merge remote-tracking branch 'tootsuite/master' into glitchsoc/master

th-downstream
Jenkins 7 years ago
commit 1366e96a02

@ -89,7 +89,8 @@ module Admin
:username, :username,
:display_name, :display_name,
:email, :email,
:ip :ip,
:staff
) )
end end
end end

@ -92,7 +92,9 @@ module Admin
def filter_params def filter_params
params.permit( params.permit(
:local, :local,
:remote :remote,
:by_domain,
:shortcode
) )
end end
end end

@ -1,11 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
module Admin::FilterHelper module Admin::FilterHelper
ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip).freeze ACCOUNT_FILTERS = %i(local remote by_domain silenced suspended recent username display_name email ip staff).freeze
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
INVITE_FILTER = %i(available expired).freeze INVITE_FILTER = %i(available expired).freeze
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS
def filter_link_to(text, link_to_params, link_class_params = link_to_params) def filter_link_to(text, link_to_params, link_class_params = link_to_params)
new_url = filtered_url_for(link_to_params) new_url = filtered_url_for(link_to_params)

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif } from '../initial_state';
export default class Avatar extends React.PureComponent { export default class Avatar extends React.PureComponent {
@ -8,12 +9,12 @@ export default class Avatar extends React.PureComponent {
account: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
style: PropTypes.object, style: PropTypes.object,
animate: PropTypes.bool,
inline: PropTypes.bool, inline: PropTypes.bool,
animate: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
animate: false, animate: autoPlayGif,
size: 20, size: 20,
inline: false, inline: false,
}; };

@ -1,22 +1,29 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif } from '../initial_state';
export default class AvatarOverlay extends React.PureComponent { export default class AvatarOverlay extends React.PureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, account: ImmutablePropTypes.map.isRequired,
friend: ImmutablePropTypes.map.isRequired, friend: ImmutablePropTypes.map.isRequired,
animate: PropTypes.bool,
};
static defaultProps = {
animate: autoPlayGif,
}; };
render() { render() {
const { account, friend } = this.props; const { account, friend, animate } = this.props;
const baseStyle = { const baseStyle = {
backgroundImage: `url(${account.get('avatar_static')})`, backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
}; };
const overlayStyle = { const overlayStyle = {
backgroundImage: `url(${friend.get('avatar_static')})`, backgroundImage: `url(${friend.get(animate ? 'avatar' : 'avatar_static')})`,
}; };
return ( return (

@ -156,6 +156,8 @@ export default class ComposeForm extends ImmutablePureComponent {
return ( return (
<div className='compose-form'> <div className='compose-form'>
<WarningContainer />
<Collapsable isVisible={this.props.spoiler} fullHeight={50}> <Collapsable isVisible={this.props.spoiler} fullHeight={50}>
<div className='spoiler-input'> <div className='spoiler-input'>
<label> <label>
@ -165,8 +167,6 @@ export default class ComposeForm extends ImmutablePureComponent {
</div> </div>
</Collapsable> </Collapsable>
<WarningContainer />
<ReplyIndicatorContainer /> <ReplyIndicatorContainer />
<div className='compose-form__autosuggest-wrapper'> <div className='compose-form__autosuggest-wrapper'>
@ -199,11 +199,11 @@ export default class ComposeForm extends ImmutablePureComponent {
<SensitiveButtonContainer /> <SensitiveButtonContainer />
<SpoilerButtonContainer /> <SpoilerButtonContainer />
</div> </div>
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div>
</div>
<div className='compose-form__publish'> <div className='compose-form__publish'>
<div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div> <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div>
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div>
</div>
</div> </div>
</div> </div>
); );

@ -6,6 +6,7 @@ import IconButton from '../../../components/icon_button';
import DisplayName from '../../../components/display_name'; import DisplayName from '../../../components/display_name';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { isRtl } from '../../../rtl';
const messages = defineMessages({ const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }, cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
@ -42,7 +43,10 @@ export default class ReplyIndicator extends ImmutablePureComponent {
return null; return null;
} }
const content = { __html: status.get('contentHtml') }; const content = { __html: status.get('contentHtml') };
const style = {
direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
};
return ( return (
<div className='reply-indicator'> <div className='reply-indicator'>
@ -55,7 +59,7 @@ export default class ReplyIndicator extends ImmutablePureComponent {
</a> </a>
</div> </div>
<div className='reply-indicator__content' dangerouslySetInnerHTML={content} /> <div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} />
</div> </div>
); );
} }

@ -62,7 +62,7 @@ export default class Upload extends ImmutablePureComponent {
render () { render () {
const { intl, media } = this.props; const { intl, media } = this.props;
const active = this.state.hovered || this.state.focused; const active = this.state.hovered || this.state.focused;
const description = this.state.dirtyDescription || media.get('description') || ''; const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || '';
return ( return (
<div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> <div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>

@ -161,7 +161,7 @@ export default class ListTimeline extends React.PureComponent {
scrollKey={`list_timeline-${columnId}`} scrollKey={`list_timeline-${columnId}`}
timelineId={`list:${id}`} timelineId={`list:${id}`}
loadMore={this.handleLoadMore} loadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet.' />} emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
/> />
</Column> </Column>
); );

@ -8,6 +8,7 @@ import {
} from '../../../actions/timelines'; } from '../../../actions/timelines';
import Column from '../../../components/column'; import Column from '../../../components/column';
import ColumnHeader from '../../../components/column_header'; import ColumnHeader from '../../../components/column_header';
import { connectHashtagStream } from '../../../actions/streaming';
@connect() @connect()
export default class HashtagTimeline extends React.PureComponent { export default class HashtagTimeline extends React.PureComponent {
@ -29,16 +30,13 @@ export default class HashtagTimeline extends React.PureComponent {
const { dispatch, hashtag } = this.props; const { dispatch, hashtag } = this.props;
dispatch(refreshHashtagTimeline(hashtag)); dispatch(refreshHashtagTimeline(hashtag));
this.disconnect = dispatch(connectHashtagStream(hashtag));
this.polling = setInterval(() => {
dispatch(refreshHashtagTimeline(hashtag));
}, 10000);
} }
componentWillUnmount () { componentWillUnmount () {
if (typeof this.polling !== 'undefined') { if (this.disconnect) {
clearInterval(this.polling); this.disconnect();
this.polling = null; this.disconnect = null;
} }
} }

@ -9,6 +9,7 @@ import {
import Column from '../../../components/column'; import Column from '../../../components/column';
import ColumnHeader from '../../../components/column_header'; import ColumnHeader from '../../../components/column_header';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import { connectPublicStream } from '../../../actions/streaming';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' }, title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
@ -35,16 +36,13 @@ export default class PublicTimeline extends React.PureComponent {
const { dispatch } = this.props; const { dispatch } = this.props;
dispatch(refreshPublicTimeline()); dispatch(refreshPublicTimeline());
this.disconnect = dispatch(connectPublicStream());
this.polling = setInterval(() => {
dispatch(refreshPublicTimeline());
}, 3000);
} }
componentWillUnmount () { componentWillUnmount () {
if (typeof this.polling !== 'undefined') { if (this.disconnect) {
clearInterval(this.polling); this.disconnect();
this.polling = null; this.disconnect = null;
} }
} }

@ -27,6 +27,8 @@ const componentMap = {
'LIST': ListTimeline, 'LIST': ListTimeline,
}; };
const isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
@component => injectIntl(component, { withRef: true }) @component => injectIntl(component, { withRef: true })
export default class ColumnsArea extends ImmutablePureComponent { export default class ColumnsArea extends ImmutablePureComponent {
@ -79,7 +81,8 @@ export default class ColumnsArea extends ImmutablePureComponent {
handleChildrenContentChange() { handleChildrenContentChange() {
if (!this.props.singleColumn) { if (!this.props.singleColumn) {
this._interruptScrollAnimation = scrollRight(this.node, this.node.scrollWidth - window.innerWidth); const modifier = isRtlLayout ? -1 : 1;
this._interruptScrollAnimation = scrollRight(this.node, (this.node.scrollWidth - window.innerWidth) * modifier);
} }
} }

@ -36,9 +36,9 @@
"column.favourites": "المفضلة", "column.favourites": "المفضلة",
"column.follow_requests": "طلبات المتابعة", "column.follow_requests": "طلبات المتابعة",
"column.home": "الرئيسية", "column.home": "الرئيسية",
"column.lists": "Lists", "column.lists": "القوائم",
"column.mutes": "الحسابات المكتومة", "column.mutes": "الحسابات المكتومة",
"column.notifications": "الإشعارات", "column.notifications": "الإخطارات",
"column.pins": "التبويقات المثبتة", "column.pins": "التبويقات المثبتة",
"column.public": "الخيط العام الموحد", "column.public": "الخيط العام الموحد",
"column_back_button.label": "العودة", "column_back_button.label": "العودة",
@ -64,7 +64,7 @@
"confirmations.delete.confirm": "حذف", "confirmations.delete.confirm": "حذف",
"confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟", "confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟",
"confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.delete_list.message": "هل تود حقا حذف هذه القائمة ؟",
"confirmations.domain_block.confirm": "إخفاء إسم النطاق كاملا", "confirmations.domain_block.confirm": "إخفاء إسم النطاق كاملا",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "أكتم", "confirmations.mute.confirm": "أكتم",
@ -109,32 +109,32 @@
"home.settings": "إعدادات العمود", "home.settings": "إعدادات العمود",
"keyboard_shortcuts.back": "للعودة", "keyboard_shortcuts.back": "للعودة",
"keyboard_shortcuts.boost": "للترقية", "keyboard_shortcuts.boost": "للترقية",
"keyboard_shortcuts.column": "to focus a status in one of the columns", "keyboard_shortcuts.column": "للتركيز على منشور على أحد الأعمدة",
"keyboard_shortcuts.compose": "to focus the compose textarea", "keyboard_shortcuts.compose": "للتركيز على نافذة تحرير النصوص",
"keyboard_shortcuts.description": "Description", "keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.down": "للإنتقال إلى أسفل القائمة", "keyboard_shortcuts.down": "للإنتقال إلى أسفل القائمة",
"keyboard_shortcuts.enter": "to open status", "keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "to favourite", "keyboard_shortcuts.favourite": "للإضافة إلى المفضلة",
"keyboard_shortcuts.heading": "Keyboard Shortcuts", "keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.hotkey": "Hotkey", "keyboard_shortcuts.hotkey": "مفتاح الإختصار",
"keyboard_shortcuts.legend": "to display this legend", "keyboard_shortcuts.legend": "لعرض هذا المفتاح",
"keyboard_shortcuts.mention": "لذِكر الناشر", "keyboard_shortcuts.mention": "لذِكر الناشر",
"keyboard_shortcuts.reply": "للردّ", "keyboard_shortcuts.reply": "للردّ",
"keyboard_shortcuts.search": "to focus search", "keyboard_shortcuts.search": "للتركيز على البحث",
"keyboard_shortcuts.toot": "لتحرير تبويق جديد", "keyboard_shortcuts.toot": "لتحرير تبويق جديد",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "للإنتقال إلى أعلى القائمة", "keyboard_shortcuts.up": "للإنتقال إلى أعلى القائمة",
"lightbox.close": "إغلاق", "lightbox.close": "إغلاق",
"lightbox.next": "التالي", "lightbox.next": "التالي",
"lightbox.previous": "العودة", "lightbox.previous": "العودة",
"lists.account.add": "Add to list", "lists.account.add": "أضف إلى القائمة",
"lists.account.remove": "Remove from list", "lists.account.remove": "إحذف من القائمة",
"lists.delete": "Delete list", "lists.delete": "Delete list",
"lists.edit": "Edit list", "lists.edit": "تعديل القائمة",
"lists.new.create": "Add list", "lists.new.create": "إنشاء قائمة",
"lists.new.title_placeholder": "New list title", "lists.new.title_placeholder": "عنوان القائمة الجديدة",
"lists.search": "Search among people you follow", "lists.search": "إبحث في قائمة الحسابات التي تُتابِعها",
"lists.subheading": "Your lists", "lists.subheading": "قوائمك",
"loading_indicator.label": "تحميل ...", "loading_indicator.label": "تحميل ...",
"media_gallery.toggle_visible": "عرض / إخفاء", "media_gallery.toggle_visible": "عرض / إخفاء",
"missing_indicator.label": "تعذر العثور عليه", "missing_indicator.label": "تعذر العثور عليه",
@ -146,7 +146,7 @@
"navigation_bar.follow_requests": "طلبات المتابعة", "navigation_bar.follow_requests": "طلبات المتابعة",
"navigation_bar.info": "معلومات إضافية", "navigation_bar.info": "معلومات إضافية",
"navigation_bar.keyboard_shortcuts": "إختصارات لوحة المفاتيح", "navigation_bar.keyboard_shortcuts": "إختصارات لوحة المفاتيح",
"navigation_bar.lists": "Lists", "navigation_bar.lists": "القوائم",
"navigation_bar.logout": "خروج", "navigation_bar.logout": "خروج",
"navigation_bar.mutes": "الحسابات المكتومة", "navigation_bar.mutes": "الحسابات المكتومة",
"navigation_bar.pins": "التبويقات المثبتة", "navigation_bar.pins": "التبويقات المثبتة",
@ -209,7 +209,7 @@
"search_popout.search_format": "نمط البحث المتقدم", "search_popout.search_format": "نمط البحث المتقدم",
"search_popout.tips.hashtag": "وسم", "search_popout.tips.hashtag": "وسم",
"search_popout.tips.status": "حالة", "search_popout.tips.status": "حالة",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية",
"search_popout.tips.user": "مستخدِم", "search_popout.tips.user": "مستخدِم",
"search_results.total": "{count, number} {count, plural, one {result} و {results}}", "search_results.total": "{count, number} {count, plural, one {result} و {results}}",
"standalone.public_title": "نظرة على ...", "standalone.public_title": "نظرة على ...",

@ -36,7 +36,7 @@
"column.favourites": "Favorits", "column.favourites": "Favorits",
"column.follow_requests": "Peticions per seguir-te", "column.follow_requests": "Peticions per seguir-te",
"column.home": "Inici", "column.home": "Inici",
"column.lists": "Lists", "column.lists": "Llistes",
"column.mutes": "Usuaris silenciats", "column.mutes": "Usuaris silenciats",
"column.notifications": "Notificacions", "column.notifications": "Notificacions",
"column.pins": "Toot fixat", "column.pins": "Toot fixat",
@ -64,7 +64,7 @@
"confirmations.delete.confirm": "Esborrar", "confirmations.delete.confirm": "Esborrar",
"confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?", "confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?",
"confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.delete_list.message": "Estàs segur que vols esborrar permanenment aquesta llista?",
"confirmations.domain_block.confirm": "Amagar tot el domini", "confirmations.domain_block.confirm": "Amagar tot el domini",
"confirmations.domain_block.message": "Estàs realment, realment segur que vols bloquejar totalment {domain}? En la majoria dels casos bloquejar o silenciar és suficient i preferible.", "confirmations.domain_block.message": "Estàs realment, realment segur que vols bloquejar totalment {domain}? En la majoria dels casos bloquejar o silenciar és suficient i preferible.",
"confirmations.mute.confirm": "Silenciar", "confirmations.mute.confirm": "Silenciar",
@ -127,14 +127,14 @@
"lightbox.close": "Tancar", "lightbox.close": "Tancar",
"lightbox.next": "Següent", "lightbox.next": "Següent",
"lightbox.previous": "Anterior", "lightbox.previous": "Anterior",
"lists.account.add": "Add to list", "lists.account.add": "Afegir a la llista",
"lists.account.remove": "Remove from list", "lists.account.remove": "Treure de la llista",
"lists.delete": "Delete list", "lists.delete": "Delete list",
"lists.edit": "Edit list", "lists.edit": "Editar llista",
"lists.new.create": "Add list", "lists.new.create": "Afegir llista",
"lists.new.title_placeholder": "New list title", "lists.new.title_placeholder": "Nou títol de llista",
"lists.search": "Search among people you follow", "lists.search": "Cercar entre les persones que segueixes",
"lists.subheading": "Your lists", "lists.subheading": "Les teves llistes",
"loading_indicator.label": "Carregant...", "loading_indicator.label": "Carregant...",
"media_gallery.toggle_visible": "Alternar visibilitat", "media_gallery.toggle_visible": "Alternar visibilitat",
"missing_indicator.label": "No trobat", "missing_indicator.label": "No trobat",
@ -146,7 +146,7 @@
"navigation_bar.follow_requests": "Sol·licituds de seguiment", "navigation_bar.follow_requests": "Sol·licituds de seguiment",
"navigation_bar.info": "Informació addicional", "navigation_bar.info": "Informació addicional",
"navigation_bar.keyboard_shortcuts": "Dreceres de teclat", "navigation_bar.keyboard_shortcuts": "Dreceres de teclat",
"navigation_bar.lists": "Lists", "navigation_bar.lists": "Llistes",
"navigation_bar.logout": "Tancar sessió", "navigation_bar.logout": "Tancar sessió",
"navigation_bar.mutes": "Usuaris silenciats", "navigation_bar.mutes": "Usuaris silenciats",
"navigation_bar.pins": "Toots fixats", "navigation_bar.pins": "Toots fixats",

@ -93,7 +93,7 @@
"empty_column.hashtag": "There is nothing in this hashtag yet.", "empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
"empty_column.home.public_timeline": "the public timeline", "empty_column.home.public_timeline": "the public timeline",
"empty_column.list": "There is nothing in this list yet.", "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
"follow_request.authorize": "Authorize", "follow_request.authorize": "Authorize",

@ -91,7 +91,7 @@
"empty_column.hashtag": "Il ny a encore aucun contenu associé à ce hashtag", "empty_column.hashtag": "Il ny a encore aucun contenu associé à ce hashtag",
"empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à dautres utilisateur⋅ice⋅s.", "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à dautres utilisateur⋅ice⋅s.",
"empty_column.home.public_timeline": "le fil public", "empty_column.home.public_timeline": "le fil public",
"empty_column.list": "Il n'y a rien dans cette liste pour l'instant.", "empty_column.list": "Il n'y a rien dans cette liste pour l'instant. Dès que des personnes de cette listes publierons de nouveaux statuts ils apparaîtront ici.",
"empty_column.notifications": "Vous navez pas encore de notification. Interagissez avec dautres utilisateur⋅ice⋅s pour débuter la conversation.", "empty_column.notifications": "Vous navez pas encore de notification. Interagissez avec dautres utilisateur⋅ice⋅s pour débuter la conversation.",
"empty_column.public": "Il ny a rien ici! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateur⋅ice⋅s dautres instances pour remplir le fil public.", "empty_column.public": "Il ny a rien ici! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateur⋅ice⋅s dautres instances pour remplir le fil public.",
"follow_request.authorize": "Accepter", "follow_request.authorize": "Accepter",

@ -36,7 +36,7 @@
"column.favourites": "Favoritas", "column.favourites": "Favoritas",
"column.follow_requests": "Peticións de seguimento", "column.follow_requests": "Peticións de seguimento",
"column.home": "Inicio", "column.home": "Inicio",
"column.lists": "Lists", "column.lists": "Listas",
"column.mutes": "Usuarias acaladas", "column.mutes": "Usuarias acaladas",
"column.notifications": "Notificacións", "column.notifications": "Notificacións",
"column.pins": "Mensaxes fixadas", "column.pins": "Mensaxes fixadas",
@ -64,7 +64,7 @@
"confirmations.delete.confirm": "Borrar", "confirmations.delete.confirm": "Borrar",
"confirmations.delete.message": "Está segura de que quere eliminar este estado?", "confirmations.delete.message": "Está segura de que quere eliminar este estado?",
"confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.delete_list.message": "Estás seguro de que queres eliminar permanentemente esta lista?",
"confirmations.domain_block.confirm": "Agochar un dominio completo", "confirmations.domain_block.confirm": "Agochar un dominio completo",
"confirmations.domain_block.message": "Realmente está segura de que quere bloquear por completo o dominio {domain}? Normalmente é suficiente, e preferible, bloquear de xeito selectivo varios elementos.", "confirmations.domain_block.message": "Realmente está segura de que quere bloquear por completo o dominio {domain}? Normalmente é suficiente, e preferible, bloquear de xeito selectivo varios elementos.",
"confirmations.mute.confirm": "Acalar", "confirmations.mute.confirm": "Acalar",
@ -127,14 +127,14 @@
"lightbox.close": "Fechar", "lightbox.close": "Fechar",
"lightbox.next": "Seguinte", "lightbox.next": "Seguinte",
"lightbox.previous": "Anterior", "lightbox.previous": "Anterior",
"lists.account.add": "Add to list", "lists.account.add": "Engadir á lista",
"lists.account.remove": "Remove from list", "lists.account.remove": "Eliminar da lista",
"lists.delete": "Delete list", "lists.delete": "Delete list",
"lists.edit": "Edit list", "lists.edit": "Editar lista",
"lists.new.create": "Add list", "lists.new.create": "Engadir lista",
"lists.new.title_placeholder": "New list title", "lists.new.title_placeholder": "Novo título da lista",
"lists.search": "Search among people you follow", "lists.search": "Procurar entre a xente que segues",
"lists.subheading": "Your lists", "lists.subheading": "As túas listas",
"loading_indicator.label": "Cargando...", "loading_indicator.label": "Cargando...",
"media_gallery.toggle_visible": "Dar visibilidade", "media_gallery.toggle_visible": "Dar visibilidade",
"missing_indicator.label": "Non atopado", "missing_indicator.label": "Non atopado",
@ -146,7 +146,7 @@
"navigation_bar.follow_requests": "Peticións de seguimento", "navigation_bar.follow_requests": "Peticións de seguimento",
"navigation_bar.info": "Sobre esta instancia", "navigation_bar.info": "Sobre esta instancia",
"navigation_bar.keyboard_shortcuts": "Atallos do teclado", "navigation_bar.keyboard_shortcuts": "Atallos do teclado",
"navigation_bar.lists": "Lists", "navigation_bar.lists": "Listas",
"navigation_bar.logout": "Sair", "navigation_bar.logout": "Sair",
"navigation_bar.mutes": "Usuarias acaladas", "navigation_bar.mutes": "Usuarias acaladas",
"navigation_bar.pins": "Mensaxes fixadas", "navigation_bar.pins": "Mensaxes fixadas",

@ -95,7 +95,7 @@
"empty_column.home.public_timeline": "連合タイムライン", "empty_column.home.public_timeline": "連合タイムライン",
"empty_column.list": "このリストにはまだなにもありません。", "empty_column.list": "このリストにはまだなにもありません。",
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
"empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう", "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう",
"follow_request.authorize": "許可", "follow_request.authorize": "許可",
"follow_request.reject": "拒否", "follow_request.reject": "拒否",
"getting_started.appsshort": "アプリ", "getting_started.appsshort": "アプリ",
@ -162,12 +162,12 @@
"notifications.clear": "通知を消去", "notifications.clear": "通知を消去",
"notifications.clear_confirmation": "本当に通知を消去しますか?", "notifications.clear_confirmation": "本当に通知を消去しますか?",
"notifications.column_settings.alert": "デスクトップ通知", "notifications.column_settings.alert": "デスクトップ通知",
"notifications.column_settings.favourite": "お気に入り", "notifications.column_settings.favourite": "お気に入り:",
"notifications.column_settings.follow": "新しいフォロワー", "notifications.column_settings.follow": "新しいフォロワー:",
"notifications.column_settings.mention": "返信", "notifications.column_settings.mention": "返信:",
"notifications.column_settings.push": "プッシュ通知", "notifications.column_settings.push": "プッシュ通知",
"notifications.column_settings.push_meta": "このデバイス", "notifications.column_settings.push_meta": "このデバイス",
"notifications.column_settings.reblog": "ブースト", "notifications.column_settings.reblog": "ブースト:",
"notifications.column_settings.show": "カラムに表示", "notifications.column_settings.show": "カラムに表示",
"notifications.column_settings.sound": "通知音を再生", "notifications.column_settings.sound": "通知音を再生",
"onboarding.done": "完了", "onboarding.done": "完了",
@ -176,7 +176,7 @@
"onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。", "onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。",
"onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。", "onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。",
"onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。", "onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。",
"onboarding.page_one.handle": "あなたは数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です", "onboarding.page_one.handle": "あなたは数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です",
"onboarding.page_one.welcome": "Mastodonへようこそ", "onboarding.page_one.welcome": "Mastodonへようこそ",
"onboarding.page_six.admin": "あなたのインスタンスの管理者は{admin}です。", "onboarding.page_six.admin": "あなたのインスタンスの管理者は{admin}です。",
"onboarding.page_six.almost_done": "以上です。", "onboarding.page_six.almost_done": "以上です。",
@ -184,7 +184,7 @@
"onboarding.page_six.apps_available": "iOS、Androidあるいは他のプラットフォームで使える{apps}があります。", "onboarding.page_six.apps_available": "iOS、Androidあるいは他のプラットフォームで使える{apps}があります。",
"onboarding.page_six.github": "MastodonはOSSです。バグ報告や機能要望あるいは貢献を{github}から行なえます。", "onboarding.page_six.github": "MastodonはOSSです。バグ報告や機能要望あるいは貢献を{github}から行なえます。",
"onboarding.page_six.guidelines": "コミュニティガイドライン", "onboarding.page_six.guidelines": "コミュニティガイドライン",
"onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください", "onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください",
"onboarding.page_six.various_app": "様々なモバイルアプリ", "onboarding.page_six.various_app": "様々なモバイルアプリ",
"onboarding.page_three.profile": "「プロフィールを編集」から、あなたの自己紹介や表示名を変更できます。またそこでは他の設定ができます。", "onboarding.page_three.profile": "「プロフィールを編集」から、あなたの自己紹介や表示名を変更できます。またそこでは他の設定ができます。",
"onboarding.page_three.search": "検索バーで、{illustration}や{introductions}のように特定のハッシュタグの投稿を見たり、ユーザーを探したりできます。", "onboarding.page_three.search": "検索バーで、{illustration}や{introductions}のように特定のハッシュタグの投稿を見たり、ユーザーを探したりできます。",
@ -215,7 +215,7 @@
"search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト", "search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト",
"search_popout.tips.user": "ユーザー", "search_popout.tips.user": "ユーザー",
"search_results.total": "{count, number}件の結果", "search_results.total": "{count, number}件の結果",
"standalone.public_title": "今こんな話をしています", "standalone.public_title": "今こんな話をしています...",
"status.cannot_reblog": "この投稿はブーストできません", "status.cannot_reblog": "この投稿はブーストできません",
"status.delete": "削除", "status.delete": "削除",
"status.embed": "埋め込み", "status.embed": "埋め込み",

@ -36,7 +36,7 @@
"column.favourites": "Favorieten", "column.favourites": "Favorieten",
"column.follow_requests": "Volgverzoeken", "column.follow_requests": "Volgverzoeken",
"column.home": "Start", "column.home": "Start",
"column.lists": "Lists", "column.lists": "Lijsten",
"column.mutes": "Genegeerde gebruikers", "column.mutes": "Genegeerde gebruikers",
"column.notifications": "Meldingen", "column.notifications": "Meldingen",
"column.pins": "Vastgezette toots", "column.pins": "Vastgezette toots",
@ -64,7 +64,7 @@
"confirmations.delete.confirm": "Verwijderen", "confirmations.delete.confirm": "Verwijderen",
"confirmations.delete.message": "Weet je het zeker dat je deze toot wilt verwijderen?", "confirmations.delete.message": "Weet je het zeker dat je deze toot wilt verwijderen?",
"confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.delete_list.message": "Weet je zeker dat je deze lijst permanent wilt verwijderen?",
"confirmations.domain_block.confirm": "Negeer alles van deze server", "confirmations.domain_block.confirm": "Negeer alles van deze server",
"confirmations.domain_block.message": "Weet je het echt, echt zeker dat je alles van {domain} wil negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en gewenst.", "confirmations.domain_block.message": "Weet je het echt, echt zeker dat je alles van {domain} wil negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en gewenst.",
"confirmations.mute.confirm": "Negeren", "confirmations.mute.confirm": "Negeren",
@ -127,14 +127,14 @@
"lightbox.close": "Sluiten", "lightbox.close": "Sluiten",
"lightbox.next": "Volgende", "lightbox.next": "Volgende",
"lightbox.previous": "Vorige", "lightbox.previous": "Vorige",
"lists.account.add": "Add to list", "lists.account.add": "Aan lijst toevoegen",
"lists.account.remove": "Remove from list", "lists.account.remove": "Uit lijst verwijderen",
"lists.delete": "Delete list", "lists.delete": "Delete list",
"lists.edit": "Edit list", "lists.edit": "Lijst bewerken",
"lists.new.create": "Add list", "lists.new.create": "Lijst toevoegen",
"lists.new.title_placeholder": "New list title", "lists.new.title_placeholder": "Naam nieuwe lijst",
"lists.search": "Search among people you follow", "lists.search": "Zoek naar mensen die je volgt",
"lists.subheading": "Your lists", "lists.subheading": "Jouw lijsten",
"loading_indicator.label": "Laden…", "loading_indicator.label": "Laden…",
"media_gallery.toggle_visible": "Media wel/niet tonen", "media_gallery.toggle_visible": "Media wel/niet tonen",
"missing_indicator.label": "Niet gevonden", "missing_indicator.label": "Niet gevonden",
@ -146,7 +146,7 @@
"navigation_bar.follow_requests": "Volgverzoeken", "navigation_bar.follow_requests": "Volgverzoeken",
"navigation_bar.info": "Uitgebreide informatie", "navigation_bar.info": "Uitgebreide informatie",
"navigation_bar.keyboard_shortcuts": "Toetsenbord sneltoetsen", "navigation_bar.keyboard_shortcuts": "Toetsenbord sneltoetsen",
"navigation_bar.lists": "Lists", "navigation_bar.lists": "Lijsten",
"navigation_bar.logout": "Afmelden", "navigation_bar.logout": "Afmelden",
"navigation_bar.mutes": "Genegeerde gebruikers", "navigation_bar.mutes": "Genegeerde gebruikers",
"navigation_bar.pins": "Vastgezette toots", "navigation_bar.pins": "Vastgezette toots",

@ -91,7 +91,7 @@
"empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.", "empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.",
"empty_column.home": "Vòstre flux dacuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a dautras personas.", "empty_column.home": "Vòstre flux dacuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a dautras personas.",
"empty_column.home.public_timeline": "lo flux public", "empty_column.home.public_timeline": "lo flux public",
"empty_column.list": "I a pas res dins la lista pel moment.", "empty_column.list": "I a pas res dins la lista pel moment. Quand de membres daquesta lista publiquen de novèls estatuts los veiretz aquí.",
"empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualquun per començar una conversacion.", "empty_column.notifications": "Avètz pas encara de notificacions. Respondètz a qualquun per començar una conversacion.",
"empty_column.public": "I a pas res aquí! Escrivètz quicòm de public, o seguètz de personas dautras instàncias per garnir lo flux public", "empty_column.public": "I a pas res aquí! Escrivètz quicòm de public, o seguètz de personas dautras instàncias per garnir lo flux public",
"follow_request.authorize": "Autorizar", "follow_request.authorize": "Autorizar",

@ -36,7 +36,7 @@
"column.favourites": "Favoritos", "column.favourites": "Favoritos",
"column.follow_requests": "Seguidores pendentes", "column.follow_requests": "Seguidores pendentes",
"column.home": "Página inicial", "column.home": "Página inicial",
"column.lists": "Lists", "column.lists": "Listas",
"column.mutes": "Usuários silenciados", "column.mutes": "Usuários silenciados",
"column.notifications": "Notificações", "column.notifications": "Notificações",
"column.pins": "Postagens fixadas", "column.pins": "Postagens fixadas",
@ -64,7 +64,7 @@
"confirmations.delete.confirm": "Excluir", "confirmations.delete.confirm": "Excluir",
"confirmations.delete.message": "Você tem certeza de que quer excluir esta postagem?", "confirmations.delete.message": "Você tem certeza de que quer excluir esta postagem?",
"confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.delete_list.message": "Você tem certeza que quer deletar permanentemente a lista?",
"confirmations.domain_block.confirm": "Esconder o domínio inteiro", "confirmations.domain_block.confirm": "Esconder o domínio inteiro",
"confirmations.domain_block.message": "Você quer mesmo bloquear {domain} inteiro? Na maioria dos casos, silenciar ou bloquear alguns usuários é o suficiente e o recomendado.", "confirmations.domain_block.message": "Você quer mesmo bloquear {domain} inteiro? Na maioria dos casos, silenciar ou bloquear alguns usuários é o suficiente e o recomendado.",
"confirmations.mute.confirm": "Silenciar", "confirmations.mute.confirm": "Silenciar",
@ -110,7 +110,7 @@
"keyboard_shortcuts.back": "para navegar de volta", "keyboard_shortcuts.back": "para navegar de volta",
"keyboard_shortcuts.boost": "para compartilhar", "keyboard_shortcuts.boost": "para compartilhar",
"keyboard_shortcuts.column": "Focar um status em uma das colunas", "keyboard_shortcuts.column": "Focar um status em uma das colunas",
"keyboard_shortcuts.compose": "to focus the compose textarea", "keyboard_shortcuts.compose": "para focar a área de redação",
"keyboard_shortcuts.description": "Description", "keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.down": "para mover para baixo na lista", "keyboard_shortcuts.down": "para mover para baixo na lista",
"keyboard_shortcuts.enter": "to open status", "keyboard_shortcuts.enter": "to open status",
@ -127,14 +127,14 @@
"lightbox.close": "Fechar", "lightbox.close": "Fechar",
"lightbox.next": "Próximo", "lightbox.next": "Próximo",
"lightbox.previous": "Anterior", "lightbox.previous": "Anterior",
"lists.account.add": "Add to list", "lists.account.add": "Adicionar a listas",
"lists.account.remove": "Remove from list", "lists.account.remove": "Remover da lista",
"lists.delete": "Delete list", "lists.delete": "Delete list",
"lists.edit": "Edit list", "lists.edit": "Editar lista",
"lists.new.create": "Add list", "lists.new.create": "Adicionar lista",
"lists.new.title_placeholder": "New list title", "lists.new.title_placeholder": "Novo título da lista",
"lists.search": "Search among people you follow", "lists.search": "Procurar entre as pessoas que você segue",
"lists.subheading": "Your lists", "lists.subheading": "Suas listas",
"loading_indicator.label": "Carregando...", "loading_indicator.label": "Carregando...",
"media_gallery.toggle_visible": "Esconder/Mostrar", "media_gallery.toggle_visible": "Esconder/Mostrar",
"missing_indicator.label": "Não encontrado", "missing_indicator.label": "Não encontrado",
@ -146,7 +146,7 @@
"navigation_bar.follow_requests": "Seguidores pendentes", "navigation_bar.follow_requests": "Seguidores pendentes",
"navigation_bar.info": "Mais informações", "navigation_bar.info": "Mais informações",
"navigation_bar.keyboard_shortcuts": "Atalhos de teclado", "navigation_bar.keyboard_shortcuts": "Atalhos de teclado",
"navigation_bar.lists": "Lists", "navigation_bar.lists": "Listas",
"navigation_bar.logout": "Sair", "navigation_bar.logout": "Sair",
"navigation_bar.mutes": "Usuários silenciados", "navigation_bar.mutes": "Usuários silenciados",
"navigation_bar.pins": "Postagens fixadas", "navigation_bar.pins": "Postagens fixadas",
@ -177,7 +177,7 @@
"onboarding.page_one.welcome": "Seja bem-vindo(a) ao Mastodon!", "onboarding.page_one.welcome": "Seja bem-vindo(a) ao Mastodon!",
"onboarding.page_six.admin": "O administrador de sua instância é {admin}.", "onboarding.page_six.admin": "O administrador de sua instância é {admin}.",
"onboarding.page_six.almost_done": "Quase acabando...", "onboarding.page_six.almost_done": "Quase acabando...",
"onboarding.page_six.appetoot": "Bon Appetoot!", "onboarding.page_six.appetoot": "Bom Apetoot!",
"onboarding.page_six.apps_available": "Há {apps} disponíveis para iOS, Android e outras plataformas.", "onboarding.page_six.apps_available": "Há {apps} disponíveis para iOS, Android e outras plataformas.",
"onboarding.page_six.github": "Mastodon é um software gratuito e de código aberto. Você pode reportar bugs, prequisitar novas funções ou contribuir para o código no {github}.", "onboarding.page_six.github": "Mastodon é um software gratuito e de código aberto. Você pode reportar bugs, prequisitar novas funções ou contribuir para o código no {github}.",
"onboarding.page_six.guidelines": "diretrizes da comunidade", "onboarding.page_six.guidelines": "diretrizes da comunidade",

@ -1,7 +1,7 @@
{ {
"account.block": "Bloquear @{name}", "account.block": "Bloquear @{name}",
"account.block_domain": "Esconder tudo do domínio {domain}", "account.block_domain": "Esconder tudo do domínio {domain}",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.",
"account.edit_profile": "Editar perfil", "account.edit_profile": "Editar perfil",
"account.follow": "Seguir", "account.follow": "Seguir",
"account.followers": "Seguidores", "account.followers": "Seguidores",
@ -19,7 +19,7 @@
"account.share": "Partilhar o perfil @{name}", "account.share": "Partilhar o perfil @{name}",
"account.show_reblogs": "Mostrar partilhas de @{name}", "account.show_reblogs": "Mostrar partilhas de @{name}",
"account.unblock": "Não bloquear @{name}", "account.unblock": "Não bloquear @{name}",
"account.unblock_domain": "Unhide {domain}", "account.unblock_domain": "Mostrar {domain}",
"account.unfollow": "Deixar de seguir", "account.unfollow": "Deixar de seguir",
"account.unmute": "Não silenciar @{name}", "account.unmute": "Não silenciar @{name}",
"account.unmute_notifications": "Deixar de silenciar @{name}", "account.unmute_notifications": "Deixar de silenciar @{name}",
@ -36,7 +36,7 @@
"column.favourites": "Favoritos", "column.favourites": "Favoritos",
"column.follow_requests": "Seguidores Pendentes", "column.follow_requests": "Seguidores Pendentes",
"column.home": "Home", "column.home": "Home",
"column.lists": "Lists", "column.lists": "Listas",
"column.mutes": "Utilizadores silenciados", "column.mutes": "Utilizadores silenciados",
"column.notifications": "Notificações", "column.notifications": "Notificações",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
@ -64,7 +64,7 @@
"confirmations.delete.confirm": "Eliminar", "confirmations.delete.confirm": "Eliminar",
"confirmations.delete.message": "De certeza que queres eliminar esta publicação?", "confirmations.delete.message": "De certeza que queres eliminar esta publicação?",
"confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "confirmations.delete_list.message": "Tens a certeza de que desejas apagar permanentemente esta lista?",
"confirmations.domain_block.confirm": "Esconder tudo deste domínio", "confirmations.domain_block.confirm": "Esconder tudo deste domínio",
"confirmations.domain_block.message": "De certeza que queres bloquear por completo o domínio {domain}? Na maioria dos casos, silenciar ou bloquear alguns utilizadores é o suficiente e o recomendado.", "confirmations.domain_block.message": "De certeza que queres bloquear por completo o domínio {domain}? Na maioria dos casos, silenciar ou bloquear alguns utilizadores é o suficiente e o recomendado.",
"confirmations.mute.confirm": "Silenciar", "confirmations.mute.confirm": "Silenciar",
@ -88,12 +88,12 @@
"emoji_button.symbols": "Símbolos", "emoji_button.symbols": "Símbolos",
"emoji_button.travel": "Viagens & Lugares", "emoji_button.travel": "Viagens & Lugares",
"empty_column.community": "Ainda não existe conteúdo local para mostrar!", "empty_column.community": "Ainda não existe conteúdo local para mostrar!",
"empty_column.hashtag": "Não foram encontradas publicações com essa hashtag", "empty_column.hashtag": "Não foram encontradas publicações com essa hashtag.",
"empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.", "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
"empty_column.home.public_timeline": "global", "empty_column.home.public_timeline": "global",
"empty_column.list": "Ainda não existem publicações nesta lista.", "empty_column.list": "Ainda não existem publicações nesta lista.",
"empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.", "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
"empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.", "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos",
"follow_request.authorize": "Autorizar", "follow_request.authorize": "Autorizar",
"follow_request.reject": "Rejeitar", "follow_request.reject": "Rejeitar",
"getting_started.appsshort": "Aplicações", "getting_started.appsshort": "Aplicações",
@ -116,7 +116,7 @@
"keyboard_shortcuts.enter": "para expandir uma publicação", "keyboard_shortcuts.enter": "para expandir uma publicação",
"keyboard_shortcuts.favourite": "para adicionar aos favoritos", "keyboard_shortcuts.favourite": "para adicionar aos favoritos",
"keyboard_shortcuts.heading": "Atalhos do teclado", "keyboard_shortcuts.heading": "Atalhos do teclado",
"keyboard_shortcuts.hotkey": "Hotkey", "keyboard_shortcuts.hotkey": "Atalho",
"keyboard_shortcuts.legend": "para mostrar esta legenda", "keyboard_shortcuts.legend": "para mostrar esta legenda",
"keyboard_shortcuts.mention": "para mencionar o autor", "keyboard_shortcuts.mention": "para mencionar o autor",
"keyboard_shortcuts.reply": "para responder", "keyboard_shortcuts.reply": "para responder",
@ -127,14 +127,14 @@
"lightbox.close": "Fechar", "lightbox.close": "Fechar",
"lightbox.next": "Próximo", "lightbox.next": "Próximo",
"lightbox.previous": "Anterior", "lightbox.previous": "Anterior",
"lists.account.add": "Add to list", "lists.account.add": "Adicionar à lista",
"lists.account.remove": "Remove from list", "lists.account.remove": "Remover da lista",
"lists.delete": "Delete list", "lists.delete": "Delete list",
"lists.edit": "Edit list", "lists.edit": "Editar lista",
"lists.new.create": "Add list", "lists.new.create": "Adicionar lista",
"lists.new.title_placeholder": "New list title", "lists.new.title_placeholder": "Novo título da lista",
"lists.search": "Search among people you follow", "lists.search": "Pesquisa entre as pessoas que segues",
"lists.subheading": "Your lists", "lists.subheading": "As tuas listas",
"loading_indicator.label": "A carregar...", "loading_indicator.label": "A carregar...",
"media_gallery.toggle_visible": "Esconder/Mostrar", "media_gallery.toggle_visible": "Esconder/Mostrar",
"missing_indicator.label": "Não encontrado", "missing_indicator.label": "Não encontrado",
@ -146,7 +146,7 @@
"navigation_bar.follow_requests": "Seguidores pendentes", "navigation_bar.follow_requests": "Seguidores pendentes",
"navigation_bar.info": "Mais informações", "navigation_bar.info": "Mais informações",
"navigation_bar.keyboard_shortcuts": "Atalhos de teclado", "navigation_bar.keyboard_shortcuts": "Atalhos de teclado",
"navigation_bar.lists": "Lists", "navigation_bar.lists": "Listas",
"navigation_bar.logout": "Sair", "navigation_bar.logout": "Sair",
"navigation_bar.mutes": "Utilizadores silenciados", "navigation_bar.mutes": "Utilizadores silenciados",
"navigation_bar.pins": "Posts fixos", "navigation_bar.pins": "Posts fixos",
@ -209,13 +209,13 @@
"search_popout.search_format": "Formato avançado de pesquisa", "search_popout.search_format": "Formato avançado de pesquisa",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags",
"search_popout.tips.user": "utilizador", "search_popout.tips.user": "utilizador",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"standalone.public_title": "Espreitar lá dentro...", "standalone.public_title": "Espreitar lá dentro...",
"status.cannot_reblog": "Este post não pode ser partilhado", "status.cannot_reblog": "Este post não pode ser partilhado",
"status.delete": "Eliminar", "status.delete": "Eliminar",
"status.embed": "Embed", "status.embed": "Incorporar",
"status.favourite": "Adicionar aos favoritos", "status.favourite": "Adicionar aos favoritos",
"status.load_more": "Carregar mais", "status.load_more": "Carregar mais",
"status.media_hidden": "Media escondida", "status.media_hidden": "Media escondida",

@ -91,7 +91,7 @@
"empty_column.hashtag": "这个话题标签下暂时没有内容。", "empty_column.hashtag": "这个话题标签下暂时没有内容。",
"empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。", "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
"empty_column.home.public_timeline": "公共时间轴", "empty_column.home.public_timeline": "公共时间轴",
"empty_column.list": "这个列表中暂时没有内容。", "empty_column.list": "这个列表中暂时没有内容。列表中用户所发送的的新嘟文将会在这里显示。",
"empty_column.notifications": "你还没有收到过通知信息,快向其他用户搭讪吧。", "empty_column.notifications": "你还没有收到过通知信息,快向其他用户搭讪吧。",
"empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户,这里就会有嘟文出现了哦!", "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户,这里就会有嘟文出现了哦!",
"follow_request.authorize": "同意", "follow_request.authorize": "同意",

@ -62,7 +62,13 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) { export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) {
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`); const params = [ `stream=${stream}` ];
if (accessToken !== null) {
params.push(`access_token=${accessToken}`);
}
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`);
ws.onopen = connected; ws.onopen = connected;
ws.onmessage = e => received(JSON.parse(e.data)); ws.onmessage = e => received(JSON.parse(e.data));

@ -265,198 +265,286 @@
.compose-form { .compose-form {
padding: 10px; padding: 10px;
}
.compose-form__warning {
color: darken($ui-secondary-color, 65%);
margin-bottom: 15px;
background: $ui-primary-color;
box-shadow: 0 2px 6px rgba($base-shadow-color, 0.3);
padding: 8px 10px;
border-radius: 4px;
font-size: 13px;
font-weight: 400;
strong { .compose-form__warning {
color: darken($ui-secondary-color, 65%); color: darken($ui-secondary-color, 65%);
font-weight: 500; margin-bottom: 15px;
background: $ui-primary-color;
box-shadow: 0 2px 6px rgba($base-shadow-color, 0.3);
padding: 8px 10px;
border-radius: 4px;
font-size: 13px;
font-weight: 400;
@each $lang in $cjk-langs { strong {
&:lang(#{$lang}) { color: darken($ui-secondary-color, 65%);
font-weight: 700; font-weight: 500;
@each $lang in $cjk-langs {
&:lang(#{$lang}) {
font-weight: 700;
}
}
}
a {
color: darken($ui-primary-color, 33%);
font-weight: 500;
text-decoration: underline;
&:hover,
&:active,
&:focus {
text-decoration: none;
} }
} }
} }
a { .compose-form__autosuggest-wrapper {
color: darken($ui-primary-color, 33%); position: relative;
font-weight: 500;
text-decoration: underline; .emoji-picker-dropdown {
position: absolute;
right: 5px;
top: 5px;
}
}
.autosuggest-textarea,
.spoiler-input {
position: relative;
}
.autosuggest-textarea__textarea,
.spoiler-input__input {
display: block;
box-sizing: border-box;
width: 100%;
margin: 0;
color: $ui-base-color;
background: $simple-background-color;
padding: 10px;
font-family: inherit;
font-size: 14px;
resize: vertical;
border: 0;
outline: 0;
&:hover,
&:active,
&:focus { &:focus {
text-decoration: none; outline: 0;
}
@media screen and (max-width: 600px) {
font-size: 16px;
} }
} }
}
.compose-form__modifiers { .spoiler-input__input {
color: $ui-base-color; border-radius: 4px;
font-family: inherit; }
font-size: 14px;
background: $simple-background-color;
border-radius: 0 0 4px;
}
.compose-form__buttons-wrapper { .autosuggest-textarea__textarea {
display: flex; min-height: 100px;
justify-content: space-between; border-radius: 4px 4px 0 0;
} padding-bottom: 0;
padding-right: 10px + 22px;
resize: none;
.compose-form__buttons { @media screen and (max-width: 600px) {
padding: 10px; height: 100px !important; // prevent auto-resize textarea
background: darken($simple-background-color, 8%); resize: vertical;
box-shadow: inset 0 5px 5px rgba($base-shadow-color, 0.05); }
border-radius: 0 0 4px 4px; }
display: flex;
.icon-button { .autosuggest-textarea__suggestions {
box-sizing: content-box; box-sizing: border-box;
padding: 0 3px; display: none;
position: absolute;
top: 100%;
width: 100%;
z-index: 99;
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
background: $ui-secondary-color;
border-radius: 0 0 4px 4px;
color: $ui-base-color;
font-size: 14px;
padding: 6px;
&.autosuggest-textarea__suggestions--visible {
display: block;
}
} }
}
.compose-form__upload-button-icon { .autosuggest-textarea__suggestions__item {
line-height: 27px; padding: 10px;
} cursor: pointer;
border-radius: 4px;
.compose-form__sensitive-button { &:hover,
display: none; &:focus,
&:active,
&.selected {
background: darken($ui-secondary-color, 10%);
}
}
.autosuggest-account,
.autosuggest-emoji {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
line-height: 18px;
font-size: 14px;
}
&.compose-form__sensitive-button--visible { .autosuggest-account-icon,
.autosuggest-emoji img {
display: block; display: block;
margin-right: 8px;
width: 16px;
height: 16px;
} }
.compose-form__sensitive-button__icon { .autosuggest-account .display-name__account {
line-height: 27px; color: lighten($ui-base-color, 36%);
} }
}
.compose-form__upload-wrapper { .compose-form__modifiers {
overflow: hidden; color: $ui-base-color;
} font-family: inherit;
font-size: 14px;
background: $simple-background-color;
.compose-form__uploads-wrapper { .compose-form__upload-wrapper {
display: flex; overflow: hidden;
flex-direction: row; }
padding: 5px;
flex-wrap: wrap;
}
.compose-form__upload { .compose-form__uploads-wrapper {
flex: 1 1 0; display: flex;
min-width: 40%; flex-direction: row;
margin: 5px; padding: 5px;
flex-wrap: wrap;
}
&-description { .compose-form__upload {
position: absolute; flex: 1 1 0;
z-index: 2; min-width: 40%;
bottom: 0; margin: 5px;
left: 0;
right: 0;
box-sizing: border-box;
background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
padding: 10px;
opacity: 0;
transition: opacity .1s ease;
input { &-description {
background: transparent; position: absolute;
color: $ui-secondary-color; z-index: 2;
border: 0; bottom: 0;
padding: 0; left: 0;
margin: 0; right: 0;
width: 100%; box-sizing: border-box;
font-family: inherit; background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
font-size: 14px; padding: 10px;
font-weight: 500; opacity: 0;
transition: opacity .1s ease;
input {
background: transparent;
color: $ui-secondary-color;
border: 0;
padding: 0;
margin: 0;
width: 100%;
font-family: inherit;
font-size: 14px;
font-weight: 500;
&:focus {
color: $white;
}
&:focus { &::placeholder {
color: $white; opacity: 0.54;
color: $ui-secondary-color;
}
}
&.active {
opacity: 1;
}
} }
&::placeholder { .icon-button {
opacity: 0.54; mix-blend-mode: difference;
color: $ui-secondary-color;
} }
} }
&.active { .compose-form__upload-thumbnail {
opacity: 1; border-radius: 4px;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
height: 100px;
width: 100%;
} }
} }
.icon-button { .compose-form__buttons-wrapper {
mix-blend-mode: difference; padding: 10px;
} background: darken($simple-background-color, 8%);
} border-radius: 0 0 4px 4px;
display: flex;
justify-content: space-between;
.compose-form__upload-thumbnail { .compose-form__buttons {
border-radius: 4px; display: flex;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
height: 100px;
width: 100%;
}
.compose-form__label { .compose-form__upload-button-icon {
display: block; line-height: 27px;
line-height: 24px; }
vertical-align: middle;
&.with-border { .compose-form__sensitive-button {
border-top: 1px solid $ui-base-color; display: none;
padding-top: 10px;
}
.compose-form__label__text { &.compose-form__sensitive-button--visible {
display: inline-block; display: block;
vertical-align: middle; }
margin-bottom: 14px;
margin-left: 8px;
color: $ui-primary-color;
}
}
.compose-form__textarea, .compose-form__sensitive-button__icon {
.follow-form__input { line-height: 27px;
background: $simple-background-color; }
}
}
&:disabled { .icon-button {
background: $ui-secondary-color; box-sizing: content-box;
} padding: 0 3px;
} }
.compose-form__autosuggest-wrapper { .character-counter__wrapper {
position: relative; align-self: center;
margin-right: 4px;
.emoji-picker-dropdown { .character-counter {
position: absolute; cursor: default;
right: 5px; font-family: 'mastodon-font-sans-serif', sans-serif;
top: 5px; font-size: 14px;
font-weight: 600;
color: lighten($ui-base-color, 12%);
&.character-counter--over {
color: $warning-red;
}
}
}
} }
}
.compose-form__publish { .compose-form__publish {
display: flex; display: flex;
min-width: 0; justify-content: flex-end;
} min-width: 0;
.compose-form__publish-button-wrapper { .compose-form__publish-button-wrapper {
overflow: hidden; overflow: hidden;
padding-top: 10px; padding-top: 10px;
}
}
} }
.emojione { .emojione {
@ -1973,121 +2061,6 @@
cursor: default; cursor: default;
} }
.autosuggest-textarea,
.spoiler-input {
position: relative;
}
.autosuggest-textarea__textarea,
.spoiler-input__input {
display: block;
box-sizing: border-box;
width: 100%;
margin: 0;
color: $ui-base-color;
background: $simple-background-color;
padding: 10px;
font-family: inherit;
font-size: 14px;
resize: vertical;
border: 0;
outline: 0;
&:focus {
outline: 0;
}
@media screen and (max-width: 600px) {
font-size: 16px;
}
}
.spoiler-input__input {
border-radius: 4px;
}
.autosuggest-textarea__textarea {
min-height: 100px;
border-radius: 4px 4px 0 0;
padding-bottom: 0;
padding-right: 10px + 22px;
resize: none;
@media screen and (max-width: 600px) {
height: 100px !important; // prevent auto-resize textarea
resize: vertical;
}
}
.autosuggest-textarea__suggestions {
box-sizing: border-box;
display: none;
position: absolute;
top: 100%;
width: 100%;
z-index: 99;
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
background: $ui-secondary-color;
border-radius: 0 0 4px 4px;
color: $ui-base-color;
font-size: 14px;
padding: 6px;
&.autosuggest-textarea__suggestions--visible {
display: block;
}
}
.autosuggest-textarea__suggestions__item {
padding: 10px;
cursor: pointer;
border-radius: 4px;
&:hover,
&:focus,
&:active,
&.selected {
background: darken($ui-secondary-color, 10%);
}
}
.autosuggest-account,
.autosuggest-emoji {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
line-height: 18px;
font-size: 14px;
}
.autosuggest-account-icon,
.autosuggest-emoji img {
display: block;
margin-right: 8px;
width: 16px;
height: 16px;
}
.autosuggest-account .display-name__account {
color: lighten($ui-base-color, 36%);
}
.character-counter__wrapper {
line-height: 36px;
margin: 0 16px 0 8px;
padding-top: 10px;
}
.character-counter {
cursor: default;
font-size: 16px;
}
.character-counter--over {
color: $warning-red;
}
.getting-started__wrapper { .getting-started__wrapper {
position: relative; position: relative;
overflow-y: auto; overflow-y: auto;

@ -7,9 +7,9 @@ body.rtl {
margin-left: 5px; margin-left: 5px;
} }
.character-counter__wrapper { .compose-form .compose-form__buttons-wrapper .character-counter__wrapper {
margin-right: 8px; margin-right: 0;
margin-left: 16px; margin-left: 4px;
} }
.navigation-bar__profile { .navigation-bar__profile {
@ -30,6 +30,22 @@ body.rtl {
.column-header__buttons { .column-header__buttons {
left: 0; left: 0;
right: auto; right: auto;
margin-left: -15px;
margin-right: 0;
}
.column-inline-form .icon-button {
margin-left: 0;
margin-right: 5px;
}
.column-header__links .text-btn {
margin-left: 10px;
margin-right: 0;
}
.account__avatar-wrapper {
float: right;
} }
.column-header__back-button { .column-header__back-button {
@ -41,10 +57,6 @@ body.rtl {
float: left; float: left;
} }
.compose-form__modifiers {
border-radius: 0 0 0 4px;
}
.setting-toggle { .setting-toggle {
margin-left: 0; margin-left: 0;
margin-right: 8px; margin-right: 8px;

@ -2,13 +2,26 @@
class ProviderDiscovery < OEmbed::ProviderDiscovery class ProviderDiscovery < OEmbed::ProviderDiscovery
class << self class << self
def get(url, **options)
provider = discover_provider(url, options)
options.delete(:html)
provider.get(url, options)
end
def discover_provider(url, **options) def discover_provider(url, **options)
res = Request.new(:get, url).perform
format = options[:format] format = options[:format]
raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html' if options[:html]
html = Nokogiri::HTML(options[:html])
else
res = Request.new(:get, url).perform
raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html'
html = Nokogiri::HTML(res.to_s) html = Nokogiri::HTML(res.to_s)
end
if format.nil? || format == :json if format.nil? || format == :json
provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value

@ -45,6 +45,8 @@ class AccountFilter
else else
Account.default_scoped Account.default_scoped
end end
when 'staff'
accounts_with_users.merge User.staff
else else
raise "Unknown filter: #{key}" raise "Unknown filter: #{key}"
end end

@ -27,6 +27,8 @@ class CustomEmojiFilter
CustomEmoji.remote CustomEmoji.remote
when 'by_domain' when 'by_domain'
CustomEmoji.where(domain: value) CustomEmoji.where(domain: value)
when 'shortcode'
CustomEmoji.where(shortcode: value)
else else
raise "Unknown filter: #{key}" raise "Unknown filter: #{key}"
end end

@ -40,6 +40,12 @@ class FetchLinkCardService < BaseService
return if res.code != 405 && (res.code != 200 || res.mime_type != 'text/html') return if res.code != 405 && (res.code != 200 || res.mime_type != 'text/html')
@response = Request.new(:get, @url).perform
return if @response.code != 200 || @response.mime_type != 'text/html'
@html = @response.to_s
attempt_oembed || attempt_opengraph attempt_oembed || attempt_opengraph
end end
@ -70,30 +76,32 @@ class FetchLinkCardService < BaseService
end end
def attempt_oembed def attempt_oembed
response = OEmbed::Providers.get(@url) embed = OEmbed::Providers.get(@url, html: @html)
return false unless response.respond_to?(:type) return false unless embed.respond_to?(:type)
@card.type = response.type @card.type = embed.type
@card.title = response.respond_to?(:title) ? response.title : '' @card.title = embed.respond_to?(:title) ? embed.title : ''
@card.author_name = response.respond_to?(:author_name) ? response.author_name : '' @card.author_name = embed.respond_to?(:author_name) ? embed.author_name : ''
@card.author_url = response.respond_to?(:author_url) ? response.author_url : '' @card.author_url = embed.respond_to?(:author_url) ? embed.author_url : ''
@card.provider_name = response.respond_to?(:provider_name) ? response.provider_name : '' @card.provider_name = embed.respond_to?(:provider_name) ? embed.provider_name : ''
@card.provider_url = response.respond_to?(:provider_url) ? response.provider_url : '' @card.provider_url = embed.respond_to?(:provider_url) ? embed.provider_url : ''
@card.width = 0 @card.width = 0
@card.height = 0 @card.height = 0
case @card.type case @card.type
when 'link' when 'link'
@card.image = URI.parse(response.thumbnail_url) if response.respond_to?(:thumbnail_url) @card.image = URI.parse(embed.thumbnail_url) if embed.respond_to?(:thumbnail_url)
when 'photo' when 'photo'
@card.embed_url = response.url return false unless embed.respond_to?(:url)
@card.width = response.width.presence || 0 @card.embed_url = embed.url
@card.height = response.height.presence || 0 @card.image = URI.parse(embed.url)
@card.width = embed.width.presence || 0
@card.height = embed.height.presence || 0
when 'video' when 'video'
@card.width = response.width.presence || 0 @card.width = embed.width.presence || 0
@card.height = response.height.presence || 0 @card.height = embed.height.presence || 0
@card.html = Formatter.instance.sanitize(response.html, Sanitize::Config::MASTODON_OEMBED) @card.html = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED)
when 'rich' when 'rich'
# Most providers rely on <script> tags, which is a no-no # Most providers rely on <script> tags, which is a no-no
return false return false
@ -105,17 +113,11 @@ class FetchLinkCardService < BaseService
end end
def attempt_opengraph def attempt_opengraph
response = Request.new(:get, @url).perform
return if response.code != 200 || response.mime_type != 'text/html'
html = response.to_s
detector = CharlockHolmes::EncodingDetector.new detector = CharlockHolmes::EncodingDetector.new
detector.strip_tags = true detector.strip_tags = true
guess = detector.detect(html, response.charset) guess = detector.detect(@html, @response.charset)
page = Nokogiri::HTML(html, nil, guess&.fetch(:encoding, nil)) page = Nokogiri::HTML(@html, nil, guess&.fetch(:encoding, nil))
if meta_property(page, 'twitter:player') if meta_property(page, 'twitter:player')
@card.type = :video @card.type = :video
@ -132,16 +134,16 @@ class FetchLinkCardService < BaseService
@card.image_remote_url = meta_property(page, 'og:image') if meta_property(page, 'og:image') @card.image_remote_url = meta_property(page, 'og:image') if meta_property(page, 'og:image')
end end
@card.title = meta_property(page, 'og:title').presence || page.at_xpath('//title')&.content || '' @card.title = meta_property(page, 'og:title').presence || page.at_xpath('//title')&.content || ''
@card.description = meta_property(page, 'og:description').presence || meta_property(page, 'description') || '' @card.description = meta_property(page, 'og:description').presence || meta_property(page, 'description') || ''
return if @card.title.blank? && @card.html.blank? return if @card.title.blank? && @card.html.blank?
@card.save_with_optional_image! @card.save_with_optional_image!
end end
def meta_property(html, property) def meta_property(page, property)
html.at_xpath("//meta[@property=\"#{property}\"]")&.attribute('content')&.value || html.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value page.at_xpath("//meta[@property=\"#{property}\"]")&.attribute('content')&.value || page.at_xpath("//meta[@name=\"#{property}\"]")&.attribute('content')&.value
end end
def lock_options def lock_options

@ -40,6 +40,6 @@ class FetchRemoteStatusService < BaseService
end end
def confirmed_domain?(domain, account) def confirmed_domain?(domain, account)
account.domain.nil? || domain.casecmp(account.domain).zero? || domain.casecmp(Addressable::URI.parse(account.remote_url || account.uri).normalized_host).zero? account.domain.nil? || domain.casecmp(account.domain).zero? || domain.casecmp(Addressable::URI.parse(account.remote_url.presence || account.uri).normalized_host).zero?
end end
end end

@ -22,7 +22,7 @@ class FollowService < BaseService
elsif source_account.requested?(target_account) elsif source_account.requested?(target_account)
# This isn't managed by a method in AccountInteractions, so we modify it # This isn't managed by a method in AccountInteractions, so we modify it
# ourselves if necessary. # ourselves if necessary.
req = follow_requests.find_by(target_account: other_account) req = source_account.follow_requests.find_by(target_account: target_account)
req.update!(show_reblogs: reblogs) req.update!(show_reblogs: reblogs)
return return
end end

@ -4,22 +4,11 @@
%td.domain %td.domain
- unless account.local? - unless account.local?
= link_to account.domain, admin_accounts_path(by_domain: account.domain) = link_to account.domain, admin_accounts_path(by_domain: account.domain)
%td.protocol %td
- unless account.local?
= account.protocol.humanize
%td.confirmed
- if account.local?
- if account.user_confirmed?
%i.fa.fa-check
- else
%i.fa.fa-times
%td.subscribed
- if account.local? - if account.local?
= t('admin.accounts.location.local') = t("admin.accounts.roles.#{account.user&.role}")
- elsif account.subscribed?
%i.fa.fa-check
- else - else
%i.fa.fa-times = account.protocol.humanize
%td %td
= table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}") = table_link_to 'circle', t('admin.accounts.web'), web_path("accounts/#{account.id}")
= table_link_to 'globe', t('admin.accounts.public'), TagManager.instance.url_for(account) = table_link_to 'globe', t('admin.accounts.public'), TagManager.instance.url_for(account)

@ -30,6 +30,11 @@
= filter_link_to t('admin.accounts.moderation.suspended'), {suspended: nil}, {suspended: '1'} = filter_link_to t('admin.accounts.moderation.suspended'), {suspended: nil}, {suspended: '1'}
- else - else
= filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1' = filter_link_to t('admin.accounts.moderation.suspended'), suspended: '1'
.filter-subset
%strong= t('admin.accounts.role')
%ul
%li= filter_link_to t('admin.accounts.moderation.all'), staff: nil
%li= filter_link_to t('admin.accounts.roles.staff'), staff: '1'
.filter-subset .filter-subset
%strong= t('admin.accounts.order.title') %strong= t('admin.accounts.order.title')
%ul %ul
@ -56,9 +61,7 @@
%tr %tr
%th= t('admin.accounts.username') %th= t('admin.accounts.username')
%th= t('admin.accounts.domain') %th= t('admin.accounts.domain')
%th= t('admin.accounts.protocol') %th
%th= t('admin.accounts.confirmed')
%th= fa_icon 'paper-plane-o'
%th %th
%tbody %tbody
= render @accounts = render @accounts

@ -104,7 +104,7 @@
- else - else
= link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:suspend, @account) = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:suspend, @account)
- unless @account.local? - if !@account.local? && @account.hub_url.present?
%hr %hr
%h3 OStatus %h3 OStatus
@ -132,6 +132,7 @@
- if @account.subscribed? - if @account.subscribed?
= link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' if can?(:unsubscribe, @account) = link_to t('admin.accounts.unsubscribe'), unsubscribe_admin_account_path(@account.id), method: :post, class: 'button negative' if can?(:unsubscribe, @account)
- if !@account.local? && @account.inbox_url.present?
%hr %hr
%h3 ActivityPub %h3 ActivityPub

@ -7,7 +7,7 @@
- if custom_emoji.local? - if custom_emoji.local?
= t('admin.accounts.location.local') = t('admin.accounts.location.local')
- else - else
= custom_emoji.domain = link_to custom_emoji.domain, admin_custom_emojis_path(by_domain: custom_emoji.domain)
%td %td
- if custom_emoji.local? - if custom_emoji.local?
- if custom_emoji.visible_in_picker - if custom_emoji.visible_in_picker

@ -17,6 +17,20 @@
- else - else
= filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil = filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil
= form_tag admin_custom_emojis_url, method: 'GET', class: 'simple_form' do
.fields-group
- Admin::FilterHelper::CUSTOM_EMOJI_FILTERS.each do |key|
- if params[key].present?
= hidden_field_tag key, params[key]
- %i(shortcode by_domain).each do |key|
.input.string.optional
= text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.custom_emojis.#{key}")
.actions
%button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_accounts_path, class: 'button negative'
.table-wrapper .table-wrapper
%table.table %table.table
%thead %thead

@ -17,9 +17,12 @@ Rails.application.configure do
config.x.alternate_domains = alternate_domains.split(/\s*,\s*/) config.x.alternate_domains = alternate_domains.split(/\s*,\s*/)
config.action_mailer.default_url_options = { host: web_host, protocol: https ? 'https://' : 'http://', trailing_slash: false } config.action_mailer.default_url_options = { host: web_host, protocol: https ? 'https://' : 'http://', trailing_slash: false }
config.x.streaming_api_base_url = 'ws://localhost:4000'
if Rails.env.production? config.x.streaming_api_base_url = ENV.fetch('STREAMING_API_BASE_URL') do
config.x.streaming_api_base_url = ENV.fetch('STREAMING_API_BASE_URL') { "ws#{https ? 's' : ''}://#{web_host}" } if Rails.env.production?
"ws#{https ? 's' : ''}://#{web_host}"
else
"ws://#{ENV['REMOTE_DEV'] == 'true' ? host.split(':').first : 'localhost'}:4000"
end
end end
end end

@ -57,20 +57,65 @@ ar:
order: order:
title: الترتيب title: الترتيب
profile_url: رابط الملف الشخصي profile_url: رابط الملف الشخصي
role: التصريحات
roles:
admin: مدير
user: مستخدِم
search: البحث
statuses: المنشورات
title: الحسابات
username: إسم المستخدم
web: الويب
custom_emojis: custom_emojis:
copy: نسخ
delete: حذف delete: حذف
emoji: إيموجي
enable: تفعيل
upload: رفع
domain_blocks:
domain: النطاق
show:
undo: إلغاء
undo: إلغاء
email_domain_blocks: email_domain_blocks:
delete: حذف delete: حذف
domain: النطاق
new:
create: إضافة نطاق
instances:
domain_name: النطاق
search: البحث
reports: reports:
are_you_sure: هل أنت متأكد ؟
comment:
label: تعليق
delete: حذف delete: حذف
report_contents: المحتويات
reported_by: أبلغ عنه من طرف
status: الحالة
title: التقارير
view: عرض
settings: settings:
contact_information:
email: البريد الإلكتروني المهني
registrations: registrations:
deletion: deletion:
desc_html: السماح لأي مستخدم إغلاق حسابه desc_html: السماح لأي مستخدم إغلاق حسابه
open:
title: فتح التسجيل
site_terms:
title: شروط الخدمة المخصصة
site_title: إسم مثيل الخادم
title: إعدادات الموقع
statuses: statuses:
back_to_account: العودة إلى صفحة الحساب
batch: batch:
delete: حذف delete: حذف
media:
title: الوسائط
title: الإدارة
application_mailer: application_mailer:
salutation: "%{name},"
settings: 'تغيير تفضيلات البريد الإلكتروني : %{link}' settings: 'تغيير تفضيلات البريد الإلكتروني : %{link}'
signature: إشعارات ماستدون من %{instance} signature: إشعارات ماستدون من %{instance}
view: 'View:' view: 'View:'
@ -83,6 +128,7 @@ ar:
forgot_password: نسيت كلمة المرور ؟ forgot_password: نسيت كلمة المرور ؟
login: تسجيل الدخول login: تسجيل الدخول
logout: خروج logout: خروج
migrate_account: الإنتقال إلى حساب آخر
register: إنشاء حساب register: إنشاء حساب
resend_confirmation: إعادة إرسال تعليمات التأكيد resend_confirmation: إعادة إرسال تعليمات التأكيد
reset_password: إعادة تعيين كلمة المرور reset_password: إعادة تعيين كلمة المرور
@ -106,15 +152,20 @@ ar:
x_months: "%{count} شه" x_months: "%{count} شه"
x_seconds: "%{count}ث" x_seconds: "%{count}ث"
deletes: deletes:
bad_password_msg: محاولة جيدة يا هاكرز ! كلمة السر خاطئة
proceed: حذف حساب proceed: حذف حساب
success_msg: تم حذف حسابك بنجاح
exports: exports:
blocks: قمت بحظر blocks: قمت بحظر
csv: CSV csv: CSV
follows: أنت تتبع follows: أنت تتبع
storage: ذاكرة التخزين storage: ذاكرة التخزين
followers:
domain: النطاق
followers_count: عدد المتابِعين
generic: generic:
changes_saved_msg: تم حفظ التعديلات بنجاح ! changes_saved_msg: تم حفظ التعديلات بنجاح !
powered_by: powered by %{link} powered_by: مدعوم بـ %{link}
save_changes: حفظ التغييرات save_changes: حفظ التغييرات
validation_errors: validation_errors:
one: Something isn't quite right yet! Please review the error below one: Something isn't quite right yet! Please review the error below
@ -128,14 +179,19 @@ ar:
upload: تحميل upload: تحميل
landing_strip_html: "<strong>%{name}</strong> is a user on %{link_to_root_path}. You can follow them or interact with them if you have an account anywhere in the fediverse.." landing_strip_html: "<strong>%{name}</strong> is a user on %{link_to_root_path}. You can follow them or interact with them if you have an account anywhere in the fediverse.."
landing_strip_signup_html: If you don't, you can <a href="%{sign_up_path}">sign up here</a>. landing_strip_signup_html: If you don't, you can <a href="%{sign_up_path}">sign up here</a>.
lists:
errors:
limit: لقد بلغت الحد الأقصى للقوائم
media_attachments: media_attachments:
validations: validations:
images_and_video: ليس بالإمكان إرفاق فيديو في منشور يحتوي مسبقا على صور images_and_video: ليس بالإمكان إرفاق فيديو في منشور يحتوي مسبقا على صور
too_many: لا يمكن إرفاق أكثر من 4 ملفات too_many: لا يمكن إرفاق أكثر من 4 ملفات
migrations:
acct: username@domain للحساب الجديد
notification_mailer: notification_mailer:
digest: digest:
body: 'Here is a brief summary of what you missed on %{instance} since your last visit on %{since}:' body: 'Here is a brief summary of what you missed on %{instance} since your last visit on %{since}:'
mention: "%{name} mentioned you in:" mention: "%{name} أشار إليك في :"
new_followers_summary: new_followers_summary:
one: لقد حصلت على متابع جديد ! one: لقد حصلت على متابع جديد !
other: لقد تحصلت على %{count} متتبعين جدد ! رائع ! other: لقد تحصلت على %{count} متتبعين جدد ! رائع !
@ -143,11 +199,11 @@ ar:
one: "إشعار واحد منذ زيارتك الأخيرة \U0001F418" one: "إشعار واحد منذ زيارتك الأخيرة \U0001F418"
other: "%{count} إشعارات جديدة منذ زيارتك الأخيرة \U0001F418" other: "%{count} إشعارات جديدة منذ زيارتك الأخيرة \U0001F418"
favourite: favourite:
body: أُعجب %{name} بمنشورك body: 'أُعجب %{name} بمنشورك :'
subject: "%{name} favourited your status" subject: "%{name} favourited your status"
follow: follow:
body: "%{name} من متتبعيك الآن !" body: "%{name} من متتبعيك الآن !"
subject: "%{name} من متتبعيك الآن !" subject: "%{name} من متتبعيك الآن"
follow_request: follow_request:
body: "%{name} has requested to follow you" body: "%{name} has requested to follow you"
subject: 'Pending follower: %{name}' subject: 'Pending follower: %{name}'
@ -171,16 +227,21 @@ ar:
pagination: pagination:
next: التالي next: التالي
prev: السابق prev: السابق
preferences:
languages: اللغات
other: إعدادات أخرى
publishing: النشر
remote_follow: remote_follow:
acct: Enter your username@domain you want to follow from acct: قم بإدخال عنوان حسابك username@domain الذي من خلاله تود المتابعة
missing_resource: Could not find the required redirect URL for your account missing_resource: Could not find the required redirect URL for your account
proceed: Proceed to follow proceed: أكمل المتابعة
prompt: 'إنك بصدد متابعة :' prompt: 'إنك بصدد متابعة :'
settings: settings:
authorized_apps: التطبيقات المرخص لها authorized_apps: التطبيقات المرخص لها
back: عودة إلى ماستدون back: عودة إلى ماستدون
edit_profile: تعديل الملف الشخصي edit_profile: تعديل الملف الشخصي
export: تصدير البيانات export: تصدير البيانات
followers: المتابِعون المُرَخّصون
import: إستيراد import: إستيراد
preferences: التفضيلات preferences: التفضيلات
settings: الإعدادات settings: الإعدادات

@ -286,7 +286,7 @@ ca:
desc_html: Mostra una insígnia de personal en una pàgina d'usuari desc_html: Mostra una insígnia de personal en una pàgina d'usuari
title: Mostra insígnia de personal title: Mostra insígnia de personal
site_description: site_description:
desc_html: Paràgraf introductori a la pàgina principal i en etiquetes meta.<br>Pots utilitzar etiquetes HTML, en particular <code>&lt;a&gt;</code> i <code>&lt;em&gt;</code>. desc_html: Paràgraf introductori a la pàgina principal i en etiquetes meta. Pots utilitzar etiquetes HTML, en particular <code>&lt;a&gt;</code> i <code>&lt;em&gt;</code>.
title: Descripció del lloc title: Descripció del lloc
site_description_extended: site_description_extended:
desc_html: Un bon lloc per al codi de conducta, regles, directrius i altres coses que distingeixen la vostra instància. Pots utilitzar etiquetes HTML desc_html: Un bon lloc per al codi de conducta, regles, directrius i altres coses que distingeixen la vostra instància. Pots utilitzar etiquetes HTML
@ -410,7 +410,7 @@ ca:
storage: Emmagatzematge storage: Emmagatzematge
followers: followers:
domain: Domini domain: Domini
explanation_html: Si desitges garantir la privacitat de les teves publicacions, has de ser conscient de qui t'està seguint. <strong> Les publicacions privades es lliuren a totes les instàncies on tens seguidors </ strong>. És possible que vulguis revisar-los i eliminar seguidors si no confies en que la teva privacitat sigui respectada pel personal o el programari d'aquestes instàncies. explanation_html: Si desitges garantir la privacitat de les teves publicacions, has de ser conscient de qui t'està seguint. <strong> Les publicacions privades es lliuren a totes les instàncies on tens seguidors </strong>. És possible que vulguis revisar-los i eliminar seguidors si no confies en que la teva privacitat sigui respectada pel personal o el programari d'aquestes instàncies.
followers_count: Nombre de seguidors followers_count: Nombre de seguidors
lock_link: Bloca el teu compte lock_link: Bloca el teu compte
purge: Elimina dels seguidors purge: Elimina dels seguidors

@ -8,8 +8,11 @@ ar:
inactive: لم يتم تنشيط حسابك بعد. inactive: لم يتم تنشيط حسابك بعد.
last_attempt: بإمكانك إعادة المحاولة مرة واحدة قبل أن يتم قفل حسابك. last_attempt: بإمكانك إعادة المحاولة مرة واحدة قبل أن يتم قفل حسابك.
locked: إن حسابك مقفل. locked: إن حسابك مقفل.
unauthenticated: يجب عليك تسجيل الدخول أو إنشاء حساب قبل المواصلة.
unconfirmed: يجب عليك تأكيد عنوان بريدك الإلكتروني قبل المواصلة. unconfirmed: يجب عليك تأكيد عنوان بريدك الإلكتروني قبل المواصلة.
mailer: mailer:
confirmation_instructions:
subject: 'ماستدون : تعليمات التأكيد لمثيل الخادوم %{instance}'
password_change: password_change:
subject: 'ماستدون : تم تغيير كلمة المرور' subject: 'ماستدون : تم تغيير كلمة المرور'
reset_password_instructions: reset_password_instructions:

@ -35,7 +35,7 @@ ja:
updated_not_active: パスワードは正常に更新されました。 updated_not_active: パスワードは正常に更新されました。
registrations: registrations:
destroyed: アカウントの作成はキャンセルされました。またのご利用をお待ちしています。 destroyed: アカウントの作成はキャンセルされました。またのご利用をお待ちしています。
signed_up: アカウントの作成が完了しました。Mastodonへようこそ signed_up: アカウントの作成が完了しました。Mastodonへようこそ
signed_up_but_inactive: アカウントの作成が完了しました。しかし、アカウントが有効化されていないためログインできませんでした。 signed_up_but_inactive: アカウントの作成が完了しました。しかし、アカウントが有効化されていないためログインできませんでした。
signed_up_but_locked: アカウントの作成が完了しました。しかし、アカウントがロックされているためログインできませんでした。 signed_up_but_locked: アカウントの作成が完了しました。しかし、アカウントがロックされているためログインできませんでした。
signed_up_but_unconfirmed: メールアドレスの確認用のリンクが入力したメールアドレスに送信されました。メール内のリンクをクリックしてアカウントを有効化してください。 signed_up_but_unconfirmed: メールアドレスの確認用のリンクが入力したメールアドレスに送信されました。メール内のリンクをクリックしてアカウントを有効化してください。
@ -58,4 +58,4 @@ ja:
not_locked: ロックされていません not_locked: ロックされていません
not_saved: not_saved:
one: エラーが発生したため、%{resource}の保存に失敗しました。 one: エラーが発生したため、%{resource}の保存に失敗しました。
other: "%{count}個のエラーが発生したため、保存に失敗しました。 %{resource}" other: "%{count}個のエラーが発生したため、%{resource}の保存に失敗しました:"

@ -10,7 +10,7 @@ pt:
inactive: A tua conta ainda não está ativada. inactive: A tua conta ainda não está ativada.
invalid: "%{authentication_keys} ou palavra-passe não válida." invalid: "%{authentication_keys} ou palavra-passe não válida."
last_attempt: Tens mais uma tentativa antes de a tua conta ficar bloqueada. last_attempt: Tens mais uma tentativa antes de a tua conta ficar bloqueada.
locked: A tua conta está bloqueada locked: A tua conta está bloqueada.
not_found_in_database: "%{authentication_keys} ou palavra-passe não válida." not_found_in_database: "%{authentication_keys} ou palavra-passe não válida."
timeout: A tua sessão expirou. Por favor, entra de novo para continuares. timeout: A tua sessão expirou. Por favor, entra de novo para continuares.
unauthenticated: Precisas de entrar na tua conta ou registares-te antes de continuar. unauthenticated: Precisas de entrar na tua conta ou registares-te antes de continuar.

@ -0,0 +1,33 @@
gl:
activerecord:
attributes:
doorkeeper/application:
name: Nome do aplicativo
redirect_uri: URI a redireccionar
website: Sitio web do aplicativo
errors:
models:
doorkeeper/application:
attributes:
redirect_uri:
fragment_present: non pode conter un fragmento.
invalid_uri: debe ser un URI válido.
relative_uri: debe ser un URI absoluto.
secured_uri: debe ser un URI HTTPS/SSL.
doorkeeper:
applications:
buttons:
authorize: Autorizar
cancel: Cancelar
destroy: Destruír
edit: Editar
submit: Enviar
confirmations:
destroy: Está segura?
edit:
title: Editar aplicativo
form:
error: Eeeeepa! Comprobe os posibles erros no formulario
help:
native_redirect_uri: Utilice %{native_redirect_uri} para probas locais
redirect_uri: Utilice unha liña por URI

@ -116,6 +116,7 @@ en:
roles: roles:
admin: Administrator admin: Administrator
moderator: Moderator moderator: Moderator
staff: Staff
user: User user: User
salmon_url: Salmon URL salmon_url: Salmon URL
search: Search search: Search
@ -160,6 +161,7 @@ en:
update_status: "%{name} updated status by %{target}" update_status: "%{name} updated status by %{target}"
title: Audit log title: Audit log
custom_emojis: custom_emojis:
by_domain: Domain
copied_msg: Successfully created local copy of the emoji copied_msg: Successfully created local copy of the emoji
copy: Copy copy: Copy
copy_failed_msg: Could not make a local copy of that emoji copy_failed_msg: Could not make a local copy of that emoji
@ -599,7 +601,7 @@ en:
notifications: Notifications notifications: Notifications
preferences: Preferences preferences: Preferences
settings: Settings settings: Settings
two_factor_authentication: Two-factor Authentication two_factor_authentication: Two-factor Auth
your_apps: Your applications your_apps: Your applications
statuses: statuses:
open_in_web: Open in web open_in_web: Open in web

@ -116,6 +116,7 @@ fr:
roles: roles:
admin: Administrateur admin: Administrateur
moderator: Modérateur moderator: Modérateur
staff: Personnel
user: Utilisateur user: Utilisateur
salmon_url: URL Salmon salmon_url: URL Salmon
search: Rechercher search: Rechercher
@ -135,12 +136,13 @@ fr:
web: Web web: Web
action_logs: action_logs:
actions: actions:
confirm_user: "%{name} adresse e-mail confirmée de l'utilisateur %{target}" confirm_user: "%{name} adresse courriel confirmée de l'utilisateur %{target}"
create_custom_emoji: "%{name} a importé de nouveaux emoji %{target}" create_custom_emoji: "%{name} a importé de nouveaux emoji %{target}"
create_domain_block: "%{name} a bloqué le domaine %{target}" create_domain_block: "%{name} a bloqué le domaine %{target}"
create_email_domain_block: "%{name} a blacklisté le domaine de l'e-mail %{target}" create_email_domain_block: "%{name} a mis le domaine du courriel %{target} sur liste noire"
demote_user: "%{name} a rétrogradé l'utilisateur %{target}"
destroy_domain_block: "%{name} a débloqué le domaine %{target}" destroy_domain_block: "%{name} a débloqué le domaine %{target}"
destroy_email_domain_block: "%{name} a mis le domaine de l'e-mail %{target} sur liste blanche" destroy_email_domain_block: "%{name} a mis le domaine du courriel %{target} sur liste blanche"
destroy_status: "%{name} a enlevé le statut de %{target}" destroy_status: "%{name} a enlevé le statut de %{target}"
disable_2fa_user: "%{name} a désactivé l'authentification à deux facteurs pour l'utilisateur %{target}" disable_2fa_user: "%{name} a désactivé l'authentification à deux facteurs pour l'utilisateur %{target}"
disable_custom_emoji: "%{name} a désactivé l'emoji %{target}" disable_custom_emoji: "%{name} a désactivé l'emoji %{target}"
@ -159,6 +161,7 @@ fr:
update_status: "%{name} a mis à jour le statut de %{target}" update_status: "%{name} a mis à jour le statut de %{target}"
title: Journal d'audit title: Journal d'audit
custom_emojis: custom_emojis:
by_domain: Domaine
copied_msg: Copie locale de lémoji créée avec succès! copied_msg: Copie locale de lémoji créée avec succès!
copy: Copier copy: Copier
copy_failed_msg: Impossible de faire une copie locale de cet émoji copy_failed_msg: Impossible de faire une copie locale de cet émoji
@ -191,7 +194,7 @@ fr:
create: Créer le blocage create: Créer le blocage
hint: Le blocage de domaine nempêchera pas la création de comptes dans la base de données, mais il appliquera automatiquement et rétrospectivement des méthodes de modération spécifiques sur ces comptes. hint: Le blocage de domaine nempêchera pas la création de comptes dans la base de données, mais il appliquera automatiquement et rétrospectivement des méthodes de modération spécifiques sur ces comptes.
severity: severity:
desc_html: "<strong>Silence</strong> rendra les messages des comptes concernés invisibles à ceux qui ne les suivent pas. <strong>Suspendre</strong> supprimera tout le contenu des comptes concernés, les médias, et les données du profil." desc_html: "<strong>Silence</strong> rendra les messages des comptes concernés invisibles à ceux qui ne les suivent pas. <strong>Suspendre</strong> supprimera tout le contenu des comptes concernés, les médias, et les données du profil. Utilisez <strong>Aucun</strong> si vous voulez simplement rejeter les fichiers multimédia."
noop: Aucune noop: Aucune
silence: Masqué silence: Masqué
suspend: Suspendre suspend: Suspendre
@ -285,7 +288,7 @@ fr:
desc_html: Montrer un badge de responsable sur une page utilisateur desc_html: Montrer un badge de responsable sur une page utilisateur
title: Montrer un badge de responsable title: Montrer un badge de responsable
site_description: site_description:
desc_html: Affichée sous la forme dun paragraphe sur la page daccueil et utilisée comme balise meta.<br/>Vous pouvez utiliser des balises HTML, en particulier <code>&lt;a&gt;</code> et <code>&lt;em&gt;</code>. desc_html: Paragraphe introductif sur la page d'accueil et dans les balises meta. Vous pouvez utiliser des balises HTML, en particulier <code>&lt;a&gt;</code> et <code>&lt;em&gt;</code>.
title: Description du site title: Description du site
site_description_extended: site_description_extended:
desc_html: Affichée sur la page dinformations complémentaires du site<br>Vous pouvez utiliser des balises HTML desc_html: Affichée sur la page dinformations complémentaires du site<br>Vous pouvez utiliser des balises HTML
@ -457,6 +460,9 @@ fr:
title: Inviter des gens title: Inviter des gens
landing_strip_html: <strong>%{name}</strong> utilise %{link_to_root_path}. Vous pouvez læ suivre et interagir si vous possédez un compte quelque part dans le "fediverse". landing_strip_html: <strong>%{name}</strong> utilise %{link_to_root_path}. Vous pouvez læ suivre et interagir si vous possédez un compte quelque part dans le "fediverse".
landing_strip_signup_html: Si ce nest pas le cas, vous pouvez <a href="%{sign_up_path}">en créer un ici</a>. landing_strip_signup_html: Si ce nest pas le cas, vous pouvez <a href="%{sign_up_path}">en créer un ici</a>.
lists:
errors:
limit: Vous avez atteint le nombre maximum de listes
media_attachments: media_attachments:
validations: validations:
images_and_video: Impossible de joindre une vidéo à un statut contenant déjà des images images_and_video: Impossible de joindre une vidéo à un statut contenant déjà des images
@ -590,11 +596,12 @@ fr:
open_in_web: Ouvrir sur le web open_in_web: Ouvrir sur le web
over_character_limit: limite de caractères dépassée de %{max} caractères over_character_limit: limite de caractères dépassée de %{max} caractères
pin_errors: pin_errors:
limit: Trop de pouets épinglés limit: Vous avez déjà épinglé le nombre maximum de pouets
ownership: Vous ne pouvez pas épingler un statut ne vous appartenant pas ownership: Vous ne pouvez pas épingler un statut ne vous appartenant pas
private: Les statuts non-publics ne peuvent pas être épinglés private: Les statuts non-publics ne peuvent pas être épinglés
reblog: Un partage ne peut pas être épinglé reblog: Un partage ne peut pas être épinglé
show_more: Afficher plus show_more: Afficher plus
title: '%{name}: "%{quote}"'
visibilities: visibilities:
private: Abonné⋅e⋅s uniquement private: Abonné⋅e⋅s uniquement
private_long: Seul⋅e⋅s vos abonné⋅e⋅s verront vos statuts private_long: Seul⋅e⋅s vos abonné⋅e⋅s verront vos statuts
@ -693,7 +700,7 @@ fr:
manual_instructions: 'Si vous ne pouvez pas scanner ce QR code et devez lentrer manuellement, voici le secret en clair :' manual_instructions: 'Si vous ne pouvez pas scanner ce QR code et devez lentrer manuellement, voici le secret en clair :'
recovery_codes: Codes de récupération recovery_codes: Codes de récupération
recovery_codes_regenerated: Codes de récupération régénérés avec succès recovery_codes_regenerated: Codes de récupération régénérés avec succès
recovery_instructions_html: Si vous perdez laccès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour récupérer laccès à votre compte. Conservez les codes de récupération en toute sécurité, par exemple, en les imprimant et en les stockant avec vos autres documents importants. recovery_instructions_html: Si vous perdez laccès à votre téléphone, vous pouvez utiliser un des codes de récupération ci-dessous pour retrouver laccès à votre compte. <strong>Conservez les codes de récupération en sécurité</strong>. Par exemple, en les imprimant et en les stockant avec vos autres documents importants.
setup: Installer setup: Installer
wrong_code: Les codes entrés sont incorrects! Lheure du serveur et celle de votre appareil sont-elles correctes? wrong_code: Les codes entrés sont incorrects! Lheure du serveur et celle de votre appareil sont-elles correctes?
users: users:

@ -151,7 +151,7 @@ ja:
memorialize_account: "%{name} さんが %{target} さんを追悼アカウントページに登録しました" memorialize_account: "%{name} さんが %{target} さんを追悼アカウントページに登録しました"
promote_user: "%{name} さんが %{target} さんを昇格しました" promote_user: "%{name} さんが %{target} さんを昇格しました"
reset_password_user: "%{name} さんが %{target} さんのパスワードをリセットしました" reset_password_user: "%{name} さんが %{target} さんのパスワードをリセットしました"
resolve_report: "%{name} さんがレポート %{target} を棄却しました" resolve_report: "%{name} さんがレポート %{target} を解決済みにしました"
silence_account: "%{name} さんが %{target} さんをサイレンスにしました" silence_account: "%{name} さんが %{target} さんをサイレンスにしました"
suspend_account: "%{name} さんが %{target} さんを停止しました" suspend_account: "%{name} さんが %{target} さんを停止しました"
unsilence_account: "%{name} さんが %{target} さんのサイレンスを解除しました" unsilence_account: "%{name} さんが %{target} さんのサイレンスを解除しました"
@ -192,13 +192,13 @@ ja:
create: ブロックを作成 create: ブロックを作成
hint: ドメインブロックはデータベース中のアカウント項目の作成を妨げませんが、遡って自動的に指定されたモデレーションをそれらのアカウントに適用します。 hint: ドメインブロックはデータベース中のアカウント項目の作成を妨げませんが、遡って自動的に指定されたモデレーションをそれらのアカウントに適用します。
severity: severity:
desc_html: "<strong>サイレンス</strong>はアカウントのトゥートをフォローしていない人から隠します。<strong>停止</strong>はそのアカウントのコンテンツ、メディア、プロフィールデータをすべて削除します。" desc_html: "<strong>サイレンス</strong>はアカウントのトゥートをフォローしていない人から隠します。<strong>停止</strong>はそのアカウントのコンテンツ、メディア、プロフィールデータをすべて削除します。メディアファイルの拒否は<strong>なし</strong>を使います。"
noop: なし noop: なし
silence: サイレンス silence: サイレンス
suspend: 停止 suspend: 停止
title: 新規ドメインブロック title: 新規ドメインブロック
reject_media: メディアファイルを拒否 reject_media: メディアファイルを拒否
reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です
severities: severities:
noop: なし noop: なし
silence: サイレンス silence: サイレンス
@ -271,7 +271,7 @@ ja:
username: 連絡先のユーザー名 username: 連絡先のユーザー名
registrations: registrations:
closed_message: closed_message:
desc_html: 新規登録を停止しているときにフロントページに表示されます。HTMLタグが使えます desc_html: 新規登録を停止しているときにフロントページに表示されます。HTMLタグが使えます
title: 新規登録停止時のメッセージ title: 新規登録停止時のメッセージ
deletion: deletion:
desc_html: 誰でも自分のアカウントを削除できるようにします desc_html: 誰でも自分のアカウントを削除できるようにします
@ -289,14 +289,14 @@ ja:
desc_html: フロントページへの表示と meta タグに使用される紹介文です。HTMLタグ、特に<code>&lt;a&gt;</code> と <code>&lt;em&gt;</code>が使えます。 desc_html: フロントページへの表示と meta タグに使用される紹介文です。HTMLタグ、特に<code>&lt;a&gt;</code> と <code>&lt;em&gt;</code>が使えます。
title: インスタンスの説明 title: インスタンスの説明
site_description_extended: site_description_extended:
desc_html: あなたのインスタンスにおける行動規範やルール、ガイドライン、そのほかの記述をする際に最適な場所です。HTMLタグが使えます desc_html: あなたのインスタンスにおける行動規範やルール、ガイドライン、そのほかの記述をする際に最適な場所です。HTMLタグが使えます
title: カスタム詳細説明 title: カスタム詳細説明
site_terms: site_terms:
desc_html: あなたは独自のプライバシーポリシーや利用規約、そのほかの法的根拠を書くことができます。HTMLタグが使えます desc_html: あなたは独自のプライバシーポリシーや利用規約、そのほかの法的根拠を書くことができます。HTMLタグが使えます
title: カスタム利用規約 title: カスタム利用規約
site_title: インスタンスの名前 site_title: インスタンスの名前
thumbnail: thumbnail:
desc_html: OpenGraphとAPIによるプレビューに使用されます。サイズは1200×630px推奨です desc_html: OpenGraphとAPIによるプレビューに使用されます。サイズは1200×630px推奨です
title: インスタンスのサムネイル title: インスタンスのサムネイル
timeline_preview: timeline_preview:
desc_html: ランディングページに公開タイムラインを表示します desc_html: ランディングページに公開タイムラインを表示します
@ -333,7 +333,7 @@ ja:
salutation: "%{name} さん" salutation: "%{name} さん"
settings: 'メール設定の変更: %{link}' settings: 'メール設定の変更: %{link}'
signature: Mastodon %{instance} インスタンスからの通知 signature: Mastodon %{instance} インスタンスからの通知
view: リンク view: 'リンク:'
applications: applications:
created: アプリが作成されました created: アプリが作成されました
destroyed: アプリが削除されました destroyed: アプリが削除されました
@ -359,12 +359,12 @@ ja:
reset_password: パスワードを再発行 reset_password: パスワードを再発行
set_new_password: 新しいパスワード set_new_password: 新しいパスワード
authorize_follow: authorize_follow:
error: 残念ながら、リモートアカウント情報の取得中にエラーが発生しました error: 残念ながら、リモートアカウント情報の取得中にエラーが発生しました
follow: フォロー follow: フォロー
follow_request: 'あなたは以下のアカウントにフォローリクエストを送信しました:' follow_request: 'あなたは以下のアカウントにフォローリクエストを送信しました:'
following: '成功! あなたは現在以下のアカウントをフォローしています:' following: '成功! あなたは現在以下のアカウントをフォローしています:'
post_follow: post_follow:
close: またはこのウィンドウを閉じます close: またはこのウィンドウを閉じます
return: ユーザーのプロフィールに戻る return: ユーザーのプロフィールに戻る
web: Web を開く web: Web を開く
title: "%{acct} をフォロー" title: "%{acct} をフォロー"
@ -384,7 +384,7 @@ ja:
x_seconds: "%{count}秒" x_seconds: "%{count}秒"
deletes: deletes:
bad_password_msg: パスワードが違います bad_password_msg: パスワードが違います
confirm_password: 本人確認のため、現在のパスワードを入力してください confirm_password: 本人確認のため、現在のパスワードを入力してください
description_html: あなたのアカウントに含まれるコンテンツは全て削除され、アカウントは無効化されます。これは恒久的なもので、<strong>取り消すことはできません</strong>。なりすましを防ぐために、同じユーザー名で再度登録することはできなくなります。 description_html: あなたのアカウントに含まれるコンテンツは全て削除され、アカウントは無効化されます。これは恒久的なもので、<strong>取り消すことはできません</strong>。なりすましを防ぐために、同じユーザー名で再度登録することはできなくなります。
proceed: アカウントを削除する proceed: アカウントを削除する
success_msg: アカウントは正常に削除されました success_msg: アカウントは正常に削除されました
@ -397,7 +397,7 @@ ja:
'422': '422':
content: セキュリティ認証に失敗しました。Cookieをブロックしていませんか content: セキュリティ認証に失敗しました。Cookieをブロックしていませんか
title: セキュリティ認証に失敗 title: セキュリティ認証に失敗
'429': リクエストの制限に達しました '429': リクエストの制限に達しました
'500': '500':
content: もうしわけありませんが、なにかが間違っています。 content: もうしわけありませんが、なにかが間違っています。
title: このページは正しくありません title: このページは正しくありません
@ -419,24 +419,24 @@ ja:
other: "%{count} 個のドメインからソフトブロックするフォロワーを処理中..." other: "%{count} 個のドメインからソフトブロックするフォロワーを処理中..."
true_privacy_html: "<strong>プライバシーの保護はエンドツーエンドの暗号化でのみ実現可能</strong>であることに留意ください。" true_privacy_html: "<strong>プライバシーの保護はエンドツーエンドの暗号化でのみ実現可能</strong>であることに留意ください。"
unlocked_warning_html: 誰でもあなたをフォローすることができ、あなたのプライベート投稿をすぐに見ることができます。フォローする人を限定したい場合は%{lock_link}に設定してください。 unlocked_warning_html: 誰でもあなたをフォローすることができ、あなたのプライベート投稿をすぐに見ることができます。フォローする人を限定したい場合は%{lock_link}に設定してください。
unlocked_warning_title: このアカウントは非公開アカウントに設定されていません unlocked_warning_title: このアカウントは非公開アカウントに設定されていません
generic: generic:
changes_saved_msg: 正常に変更されました changes_saved_msg: 正常に変更されました
powered_by: powered by %{link} powered_by: powered by %{link}
save_changes: 変更を保存 save_changes: 変更を保存
use_this: これを使う use_this: これを使う
validation_errors: validation_errors:
one: エラーが発生しました。以下のエラーを確認してください。 one: エラーが発生しました 以下のエラーを確認してください
other: エラーが発生しました以下の%{count}個のエラーを確認してください other: エラーが発生しました 以下の%{count}個のエラーを確認してください
imports: imports:
preface: 他のインスタンスでエクスポートされたファイルから、フォロー/ブロックした情報をこのインスタンス上のアカウントにインポートできます。 preface: 他のインスタンスでエクスポートされたファイルから、フォロー/ブロックした情報をこのインスタンス上のアカウントにインポートできます。
success: ファイルは正常にアップロードされ、現在処理中です。しばらくしてから確認してください success: ファイルは正常にアップロードされ、現在処理中です。しばらくしてから確認してください
types: types:
blocking: ブロックしたアカウントリスト blocking: ブロックしたアカウントリスト
following: フォロー中のアカウントリスト following: フォロー中のアカウントリスト
muting: ミュートしたアカウントリスト muting: ミュートしたアカウントリスト
upload: アップロード upload: アップロード
in_memoriam_html: 故人を偲んで in_memoriam_html: 故人を偲んで
invites: invites:
delete: 無効化 delete: 無効化
expired: 期限切れ expired: 期限切れ
@ -452,7 +452,7 @@ ja:
one: '1' one: '1'
other: "%{count}" other: "%{count}"
max_uses_prompt: 無制限 max_uses_prompt: 無制限
prompt: リンクを生成・共有してこのインスタンスへの新規登録を受け付けることができます prompt: リンクを生成・共有してこのインスタンスへの新規登録を受け付けることができます
table: table:
expires_at: 有効期限 expires_at: 有効期限
uses: 使用 uses: 使用
@ -467,15 +467,18 @@ ja:
remove_all: すべて削除 remove_all: すべて削除
landing_strip_html: "<strong>%{name}</strong> さんはインスタンス %{link_to_root_path} のユーザーです。アカウントさえ持っていればフォローしたり会話したりできます。" landing_strip_html: "<strong>%{name}</strong> さんはインスタンス %{link_to_root_path} のユーザーです。アカウントさえ持っていればフォローしたり会話したりできます。"
landing_strip_signup_html: もしお持ちでないなら <a href="%{sign_up_path}">こちら</a> からサインアップできます。 landing_strip_signup_html: もしお持ちでないなら <a href="%{sign_up_path}">こちら</a> からサインアップできます。
lists:
errors:
limit: リストの上限に達しました
media_attachments: media_attachments:
validations: validations:
images_and_video: 既に画像が追加されているため、動画を追加することはできません。 images_and_video: 既に画像が追加されているため、動画を追加することはできません
too_many: 追加できるファイルは4つまでです too_many: 追加できるファイルは4つまでです
migrations: migrations:
acct: 引っ越し先の ユーザー名@ドメイン acct: 引っ越し先の ユーザー名@ドメイン
currently_redirecting: 'あなたのプロフィールは引っ越し先が設定されています:' currently_redirecting: 'あなたのプロフィールは引っ越し先が設定されています:'
proceed: 保存 proceed: 保存
updated_msg: アカウントの引っ越し設定を更新しました updated_msg: アカウントの引っ越し設定を更新しました
moderation: moderation:
title: モデレーション title: モデレーション
notification_mailer: notification_mailer:
@ -492,7 +495,7 @@ ja:
body: "%{name} さんにお気に入り登録された、あなたのトゥートがあります:" body: "%{name} さんにお気に入り登録された、あなたのトゥートがあります:"
subject: "%{name} さんにお気に入りに登録されました" subject: "%{name} さんにお気に入りに登録されました"
follow: follow:
body: "%{name} さんにフォローされています" body: "%{name} さんにフォローされています"
subject: "%{name} さんにフォローされています" subject: "%{name} さんにフォローされています"
follow_request: follow_request:
body: "%{name} さんがあなたにフォローをリクエストしました" body: "%{name} さんがあなたにフォローをリクエストしました"
@ -601,7 +604,7 @@ ja:
open_in_web: Webで開く open_in_web: Webで開く
over_character_limit: 上限は %{max}文字までです over_character_limit: 上限は %{max}文字までです
pin_errors: pin_errors:
limit: 固定されているトゥートが多すぎます limit: 固定されているトゥートの上限に達しました
ownership: 他人のトゥートを固定することはできません ownership: 他人のトゥートを固定することはできません
private: 非公開のトゥートを固定することはできません private: 非公開のトゥートを固定することはできません
reblog: ブーストされたトゥートを固定することはできません reblog: ブーストされたトゥートを固定することはできません
@ -692,7 +695,7 @@ ja:
formats: formats:
default: "%Y年%m月%d日 %H:%M" default: "%Y年%m月%d日 %H:%M"
two_factor_authentication: two_factor_authentication:
code_hint: 確認するには認証アプリで表示されたコードを入力してください code_hint: 確認するには認証アプリで表示されたコードを入力してください
description_html: "<strong>二段階認証</strong>を有効にするとログイン時、電話でコードを受け取る必要があります。" description_html: "<strong>二段階認証</strong>を有効にするとログイン時、電話でコードを受け取る必要があります。"
disable: 無効 disable: 無効
enable: 有効 enable: 有効
@ -703,7 +706,7 @@ ja:
lost_recovery_codes: リカバリーコードを使用すると携帯電話を紛失した場合でもアカウントにアクセスできるようになります。 リカバリーコードを紛失した場合もここで再生成することができますが、古いリカバリーコードは無効になります。 lost_recovery_codes: リカバリーコードを使用すると携帯電話を紛失した場合でもアカウントにアクセスできるようになります。 リカバリーコードを紛失した場合もここで再生成することができますが、古いリカバリーコードは無効になります。
manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:' manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:'
recovery_codes: リカバリーコード recovery_codes: リカバリーコード
recovery_codes_regenerated: リカバリーコードが再生成されました recovery_codes_regenerated: リカバリーコードが再生成されました
recovery_instructions_html: 携帯電話を紛失した場合、以下の内どれかのリカバリーコードを使用してアカウントへアクセスすることができます。<strong>リカバリーコードは大切に保全してください。</strong>たとえば印刷してほかの重要な書類と一緒に保管することができます。 recovery_instructions_html: 携帯電話を紛失した場合、以下の内どれかのリカバリーコードを使用してアカウントへアクセスすることができます。<strong>リカバリーコードは大切に保全してください。</strong>たとえば印刷してほかの重要な書類と一緒に保管することができます。
setup: 初期設定 setup: 初期設定
wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。 wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。

@ -458,6 +458,9 @@ nl:
title: Mensen uitnodigen title: Mensen uitnodigen
landing_strip_html: "<strong>%{name}</strong> is een gebruiker op %{link_to_root_path}. Je kunt deze volgen en ermee communiceren als je op Mastodon (of ergens anders in de fediverse) een account hebt." landing_strip_html: "<strong>%{name}</strong> is een gebruiker op %{link_to_root_path}. Je kunt deze volgen en ermee communiceren als je op Mastodon (of ergens anders in de fediverse) een account hebt."
landing_strip_signup_html: Als je dat niet hebt, kun je je <a href="%{sign_up_path}">hier registreren</a>. landing_strip_signup_html: Als je dat niet hebt, kun je je <a href="%{sign_up_path}">hier registreren</a>.
lists:
errors:
limit: Je hebt het maximaal aantal lijsten bereikt
media_attachments: media_attachments:
validations: validations:
images_and_video: Een video kan niet aan een toot met afbeeldingen worden gekoppeld images_and_video: Een video kan niet aan een toot met afbeeldingen worden gekoppeld
@ -591,7 +594,7 @@ nl:
open_in_web: In de webapp openen open_in_web: In de webapp openen
over_character_limit: Limiet van %{max} tekens overschreden over_character_limit: Limiet van %{max} tekens overschreden
pin_errors: pin_errors:
limit: Te veel toots vastgezet limit: Je hebt het maximaal aantal toots al vastgezet
ownership: Een toot van iemand anders kan niet worden vastgezet ownership: Een toot van iemand anders kan niet worden vastgezet
private: Alleen openbare toots kunnen worden vastgezet private: Alleen openbare toots kunnen worden vastgezet
reblog: Een boost kan niet worden vastgezet reblog: Een boost kan niet worden vastgezet

@ -114,8 +114,9 @@ oc:
resubscribe: Se tornar abonar resubscribe: Se tornar abonar
role: Permissions role: Permissions
roles: roles:
admin: Admin admin: Administrator
moderator: Mod moderator: Moderator
staff: Personnal
user: Uitlizaire user: Uitlizaire
salmon_url: URL Salmon salmon_url: URL Salmon
search: Cercar search: Cercar
@ -160,6 +161,7 @@ oc:
update_status: "%{name} metèt a jorn lestatut a %{target}" update_status: "%{name} metèt a jorn lestatut a %{target}"
title: Audit log title: Audit log
custom_emojis: custom_emojis:
by_domain: Domeni
copied_msg: Còpia locala de lemoji ben creada copied_msg: Còpia locala de lemoji ben creada
copy: Copiar copy: Copiar
copy_failed_msg: Fracàs de la còpia locala de lemoji copy_failed_msg: Fracàs de la còpia locala de lemoji
@ -343,7 +345,7 @@ oc:
warning: Mèfi! Agachatz de partejar aquela donada amb degun! warning: Mèfi! Agachatz de partejar aquela donada amb degun!
your_token: Vòstre geton daccès your_token: Vòstre geton daccès
auth: auth:
agreement_html: En vos marcar acceptatz <a href="%{rules_path}">nòstres tèrmes de servici</a> e <a href="%{terms_path}">politica de confidencialitat</a>. agreement_html: En vos marcar acceptatz <a href="%{rules_path}">las règlas de linstància</a> e <a href="%{terms_path}">politica de confidencialitat</a>.
change_password: Seguretat change_password: Seguretat
delete_account: Suprimir lo compte delete_account: Suprimir lo compte
delete_account_html: Se volètz suprimir vòstre compte, podètz <a href="%{path}">o far aquí</a>. Vos demandarem que confirmetz. delete_account_html: Se volètz suprimir vòstre compte, podètz <a href="%{path}">o far aquí</a>. Vos demandarem que confirmetz.
@ -677,6 +679,7 @@ oc:
private: Se pòt pas penjar los tuts pas publics private: Se pòt pas penjar los tuts pas publics
reblog: Se pòt pas penjar un tut partejat reblog: Se pòt pas penjar un tut partejat
show_more: Ne veire mai show_more: Ne veire mai
title: '%{name}: "%{quote}"'
visibilities: visibilities:
private: Seguidors solament private: Seguidors solament
private_long: Mostrar pas quals seguidors private_long: Mostrar pas quals seguidors

@ -469,6 +469,9 @@ pl:
remove_all: Usuń wszystkie remove_all: Usuń wszystkie
landing_strip_html: "<strong>%{name}</strong> ma konto na %{link_to_root_path}. Możesz je śledzić i wejść z nim w interakcję jeśli masz konto gdziekolwiek w Fediwersum." landing_strip_html: "<strong>%{name}</strong> ma konto na %{link_to_root_path}. Możesz je śledzić i wejść z nim w interakcję jeśli masz konto gdziekolwiek w Fediwersum."
landing_strip_signup_html: Jeśli jeszcze go nie masz, możesz <a href="%{sign_up_path}">stworzyć konto</a>. landing_strip_signup_html: Jeśli jeszcze go nie masz, możesz <a href="%{sign_up_path}">stworzyć konto</a>.
lists:
errors:
limit: Przekroczyłeś maksymalną liczbę utworzonych list
media_attachments: media_attachments:
validations: validations:
images_and_video: Nie możesz załączyć pliku wideo do wpisu, który zawiera już zdjęcia images_and_video: Nie możesz załączyć pliku wideo do wpisu, który zawiera już zdjęcia
@ -606,7 +609,7 @@ pl:
open_in_web: Otwórz w przeglądarce open_in_web: Otwórz w przeglądarce
over_character_limit: limit %{max} znaków przekroczony over_character_limit: limit %{max} znaków przekroczony
pin_errors: pin_errors:
limit: Nie możesz przypiąć więcej wpisów limit: Przekroczyłeś maksymalną liczbę przypiętych wpisów
ownership: Nie możesz przypiąć cudzego wpisu ownership: Nie możesz przypiąć cudzego wpisu
private: Nie możesz przypiąć niepublicznego wpisu private: Nie możesz przypiąć niepublicznego wpisu
reblog: Nie możesz przypiąć podbicia wpisu reblog: Nie możesz przypiąć podbicia wpisu

@ -282,6 +282,9 @@ pt-BR:
open: open:
desc_html: Permitir que qualquer um crie uma conta desc_html: Permitir que qualquer um crie uma conta
title: Cadastro aberto title: Cadastro aberto
show_staff_badge:
desc_html: Mostrar uma insígnia de equipe na página de usuário
title: Mostrar insígnia de equipe
site_description: site_description:
desc_html: Parágrafo introdutório na página inicial e em meta tags. Você pode usar tags HTML, em especial <code>&lt;a&gt;</code> e <code>&lt;em&gt;</code>. desc_html: Parágrafo introdutório na página inicial e em meta tags. Você pode usar tags HTML, em especial <code>&lt;a&gt;</code> e <code>&lt;em&gt;</code>.
title: Descrição da instância title: Descrição da instância
@ -350,7 +353,7 @@ pt-BR:
login: Entrar login: Entrar
logout: Sair logout: Sair
migrate_account: Mudar para uma conta diferente migrate_account: Mudar para uma conta diferente
migrate_account_html: Se você quer redirecionar essa conta para uma outra você pode <a href="%{path}">configura isso aqui</a>. migrate_account_html: Se você quer redirecionar essa conta para uma outra você pode <a href="%{path}">configurar isso aqui</a>.
register: Cadastrar-se register: Cadastrar-se
resend_confirmation: Reenviar instruções de confirmação resend_confirmation: Reenviar instruções de confirmação
reset_password: Redefinir senha reset_password: Redefinir senha
@ -455,6 +458,9 @@ pt-BR:
title: Convidar pessoas title: Convidar pessoas
landing_strip_html: "<strong>%{name}</strong> é um usuário no %{link_to_root_path}. Você pode segui-lo ou interagir com ele se você tiver uma conta em qualquer lugar no fediverso." landing_strip_html: "<strong>%{name}</strong> é um usuário no %{link_to_root_path}. Você pode segui-lo ou interagir com ele se você tiver uma conta em qualquer lugar no fediverso."
landing_strip_signup_html: Se não, você pode <a href="%{sign_up_path}">se cadastrar aqui</a>. landing_strip_signup_html: Se não, você pode <a href="%{sign_up_path}">se cadastrar aqui</a>.
lists:
errors:
limit: Você alcançou o número máximo de listas
media_attachments: media_attachments:
validations: validations:
images_and_video: Não é possível anexar um vídeo a uma postagem que já contém imagens images_and_video: Não é possível anexar um vídeo a uma postagem que já contém imagens

@ -0,0 +1,75 @@
gl:
simple_form:
hints:
defaults:
avatar: PNG, GIF ou JPG. Como moito 2MB. Será reducida ate 120x120px
digest: Enviar despois de un período longo de inactividade con un resumo das
mencións que recibeu na súa ausencia
display_name:
one: <span class="name-counter">1</span> caracter restante
other: <span class="name-counter">%{count}</span> caracteres restantes
header: PNG, GIF ou JPG. Como moito 2MB. Será reducida a 700x335px
locked: Require que vostede aprove as seguidoras de xeito manual
note:
one: <span class="note-counter">1</span> caracter restante
other: <span class="note-counter">%{count}</span> caracteres restantes
setting_noindex: Afecta ao seu perfil público e páxinas de estado
setting_theme: Afecta ao aspecto de Mastodon en calquer dispositivo cando
está conectada.
imports:
data: Ficheiro CSV exportado desde outra instancia Mastodon
sessions:
otp: Introduza o código de Doble-Factor desde o seu teléfono ou utilice un
dos seus códigos de recuperación.
user:
filtered_languages: Os idiomas marcados filtraranse das liñas temporais públicas
para vostede
labels:
defaults:
avatar: Avatar
confirm_new_password: Confirme o novo contrasinal
confirm_password: Confirme o contrasinal
current_password: Contrasinal actual
data: Data
display_name: Nome mostrado
email: enderezo correo electrónico
expires_in: Caducidade despois de
filtered_languages: Idiomas filtrados
header: Cabezallo
locale: Idioma
locked: Protexer conta
max_uses: Número máximo de usos
new_password: Novo contrasinal
note: Sobre vostede
otp_attempt: Código de Doble-Factor
password: Contrasinal
setting_auto_play_gif: Reprodución automática de GIFs animados
setting_boost_modal: Pedir confirmación antes de promocionar
setting_default_privacy: Intimidade da publicación
setting_default_sensitive: Marcar sempre multimedia como sensible
setting_delete_modal: Solicitar confirmación antes de eliminar unha mensaxe
setting_noindex: Pedir non aparecer nas buscas dos motores de busca
setting_reduce_motion: Reducir o movemento nas animacións
setting_system_font_ui: Utilizar a tipografía por defecto do sistema
setting_theme: Decorado da instancia
setting_unfollow_modal: Solicitar confirmación antes de deixar de seguir alguén
severity: Severidade
type: Tipo de importación
username: Nome de usuaria
interactions:
must_be_follower: Bloquear as notificacións de non-seguidoras
must_be_following: Bloquea as notificacións de personas que non segue
must_be_following_dm: Bloquea as mensaxes directas de personas que non segue
notification_emails:
digest: Enviar correos con resumos
favourite: Enviar un correo cando alguén marca como favorita unha das súas
publicacións
follow: Enviar un correo cando alguén a segue
follow_request: Enviar un correo cando alguén solicita seguila
mention: Enviar un correo cando alguén a menciona
reblog: Enviar un correo cando alguén promociona a súa mensaxe
'no': Non
required:
mark: '*'
text: requerido
'yes': Si

@ -116,6 +116,7 @@ zh-CN:
roles: roles:
admin: 管理员 admin: 管理员
moderator: 协管 moderator: 协管
staff: 工作人员
user: 普通用户 user: 普通用户
salmon_url: Salmon URL salmon_url: Salmon URL
search: 搜索 search: 搜索
@ -160,6 +161,7 @@ zh-CN:
update_status: "%{name} 刷新了 %{target} 的嘟文" update_status: "%{name} 刷新了 %{target} 的嘟文"
title: 运营日志 title: 运营日志
custom_emojis: custom_emojis:
by_domain: 域名
copied_msg: 成功将表情复制到本地 copied_msg: 成功将表情复制到本地
copy: 复制 copy: 复制
copy_failed_msg: 无法将表情复制到本地 copy_failed_msg: 无法将表情复制到本地
@ -281,8 +283,8 @@ zh-CN:
desc_html: 允许任何人建立一个帐户 desc_html: 允许任何人建立一个帐户
title: 开放注册 title: 开放注册
show_staff_badge: show_staff_badge:
desc_html: 在个人资料页上显示管理员标志 desc_html: 在个人资料页上显示工作人员标志
title: 显示管理员标志 title: 显示工作人员标志
site_description: site_description:
desc_html: 展示在首页以及 meta 标签中的网站简介。可以使用 HTML 标签,包括 <code>&lt;a&gt;</code> 和 <code>&lt;em&gt;</code>。 desc_html: 展示在首页以及 meta 标签中的网站简介。可以使用 HTML 标签,包括 <code>&lt;a&gt;</code> 和 <code>&lt;em&gt;</code>。
title: 本站简介 title: 本站简介
@ -341,7 +343,7 @@ zh-CN:
warning: 一定小心,千万不要把它分享给任何人! warning: 一定小心,千万不要把它分享给任何人!
your_token: 你的访问令牌 your_token: 你的访问令牌
auth: auth:
agreement_html: 注册即表示你同意<a href="%{rules_path}">我们的使用条款</a>和<a href="%{terms_path}">隐私权政策</a>。 agreement_html: 注册即表示你同意遵守<a href="%{rules_path}">本实例的相关规定</a>和<a href="%{terms_path}">我们的使用条款</a>。
change_password: 帐户安全 change_password: 帐户安全
delete_account: 删除帐户 delete_account: 删除帐户
delete_account_html: 如果你想删除你的帐户,请<a href="%{path}">点击这里继续</a>。你需要确认你的操作。 delete_account_html: 如果你想删除你的帐户,请<a href="%{path}">点击这里继续</a>。你需要确认你的操作。
@ -368,18 +370,18 @@ zh-CN:
title: 关注 %{acct} title: 关注 %{acct}
datetime: datetime:
distance_in_words: distance_in_words:
about_x_hours: "%{count} 时" about_x_hours: "%{count}时"
about_x_months: "%{count} 个月" about_x_months: "%{count}个月"
about_x_years: "%{count} 年" about_x_years: "%{count}年"
almost_x_years: "%{count} 年" almost_x_years: "%{count}年"
half_a_minute: 刚刚 half_a_minute: 刚刚
less_than_x_minutes: "%{count} 分" less_than_x_minutes: "%{count}分"
less_than_x_seconds: 刚刚 less_than_x_seconds: 刚刚
over_x_years: "%{count} 年" over_x_years: "%{count}年"
x_days: "%{count} 天" x_days: "%{count}天"
x_minutes: "%{count} 分" x_minutes: "%{count}分"
x_months: "%{count} 个月" x_months: "%{count}个月"
x_seconds: "%{count} 秒" x_seconds: "%{count}秒"
deletes: deletes:
bad_password_msg: 想得美,黑客!密码输入错误 bad_password_msg: 想得美,黑客!密码输入错误
confirm_password: 输入你当前的密码来验证身份 confirm_password: 输入你当前的密码来验证身份
@ -591,6 +593,7 @@ zh-CN:
private: 不能置顶非公开的嘟文 private: 不能置顶非公开的嘟文
reblog: 不能置顶转嘟 reblog: 不能置顶转嘟
show_more: 显示更多 show_more: 显示更多
title: "%{name}:“%{quote}”"
visibilities: visibilities:
private: 仅关注者 private: 仅关注者
private_long: 只有关注你的用户能看到 private_long: 只有关注你的用户能看到

@ -0,0 +1,6 @@
class RemoveDuplicateIndexesInLists < ActiveRecord::Migration[5.1]
def change
remove_index :list_accounts, name: "index_list_accounts_on_account_id"
remove_index :list_accounts, name: "index_list_accounts_on_list_id"
end
end

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20171201000000) do ActiveRecord::Schema.define(version: 20171212195226) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -211,10 +211,8 @@ ActiveRecord::Schema.define(version: 20171201000000) do
t.bigint "account_id", null: false t.bigint "account_id", null: false
t.bigint "follow_id", null: false t.bigint "follow_id", null: false
t.index ["account_id", "list_id"], name: "index_list_accounts_on_account_id_and_list_id", unique: true t.index ["account_id", "list_id"], name: "index_list_accounts_on_account_id_and_list_id", unique: true
t.index ["account_id"], name: "index_list_accounts_on_account_id"
t.index ["follow_id"], name: "index_list_accounts_on_follow_id" t.index ["follow_id"], name: "index_list_accounts_on_follow_id"
t.index ["list_id", "account_id"], name: "index_list_accounts_on_list_id_and_account_id" t.index ["list_id", "account_id"], name: "index_list_accounts_on_list_id_and_account_id"
t.index ["list_id"], name: "index_list_accounts_on_list_id"
end end
create_table "lists", force: :cascade do |t| create_table "lists", force: :cascade do |t|

@ -17,7 +17,7 @@ module Mastodon
end end
def pre def pre
'rc3' 'rc4'
end end
def flags def flags

@ -97,6 +97,8 @@ const startWorker = (workerId) => {
}; };
const app = express(); const app = express();
app.set('trusted proxy', process.env.TRUSTED_PROXY_IP || 'loopback,uniquelocal');
const pgPool = new pg.Pool(Object.assign(pgConfigs[env], dbUrlToConfig(process.env.DATABASE_URL))); const pgPool = new pg.Pool(Object.assign(pgConfigs[env], dbUrlToConfig(process.env.DATABASE_URL)));
const server = http.createServer(app); const server = http.createServer(app);
const redisNamespace = process.env.REDIS_NAMESPACE || null; const redisNamespace = process.env.REDIS_NAMESPACE || null;
@ -177,6 +179,12 @@ const startWorker = (workerId) => {
next(); next();
}; };
const setRemoteAddress = (req, res, next) => {
req.remoteAddress = req.connection.remoteAddress;
next();
};
const accountFromToken = (token, req, next) => { const accountFromToken = (token, req, next) => {
pgPool.connect((err, client, done) => { pgPool.connect((err, client, done) => {
if (err) { if (err) {
@ -208,17 +216,22 @@ const startWorker = (workerId) => {
}); });
}; };
const accountFromRequest = (req, next) => { const accountFromRequest = (req, next, required = true) => {
const authorization = req.headers.authorization; const authorization = req.headers.authorization;
const location = url.parse(req.url, true); const location = url.parse(req.url, true);
const accessToken = location.query.access_token; const accessToken = location.query.access_token;
if (!authorization && !accessToken) { if (!authorization && !accessToken) {
const err = new Error('Missing access token'); if (required) {
err.statusCode = 401; const err = new Error('Missing access token');
err.statusCode = 401;
next(err); next(err);
return; return;
} else {
next();
return;
}
} }
const token = authorization ? authorization.replace(/^Bearer /, '') : accessToken; const token = authorization ? authorization.replace(/^Bearer /, '') : accessToken;
@ -226,7 +239,17 @@ const startWorker = (workerId) => {
accountFromToken(token, req, next); accountFromToken(token, req, next);
}; };
const PUBLIC_STREAMS = [
'public',
'public:local',
'hashtag',
'hashtag:local',
];
const wsVerifyClient = (info, cb) => { const wsVerifyClient = (info, cb) => {
const location = url.parse(info.req.url, true);
const authRequired = !PUBLIC_STREAMS.some(stream => stream === location.query.stream);
accountFromRequest(info.req, err => { accountFromRequest(info.req, err => {
if (!err) { if (!err) {
cb(true, undefined, undefined); cb(true, undefined, undefined);
@ -234,16 +257,24 @@ const startWorker = (workerId) => {
log.error(info.req.requestId, err.toString()); log.error(info.req.requestId, err.toString());
cb(false, 401, 'Unauthorized'); cb(false, 401, 'Unauthorized');
} }
}); }, authRequired);
}; };
const PUBLIC_ENDPOINTS = [
'/api/v1/streaming/public',
'/api/v1/streaming/public/local',
'/api/v1/streaming/hashtag',
'/api/v1/streaming/hashtag/local',
];
const authenticationMiddleware = (req, res, next) => { const authenticationMiddleware = (req, res, next) => {
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
next(); next();
return; return;
} }
accountFromRequest(req, next); const authRequired = !PUBLIC_ENDPOINTS.some(endpoint => endpoint === req.path);
accountFromRequest(req, next, authRequired);
}; };
const errorMiddleware = (err, req, res, {}) => { const errorMiddleware = (err, req, res, {}) => {
@ -275,8 +306,10 @@ const startWorker = (workerId) => {
}; };
const streamFrom = (id, req, output, attachCloseHandler, needsFiltering = false, notificationOnly = false) => { const streamFrom = (id, req, output, attachCloseHandler, needsFiltering = false, notificationOnly = false) => {
const accountId = req.accountId || req.remoteAddress;
const streamType = notificationOnly ? ' (notification)' : ''; const streamType = notificationOnly ? ' (notification)' : '';
log.verbose(req.requestId, `Starting stream from ${id} for ${req.accountId}${streamType}`); log.verbose(req.requestId, `Starting stream from ${id} for ${accountId}${streamType}`);
const listener = message => { const listener = message => {
const { event, payload, queued_at } = JSON.parse(message); const { event, payload, queued_at } = JSON.parse(message);
@ -286,7 +319,7 @@ const startWorker = (workerId) => {
const delta = now - queued_at; const delta = now - queued_at;
const encodedPayload = typeof payload === 'object' ? JSON.stringify(payload) : payload; const encodedPayload = typeof payload === 'object' ? JSON.stringify(payload) : payload;
log.silly(req.requestId, `Transmitting for ${req.accountId}: ${event} ${encodedPayload} Delay: ${delta}ms`); log.silly(req.requestId, `Transmitting for ${accountId}: ${event} ${encodedPayload} Delay: ${delta}ms`);
output(event, encodedPayload); output(event, encodedPayload);
}; };
@ -313,26 +346,31 @@ const startWorker = (workerId) => {
return; return;
} }
const queries = [ if (req.accountId) {
client.query(`SELECT 1 FROM blocks WHERE (account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})) OR (account_id = $2 AND target_account_id = $1) UNION SELECT 1 FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})`, [req.accountId, unpackedPayload.account.id].concat(targetAccountIds)), const queries = [
]; client.query(`SELECT 1 FROM blocks WHERE (account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})) OR (account_id = $2 AND target_account_id = $1) UNION SELECT 1 FROM mutes WHERE account_id = $1 AND target_account_id IN (${placeholders(targetAccountIds, 2)})`, [req.accountId, unpackedPayload.account.id].concat(targetAccountIds)),
];
if (accountDomain) { if (accountDomain) {
queries.push(client.query('SELECT 1 FROM account_domain_blocks WHERE account_id = $1 AND domain = $2', [req.accountId, accountDomain])); queries.push(client.query('SELECT 1 FROM account_domain_blocks WHERE account_id = $1 AND domain = $2', [req.accountId, accountDomain]));
} }
Promise.all(queries).then(values => { Promise.all(queries).then(values => {
done(); done();
if (values[0].rows.length > 0 || (values.length > 1 && values[1].rows.length > 0)) { if (values[0].rows.length > 0 || (values.length > 1 && values[1].rows.length > 0)) {
return; return;
} }
transmit(); transmit();
}).catch(err => { }).catch(err => {
done();
log.error(err);
});
} else {
done(); done();
log.error(err); transmit();
}); }
}); });
} else { } else {
transmit(); transmit();
@ -345,13 +383,15 @@ const startWorker = (workerId) => {
// Setup stream output to HTTP // Setup stream output to HTTP
const streamToHttp = (req, res) => { const streamToHttp = (req, res) => {
const accountId = req.accountId || req.remoteAddress;
res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Transfer-Encoding', 'chunked'); res.setHeader('Transfer-Encoding', 'chunked');
const heartbeat = setInterval(() => res.write(':thump\n'), 15000); const heartbeat = setInterval(() => res.write(':thump\n'), 15000);
req.on('close', () => { req.on('close', () => {
log.verbose(req.requestId, `Ending stream for ${req.accountId}`); log.verbose(req.requestId, `Ending stream for ${accountId}`);
clearInterval(heartbeat); clearInterval(heartbeat);
}); });
@ -383,8 +423,10 @@ const startWorker = (workerId) => {
// Setup stream end for WebSockets // Setup stream end for WebSockets
const streamWsEnd = (req, ws, closeHandler = false) => (id, listener) => { const streamWsEnd = (req, ws, closeHandler = false) => (id, listener) => {
const accountId = req.accountId || req.remoteAddress;
ws.on('close', () => { ws.on('close', () => {
log.verbose(req.requestId, `Ending stream for ${req.accountId}`); log.verbose(req.requestId, `Ending stream for ${accountId}`);
unsubscribe(id, listener); unsubscribe(id, listener);
if (closeHandler) { if (closeHandler) {
closeHandler(); closeHandler();
@ -392,7 +434,7 @@ const startWorker = (workerId) => {
}); });
ws.on('error', () => { ws.on('error', () => {
log.verbose(req.requestId, `Ending stream for ${req.accountId}`); log.verbose(req.requestId, `Ending stream for ${accountId}`);
unsubscribe(id, listener); unsubscribe(id, listener);
if (closeHandler) { if (closeHandler) {
closeHandler(); closeHandler();
@ -401,6 +443,7 @@ const startWorker = (workerId) => {
}; };
app.use(setRequestId); app.use(setRequestId);
app.use(setRemoteAddress);
app.use(allowCrossDomain); app.use(allowCrossDomain);
app.use(authenticationMiddleware); app.use(authenticationMiddleware);
app.use(errorMiddleware); app.use(errorMiddleware);
@ -455,6 +498,7 @@ const startWorker = (workerId) => {
const req = ws.upgradeReq; const req = ws.upgradeReq;
const location = url.parse(req.url, true); const location = url.parse(req.url, true);
req.requestId = uuid.v4(); req.requestId = uuid.v4();
req.remoteAddress = ws._socket.remoteAddress;
ws.isAlive = true; ws.isAlive = true;
@ -527,12 +571,14 @@ const startWorker = (workerId) => {
const onError = (err) => { const onError = (err) => {
log.error(err); log.error(err);
server.close();
process.exit(0);
}; };
process.on('SIGINT', onExit); process.on('SIGINT', onExit);
process.on('SIGTERM', onExit); process.on('SIGTERM', onExit);
process.on('exit', onExit); process.on('exit', onExit);
process.on('error', onError); process.on('uncaughtException', onError);
}; };
throng({ throng({

Loading…
Cancel
Save