Merge pull request #1138 from ThibG/glitch-soc/merge-upstream

Merge upstream changes
main
ThibG 5 years ago committed by GitHub
commit ed10ae2693
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -169,15 +169,12 @@ STREAMING_CLUSTER_NUM=1
# Maximum allowed display name characters # Maximum allowed display name characters
# MAX_DISPLAY_NAME_CHARS=30 # MAX_DISPLAY_NAME_CHARS=30
# Maximum image and video upload sizes # Maximum image and video/audio upload sizes
# Units are in bytes # Units are in bytes
# 1048576 bytes equals 1 megabyte # 1048576 bytes equals 1 megabyte
# MAX_IMAGE_SIZE=8388608 # MAX_IMAGE_SIZE=8388608
# MAX_VIDEO_SIZE=41943040 # MAX_VIDEO_SIZE=41943040
# Maximum length of audio uploads in seconds
# MAX_AUDIO_LENGTH=60
# LDAP authentication (optional) # LDAP authentication (optional)
# LDAP_ENABLED=true # LDAP_ENABLED=true
# LDAP_HOST=localhost # LDAP_HOST=localhost

@ -3,6 +3,45 @@ Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
## [2.9.2] - 2019-06-22
### Added
- Add `short_description` and `approval_required` to `GET /api/v1/instance` ([Gargron](https://github.com/tootsuite/mastodon/pull/11146))
### Changed
- Change camera icon to paperclip icon in upload form ([koyuawsmbrtn](https://github.com/tootsuite/mastodon/pull/11149))
### Fixed
- Fix audio-only OGG and WebM files not being processed as such ([Gargron](https://github.com/tootsuite/mastodon/pull/11151))
- Fix audio not being downloaded from remote servers ([Gargron](https://github.com/tootsuite/mastodon/pull/11145))
## [2.9.1] - 2019-06-22
### Added
- Add moderation API ([Gargron](https://github.com/tootsuite/mastodon/pull/9387))
- Add audio uploads ([Gargron](https://github.com/tootsuite/mastodon/pull/11123), [Gargron](https://github.com/tootsuite/mastodon/pull/11141))
### Changed
- Change domain blocks to automatically support subdomains ([Gargron](https://github.com/tootsuite/mastodon/pull/11138))
- Change Nanobox configuration to bring it up to date ([danhunsaker](https://github.com/tootsuite/mastodon/pull/11083))
### Removed
- Remove expensive counters from federation page in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/11139))
### Fixed
- Fix converted media being saved with original extension and mime type ([Gargron](https://github.com/tootsuite/mastodon/pull/11130))
- Fix layout of identity proofs settings ([acid-chicken](https://github.com/tootsuite/mastodon/pull/11126))
- Fix active scope only returning suspended users ([ThibG](https://github.com/tootsuite/mastodon/pull/11111))
- Fix sanitizer making block level elements unreadable ([Gargron](https://github.com/tootsuite/mastodon/pull/10836))
- Fix label for site theme not being translated in admin UI ([palindromordnilap](https://github.com/tootsuite/mastodon/pull/11121))
- Fix statuses not being filtered irreversibly in web UI under some circumstances ([ThibG](https://github.com/tootsuite/mastodon/pull/11113))
- Fix scrolling behaviour in compose form ([ThibG](https://github.com/tootsuite/mastodon/pull/11093))
## [2.9.0] - 2019-06-13 ## [2.9.0] - 2019-06-13
### Added ### Added

@ -127,6 +127,7 @@ module Admin
:by_domain, :by_domain,
:active, :active,
:pending, :pending,
:disabled,
:silenced, :silenced,
:suspended, :suspended,
:username, :username,

@ -13,7 +13,7 @@ module Admin
authorize :domain_block, :create? authorize :domain_block, :create?
@domain_block = DomainBlock.new(resource_params) @domain_block = DomainBlock.new(resource_params)
existing_domain_block = resource_params[:domain].present? ? DomainBlock.find_by(domain: resource_params[:domain]) : nil existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block) if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
@domain_block.save @domain_block.save

@ -18,7 +18,7 @@ module Admin
@blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count @blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
@available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url) @available = DeliveryFailureTracker.available?(Account.select(:shared_inbox_url).where(domain: params[:id]).first&.shared_inbox_url)
@media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size) @media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
@domain_block = DomainBlock.find_by(domain: params[:id]) @domain_block = DomainBlock.rule_for(params[:id])
end end
private private

@ -0,0 +1,32 @@
# frozen_string_literal: true
class Api::V1::Admin::AccountActionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }
before_action :require_staff!
before_action :set_account
def create
account_action = Admin::AccountAction.new(resource_params)
account_action.target_account = @account
account_action.current_account = current_account
account_action.save!
render_empty
end
private
def set_account
@account = Account.find(params[:account_id])
end
def resource_params
params.permit(
:type,
:report_id,
:warning_preset_id,
:text,
:send_email_notification
)
end
end

@ -0,0 +1,128 @@
# frozen_string_literal: true
class Api::V1::Admin::AccountsController < Api::BaseController
include Authorization
include AccountableConcern
LIMIT = 100
before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :show]
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, except: [:index, :show]
before_action :require_staff!
before_action :set_accounts, only: :index
before_action :set_account, except: :index
before_action :require_local_account!, only: [:enable, :approve, :reject]
after_action :insert_pagination_headers, only: :index
FILTER_PARAMS = %i(
local
remote
by_domain
active
pending
disabled
silenced
suspended
username
display_name
email
ip
staff
).freeze
PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze
def index
authorize :account, :index?
render json: @accounts, each_serializer: REST::Admin::AccountSerializer
end
def show
authorize @account, :show?
render json: @account, serializer: REST::Admin::AccountSerializer
end
def enable
authorize @account.user, :enable?
@account.user.enable!
log_action :enable, @account.user
render json: @account, serializer: REST::Admin::AccountSerializer
end
def approve
authorize @account.user, :approve?
@account.user.approve!
render json: @account, serializer: REST::Admin::AccountSerializer
end
def reject
authorize @account.user, :reject?
SuspendAccountService.new.call(@account, including_user: true, destroy: true, skip_distribution: true)
render json: @account, serializer: REST::Admin::AccountSerializer
end
def unsilence
authorize @account, :unsilence?
@account.unsilence!
log_action :unsilence, @account
render json: @account, serializer: REST::Admin::AccountSerializer
end
def unsuspend
authorize @account, :unsuspend?
@account.unsuspend!
log_action :unsuspend, @account
render json: @account, serializer: REST::Admin::AccountSerializer
end
private
def set_accounts
@accounts = filtered_accounts.order(id: :desc).includes(user: [:invite_request, :invite]).paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def set_account
@account = Account.find(params[:id])
end
def filtered_accounts
AccountFilter.new(filter_params).results
end
def filter_params
params.permit(*FILTER_PARAMS)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
api_v1_admin_accounts_url(pagination_params(max_id: pagination_max_id)) if records_continue?
end
def prev_path
api_v1_admin_accounts_url(pagination_params(min_id: pagination_since_id)) unless @accounts.empty?
end
def pagination_max_id
@accounts.last.id
end
def pagination_since_id
@accounts.first.id
end
def records_continue?
@accounts.size == limit_param(LIMIT)
end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
def require_local_account!
forbidden unless @account.local? && @account.user.present?
end
end

@ -0,0 +1,108 @@
# frozen_string_literal: true
class Api::V1::Admin::ReportsController < Api::BaseController
include Authorization
include AccountableConcern
LIMIT = 100
before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:reports' }, only: [:index, :show]
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:reports' }, except: [:index, :show]
before_action :require_staff!
before_action :set_reports, only: :index
before_action :set_report, except: :index
after_action :insert_pagination_headers, only: :index
FILTER_PARAMS = %i(
resolved
account_id
target_account_id
).freeze
PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze
def index
authorize :report, :index?
render json: @reports, each_serializer: REST::Admin::ReportSerializer
end
def show
authorize @report, :show?
render json: @report, serializer: REST::Admin::ReportSerializer
end
def assign_to_self
authorize @report, :update?
@report.update!(assigned_account_id: current_account.id)
log_action :assigned_to_self, @report
render json: @report, serializer: REST::Admin::ReportSerializer
end
def unassign
authorize @report, :update?
@report.update!(assigned_account_id: nil)
log_action :unassigned, @report
render json: @report, serializer: REST::Admin::ReportSerializer
end
def reopen
authorize @report, :update?
@report.unresolve!
log_action :reopen, @report
render json: @report, serializer: REST::Admin::ReportSerializer
end
def resolve
authorize @report, :update?
@report.resolve!(current_account)
log_action :resolve, @report
render json: @report, serializer: REST::Admin::ReportSerializer
end
private
def set_reports
@reports = filtered_reports.order(id: :desc).with_accounts.paginate_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def set_report
@report = Report.find(params[:id])
end
def filtered_reports
ReportFilter.new(filter_params).results
end
def filter_params
params.permit(*FILTER_PARAMS)
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
api_v1_admin_reports_url(pagination_params(max_id: pagination_max_id)) if records_continue?
end
def prev_path
api_v1_admin_reports_url(pagination_params(min_id: pagination_since_id)) unless @reports.empty?
end
def pagination_max_id
@reports.last.id
end
def pagination_since_id
@reports.first.id
end
def records_continue?
@reports.size == limit_param(LIMIT)
end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end

@ -7,6 +7,8 @@ class MediaController < ApplicationController
before_action :set_media_attachment before_action :set_media_attachment
before_action :verify_permitted_status! before_action :verify_permitted_status!
before_action :check_playable, only: :player
before_action :allow_iframing, only: :player
content_security_policy only: :player do |p| content_security_policy only: :player do |p|
p.frame_ancestors(false) p.frame_ancestors(false)
@ -18,8 +20,6 @@ class MediaController < ApplicationController
def player def player
@body_classes = 'player' @body_classes = 'player'
response.headers['X-Frame-Options'] = 'ALLOWALL'
raise ActiveRecord::RecordNotFound unless @media_attachment.video? || @media_attachment.gifv?
end end
private private
@ -34,4 +34,12 @@ class MediaController < ApplicationController
# Reraise in order to get a 404 instead of a 403 error code # Reraise in order to get a 404 instead of a 403 error code
raise ActiveRecord::RecordNotFound raise ActiveRecord::RecordNotFound
end end
def check_playable
not_found unless @media_attachment.larger_media_format?
end
def allow_iframing
response.headers['X-Frame-Options'] = 'ALLOWALL'
end
end end

@ -39,6 +39,6 @@ class MediaProxyController < ApplicationController
end end
def reject_media? def reject_media?
DomainBlock.find_by(domain: @media_attachment.account.domain)&.reject_media? DomainBlock.reject_media?(@media_attachment.account.domain)
end end
end end

@ -61,8 +61,4 @@ class Settings::IdentityProofsController < Settings::BaseController
def post_params def post_params
params.require(:account_identity_proof).permit(:post_status, :status_text) params.require(:account_identity_proof).permit(:post_status, :status_text)
end end
def set_body_classes
@body_classes = ''
end
end end

@ -177,7 +177,7 @@ class Item extends React.PureComponent {
if (attachment.get('type') === 'unknown') { if (attachment.get('type') === 'unknown') {
return ( return (
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}> <div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }}> <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }} title={attachment.get('description')}>
<canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' /> <canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
</a> </a>
</div> </div>

@ -521,16 +521,16 @@ export default class Status extends ImmutablePureComponent {
media={status.get('media_attachments')} media={status.get('media_attachments')}
/> />
); );
} else if (attachments.getIn([0, 'type']) === 'video') { // Media type is 'video' } else if (['video', 'audio'].includes(attachments.getIn([0, 'type']))) {
const video = status.getIn(['media_attachments', 0]); const attachment = status.getIn(['media_attachments', 0]);
media = ( media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} > <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
{Component => (<Component {Component => (<Component
preview={video.get('preview_url')} preview={attachment.get('preview_url')}
blurhash={video.get('blurhash')} blurhash={attachment.get('blurhash')}
src={video.get('url')} src={attachment.get('url')}
alt={video.get('description')} alt={attachment.get('description')}
inline inline
sensitive={status.get('sensitive')} sensitive={status.get('sensitive')}
letterbox={settings.getIn(['media', 'letterbox'])} letterbox={settings.getIn(['media', 'letterbox'])}
@ -544,7 +544,7 @@ export default class Status extends ImmutablePureComponent {
/>)} />)}
</Bundle> </Bundle>
); );
mediaIcon = 'video-camera'; mediaIcon = attachment.get('type') === 'video' ? 'video-camera' : 'music';
} else { // Media type is 'image' or 'gifv' } else { // Media type is 'image' or 'gifv'
media = ( media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}> <Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>

@ -16,7 +16,7 @@ function mapStateToProps (state) {
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','), acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','),
resetFileKey: state.getIn(['compose', 'resetFileKey']), resetFileKey: state.getIn(['compose', 'resetFileKey']),
hasPoll: !!poll, hasPoll: !!poll,
allowMedia: !poll && (media ? media.size < 4 && !media.some(item => item.get('type') === 'video') : true), allowMedia: !poll && (media ? media.size < 4 && !media.some(item => ['video', 'audio'].includes(item.get('type'))) : true),
hasMedia: media && !!media.size, hasMedia: media && !!media.size,
allowPoll: !(media && !!media.size), allowPoll: !(media && !!media.size),
showContentTypeChoice: state.getIn(['local_settings', 'show_content_type_choice']), showContentTypeChoice: state.getIn(['local_settings', 'show_content_type_choice']),

@ -131,14 +131,14 @@ export default class DetailedStatus extends ImmutablePureComponent {
} else if (status.get('media_attachments').size > 0) { } else if (status.get('media_attachments').size > 0) {
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
media = <AttachmentList media={status.get('media_attachments')} />; media = <AttachmentList media={status.get('media_attachments')} />;
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { } else if (['video', 'audio'].includes(status.getIn(['media_attachments', 0, 'type']))) {
const video = status.getIn(['media_attachments', 0]); const attachment = status.getIn(['media_attachments', 0]);
media = ( media = (
<Video <Video
preview={video.get('preview_url')} preview={attachment.get('preview_url')}
blurhash={video.get('blurhash')} blurhash={attachment.get('blurhash')}
src={video.get('url')} src={attachment.get('url')}
alt={video.get('description')} alt={attachment.get('description')}
inline inline
sensitive={status.get('sensitive')} sensitive={status.get('sensitive')}
letterbox={settings.getIn(['media', 'letterbox'])} letterbox={settings.getIn(['media', 'letterbox'])}
@ -150,7 +150,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
onToggleVisibility={this.props.onToggleMediaVisibility} onToggleVisibility={this.props.onToggleMediaVisibility}
/> />
); );
mediaIcon = 'video-camera'; mediaIcon = attachment.get('type') === 'video' ? 'video-camera' : 'music';
} else { } else {
media = ( media = (
<MediaGallery <MediaGallery

@ -370,6 +370,7 @@
border-radius: 4px; border-radius: 4px;
height: 140px; height: 140px;
width: 100%; width: 100%;
background-color: $base-shadow-color;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;

@ -157,7 +157,7 @@ class Item extends React.PureComponent {
if (attachment.get('type') === 'unknown') { if (attachment.get('type') === 'unknown') {
return ( return (
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}> <div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }}> <a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }} title={attachment.get('description')}>
<canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' /> <canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
</a> </a>
</div> </div>

@ -333,17 +333,17 @@ class Status extends ImmutablePureComponent {
media={status.get('media_attachments')} media={status.get('media_attachments')}
/> />
); );
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { } else if (['video', 'audio'].includes(status.getIn(['media_attachments', 0, 'type']))) {
const video = status.getIn(['media_attachments', 0]); const attachment = status.getIn(['media_attachments', 0]);
media = ( media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} > <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
{Component => ( {Component => (
<Component <Component
preview={video.get('preview_url')} preview={attachment.get('preview_url')}
blurhash={video.get('blurhash')} blurhash={attachment.get('blurhash')}
src={video.get('url')} src={attachment.get('url')}
alt={video.get('description')} alt={attachment.get('description')}
width={this.props.cachedMediaWidth} width={this.props.cachedMediaWidth}
height={110} height={110}
inline inline

@ -7,9 +7,11 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
const messages = defineMessages({ const messages = defineMessages({
upload: { id: 'upload_button.label', defaultMessage: 'Add media (JPEG, PNG, GIF, WebM, MP4, MOV)' }, upload: { id: 'upload_button.label', defaultMessage: 'Add media ({formats})' },
}); });
const SUPPORTED_FORMATS = 'JPEG, PNG, GIF, WebM, MP4, MOV, OGG, WAV, MP3, FLAC';
const makeMapStateToProps = () => { const makeMapStateToProps = () => {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']), acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
@ -60,9 +62,9 @@ class UploadButton extends ImmutablePureComponent {
return ( return (
<div className='compose-form__upload-button'> <div className='compose-form__upload-button'>
<IconButton icon='camera' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} /> <IconButton icon='paperclip' title={intl.formatMessage(messages.upload, { formats: SUPPORTED_FORMATS })} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
<label> <label>
<span style={{ display: 'none' }}>{intl.formatMessage(messages.upload)}</span> <span style={{ display: 'none' }}>{intl.formatMessage(messages.upload, { formats: SUPPORTED_FORMATS })}</span>
<input <input
key={resetFileKey} key={resetFileKey}
ref={this.setRef} ref={this.setRef}

@ -3,7 +3,7 @@ import UploadButton from '../components/upload_button';
import { uploadCompose } from '../../../actions/compose'; import { uploadCompose } from '../../../actions/compose';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')), disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio'].includes(m.get('type')))),
unavailable: state.getIn(['compose', 'poll']) !== null, unavailable: state.getIn(['compose', 'poll']) !== null,
resetFileKey: state.getIn(['compose', 'resetFileKey']), resetFileKey: state.getIn(['compose', 'resetFileKey']),
}); });

@ -107,15 +107,15 @@ export default class DetailedStatus extends ImmutablePureComponent {
} }
if (status.get('media_attachments').size > 0) { if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'video') { if (['video', 'audio'].includes(status.getIn(['media_attachments', 0, 'type']))) {
const video = status.getIn(['media_attachments', 0]); const attachment = status.getIn(['media_attachments', 0]);
media = ( media = (
<Video <Video
preview={video.get('preview_url')} preview={attachment.get('preview_url')}
blurhash={video.get('blurhash')} blurhash={attachment.get('blurhash')}
src={video.get('url')} src={attachment.get('url')}
alt={video.get('description')} alt={attachment.get('description')}
width={300} width={300}
height={150} height={150}
inline inline

@ -369,7 +369,7 @@
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} آخرون {people}} يتحدثون", "trends.count_by_accounts": "{count} {rawCount, plural, one {person} آخرون {people}} يتحدثون",
"ui.beforeunload": "سوف تفقد مسودتك إن تركت ماستدون.", "ui.beforeunload": "سوف تفقد مسودتك إن تركت ماستدون.",
"upload_area.title": "اسحب ثم أفلت للرفع", "upload_area.title": "اسحب ثم أفلت للرفع",
"upload_button.label": "إضافة وسائط (JPEG، PNG، GIF، WebM، MP4، MOV)", "upload_button.label": "إضافة وسائط ({formats})",
"upload_error.limit": "لقد تم بلوغ الحد الأقصى المسموح به لإرسال الملفات.", "upload_error.limit": "لقد تم بلوغ الحد الأقصى المسموح به لإرسال الملفات.",
"upload_error.poll": "لا يمكن إدراج ملفات في استطلاعات الرأي.", "upload_error.poll": "لا يمكن إدراج ملفات في استطلاعات الرأي.",
"upload_form.description": "وصف للمعاقين بصريا", "upload_form.description": "وصف للمعاقين بصريا",

@ -314,7 +314,7 @@
"search_results.accounts": "Gent", "search_results.accounts": "Gent",
"search_results.hashtags": "Etiquetes", "search_results.hashtags": "Etiquetes",
"search_results.statuses": "Toots", "search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
"status.admin_account": "Obre l'interfície de moderació per a @{name}", "status.admin_account": "Obre l'interfície de moderació per a @{name}",
"status.admin_status": "Obre aquest toot a la interfície de moderació", "status.admin_status": "Obre aquest toot a la interfície de moderació",
"status.block": "Bloqueja @{name}", "status.block": "Bloqueja @{name}",
@ -366,7 +366,7 @@
"time_remaining.minutes": "{number, plural, one {# minut} other {# minuts}} restants", "time_remaining.minutes": "{number, plural, one {# minut} other {# minuts}} restants",
"time_remaining.moments": "Moments restants", "time_remaining.moments": "Moments restants",
"time_remaining.seconds": "{number, plural, one {# segon} other {# segons}} restants", "time_remaining.seconds": "{number, plural, one {# segon} other {# segons}} restants",
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking", "trends.count_by_accounts": "{count} {rawCount, plural, one {persona} other {gent}} talking",
"ui.beforeunload": "El teu esborrany es perdrà si surts de Mastodon.", "ui.beforeunload": "El teu esborrany es perdrà si surts de Mastodon.",
"upload_area.title": "Arrossega i deixa anar per a carregar", "upload_area.title": "Arrossega i deixa anar per a carregar",
"upload_button.label": "Afegir multimèdia (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_button.label": "Afegir multimèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",

@ -369,7 +369,7 @@
"trends.count_by_accounts": "{count} {rawCount, plural, eine {Person} other {Personen}} reden darüber", "trends.count_by_accounts": "{count} {rawCount, plural, eine {Person} other {Personen}} reden darüber",
"ui.beforeunload": "Dein Entwurf geht verloren, wenn du Mastodon verlässt.", "ui.beforeunload": "Dein Entwurf geht verloren, wenn du Mastodon verlässt.",
"upload_area.title": "Zum Hochladen hereinziehen", "upload_area.title": "Zum Hochladen hereinziehen",
"upload_button.label": "Mediendatei hinzufügen (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_button.label": "Mediendatei hinzufügen ({formats})",
"upload_error.limit": "Dateiupload-Limit erreicht.", "upload_error.limit": "Dateiupload-Limit erreicht.",
"upload_error.poll": "Dateiuploads sind in Kombination mit Umfragen nicht erlaubt.", "upload_error.poll": "Dateiuploads sind in Kombination mit Umfragen nicht erlaubt.",
"upload_form.description": "Für Menschen mit Sehbehinderung beschreiben", "upload_form.description": "Für Menschen mit Sehbehinderung beschreiben",

@ -1051,7 +1051,7 @@
{ {
"descriptors": [ "descriptors": [
{ {
"defaultMessage": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)", "defaultMessage": "Add media ({formats})",
"id": "upload_button.label" "id": "upload_button.label"
} }
], ],

@ -374,7 +374,7 @@
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking", "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
"upload_area.title": "Drag & drop to upload", "upload_area.title": "Drag & drop to upload",
"upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_button.label": "Add media ({formats})",
"upload_error.limit": "File upload limit exceeded.", "upload_error.limit": "File upload limit exceeded.",
"upload_error.poll": "File upload not allowed with polls.", "upload_error.poll": "File upload not allowed with polls.",
"upload_form.description": "Describe for the visually impaired", "upload_form.description": "Describe for the visually impaired",

@ -71,20 +71,20 @@
"compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille rajaamasi julkaisut.", "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille rajaamasi julkaisut.",
"compose_form.lock_disclaimer.lock": "lukittu", "compose_form.lock_disclaimer.lock": "lukittu",
"compose_form.placeholder": "Mitä mietit?", "compose_form.placeholder": "Mitä mietit?",
"compose_form.poll.add_option": "Add a choice", "compose_form.poll.add_option": "Lisää valinta",
"compose_form.poll.duration": "Poll duration", "compose_form.poll.duration": "Äänestyksen kesto",
"compose_form.poll.option_placeholder": "Choice {number}", "compose_form.poll.option_placeholder": "Valinta numero",
"compose_form.poll.remove_option": "Remove this choice", "compose_form.poll.remove_option": "Poista tämä valinta",
"compose_form.publish": "Tuuttaa", "compose_form.publish": "Tuuttaa",
"compose_form.publish_loud": "{publish}!", "compose_form.publish_loud": "Julkista!",
"compose_form.sensitive.hide": "Mark media as sensitive", "compose_form.sensitive.hide": "Valitse tämä arkaluontoisena",
"compose_form.sensitive.marked": "Media on merkitty arkaluontoiseksi", "compose_form.sensitive.marked": "Media on merkitty arkaluontoiseksi",
"compose_form.sensitive.unmarked": "Mediaa ei ole merkitty arkaluontoiseksi", "compose_form.sensitive.unmarked": "Mediaa ei ole merkitty arkaluontoiseksi",
"compose_form.spoiler.marked": "Teksti on piilotettu varoituksen taakse", "compose_form.spoiler.marked": "Teksti on piilotettu varoituksen taakse",
"compose_form.spoiler.unmarked": "Teksti ei ole piilotettu", "compose_form.spoiler.unmarked": "Teksti ei ole piilotettu",
"compose_form.spoiler_placeholder": "Sisältövaroitus", "compose_form.spoiler_placeholder": "Sisältövaroitus",
"confirmation_modal.cancel": "Peruuta", "confirmation_modal.cancel": "Peruuta",
"confirmations.block.block_and_report": "Block & Report", "confirmations.block.block_and_report": "Estä ja raportoi",
"confirmations.block.confirm": "Estä", "confirmations.block.confirm": "Estä",
"confirmations.block.message": "Haluatko varmasti estää käyttäjän {name}?", "confirmations.block.message": "Haluatko varmasti estää käyttäjän {name}?",
"confirmations.delete.confirm": "Poista", "confirmations.delete.confirm": "Poista",
@ -118,7 +118,7 @@
"emoji_button.symbols": "Symbolit", "emoji_button.symbols": "Symbolit",
"emoji_button.travel": "Matkailu", "emoji_button.travel": "Matkailu",
"empty_column.account_timeline": "Ei ole 'toots' täällä!", "empty_column.account_timeline": "Ei ole 'toots' täällä!",
"empty_column.account_unavailable": "Profile unavailable", "empty_column.account_unavailable": "Profiilia ei löydy",
"empty_column.blocks": "Et ole vielä estänyt yhtään käyttäjää.", "empty_column.blocks": "Et ole vielä estänyt yhtään käyttäjää.",
"empty_column.community": "Paikallinen aikajana on tyhjä. Homma lähtee käyntiin, kun kirjoitat jotain julkista!", "empty_column.community": "Paikallinen aikajana on tyhjä. Homma lähtee käyntiin, kun kirjoitat jotain julkista!",
"empty_column.direct": "Sinulla ei ole vielä yhtään viestiä yksittäiselle käyttäjälle. Kun lähetät tai vastaanotat sellaisen, se näkyy täällä.", "empty_column.direct": "Sinulla ei ole vielä yhtään viestiä yksittäiselle käyttäjälle. Kun lähetät tai vastaanotat sellaisen, se näkyy täällä.",
@ -138,7 +138,7 @@
"follow_request.reject": "Hylkää", "follow_request.reject": "Hylkää",
"getting_started.developers": "Kehittäjille", "getting_started.developers": "Kehittäjille",
"getting_started.directory": "Profiili hakemisto", "getting_started.directory": "Profiili hakemisto",
"getting_started.documentation": "Documentation", "getting_started.documentation": "Documentaatio",
"getting_started.heading": "Aloitus", "getting_started.heading": "Aloitus",
"getting_started.invite": "Kutsu ihmisiä", "getting_started.invite": "Kutsu ihmisiä",
"getting_started.open_source_notice": "Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHubissa: {github}.", "getting_started.open_source_notice": "Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHubissa: {github}.",
@ -147,8 +147,8 @@
"hashtag.column_header.tag_mode.all": "ja {additional}", "hashtag.column_header.tag_mode.all": "ja {additional}",
"hashtag.column_header.tag_mode.any": "tai {additional}", "hashtag.column_header.tag_mode.any": "tai {additional}",
"hashtag.column_header.tag_mode.none": "ilman {additional}", "hashtag.column_header.tag_mode.none": "ilman {additional}",
"hashtag.column_settings.select.no_options_message": "No suggestions found", "hashtag.column_settings.select.no_options_message": "Ehdostuta ei löydetty",
"hashtag.column_settings.select.placeholder": "Enter hashtags…", "hashtag.column_settings.select.placeholder": "Laita häshtägejä…",
"hashtag.column_settings.tag_mode.all": "Kaikki", "hashtag.column_settings.tag_mode.all": "Kaikki",
"hashtag.column_settings.tag_mode.any": "Kaikki", "hashtag.column_settings.tag_mode.any": "Kaikki",
"hashtag.column_settings.tag_mode.none": "Ei mikään", "hashtag.column_settings.tag_mode.none": "Ei mikään",
@ -156,25 +156,25 @@
"home.column_settings.basic": "Perusasetukset", "home.column_settings.basic": "Perusasetukset",
"home.column_settings.show_reblogs": "Näytä buustaukset", "home.column_settings.show_reblogs": "Näytä buustaukset",
"home.column_settings.show_replies": "Näytä vastaukset", "home.column_settings.show_replies": "Näytä vastaukset",
"intervals.full.days": "{number, plural, one {# day} other {# days}}", "intervals.full.days": "Päivä päiviä",
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}", "intervals.full.hours": "Tunti tunteja",
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}", "intervals.full.minutes": "Minuuti minuuteja",
"introduction.federation.action": "Seuraava", "introduction.federation.action": "Seuraava",
"introduction.federation.federated.headline": "Federated", "introduction.federation.federated.headline": "Federaatioitettu",
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.", "introduction.federation.federated.text": "Julkisia viestejä muiden serverien that is not a word aikoo tulla federoituun aikajanaan.",
"introduction.federation.home.headline": "Home", "introduction.federation.home.headline": "Koti",
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!", "introduction.federation.home.text": "Viestit muilta pelaajilta jota seuraat aikovat tulla koti sivuusi. Voit seurata ketä vain missä vain serverillä!",
"introduction.federation.local.headline": "Local", "introduction.federation.local.headline": "Paikallinen",
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.", "introduction.federation.local.text": "Julkiset viestit muilta pelaajilta samalla serverillä tulevat sinun paikalliseen aikajanaan.",
"introduction.interactions.action": "Finish toot-orial!", "introduction.interactions.action": "Suorita harjoitus!",
"introduction.interactions.favourite.headline": "Favourite", "introduction.interactions.favourite.headline": "Lempi",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.", "introduction.interactions.favourite.text": "Toot is not a word.",
"introduction.interactions.reblog.headline": "Boost", "introduction.interactions.reblog.headline": "Nopeutus",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.", "introduction.interactions.reblog.text": "Toot is not a word",
"introduction.interactions.reply.headline": "Reply", "introduction.interactions.reply.headline": "Vastaa",
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.", "introduction.interactions.reply.text": "TOOT IS NOT A WORD",
"introduction.welcome.action": "Let's go!", "introduction.welcome.action": "Mennään!",
"introduction.welcome.headline": "First steps", "introduction.welcome.headline": "Ensimmäiset askeleet",
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.", "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
"keyboard_shortcuts.back": "liiku taaksepäin", "keyboard_shortcuts.back": "liiku taaksepäin",
"keyboard_shortcuts.blocked": "avaa lista estetyistä käyttäjistä", "keyboard_shortcuts.blocked": "avaa lista estetyistä käyttäjistä",

@ -40,7 +40,7 @@
"boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta", "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
"bundle_column_error.body": "E' avvenuto un errore durante il caricamento di questo componente.", "bundle_column_error.body": "E' avvenuto un errore durante il caricamento di questo componente.",
"bundle_column_error.retry": "Riprova", "bundle_column_error.retry": "Riprova",
"bundle_column_error.title": "Network error", "bundle_column_error.title": "Errore di rete",
"bundle_modal_error.close": "Chiudi", "bundle_modal_error.close": "Chiudi",
"bundle_modal_error.message": "C'è stato un errore mentre questo componente veniva caricato.", "bundle_modal_error.message": "C'è stato un errore mentre questo componente veniva caricato.",
"bundle_modal_error.retry": "Riprova", "bundle_modal_error.retry": "Riprova",
@ -71,20 +71,20 @@
"compose_form.lock_disclaimer": "Il tuo account non è {bloccato}. Chiunque può decidere di seguirti per vedere i tuoi post per soli seguaci.", "compose_form.lock_disclaimer": "Il tuo account non è {bloccato}. Chiunque può decidere di seguirti per vedere i tuoi post per soli seguaci.",
"compose_form.lock_disclaimer.lock": "bloccato", "compose_form.lock_disclaimer.lock": "bloccato",
"compose_form.placeholder": "A cosa stai pensando?", "compose_form.placeholder": "A cosa stai pensando?",
"compose_form.poll.add_option": "Add a choice", "compose_form.poll.add_option": "Aggiungi una scelta",
"compose_form.poll.duration": "Poll duration", "compose_form.poll.duration": "Durata del sondaggio",
"compose_form.poll.option_placeholder": "Choice {number}", "compose_form.poll.option_placeholder": "Scelta {number}",
"compose_form.poll.remove_option": "Remove this choice", "compose_form.poll.remove_option": "Rimuovi questa scelta",
"compose_form.publish": "Toot", "compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!", "compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Mark media as sensitive", "compose_form.sensitive.hide": "Segna media come sensibile",
"compose_form.sensitive.marked": "Questo media è contrassegnato come sensibile", "compose_form.sensitive.marked": "Questo media è contrassegnato come sensibile",
"compose_form.sensitive.unmarked": "Questo media non è contrassegnato come sensibile", "compose_form.sensitive.unmarked": "Questo media non è contrassegnato come sensibile",
"compose_form.spoiler.marked": "Il testo è nascosto dall'avviso", "compose_form.spoiler.marked": "Il testo è nascosto dall'avviso",
"compose_form.spoiler.unmarked": "Il testo non è nascosto", "compose_form.spoiler.unmarked": "Il testo non è nascosto",
"compose_form.spoiler_placeholder": "Content warning", "compose_form.spoiler_placeholder": "Content warning",
"confirmation_modal.cancel": "Annulla", "confirmation_modal.cancel": "Annulla",
"confirmations.block.block_and_report": "Block & Report", "confirmations.block.block_and_report": "Blocca & Segnala",
"confirmations.block.confirm": "Blocca", "confirmations.block.confirm": "Blocca",
"confirmations.block.message": "Sei sicuro di voler bloccare {name}?", "confirmations.block.message": "Sei sicuro di voler bloccare {name}?",
"confirmations.delete.confirm": "Cancella", "confirmations.delete.confirm": "Cancella",
@ -118,7 +118,7 @@
"emoji_button.symbols": "Simboli", "emoji_button.symbols": "Simboli",
"emoji_button.travel": "Viaggi e luoghi", "emoji_button.travel": "Viaggi e luoghi",
"empty_column.account_timeline": "Non ci sono toot qui!", "empty_column.account_timeline": "Non ci sono toot qui!",
"empty_column.account_unavailable": "Profile unavailable", "empty_column.account_unavailable": "Profilo non disponibile",
"empty_column.blocks": "Non hai ancora bloccato nessun utente.", "empty_column.blocks": "Non hai ancora bloccato nessun utente.",
"empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!", "empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!",
"empty_column.direct": "Non hai ancora nessun messaggio diretto. Quando ne manderai o riceverai qualcuno, apparirà qui.", "empty_column.direct": "Non hai ancora nessun messaggio diretto. Quando ne manderai o riceverai qualcuno, apparirà qui.",
@ -156,15 +156,15 @@
"home.column_settings.basic": "Semplice", "home.column_settings.basic": "Semplice",
"home.column_settings.show_reblogs": "Mostra post condivisi", "home.column_settings.show_reblogs": "Mostra post condivisi",
"home.column_settings.show_replies": "Mostra risposte", "home.column_settings.show_replies": "Mostra risposte",
"intervals.full.days": "{number, plural, one {# day} other {# days}}", "intervals.full.days": "{number, plural, one {# giorno} other {# giorni}}",
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}", "intervals.full.hours": "{number, plural, one {# ora} other {# ore}}",
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}", "intervals.full.minutes": "{number, plural, one {# minuto} other {# minuti}}",
"introduction.federation.action": "Avanti", "introduction.federation.action": "Avanti",
"introduction.federation.federated.headline": "Federated", "introduction.federation.federated.headline": "Federato",
"introduction.federation.federated.text": "I post pubblici provenienti da altri server del fediverse saranno mostrati nella timeline federata.", "introduction.federation.federated.text": "I post pubblici provenienti da altri server del fediverse saranno mostrati nella timeline federata.",
"introduction.federation.home.headline": "Home", "introduction.federation.home.headline": "Home",
"introduction.federation.home.text": "I post scritti da persone che segui saranno mostrati nella timeline home. Puoi seguire chiunque su qualunque server!", "introduction.federation.home.text": "I post scritti da persone che segui saranno mostrati nella timeline home. Puoi seguire chiunque su qualunque server!",
"introduction.federation.local.headline": "Local", "introduction.federation.local.headline": "Locale",
"introduction.federation.local.text": "I post pubblici scritti da persone sul tuo stesso server saranno mostrati nella timeline locale.", "introduction.federation.local.text": "I post pubblici scritti da persone sul tuo stesso server saranno mostrati nella timeline locale.",
"introduction.interactions.action": "Finisci il tutorial!", "introduction.interactions.action": "Finisci il tutorial!",
"introduction.interactions.favourite.headline": "Apprezza", "introduction.interactions.favourite.headline": "Apprezza",
@ -204,17 +204,17 @@
"keyboard_shortcuts.search": "per spostare il focus sulla ricerca", "keyboard_shortcuts.search": "per spostare il focus sulla ricerca",
"keyboard_shortcuts.start": "per aprire la colonna \"Come iniziare\"", "keyboard_shortcuts.start": "per aprire la colonna \"Come iniziare\"",
"keyboard_shortcuts.toggle_hidden": "per mostrare/nascondere il testo dei CW", "keyboard_shortcuts.toggle_hidden": "per mostrare/nascondere il testo dei CW",
"keyboard_shortcuts.toggle_sensitivity": "to show/hide media", "keyboard_shortcuts.toggle_sensitivity": "mostrare/nascondere media",
"keyboard_shortcuts.toot": "per iniziare a scrivere un toot completamente nuovo", "keyboard_shortcuts.toot": "per iniziare a scrivere un toot completamente nuovo",
"keyboard_shortcuts.unfocus": "per uscire dall'area di composizione o dalla ricerca", "keyboard_shortcuts.unfocus": "per uscire dall'area di composizione o dalla ricerca",
"keyboard_shortcuts.up": "per spostarsi in alto nella lista", "keyboard_shortcuts.up": "per spostarsi in alto nella lista",
"lightbox.close": "Chiudi", "lightbox.close": "Chiudi",
"lightbox.next": "Successivo", "lightbox.next": "Successivo",
"lightbox.previous": "Precedente", "lightbox.previous": "Precedente",
"lightbox.view_context": "View context", "lightbox.view_context": "Mostra contesto",
"lists.account.add": "Aggiungi alla lista", "lists.account.add": "Aggiungi alla lista",
"lists.account.remove": "Togli dalla lista", "lists.account.remove": "Togli dalla lista",
"lists.delete": "Delete list", "lists.delete": "Elimina lista",
"lists.edit": "Modifica lista", "lists.edit": "Modifica lista",
"lists.edit.submit": "Cambia titolo", "lists.edit.submit": "Cambia titolo",
"lists.new.create": "Aggiungi lista", "lists.new.create": "Aggiungi lista",
@ -243,16 +243,16 @@
"navigation_bar.lists": "Liste", "navigation_bar.lists": "Liste",
"navigation_bar.logout": "Esci", "navigation_bar.logout": "Esci",
"navigation_bar.mutes": "Utenti silenziati", "navigation_bar.mutes": "Utenti silenziati",
"navigation_bar.personal": "Personal", "navigation_bar.personal": "Personale",
"navigation_bar.pins": "Toot fissati in cima", "navigation_bar.pins": "Toot fissati in cima",
"navigation_bar.preferences": "Impostazioni", "navigation_bar.preferences": "Impostazioni",
"navigation_bar.profile_directory": "Profile directory", "navigation_bar.profile_directory": "Directory dei profili",
"navigation_bar.public_timeline": "Timeline federata", "navigation_bar.public_timeline": "Timeline federata",
"navigation_bar.security": "Sicurezza", "navigation_bar.security": "Sicurezza",
"notification.favourite": "{name} ha apprezzato il tuo post", "notification.favourite": "{name} ha apprezzato il tuo post",
"notification.follow": "{name} ha iniziato a seguirti", "notification.follow": "{name} ha iniziato a seguirti",
"notification.mention": "{name} ti ha menzionato", "notification.mention": "{name} ti ha menzionato",
"notification.poll": "A poll you have voted in has ended", "notification.poll": "Un sondaggio in cui hai votato è terminato",
"notification.reblog": "{name} ha condiviso il tuo post", "notification.reblog": "{name} ha condiviso il tuo post",
"notifications.clear": "Cancella notifiche", "notifications.clear": "Cancella notifiche",
"notifications.clear_confirmation": "Vuoi davvero cancellare tutte le notifiche?", "notifications.clear_confirmation": "Vuoi davvero cancellare tutte le notifiche?",
@ -263,7 +263,7 @@
"notifications.column_settings.filter_bar.show": "Mostra", "notifications.column_settings.filter_bar.show": "Mostra",
"notifications.column_settings.follow": "Nuovi seguaci:", "notifications.column_settings.follow": "Nuovi seguaci:",
"notifications.column_settings.mention": "Menzioni:", "notifications.column_settings.mention": "Menzioni:",
"notifications.column_settings.poll": "Poll results:", "notifications.column_settings.poll": "Risultati del sondaggio:",
"notifications.column_settings.push": "Notifiche push", "notifications.column_settings.push": "Notifiche push",
"notifications.column_settings.reblog": "Post condivisi:", "notifications.column_settings.reblog": "Post condivisi:",
"notifications.column_settings.show": "Mostra in colonna", "notifications.column_settings.show": "Mostra in colonna",
@ -273,14 +273,14 @@
"notifications.filter.favourites": "Apprezzati", "notifications.filter.favourites": "Apprezzati",
"notifications.filter.follows": "Seguaci", "notifications.filter.follows": "Seguaci",
"notifications.filter.mentions": "Menzioni", "notifications.filter.mentions": "Menzioni",
"notifications.filter.polls": "Poll results", "notifications.filter.polls": "Risultati del sondaggio",
"notifications.group": "{count} notifiche", "notifications.group": "{count} notifiche",
"poll.closed": "Chiuso", "poll.closed": "Chiuso",
"poll.refresh": "Aggiorna", "poll.refresh": "Aggiorna",
"poll.total_votes": "{count, plural, one {# voto} other {# voti}}", "poll.total_votes": "{count, plural, one {# voto} other {# voti}}",
"poll.vote": "Vota", "poll.vote": "Vota",
"poll_button.add_poll": "Add a poll", "poll_button.add_poll": "Aggiungi un sondaggio",
"poll_button.remove_poll": "Remove poll", "poll_button.remove_poll": "Rimuovi sondaggio",
"privacy.change": "Modifica privacy del post", "privacy.change": "Modifica privacy del post",
"privacy.direct.long": "Invia solo a utenti menzionati", "privacy.direct.long": "Invia solo a utenti menzionati",
"privacy.direct.short": "Diretto", "privacy.direct.short": "Diretto",
@ -292,8 +292,8 @@
"privacy.unlisted.short": "Non elencato", "privacy.unlisted.short": "Non elencato",
"regeneration_indicator.label": "Caricamento in corso…", "regeneration_indicator.label": "Caricamento in corso…",
"regeneration_indicator.sublabel": "Stiamo preparando il tuo home feed!", "regeneration_indicator.sublabel": "Stiamo preparando il tuo home feed!",
"relative_time.days": "{number}d", "relative_time.days": "{number}g",
"relative_time.hours": "{number}h", "relative_time.hours": "{number}o",
"relative_time.just_now": "ora", "relative_time.just_now": "ora",
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
@ -307,8 +307,8 @@
"search.placeholder": "Cerca", "search.placeholder": "Cerca",
"search_popout.search_format": "Formato di ricerca avanzato", "search_popout.search_format": "Formato di ricerca avanzato",
"search_popout.tips.full_text": "Testo semplice per trovare gli status che hai scritto, segnato come apprezzati, condiviso o in cui sei stato citato, e inoltre i nomi utente, nomi visualizzati e hashtag che lo contengono.", "search_popout.tips.full_text": "Testo semplice per trovare gli status che hai scritto, segnato come apprezzati, condiviso o in cui sei stato citato, e inoltre i nomi utente, nomi visualizzati e hashtag che lo contengono.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "etichetta",
"search_popout.tips.status": "status", "search_popout.tips.status": "stato",
"search_popout.tips.text": "Testo semplice per trovare nomi visualizzati, nomi utente e hashtag che lo contengono", "search_popout.tips.text": "Testo semplice per trovare nomi visualizzati, nomi utente e hashtag che lo contengono",
"search_popout.tips.user": "utente", "search_popout.tips.user": "utente",
"search_results.accounts": "Gente", "search_results.accounts": "Gente",
@ -371,7 +371,7 @@
"upload_area.title": "Trascina per caricare", "upload_area.title": "Trascina per caricare",
"upload_button.label": "Aggiungi file multimediale", "upload_button.label": "Aggiungi file multimediale",
"upload_error.limit": "Limite al caricamento di file superato.", "upload_error.limit": "Limite al caricamento di file superato.",
"upload_error.poll": "File upload not allowed with polls.", "upload_error.poll": "Caricamento file non consentito nei sondaggi.",
"upload_form.description": "Descrizione per utenti con disabilità visive", "upload_form.description": "Descrizione per utenti con disabilità visive",
"upload_form.focus": "Modifica anteprima", "upload_form.focus": "Modifica anteprima",
"upload_form.undo": "Cancella", "upload_form.undo": "Cancella",

@ -374,7 +374,7 @@
"trends.count_by_accounts": "{count}人がトゥート", "trends.count_by_accounts": "{count}人がトゥート",
"ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。", "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。",
"upload_area.title": "ドラッグ&ドロップでアップロード", "upload_area.title": "ドラッグ&ドロップでアップロード",
"upload_button.label": "メディアを追加 (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_button.label": "メディアを追加 ({formats})",
"upload_error.limit": "アップロードできる上限を超えています。", "upload_error.limit": "アップロードできる上限を超えています。",
"upload_error.poll": "アンケートではファイルをアップロードできません。", "upload_error.poll": "アンケートではファイルをアップロードできません。",
"upload_form.description": "視覚障害者のための説明", "upload_form.description": "視覚障害者のための説明",

@ -361,15 +361,15 @@
"tabs_bar.local_timeline": "Lokaal", "tabs_bar.local_timeline": "Lokaal",
"tabs_bar.notifications": "Meldingen", "tabs_bar.notifications": "Meldingen",
"tabs_bar.search": "Zoeken", "tabs_bar.search": "Zoeken",
"time_remaining.days": "{number, plural, one {# dag} other {# dagen}} left", "time_remaining.days": "{number, plural, one {# dag} other {# dagen}} te gaan",
"time_remaining.hours": "{number, plural, one {# uur} other {# uur}} left", "time_remaining.hours": "{number, plural, one {# uur} other {# uur}} te gaan",
"time_remaining.minutes": "{number, plural, one {# minuut} other {# minuten}} left", "time_remaining.minutes": "{number, plural, one {# minuut} other {# minuten}} te gaan",
"time_remaining.moments": "Nog enkele ogenblikken resterend", "time_remaining.moments": "Nog enkele ogenblikken resterend",
"time_remaining.seconds": "{number, plural, one {# seconde} other {# seconden}} left", "time_remaining.seconds": "{number, plural, one {# seconde} other {# seconden}} te gaan",
"trends.count_by_accounts": "{count} {rawCount, plural, one {persoon praat} other {mensen praten}} hierover", "trends.count_by_accounts": "{count} {rawCount, plural, one {persoon praat} other {mensen praten}} hierover",
"ui.beforeunload": "Je concept zal verloren gaan als je Mastodon verlaat.", "ui.beforeunload": "Je concept zal verloren gaan als je Mastodon verlaat.",
"upload_area.title": "Hierin slepen om te uploaden", "upload_area.title": "Hiernaar toe slepen om te uploaden",
"upload_button.label": "Media toevoegen (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_button.label": "Media toevoegen ({formats})",
"upload_error.limit": "Uploadlimiet van bestand overschreden.", "upload_error.limit": "Uploadlimiet van bestand overschreden.",
"upload_error.poll": "Het uploaden van bestanden is in polls niet toegestaan.", "upload_error.poll": "Het uploaden van bestanden is in polls niet toegestaan.",
"upload_form.description": "Omschrijf dit voor mensen met een visuele beperking", "upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",

@ -1,5 +1,5 @@
{ {
"account.add_or_remove_from_list": "Pridaj, alebo odstráň zo zoznamov", "account.add_or_remove_from_list": "Pridaj do, alebo odober zo zoznamov",
"account.badges.bot": "Bot", "account.badges.bot": "Bot",
"account.block": "Blokuj @{name}", "account.block": "Blokuj @{name}",
"account.block_domain": "Ukry všetko z {domain}", "account.block_domain": "Ukry všetko z {domain}",

@ -149,10 +149,10 @@
"hashtag.column_header.tag_mode.none": "brez {additional}", "hashtag.column_header.tag_mode.none": "brez {additional}",
"hashtag.column_settings.select.no_options_message": "Ni najdenih predlogov", "hashtag.column_settings.select.no_options_message": "Ni najdenih predlogov",
"hashtag.column_settings.select.placeholder": "Vpiši ključnik…", "hashtag.column_settings.select.placeholder": "Vpiši ključnik…",
"hashtag.column_settings.tag_mode.all": "Vse našteto", "hashtag.column_settings.tag_mode.all": "Vse od naštetega",
"hashtag.column_settings.tag_mode.any": "Karkoli od naštetega", "hashtag.column_settings.tag_mode.any": "Karkoli od naštetega",
"hashtag.column_settings.tag_mode.none": "Nič od naštetega", "hashtag.column_settings.tag_mode.none": "Nič od naštetega",
"hashtag.column_settings.tag_toggle": "V ta stolpec vključite dodatne oznake", "hashtag.column_settings.tag_toggle": "Za ta stolpec vključi dodatne oznake",
"home.column_settings.basic": "Osnovno", "home.column_settings.basic": "Osnovno",
"home.column_settings.show_reblogs": "Pokaži spodbude", "home.column_settings.show_reblogs": "Pokaži spodbude",
"home.column_settings.show_replies": "Pokaži odgovore", "home.column_settings.show_replies": "Pokaži odgovore",
@ -161,187 +161,187 @@
"intervals.full.minutes": "{number, plural, one {# minuta} two {# minuti} few {# minute} other {# minut}}", "intervals.full.minutes": "{number, plural, one {# minuta} two {# minuti} few {# minute} other {# minut}}",
"introduction.federation.action": "Naprej", "introduction.federation.action": "Naprej",
"introduction.federation.federated.headline": "Združeno", "introduction.federation.federated.headline": "Združeno",
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.", "introduction.federation.federated.text": "Javne objave iz drugih strežnikov fediverse-a bodo prikazane v združeni časovnici.",
"introduction.federation.home.headline": "Home", "introduction.federation.home.headline": "Domov",
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!", "introduction.federation.home.text": "Objave oseb, ki jim sledite, bodo prikazane v vaši domači časovnici. Lahko sledite vsakomur na katerem koli strežniku!",
"introduction.federation.local.headline": "Local", "introduction.federation.local.headline": "Lokalno",
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.", "introduction.federation.local.text": "Javne objave ljudi na istem strežniku, se bodo prikazale na lokalni časovnici.",
"introduction.interactions.action": "Finish toot-orial!", "introduction.interactions.action": "Zaključi vadnico!",
"introduction.interactions.favourite.headline": "Favourite", "introduction.interactions.favourite.headline": "Priljubljeni",
"introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.", "introduction.interactions.favourite.text": "Tut lahko shranite za pozneje in ga vzljubite ter s tem pokažete avtorju, da vam je ta tut priljubljen.",
"introduction.interactions.reblog.headline": "Boost", "introduction.interactions.reblog.headline": "Spodbudi",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.", "introduction.interactions.reblog.text": "Tute drugih ljudi lahko delite z vašimi sledilci, tako da spodbudite tute.",
"introduction.interactions.reply.headline": "Reply", "introduction.interactions.reply.headline": "Odgovori",
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.", "introduction.interactions.reply.text": "Lahko odgovarjate na tuje in vaše tute, kar bo odgovore povezalo v pogovor.",
"introduction.welcome.action": "Let's go!", "introduction.welcome.action": "Gremo!",
"introduction.welcome.headline": "First steps", "introduction.welcome.headline": "Prvi koraki",
"introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.", "introduction.welcome.text": "Dobrodošli v fediverse-u! Čez nekaj trenutkov boste lahko oddajali sporočila in se pogovarjali s prijatelji prek različnih strežnikov. Vendar je ta strežnik {domain} poseben - gosti vaš profil, zato si zapomnite njegovo ime.",
"keyboard_shortcuts.back": "za krmarjenje nazaj", "keyboard_shortcuts.back": "pojdi nazaj",
"keyboard_shortcuts.blocked": "to open blocked users list", "keyboard_shortcuts.blocked": "odpri seznam blokiranih uporabnikov",
"keyboard_shortcuts.boost": "suniti", "keyboard_shortcuts.boost": "spodbudi",
"keyboard_shortcuts.column": "osredotočiti status v enega od stolpcev", "keyboard_shortcuts.column": "fokusiraj na status v enemu od stolpcev",
"keyboard_shortcuts.compose": "osredotočiti na sestavljanje besedila", "keyboard_shortcuts.compose": "fokusiraj na območje za sestavljanje besedila",
"keyboard_shortcuts.description": "Opis", "keyboard_shortcuts.description": "Opis",
"keyboard_shortcuts.direct": "to open direct messages column", "keyboard_shortcuts.direct": "odpri stolpec za neposredna sporočila",
"keyboard_shortcuts.down": "premakniti navzdol po seznamu", "keyboard_shortcuts.down": "premakni se navzdol po seznamu",
"keyboard_shortcuts.enter": "odpreti status", "keyboard_shortcuts.enter": "odpri status",
"keyboard_shortcuts.favourite": "to favourite", "keyboard_shortcuts.favourite": "vzljubi",
"keyboard_shortcuts.favourites": "to open favourites list", "keyboard_shortcuts.favourites": "odpri seznam priljubljenih",
"keyboard_shortcuts.federated": "to open federated timeline", "keyboard_shortcuts.federated": "odpri združeno časovnico",
"keyboard_shortcuts.heading": "Tipkovne bližnjice", "keyboard_shortcuts.heading": "Tipkovne bližnjice",
"keyboard_shortcuts.home": "to open home timeline", "keyboard_shortcuts.home": "odpri domačo časovnico",
"keyboard_shortcuts.hotkey": "Hitra tipka", "keyboard_shortcuts.hotkey": "Hitra tipka",
"keyboard_shortcuts.legend": "to display this legend", "keyboard_shortcuts.legend": "pokaži to legendo",
"keyboard_shortcuts.local": "to open local timeline", "keyboard_shortcuts.local": "odpri lokalno časovnico",
"keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.mention": "omeni avtorja",
"keyboard_shortcuts.muted": "to open muted users list", "keyboard_shortcuts.muted": "odpri seznam utišanih uporabnikov",
"keyboard_shortcuts.my_profile": "to open your profile", "keyboard_shortcuts.my_profile": "odpri svoj profil",
"keyboard_shortcuts.notifications": "to open notifications column", "keyboard_shortcuts.notifications": "odpri stolpec z obvestili",
"keyboard_shortcuts.pinned": "to open pinned toots list", "keyboard_shortcuts.pinned": "odpri seznam pripetih tutov",
"keyboard_shortcuts.profile": "to open author's profile", "keyboard_shortcuts.profile": "odpri avtorjev profil",
"keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.reply": "odgovori",
"keyboard_shortcuts.requests": "to open follow requests list", "keyboard_shortcuts.requests": "odpri seznam s prošnjami za sledenje",
"keyboard_shortcuts.search": "to focus search", "keyboard_shortcuts.search": "fokusiraj na iskanje",
"keyboard_shortcuts.start": "to open \"get started\" column", "keyboard_shortcuts.start": "odpri stolpec \"začni\"",
"keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toggle_hidden": "prikaži/skrij besedilo za CW",
"keyboard_shortcuts.toggle_sensitivity": "to show/hide media", "keyboard_shortcuts.toggle_sensitivity": "prikaži/skrij medije",
"keyboard_shortcuts.toot": "da začnete povsem nov tut", "keyboard_shortcuts.toot": "začni povsem nov tut",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.unfocus": "odfokusiraj območje za sestavljanje besedila/iskanje",
"keyboard_shortcuts.up": "to move up in the list", "keyboard_shortcuts.up": "premakni se navzgor po seznamu",
"lightbox.close": "Close", "lightbox.close": "Zapri",
"lightbox.next": "Next", "lightbox.next": "Naslednji",
"lightbox.previous": "Previous", "lightbox.previous": "Prejšnji",
"lightbox.view_context": "View context", "lightbox.view_context": "Poglej kontekst",
"lists.account.add": "Add to list", "lists.account.add": "Dodaj na seznam",
"lists.account.remove": "Remove from list", "lists.account.remove": "Odstrani s seznama",
"lists.delete": "Delete list", "lists.delete": "Izbriši seznam",
"lists.edit": "Edit list", "lists.edit": "Uredi seznam",
"lists.edit.submit": "Change title", "lists.edit.submit": "Spremeni naslov",
"lists.new.create": "Add list", "lists.new.create": "Dodaj seznam",
"lists.new.title_placeholder": "New list title", "lists.new.title_placeholder": "Nov naslov seznama",
"lists.search": "Search among people you follow", "lists.search": "Išči med ljudmi, katerim sledite",
"lists.subheading": "Your lists", "lists.subheading": "Vaši seznami",
"loading_indicator.label": "Loading...", "loading_indicator.label": "Nalaganje...",
"media_gallery.toggle_visible": "Toggle visibility", "media_gallery.toggle_visible": "Preklopi vidljivost",
"missing_indicator.label": "Not found", "missing_indicator.label": "Ni najdeno",
"missing_indicator.sublabel": "This resource could not be found", "missing_indicator.sublabel": "Tega vira ni bilo mogoče najti",
"mute_modal.hide_notifications": "Hide notifications from this user?", "mute_modal.hide_notifications": "Skrij obvestila tega uporabnika?",
"navigation_bar.apps": "Mobile apps", "navigation_bar.apps": "Mobilne aplikacije",
"navigation_bar.blocks": "Blocked users", "navigation_bar.blocks": "Blokirani uporabniki",
"navigation_bar.community_timeline": "Local timeline", "navigation_bar.community_timeline": "Lokalna časovnica",
"navigation_bar.compose": "Compose new toot", "navigation_bar.compose": "Sestavi nov tut",
"navigation_bar.direct": "Direct messages", "navigation_bar.direct": "Neposredna sporočila",
"navigation_bar.discover": "Discover", "navigation_bar.discover": "Odkrijte",
"navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.domain_blocks": "Skrite domene",
"navigation_bar.edit_profile": "Edit profile", "navigation_bar.edit_profile": "Uredi profil",
"navigation_bar.favourites": "Favourites", "navigation_bar.favourites": "Priljubljeni",
"navigation_bar.filters": "Muted words", "navigation_bar.filters": "Utišane besede",
"navigation_bar.follow_requests": "Follow requests", "navigation_bar.follow_requests": "Prošnje za sledenje",
"navigation_bar.follows_and_followers": "Follows and followers", "navigation_bar.follows_and_followers": "Sledenja in sledilci",
"navigation_bar.info": "O tem vozlišču", "navigation_bar.info": "O tem strežniku",
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", "navigation_bar.keyboard_shortcuts": "Hitre tipke",
"navigation_bar.lists": "Lists", "navigation_bar.lists": "Seznami",
"navigation_bar.logout": "Logout", "navigation_bar.logout": "Odjava",
"navigation_bar.mutes": "Muted users", "navigation_bar.mutes": "Utišani uporabniki",
"navigation_bar.personal": "Personal", "navigation_bar.personal": "Osebno",
"navigation_bar.pins": "Pripeti tuti", "navigation_bar.pins": "Pripeti tuti",
"navigation_bar.preferences": "Preferences", "navigation_bar.preferences": "Nastavitve",
"navigation_bar.profile_directory": "Profile directory", "navigation_bar.profile_directory": "Imenik profilov",
"navigation_bar.public_timeline": "Federated timeline", "navigation_bar.public_timeline": "Združena časovnica",
"navigation_bar.security": "Security", "navigation_bar.security": "Varnost",
"notification.favourite": "{name} favourited your status", "notification.favourite": "{name} je vzljubil/a vaš status",
"notification.follow": "{name} followed you", "notification.follow": "{name} vam sledi",
"notification.mention": "{name} mentioned you", "notification.mention": "{name} vas je omenil/a",
"notification.poll": "A poll you have voted in has ended", "notification.poll": "Glasovanje, v katerem ste sodelovali, se je končalo",
"notification.reblog": "{name} boosted your status", "notification.reblog": "{name} je spodbudil/a vaš status",
"notifications.clear": "Clear notifications", "notifications.clear": "Počisti obvestila",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", "notifications.clear_confirmation": "Ali ste prepričani, da želite trajno izbrisati vsa vaša obvestila?",
"notifications.column_settings.alert": "Desktop notifications", "notifications.column_settings.alert": "Namizna obvestila",
"notifications.column_settings.favourite": "Favourites:", "notifications.column_settings.favourite": "Priljubljeni:",
"notifications.column_settings.filter_bar.advanced": "Display all categories", "notifications.column_settings.filter_bar.advanced": "Prikaži vse kategorije",
"notifications.column_settings.filter_bar.category": "Quick filter bar", "notifications.column_settings.filter_bar.category": "Vrstica za hitro filtriranje",
"notifications.column_settings.filter_bar.show": "Show", "notifications.column_settings.filter_bar.show": "Pokaži",
"notifications.column_settings.follow": "New followers:", "notifications.column_settings.follow": "Novi sledilci:",
"notifications.column_settings.mention": "Mentions:", "notifications.column_settings.mention": "Omembe:",
"notifications.column_settings.poll": "Poll results:", "notifications.column_settings.poll": "Rezultati glasovanja:",
"notifications.column_settings.push": "Push notifications", "notifications.column_settings.push": "Potisna obvestila",
"notifications.column_settings.reblog": "Boosts:", "notifications.column_settings.reblog": "Spodbude:",
"notifications.column_settings.show": "Show in column", "notifications.column_settings.show": "Prikaži v stolpcu",
"notifications.column_settings.sound": "Play sound", "notifications.column_settings.sound": "Predvajaj zvok",
"notifications.filter.all": "All", "notifications.filter.all": "Vse",
"notifications.filter.boosts": "Boosts", "notifications.filter.boosts": "Spodbude",
"notifications.filter.favourites": "Favourites", "notifications.filter.favourites": "Priljubljeni",
"notifications.filter.follows": "Follows", "notifications.filter.follows": "Sledi",
"notifications.filter.mentions": "Mentions", "notifications.filter.mentions": "Omembe",
"notifications.filter.polls": "Poll results", "notifications.filter.polls": "Rezultati glasovanj",
"notifications.group": "{count} notifications", "notifications.group": "{count} obvestil",
"poll.closed": "Closed", "poll.closed": "Zaprto",
"poll.refresh": "Refresh", "poll.refresh": "Osveži",
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}", "poll.total_votes": "{count, plural,one {# glas} other {# glasov}}",
"poll.vote": "Vote", "poll.vote": "Glasuj",
"poll_button.add_poll": "Add a poll", "poll_button.add_poll": "Dodaj anketo",
"poll_button.remove_poll": "Remove poll", "poll_button.remove_poll": "Odstrani anketo",
"privacy.change": "Adjust status privacy", "privacy.change": "Prilagodi zasebnost statusa",
"privacy.direct.long": "Post to mentioned users only", "privacy.direct.long": "Objavi samo omenjenim uporabnikom",
"privacy.direct.short": "Direct", "privacy.direct.short": "Neposredno",
"privacy.private.long": "Post to followers only", "privacy.private.long": "Objavi samo sledilcem",
"privacy.private.short": "Followers-only", "privacy.private.short": "Samo sledilci",
"privacy.public.long": "Post to public timelines", "privacy.public.long": "Objavi na javne časovnice",
"privacy.public.short": "Public", "privacy.public.short": "Javno",
"privacy.unlisted.long": "Do not show in public timelines", "privacy.unlisted.long": "Ne objavi na javne časovnice",
"privacy.unlisted.short": "Unlisted", "privacy.unlisted.short": "Ni prikazano",
"regeneration_indicator.label": "Loading…", "regeneration_indicator.label": "Nalaganje…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!", "regeneration_indicator.sublabel": "Vaš domači vir se pripravlja!",
"relative_time.days": "{number}d", "relative_time.days": "{number}d",
"relative_time.hours": "{number}h", "relative_time.hours": "{number}h",
"relative_time.just_now": "now", "relative_time.just_now": "zdaj",
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancel", "reply_indicator.cancel": "Prekliči",
"report.forward": "Forward to {target}", "report.forward": "Posreduj do {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", "report.forward_hint": "Račun je iz drugega strežnika. Pošljem anonimno kopijo poročila tudi na drugi strežnik?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", "report.hint": "Poročilo bo poslano moderatorjem vašega vozlišča. Spodaj lahko navedete, zakaj prijavljate ta račun:",
"report.placeholder": "Additional comments", "report.placeholder": "Dodatni komentarji",
"report.submit": "Submit", "report.submit": "Pošlji",
"report.target": "Report {target}", "report.target": "Prijavi {target}",
"search.placeholder": "Search", "search.placeholder": "Iskanje",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Napredna oblika iskanja",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", "search_popout.tips.full_text": "Enostavno besedilo vrne statuse, ki ste jih napisali, vzljubili, spodbudili ali ste bili v njih omenjeni, kot tudi ujemajoča se uporabniška imena, prikazna imena in ključnike.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "ključnik",
"search_popout.tips.status": "status", "search_popout.tips.status": "stanje",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Enostavno besedilo vrne ujemajoča se prikazna imena, uporabniška imena in ključnike",
"search_popout.tips.user": "user", "search_popout.tips.user": "uporabnik",
"search_results.accounts": "People", "search_results.accounts": "Ljudje",
"search_results.hashtags": "Hashtags", "search_results.hashtags": "Ključniki",
"search_results.statuses": "Tuti", "search_results.statuses": "Tuti",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}", "search_results.total": "{count, number} {count, plural, one {rezultat} other {rezultatov}}",
"status.admin_account": "Open moderation interface for @{name}", "status.admin_account": "Odpri vmesnik za moderiranje za @{name}",
"status.admin_status": "Open this status in the moderation interface", "status.admin_status": "Odpri status v vmesniku za moderiranje",
"status.block": "Block @{name}", "status.block": "Blokiraj @{name}",
"status.cancel_reblog_private": "Unboost", "status.cancel_reblog_private": "Prekini spodbudo",
"status.cannot_reblog": "This post cannot be boosted", "status.cannot_reblog": "Te objave ni mogoče spodbuditi",
"status.copy": "Copy link to status", "status.copy": "Kopiraj povezavo do statusa",
"status.delete": "Delete", "status.delete": "Izbriši",
"status.detailed_status": "Detailed conversation view", "status.detailed_status": "Podroben pogled pogovora",
"status.direct": "Direct message @{name}", "status.direct": "Neposredno sporočilo @{name}",
"status.embed": "Embed", "status.embed": "Vgradi",
"status.favourite": "Favourite", "status.favourite": "Priljubljen",
"status.filtered": "Filtered", "status.filtered": "Filtrirano",
"status.load_more": "Load more", "status.load_more": "Naloži več",
"status.media_hidden": "Media hidden", "status.media_hidden": "Mediji so skriti",
"status.mention": "Mention @{name}", "status.mention": "Omeni @{name}",
"status.more": "More", "status.more": "Več",
"status.mute": "Mute @{name}", "status.mute": "Utišaj @{name}",
"status.mute_conversation": "Mute conversation", "status.mute_conversation": "Utišaj pogovor",
"status.open": "Expand this status", "status.open": "Razširi ta status",
"status.pin": "Pin on profile", "status.pin": "Pripni na profil",
"status.pinned": "Pripeti tut", "status.pinned": "Pripeti tut",
"status.read_more": "Read more", "status.read_more": "Preberi več",
"status.reblog": "Suni", "status.reblog": "Spodbudi",
"status.reblog_private": "Suni v prvotno občinstvo", "status.reblog_private": "Spodbudi izvirnemu občinstvu",
"status.reblogged_by": "{name} sunjen", "status.reblogged_by": "{name} spodbujen",
"status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.", "status.reblogs.empty": "Nihče še ni spodbudil tega tuta. Ko se bo to zgodilo, se bodo pojavili tukaj.",
"status.redraft": "Delete & re-draft", "status.redraft": "Izbriši in preoblikuj",
"status.reply": "Odgovori", "status.reply": "Odgovori",
"status.replyAll": "Odgovori na objavo", "status.replyAll": "Odgovori na objavo",
"status.report": "Prijavi @{name}", "status.report": "Prijavi @{name}",

@ -320,7 +320,7 @@
"status.block": "屏蔽 @{name}", "status.block": "屏蔽 @{name}",
"status.cancel_reblog_private": "取消转嘟", "status.cancel_reblog_private": "取消转嘟",
"status.cannot_reblog": "无法转嘟这条嘟文", "status.cannot_reblog": "无法转嘟这条嘟文",
"status.copy": "复制链接到嘟文中", "status.copy": "复制嘟文链接",
"status.delete": "删除", "status.delete": "删除",
"status.detailed_status": "对话详情", "status.detailed_status": "对话详情",
"status.direct": "发送私信给 @{name}", "status.direct": "发送私信给 @{name}",

@ -557,6 +557,7 @@
.compose-form__upload-thumbnail { .compose-form__upload-thumbnail {
border-radius: 4px; border-radius: 4px;
background-color: $base-shadow-color;
background-position: center; background-position: center;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;

@ -370,7 +370,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end end
def unsupported_media_type?(mime_type) def unsupported_media_type?(mime_type)
mime_type.present? && !(MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES).include?(mime_type) mime_type.present? && !MediaAttachment.supported_mime_types.include?(mime_type)
end end
def supported_blurhash?(blurhash) def supported_blurhash?(blurhash)
@ -380,7 +380,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
def skip_download? def skip_download?
return @skip_download if defined?(@skip_download) return @skip_download if defined?(@skip_download)
@skip_download ||= DomainBlock.find_by(domain: @account.domain)&.reject_media? @skip_download ||= DomainBlock.reject_media?(@account.domain)
end end
def reply_to_local? def reply_to_local?

@ -23,7 +23,7 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
private private
def skip_reports? def skip_reports?
DomainBlock.find_by(domain: @account.domain)&.reject_reports? DomainBlock.reject_reports?(@account.domain)
end end
def object_uris def object_uris

@ -148,7 +148,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
end end
def save_media def save_media
do_not_download = DomainBlock.find_by(domain: @account.domain)&.reject_media? do_not_download = DomainBlock.reject_media?(@account.domain)
media_attachments = [] media_attachments = []
@xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: OStatus::TagManager::XMLNS).each do |link| @xml.xpath('./xmlns:link[@rel="enclosure"]', xmlns: OStatus::TagManager::XMLNS).each do |link|
@ -176,7 +176,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
end end
def save_emojis(parent) def save_emojis(parent)
do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media? do_not_download = DomainBlock.reject_media?(parent.account.domain)
return if do_not_download return if do_not_download

@ -102,6 +102,7 @@ class Account < ApplicationRecord
scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) } scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) } scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) }
scope :popular, -> { order('account_stats.followers_count desc') } scope :popular, -> { order('account_stats.followers_count desc') }
scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) }
delegate :email, delegate :email,
:unconfirmed_email, :unconfirmed_email,
@ -110,6 +111,8 @@ class Account < ApplicationRecord
:confirmed?, :confirmed?,
:approved?, :approved?,
:pending?, :pending?,
:disabled?,
:role,
:admin?, :admin?,
:moderator?, :moderator?,
:staff?, :staff?,

@ -37,6 +37,8 @@ class AccountFilter
Account.without_suspended Account.without_suspended
when 'pending' when 'pending'
accounts_with_users.merge User.pending accounts_with_users.merge User.pending
when 'disabled'
accounts_with_users.merge User.disabled
when 'silenced' when 'silenced'
Account.silenced Account.silenced
when 'suspended' when 'suspended'

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'mime/types' require 'mime/types/columnar'
module Attachmentable module Attachmentable
extend ActiveSupport::Concern extend ActiveSupport::Concern
@ -10,10 +10,21 @@ module Attachmentable
included do included do
before_post_process :set_file_extensions before_post_process :set_file_extensions
before_post_process :check_image_dimensions before_post_process :check_image_dimensions
before_post_process :set_file_content_type
end end
private private
def set_file_content_type
self.class.attachment_definitions.each_key do |attachment_name|
attachment = send(attachment_name)
next if attachment.blank? || attachment.queued_for_write[:original].blank?
attachment.instance_write :content_type, calculated_content_type(attachment)
end
end
def set_file_extensions def set_file_extensions
self.class.attachment_definitions.each_key do |attachment_name| self.class.attachment_definitions.each_key do |attachment_name|
attachment = send(attachment_name) attachment = send(attachment_name)
@ -47,4 +58,10 @@ module Attachmentable
extension extension
end end
def calculated_content_type(attachment)
Paperclip.run('file', '-b --mime :file', file: attachment.queued_for_write[:original].path).split(/[:;\s]+/).first.chomp
rescue Terrapin::CommandLineError
''
end
end end

@ -13,6 +13,20 @@ module UserRoles
admin? || moderator? admin? || moderator?
end end
def role=(value)
case value
when 'admin'
self.admin = true
self.moderator = false
when 'moderator'
self.admin = false
self.moderator = true
else
self.admin = false
self.moderator = false
end
end
def role def role
if admin? if admin?
'admin' 'admin'

@ -39,6 +39,7 @@ class CustomEmoji < ApplicationRecord
scope :local, -> { where(domain: nil) } scope :local, -> { where(domain: nil) }
scope :remote, -> { where.not(domain: nil) } scope :remote, -> { where.not(domain: nil) }
scope :alphabetic, -> { order(domain: :asc, shortcode: :asc) } scope :alphabetic, -> { order(domain: :asc, shortcode: :asc) }
scope :by_domain_and_subdomains, ->(domain) { where(domain: domain).or(where(arel_table[:domain].matches('%.' + domain))) }
remotable_attachment :image, LIMIT remotable_attachment :image, LIMIT

@ -24,14 +24,41 @@ class DomainBlock < ApplicationRecord
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
def self.blocked?(domain) class << self
where(domain: domain, severity: :suspend).exists? def suspend?(domain)
!!rule_for(domain)&.suspend?
end
def silence?(domain)
!!rule_for(domain)&.silence?
end
def reject_media?(domain)
!!rule_for(domain)&.reject_media?
end
def reject_reports?(domain)
!!rule_for(domain)&.reject_reports?
end
alias blocked? suspend?
def rule_for(domain)
return if domain.blank?
uri = Addressable::URI.new.tap { |u| u.host = domain.gsub(/[\/]/, '') }
segments = uri.normalized_host.split('.')
variants = segments.map.with_index { |_, i| segments[i..-1].join('.') }
where(domain: variants[0..-2]).order(Arel.sql('char_length(domain) desc')).first
end
end end
def stricter_than?(other_block) def stricter_than?(other_block)
return true if suspend? return true if suspend?
return false if other_block.suspend? && (silence? || noop?) return false if other_block.suspend? && (silence? || noop?)
return false if other_block.silence? && noop? return false if other_block.silence? && noop?
(reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports) (reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports)
end end

@ -8,15 +8,11 @@ class Instance
def initialize(resource) def initialize(resource)
@domain = resource.domain @domain = resource.domain
@accounts_count = resource.is_a?(DomainBlock) ? nil : resource.accounts_count @accounts_count = resource.is_a?(DomainBlock) ? nil : resource.accounts_count
@domain_block = resource.is_a?(DomainBlock) ? resource : DomainBlock.find_by(domain: domain) @domain_block = resource.is_a?(DomainBlock) ? resource : DomainBlock.rule_for(domain)
end end
def cached_sample_accounts def countable?
Rails.cache.fetch("#{cache_key}/sample_accounts", expires_in: 12.hours) { Account.where(domain: domain).searchable.joins(:account_stat).popular.limit(3) } @accounts_count.present?
end
def cached_accounts_count
@accounts_count || Rails.cache.fetch("#{cache_key}/count", expires_in: 12.hours) { Account.where(domain: domain).count }
end end
def to_param def to_param

@ -24,16 +24,16 @@
class MediaAttachment < ApplicationRecord class MediaAttachment < ApplicationRecord
self.inheritance_column = nil self.inheritance_column = nil
enum type: [:image, :gifv, :video, :audio, :unknown] enum type: [:image, :gifv, :video, :unknown, :audio]
IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].freeze IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'].freeze
VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v', '.mov'].freeze VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v', '.mov'].freeze
AUDIO_FILE_EXTENSIONS = ['.mp3', '.m4a', '.wav', '.ogg'].freeze AUDIO_FILE_EXTENSIONS = ['.ogg', '.oga', '.mp3', '.m4a', '.wav', '.flac', '.opus'].freeze
IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime'].freeze VIDEO_MIME_TYPES = ['video/webm', 'video/mp4', 'video/quicktime', 'video/ogg'].freeze
VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze VIDEO_CONVERTIBLE_MIME_TYPES = ['video/webm', 'video/quicktime'].freeze
AUDIO_MIME_TYPES = ['audio/mpeg', 'audio/mp4', 'audio/vnd.wav', 'audio/wav', 'audio/x-wav', 'audio/x-wave', 'audio/ogg',].freeze AUDIO_MIME_TYPES = ['audio/wave', 'audio/wav', 'audio/x-wav', 'audio/x-wave', 'audio/vdn.wav', 'audio/x-pn-wave', 'audio/ogg', 'audio/mpeg', 'audio/mp3', 'audio/mp4', 'audio/webm', 'audio/flac'].freeze
BLURHASH_OPTIONS = { BLURHASH_OPTIONS = {
x_comp: 4, x_comp: 4,
@ -53,22 +53,6 @@ class MediaAttachment < ApplicationRecord
}, },
}.freeze }.freeze
AUDIO_STYLES = {
original: {
format: 'mp4',
convert_options: {
output: {
filter_complex: '"[0:a]compand,showwaves=s=640x360:mode=line,format=yuv420p[v]"',
map: '"[v]" -map 0:a',
threads: 2,
vcodec: 'libx264',
acodec: 'aac',
movflags: '+faststart',
},
},
},
}.freeze
VIDEO_STYLES = { VIDEO_STYLES = {
small: { small: {
convert_options: { convert_options: {
@ -83,8 +67,21 @@ class MediaAttachment < ApplicationRecord
}, },
}.freeze }.freeze
AUDIO_STYLES = {
original: {
format: 'mp3',
content_type: 'audio/mpeg',
convert_options: {
output: {
'q:a' => 2,
},
},
},
}.freeze
VIDEO_FORMAT = { VIDEO_FORMAT = {
format: 'mp4', format: 'mp4',
content_type: 'video/mp4',
convert_options: { convert_options: {
output: { output: {
'loglevel' => 'fatal', 'loglevel' => 'fatal',
@ -101,6 +98,11 @@ class MediaAttachment < ApplicationRecord
}, },
}.freeze }.freeze
VIDEO_CONVERTED_STYLES = {
small: VIDEO_STYLES[:small],
original: VIDEO_FORMAT,
}.freeze
IMAGE_LIMIT = (ENV['MAX_IMAGE_SIZE'] || 8.megabytes).to_i IMAGE_LIMIT = (ENV['MAX_IMAGE_SIZE'] || 8.megabytes).to_i
VIDEO_LIMIT = (ENV['MAX_VIDEO_SIZE'] || 40.megabytes).to_i VIDEO_LIMIT = (ENV['MAX_VIDEO_SIZE'] || 40.megabytes).to_i
@ -114,8 +116,8 @@ class MediaAttachment < ApplicationRecord
convert_options: { all: '-quality 90 -strip' } convert_options: { all: '-quality 90 -strip' }
validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES
validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :video_or_gifv? validates_attachment_size :file, less_than: IMAGE_LIMIT, unless: :larger_media_format?
validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :video_or_gifv? validates_attachment_size :file, less_than: VIDEO_LIMIT, if: :larger_media_format?
remotable_attachment :file, VIDEO_LIMIT remotable_attachment :file, VIDEO_LIMIT
include Attachmentable include Attachmentable
@ -138,8 +140,12 @@ class MediaAttachment < ApplicationRecord
file.blank? && remote_url.present? file.blank? && remote_url.present?
end end
def video_or_gifv? def larger_media_format?
video? || gifv? video? || gifv? || audio?
end
def audio_or_video?
audio? || video?
end end
def to_param def to_param
@ -171,37 +177,37 @@ class MediaAttachment < ApplicationRecord
before_save :set_meta before_save :set_meta
class << self class << self
def supported_mime_types
IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES
end
def supported_file_extensions
IMAGE_FILE_EXTENSIONS + VIDEO_FILE_EXTENSIONS + AUDIO_FILE_EXTENSIONS
end
private private
def file_styles(f) def file_styles(f)
if f.instance.file_content_type == 'image/gif' if f.instance.file_content_type == 'image/gif' || VIDEO_CONVERTIBLE_MIME_TYPES.include?(f.instance.file_content_type)
{ VIDEO_CONVERTED_STYLES
small: IMAGE_STYLES[:small], elsif IMAGE_MIME_TYPES.include?(f.instance.file_content_type)
original: VIDEO_FORMAT,
}
elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type
IMAGE_STYLES IMAGE_STYLES
elsif AUDIO_MIME_TYPES.include? f.instance.file_content_type elsif VIDEO_MIME_TYPES.include?(f.instance.file_content_type)
AUDIO_STYLES
elsif VIDEO_CONVERTIBLE_MIME_TYPES.include?(f.instance.file_content_type)
{
small: VIDEO_STYLES[:small],
original: VIDEO_FORMAT,
}
else
VIDEO_STYLES VIDEO_STYLES
else
AUDIO_STYLES
end end
end end
def file_processors(f) def file_processors(f)
if f.file_content_type == 'image/gif' if f.file_content_type == 'image/gif'
[:gif_transcoder, :blurhash_transcoder] [:gif_transcoder, :blurhash_transcoder]
elsif VIDEO_MIME_TYPES.include? f.file_content_type elsif VIDEO_MIME_TYPES.include?(f.file_content_type)
[:video_transcoder, :blurhash_transcoder] [:video_transcoder, :blurhash_transcoder, :type_corrector]
elsif AUDIO_MIME_TYPES.include? f.file_content_type elsif AUDIO_MIME_TYPES.include?(f.file_content_type)
[:audio_transcoder] [:transcoder, :type_corrector]
else else
[:lazy_thumbnail, :blurhash_transcoder] [:lazy_thumbnail, :blurhash_transcoder, :type_corrector]
end end
end end
end end
@ -224,7 +230,15 @@ class MediaAttachment < ApplicationRecord
end end
def set_type_and_extension def set_type_and_extension
self.type = VIDEO_MIME_TYPES.include?(file_content_type) ? :video : AUDIO_MIME_TYPES.include?(file_content_type) ? :audio : :image self.type = begin
if VIDEO_MIME_TYPES.include?(file_content_type)
:video
elsif AUDIO_MIME_TYPES.include?(file_content_type)
:audio
else
:image
end
end
end end
def set_meta def set_meta
@ -267,7 +281,7 @@ class MediaAttachment < ApplicationRecord
frame_rate: movie.frame_rate, frame_rate: movie.frame_rate,
duration: movie.duration, duration: movie.duration,
bitrate: movie.bitrate, bitrate: movie.bitrate,
} }.compact
end end
def reset_parent_cache def reset_parent_cache

@ -17,6 +17,8 @@
# #
class Report < ApplicationRecord class Report < ApplicationRecord
include Paginable
belongs_to :account belongs_to :account
belongs_to :target_account, class_name: 'Account' belongs_to :target_account, class_name: 'Account'
belongs_to :action_taken_by_account, class_name: 'Account', optional: true belongs_to :action_taken_by_account, class_name: 'Account', optional: true
@ -26,6 +28,7 @@ class Report < ApplicationRecord
scope :unresolved, -> { where(action_taken: false) } scope :unresolved, -> { where(action_taken: false) }
scope :resolved, -> { where(action_taken: true) } scope :resolved, -> { where(action_taken: true) }
scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].each_with_object({}) { |k, h| h[k] = { user: [:invite_request, :invite] } }) }
validates :comment, length: { maximum: 1000 } validates :comment, length: { maximum: 1000 }

@ -9,9 +9,11 @@ class ReportFilter
def results def results
scope = Report.unresolved scope = Report.unresolved
params.each do |key, value| params.each do |key, value|
scope = scope.merge scope_for(key, value) scope = scope.merge scope_for(key, value)
end end
scope scope
end end

@ -87,6 +87,7 @@ class User < ApplicationRecord
scope :approved, -> { where(approved: true) } scope :approved, -> { where(approved: true) }
scope :confirmed, -> { where.not(confirmed_at: nil) } scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :enabled, -> { where(disabled: false) } scope :enabled, -> { where(disabled: false) }
scope :disabled, -> { where(disabled: true) }
scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) } scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended_at: nil }) } scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended_at: nil }) }
scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) } scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }

@ -76,7 +76,7 @@ class InitialStateSerializer < ActiveModel::Serializer
end end
def media_attachments def media_attachments
{ accept_content_types: MediaAttachment::IMAGE_FILE_EXTENSIONS + MediaAttachment::VIDEO_FILE_EXTENSIONS + MediaAttachment::AUDIO_FILE_EXTENSIONS + MediaAttachment::IMAGE_MIME_TYPES + MediaAttachment::VIDEO_MIME_TYPES + MediaAttachment::AUDIO_MIME_TYPES } { accept_content_types: MediaAttachment.supported_file_extensions + MediaAttachment.supported_mime_types }
end end
private private

@ -0,0 +1,77 @@
# frozen_string_literal: true
class REST::Admin::AccountSerializer < ActiveModel::Serializer
attributes :id, :username, :domain, :created_at,
:email, :ip, :role, :confirmed, :suspended,
:silenced, :disabled, :approved, :locale,
:invite_request
attribute :created_by_application_id, if: :created_by_application?
attribute :invited_by_account_id, if: :invited?
has_one :account, serializer: REST::AccountSerializer
def id
object.id.to_s
end
def email
object.user_email
end
def ip
object.user_current_sign_in_ip.to_s.presence
end
def role
object.user_role
end
def suspended
object.suspended?
end
def silenced
object.silenced?
end
def confirmed
object.user_confirmed?
end
def disabled
object.user_disabled?
end
def approved
object.user_approved?
end
def account
object
end
def locale
object.user_locale
end
def created_by_application_id
object.user&.created_by_application_id&.to_s&.presence
end
def invite_request
object.user&.invite_request&.text
end
def invited_by_account_id
object.user&.invite&.user&.account_id&.to_s&.presence
end
def invited?
object.user&.invited?
end
def created_by_application?
object.user&.created_by_application_id&.present?
end
end

@ -0,0 +1,16 @@
# frozen_string_literal: true
class REST::Admin::ReportSerializer < ActiveModel::Serializer
attributes :id, :action_taken, :comment, :created_at, :updated_at
has_one :account, serializer: REST::Admin::AccountSerializer
has_one :target_account, serializer: REST::Admin::AccountSerializer
has_one :assigned_account, serializer: REST::Admin::AccountSerializer
has_one :action_taken_by_account, serializer: REST::Admin::AccountSerializer
has_many :statuses, serializer: REST::StatusSerializer
def id
object.id.to_s
end
end

@ -3,9 +3,9 @@
class REST::InstanceSerializer < ActiveModel::Serializer class REST::InstanceSerializer < ActiveModel::Serializer
include RoutingHelper include RoutingHelper
attributes :uri, :title, :description, :email, attributes :uri, :title, :short_description, :description, :email,
:version, :urls, :stats, :thumbnail, :max_toot_chars, :poll_limits, :version, :urls, :stats, :thumbnail, :max_toot_chars, :poll_limits,
:languages, :registrations :languages, :registrations, :approval_required
has_one :contact_account, serializer: REST::AccountSerializer has_one :contact_account, serializer: REST::AccountSerializer
@ -19,6 +19,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
Setting.site_title Setting.site_title
end end
def short_description
Setting.site_short_description
end
def description def description
Setting.site_description Setting.site_description
end end
@ -68,6 +72,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode Setting.registrations_mode != 'none' && !Rails.configuration.x.single_user_mode
end end
def approval_required
Setting.registrations_mode == 'approved'
end
private private
def instance_presenter def instance_presenter

@ -205,7 +205,7 @@ class ActivityPub::ProcessAccountService < BaseService
def domain_block def domain_block
return @domain_block if defined?(@domain_block) return @domain_block if defined?(@domain_block)
@domain_block = DomainBlock.find_by(domain: @domain) @domain_block = DomainBlock.rule_for(@domain)
end end
def key_changed? def key_changed?

@ -76,7 +76,7 @@ class BlockDomainService < BaseService
end end
def blocked_domain_accounts def blocked_domain_accounts
Account.where(domain: blocked_domain) Account.by_domain_and_subdomains(blocked_domain)
end end
def media_from_blocked_domain def media_from_blocked_domain
@ -84,6 +84,6 @@ class BlockDomainService < BaseService
end end
def emojis_from_blocked_domains def emojis_from_blocked_domains
CustomEmoji.where(domain: blocked_domain) CustomEmoji.by_domain_and_subdomains(blocked_domain)
end end
end end

@ -107,7 +107,7 @@ class PostStatusService < BaseService
@media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i)) @media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:video?) raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:audio_or_video?)
end end
def language_from_option(str) def language_from_option(str)

@ -146,7 +146,7 @@ class ResolveAccountService < BaseService
def domain_block def domain_block
return @domain_block if defined?(@domain_block) return @domain_block if defined?(@domain_block)
@domain_block = DomainBlock.find_by(domain: @domain) @domain_block = DomainBlock.rule_for(@domain)
end end
def atom_url def atom_url

@ -14,7 +14,8 @@ class UnblockDomainService < BaseService
end end
def blocked_accounts def blocked_accounts
scope = Account.where(domain: domain_block.domain) scope = Account.by_domain_and_subdomains(domain_block.domain)
if domain_block.silence? if domain_block.silence?
scope.where(silenced_at: @domain_block.created_at) scope.where(silenced_at: @domain_block.created_at)
else else

@ -26,7 +26,7 @@ class UpdateRemoteProfileService < BaseService
account.note = remote_profile.note || '' account.note = remote_profile.note || ''
account.locked = remote_profile.locked? account.locked = remote_profile.locked?
if !account.suspended? && !DomainBlock.find_by(domain: account.domain)&.reject_media? if !account.suspended? && !DomainBlock.reject_media?(account.domain)
if remote_profile.avatar.present? if remote_profile.avatar.present?
account.avatar_remote_url = remote_profile.avatar account.avatar_remote_url = remote_profile.avatar
else else
@ -46,7 +46,7 @@ class UpdateRemoteProfileService < BaseService
end end
def save_emojis def save_emojis
do_not_download = DomainBlock.find_by(domain: account.domain)&.reject_media? do_not_download = DomainBlock.reject_media?(account.domain)
return if do_not_download return if do_not_download

@ -33,21 +33,22 @@
%h4 %h4
= instance.domain = instance.domain
%small %small
= t('admin.instances.known_accounts', count: instance.cached_accounts_count)
- if instance.domain_block - if instance.domain_block
- first_item = true
- if !instance.domain_block.noop? - if !instance.domain_block.noop?
&bull;
= t("admin.domain_blocks.severity.#{instance.domain_block.severity}") = t("admin.domain_blocks.severity.#{instance.domain_block.severity}")
- first_item = false
- if instance.domain_block.reject_media? - if instance.domain_block.reject_media?
&bull; - unless first_item
&bull;
= t('admin.domain_blocks.rejecting_media') = t('admin.domain_blocks.rejecting_media')
- first_item = false
- if instance.domain_block.reject_reports? - if instance.domain_block.reject_reports?
&bull; - unless first_item
&bull;
= t('admin.domain_blocks.rejecting_reports') = t('admin.domain_blocks.rejecting_reports')
- else
.avatar-stack = t('admin.accounts.no_limits_imposed')
- instance.cached_sample_accounts.each do |account| - if instance.countable?
= image_tag current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url, width: 48, height: 48, alt: '', class: 'account__avatar' .trends__item__current{ title: t('admin.instances.known_accounts', count: instance.accounts_count) }= number_to_human instance.accounts_count, strip_insignificant_zeros: true
= paginate paginated_instances = paginate paginated_instances

@ -27,7 +27,7 @@
= render partial: 'stream_entries/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay } = render partial: 'stream_entries/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
- if !status.media_attachments.empty? - if !status.media_attachments.empty?
- if status.media_attachments.first.video? - if status.media_attachments.first.audio_or_video?
- video = status.media_attachments.first - video = status.media_attachments.first
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 670, height: 380, detailed: true, inline: true, alt: video.description do
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments } = render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }

