Add buttons to block and unblock domain (#3127)

* Add buttons to block and unblock domain

* Relationship API now returns "domain_blocking" status for accounts,
rename "block entire domain" to "hide entire domain", fix unblocking domain,
do not block notifications from domain-blocked-but-followed people, do
not send Salmons to domain blocked users

* Add test

* Personal domain blocks shouldn't affect Salmon after all, since in this
direction of communication the control is very thin when it comes to
public stuff. Best stay consistent and not affect federation in this way

* Ignore followers and follow request from domain blocked folks,
ensure account domain blocks are not created for empty domain,
and avoid duplicates in validation

* Purge followers when blocking domain (without soft-blocks, since they
are useless here)

* Add tests, fix local timeline being empty when having any domain blocks
This commit is contained in:
Eugen Rochko 2017-05-19 21:05:32 +02:00 committed by GitHub
parent 1548695c83
commit f1ab70649b
46 changed files with 436 additions and 43 deletions

View File

@ -71,11 +71,12 @@ class Api::V1::AccountsController < ApiController
def block
BlockService.new.call(current_user.account, @account)
@following = { @account.id => false }
@followed_by = { @account.id => false }
@blocking = { @account.id => true }
@requested = { @account.id => false }
@muting = { @account.id => current_user.account.muting?(@account.id) }
@following = { @account.id => false }
@followed_by = { @account.id => false }
@blocking = { @account.id => true }
@requested = { @account.id => false }
@muting = { @account.id => current_account.muting?(@account.id) }
@domain_blocking = { @account.id => current_account.domain_blocking?(@account.domain) }
render :relationship
end
@ -107,12 +108,13 @@ class Api::V1::AccountsController < ApiController
def relationships
ids = params[:id].is_a?(Enumerable) ? params[:id].map(&:to_i) : [params[:id].to_i]
@accounts = Account.where(id: ids).select('id')
@following = Account.following_map(ids, current_user.account_id)
@followed_by = Account.followed_by_map(ids, current_user.account_id)
@blocking = Account.blocking_map(ids, current_user.account_id)
@muting = Account.muting_map(ids, current_user.account_id)
@requested = Account.requested_map(ids, current_user.account_id)
@accounts = Account.where(id: ids).select('id')
@following = Account.following_map(ids, current_user.account_id)
@followed_by = Account.followed_by_map(ids, current_user.account_id)
@blocking = Account.blocking_map(ids, current_user.account_id)
@muting = Account.muting_map(ids, current_user.account_id)
@requested = Account.requested_map(ids, current_user.account_id)
@domain_blocking = Account.domain_blocking_map(ids, current_user.account_id)
end
def search
@ -128,11 +130,12 @@ class Api::V1::AccountsController < ApiController
end
def set_relationship
@following = Account.following_map([@account.id], current_user.account_id)
@followed_by = Account.followed_by_map([@account.id], current_user.account_id)
@blocking = Account.blocking_map([@account.id], current_user.account_id)
@muting = Account.muting_map([@account.id], current_user.account_id)
@requested = Account.requested_map([@account.id], current_user.account_id)
@following = Account.following_map([@account.id], current_user.account_id)
@followed_by = Account.followed_by_map([@account.id], current_user.account_id)
@blocking = Account.blocking_map([@account.id], current_user.account_id)
@muting = Account.muting_map([@account.id], current_user.account_id)
@requested = Account.requested_map([@account.id], current_user.account_id)
@domain_blocking = Account.domain_blocking_map([@account.id], current_user.account_id)
end
def pagination_params(core_params)

View File

@ -17,7 +17,7 @@ class Api::V1::DomainBlocksController < ApiController
end
def create
current_account.block_domain!(domain_block_params[:domain])
BlockDomainFromAccountService.new.call(current_account, domain_block_params[:domain])
render_empty
end

View File

@ -0,0 +1,117 @@
import api, { getLinks } from '../api'
export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL';
export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST';
export const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS';
export const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL';
export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST';
export const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS';
export const DOMAIN_BLOCKS_FETCH_FAIL = 'DOMAIN_BLOCKS_FETCH_FAIL';
export function blockDomain(domain, accountId) {
return (dispatch, getState) => {
dispatch(blockDomainRequest(domain));
api(getState).post('/api/v1/domain_blocks', { domain }).then(response => {
dispatch(blockDomainSuccess(domain, accountId));
}).catch(err => {
dispatch(blockDomainFail(domain, err));
});
};
};
export function blockDomainRequest(domain) {
return {
type: DOMAIN_BLOCK_REQUEST,
domain
};
};
export function blockDomainSuccess(domain, accountId) {
return {
type: DOMAIN_BLOCK_SUCCESS,
domain,
accountId
};
};
export function blockDomainFail(domain, error) {
return {
type: DOMAIN_BLOCK_FAIL,
domain,
error
};
};
export function unblockDomain(domain, accountId) {
return (dispatch, getState) => {
dispatch(unblockDomainRequest(domain));
api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(response => {
dispatch(unblockDomainSuccess(domain, accountId));
}).catch(err => {
dispatch(unblockDomainFail(domain, err));
});
};
};
export function unblockDomainRequest(domain) {
return {
type: DOMAIN_UNBLOCK_REQUEST,
domain
};
};
export function unblockDomainSuccess(domain, accountId) {
return {
type: DOMAIN_UNBLOCK_SUCCESS,
domain,
accountId
};
};
export function unblockDomainFail(domain, error) {
return {
type: DOMAIN_UNBLOCK_FAIL,
domain,
error
};
};
export function fetchDomainBlocks() {
return (dispatch, getState) => {
dispatch(fetchDomainBlocksRequest());
api(getState).get().then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null));
}).catch(err => {
dispatch(fetchDomainBlocksFail(err));
});
};
};
export function fetchDomainBlocksRequest() {
return {
type: DOMAIN_BLOCKS_FETCH_REQUEST
};
};
export function fetchDomainBlocksSuccess(domains, next) {
return {
type: DOMAIN_BLOCKS_FETCH_SUCCESS,
domains,
next
};
};
export function fetchDomainBlocksFail(error) {
return {
type: DOMAIN_BLOCKS_FETCH_FAIL,
error
};
};