@ -31,7 +31,7 @@
= render partial: 'stream_entries/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay } = render partial: 'stream_entries/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: autoplay }
- if !status.media_attachments.empty? - if !status.media_attachments.empty?
- if status.media_attachments.first.video? - if status.media_attachments.first.audio_or_video?
- video = status.media_attachments.first - video = status.media_attachments.first
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description do = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), blurhash: video.blurhash, sensitive: !current_account&.user&.show_all_media? && status.sensitive? || current_account&.user&.hide_all_media?, width: 610, height: 343, inline: true, alt: video.description do
= render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments } = render partial: 'stream_entries/attachment_list', locals: { attachments: status.media_attachments }

@ -10,7 +10,7 @@ require_relative '../app/lib/exceptions'
require_relative '../lib/paperclip/lazy_thumbnail' require_relative '../lib/paperclip/lazy_thumbnail'
require_relative '../lib/paperclip/gif_transcoder' require_relative '../lib/paperclip/gif_transcoder'
require_relative '../lib/paperclip/video_transcoder' require_relative '../lib/paperclip/video_transcoder'
require_relative '../lib/paperclip/audio_transcoder' require_relative '../lib/paperclip/type_corrector'
require_relative '../lib/mastodon/snowflake' require_relative '../lib/mastodon/snowflake'
require_relative '../lib/mastodon/version' require_relative '../lib/mastodon/version'
require_relative '../lib/devise/ldap_authenticatable' require_relative '../lib/devise/ldap_authenticatable'