View File

@ -15,7 +15,9 @@ const messages = defineMessages({
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
follow: { id: 'account.follow', defaultMessage: 'Follow' },
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' }
disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' },
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
});
class ActionBar extends React.PureComponent {
@ -28,6 +30,8 @@ class ActionBar extends React.PureComponent {
onMention: PropTypes.func.isRequired,
onReport: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired,
onBlockDomain: PropTypes.func.isRequired,
onUnblockDomain: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired
};
@ -59,7 +63,16 @@ class ActionBar extends React.PureComponent {
}
if (account.get('acct') !== account.get('username')) {
const domain = account.get('acct').split('@')[1];
extraInfo = <abbr title={intl.formatMessage(messages.disclaimer)}>*</abbr>;
menu.push(null);
if (account.getIn(['relationship', 'domain_blocking'])) {
menu.push({ text: intl.formatMessage(messages.unblockDomain, { domain }), action: this.props.onUnblockDomain });
} else {
menu.push({ text: intl.formatMessage(messages.blockDomain, { domain }), action: this.props.onBlockDomain });
}
}
return (

View File

@ -15,7 +15,9 @@ class Header extends ImmutablePureComponent {
onBlock: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onReport: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired
onMute: PropTypes.func.isRequired,
onBlockDomain: PropTypes.func.isRequired,
onUnblockDomain: PropTypes.func.isRequired,
};
static contextTypes = {
@ -43,6 +45,22 @@ class Header extends ImmutablePureComponent {
this.props.onMute(this.props.account);
}
handleBlockDomain = () => {
const domain = this.props.account.get('acct').split('@')[1];
if (!domain) return;
this.props.onBlockDomain(domain, this.props.account.get('id'));
}
handleUnblockDomain = () => {
const domain = this.props.account.get('acct').split('@')[1];
if (!domain) return;
this.props.onUnblockDomain(domain, this.props.account.get('id'));
}
render () {
const { account, me } = this.props;
@ -65,6 +83,8 @@ class Header extends ImmutablePureComponent {
onMention={this.handleMention}
onReport={this.handleReport}
onMute={this.handleMute}
onBlockDomain={this.handleBlockDomain}
onUnblockDomain={this.handleUnblockDomain}
/>
</div>
);

View File

@ -13,11 +13,13 @@ import {
import { mentionCompose } from '../../../actions/compose';
import { initReport } from '../../../actions/reports';
import { openModal } from '../../../actions/modal';
import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
const messages = defineMessages({
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' }
muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
});
const makeMapStateToProps = () => {
@ -70,6 +72,18 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
onConfirm: () => dispatch(muteAccount(account.get('id')))
}));
}
},
onBlockDomain (domain, accountId) {
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='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.' values={{ domain: <strong>{domain}</strong> }} />,
confirm: intl.formatMessage(messages.blockDomainConfirm),
onConfirm: () => dispatch(blockDomain(domain, accountId))
}));
},
onUnblockDomain (domain, accountId) {
dispatch(unblockDomain(domain, accountId));
}
});

View File