@ -82,7 +82,13 @@ Doorkeeper.configure do
:'read:search', :'read:search',
:'read:statuses', :'read:statuses',
:follow, :follow,
:push :push,
:'admin:read',
:'admin:read:accounts',
:'admin:read:reports',
:'admin:write',
:'admin:write:accounts',
:'admin:write:reports'
# Change the way client credentials are retrieved from the request object. # Change the way client credentials are retrieved from the request object.
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then

@ -1 +1,13 @@
---
fi: fi:
activerecord:
attributes:
poll:
expires_at: Määräaika
options: Vaihtoehdot
errors:
models:
account:
attributes:
username:
invalid: Vain kirjaimia, numeroita ja alleviivoja

@ -1,12 +1,16 @@
--- ---
it: it:
activerecord: activerecord:
attributes:
poll:
expires_at: Scadenza
options: Scelte
errors: errors:
models: models:
account: account:
attributes: attributes:
username: username:
invalid: solo lettere, numeri e trattino basso invalid: solo lettere, numeri e trattini bassi
status: status:
attributes: attributes:
reblog: reblog:

@ -12,6 +12,7 @@ it:
last_attempt: Hai un altro tentativo prima che il tuo account venga bloccato. last_attempt: Hai un altro tentativo prima che il tuo account venga bloccato.
locked: Il tuo account è stato bloccato. locked: Il tuo account è stato bloccato.
not_found_in_database: "%{authentication_keys} o password invalida." not_found_in_database: "%{authentication_keys} o password invalida."
pending: Il tuo account è ancora in fase di approvazione.
timeout: La tua sessione è terminata. Per favore, effettua l'accesso o registrati per continuare. timeout: La tua sessione è terminata. Per favore, effettua l'accesso o registrati per continuare.
unauthenticated: Devi effettuare l'accesso o registrarti per continuare. unauthenticated: Devi effettuare l'accesso o registrarti per continuare.
unconfirmed: Devi confermare il tuo indirizzo email per continuare. unconfirmed: Devi confermare il tuo indirizzo email per continuare.
@ -20,6 +21,7 @@ it:
action: Verifica indirizzo email action: Verifica indirizzo email
action_with_app: Conferma e torna a %{app} action_with_app: Conferma e torna a %{app}
explanation: Hai creato un account su %{host} con questo indirizzo email. Sei lonatno solo un clic dall'attivarlo. Se non sei stato tu, per favore ignora questa email. explanation: Hai creato un account su %{host} con questo indirizzo email. Sei lonatno solo un clic dall'attivarlo. Se non sei stato tu, per favore ignora questa email.
explanation_when_pending: Hai richiesto un invito a %{host} con questo indirizzo email. Una volta confermato il tuo indirizzo e-mail, analizzeremo la tua richiesta. Non potrai eseguire l'accesso fino a quel momento. Se la tua richiesta sarà rifiutata, i tuoi dati saranno rimossi, quindi nessun'altra azione ti sarà richiesta. Se non fossi stato tu, per favore ignora questa email.
extra_html: Per favore controlla<a href="%{terms_path}">le regole del server</a> e <a href="%{policy_path}">i nostri termini di servizio</a>. extra_html: Per favore controlla<a href="%{terms_path}">le regole del server</a> e <a href="%{policy_path}">i nostri termini di servizio</a>.
subject: 'Mastodon: Istruzioni di conferma per %{instance}' subject: 'Mastodon: Istruzioni di conferma per %{instance}'
title: Verifica indirizzo email title: Verifica indirizzo email
@ -60,6 +62,7 @@ it:
signed_up: Benvenuto! Ti sei registrato con successo. signed_up: Benvenuto! Ti sei registrato con successo.
signed_up_but_inactive: Ti sei registrato con successo. Purtroppo però non possiamo farti accedere perché non hai ancora attivato il tuo account. signed_up_but_inactive: Ti sei registrato con successo. Purtroppo però non possiamo farti accedere perché non hai ancora attivato il tuo account.
signed_up_but_locked: Ti sei registrato con successo. Purtroppo però non possiamo farti accedere perché il tuo account è bloccato. signed_up_but_locked: Ti sei registrato con successo. Purtroppo però non possiamo farti accedere perché il tuo account è bloccato.
signed_up_but_pending: Un messaggio con un collegamento per la conferma è stato inviato al tuo indirizzo email. Dopo aver cliccato il collegamento, esamineremo la tua richiesta. Ti sarà notificato se verrà approvata.
signed_up_but_unconfirmed: Un messaggio con un link di conferma è stato inviato al tuo indirizzo email. Per favore, visita il link per attivare il tuo account. signed_up_but_unconfirmed: Un messaggio con un link di conferma è stato inviato al tuo indirizzo email. Per favore, visita il link per attivare il tuo account.
update_needs_confirmation: Hai aggiornato correttamente il tuo account, ma abbiamo bisogno di verificare il tuo nuovo indirizzo email. Per favore, controlla la posta in arrivo e visita il link di conferma per verificare il tuo indirizzo email. update_needs_confirmation: Hai aggiornato correttamente il tuo account, ma abbiamo bisogno di verificare il tuo nuovo indirizzo email. Per favore, controlla la posta in arrivo e visita il link di conferma per verificare il tuo indirizzo email.
updated: Il tuo account è stato aggiornato con successo. updated: Il tuo account è stato aggiornato con successo.

@ -114,6 +114,12 @@ ca:
application: application:
title: OAuth autorització requerida title: OAuth autorització requerida
scopes: scopes:
admin:read: llegir totes les dades en el servidor
admin:read:accounts: llegir l'informació sensible de tots els comptes
admin:read:reports: llegir l'informació sensible de tots els informes i comptes reportats
admin:write: modificar totes les dades en el servidor
admin:write:accounts: fer l'acció de moderació en els comptes
admin:write:reports: fer l'acció de moderació en els informes
follow: seguir, blocar, desblocar i deixar de seguir comptes follow: seguir, blocar, desblocar i deixar de seguir comptes
push: rebre notificacions push del teu compte push: rebre notificacions push del teu compte
read: llegir les dades del teu compte read: llegir les dades del teu compte

@ -114,6 +114,12 @@ co:
application: application:
title: Auturizazione OAuth riquestata title: Auturizazione OAuth riquestata
scopes: scopes:
admin:read: leghje tutti i dati nant'à u servore
admin:read:accounts: leghje i cuntinuti sensibili di tutti i conti
admin:read:reports: leghje i cuntinuti sensibili di tutti i rapporti è conti signalati
admin:write: mudificà tutti i dati nant'à u servore
admin:write:accounts: realizà azzione di muderazione nant'à i conti
admin:write:reports: realizà azzione di muderazione nant'à i rapporti
follow: Mudificà rilazione trà i conti follow: Mudificà rilazione trà i conti
push: Riceve e vostre nutificazione push push: Riceve e vostre nutificazione push
read: leghje tutte linfurmazioni di u vostru contu read: leghje tutte linfurmazioni di u vostru contu