@ -1,17 +1,20 @@
{
"account.block": "حظر @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "هذا المستخدم من مثيل خادم آخر. قد يكون هذا الرقم أكبر.",
"account.edit_profile": "تعديل الملف الشخصي",
"account.follow": "تابِع",
"account.followers": "المتابعون",
"account.follows": "يتبع",
"account.follows_you": "يتابعك",
"account.media": "Media",
"account.mention": "أُذكُر @{name}",
"account.mute": "أكتم @{name}",
"account.posts": "المشاركات",
"account.report": "أبلغ عن @{name}",
"account.requested": "في انتظار الموافقة",
"account.unblock": "إلغاء الحظر عن @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "إلغاء المتابعة",
"account.unmute": "إلغاء الكتم عن @{name}",
"boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة",
@ -40,6 +43,8 @@
"confirmations.block.message": "هل أنت متأكد أنك تريد حجب {name} ؟",
"confirmations.delete.confirm": "حذف",
"confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "أكتم",
"confirmations.mute.message": "هل أنت متأكد أنك تريد كتم {name} ؟",
"emoji_button.activity": "الأنشطة",

View File

@ -1,17 +1,20 @@
{
"account.block": "Блокирай",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "This user is from another instance. This number may be larger.",
"account.edit_profile": "Редактирай профила си",
"account.follow": "Последвай",
"account.followers": "Последователи",
"account.follows": "Следвам",
"account.follows_you": "Твой последовател",
"account.media": "Media",
"account.mention": "Споменаване",
"account.mute": "Mute @{name}",
"account.posts": "Публикации",
"account.report": "Report @{name}",
"account.requested": "В очакване на одобрение",
"account.unblock": "Не блокирай",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Не следвай",
"account.unmute": "Unmute @{name}",
"boost_modal.combo": "You can press {combo} to skip this next time",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "Bloquejar @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Aquest usuari és d'un altra instància. Aquest número podria ser més gran.",
"account.edit_profile": "Editar perfil",
"account.follow": "Seguir",
"account.followers": "Seguidors",
"account.follows": "Seguint",
"account.follows_you": "et segueix",
"account.media": "Media",
"account.mention": "Esmentar @{name}",
"account.mute": "Silenciar @{name}",
"account.posts": "Publicacions",
"account.report": "Informe @{name}",
"account.requested": "Esperant aprovació",
"account.unblock": "Desbloquejar @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Deixar de seguir",
"account.unmute": "Treure silenci de @{name}",
"boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
@ -40,6 +43,8 @@
"confirmations.block.message": "Estàs segur que vols bloquejar {name}?",
"confirmations.delete.confirm": "Esborrar",
"confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Silenciar",
"confirmations.mute.message": "Estàs segur que vols silenciar {name}?",
"emoji_button.activity": "Activitat",

View File

@ -1,17 +1,20 @@
{
"account.block": "@{name} blocken",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Dieser Benutzer ist von einer anderen Instanz. Diese Zahl könnte größer sein.",
"account.edit_profile": "Profil bearbeiten",
"account.follow": "Folgen",
"account.followers": "Folgende",
"account.follows": "Folgt",
"account.follows_you": "Folgt dir",
"account.media": "Media",
"account.mention": "@{name} erwähnen",
"account.mute": "@{name} stummschalten",
"account.posts": "Beiträge",
"account.report": "@{name} melden",
"account.requested": "Warte auf Erlaubnis",
"account.unblock": "@{name} entblocken",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Entfolgen",
"account.unmute": "@{name} nicht mehr stummschalten",
"boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -240,6 +240,15 @@
],
"path": "app/javascript/mastodon/containers/status_container.json"
},
{
"descriptors": [
{
"defaultMessage": "Media",
"id": "account.media"
}
],
"path": "app/javascript/mastodon/features/account_gallery/index.json"
},
{
"descriptors": [
{
@ -250,6 +259,10 @@
"defaultMessage": "Mute",
"id": "confirmations.mute.confirm"
},
{
"defaultMessage": "Hide entire domain",
"id": "confirmations.domain_block.confirm"
},
{
"defaultMessage": "Are you sure you want to block {name}?",
"id": "confirmations.block.message"
@ -257,6 +270,10 @@
{
"defaultMessage": "Are you sure you want to mute {name}?",
"id": "confirmations.mute.message"
},
{
"defaultMessage": "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.",
"id": "confirmations.domain_block.message"
}
],
"path": "app/javascript/mastodon/features/account_timeline/containers/header_container.json"
@ -303,6 +320,14 @@
"defaultMessage": "This user is from another instance. This number may be larger.",
"id": "account.disclaimer"
},
{
"defaultMessage": "Hide everything from {domain}",
"id": "account.block_domain"
},
{
"defaultMessage": "Unhide {domain}",
"id": "account.unblock_domain"
},
{
"defaultMessage": "Posts",
"id": "account.posts"

View File

@ -1,17 +1,20 @@
{
"account.block": "Block @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "This user is from another instance. This number may be larger.",
"account.edit_profile": "Edit profile",
"account.follow": "Follow",
"account.followers": "Followers",
"account.follows": "Follows",
"account.follows_you": "Follows you",
"account.media": "Media",
"account.mention": "Mention @{name}",
"account.mute": "Mute @{name}",
"account.posts": "Posts",
"account.report": "Report @{name}",
"account.requested": "Awaiting approval",
"account.unblock": "Unblock @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Unfollow",
"account.unmute": "Unmute @{name}",
"boost_modal.combo": "You can press {combo} to skip this next time",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "Bloki @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "This user is from another instance. This number may be larger.",
"account.edit_profile": "Redakti la profilon",
"account.follow": "Sekvi",
"account.followers": "Sekvantoj",
"account.follows": "Sekvatoj",
"account.follows_you": "Sekvas vin",
"account.media": "Media",
"account.mention": "Mencii @{name}",
"account.mute": "Mute @{name}",
"account.posts": "Mesaĝoj",
"account.report": "Report @{name}",
"account.requested": "Atendas aprobon",
"account.unblock": "Malbloki @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Malsekvi",
"account.unmute": "Unmute @{name}",
"boost_modal.combo": "You can press {combo} to skip this next time",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "Bloquear",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "This user is from another instance. This number may be larger.",
"account.edit_profile": "Editar perfil",
"account.follow": "Seguir",
"account.followers": "Seguidores",
"account.follows": "Seguir",
"account.follows_you": "Te sigue",
"account.media": "Media",
"account.mention": "Mencionar",
"account.mute": "Silenciar",
"account.posts": "Publicaciones",
"account.report": "Report @{name}",
"account.requested": "Esperando aprobación",
"account.unblock": "Desbloquear",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Dejar de seguir",
"account.unmute": "Unmute @{name}",
"boost_modal.combo": "You can press {combo} to skip this next time",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "مسدودسازی @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "این کاربر عضو سرور متفاوتی است. شاید عدد واقعی بیشتر از این باشد.",
"account.edit_profile": "ویرایش نمایه",
"account.follow": "پی بگیرید",
"account.followers": "پیگیران",
"account.follows": "پی می‌گیرد",
"account.follows_you": "پیگیر شماست",
"account.media": "Media",
"account.mention": "نام‌بردن از @{name}",
"account.mute": "بی‌صدا کردن @{name}",
"account.posts": "نوشته‌ها",
"account.report": "گزارش @{name}",
"account.requested": "در انتظار پذیرش",
"account.unblock": "رفع انسداد @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "پایان پیگیری",
"account.unmute": "باصدا کردن @{name}",
"boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
@ -40,6 +43,8 @@
"confirmations.block.message": "آیا واقعاً می‌خواهید {name} را مسدود کنید؟",
"confirmations.delete.confirm": "پاک کن",
"confirmations.delete.message": "آیا واقعاً می‌خواهید این نوشته را پاک کنید؟",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "بی‌صدا کن",
"confirmations.mute.message": "آیا واقعاً می‌خواهید {name} را بی‌صدا کنید؟",
"emoji_button.activity": "فعالیت",

View File

@ -1,17 +1,20 @@
{
"account.block": "Estä @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "This user is from another instance. This number may be larger.",
"account.edit_profile": "Muokkaa",
"account.follow": "Seuraa",
"account.followers": "Seuraajia",
"account.follows": "Seuraa",
"account.follows_you": "Seuraa sinua",
"account.media": "Media",
"account.mention": "Mainitse @{name}",
"account.mute": "Mute @{name}",
"account.posts": "Postit",
"account.report": "Report @{name}",
"account.requested": "Odottaa hyväksyntää",
"account.unblock": "Salli @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Lopeta seuraaminen",
"account.unmute": "Unmute @{name}",
"boost_modal.combo": "You can press {combo} to skip this next time",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "Bloquer",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.",
"account.edit_profile": "Modifier le profil",
"account.follow": "Suivre",
"account.followers": "Abonné⋅e⋅s",
"account.follows": "Abonnements",
"account.follows_you": "Vous suit",
"account.media": "Media",
"account.mention": "Mentionner",
"account.mute": "Masquer",
"account.posts": "Statuts",
"account.report": "Signaler",
"account.requested": "Invitation envoyée",
"account.unblock": "Débloquer",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Ne plus suivre",
"account.unmute": "Ne plus masquer",
"boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois",
@ -40,6 +43,8 @@
"confirmations.block.message": "Confirmez vous le blocage de {name} ?",
"confirmations.delete.confirm": "Supprimer",
"confirmations.delete.message": "Confirmez vous la suppression de ce pouet ?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Silencer",
"confirmations.mute.message": "Confirmez vous la silenciation {name} ?",
"emoji_button.activity": "Activités",

View File

@ -1,17 +1,20 @@
{
"account.block": "חסימת @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "משתמש זה מגיע מקהילה אחרת. המספר הזה עשוי להיות גדול יותר.",
"account.edit_profile": "עריכת פרופיל",
"account.follow": "מעקב",
"account.followers": "עוקבים",
"account.follows": "נעקבים",
"account.follows_you": "במעקב אחריך",
"account.media": "Media",
"account.mention": "אזכור של @{name}",
"account.mute": "להשתיק את @{name}",
"account.posts": "הודעות",
"account.report": "לדווח על @{name}",
"account.requested": "בהמתנה לאישור",
"account.unblock": "הסרת חסימה מעל @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "הפסקת מעקב",
"account.unmute": "הפסקת השתקת @{name}",
"boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
@ -40,6 +43,8 @@
"confirmations.block.message": "לחסום את {name}?",
"confirmations.delete.confirm": "למחוק",
"confirmations.delete.message": "למחוק את ההודעה?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "להשתיק",
"confirmations.mute.message": "להשתיק את {name}?",
"emoji_button.activity": "פעילות",

View File

@ -1,17 +1,20 @@
{
"account.block": "Blokiraj @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.",
"account.edit_profile": "Uredi profil",
"account.follow": "Slijedi",
"account.followers": "Sljedbenici",
"account.follows": "Slijedi",
"account.follows_you": "te slijedi",
"account.media": "Media",
"account.mention": "Spomeni @{name}",
"account.mute": "Utišaj @{name}",
"account.posts": "Postovi",
"account.report": "Prijavi @{name}",
"account.requested": "Čeka pristanak",
"account.unblock": "Deblokiraj @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Prestani slijediti",
"account.unmute": "Poništi utišavanje @{name}",
"boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "Blokkolás",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "This user is from another instance. This number may be larger.",
"account.edit_profile": "Profil szerkesztése",
"account.follow": "Követés",
"account.followers": "Követők",
"account.follows": "Követve",
"account.follows_you": "Követnek téged",
"account.media": "Media",
"account.mention": "Említés",
"account.mute": "Mute @{name}",
"account.posts": "Posts",
"account.report": "Report @{name}",
"account.requested": "Awaiting approval",
"account.unblock": "Blokkolás levétele",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Követés abbahagyása",
"account.unmute": "Unmute @{name}",
"boost_modal.combo": "You can press {combo} to skip this next time",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "Blokir @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Pengguna ini berasal dari server lain. Angka berikut mungkin lebih besar.",
"account.edit_profile": "Ubah profil",
"account.follow": "Ikuti",
"account.followers": "Pengikut",
"account.follows": "Mengikuti",
"account.follows_you": "Mengikuti anda",
"account.media": "Media",
"account.mention": "Balasan @{name}",
"account.mute": "Bisukan @{name}",
"account.posts": "Postingan",
"account.report": "Laporkan @{name}",
"account.requested": "Menunggu persetujuan",
"account.unblock": "Hapus blokir @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Berhenti mengikuti",
"account.unmute": "Berhenti membisukan @{name}",
"boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
@ -40,6 +43,8 @@
"confirmations.block.message": "Apa anda yakin ingin memblokir {name}?",
"confirmations.delete.confirm": "Hapus",
"confirmations.delete.message": "Apa anda yakin akan menghapus status ini?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Bisukan",
"confirmations.mute.message": "Apa anda yakin ingin membisukan {name}?",
"emoji_button.activity": "Aktivitas",

View File

@ -1,17 +1,20 @@
{
"account.block": "Blokusar @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Ca uzero esas de altra instaluro. Ca nombro forsan esas plu granda.",
"account.edit_profile": "Modifikar profilo",
"account.follow": "Sequar",
"account.followers": "Sequanti",
"account.follows": "Sequas",
"account.follows_you": "Sequas tu",
"account.media": "Media",
"account.mention": "Mencionar @{name}",
"account.mute": "Celar @{name}",
"account.posts": "Mesaji",
"account.report": "Denuncar @{name}",
"account.requested": "Vartante aprobo",
"account.unblock": "Desblokusar @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Ne plus sequar",
"account.unmute": "Ne plus celar @{name}",
"boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "Blocca @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Questo utente si trova su un altro server. Questo numero potrebbe essere maggiore.",
"account.edit_profile": "Modifica profilo",
"account.follow": "Segui",
"account.followers": "Seguaci",
"account.follows": "Segue",
"account.follows_you": "Ti segue",
"account.media": "Media",
"account.mention": "Menziona @{name}",
"account.mute": "Silenzia @{name}",
"account.posts": "Posts",
"account.report": "Segnala @{name}",
"account.requested": "In attesa di approvazione",
"account.unblock": "Sblocca @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Non seguire",
"account.unmute": "Non silenziare @{name}",
"boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "ブロック",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "このユーザーは他のインスタンスに所属しているため、数字が正確で無い場合があります。",
"account.edit_profile": "プロフィールを編集",
"account.follow": "フォロー",
"account.followers": "フォロワー",
"account.follows": "フォロー",
"account.follows_you": "フォローされています",
"account.media": "Media",
"account.mention": "返信",
"account.mute": "ミュート",
"account.posts": "投稿",
"account.report": "通報",
"account.requested": "承認待ち",
"account.unblock": "ブロック解除",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "フォロー解除",
"account.unmute": "ミュート解除",
"boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
@ -40,6 +43,8 @@
"confirmations.block.message": "本当に{name}をブロックしますか?",
"confirmations.delete.confirm": "削除",
"confirmations.delete.message": "本当に削除しますか?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "ミュート",
"confirmations.mute.message": "本当に{name}をミュートしますか?",
"emoji_button.activity": "活動",

View File

@ -1,17 +1,20 @@
{
"account.block": "Blokkeer @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Deze gebruiker zit op een andere server. Dit getal kan hoger zijn.",
"account.edit_profile": "Profiel bewerken",
"account.follow": "Volgen",
"account.followers": "Volgers",
"account.follows": "Volgt",
"account.follows_you": "Volgt jou",
"account.media": "Media",
"account.mention": "Vermeld @{name}",
"account.mute": "Negeer @{name}",
"account.posts": "Berichten",
"account.report": "Rapporteer @{name}",
"account.requested": "Wacht op goedkeuring",
"account.unblock": "Deblokkeer @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Ontvolgen",
"account.unmute": "Negeer @{name} niet meer",
"boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
@ -40,6 +43,8 @@
"confirmations.block.message": "Weet je zeker dat je {name} wilt blokkeren?",
"confirmations.delete.confirm": "Verwijderen",
"confirmations.delete.message": "Weet je zeker dat je deze toot wilt verwijderen?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Negeren",
"confirmations.mute.message": "Weet je zeker dat je {name} wilt negeren?",
"emoji_button.activity": "Activiteiten",

View File

@ -1,17 +1,20 @@
{
"account.block": "Blokkér @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Denne brukeren er fra en annen instans. Dette tallet kan være høyere.",
"account.edit_profile": "Rediger profil",
"account.follow": "Følg",
"account.followers": "Følgere",
"account.follows": "Følger",
"account.follows_you": "Følger deg",
"account.media": "Media",
"account.mention": "Nevn @{name}",
"account.mute": "Demp @{name}",
"account.posts": "Innlegg",
"account.report": "Rapportér @{name}",
"account.requested": "Venter på godkjennelse",
"account.unblock": "Avblokker @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Avfølg",
"account.unmute": "Avdemp @{name}",
"boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "Blocar",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Aqueste compte es sus una autra instància. Los nombres pòdon èsser mai grandes.",
"account.edit_profile": "Modificar lo perfil",
"account.follow": "Sègre",
"account.followers": "Abonats",
"account.follows": "Abonaments",
"account.follows_you": "Vos sèc",
"account.media": "Media",
"account.mention": "Mencionar",
"account.mute": "Rescondre",
"account.posts": "Estatuts",
"account.report": "Senhalar",
"account.requested": "Invitacion mandada",
"account.unblock": "Desblocar",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Quitar de sègre",
"account.unmute": "Quitar de rescondre",
"boost_modal.combo": "Podètz butar {combo} per passar aquò lo còp que ven",
@ -40,6 +43,8 @@
"confirmations.block.message": "Sètz segur de voler blocar {name}?",
"confirmations.delete.confirm": "Suprimir",
"confirmations.delete.message": "Sètz segur de voler suprimir lestatut ?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Metre en silenci",
"confirmations.mute.message": "Sètz segur de voler metre en silenci {name}?",
"emoji_button.activity": "Activitat",

View File

@ -1,17 +1,20 @@
{
"account.block": "Blokuj @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Ten użytkownik pochodzi z innej instancji. Ta liczba może być większa.",
"account.edit_profile": "Edytuj profil",
"account.follow": "Obserwuj",
"account.followers": "Obserwujący",
"account.follows": "Obserwacje",
"account.follows_you": "Obserwują cię",
"account.media": "Media",
"account.mention": "Wspomnij o @{name}",
"account.mute": "Wycisz @{name}",
"account.posts": "Posty",
"account.report": "Zgłoś @{name}",
"account.requested": "Oczekująca prośba",
"account.unblock": "Odblokuj @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Przestań obserwować",
"account.unmute": "Cofnij wyciszenie @{name}",
"boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
@ -40,6 +43,8 @@
"confirmations.block.message": "Czy na pewno chcesz zablokować {name}?",
"confirmations.delete.confirm": "Usuń",
"confirmations.delete.message": "Czy na pewno chcesz usunąć ten status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Wycisz",
"confirmations.mute.message": "Czy na pewno chcesz wyciszyć {name}?",
"emoji_button.activity": "Aktywność",

View File

@ -1,17 +1,20 @@
{
"account.block": "Bloquear @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
"account.edit_profile": "Editar perfil",
"account.follow": "Seguir",
"account.followers": "Seguidores",
"account.follows": "Segue",
"account.follows_you": "É teu seguidor",
"account.media": "Media",
"account.mention": "Mencionar @{name}",
"account.mute": "Silenciar @{name}",
"account.posts": "Posts",
"account.report": "Denunciar @{name}",
"account.requested": "A aguardar aprovação",
"account.unblock": "Não bloquear @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Deixar de seguir",
"account.unmute": "Não silenciar @{name}",
"boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "Bloquear @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
"account.edit_profile": "Editar perfil",
"account.follow": "Seguir",
"account.followers": "Seguidores",
"account.follows": "Segue",
"account.follows_you": "É teu seguidor",
"account.media": "Media",
"account.mention": "Mencionar @{name}",
"account.mute": "Silenciar @{name}",
"account.posts": "Posts",
"account.report": "Denunciar @{name}",
"account.requested": "A aguardar aprovação",
"account.unblock": "Não bloquear @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Deixar de seguir",
"account.unmute": "Não silenciar @{name}",
"boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
@ -40,6 +43,8 @@
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"emoji_button.activity": "Activity",

View File

@ -1,17 +1,20 @@
{
"account.block": "Блокировать",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Это пользователь с другого узла. Число может быть больше.",
"account.edit_profile": "Изменить профиль",
"account.follow": "Подписаться",
"account.followers": "Подписаны",
"account.follows": "Подписки",
"account.follows_you": "Подписан(а) на Вас",
"account.media": "Media",
"account.mention": "Упомянуть",
"account.mute": "Заглушить",
"account.posts": "Посты",
"account.report": "Пожаловаться",
"account.requested": "Ожидает подтверждения",
"account.unblock": "Разблокировать",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Отписаться",
"account.unmute": "Снять глушение",
"boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
@ -40,6 +43,8 @@
"confirmations.block.message": "Вы уверены, что хотите заблокировать {name}?",
"confirmations.delete.confirm": "Удалить",
"confirmations.delete.message": "Вы уверены, что хотите удалить этот статус?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Заглушить",
"confirmations.mute.message": "Вы уверены, что хотите заглушить {name}?",
"emoji_button.activity": "Занятия",

View File

@ -1,17 +1,20 @@
{
"account.block": "Engelle @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Bu kullanıcının hesabı farklı sunucuda bulunduğu için bu sayı daha fazla olabilir.",
"account.edit_profile": "Profili düzenle",
"account.follow": "Takip et",
"account.followers": "Takipçiler",
"account.follows": "Takip ettikleri",
"account.follows_you": "Seni takip ediyor",
"account.media": "Media",
"account.mention": "Bahset @{name}",
"account.mute": "Sustur @{name}",
"account.posts": "Gönderiler",
"account.report": "Rapor et @{name}",
"account.requested": "Onay bekleniyor",
"account.unblock": "Engeli kaldır @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Takipten vazgeç",
"account.unmute": "Sesi aç @{name}",
"boost_modal.combo": "Bir dahaki sefere {combo} tuşuna basabilirsiniz",
@ -40,6 +43,8 @@
"confirmations.block.message": "{name} kullanıcısını engellemek istiyor musunuz?",
"confirmations.delete.confirm": "Sil",
"confirmations.delete.message": "Bu gönderiyi silmek istiyor musunuz?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Sessize al",
"confirmations.mute.message": "{name} kullanıcısını sessize almak istiyor musunuz?",
"emoji_button.activity": "Aktivite",

View File

@ -1,17 +1,20 @@
{
"account.block": "Заблокувати",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "Це користувач з іншої інстанції. Число може бути більше.",
"account.edit_profile": "Налаштування профілю",
"account.follow": "Підписатися",
"account.followers": "Підписники",
"account.follows": "Підписки",
"account.follows_you": "Підписаний(-а) на Вас",
"account.media": "Media",
"account.mention": "Згадати",
"account.mute": "Заглушити",
"account.posts": "Пости",
"account.report": "Поскаржитися",
"account.requested": "Очікує підтвердження",
"account.unblock": "Розблокувати",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Відписатися",
"account.unmute": "Зняти глушення",
"boost_modal.combo": "Ви можете натиснути {combo}, щоб пропустити це наступного разу",
@ -40,6 +43,8 @@
"confirmations.block.message": "Ви впевнені, що хочете заблокувати {name}?",
"confirmations.delete.confirm": "Видалити",
"confirmations.delete.message": "Ви впевнені, що хочете видалити цей допис?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Заглушить",
"confirmations.mute.message": "Ви впевнені, що хочете заглушити {name}?",
"emoji_button.activity": "Заняття",

View File

@ -1,17 +1,20 @@
{
"account.block": "屏蔽 @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "由于这个账户处于另一个服务站上,实际数字会比这个更多。",
"account.edit_profile": "修改个人资料",
"account.follow": "关注",
"account.followers": "关注者",
"account.follows": "正关注",
"account.follows_you": "关注你",
"account.media": "Media",
"account.mention": "提及 @{name}",
"account.mute": "将 @{name} 静音",
"account.posts": "嘟文",
"account.report": "举报 @{name}",
"account.requested": "等候审批",
"account.unblock": "解除对 @{name} 的屏蔽",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "取消关注",
"account.unmute": "取消 @{name} 的静音",
"boost_modal.combo": "如你想在下次路过时显示,请按{combo}",
@ -40,6 +43,8 @@
"confirmations.block.message": "想好了,真的要屏蔽 {name}?",
"confirmations.delete.confirm": "删除",
"confirmations.delete.message": "想好了,真的要删除这条嘟文?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "静音",
"confirmations.mute.message": "想好了,真的要静音 {name}?",
"emoji_button.activity": "活动",

View File

@ -1,17 +1,20 @@
{
"account.block": "封鎖 @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer": "由於這個用戶在另一個服務站,實際數字會比這個更多。",
"account.edit_profile": "修改個人資料",
"account.follow": "關注",
"account.followers": "關注的人",
"account.follows": "正在關注",
"account.follows_you": "關注你",
"account.media": "Media",
"account.mention": "提及 @{name}",
"account.mute": "將 @{name} 靜音",
"account.posts": "文章",
"account.report": "舉報 @{name}",
"account.requested": "等候審批",
"account.unblock": "解除對 @{name} 的封鎖",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "取消關注",
"account.unmute": "取消 @{name} 的靜音",
"boost_modal.combo": "如你想在下次路過這顯示,請按{combo}",
@ -40,6 +43,8 @@
"confirmations.block.message": "你確定要封鎖{name}嗎?",
"confirmations.delete.confirm": "刪除",
"confirmations.delete.message": "你確定要刪除{name}嗎?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "靜音",
"confirmations.mute.message": "你確定要將{name}靜音嗎?",
"emoji_button.activity": "活動",

View File

@ -7,6 +7,10 @@ import {
ACCOUNT_UNMUTE_SUCCESS,
RELATIONSHIPS_FETCH_SUCCESS
} from '../actions/accounts';
import {
DOMAIN_BLOCK_SUCCESS,
DOMAIN_UNBLOCK_SUCCESS
} from '../actions/domain_blocks';
import Immutable from 'immutable';
const normalizeRelationship = (state, relationship) => state.set(relationship.id, Immutable.fromJS(relationship));
@ -32,6 +36,10 @@ export default function relationships(state = initialState, action) {
return normalizeRelationship(state, action.relationship);
case RELATIONSHIPS_FETCH_SUCCESS:
return normalizeRelationships(state, action.relationships);
case DOMAIN_BLOCK_SUCCESS:
return state.setIn([action.accountId, 'domain_blocking'], true);
case DOMAIN_UNBLOCK_SUCCESS:
return state.setIn([action.accountId, 'domain_blocking'], false);
default:
return state;
}

View File

@ -14,6 +14,7 @@ class AccountDomainBlock < ApplicationRecord
include Paginable
belongs_to :account, required: true
validates :domain, presence: true, uniqueness: { scope: :account_id }
after_create :remove_blocking_cache
after_destroy :remove_blocking_cache

View File

@ -23,6 +23,12 @@ module AccountInteractions
def requested_map(target_account_ids, account_id)
follow_mapping(FollowRequest.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
end
def domain_blocking_map(target_account_ids, account_id)
accounts_map = Account.where(id: target_account_ids).select('id, domain').map { |a| [a.id, a.domain] }.to_h
blocked_domains = AccountDomainBlock.where(account_id: account_id, domain: accounts_map.values).pluck(:domain)
accounts_map.map { |id, domain| [id, blocked_domains.include?(domain)] }.to_h
end
end
included do

View File

@ -67,7 +67,8 @@ class Status < ApplicationRecord
scope :local_only, -> { left_outer_joins(:account).where(accounts: { domain: nil }) }
scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: false }) }
scope :including_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced: true }) }
scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids, accounts: { domain: account.excluded_from_timeline_domains }) }
scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) }
scope :not_domain_blocked_by_account, ->(account) { left_outer_joins(:account).where.not(accounts: { domain: account.excluded_from_timeline_domains }) }
cache_associated :account, :application, :media_attachments, :tags, :stream_entry, mentions: :account, reblog: [:account, :application, :stream_entry, :tags, :media_attachments, mentions: :account], thread: :account
@ -152,13 +153,13 @@ class Status < ApplicationRecord
def as_public_timeline(account = nil, local_only = false)
query = timeline_scope(local_only).without_replies
apply_timeline_filters(query, account)
apply_timeline_filters(query, account, local_only)
end
def as_tag_timeline(tag, account = nil, local_only = false)
query = timeline_scope(local_only).tagged_with(tag)
apply_timeline_filters(query, account)
apply_timeline_filters(query, account, local_only)
end
def as_outbox_timeline(account)
@ -222,16 +223,17 @@ class Status < ApplicationRecord
.without_reblogs
end
def apply_timeline_filters(query, account)
def apply_timeline_filters(query, account, local_only)
if account.nil?
filter_timeline_default(query)
else
filter_timeline_for_account(query, account)
filter_timeline_for_account(query, account, local_only)
end
end
def filter_timeline_for_account(query, account)
def filter_timeline_for_account(query, account, local_only)
query = query.not_excluded_by_account(account)
query = query.not_domain_blocked_by_account(account) unless local_only
query = query.in_allowed_languages(account) if account.allowed_languages.present?
query.merge(account_silencing_filter(account))
end

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
class BlockDomainFromAccountService < BaseService
def call(account, domain)
account.block_domain!(domain)
account.passive_relationships.where(account: Account.where(domain: domain)).delete_all
end
end

View File

@ -37,15 +37,15 @@ class NotifyService < BaseService
end
def blocked?
blocked = @recipient.suspended? # Skip if the recipient account is suspended anyway
blocked ||= @recipient.id == @notification.from_account.id # Skip for interactions with self
blocked ||= @recipient.domain_blocking?(@notification.from_account.domain) # Skip for domain blocked accounts
blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts
blocked ||= (@notification.from_account.silenced? && !@recipient.following?(@notification.from_account)) # Hellban
blocked ||= (@recipient.user.settings.interactions['must_be_follower'] && !@notification.from_account.following?(@recipient)) # Options
blocked ||= (@recipient.user.settings.interactions['must_be_following'] && !@recipient.following?(@notification.from_account)) # Options
blocked = @recipient.suspended? # Skip if the recipient account is suspended anyway
blocked ||= @recipient.id == @notification.from_account.id # Skip for interactions with self
blocked ||= @recipient.domain_blocking?(@notification.from_account.domain) && !@recipient.following?(@notification.from_account) # Skip for domain blocked accounts
blocked ||= @recipient.blocking?(@notification.from_account) # Skip for blocked accounts
blocked ||= (@notification.from_account.silenced? && !@recipient.following?(@notification.from_account)) # Hellban
blocked ||= (@recipient.user.settings.interactions['must_be_follower'] && !@notification.from_account.following?(@recipient)) # Options
blocked ||= (@recipient.user.settings.interactions['must_be_following'] && !@recipient.following?(@notification.from_account)) # Options
blocked ||= conversation_muted?
blocked ||= send("blocked_#{@notification.type}?") # Type-dependent filters
blocked ||= send("blocked_#{@notification.type}?") # Type-dependent filters
blocked
end

View File

@ -21,9 +21,9 @@ class ProcessInteractionService < BaseService
case verb(xml)
when :follow
follow!(account, target_account) unless target_account.locked? || target_account.blocking?(account)
follow!(account, target_account) unless target_account.locked? || target_account.blocking?(account) || target_account.domain_blocking?(account.domain)
when :request_friend
follow_request!(account, target_account) unless !target_account.locked? || target_account.blocking?(account)
follow_request!(account, target_account) unless !target_account.locked? || target_account.blocking?(account) || target_account.domain_blocking?(account.domain)
when :authorize
authorize_follow_request!(account, target_account)
when :reject

View File

@ -6,12 +6,22 @@ class SendInteractionService < BaseService
# @param [Account] source_account
# @param [Account] target_account
def call(xml, source_account, target_account)
envelope = salmon.pack(xml, source_account.keypair)
salmon.post(target_account.salmon_url, envelope)
@xml = xml
@source_account = source_account
@target_account = target_account
return if block_notification?
envelope = salmon.pack(@xml, @source_account.keypair)
salmon.post(@target_account.salmon_url, envelope)
end
private
def block_notification?
DomainBlock.blocked?(@target_account.domain)
end
def salmon
@salmon ||= OStatus2::Salmon.new
end

View File

@ -1,8 +1,9 @@
object @account
attribute :id
node(:following) { |account| @following[account.id] || false }
node(:followed_by) { |account| @followed_by[account.id] || false }
node(:blocking) { |account| @blocking[account.id] || false }
node(:muting) { |account| @muting[account.id] || false }
node(:requested) { |account| @requested[account.id] || false }
node(:following) { |account| @following[account.id] || false }
node(:followed_by) { |account| @followed_by[account.id] || false }
node(:blocking) { |account| @blocking[account.id] || false }
node(:muting) { |account| @muting[account.id] || false }
node(:requested) { |account| @requested[account.id] || false }
node(:domain_blocking) { |account| @domain_blocking[account.id] || false }

View File

@ -0,0 +1,19 @@
require 'rails_helper'
RSpec.describe BlockDomainFromAccountService do
let!(:wolf) { Fabricate(:account, username: 'wolf', domain: 'evil.org') }
let!(:alice) { Fabricate(:account, username: 'alice') }
subject { BlockDomainFromAccountService.new }
it 'creates domain block' do
subject.call(alice, 'evil.org')
expect(alice.domain_blocking?('evil.org')).to be true
end
it 'purge followers from blocked domain' do
wolf.follow!(alice)
subject.call(alice, 'evil.org')
expect(wolf.following?(alice)).to be false
end
end

View File

@ -22,6 +22,12 @@ RSpec.describe NotifyService do
is_expected.to_not change(Notification, :count)
end
it 'does still notify when sender\'s domain is blocked but sender is followed' do
recipient.block_domain!(sender.domain)
recipient.follow!(sender)
is_expected.to change(Notification, :count)
end
it 'does not notify when sender is silenced and not followed' do
sender.update(silenced: true)
is_expected.to_not change(Notification, :count)