@ -114,6 +114,12 @@ cs:
application: application:
title: Je požadována autorizace OAuth title: Je požadována autorizace OAuth
scopes: scopes:
admin:read: číst všechna data na serveru
admin:read:accounts: číst citlivé informace všech účtů
admin:read:reports: číst citlivé informace všech nahlášení a nahlášených účtů
admin:write: měnit všechna data na serveru
admin:write:accounts: provádět moderátorské akce s účty
admin:write:reports: provádět moderátorské akce s nahlášeními
follow: upravovat vztahy mezi profily follow: upravovat vztahy mezi profily
push: přijímat vaše push oznámení push: přijímat vaše push oznámení
read: vidět všechna data vašeho účtu read: vidět všechna data vašeho účtu

@ -114,6 +114,12 @@ de:
application: application:
title: OAuth-Autorisierung nötig title: OAuth-Autorisierung nötig
scopes: scopes:
admin:read: alle Daten auf dem Server lesen
admin:read:accounts: sensible Daten aller Konten lesen
admin:read:reports: sensible Daten aller Meldungen und gemeldeten Konten lesen
admin:write: alle Daten auf dem Server ändern
admin:write:accounts: Moderationsaktionen auf Konten ausführen
admin:write:reports: Moderationsaktionen auf Meldungen ausführen
follow: Kontenbeziehungen verändern follow: Kontenbeziehungen verändern
push: deine Push-Benachrichtigungen erhalten push: deine Push-Benachrichtigungen erhalten
read: all deine Daten lesen read: all deine Daten lesen

@ -114,6 +114,12 @@ el:
application: application:
title: Απαιτείται έγκριση OAuth title: Απαιτείται έγκριση OAuth
scopes: scopes:
admin:read: ανάγνωση δεδομένων στον διακομιστή
admin:read:accounts: ανάγνωση ευαίσθητων πληροφοριών όλων των λογαριασμών
admin:read:reports: ανάγνωση ευαίσθητων πληροφοριών όλων των καταγγελιών και των καταγγελλομένων λογαριασμών
admin:write: αλλαγή δεδομένων στον διακομιστή
admin:write:accounts: εκτέλεση διαχειριστικών ενεργειών σε λογαριασμούς
admin:write:reports: εκτέλεση διαχειριστικών ενεργειών σε καταγγελίες
follow: να αλλάζει τις σχέσεις με λογαριασμούς follow: να αλλάζει τις σχέσεις με λογαριασμούς
push: να λαμβάνει τις ειδοποιήσεις σου push: να λαμβάνει τις ειδοποιήσεις σου
read: να διαβάζει όλα τα στοιχεία του λογαριασμού σου read: να διαβάζει όλα τα στοιχεία του λογαριασμού σου

@ -114,6 +114,12 @@ en:
application: application:
title: OAuth authorization required title: OAuth authorization required
scopes: scopes:
admin:read: read all data on the server
admin:read:accounts: read sensitive information of all accounts
admin:read:reports: read sensitive information of all reports and reported accounts
admin:write: modify all data on the server
admin:write:accounts: perform moderation actions on accounts
admin:write:reports: perform moderation actions on reports
follow: modify account relationships follow: modify account relationships
push: receive your push notifications push: receive your push notifications
read: read all your account's data read: read all your account's data

@ -114,6 +114,12 @@ gl:
application: application:
title: Precisa autorización OAuth title: Precisa autorización OAuth
scopes: scopes:
admin:read: ler todos os datos no servidor
admin:read:accounts: ler información sensible de todas as contas
admin:read:reports: ler información sensible de todos os informes e contas reportadas
admin:write: modificar todos os datos no servidor
admin:write:accounts: executar accións de moderación nas contas
admin:write:reports: executar accións de moderación nos informes
follow: modificar as relacións da conta follow: modificar as relacións da conta
push: recibir notificacións push push: recibir notificacións push
read: ler todos os datos da súa conta read: ler todos os datos da súa conta

@ -36,9 +36,11 @@ it:
scopes: Dividi gli scopes con spazi. Lascia vuoto per utilizzare gli scopes di default. scopes: Dividi gli scopes con spazi. Lascia vuoto per utilizzare gli scopes di default.
index: index:
application: Applicazione application: Applicazione
callback_url: URL di callback
delete: Elimina delete: Elimina
name: Nome name: Nome
new: Nuova applicazione new: Nuova applicazione
scopes: Visibilità
show: Mostra show: Mostra
title: Le tue applicazioni title: Le tue applicazioni
new: new:
@ -112,6 +114,12 @@ it:
application: application:
title: Autorizzazione OAuth richiesta title: Autorizzazione OAuth richiesta
scopes: scopes:
admin:read: leggere tutti i dati dal server
admin:read:accounts: leggere dati sensibili di tutti gli account
admin:read:reports: leggere dati sensibili di tutte le segnalazioni e gli account segnalati
admin:write: modificare tutti i dati sul server
admin:write:accounts: eseguire azioni di moderazione sugli account
admin:write:reports: eseguire azioni di moderazione sulle segnalazioni
follow: modificare relazioni tra account follow: modificare relazioni tra account
push: ricevere le tue notifiche push push: ricevere le tue notifiche push
read: leggere tutte le informazioni del tuo account read: leggere tutte le informazioni del tuo account

@ -114,6 +114,12 @@ ja:
application: application:
title: OAuth認証 title: OAuth認証
scopes: scopes:
admin:read: サーバーのすべてのデータの読み取り
admin:read:accounts: すべてのアカウントの機密情報の読み取り
admin:read:reports: すべての通報と通報されたアカウントの機密情報の読み取り
admin:write: サーバーのすべてのデータの変更
admin:write:accounts: アカウントに対するアクションの実行
admin:write:reports: 通報に対するアクションの実行
follow: アカウントのつながりを変更 follow: アカウントのつながりを変更
push: プッシュ通知の受信 push: プッシュ通知の受信
read: アカウントのすべてのデータの読み取り read: アカウントのすべてのデータの読み取り

@ -114,6 +114,12 @@ pl:
application: application:
title: Uwierzytelnienie OAuth jest wymagane title: Uwierzytelnienie OAuth jest wymagane
scopes: scopes:
admin:read: odczytaj wszystkie dane na serwerze
admin:read:accounts: odczytaj wrażliwe informacje na wszystkich kontach
admin:read:reports: odczytaj wrażliwe informacje ze wszystkich zgłoszeń oraz zgłoszonych kont
admin:write: zmodyfikuj wszystkie dane na serwerze
admin:write:accounts: wykonaj działania moderacyjne na kontach
admin:write:reports: wykonaj działania moderacyjne na zgłoszeniach
follow: możliwość śledzenia kont follow: możliwość śledzenia kont
push: otrzymywanie powiadomień push dla Twojego konta push: otrzymywanie powiadomień push dla Twojego konta
read: możliwość odczytu wszystkich danych konta read: możliwość odczytu wszystkich danych konta

@ -114,6 +114,12 @@ sl:
application: application:
title: Potrebna je OAuth pooblastitev title: Potrebna je OAuth pooblastitev
scopes: scopes:
admin:read: preberi vse podatke na strežniku
admin:read:accounts: preberi občutljive informacije vseh računov
admin:read:reports: preberi občutljive informacije vseh prijav in prijavljenih računov
admin:write: spremeni vse podatke na strežniku
admin:write:accounts: izvedi moderirana dejanja na računih
admin:write:reports: izvedi moderirana dejanja na prijavah
follow: spremeni razmerja med računi follow: spremeni razmerja med računi
push: prejmi potisna obvestila push: prejmi potisna obvestila
read: preberi vse podatke svojega računa read: preberi vse podatke svojega računa

@ -113,6 +113,12 @@ zh-CN:
application: application:
title: 需要 OAuth 认证 title: 需要 OAuth 认证
scopes: scopes:
admin:read: 读取服务器上的所有数据
admin:read:accounts: 读取所有账户的敏感信息
admin:read:reports: 读取所有举报和被举报账户的敏感信息
admin:write: 修改服务器上的所有数据
admin:write:accounts: 对账户执行管理操作
admin:write:reports: 对举报执行管理操作
follow: 关注或屏蔽用户 follow: 关注或屏蔽用户
push: 接收你的帐户的推送通知 push: 接收你的帐户的推送通知
read: 读取你的帐户数据 read: 读取你的帐户数据

@ -7,23 +7,33 @@ it:
active_count_after: attivo active_count_after: attivo
active_footnote: Utenti Attivi Mensili (MAU) active_footnote: Utenti Attivi Mensili (MAU)
administered_by: 'Amministrato da:' administered_by: 'Amministrato da:'
api: API
apps: Applicazioni Mobile apps: Applicazioni Mobile
apps_platforms: Usa Mastodon da iOS, Android e altre piattaforme apps_platforms: Usa Mastodon da iOS, Android e altre piattaforme
browse_directory: Sfoglia la directory dei profili e filtra per interessi
browse_public_posts: Sfoglia il flusso in tempo reale di post pubblici su Mastodon
contact: Contatti contact: Contatti
contact_missing: Non impostato contact_missing: Non impostato
contact_unavailable: N/D contact_unavailable: N/D
discover_users: Scopri utenti
documentation: Documentazione documentation: Documentazione
extended_description_html: | extended_description_html: |
<h3>Un buon posto per le regole</h3> <h3>Un buon posto per le regole</h3>
<p>La descrizione estesa non è ancora stata preparata.</p> <p>La descrizione estesa non è ancora stata preparata.</p>
federation_hint_html: Con un account su %{instance} sarai in grado di seguire persone su qualsiasi server Mastodon e oltre.
generic_description: "%{domain} è un server nella rete" generic_description: "%{domain} è un server nella rete"
get_apps: Prova l'app per smartphone get_apps: Prova un'app per smartphone
hosted_on: Mastodon ospitato su %{domain} hosted_on: Mastodon ospitato su %{domain}
learn_more: Scopri altro learn_more: Scopri altro
privacy_policy: Politica della privacy privacy_policy: Politica della privacy
see_whats_happening: Guarda cosa succede
server_stats: 'Statistiche del server:'
source_code: Codice sorgente source_code: Codice sorgente
status_count_after:
one: stato
other: stati
status_count_before: Che hanno pubblicato status_count_before: Che hanno pubblicato
tagline: Segui vecchi amici e trovane nuovi tagline: Segui amici e trovane di nuovi
terms: Termini di Servizio terms: Termini di Servizio
user_count_after: user_count_after:
one: utente one: utente
@ -40,6 +50,7 @@ it:
joined: Dal %{date} joined: Dal %{date}
last_active: ultima attività last_active: ultima attività
link_verified_on: La proprietà di questo link è stata controllata il %{date} link_verified_on: La proprietà di questo link è stata controllata il %{date}
media: Media
moved_html: "%{name} è stato spostato su %{new_profile_link}:" moved_html: "%{name} è stato spostato su %{new_profile_link}:"
network_hidden: Questa informazione non e' disponibile network_hidden: Questa informazione non e' disponibile
nothing_here: Qui non c'è nulla! nothing_here: Qui non c'è nulla!
@ -47,12 +58,17 @@ it:
people_who_follow: Persone che seguono %{name} people_who_follow: Persone che seguono %{name}
pin_errors: pin_errors:
following: Devi gia seguire la persona che vuoi promuovere following: Devi gia seguire la persona che vuoi promuovere
posts:
one: Toot
other: Toot
posts_tab_heading: Toot posts_tab_heading: Toot
posts_with_replies: Toot e risposte posts_with_replies: Toot e risposte
reserved_username: Il nome utente è gia stato preso reserved_username: Il nome utente è gia stato preso
roles: roles:
admin: Amministratore admin: Amministratore
bot: Bot
moderator: Moderatore moderator: Moderatore
unavailable: Profilo non disponibile
unfollow: Non seguire più unfollow: Non seguire più
admin: admin:
account_actions: account_actions:
@ -64,7 +80,10 @@ it:
delete: Elimina delete: Elimina
destroyed_msg: Nota di moderazione distrutta con successo! destroyed_msg: Nota di moderazione distrutta con successo!
accounts: accounts:
approve: Approva
approve_all: Approva tutto
are_you_sure: Sei sicuro? are_you_sure: Sei sicuro?
avatar: Immagine di profilo
by_domain: Dominio by_domain: Dominio
change_email: change_email:
changed_msg: Account email cambiato con successo! changed_msg: Account email cambiato con successo!
@ -84,6 +103,7 @@ it:
display_name: Nome visualizzato display_name: Nome visualizzato
domain: Dominio domain: Dominio
edit: Modifica edit: Modifica
email: Email
email_status: Stato email email_status: Stato email
enable: Abilita enable: Abilita
enabled: Abilitato enabled: Abilitato
@ -94,6 +114,7 @@ it:
header: Intestazione header: Intestazione
inbox_url: URL inbox inbox_url: URL inbox
invited_by: Invitato da invited_by: Invitato da
ip: IP
joined: Unito joined: Unito
location: location:
all: Tutto all: Tutto
@ -106,15 +127,18 @@ it:
moderation: moderation:
active: Attivo active: Attivo
all: Tutto all: Tutto
pending: In attesa
silenced: Silenziati silenced: Silenziati
suspended: Sospesi suspended: Sospesi
title: Moderazione title: Moderazione
moderation_notes: Note di moderazione moderation_notes: Note di moderazione
most_recent_activity: Attività più recenti most_recent_activity: Attività più recenti
most_recent_ip: IP più recenti most_recent_ip: IP più recenti
no_account_selected: Nessun account è stato modificato visto che non ne è stato selezionato nessuno
no_limits_imposed: Nessun limite imposto no_limits_imposed: Nessun limite imposto
not_subscribed: Non sottoscritto not_subscribed: Non sottoscritto
outbox_url: URL outbox outbox_url: URL outbox
pending: Revisioni in attesa
perform_full_suspension: Sospendi perform_full_suspension: Sospendi
profile_url: URL profilo profile_url: URL profilo
promote: Promuovi promote: Promuovi
@ -122,6 +146,8 @@ it:
public: Pubblico public: Pubblico
push_subscription_expires: Sottoscrizione PuSH scaduta push_subscription_expires: Sottoscrizione PuSH scaduta
redownload: Aggiorna avatar redownload: Aggiorna avatar
reject: Rifiuta
reject_all: Rifiuta tutto
remove_avatar: Rimuovi avatar remove_avatar: Rimuovi avatar
remove_header: Rimuovi intestazione remove_header: Rimuovi intestazione
resend_confirmation: resend_confirmation:
@ -148,6 +174,7 @@ it:
statuses: Stati statuses: Stati
subscribe: Sottoscrivi subscribe: Sottoscrivi
suspended: Sospeso suspended: Sospeso
time_in_queue: Attesa in coda %{time}
title: Account title: Account
unconfirmed_email: Email non confermata unconfirmed_email: Email non confermata
undo_silenced: Rimuovi silenzia undo_silenced: Rimuovi silenzia
@ -155,6 +182,7 @@ it:
unsubscribe: Annulla l'iscrizione unsubscribe: Annulla l'iscrizione
username: Nome utente username: Nome utente
warn: Avverti warn: Avverti
web: Web
action_logs: action_logs:
actions: actions:
assigned_to_self_report: "%{name} ha assegnato il rapporto %{target} a se stesso" assigned_to_self_report: "%{name} ha assegnato il rapporto %{target} a se stesso"
@ -188,6 +216,7 @@ it:
update_custom_emoji: "%{name} ha aggiornato l'emoji %{target}" update_custom_emoji: "%{name} ha aggiornato l'emoji %{target}"
update_status: "%{name} stato aggiornato da %{target}" update_status: "%{name} stato aggiornato da %{target}"
deleted_status: "(stato cancellato)" deleted_status: "(stato cancellato)"
title: Registro di controllo
custom_emojis: custom_emojis:
by_domain: Dominio by_domain: Dominio
copied_msg: Creata con successo una copia locale dell'emoji copied_msg: Creata con successo una copia locale dell'emoji
@ -198,6 +227,7 @@ it:
destroyed_msg: Emoji distrutto con successo! destroyed_msg: Emoji distrutto con successo!
disable: Disabilita disable: Disabilita
disabled_msg: Questa emoji è stata disabilitata con successo disabled_msg: Questa emoji è stata disabilitata con successo
emoji: Emoji
enable: Abilita enable: Abilita
enabled_msg: Questa emoji è stata abilitata con successo enabled_msg: Questa emoji è stata abilitata con successo
image_hint: PNG fino a 50 KB image_hint: PNG fino a 50 KB
@ -205,6 +235,7 @@ it:
new: new:
title: Aggiungi nuovo emoji personalizzato title: Aggiungi nuovo emoji personalizzato
overwrite: Sovrascrivi overwrite: Sovrascrivi
shortcode: Scorciatoia
shortcode_hint: Almeno due caratteri, solo caratteri alfanumerici e trattino basso shortcode_hint: Almeno due caratteri, solo caratteri alfanumerici e trattino basso
title: Emoji personalizzate title: Emoji personalizzate
unlisted: Non elencato unlisted: Non elencato
@ -212,19 +243,23 @@ it:
updated_msg: Emoji aggiornata con successo! updated_msg: Emoji aggiornata con successo!
upload: Carica upload: Carica
dashboard: dashboard:
backlog: lavori arretrati
config: Configurazione config: Configurazione
feature_deletions: Cancellazioni di account feature_deletions: Cancellazioni di account
feature_invites: Link di invito feature_invites: Link di invito
feature_profile_directory: Directory dei profili feature_profile_directory: Directory dei profili
feature_registrations: Registrazioni feature_registrations: Registrazioni
feature_relay: Ripetitore di federazione feature_relay: Ripetitore di federazione
feature_timeline_preview: Anteprima timeline
features: Funzionalità features: Funzionalità
hidden_service: Federazione con servizi nascosti hidden_service: Federazione con servizi nascosti
open_reports: apri report open_reports: apri report
recent_users: Utenti Recenti recent_users: Utenti Recenti
search: Ricerca testo intero search: Ricerca testo intero
single_user_mode: Modalita utente singolo single_user_mode: Modalita utente singolo
software: Software
space: Utilizzo dello spazio space: Utilizzo dello spazio
title: Cruscotto
total_users: utenti totali total_users: utenti totali
trends: Tendenze trends: Tendenze
week_interactions: interazioni per questa settimana week_interactions: interazioni per questa settimana
@ -235,6 +270,7 @@ it:
created_msg: Il blocco del dominio sta venendo processato created_msg: Il blocco del dominio sta venendo processato
destroyed_msg: Il blocco del dominio è stato rimosso destroyed_msg: Il blocco del dominio è stato rimosso
domain: Dominio domain: Dominio
existing_domain_block_html: Hai già impostato limitazioni più stringenti su %{name}, dovresti <a href="%{unblock_url}">sbloccare</a> prima.
new: new:
create: Crea blocco create: Crea blocco
hint: Il blocco dominio non previene la creazione di utenti nel database, ma applicherà automaticamente e retroattivamente metodi di moderazione specifici su quegli account. hint: Il blocco dominio non previene la creazione di utenti nel database, ma applicherà automaticamente e retroattivamente metodi di moderazione specifici su quegli account.
@ -248,6 +284,8 @@ it:
reject_media_hint: Rimuovi i file media salvati in locale e blocca i download futuri. Irrilevante per le sospensioni reject_media_hint: Rimuovi i file media salvati in locale e blocca i download futuri. Irrilevante per le sospensioni
reject_reports: Respingi rapporti reject_reports: Respingi rapporti
reject_reports_hint: Ignora tutti i rapporti provenienti da questo dominio. Irrilevante per sospensioni reject_reports_hint: Ignora tutti i rapporti provenienti da questo dominio. Irrilevante per sospensioni
rejecting_media: rigetta file media
rejecting_reports: rigetta segnalazioni
severity: severity:
silence: silenziato silence: silenziato
suspend: sospeso suspend: sospeso
@ -276,16 +314,19 @@ it:
title: Seguaci di %{acct} title: Seguaci di %{acct}
instances: instances:
by_domain: Dominio by_domain: Dominio
delivery_available: Distribuzione disponibile
known_accounts: known_accounts:
one: "%{count} account noto" one: "%{count} account noto"
other: "%{count} account noti" other: "%{count} account noti"
moderation: moderation:
all: Tutto
limited: Limitato limited: Limitato
title: Moderazione title: Moderazione
title: Istanze conosciute title: Istanze conosciute
total_blocked_by_us: Bloccato da noi total_blocked_by_us: Bloccato da noi
total_followed_by_them: Seguito da loro total_followed_by_them: Seguito da loro
total_followed_by_us: Seguito da noi total_followed_by_us: Seguito da noi
total_reported: Segnalazioni su di loro
total_storage: Media allegati total_storage: Media allegati
invites: invites:
deactivate_all: Disattiva tutto deactivate_all: Disattiva tutto
@ -295,6 +336,8 @@ it:
expired: Scaduto expired: Scaduto
title: Filtro title: Filtro
title: Inviti title: Inviti
pending_accounts:
title: Account in attesa (%{count})
relays: relays:
add_new: Aggiungi ripetitore add_new: Aggiungi ripetitore
delete: Cancella delete: Cancella
@ -308,12 +351,14 @@ it:
pending: In attesa dell'approvazione del ripetitore pending: In attesa dell'approvazione del ripetitore
save_and_enable: Salva e attiva save_and_enable: Salva e attiva
setup: Crea una connessione con un ripetitore setup: Crea una connessione con un ripetitore
status: Stato
title: Ripetitori title: Ripetitori
report_notes: report_notes:
created_msg: Nota rapporto creata! created_msg: Nota rapporto creata!
destroyed_msg: Nota rapporto cancellata! destroyed_msg: Nota rapporto cancellata!
reports: reports:
account: account:
note: note
report: rapporto report: rapporto
action_taken_by: Azione intrapresa da action_taken_by: Azione intrapresa da
are_you_sure: Sei sicuro? are_you_sure: Sei sicuro?
@ -349,6 +394,7 @@ it:
desc_html: Separa i nomi utente con virgola. Funziona solo con account locali e non bloccati. Quando vuoto, valido per tutti gli amministratori locali. desc_html: Separa i nomi utente con virgola. Funziona solo con account locali e non bloccati. Quando vuoto, valido per tutti gli amministratori locali.
title: Seguiti predefiniti per i nuovi utenti title: Seguiti predefiniti per i nuovi utenti
contact_information: contact_information:
email: E-mail di lavoro
username: Nome utente del contatto username: Nome utente del contatto
custom_css: custom_css:
desc_html: Modifica l'aspetto con il CSS caricato in ogni pagina desc_html: Modifica l'aspetto con il CSS caricato in ogni pagina
@ -378,10 +424,17 @@ it:
min_invite_role: min_invite_role:
disabled: Nessuno disabled: Nessuno
title: Permetti inviti da title: Permetti inviti da
registrations_mode:
modes:
approved: Approvazione richiesta per le iscrizioni
none: Nessuno può iscriversi
open: Chiunque può iscriversi
title: Modalità di registrazione
show_known_fediverse_at_about_page: show_known_fediverse_at_about_page:
desc_html: Quando attivato, mostra nell'anteprima i toot da tutte le istanze conosciute. Altrimenti mostra solo i toot locali. desc_html: Quando attivato, mostra nell'anteprima i toot da tutte le istanze conosciute. Altrimenti mostra solo i toot locali.
title: Mostra la fediverse conosciuta nell'anteprima della timeline title: Mostra la fediverse conosciuta nell'anteprima della timeline
show_staff_badge: show_staff_badge:
desc_html: Mostra un distintivo dello staff sulla pagina dell'utente
title: Mostra badge staff title: Mostra badge staff
site_description: site_description:
desc_html: Paragrafo introduttivo nella pagina iniziale. Descrive ciò che rende speciale questo server Mastodon e qualunque altra cosa sia importante dire. Potete usare marcatori HTML, in particolare <code>&lt;a&gt;</code> e <code>&lt;em&gt;</code>. desc_html: Paragrafo introduttivo nella pagina iniziale. Descrive ciò che rende speciale questo server Mastodon e qualunque altra cosa sia importante dire. Potete usare marcatori HTML, in particolare <code>&lt;a&gt;</code> e <code>&lt;em&gt;</code>.
@ -410,6 +463,8 @@ it:
nsfw_off: Segna come non sensibile nsfw_off: Segna come non sensibile
nsfw_on: Segna come sensibile nsfw_on: Segna come sensibile
failed_to_execute: Impossibile eseguire failed_to_execute: Impossibile eseguire
media:
title: Media
no_media: Nessun media no_media: Nessun media
no_status_selected: Nessun status è stato modificato perché nessuno era stato selezionato no_status_selected: Nessun status è stato modificato perché nessuno era stato selezionato
title: Gli status dell'account title: Gli status dell'account
@ -418,11 +473,14 @@ it:
callback_url: URL Callback callback_url: URL Callback
confirmed: Confermato confirmed: Confermato
expires_in: Scade in expires_in: Scade in
last_delivery: Ultima distribuzione
title: WebSub
topic: Argomento topic: Argomento
tags: tags:
accounts: Account accounts: Account
hidden: Nascosto hidden: Nascosto
hide: Non mostrare nella directory hide: Nascondi dalla directory
name: Etichetta
title: Hashtag title: Hashtag
unhide: Mostra nella directory unhide: Mostra nella directory
visible: Visibile visible: Visibile
@ -433,8 +491,25 @@ it:
edit: Modifica edit: Modifica
edit_preset: Modifica avviso predefinito edit_preset: Modifica avviso predefinito
title: Gestisci avvisi predefiniti title: Gestisci avvisi predefiniti
admin_mailer:
new_pending_account:
body: I dettagli del nuovo account sono qui sotto. Puoi approvare o rifiutare questa richiesta.
subject: Nuovo account pronto per la revisione su %{instance} (%{username})
new_report:
body: "%{reporter} ha segnalato %{target}"
body_remote: Qualcuno da %{domain} ha segnalato %{target}
subject: Nuova segnalazione per %{instance} (#%{id})
appearance:
advanced_web_interface: Interfaccia web avanzata
advanced_web_interface_hint: |-
Se vuoi utilizzare l'intera larghezza dello schermo, l'interfaccia web avanzata ti consente di configurare varie colonne per mostrare più informazioni allo stesso tempo, secondo le tue preferenze:
Home, notifiche, timeline federata, qualsiasi numero di liste e etichette.
animations_and_accessibility: Animazioni e accessibiiltà
confirmation_dialogs: Dialoghi di conferma
sensitive_content: Contenuto sensibile
application_mailer: application_mailer:
notification_preferences: Cambia preferenze email notification_preferences: Cambia preferenze email
salutation: "%{name},"
settings: 'Cambia le impostazioni per le email: %{link}' settings: 'Cambia le impostazioni per le email: %{link}'
view: 'Guarda:' view: 'Guarda:'
view_profile: Mostra profilo view_profile: Mostra profilo
@ -446,22 +521,32 @@ it:
regenerate_token: Rigenera il token di accesso regenerate_token: Rigenera il token di accesso
token_regenerated: Token di accesso rigenerato token_regenerated: Token di accesso rigenerato
warning: Fa' molta attenzione con questi dati. Non fornirli mai a nessun altro! warning: Fa' molta attenzione con questi dati. Non fornirli mai a nessun altro!
your_token: Il tuo token di accesso
auth: auth:
apply_for_account: Richiedi un invito
change_password: Password
checkbox_agreement_html: Sono d'accordo con le <a href="%{rules_path}" target="_blank">regole del server</a> ed i <a href="%{terms_path}" target="_blank">termini di servizio</a>
confirm_email: Conferma email confirm_email: Conferma email
delete_account: Elimina account delete_account: Elimina account
delete_account_html: Se desideri cancellare il tuo account, puoi <a href="%{path}">farlo qui</a>. Ti sarà chiesta conferma. delete_account_html: Se desideri cancellare il tuo account, puoi <a href="%{path}">farlo qui</a>. Ti sarà chiesta conferma.
didnt_get_confirmation: Non hai ricevuto le istruzioni di conferma? didnt_get_confirmation: Non hai ricevuto le istruzioni di conferma?
forgot_password: Hai dimenticato la tua password? forgot_password: Hai dimenticato la tua password?
invalid_reset_password_token: Il token di reimpostazione della password non è valido o è scaduto. Per favore richiedine uno nuovo.
login: Entra login: Entra
logout: Esci da Mastodon logout: Esci da Mastodon
migrate_account: Sposta ad un account differente migrate_account: Sposta ad un account differente
migrate_account_html: Se vuoi che questo account sia reindirizzato a uno diverso, puoi <a href="%{path}">configurarlo qui</a>. migrate_account_html: Se vuoi che questo account sia reindirizzato a uno diverso, puoi <a href="%{path}">configurarlo qui</a>.
or_log_in_with: Oppure accedi con or_log_in_with: Oppure accedi con
providers:
cas: CAS
saml: SAML
register: Iscriviti register: Iscriviti
registration_closed: "%{instance} non accetta nuovi membri"
resend_confirmation: Invia di nuovo le istruzioni di conferma resend_confirmation: Invia di nuovo le istruzioni di conferma
reset_password: Resetta la password reset_password: Resetta la password
security: Credenziali security: Credenziali
set_new_password: Imposta una nuova password set_new_password: Imposta una nuova password
trouble_logging_in: Problemi di accesso?
authorize_follow: authorize_follow:
already_following: Stai già seguendo questo account already_following: Stai già seguendo questo account
error: Sfortunatamente c'è stato un errore nel consultare l'account remoto error: Sfortunatamente c'è stato un errore nel consultare l'account remoto
@ -494,6 +579,7 @@ it:
proceed: Cancella l'account proceed: Cancella l'account
success_msg: Il tuo account è stato cancellato success_msg: Il tuo account è stato cancellato
warning_html: È garantita la cancellazione del contenuto solo da questo server. I contenuti che sono stati ampiamente condivisi probabilmente lasceranno delle tracce. I server offline e quelli che non ricevono più i tuoi aggiornamenti non aggiorneranno i loro database. warning_html: È garantita la cancellazione del contenuto solo da questo server. I contenuti che sono stati ampiamente condivisi probabilmente lasceranno delle tracce. I server offline e quelli che non ricevono più i tuoi aggiornamenti non aggiorneranno i loro database.
warning_title: Disponibilità di contenuto diffuso
directories: directories:
directory: Directory dei profili directory: Directory dei profili
enabled: Attualmente sei elencato nella directory. enabled: Attualmente sei elencato nella directory.
@ -511,11 +597,14 @@ it:
'422': '422':
content: Verifica di sicurezza non riuscita. Stai bloccando i cookies? content: Verifica di sicurezza non riuscita. Stai bloccando i cookies?
title: Verifica di sicurezza non riuscita title: Verifica di sicurezza non riuscita
'429': Throttled '429': Limitato
'500': '500':
content: Siamo spiacenti, ma qualcosa non ha funzionato dal nostro lato. content: Siamo spiacenti, ma qualcosa non ha funzionato dal nostro lato.
title: Questa pagina non è corretta title: Questa pagina non è corretta
noscript_html: Per usare l'interfaccia web di Mastodon dovi abilitare JavaScript. In alternativa puoi provare una delle <a href="%{apps_path}">app native</a> per Mastodon per la tua piattaforma. noscript_html: Per usare l'interfaccia web di Mastodon dovi abilitare JavaScript. In alternativa puoi provare una delle <a href="%{apps_path}">app native</a> per Mastodon per la tua piattaforma.
existing_username_validator:
not_found: impossibile trovare un utente locale con quel nome utente
not_found_multiple: impossibile trovare %{usernames}
exports: exports:
archive_takeout: archive_takeout:
date: Data date: Data
@ -525,6 +614,7 @@ it:
request: Richiedi il tuo archivio request: Richiedi il tuo archivio
size: Dimensioni size: Dimensioni
blocks: Stai bloccando blocks: Stai bloccando
csv: CSV
domain_blocks: Blocchi di dominio domain_blocks: Blocchi di dominio
follows: Stai seguendo follows: Stai seguendo
lists: Liste lists: Liste
@ -544,6 +634,7 @@ it:
title: Modifica filtro title: Modifica filtro
errors: errors:
invalid_context: Contesto mancante o non valido invalid_context: Contesto mancante o non valido
invalid_irreversible: Il filtraggio irreversibile funziona solo nei contesti di home o notifiche
index: index:
delete: Cancella delete: Cancella
title: Filtri title: Filtri
@ -552,13 +643,36 @@ it:
footer: footer:
developers: Sviluppatori developers: Sviluppatori
more: Altro… more: Altro…
resources: Risorse
generic: generic:
all: Tutto
changes_saved_msg: Modifiche effettuate con successo! changes_saved_msg: Modifiche effettuate con successo!
copy: Copia copy: Copia
order_by: Ordina per
save_changes: Salva modifiche save_changes: Salva modifiche
validation_errors: validation_errors:
one: Qualcosa ancora non va bene! Per favore, controlla l'errore qui sotto one: Qualcosa ancora non va bene! Per favore, controlla l'errore qui sotto
other: Qualcosa ancora non va bene! Per favore, controlla i %{count} errori qui sotto other: Qualcosa ancora non va bene! Per favore, controlla i %{count} errori qui sotto
html_validator:
invalid_markup: 'contiene markup HTML non valido: %{error}'
identity_proofs:
active: Attive
authorize: Si, autorizza
authorize_connection_prompt: Autorizzare questa connessione crittografata?
errors:
failed: La connessione crittografata non è riuscita. Per favore riprova da %{provider}.
keybase:
invalid_token: I toked di Keybase sono hash di firme e devono essere lunghi 66 caratteri esadecimali
verification_failed: Keybase non riconosce questo token come firma dell'utente Keybase %{kb_username}. Per favore riprova da Keybase.
wrong_user: Impossibile creare una prova per %{proving} mentre si è effettuato l'accesso come %{current}. Accedi come %{proving} e riprova.
explanation_html: Qui puoi connettere crittograficamente le tue altre identità, come il profilo Keybase. Questo consente ad altre persone di inviarti messaggi criptati e fidarsi dei contenuto che tu invii a loro.
i_am_html: Io sono %{username} su %{service}.
identity: Identità
inactive: Inattiva
publicize_checkbox: 'E posta questo:'
publicize_toot: 'É provato! Io sono %{username} su %{service}: %{url}'
status: Stato della verifica
view_proof: Vedi prova
imports: imports:
modes: modes:
merge: Fondi merge: Fondi
@ -573,6 +687,7 @@ it:
following: Lista dei seguaci following: Lista dei seguaci
muting: Lista dei silenziati muting: Lista dei silenziati
upload: Carica upload: Carica
in_memoriam_html: In Memoriam.
invites: invites:
delete: Disattiva delete: Disattiva
expired: Scaduto expired: Scaduto
@ -643,14 +758,26 @@ it:
body: 'Il tuo status è stato condiviso da %{name}:' body: 'Il tuo status è stato condiviso da %{name}:'
subject: "%{name} ha condiviso il tuo status" subject: "%{name} ha condiviso il tuo status"
title: Nuova condivisione title: Nuova condivisione
number:
human:
decimal_units:
format: "%n%u"
units:
billion: G
million: M
quadrillion: P
thousand: k
trillion: T
pagination: pagination:
newer: Più recente newer: Più recente
next: Avanti next: Avanti
older: Più vecchio older: Più vecchio
prev: Indietro prev: Indietro
truncate: "&hellip;"
polls: polls:
errors: errors:
already_voted: Hai già votato in questo sondaggio already_voted: Hai già votato in questo sondaggio
duplicate_options: contiene oggetti duplicati
duration_too_long: è troppo lontano nel futuro duration_too_long: è troppo lontano nel futuro
duration_too_short: è troppo presto duration_too_short: è troppo presto
expired: Il sondaggio si è già concluso expired: Il sondaggio si è già concluso
@ -659,12 +786,28 @@ it:
too_many_options: non può contenere più di %{max} elementi too_many_options: non può contenere più di %{max} elementi
preferences: preferences:
other: Altro other: Altro
posting_defaults: Predefinite di pubblicazione
public_timelines: Timeline pubbliche
relationships:
activity: Attività dell'account
dormant: Dormiente
last_active: Ultima volta attivo
most_recent: Più recente
moved: Trasferito
mutual: Reciproco
primary: Principale
relationship: Relazione
remove_selected_domains: Rimuovi tutti i seguaci dai domini selezionati
remove_selected_followers: Rimuovi i seguaci selezionati
remove_selected_follows: Smetti di seguire gli utenti selezionati
status: Stato dell'account
remote_follow: remote_follow:
acct: Inserisci il tuo username@dominio da cui vuoi seguire questo utente acct: Inserisci il tuo username@dominio da cui vuoi seguire questo utente
missing_resource: Impossibile trovare l'URL di reindirizzamento richiesto per il tuo account missing_resource: Impossibile trovare l'URL di reindirizzamento richiesto per il tuo account
no_account_html: Non hai un account? Puoi <a href='%{sign_up_path}' target='_blank'>iscriverti qui</a> no_account_html: Non hai un account? Puoi <a href='%{sign_up_path}' target='_blank'>iscriverti qui</a>
proceed: Conferma proceed: Conferma
prompt: 'Stai per seguire:' prompt: 'Stai per seguire:'
reason_html: "<strong>Perchè questo passo è necessario?</strong> <code>%{instance}</code> potrebbe non essere il server nel quale tu sei registrato, quindi dobbiamo reindirizzarti prima al tuo server."
remote_interaction: remote_interaction:
favourite: favourite:
proceed: Continua per segnare come apprezzato proceed: Continua per segnare come apprezzato
@ -685,15 +828,49 @@ it:
too_soon: La data di pubblicazione deve essere nel futuro too_soon: La data di pubblicazione deve essere nel futuro
sessions: sessions:
activity: Ultima attività activity: Ultima attività
browser: Browser
browsers: browsers:
alipay: Alipay
blackberry: Blackberry
chrome: Chrome
edge: Microsoft Edge
electron: Electron
firefox: Firefox
generic: Browser sconosciuto generic: Browser sconosciuto
ie: Internet Explorer
micro_messenger: MicroMessenger
nokia: Nokia S40 Ovi Browser
opera: Opera
otter: Otter
phantom_js: PhantomJS
qq: QQ Browser
safari: Safari
uc_browser: UCBrowser
weibo: Weibo
current_session: Sessione corrente current_session: Sessione corrente
description: "%{browser} su %{platform}" description: "%{browser} su %{platform}"
explanation: Questi sono i browser da cui attualmente è avvenuto l'accesso al tuo account Mastodon. explanation: Questi sono i browser da cui attualmente è avvenuto l'accesso al tuo account Mastodon.
ip: IP
platforms: platforms:
adobe_air: Adobe Air
android: Android
blackberry: Blackberry
chrome_os: ChromeOS
firefox_os: Firefox OS
ios: iOS
linux: Linux
mac: Mac
other: piattaforma sconosciuta other: piattaforma sconosciuta
windows: Windows
windows_mobile: Windows Mobile
windows_phone: Windows Phone
revoke: Revoca
revoke_success: Sessione revocata con successo
title: Sessioni title: Sessioni
settings: settings:
account: Account
account_settings: Impostazioni dell'account
appearance: Interfaccia
authorized_apps: Applicazioni autorizzate authorized_apps: Applicazioni autorizzate
back: Torna a Mastodon back: Torna a Mastodon
delete: Cancellazione account delete: Cancellazione account
@ -701,10 +878,14 @@ it:
edit_profile: Modifica profilo edit_profile: Modifica profilo
export: Esporta impostazioni export: Esporta impostazioni
featured_tags: Hashtag in evidenza featured_tags: Hashtag in evidenza
identity_proofs: Prove di identità
import: Importa import: Importa
import_and_export: Importa ed esporta
migrate: Migrazione dell'account migrate: Migrazione dell'account
notifications: Notifiche notifications: Notifiche
preferences: Preferenze preferences: Preferenze
profile: Profilo
relationships: Follows and followers
two_factor_authentication: Autenticazione a due fattori two_factor_authentication: Autenticazione a due fattori
statuses: statuses:
attached: attached:
@ -712,7 +893,11 @@ it:
image: image:
one: "%{count} immagine" one: "%{count} immagine"
other: "%{count} immagini" other: "%{count} immagini"
video:
one: "%{count} video"
other: "%{count} video"
boosted_from_html: Condiviso da %{acct_link} boosted_from_html: Condiviso da %{acct_link}
content_warning: 'Avviso di contenuto: %{warning}'
disallowed_hashtags: disallowed_hashtags:
one: 'contiene un hashtag non permesso: %{tags}' one: 'contiene un hashtag non permesso: %{tags}'
other: 'contiene gli hashtags non permessi: %{tags}' other: 'contiene gli hashtags non permessi: %{tags}'
@ -731,6 +916,7 @@ it:
vote: Vota vote: Vota
show_more: Mostra di più show_more: Mostra di più
sign_in_to_participate: Accedi per partecipare alla conversazione sign_in_to_participate: Accedi per partecipare alla conversazione
title: '%{name}: "%{quote}"'
visibilities: visibilities:
private: Mostra solo ai tuoi seguaci private: Mostra solo ai tuoi seguaci
private_long: Mostra solo ai seguaci private_long: Mostra solo ai seguaci
@ -748,6 +934,10 @@ it:
contrast: Mastodon (contrasto elevato) contrast: Mastodon (contrasto elevato)
default: Mastodon (scuro) default: Mastodon (scuro)
mastodon-light: Mastodon (chiaro) mastodon-light: Mastodon (chiaro)
time:
formats:
default: "%b %d, %Y, %H:%M"
month: "%b %Y"
two_factor_authentication: two_factor_authentication:
code_hint: Inserisci il codice generato dalla tua app di autenticazione code_hint: Inserisci il codice generato dalla tua app di autenticazione
description_html: Se abiliti <strong>l'autorizzazione a due fattori</strong>, entrare nel tuo account ti richiederà di avere vicino il tuo telefono, il quale ti genererà un codice per eseguire l'accesso. description_html: Se abiliti <strong>l'autorizzazione a due fattori</strong>, entrare nel tuo account ti richiederà di avere vicino il tuo telefono, il quale ti genererà un codice per eseguire l'accesso.
@ -769,7 +959,24 @@ it:
explanation: Hai richiesto un backup completo del tuo account Mastodon. È pronto per essere scaricato! explanation: Hai richiesto un backup completo del tuo account Mastodon. È pronto per essere scaricato!
subject: Il tuo archivio è pronto per essere scaricato subject: Il tuo archivio è pronto per essere scaricato
title: Esportazione archivio title: Esportazione archivio
warning:
explanation:
disable: Mentre il tuo account è congelato, i tuoi dati dell'account rimangono intatti, ma non potrai eseguire nessuna azione fintanto che non viene sbloccato.
silence: Mentre il tuo account è limitato, solo le persone che già ti seguono possono vedere i tuoi toot su questo server, e potresti essere escluso da vari elenchi pubblici. Comunque, altri possono manualmente seguirti.
suspend: Il tuo account è stato sospeso, e tutti i tuoi toot ed i tuoi file media caricati sono stati irreversibilmente rimossi da questo server, e dai server dove avevi dei seguaci.
review_server_policies: Rivedi regole del server
subject:
disable: Il tuo account %{acct} è stato congelato
none: Avviso per %{acct}
silence: Il tuo account %{acct} è stato limitato
suspend: Il tuo account %{acct} è stato sospeso
title:
disable: Account congelato
none: Avviso
silence: Account limitato
suspend: Account sospeso
welcome: welcome:
edit_profile_action: Imposta profilo
edit_profile_step: Puoi personalizzare il tuo profilo caricando un avatar, un'intestazione, modificando il tuo nome visualizzato e così via. Se vuoi controllare i tuoi nuovi seguaci prima di autorizzarli a seguirti, puoi bloccare il tuo account. edit_profile_step: Puoi personalizzare il tuo profilo caricando un avatar, un'intestazione, modificando il tuo nome visualizzato e così via. Se vuoi controllare i tuoi nuovi seguaci prima di autorizzarli a seguirti, puoi bloccare il tuo account.
explanation: Ecco alcuni suggerimenti per iniziare explanation: Ecco alcuni suggerimenti per iniziare
final_action: Inizia a postare final_action: Inizia a postare
@ -789,6 +996,7 @@ it:
follow_limit_reached: Non puoi seguire più di %{limit} persone follow_limit_reached: Non puoi seguire più di %{limit} persone
invalid_email: L'indirizzo email inserito non è valido invalid_email: L'indirizzo email inserito non è valido
invalid_otp_token: Codice d'accesso non valido invalid_otp_token: Codice d'accesso non valido
otp_lost_help_html: Se perdessi l'accesso ad entrambi, puoi entrare in contatto con %{email}
seamless_external_login: Ti sei collegato per mezzo di un servizio esterno, quindi le impostazioni di email e password non sono disponibili. seamless_external_login: Ti sei collegato per mezzo di un servizio esterno, quindi le impostazioni di email e password non sono disponibili.
signed_in_as: 'Hai effettuato l''accesso come:' signed_in_as: 'Hai effettuato l''accesso come:'
verification: verification:

@ -35,7 +35,7 @@ pl:
one: wpisu one: wpisu
other: wpisów other: wpisów
status_count_before: Są autorami status_count_before: Są autorami
tagline: Śledź znajomych i poznawal nowych tagline: Śledź znajomych i poznawaj nowych
terms: Zasady użytkowania terms: Zasady użytkowania
user_count_after: user_count_after:
few: użytkowników few: użytkowników

@ -27,6 +27,7 @@ it:
phrase: Il confronto sarà eseguito ignorando minuscole/maiuscole e i content warning phrase: Il confronto sarà eseguito ignorando minuscole/maiuscole e i content warning
scopes: A quali API l'applicazione potrà avere accesso. Se selezionate un ambito di alto livello, non c'è bisogno di selezionare quelle singole. scopes: A quali API l'applicazione potrà avere accesso. Se selezionate un ambito di alto livello, non c'è bisogno di selezionare quelle singole.
setting_aggregate_reblogs: Non mostrare nuove condivisioni per toot che sono stati condivisi di recente (ha effetto solo sulle nuove condivisioni) setting_aggregate_reblogs: Non mostrare nuove condivisioni per toot che sono stati condivisi di recente (ha effetto solo sulle nuove condivisioni)
setting_default_sensitive: Media con contenuti sensibili sono nascosti in modo predefinito e possono essere rivelati con un click
setting_display_media_default: Nascondi media segnati come sensibili setting_display_media_default: Nascondi media segnati come sensibili
setting_display_media_hide_all: Nascondi sempre tutti i media setting_display_media_hide_all: Nascondi sempre tutti i media
setting_display_media_show_all: Nascondi sempre i media segnati come sensibili setting_display_media_show_all: Nascondi sempre i media segnati come sensibili
@ -39,6 +40,8 @@ it:
name: 'Eccone alcuni che potresti usare:' name: 'Eccone alcuni che potresti usare:'
imports: imports:
data: File CSV esportato da un altro server Mastodon data: File CSV esportato da un altro server Mastodon
invite_request:
text: Questo ci aiuterà ad esaminare la tua richiesta
sessions: sessions:
otp: 'Inserisci il codice a due fattori generato dall''app del tuo telefono o usa uno dei codici di recupero:' otp: 'Inserisci il codice a due fattori generato dall''app del tuo telefono o usa uno dei codici di recupero:'
user: user:
@ -62,12 +65,14 @@ it:
warning_preset_id: Usa un avviso preimpostato warning_preset_id: Usa un avviso preimpostato
defaults: defaults:
autofollow: Invita a seguire il tuo account autofollow: Invita a seguire il tuo account
avatar: Immagine di profilo
bot: Questo account è un bot bot: Questo account è un bot
chosen_languages: Filtra lingue chosen_languages: Filtra lingue
confirm_new_password: Conferma nuova password confirm_new_password: Conferma nuova password
confirm_password: Conferma password confirm_password: Conferma password
context: Contesti del filtro context: Contesti del filtro
current_password: Password corrente current_password: Password corrente
data: Data
discoverable: Inserisci questo account nella directory discoverable: Inserisci questo account nella directory
display_name: Nome visualizzato display_name: Nome visualizzato
email: Indirizzo email email: Indirizzo email
@ -82,7 +87,9 @@ it:
new_password: Nuova password new_password: Nuova password
note: Biografia note: Biografia
otp_attempt: Codice due-fattori otp_attempt: Codice due-fattori
password: Password
phrase: Parola chiave o frase phrase: Parola chiave o frase
setting_advanced_layout: Abilita interfaccia web avanzata
setting_aggregate_reblogs: Raggruppa condivisioni in timeline setting_aggregate_reblogs: Raggruppa condivisioni in timeline
setting_auto_play_gif: Play automatico GIF animate setting_auto_play_gif: Play automatico GIF animate
setting_boost_modal: Mostra dialogo di conferma prima del boost setting_boost_modal: Mostra dialogo di conferma prima del boost
@ -107,18 +114,26 @@ it:
username: Nome utente username: Nome utente
username_or_email: Nome utente o email username_or_email: Nome utente o email
whole_word: Parola intera whole_word: Parola intera
featured_tag:
name: Etichetta
interactions: interactions:
must_be_follower: Blocca notifiche da chi non ti segue must_be_follower: Blocca notifiche da chi non ti segue
must_be_following: Blocca notifiche dalle persone che non segui must_be_following: Blocca notifiche dalle persone che non segui
must_be_following_dm: Blocca i messaggi diretti dalle persone che non segui must_be_following_dm: Blocca i messaggi diretti dalle persone che non segui
invite_request:
text: Perchè vuoi unirti?
notification_emails: notification_emails:
digest: Invia email riassuntive digest: Invia email riassuntive
favourite: Invia email quando segna come preferito al tuo stato favourite: Invia email quando segna come preferito al tuo stato
follow: Invia email quando qualcuno ti segue follow: Invia email quando qualcuno ti segue
follow_request: Invia email quando qualcuno richiede di seguirti follow_request: Invia email quando qualcuno richiede di seguirti
mention: Invia email quando qualcuno ti menziona mention: Invia email quando qualcuno ti menziona
pending_account: Invia e-mail quando un nuovo account richiede l'approvazione
reblog: Invia email quando qualcuno da un boost al tuo stato reblog: Invia email quando qualcuno da un boost al tuo stato
report: Manda una mail quando viene inviato un nuovo rapporto report: Manda una mail quando viene inviato un nuovo rapporto
'no': 'No'
recommended: Consigliato
required: required:
mark: "*"
text: richiesto text: richiesto
'yes': Si 'yes': Si

@ -135,5 +135,6 @@ ja:
'no': いいえ 'no': いいえ
recommended: おすすめ recommended: おすすめ
required: required:
mark: "*"
text: 必須 text: 必須
'yes': はい 'yes': はい

@ -546,6 +546,7 @@ sk:
migrate_account_html: Ak si želáš presmerovať tento účet na nejaký iný, môžeš si to <a href="%{path}">nastaviť tu</a>. migrate_account_html: Ak si želáš presmerovať tento účet na nejaký iný, môžeš si to <a href="%{path}">nastaviť tu</a>.
or_log_in_with: Alebo prihlás s or_log_in_with: Alebo prihlás s
register: Zaregistruj sa register: Zaregistruj sa
registration_closed: "%{instance} neprijíma nových členov"
resend_confirmation: Zašli potvrdzujúce pokyny znovu resend_confirmation: Zašli potvrdzujúce pokyny znovu
reset_password: Obnov heslo reset_password: Obnov heslo
security: Zabezpečenie security: Zabezpečenie
@ -668,6 +669,7 @@ sk:
publicize_checkbox: 'A poslať toto:' publicize_checkbox: 'A poslať toto:'
publicize_toot: 'Je to dokázané! Na %{service} som %{username}: %{url}' publicize_toot: 'Je to dokázané! Na %{service} som %{username}: %{url}'
status: Stav overenia status: Stav overenia
view_proof: Ukáž overenie
imports: imports:
modes: modes:
merge: Spoj dohromady merge: Spoj dohromady
@ -766,6 +768,7 @@ sk:
too_many_options: nemôže zahŕňať viac ako %{max} položiek too_many_options: nemôže zahŕňať viac ako %{max} položiek
preferences: preferences:
other: Ostatné other: Ostatné
public_timelines: Verejné časové osi
relationships: relationships:
activity: Aktivita účtu activity: Aktivita účtu
dormant: Spiace dormant: Spiace
@ -773,6 +776,7 @@ sk:
most_recent: Najnovšie most_recent: Najnovšie
moved: Presunuli sa moved: Presunuli sa
mutual: Spoločné mutual: Spoločné
primary: Hlavné
relationship: Vzťah relationship: Vzťah
remove_selected_followers: Odstráň vybraných následovatrľov remove_selected_followers: Odstráň vybraných následovatrľov
remove_selected_follows: Prestaň sledovať vybraných užívateľov remove_selected_follows: Prestaň sledovať vybraných užívateľov

@ -409,6 +409,29 @@ Rails.application.routes.draw do
namespace :push do namespace :push do
resource :subscription, only: [:create, :show, :update, :destroy] resource :subscription, only: [:create, :show, :update, :destroy]
end end
namespace :admin do
resources :accounts, only: [:index, :show] do
member do
post :enable
post :unsilence
post :unsuspend
post :approve
post :reject
end
resource :action, only: [:create], controller: 'account_actions'
end
resources :reports, only: [:index, :show] do
member do
post :assign_to_self
post :unassign
post :reopen
post :resolve
end
end
end
end end
namespace :v2 do namespace :v2 do

@ -13,7 +13,7 @@ module Mastodon
end end
def patch def patch
0 2
end end
def pre def pre

@ -1,23 +0,0 @@
# frozen_string_literal: true
module Paperclip
class AudioTranscoder < Paperclip::Processor
def make
max_aud_len = (ENV['MAX_AUDIO_LENGTH'] || 60.0).to_f
meta = ::Av.cli.identify(@file.path)
# {:length=>"0:00:02.14", :duration=>2.14, :audio_encode=>"mp3", :audio_bitrate=>"44100 Hz", :audio_channels=>"mono"}
if meta[:duration] > max_aud_len
raise Mastodon::ValidationError, "Audio uploads must be less than #{max_aud_len} seconds in length."
end
final_file = Paperclip::Transcoder.make(file, options, attachment)
attachment.instance.file_file_name = 'media.mp4'
attachment.instance.file_content_type = 'video/mp4'
attachment.instance.type = MediaAttachment.types[:video]
final_file
end
end
end

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'mime/types/columnar'
module Paperclip
class TypeCorrector < Paperclip::Processor
def make
target_extension = options[:format]
extension = File.extname(attachment.instance.file_file_name)
return @file unless options[:style] == :original && target_extension && extension != target_extension
attachment.instance.file_content_type = options[:content_type] || attachment.instance.file_content_type
attachment.instance.file_file_name = File.basename(attachment.instance.file_file_name, '.*') + '.' + target_extension
@file
end
end
end

@ -0,0 +1,57 @@
require 'rails_helper'
RSpec.describe Api::V1::Admin::AccountActionsController, type: :controller do
render_views
let(:role) { 'moderator' }
let(:user) { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:account) { Fabricate(:user).account }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { wrong_role }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
describe 'POST #create' do
before do
post :create, params: { account_id: account.id, type: 'disable' }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'performs action against account' do
expect(account.reload.user_disabled?).to be true
end
it 'logs action' do
log_item = Admin::ActionLog.last
expect(log_item).to_not be_nil
expect(log_item.action).to eq :disable
expect(log_item.account_id).to eq user.account_id
expect(log_item.target_id).to eq account.user.id
end
end
end

@ -0,0 +1,147 @@
require 'rails_helper'
RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
render_views
let(:role) { 'moderator' }
let(:user) { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:account) { Fabricate(:user).account }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { wrong_role }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
describe 'GET #index' do
before do
get :index
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'GET #show' do
before do
get :show, params: { id: account.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'POST #approve' do
before do
account.user.update(approved: false)
post :approve, params: { id: account.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'approves user' do
expect(account.reload.user_approved?).to be true
end
end
describe 'POST #reject' do
before do
account.user.update(approved: false)
post :reject, params: { id: account.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'removes user' do
expect(User.where(id: account.user.id).count).to eq 0
end
end
describe 'POST #enable' do
before do
account.user.update(disabled: true)
post :enable, params: { id: account.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'enables user' do
expect(account.reload.user_disabled?).to be false
end
end
describe 'POST #unsuspend' do
before do
account.touch(:suspended_at)
post :unsuspend, params: { id: account.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'unsuspends account' do
expect(account.reload.suspended?).to be false
end
end
describe 'POST #unsilence' do
before do
account.touch(:silenced_at)
post :unsilence, params: { id: account.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'unsilences account' do
expect(account.reload.silenced?).to be false
end
end
end

@ -0,0 +1,109 @@
require 'rails_helper'
RSpec.describe Api::V1::Admin::ReportsController, type: :controller do
render_views
let(:role) { 'moderator' }
let(:user) { Fabricate(:user, role: role, account: Fabricate(:account, username: 'alice')) }
let(:scopes) { 'admin:read admin:write' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:report) { Fabricate(:report) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
shared_examples 'forbidden for wrong scope' do |wrong_scope|
let(:scopes) { wrong_scope }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
shared_examples 'forbidden for wrong role' do |wrong_role|
let(:role) { wrong_role }
it 'returns http forbidden' do
expect(response).to have_http_status(403)
end
end
describe 'GET #index' do
before do
get :index
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'GET #show' do
before do
get :show, params: { id: report.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'POST #resolve' do
before do
post :resolve, params: { id: report.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'POST #reopen' do
before do
post :reopen, params: { id: report.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'POST #assign_to_self' do
before do
post :assign_to_self, params: { id: report.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
describe 'POST #unassign' do
before do
post :unassign, params: { id: report.id }
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
it_behaves_like 'forbidden for wrong role', 'user'
it 'returns http success' do
expect(response).to have_http_status(200)
end
end
end

@ -687,6 +687,23 @@ RSpec.describe Account, type: :model do
end end
end end
describe 'by_domain_and_subdomains' do
it 'returns exact domain matches' do
account = Fabricate(:account, domain: 'example.com')
expect(Account.by_domain_and_subdomains('example.com')).to eq [account]
end
it 'returns subdomains' do
account = Fabricate(:account, domain: 'foo.example.com')
expect(Account.by_domain_and_subdomains('example.com')).to eq [account]
end
it 'does not return partially matching domains' do
account = Fabricate(:account, domain: 'grexample.com')
expect(Account.by_domain_and_subdomains('example.com')).to_not eq [account]
end
end
describe 'expiring' do describe 'expiring' do
it 'returns remote accounts with followers whose subscription expiration date is past or not given' do it 'returns remote accounts with followers whose subscription expiration date is past or not given' do
local = Fabricate(:account, domain: nil) local = Fabricate(:account, domain: nil)

@ -21,23 +21,40 @@ RSpec.describe DomainBlock, type: :model do
end end
end end
describe 'blocked?' do describe '.blocked?' do
it 'returns true if the domain is suspended' do it 'returns true if the domain is suspended' do
Fabricate(:domain_block, domain: 'domain', severity: :suspend) Fabricate(:domain_block, domain: 'example.com', severity: :suspend)
expect(DomainBlock.blocked?('domain')).to eq true expect(DomainBlock.blocked?('example.com')).to eq true
end end
it 'returns false even if the domain is silenced' do it 'returns false even if the domain is silenced' do
Fabricate(:domain_block, domain: 'domain', severity: :silence) Fabricate(:domain_block, domain: 'example.com', severity: :silence)
expect(DomainBlock.blocked?('domain')).to eq false expect(DomainBlock.blocked?('example.com')).to eq false
end end
it 'returns false if the domain is not suspended nor silenced' do it 'returns false if the domain is not suspended nor silenced' do
expect(DomainBlock.blocked?('domain')).to eq false expect(DomainBlock.blocked?('example.com')).to eq false
end end
end end
describe 'stricter_than?' do describe '.rule_for' do
it 'returns rule matching a blocked domain' do
block = Fabricate(:domain_block, domain: 'example.com')
expect(DomainBlock.rule_for('example.com')).to eq block
end
it 'returns a rule matching a subdomain of a blocked domain' do
block = Fabricate(:domain_block, domain: 'example.com')
expect(DomainBlock.rule_for('sub.example.com')).to eq block
end
it 'returns a rule matching a blocked subdomain' do
block = Fabricate(:domain_block, domain: 'sub.example.com')
expect(DomainBlock.rule_for('sub.example.com')).to eq block
end
end
describe '#stricter_than?' do
it 'returns true if the new block has suspend severity while the old has lower severity' do it 'returns true if the new block has suspend severity while the old has lower severity' do
suspend = DomainBlock.new(domain: 'domain', severity: :suspend) suspend = DomainBlock.new(domain: 'domain', severity: :suspend)
silence = DomainBlock.new(domain: 'domain', severity: :silence) silence = DomainBlock.new(domain: 'domain', severity: :silence)

Loading…
Cancel
Save