Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- app/controllers/auth/sessions_controller.rb
  Minor conflict due to glitch-soc's theming code
main
Thibaut Girka 5 years ago
commit 5cadb47238

@ -116,9 +116,9 @@ group :production, :test do
end end
group :test do group :test do
gem 'capybara', '~> 3.28' gem 'capybara', '~> 3.29'
gem 'climate_control', '~> 0.2' gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.2' gem 'faker', '~> 2.3'
gem 'microformats', '~> 4.1' gem 'microformats', '~> 4.1'
gem 'rails-controller-testing', '~> 1.0' gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.0' gem 'rspec-sidekiq', '~> 3.0'

@ -150,7 +150,7 @@ GEM
sshkit (~> 1.3) sshkit (~> 1.3)
capistrano-yarn (2.0.2) capistrano-yarn (2.0.2)
capistrano (~> 3.0) capistrano (~> 3.0)
capybara (3.28.0) capybara (3.29.0)
addressable addressable
mini_mime (>= 0.1.3) mini_mime (>= 0.1.3)
nokogiri (~> 1.8) nokogiri (~> 1.8)
@ -231,7 +231,7 @@ GEM
tzinfo tzinfo
excon (0.62.0) excon (0.62.0)
fabrication (2.20.2) fabrication (2.20.2)
faker (2.2.2) faker (2.3.0)
i18n (~> 1.6.0) i18n (~> 1.6.0)
faraday (0.15.0) faraday (0.15.0)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
@ -323,7 +323,7 @@ GEM
multi_json (~> 1.12) multi_json (~> 1.12)
rdf (~> 3.0) rdf (~> 3.0)
jsonapi-renderer (0.2.2) jsonapi-renderer (0.2.2)
jwt (2.2.1) jwt (2.1.0)
kaminari (1.1.1) kaminari (1.1.1)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
kaminari-actionview (= 1.1.1) kaminari-actionview (= 1.1.1)
@ -381,7 +381,7 @@ GEM
net-scp (2.0.0) net-scp (2.0.0)
net-ssh (>= 2.6.5, < 6.0.0) net-ssh (>= 2.6.5, < 6.0.0)
net-ssh (5.2.0) net-ssh (5.2.0)
nio4r (2.4.0) nio4r (2.5.1)
nokogiri (1.10.4) nokogiri (1.10.4)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
nokogumbo (2.0.1) nokogumbo (2.0.1)
@ -447,7 +447,7 @@ GEM
pry-rails (0.3.9) pry-rails (0.3.9)
pry (>= 0.10.4) pry (>= 0.10.4)
public_suffix (4.0.1) public_suffix (4.0.1)
puma (4.1.0) puma (4.1.1)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.1.0) pundit (2.1.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
@ -640,7 +640,7 @@ GEM
unf (~> 0.1.0) unf (~> 0.1.0)
tzinfo (1.2.5) tzinfo (1.2.5)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo-data (1.2019.2) tzinfo-data (1.2019.3)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
@ -649,7 +649,7 @@ GEM
uniform_notifier (1.12.1) uniform_notifier (1.12.1)
warden (1.2.8) warden (1.2.8)
rack (>= 2.0.6) rack (>= 2.0.6)
webmock (3.7.1) webmock (3.7.3)
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
@ -657,7 +657,7 @@ GEM
activesupport (>= 4.2) activesupport (>= 4.2)
rack-proxy (>= 0.6.1) rack-proxy (>= 0.6.1)
railties (>= 4.2) railties (>= 4.2)
webpush (1.0.0) webpush (0.3.8)
hkdf (~> 0.2) hkdf (~> 0.2)
jwt (~> 2.0) jwt (~> 2.0)
websocket-driver (0.7.0) websocket-driver (0.7.0)
@ -688,7 +688,7 @@ DEPENDENCIES
capistrano-rails (~> 1.4) capistrano-rails (~> 1.4)
capistrano-rbenv (~> 2.1) capistrano-rbenv (~> 2.1)
capistrano-yarn (~> 2.0) capistrano-yarn (~> 2.0)
capybara (~> 3.28) capybara (~> 3.29)
charlock_holmes (~> 0.7.6) charlock_holmes (~> 0.7.6)
chewy (~> 5.0) chewy (~> 5.0)
cld3 (~> 3.2.4) cld3 (~> 3.2.4)
@ -703,7 +703,7 @@ DEPENDENCIES
doorkeeper (~> 5.1) doorkeeper (~> 5.1)
dotenv-rails (~> 2.7) dotenv-rails (~> 2.7)
fabrication (~> 2.20) fabrication (~> 2.20)
faker (~> 2.2) faker (~> 2.3)
fast_blank (~> 1.0) fast_blank (~> 1.0)
fastimage fastimage
fog-core (<= 2.1.0) fog-core (<= 2.1.0)

@ -2,7 +2,6 @@
module Admin module Admin
class TagsController < BaseController class TagsController < BaseController
before_action :set_tags, only: :index
before_action :set_tag, except: [:index, :batch, :approve_all, :reject_all] before_action :set_tag, except: [:index, :batch, :approve_all, :reject_all]
before_action :set_usage_by_domain, except: [:index, :batch, :approve_all, :reject_all] before_action :set_usage_by_domain, except: [:index, :batch, :approve_all, :reject_all]
before_action :set_counters, except: [:index, :batch, :approve_all, :reject_all] before_action :set_counters, except: [:index, :batch, :approve_all, :reject_all]
@ -10,6 +9,7 @@ module Admin
def index def index
authorize :tag, :index? authorize :tag, :index?
@tags = filtered_tags.page(params[:page])
@form = Form::TagBatch.new @form = Form::TagBatch.new
end end
@ -48,10 +48,6 @@ module Admin
private private
def set_tags
@tags = filtered_tags.page(params[:page])
end
def set_tag def set_tag
@tag = Tag.find(params[:id]) @tag = Tag.find(params[:id])
end end
@ -73,16 +69,11 @@ module Admin
end end
def filtered_tags def filtered_tags
scope = Tag TagFilter.new(filter_params).results
scope = scope.discoverable if filter_params[:context] == 'directory'
scope = scope.unreviewed if filter_params[:review] == 'unreviewed'
scope = scope.reviewed.order(reviewed_at: :desc) if filter_params[:review] == 'reviewed'
scope = scope.pending_review.order(requested_review_at: :desc) if filter_params[:review] == 'pending_review'
scope.order(max_score: :desc)
end end
def filter_params def filter_params
params.slice(:context, :review, :page).permit(:context, :review, :page) params.slice(:directory, :reviewed, :unreviewed, :pending_review, :page, :popular, :active, :name).permit(:directory, :reviewed, :unreviewed, :pending_review, :page, :popular, :active, :name)
end end
def tag_params def tag_params

@ -8,7 +8,6 @@ class Auth::SessionsController < Devise::SessionsController
skip_before_action :require_no_authentication, only: [:create] skip_before_action :require_no_authentication, only: [:create]
skip_before_action :require_functional! skip_before_action :require_functional!
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
prepend_before_action :set_pack prepend_before_action :set_pack
before_action :set_instance_presenter, only: [:new] before_action :set_instance_presenter, only: [:new]
@ -23,9 +22,22 @@ class Auth::SessionsController < Devise::SessionsController
end end
def create def create
super do |resource| self.resource = begin
remember_me(resource) if user_params[:email].blank? && session[:otp_user_id].present?
flash.delete(:notice) User.find(session[:otp_user_id])
else
warden.authenticate!(auth_options)
end
end
if resource.otp_required_for_login?
if user_params[:otp_attempt].present? && session[:otp_user_id].present?
authenticate_with_two_factor_via_otp(resource)
else
prompt_for_two_factor(resource)
end
else
authenticate_and_respond(resource)
end end
end end
@ -38,18 +50,6 @@ class Auth::SessionsController < Devise::SessionsController
protected protected
def find_user
if session[:otp_user_id]
User.find(session[:otp_user_id])
elsif user_params[:email]
if use_seamless_external_login? && Devise.check_at_sign && user_params[:email].index('@').nil?
User.joins(:account).find_by(accounts: { username: user_params[:email] })
else
User.find_for_authentication(email: user_params[:email])
end
end
end
def user_params def user_params
params.require(:user).permit(:email, :password, :otp_attempt) params.require(:user).permit(:email, :password, :otp_attempt)
end end
@ -72,32 +72,17 @@ class Auth::SessionsController < Devise::SessionsController
super super
end end
def two_factor_enabled?
find_user.try(:otp_required_for_login?)
end
def valid_otp_attempt?(user) def valid_otp_attempt?(user)
user.validate_and_consume_otp!(user_params[:otp_attempt]) || user.validate_and_consume_otp!(user_params[:otp_attempt]) ||
user.invalidate_otp_backup_code!(user_params[:otp_attempt]) user.invalidate_otp_backup_code!(user_params[:otp_attempt])
rescue OpenSSL::Cipher::CipherError => _error rescue OpenSSL::Cipher::CipherError
false false
end end
def authenticate_with_two_factor
user = self.resource = find_user
if user_params[:otp_attempt].present? && session[:otp_user_id]
authenticate_with_two_factor_via_otp(user)
elsif user&.valid_password?(user_params[:password])
prompt_for_two_factor(user)
end
end
def authenticate_with_two_factor_via_otp(user) def authenticate_with_two_factor_via_otp(user)
if valid_otp_attempt?(user) if valid_otp_attempt?(user)
session.delete(:otp_user_id) session.delete(:otp_user_id)
remember_me(user) authenticate_and_respond(user)
sign_in(user)
else else
flash.now[:alert] = I18n.t('users.invalid_otp_token') flash.now[:alert] = I18n.t('users.invalid_otp_token')
prompt_for_two_factor(user) prompt_for_two_factor(user)
@ -109,6 +94,13 @@ class Auth::SessionsController < Devise::SessionsController
render :two_factor render :two_factor
end end
def authenticate_and_respond(user)
sign_in(user)
remember_me(user)
respond_with user, location: after_sign_in_path_for(user)
end
private private
def set_pack def set_pack
@ -125,9 +117,11 @@ class Auth::SessionsController < Devise::SessionsController
def home_paths(resource) def home_paths(resource)
paths = [about_path] paths = [about_path]
if single_user_mode? && resource.is_a?(User) if single_user_mode? && resource.is_a?(User)
paths << short_account_path(username: resource.account) paths << short_account_path(username: resource.account)
end end
paths paths
end end

@ -14,12 +14,11 @@ class Settings::DeletesController < Settings::BaseController
end end
def destroy def destroy
if current_user.valid_password?(delete_params[:password]) if challenge_passed?
Admin::SuspensionWorker.perform_async(current_user.account_id, true) destroy_account!
sign_out
redirect_to new_user_session_path, notice: I18n.t('deletes.success_msg') redirect_to new_user_session_path, notice: I18n.t('deletes.success_msg')
else else
redirect_to settings_delete_path, alert: I18n.t('deletes.bad_password_msg') redirect_to settings_delete_path, alert: I18n.t('deletes.challenge_not_passed')
end end
end end
@ -29,11 +28,25 @@ class Settings::DeletesController < Settings::BaseController
redirect_to root_path unless Setting.open_deletion redirect_to root_path unless Setting.open_deletion
end end
def delete_params def resource_params
params.require(:form_delete_confirmation).permit(:password) params.require(:form_delete_confirmation).permit(:password, :username)
end end
def require_not_suspended! def require_not_suspended!
forbidden if current_account.suspended? forbidden if current_account.suspended?
end end
def challenge_passed?
if current_user.encrypted_password.blank?
current_account.username == resource_params[:username]
else
current_user.valid_password?(resource_params[:password])
end
end
def destroy_account!
current_account.suspend!
Admin::SuspensionWorker.perform_async(current_user.account_id, true)
sign_out
end
end end

@ -15,7 +15,7 @@ module Settings
end end
def create def create
if current_user.validate_and_consume_otp!(confirmation_params[:code]) if current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt])
flash.now[:notice] = I18n.t('two_factor_authentication.enabled_success') flash.now[:notice] = I18n.t('two_factor_authentication.enabled_success')
current_user.otp_required_for_login = true current_user.otp_required_for_login = true
@ -33,7 +33,7 @@ module Settings
private private
def confirmation_params def confirmation_params
params.require(:form_two_factor_confirmation).permit(:code) params.require(:form_two_factor_confirmation).permit(:otp_attempt)
end end
def prepare_two_factor_form def prepare_two_factor_form

@ -34,7 +34,7 @@ module Settings
private private
def confirmation_params def confirmation_params
params.require(:form_two_factor_confirmation).permit(:code) params.require(:form_two_factor_confirmation).permit(:otp_attempt)
end end
def verify_otp_required def verify_otp_required
@ -42,8 +42,8 @@ module Settings
end end
def acceptable_code? def acceptable_code?
current_user.validate_and_consume_otp!(confirmation_params[:code]) || current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt]) ||
current_user.invalidate_otp_backup_code!(confirmation_params[:code]) current_user.invalidate_otp_backup_code!(confirmation_params[:otp_attempt])
end end
end end
end end

@ -5,18 +5,22 @@ module WellKnown
include RoutingHelper include RoutingHelper
before_action { response.headers['Vary'] = 'Accept' } before_action { response.headers['Vary'] = 'Accept' }
before_action :set_account
before_action :check_account_suspension
def show rescue_from ActiveRecord::RecordNotFound, ActionController::ParameterMissing, with: :not_found
@account = Account.find_local!(username_from_resource)
def show
expires_in 3.days, public: true expires_in 3.days, public: true
render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json' render json: @account, serializer: WebfingerSerializer, content_type: 'application/jrd+json'
rescue ActiveRecord::RecordNotFound, ActionController::ParameterMissing
head 404
end end
private private
def set_account
@account = Account.find_local!(username_from_resource)
end
def username_from_resource def username_from_resource
resource_user = resource_param resource_user = resource_param
username, domain = resource_user.split('@') username, domain = resource_user.split('@')
@ -28,5 +32,17 @@ module WellKnown
def resource_param def resource_param
params.require(:resource) params.require(:resource)
end end
def check_account_suspension
expires_in(3.minutes, public: true) && gone if @account.suspended?
end
def not_found
head 404
end
def gone
head 410
end
end end
end end

@ -5,7 +5,7 @@ module Admin::FilterHelper
REPORT_FILTERS = %i(resolved account_id target_account_id).freeze REPORT_FILTERS = %i(resolved account_id target_account_id).freeze
INVITE_FILTER = %i(available expired).freeze INVITE_FILTER = %i(available expired).freeze
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
TAGS_FILTERS = %i(context review).freeze TAGS_FILTERS = %i(directory reviewed unreviewed pending_review popular active name).freeze
INSTANCES_FILTERS = %i(limited by_domain).freeze INSTANCES_FILTERS = %i(limited by_domain).freeze
FOLLOWERS_FILTERS = %i(relationship status by_domain activity order).freeze FOLLOWERS_FILTERS = %i(relationship status by_domain activity order).freeze

@ -42,7 +42,8 @@ module SettingsHelper
no: 'Norsk', no: 'Norsk',
oc: 'Occitan', oc: 'Occitan',
pl: 'Polski', pl: 'Polski',
pt: 'Português (Portugal)', pt: 'Português',
'pt-PT': 'Português (Portugal)',
'pt-BR': 'Português (Brasil)', 'pt-BR': 'Português (Brasil)',
ro: 'Română', ro: 'Română',
ru: 'Русский', ru: 'Русский',

@ -151,7 +151,7 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
dispatch(importFetchedAccounts(response.data.map(item => item.account))); dispatch(importFetchedAccounts(response.data.map(item => item.account)));
dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent && preferPendingItems)); dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
fetchRelatedRelationships(dispatch, response.data); fetchRelatedRelationships(dispatch, response.data);
done(); done();
}).catch(error => { }).catch(error => {
@ -168,11 +168,12 @@ export function expandNotificationsRequest(isLoadingMore) {
}; };
}; };
export function expandNotificationsSuccess(notifications, next, isLoadingMore, usePendingItems) { export function expandNotificationsSuccess(notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) {
return { return {
type: NOTIFICATIONS_EXPAND_SUCCESS, type: NOTIFICATIONS_EXPAND_SUCCESS,
notifications, notifications,
next, next,
isLoadingRecent: isLoadingRecent,
usePendingItems, usePendingItems,
skipLoading: !isLoadingMore, skipLoading: !isLoadingMore,
}; };

@ -32,8 +32,38 @@ class Poll extends ImmutablePureComponent {
state = { state = {
selected: {}, selected: {},
expired: null,
}; };
static getDerivedStateFromProps (props, state) {
const { poll, intl } = props;
const expired = poll.get('expired') || (new Date(poll.get('expires_at'))).getTime() < intl.now();
return (expired === state.expired) ? null : { expired };
}
componentDidMount () {
this._setupTimer();
}
componentDidUpdate () {
this._setupTimer();
}
componentWillUnmount () {
clearTimeout(this._timer);
}
_setupTimer () {
const { poll, intl } = this.props;
clearTimeout(this._timer);
if (!this.state.expired) {
const delay = (new Date(poll.get('expires_at'))).getTime() - intl.now();
this._timer = setTimeout(() => {
this.setState({ expired: true });
}, delay);
}
}
handleOptionChange = e => { handleOptionChange = e => {
const { target: { value } } = e; const { target: { value } } = e;
@ -68,12 +98,11 @@ class Poll extends ImmutablePureComponent {
this.props.dispatch(fetchPoll(this.props.poll.get('id'))); this.props.dispatch(fetchPoll(this.props.poll.get('id')));
}; };
renderOption (option, optionIndex) { renderOption (option, optionIndex, showResults) {
const { poll, disabled } = this.props; const { poll, disabled } = this.props;
const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100; const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count')); const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
const active = !!this.state.selected[`${optionIndex}`]; const active = !!this.state.selected[`${optionIndex}`];
const showResults = poll.get('voted') || poll.get('expired');
let titleEmojified = option.get('title_emojified'); let titleEmojified = option.get('title_emojified');
if (!titleEmojified) { if (!titleEmojified) {
@ -112,19 +141,20 @@ class Poll extends ImmutablePureComponent {
render () { render () {
const { poll, intl } = this.props; const { poll, intl } = this.props;
const { expired } = this.state;
if (!poll) { if (!poll) {
return null; return null;
} }
const timeRemaining = poll.get('expired') ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />; const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
const showResults = poll.get('voted') || poll.get('expired'); const showResults = poll.get('voted') || expired;
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item); const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
return ( return (
<div className='poll'> <div className='poll'>
<ul> <ul>
{poll.get('options').map((option, i) => this.renderOption(option, i))} {poll.get('options').map((option, i) => this.renderOption(option, i, showResults))}
</ul> </ul>
<div className='poll__footer'> <div className='poll__footer'>

@ -172,8 +172,9 @@ export default class ScrollableList extends PureComponent {
const someItemInserted = React.Children.count(prevProps.children) > 0 && const someItemInserted = React.Children.count(prevProps.children) > 0 &&
React.Children.count(prevProps.children) < React.Children.count(this.props.children) && React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props); this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
const pendingChanged = (prevProps.numPending > 0) !== (this.props.numPending > 0);
if (someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently)) { if (pendingChanged || someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently)) {
return this.getScrollHeight() - this.getScrollTop(); return this.getScrollHeight() - this.getScrollTop();
} else { } else {
return null; return null;
@ -261,6 +262,13 @@ export default class ScrollableList extends PureComponent {
handleLoadPending = e => { handleLoadPending = e => {
e.preventDefault(); e.preventDefault();
this.props.onLoadPending(); this.props.onLoadPending();
// Prevent the weird scroll-jumping behavior, as we explicitly don't want to
// scroll to top, and we know the scroll height is going to change
this.scrollToTopOnMouseIdle = false;
this.lastScrollWasSynthetic = false;
this.clearMouseIdleTimer();
this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);
this.mouseMovedRecently = true;
} }
render () { render () {

@ -18,9 +18,10 @@ const mapStateToProps = (state, { onlyMedia, columnId }) => {
const uuid = columnId; const uuid = columnId;
const columns = state.getIn(['settings', 'columns']); const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid); const index = columns.findIndex(c => c.get('uuid') === uuid);
const timelineState = state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`]);
return { return {
hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0, hasUnread: !!timelineState && (timelineState.get('unread') > 0 || timelineState.get('pendingItems').size > 0),
onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']), onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']),
}; };
}; };

@ -39,7 +39,7 @@ const mapStateToProps = state => ({
showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']), showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
notifications: getNotifications(state), notifications: getNotifications(state),
isLoading: state.getIn(['notifications', 'isLoading'], true), isLoading: state.getIn(['notifications', 'isLoading'], true),
isUnread: state.getIn(['notifications', 'unread']) > 0, isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0,
hasMore: state.getIn(['notifications', 'hasMore']), hasMore: state.getIn(['notifications', 'hasMore']),
numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size, numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
}); });

@ -226,7 +226,7 @@ class FocalPointModal extends ImmutablePureComponent {
<CharacterCounter max={1500} text={detecting ? '' : description} /> <CharacterCounter max={1500} text={detecting ? '' : description} />
</div> </div>
<Button disabled={!dirty || detecting || length(description) > 420} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} /> <Button disabled={!dirty || detecting || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} />
</div> </div>
<div className='focal-point-modal__content'> <div className='focal-point-modal__content'>

@ -66,6 +66,7 @@ const mapStateToProps = state => ({
isComposing: state.getIn(['compose', 'is_composing']), isComposing: state.getIn(['compose', 'is_composing']),
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0, hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0, hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null, dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
}); });
@ -232,6 +233,7 @@ class UI extends React.PureComponent {
isComposing: PropTypes.bool, isComposing: PropTypes.bool,
hasComposingText: PropTypes.bool, hasComposingText: PropTypes.bool,
hasMediaAttachments: PropTypes.bool, hasMediaAttachments: PropTypes.bool,
canUploadMore: PropTypes.bool,
location: PropTypes.object, location: PropTypes.object,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
dropdownMenuIsOpen: PropTypes.bool, dropdownMenuIsOpen: PropTypes.bool,
@ -278,13 +280,14 @@ class UI extends React.PureComponent {
this.dragTargets.push(e.target); this.dragTargets.push(e.target);
} }
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')) { if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore) {
this.setState({ draggingOver: true }); this.setState({ draggingOver: true });
} }
} }
handleDragOver = (e) => { handleDragOver = (e) => {
if (this.dataTransferIsText(e.dataTransfer)) return false; if (this.dataTransferIsText(e.dataTransfer)) return false;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -299,12 +302,13 @@ class UI extends React.PureComponent {
handleDrop = (e) => { handleDrop = (e) => {
if (this.dataTransferIsText(e.dataTransfer)) return; if (this.dataTransferIsText(e.dataTransfer)) return;
e.preventDefault(); e.preventDefault();
this.setState({ draggingOver: false }); this.setState({ draggingOver: false });
this.dragTargets = []; this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1) { if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore) {
this.props.dispatch(uploadCompose(e.dataTransfer.files)); this.props.dispatch(uploadCompose(e.dataTransfer.files));
} }
} }

@ -63,6 +63,7 @@
"column.notifications": "الإخطارات", "column.notifications": "الإخطارات",
"column.pins": "التبويقات المثبتة", "column.pins": "التبويقات المثبتة",
"column.public": "الخيط العام الموحد", "column.public": "الخيط العام الموحد",
"column.status": "Toot",
"column_back_button.label": "العودة", "column_back_button.label": "العودة",
"column_header.hide_settings": "إخفاء الإعدادات", "column_header.hide_settings": "إخفاء الإعدادات",
"column_header.moveLeft_settings": "نقل القائمة إلى اليسار", "column_header.moveLeft_settings": "نقل القائمة إلى اليسار",
@ -112,8 +113,8 @@
"confirmations.unfollow.message": "متأكد من أنك تريد إلغاء متابعة {name} ؟", "confirmations.unfollow.message": "متأكد من أنك تريد إلغاء متابعة {name} ؟",
"directory.federated": "From known fediverse", "directory.federated": "From known fediverse",
"directory.local": "From {domain} only", "directory.local": "From {domain} only",
"directory.new_arrivals": "New arrivals", "directory.new_arrivals": "الوافدون الجُدد",
"directory.recently_active": "Recently active", "directory.recently_active": "نشط مؤخرا",
"embed.instructions": "يمكنكم إدماج هذا المنشور على موقعكم الإلكتروني عن طريق نسخ الشفرة أدناه.", "embed.instructions": "يمكنكم إدماج هذا المنشور على موقعكم الإلكتروني عن طريق نسخ الشفرة أدناه.",
"embed.preview": "هكذا ما سوف يبدو عليه:", "embed.preview": "هكذا ما سوف يبدو عليه:",
"emoji_button.activity": "الأنشطة", "emoji_button.activity": "الأنشطة",
@ -368,7 +369,7 @@
"status.show_more": "أظهر المزيد", "status.show_more": "أظهر المزيد",
"status.show_more_all": "توسيع الكل", "status.show_more_all": "توسيع الكل",
"status.show_thread": "الكشف عن المحادثة", "status.show_thread": "الكشف عن المحادثة",
"status.uncached_media_warning": "Not available", "status.uncached_media_warning": "غير متوفر",
"status.unmute_conversation": "فك الكتم عن المحادثة", "status.unmute_conversation": "فك الكتم عن المحادثة",
"status.unpin": "فك التدبيس من الملف الشخصي", "status.unpin": "فك التدبيس من الملف الشخصي",
"suggestions.dismiss": "إلغاء الاقتراح", "suggestions.dismiss": "إلغاء الاقتراح",

@ -63,6 +63,7 @@
"column.notifications": "Avisos", "column.notifications": "Avisos",
"column.pins": "Toots fixaos", "column.pins": "Toots fixaos",
"column.public": "Llinia temporal federada", "column.public": "Llinia temporal federada",
"column.status": "Toot",
"column_back_button.label": "Atrás", "column_back_button.label": "Atrás",
"column_header.hide_settings": "Hide settings", "column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Mover la columna a la esquierda", "column_header.moveLeft_settings": "Mover la columna a la esquierda",

@ -63,6 +63,7 @@
"column.notifications": "Известия", "column.notifications": "Известия",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "Публичен канал", "column.public": "Публичен канал",
"column.status": "Toot",
"column_back_button.label": "Назад", "column_back_button.label": "Назад",
"column_header.hide_settings": "Hide settings", "column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left", "column_header.moveLeft_settings": "Move column to the left",

@ -63,6 +63,7 @@
"column.notifications": "প্রজ্ঞাপনগুলো", "column.notifications": "প্রজ্ঞাপনগুলো",
"column.pins": "পিন করা টুট", "column.pins": "পিন করা টুট",
"column.public": "যুক্ত সময়রেখা", "column.public": "যুক্ত সময়রেখা",
"column.status": "Toot",
"column_back_button.label": "পেছনে", "column_back_button.label": "পেছনে",
"column_header.hide_settings": "সেটিংগুলো সরান", "column_header.hide_settings": "সেটিংগুলো সরান",
"column_header.moveLeft_settings": "কলমটা বামে সরান", "column_header.moveLeft_settings": "কলমটা বামে সরান",

@ -0,0 +1,414 @@
{
"account.add_or_remove_from_list": "Add or Remove from lists",
"account.badges.bot": "Bot",
"account.block": "Block @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.cancel_follow_request": "Cancel follow request",
"account.direct": "Direct message @{name}",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Edit profile",
"account.endorse": "Feature on profile",
"account.follow": "Follow",
"account.followers": "Followers",
"account.followers.empty": "No one follows this user yet.",
"account.follows": "Follows",
"account.follows.empty": "This user doesn't follow anyone yet.",
"account.follows_you": "Follows you",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.last_status": "Last active",
"account.link_verified_on": "Ownership of this link was checked on {date}",
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
"account.media": "Media",
"account.mention": "Mention @{name}",
"account.moved_to": "{name} has moved to:",
"account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.never_active": "Never",
"account.posts": "Toots",
"account.posts_with_replies": "Toots and replies",
"account.report": "Report @{name}",
"account.requested": "Awaiting approval",
"account.share": "Share @{name}'s profile",
"account.show_reblogs": "Show boosts from @{name}",
"account.unblock": "Unblock @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unendorse": "Don't feature on profile",
"account.unfollow": "Unfollow",
"account.unmute": "Unmute @{name}",
"account.unmute_notifications": "Unmute notifications from @{name}",
"alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",
"bundle_column_error.title": "Network error",
"bundle_modal_error.close": "Close",
"bundle_modal_error.message": "Something went wrong while loading this component.",
"bundle_modal_error.retry": "Try again",
"column.blocks": "Blocked users",
"column.community": "Local timeline",
"column.direct": "Direct messages",
"column.directory": "Browse profiles",
"column.domain_blocks": "Hidden domains",
"column.favourites": "Favourites",
"column.follow_requests": "Follow requests",
"column.home": "Home",
"column.lists": "Lists",
"column.mutes": "Muted users",
"column.notifications": "Notifications",
"column.pins": "Pinned toot",
"column.public": "Federated timeline",
"column.status": "Toot",
"column_back_button.label": "Back",
"column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left",
"column_header.moveRight_settings": "Move column to the right",
"column_header.pin": "Pin",
"column_header.show_settings": "Show settings",
"column_header.unpin": "Unpin",
"column_subheading.settings": "Settings",
"community.column_settings.media_only": "Media only",
"compose_form.direct_message_warning": "This toot will only be sent to all the mentioned users.",
"compose_form.direct_message_warning_learn_more": "Learn more",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "What is on your mind?",
"compose_form.poll.add_option": "Add a choice",
"compose_form.poll.duration": "Poll duration",
"compose_form.poll.option_placeholder": "Choice {number}",
"compose_form.poll.remove_option": "Remove this choice",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Mark media as sensitive",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Write your warning here",
"confirmation_modal.cancel": "Cancel",
"confirmations.block.block_and_report": "Block & Report",
"confirmations.block.confirm": "Block",
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
"confirmations.logout.confirm": "Log out",
"confirmations.logout.message": "Are you sure you want to log out?",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"confirmations.redraft.confirm": "Delete & redraft",
"confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
"confirmations.reply.confirm": "Reply",
"confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
"directory.federated": "From known fediverse",
"directory.local": "From {domain} only",
"directory.new_arrivals": "New arrivals",
"directory.recently_active": "Recently active",
"embed.instructions": "Embed this status on your website by copying the code below.",
"embed.preview": "Here is what it will look like:",
"emoji_button.activity": "Activity",
"emoji_button.custom": "Custom",
"emoji_button.flags": "Flags",
"emoji_button.food": "Food & Drink",
"emoji_button.label": "Insert emoji",
"emoji_button.nature": "Nature",
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objects",
"emoji_button.people": "People",
"emoji_button.recent": "Frequently used",
"emoji_button.search": "Search...",
"emoji_button.search_results": "Search results",
"emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places",
"empty_column.account_timeline": "No toots here!",
"empty_column.account_unavailable": "Profile unavailable",
"empty_column.blocks": "You haven't blocked any users yet.",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.domain_blocks": "There are no hidden domains yet.",
"empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
"empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
"empty_column.home.public_timeline": "the public timeline",
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
"empty_column.mutes": "You haven't muted any users yet.",
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"getting_started.developers": "Developers",
"getting_started.directory": "Profile directory",
"getting_started.documentation": "Documentation",
"getting_started.heading": "Getting started",
"getting_started.invite": "Invite people",
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
"getting_started.security": "Security",
"getting_started.terms": "Terms of service",
"hashtag.column_header.tag_mode.all": "and {additional}",
"hashtag.column_header.tag_mode.any": "or {additional}",
"hashtag.column_header.tag_mode.none": "without {additional}",
"hashtag.column_settings.select.no_options_message": "No suggestions found",
"hashtag.column_settings.select.placeholder": "Enter hashtags…",
"hashtag.column_settings.tag_mode.all": "All of these",
"hashtag.column_settings.tag_mode.any": "Any of these",
"hashtag.column_settings.tag_mode.none": "None of these",
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
"home.column_settings.basic": "Basic",
"home.column_settings.show_reblogs": "Show boosts",
"home.column_settings.show_replies": "Show replies",
"home.column_settings.update_live": "Update in real-time",
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
"introduction.federation.action": "Next",
"introduction.federation.federated.headline": "Federated",
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
"introduction.federation.home.headline": "Home",
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
"introduction.federation.local.headline": "Local",
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish toot-orial!",
"introduction.interactions.favourite.headline": "Favourite",
"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.reblog.headline": "Boost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
"introduction.interactions.reply.headline": "Reply",
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
"introduction.welcome.action": "Let's go!",
"introduction.welcome.headline": "First steps",
"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": "to navigate back",
"keyboard_shortcuts.blocked": "to open blocked users list",
"keyboard_shortcuts.boost": "to boost",
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.direct": "to open direct messages column",
"keyboard_shortcuts.down": "to move down in the list",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "to favourite",
"keyboard_shortcuts.favourites": "to open favourites list",
"keyboard_shortcuts.federated": "to open federated timeline",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.home": "to open home timeline",
"keyboard_shortcuts.hotkey": "Hotkey",
"keyboard_shortcuts.legend": "to display this legend",
"keyboard_shortcuts.local": "to open local timeline",
"keyboard_shortcuts.mention": "to mention author",
"keyboard_shortcuts.muted": "to open muted users list",
"keyboard_shortcuts.my_profile": "to open your profile",
"keyboard_shortcuts.notifications": "to open notifications column",
"keyboard_shortcuts.pinned": "to open pinned toots list",
"keyboard_shortcuts.profile": "to open author's profile",
"keyboard_shortcuts.reply": "to reply",
"keyboard_shortcuts.requests": "to open follow requests list",
"keyboard_shortcuts.search": "to focus search",
"keyboard_shortcuts.start": "to open \"get started\" column",
"keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
"keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
"keyboard_shortcuts.toot": "to start a brand new toot",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "to move up in the list",
"lightbox.close": "Close",
"lightbox.next": "Next",
"lightbox.previous": "Previous",
"lightbox.view_context": "View context",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.delete": "Delete list",
"lists.edit": "Edit list",
"lists.edit.submit": "Change title",
"lists.new.create": "Add list",
"lists.new.title_placeholder": "New list title",
"lists.search": "Search among people you follow",
"lists.subheading": "Your lists",
"load_pending": "{count, plural, one {# new item} other {# new items}}",
"loading_indicator.label": "Loading...",
"media_gallery.toggle_visible": "Toggle visibility",
"missing_indicator.label": "Not found",
"missing_indicator.sublabel": "This resource could not be found",
"mute_modal.hide_notifications": "Hide notifications from this user?",
"navigation_bar.apps": "Mobile apps",
"navigation_bar.blocks": "Blocked users",
"navigation_bar.community_timeline": "Local timeline",
"navigation_bar.compose": "Compose new toot",
"navigation_bar.direct": "Direct messages",
"navigation_bar.discover": "Discover",
"navigation_bar.domain_blocks": "Hidden domains",
"navigation_bar.edit_profile": "Edit profile",
"navigation_bar.favourites": "Favourites",
"navigation_bar.filters": "Muted words",
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.info": "About this server",
"navigation_bar.keyboard_shortcuts": "Hotkeys",
"navigation_bar.lists": "Lists",
"navigation_bar.logout": "Logout",
"navigation_bar.mutes": "Muted users",
"navigation_bar.personal": "Personal",
"navigation_bar.pins": "Pinned toots",
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
"navigation_bar.security": "Security",
"notification.and_n_others": "and {count, plural, one {# other} other {# others}}",
"notification.favourite": "{name} favourited your status",
"notification.follow": "{name} followed you",
"notification.mention": "{name} mentioned you",
"notification.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} boosted your status",
"notifications.clear": "Clear notifications",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
"notifications.column_settings.alert": "Desktop notifications",
"notifications.column_settings.favourite": "Favourites:",
"notifications.column_settings.filter_bar.advanced": "Display all categories",
"notifications.column_settings.filter_bar.category": "Quick filter bar",
"notifications.column_settings.filter_bar.show": "Show",
"notifications.column_settings.follow": "New followers:",
"notifications.column_settings.mention": "Mentions:",
"notifications.column_settings.poll": "Poll results:",
"notifications.column_settings.push": "Push notifications",
"notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "Show in column",
"notifications.column_settings.sound": "Play sound",
"notifications.filter.all": "All",
"notifications.filter.boosts": "Boosts",
"notifications.filter.favourites": "Favourites",
"notifications.filter.follows": "Follows",
"notifications.filter.mentions": "Mentions",
"notifications.filter.polls": "Poll results",
"notifications.group": "{count} notifications",
"poll.closed": "Closed",
"poll.refresh": "Refresh",
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
"poll.vote": "Vote",
"poll_button.add_poll": "Add a poll",
"poll_button.remove_poll": "Remove poll",
"privacy.change": "Adjust status privacy",
"privacy.direct.long": "Post to mentioned users only",
"privacy.direct.short": "Direct",
"privacy.private.long": "Post to followers only",
"privacy.private.short": "Followers-only",
"privacy.public.long": "Post to public timelines",
"privacy.public.short": "Public",
"privacy.unlisted.long": "Do not show in public timelines",
"privacy.unlisted.short": "Unlisted",
"regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "now",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancel",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Additional comments",
"report.submit": "Submit",
"report.target": "Report {target}",
"search.placeholder": "Search",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "Block @{name}",
"status.cancel_reblog_private": "Unboost",
"status.cannot_reblog": "This post cannot be boosted",
"status.copy": "Copy link to status",
"status.delete": "Delete",
"status.detailed_status": "Detailed conversation view",
"status.direct": "Direct message @{name}",
"status.embed": "Embed",
"status.favourite": "Favourite",
"status.filtered": "Filtered",
"status.load_more": "Load more",
"status.media_hidden": "Media hidden",
"status.mention": "Mention @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.read_more": "Read more",
"status.reblog": "Boost",
"status.reblog_private": "Boost to original audience",
"status.reblogged_by": "{name} boosted",
"status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
"status.redraft": "Delete & re-draft",
"status.reply": "Reply",
"status.replyAll": "Reply to thread",
"status.report": "Report @{name}",
"status.sensitive_warning": "Sensitive content",
"status.share": "Share",
"status.show_less": "Show less",
"status.show_less_all": "Show less for all",
"status.show_more": "Show more",
"status.show_more_all": "Show more for all",
"status.show_thread": "Show thread",
"status.uncached_media_warning": "Not available",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"suggestions.dismiss": "Dismiss suggestion",
"suggestions.header": "You might be interested in…",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Local",
"tabs_bar.notifications": "Notifications",
"tabs_bar.search": "Search",
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
"time_remaining.moments": "Moments remaining",
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
"trends.trending_now": "Trending now",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
"upload_area.title": "Drag & drop to upload",
"upload_button.label": "Add media ({formats})",
"upload_error.limit": "File upload limit exceeded.",
"upload_error.poll": "File upload not allowed with polls.",
"upload_form.description": "Describe for the visually impaired",
"upload_form.edit": "Edit",
"upload_form.undo": "Delete",
"upload_modal.analyzing_picture": "Analyzing picture…",
"upload_modal.apply": "Apply",
"upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
"upload_modal.detect_text": "Detect text from picture",
"upload_modal.edit_media": "Edit media",
"upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
"upload_modal.preview_label": "Preview ({ratio})",
"upload_progress.label": "Uploading...",
"video.close": "Close video",
"video.exit_fullscreen": "Exit full screen",
"video.expand": "Expand video",
"video.fullscreen": "Full screen",
"video.hide": "Hide video",
"video.mute": "Mute sound",
"video.pause": "Pause",
"video.play": "Play",
"video.unmute": "Unmute sound"
}

@ -63,6 +63,7 @@
"column.notifications": "Notificacions", "column.notifications": "Notificacions",
"column.pins": "Toots fixats", "column.pins": "Toots fixats",
"column.public": "Línia de temps federada", "column.public": "Línia de temps federada",
"column.status": "Toot",
"column_back_button.label": "Enrere", "column_back_button.label": "Enrere",
"column_header.hide_settings": "Amaga la configuració", "column_header.hide_settings": "Amaga la configuració",
"column_header.moveLeft_settings": "Mou la columna cap a l'esquerra", "column_header.moveLeft_settings": "Mou la columna cap a l'esquerra",

@ -63,6 +63,7 @@
"column.notifications": "Nutificazione", "column.notifications": "Nutificazione",
"column.pins": "Statuti puntarulati", "column.pins": "Statuti puntarulati",
"column.public": "Linea pubblica glubale", "column.public": "Linea pubblica glubale",
"column.status": "Toot",
"column_back_button.label": "Ritornu", "column_back_button.label": "Ritornu",
"column_header.hide_settings": "Piattà i parametri", "column_header.hide_settings": "Piattà i parametri",
"column_header.moveLeft_settings": "Spiazzà à manca", "column_header.moveLeft_settings": "Spiazzà à manca",

@ -63,6 +63,7 @@
"column.notifications": "Oznámení", "column.notifications": "Oznámení",
"column.pins": "Připnuté tooty", "column.pins": "Připnuté tooty",
"column.public": "Federovaná časová osa", "column.public": "Federovaná časová osa",
"column.status": "Toot",
"column_back_button.label": "Zpět", "column_back_button.label": "Zpět",
"column_header.hide_settings": "Skrýt nastavení", "column_header.hide_settings": "Skrýt nastavení",
"column_header.moveLeft_settings": "Posunout sloupec doleva", "column_header.moveLeft_settings": "Posunout sloupec doleva",

@ -63,6 +63,7 @@
"column.notifications": "Hysbysiadau", "column.notifications": "Hysbysiadau",
"column.pins": "Tŵtiau wedi eu pinio", "column.pins": "Tŵtiau wedi eu pinio",
"column.public": "Ffrwd y ffederasiwn", "column.public": "Ffrwd y ffederasiwn",
"column.status": "Toot",
"column_back_button.label": "Nôl", "column_back_button.label": "Nôl",
"column_header.hide_settings": "Cuddio dewisiadau", "column_header.hide_settings": "Cuddio dewisiadau",
"column_header.moveLeft_settings": "Symud y golofn i'r chwith", "column_header.moveLeft_settings": "Symud y golofn i'r chwith",

@ -63,6 +63,7 @@
"column.notifications": "Notifikationer", "column.notifications": "Notifikationer",
"column.pins": "Fastgjorte trut", "column.pins": "Fastgjorte trut",
"column.public": "Fælles tidslinje", "column.public": "Fælles tidslinje",
"column.status": "Toot",
"column_back_button.label": "Tilbage", "column_back_button.label": "Tilbage",
"column_header.hide_settings": "Skjul indstillinger", "column_header.hide_settings": "Skjul indstillinger",
"column_header.moveLeft_settings": "Flyt kolonne til venstre", "column_header.moveLeft_settings": "Flyt kolonne til venstre",

@ -63,6 +63,7 @@
"column.notifications": "Mitteilungen", "column.notifications": "Mitteilungen",
"column.pins": "Angeheftete Beiträge", "column.pins": "Angeheftete Beiträge",
"column.public": "Föderierte Zeitleiste", "column.public": "Föderierte Zeitleiste",
"column.status": "Toot",
"column_back_button.label": "Zurück", "column_back_button.label": "Zurück",
"column_header.hide_settings": "Einstellungen verbergen", "column_header.hide_settings": "Einstellungen verbergen",
"column_header.moveLeft_settings": "Spalte nach links verschieben", "column_header.moveLeft_settings": "Spalte nach links verschieben",

@ -1132,28 +1132,24 @@
{ {
"descriptors": [ "descriptors": [
{ {
"defaultMessage": "Delete", "defaultMessage": "Uploading...",
"id": "upload_form.undo" "id": "upload_progress.label"
},
{
"defaultMessage": "Edit",
"id": "upload_form.edit"
} }
], ],
"path": "app/javascript/mastodon/features/compose/components/upload.json" "path": "app/javascript/mastodon/features/compose/components/upload_progress.json"
}, },
{ {
"descriptors": [ "descriptors": [
{ {
"defaultMessage": "Are you sure you want to log out?", "defaultMessage": "Delete",
"id": "confirmations.logout.message" "id": "upload_form.undo"
}, },
{ {
"defaultMessage": "Log out", "defaultMessage": "Edit",
"id": "confirmations.logout.confirm" "id": "upload_form.edit"
} }
], ],
"path": "app/javascript/mastodon/features/compose/containers/navigation_container.json" "path": "app/javascript/mastodon/features/compose/components/upload.json"
}, },
{ {
"descriptors": [ "descriptors": [
@ -1584,10 +1580,6 @@
}, },
{ {
"descriptors": [ "descriptors": [
{
"defaultMessage": "Basic",
"id": "home.column_settings.basic"
},
{ {
"defaultMessage": "Show boosts", "defaultMessage": "Show boosts",
"id": "home.column_settings.show_reblogs" "id": "home.column_settings.show_reblogs"
@ -1969,6 +1961,14 @@
"defaultMessage": "Push notifications", "defaultMessage": "Push notifications",
"id": "notifications.column_settings.push" "id": "notifications.column_settings.push"
}, },
{
"defaultMessage": "Basic",
"id": "home.column_settings.basic"
},
{
"defaultMessage": "Update in real-time",
"id": "home.column_settings.update_live"
},
{ {
"defaultMessage": "Quick filter bar", "defaultMessage": "Quick filter bar",
"id": "notifications.column_settings.filter_bar.category" "id": "notifications.column_settings.filter_bar.category"
@ -2027,6 +2027,10 @@
}, },
{ {
"descriptors": [ "descriptors": [
{
"defaultMessage": "and {count, plural, one {# other} other {# others}}",
"id": "notification.and_n_others"
},
{ {
"defaultMessage": "{name} followed you", "defaultMessage": "{name} followed you",
"id": "notification.follow" "id": "notification.follow"
@ -2283,6 +2287,10 @@
"defaultMessage": "Block & Report", "defaultMessage": "Block & Report",
"id": "confirmations.block.block_and_report" "id": "confirmations.block.block_and_report"
}, },
{
"defaultMessage": "Toot",
"id": "column.status"
},
{ {
"defaultMessage": "Are you sure you want to block {name}?", "defaultMessage": "Are you sure you want to block {name}?",
"id": "confirmations.block.message" "id": "confirmations.block.message"

@ -63,6 +63,7 @@
"column.notifications": "Ειδοποιήσεις", "column.notifications": "Ειδοποιήσεις",
"column.pins": "Καρφιτσωμένα τουτ", "column.pins": "Καρφιτσωμένα τουτ",
"column.public": "Ομοσπονδιακή ροή", "column.public": "Ομοσπονδιακή ροή",
"column.status": "Toot",
"column_back_button.label": "Πίσω", "column_back_button.label": "Πίσω",
"column_header.hide_settings": "Απόκρυψη ρυθμίσεων", "column_header.hide_settings": "Απόκρυψη ρυθμίσεων",
"column_header.moveLeft_settings": "Μεταφορά κολώνας αριστερά", "column_header.moveLeft_settings": "Μεταφορά κολώνας αριστερά",

@ -63,6 +63,7 @@
"column.notifications": "Notifications", "column.notifications": "Notifications",
"column.pins": "Pinned toots", "column.pins": "Pinned toots",
"column.public": "Federated timeline", "column.public": "Federated timeline",
"column.status": "Toot",
"column_back_button.label": "Back", "column_back_button.label": "Back",
"column_header.hide_settings": "Hide settings", "column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left", "column_header.moveLeft_settings": "Move column to the left",
@ -173,6 +174,7 @@
"home.column_settings.basic": "Basic", "home.column_settings.basic": "Basic",
"home.column_settings.show_reblogs": "Show boosts", "home.column_settings.show_reblogs": "Show boosts",
"home.column_settings.show_replies": "Show replies", "home.column_settings.show_replies": "Show replies",
"home.column_settings.update_live": "Update in real-time",
"intervals.full.days": "{number, plural, one {# day} other {# days}}", "intervals.full.days": "{number, plural, one {# day} other {# days}}",
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}", "intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}", "intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
@ -267,6 +269,7 @@
"navigation_bar.preferences": "Preferences", "navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline", "navigation_bar.public_timeline": "Federated timeline",
"navigation_bar.security": "Security", "navigation_bar.security": "Security",
"notification.and_n_others": "and {count, plural, one {# other} other {# others}}",
"notification.favourite": "{name} favourited your status", "notification.favourite": "{name} favourited your status",
"notification.follow": "{name} followed you", "notification.follow": "{name} followed you",
"notification.mention": "{name} mentioned you", "notification.mention": "{name} mentioned you",

@ -63,6 +63,7 @@
"column.notifications": "Sciigoj", "column.notifications": "Sciigoj",
"column.pins": "Alpinglitaj mesaĝoj", "column.pins": "Alpinglitaj mesaĝoj",
"column.public": "Fratara tempolinio", "column.public": "Fratara tempolinio",
"column.status": "Toot",
"column_back_button.label": "Reveni", "column_back_button.label": "Reveni",
"column_header.hide_settings": "Kaŝi agordojn", "column_header.hide_settings": "Kaŝi agordojn",
"column_header.moveLeft_settings": "Movi kolumnon maldekstren", "column_header.moveLeft_settings": "Movi kolumnon maldekstren",

@ -4,7 +4,7 @@
"account.block": "Bloquear a @{name}", "account.block": "Bloquear a @{name}",
"account.block_domain": "Ocultar todo de {domain}", "account.block_domain": "Ocultar todo de {domain}",
"account.blocked": "Bloqueado", "account.blocked": "Bloqueado",
"account.cancel_follow_request": "Cancel follow request", "account.cancel_follow_request": "Cancelar la solicitud de seguimiento",
"account.direct": "Mensaje directo a @{name}", "account.direct": "Mensaje directo a @{name}",
"account.domain_blocked": "Dominio oculto", "account.domain_blocked": "Dominio oculto",
"account.edit_profile": "Editar perfil", "account.edit_profile": "Editar perfil",
@ -16,7 +16,7 @@
"account.follows.empty": "Este usuario todavía no sigue a nadie.", "account.follows.empty": "Este usuario todavía no sigue a nadie.",
"account.follows_you": "Te sigue", "account.follows_you": "Te sigue",
"account.hide_reblogs": "Ocultar retoots de @{name}", "account.hide_reblogs": "Ocultar retoots de @{name}",
"account.last_status": "Last active", "account.last_status": "Última actividad",
"account.link_verified_on": "El proprietario de este link fue comprobado el {date}", "account.link_verified_on": "El proprietario de este link fue comprobado el {date}",
"account.locked_info": "El estado de privacidad de esta cuenta està configurado como bloqueado. El proprietario debe revisar manualmente quien puede seguirle.", "account.locked_info": "El estado de privacidad de esta cuenta està configurado como bloqueado. El proprietario debe revisar manualmente quien puede seguirle.",
"account.media": "Multimedia", "account.media": "Multimedia",
@ -25,7 +25,7 @@
"account.mute": "Silenciar a @{name}", "account.mute": "Silenciar a @{name}",
"account.mute_notifications": "Silenciar notificaciones de @{name}", "account.mute_notifications": "Silenciar notificaciones de @{name}",
"account.muted": "Silenciado", "account.muted": "Silenciado",
"account.never_active": "Never", "account.never_active": "Nunca",
"account.posts": "Toots", "account.posts": "Toots",
"account.posts_with_replies": "Toots con respuestas", "account.posts_with_replies": "Toots con respuestas",
"account.report": "Reportar a @{name}", "account.report": "Reportar a @{name}",
@ -39,7 +39,7 @@
"account.unmute": "Dejar de silenciar a @{name}", "account.unmute": "Dejar de silenciar a @{name}",
"account.unmute_notifications": "Dejar de silenciar las notificaciones de @{name}", "account.unmute_notifications": "Dejar de silenciar las notificaciones de @{name}",
"alert.rate_limited.message": "Please retry after {retry_time, time, medium}.", "alert.rate_limited.message": "Please retry after {retry_time, time, medium}.",
"alert.rate_limited.title": "Rate limited", "alert.rate_limited.title": "Tarifa limitada",
"alert.unexpected.message": "Hubo un error inesperado.", "alert.unexpected.message": "Hubo un error inesperado.",
"alert.unexpected.title": "¡Ups!", "alert.unexpected.title": "¡Ups!",
"autosuggest_hashtag.per_week": "{count} per week", "autosuggest_hashtag.per_week": "{count} per week",
@ -53,7 +53,7 @@
"column.blocks": "Usuarios bloqueados", "column.blocks": "Usuarios bloqueados",
"column.community": "Línea de tiempo local", "column.community": "Línea de tiempo local",
"column.direct": "Mensajes directos", "column.direct": "Mensajes directos",
"column.directory": "Browse profiles", "column.directory": "Buscar perfiles",
"column.domain_blocks": "Dominios ocultados", "column.domain_blocks": "Dominios ocultados",
"column.favourites": "Favoritos", "column.favourites": "Favoritos",
"column.follow_requests": "Solicitudes de seguimiento", "column.follow_requests": "Solicitudes de seguimiento",
@ -63,6 +63,7 @@
"column.notifications": "Notificaciones", "column.notifications": "Notificaciones",
"column.pins": "Toots fijados", "column.pins": "Toots fijados",
"column.public": "Línea de tiempo federada", "column.public": "Línea de tiempo federada",
"column.status": "Toot",
"column_back_button.label": "Atrás", "column_back_button.label": "Atrás",
"column_header.hide_settings": "Ocultar configuración", "column_header.hide_settings": "Ocultar configuración",
"column_header.moveLeft_settings": "Mover columna a la izquierda", "column_header.moveLeft_settings": "Mover columna a la izquierda",
@ -100,8 +101,8 @@
"confirmations.delete_list.message": "¿Seguro que quieres borrar esta lista permanentemente?", "confirmations.delete_list.message": "¿Seguro que quieres borrar esta lista permanentemente?",
"confirmations.domain_block.confirm": "Ocultar dominio entero", "confirmations.domain_block.confirm": "Ocultar dominio entero",
"confirmations.domain_block.message": "¿Seguro de que quieres bloquear al dominio {domain} entero? En general unos cuantos bloqueos y silenciados concretos es suficiente y preferible.", "confirmations.domain_block.message": "¿Seguro de que quieres bloquear al dominio {domain} entero? En general unos cuantos bloqueos y silenciados concretos es suficiente y preferible.",
"confirmations.logout.confirm": "Log out", "confirmations.logout.confirm": "Cerrar sesión",
"confirmations.logout.message": "Are you sure you want to log out?", "confirmations.logout.message": "¿Estás seguro de querer cerrar la sesión?",
"confirmations.mute.confirm": "Silenciar", "confirmations.mute.confirm": "Silenciar",
"confirmations.mute.message": "¿Estás seguro de que quieres silenciar a {name}?", "confirmations.mute.message": "¿Estás seguro de que quieres silenciar a {name}?",
"confirmations.redraft.confirm": "Borrar y volver a borrador", "confirmations.redraft.confirm": "Borrar y volver a borrador",
@ -110,10 +111,10 @@
"confirmations.reply.message": "Responder sobrescribirá el mensaje que estás escribiendo. ¿Estás seguro de que deseas continuar?", "confirmations.reply.message": "Responder sobrescribirá el mensaje que estás escribiendo. ¿Estás seguro de que deseas continuar?",
"confirmations.unfollow.confirm": "Dejar de seguir", "confirmations.unfollow.confirm": "Dejar de seguir",
"confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?", "confirmations.unfollow.message": "¿Estás seguro de que quieres dejar de seguir a {name}?",
"directory.federated": "From known fediverse", "directory.federated": "Desde el fediverso conocido",
"directory.local": "From {domain} only", "directory.local": "Sólo de {domain}",
"directory.new_arrivals": "New arrivals", "directory.new_arrivals": "Recién llegados",
"directory.recently_active": "Recently active", "directory.recently_active": "Recientemente activo",
"embed.instructions": "Añade este toot a tu sitio web con el siguiente código.", "embed.instructions": "Añade este toot a tu sitio web con el siguiente código.",
"embed.preview": "Así es como se verá:", "embed.preview": "Así es como se verá:",
"emoji_button.activity": "Actividad", "emoji_button.activity": "Actividad",
@ -368,7 +369,7 @@
"status.show_more": "Mostrar más", "status.show_more": "Mostrar más",
"status.show_more_all": "Mostrar más para todo", "status.show_more_all": "Mostrar más para todo",
"status.show_thread": "Ver hilo", "status.show_thread": "Ver hilo",
"status.uncached_media_warning": "Not available", "status.uncached_media_warning": "No disponible",
"status.unmute_conversation": "Dejar de silenciar conversación", "status.unmute_conversation": "Dejar de silenciar conversación",
"status.unpin": "Dejar de fijar", "status.unpin": "Dejar de fijar",
"suggestions.dismiss": "Descartar sugerencia", "suggestions.dismiss": "Descartar sugerencia",
@ -384,21 +385,21 @@
"time_remaining.moments": "Momentos restantes", "time_remaining.moments": "Momentos restantes",
"time_remaining.seconds": "{number, plural, one {# segundo restante} other {# segundos restantes}}", "time_remaining.seconds": "{number, plural, one {# segundo restante} other {# segundos restantes}}",
"trends.count_by_accounts": "{count} {rawCount, plural, one {persona} other {personas}} hablando", "trends.count_by_accounts": "{count} {rawCount, plural, one {persona} other {personas}} hablando",
"trends.trending_now": "Trending now", "trends.trending_now": "Tendencia ahora",
"ui.beforeunload": "Tu borrador se perderá si sales de Mastodon.", "ui.beforeunload": "Tu borrador se perderá si sales de Mastodon.",
"upload_area.title": "Arrastra y suelta para subir", "upload_area.title": "Arrastra y suelta para subir",
"upload_button.label": "Subir multimedia (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_button.label": "Subir multimedia (JPEG, PNG, GIF, WebM, MP4, MOV)",
"upload_error.limit": "Límite de subida de archivos excedido.", "upload_error.limit": "Límite de subida de archivos excedido.",
"upload_error.poll": "Subida de archivos no permitida con encuestas.", "upload_error.poll": "Subida de archivos no permitida con encuestas.",
"upload_form.description": "Describir para los usuarios con dificultad visual", "upload_form.description": "Describir para los usuarios con dificultad visual",
"upload_form.edit": "Edit", "upload_form.edit": "Editar",
"upload_form.undo": "Borrar", "upload_form.undo": "Borrar",
"upload_modal.analyzing_picture": "Analyzing picture…", "upload_modal.analyzing_picture": "Analizando imagen…",
"upload_modal.apply": "Apply", "upload_modal.apply": "Aplicar",
"upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog", "upload_modal.description_placeholder": "Un rápido zorro marrón salta sobre el perro perezoso",
"upload_modal.detect_text": "Detect text from picture", "upload_modal.detect_text": "Detectar texto de la imagen",
"upload_modal.edit_media": "Edit media", "upload_modal.edit_media": "Editar multimedia",
"upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.", "upload_modal.hint": "Haga clic o arrastre el círculo en la vista previa para elegir el punto focal que siempre estará a la vista en todas las miniaturas.",
"upload_modal.preview_label": "Preview ({ratio})", "upload_modal.preview_label": "Preview ({ratio})",
"upload_progress.label": "Subiendo…", "upload_progress.label": "Subiendo…",
"video.close": "Cerrar video", "video.close": "Cerrar video",

@ -63,6 +63,7 @@
"column.notifications": "Teated", "column.notifications": "Teated",
"column.pins": "Kinnitatud upitused", "column.pins": "Kinnitatud upitused",
"column.public": "Föderatiivne ajajoon", "column.public": "Föderatiivne ajajoon",
"column.status": "Toot",
"column_back_button.label": "Tagasi", "column_back_button.label": "Tagasi",
"column_header.hide_settings": "Peida sätted", "column_header.hide_settings": "Peida sätted",
"column_header.moveLeft_settings": "Liiguta tulp vasakule", "column_header.moveLeft_settings": "Liiguta tulp vasakule",

@ -63,6 +63,7 @@
"column.notifications": "Jakinarazpenak", "column.notifications": "Jakinarazpenak",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "Federatutako denbora-lerroa", "column.public": "Federatutako denbora-lerroa",
"column.status": "Toot",
"column_back_button.label": "Atzera", "column_back_button.label": "Atzera",
"column_header.hide_settings": "Ezkutatu ezarpenak", "column_header.hide_settings": "Ezkutatu ezarpenak",
"column_header.moveLeft_settings": "Eraman zutabea ezkerrera", "column_header.moveLeft_settings": "Eraman zutabea ezkerrera",

@ -63,6 +63,7 @@
"column.notifications": "اعلان‌ها", "column.notifications": "اعلان‌ها",
"column.pins": "نوشته‌های ثابت", "column.pins": "نوشته‌های ثابت",
"column.public": "نوشته‌های همه‌جا", "column.public": "نوشته‌های همه‌جا",
"column.status": "Toot",
"column_back_button.label": "بازگشت", "column_back_button.label": "بازگشت",
"column_header.hide_settings": "نهفتن تنظیمات", "column_header.hide_settings": "نهفتن تنظیمات",
"column_header.moveLeft_settings": "انتقال ستون به راست", "column_header.moveLeft_settings": "انتقال ستون به راست",

@ -63,6 +63,7 @@
"column.notifications": "Ilmoitukset", "column.notifications": "Ilmoitukset",
"column.pins": "Kiinnitetty tuuttaus", "column.pins": "Kiinnitetty tuuttaus",
"column.public": "Yleinen aikajana", "column.public": "Yleinen aikajana",
"column.status": "Toot",
"column_back_button.label": "Takaisin", "column_back_button.label": "Takaisin",
"column_header.hide_settings": "Piilota asetukset", "column_header.hide_settings": "Piilota asetukset",
"column_header.moveLeft_settings": "Siirrä saraketta vasemmalle", "column_header.moveLeft_settings": "Siirrä saraketta vasemmalle",

@ -63,6 +63,7 @@
"column.notifications": "Notifications", "column.notifications": "Notifications",
"column.pins": "Pouets épinglés", "column.pins": "Pouets épinglés",
"column.public": "Fil public global", "column.public": "Fil public global",
"column.status": "Toot",
"column_back_button.label": "Retour", "column_back_button.label": "Retour",
"column_header.hide_settings": "Masquer les paramètres", "column_header.hide_settings": "Masquer les paramètres",
"column_header.moveLeft_settings": "Déplacer la colonne vers la gauche", "column_header.moveLeft_settings": "Déplacer la colonne vers la gauche",

@ -63,6 +63,7 @@
"column.notifications": "Notificacións", "column.notifications": "Notificacións",
"column.pins": "Mensaxes fixadas", "column.pins": "Mensaxes fixadas",
"column.public": "Liña temporal federada", "column.public": "Liña temporal federada",
"column.status": "Toot",
"column_back_button.label": "Atrás", "column_back_button.label": "Atrás",
"column_header.hide_settings": "Agochar axustes", "column_header.hide_settings": "Agochar axustes",
"column_header.moveLeft_settings": "Mover a columna hacia a esquerda", "column_header.moveLeft_settings": "Mover a columna hacia a esquerda",

@ -63,6 +63,7 @@
"column.notifications": "התראות", "column.notifications": "התראות",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "בפרהסיה", "column.public": "בפרהסיה",
"column.status": "Toot",
"column_back_button.label": "חזרה", "column_back_button.label": "חזרה",
"column_header.hide_settings": "הסתרת העדפות", "column_header.hide_settings": "הסתרת העדפות",
"column_header.moveLeft_settings": "הזחת טור לשמאל", "column_header.moveLeft_settings": "הזחת טור לשמאל",

@ -63,6 +63,7 @@
"column.notifications": "Notifications", "column.notifications": "Notifications",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "Federated timeline", "column.public": "Federated timeline",
"column.status": "Toot",
"column_back_button.label": "Back", "column_back_button.label": "Back",
"column_header.hide_settings": "Hide settings", "column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left", "column_header.moveLeft_settings": "Move column to the left",

@ -63,6 +63,7 @@
"column.notifications": "Notifikacije", "column.notifications": "Notifikacije",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "Federalni timeline", "column.public": "Federalni timeline",
"column.status": "Toot",
"column_back_button.label": "Natrag", "column_back_button.label": "Natrag",
"column_header.hide_settings": "Hide settings", "column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left", "column_header.moveLeft_settings": "Move column to the left",

@ -63,6 +63,7 @@
"column.notifications": "Értesítések", "column.notifications": "Értesítések",
"column.pins": "Kitűzött tülkök", "column.pins": "Kitűzött tülkök",
"column.public": "Nyilvános idővonal", "column.public": "Nyilvános idővonal",
"column.status": "Toot",
"column_back_button.label": "Vissza", "column_back_button.label": "Vissza",
"column_header.hide_settings": "Beállítások elrejtése", "column_header.hide_settings": "Beállítások elrejtése",
"column_header.moveLeft_settings": "Oszlop elmozdítása balra", "column_header.moveLeft_settings": "Oszlop elmozdítása balra",

@ -63,6 +63,7 @@
"column.notifications": "Ծանուցումներ", "column.notifications": "Ծանուցումներ",
"column.pins": "Ամրացված թթեր", "column.pins": "Ամրացված թթեր",
"column.public": "Դաշնային հոսք", "column.public": "Դաշնային հոսք",
"column.status": "Toot",
"column_back_button.label": "Ետ", "column_back_button.label": "Ետ",
"column_header.hide_settings": "Թաքցնել կարգավորումները", "column_header.hide_settings": "Թաքցնել կարգավորումները",
"column_header.moveLeft_settings": "Տեղաշարժել սյունը ձախ", "column_header.moveLeft_settings": "Տեղաշարժել սյունը ձախ",

@ -63,6 +63,7 @@
"column.notifications": "Notifikasi", "column.notifications": "Notifikasi",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "Linimasa gabungan", "column.public": "Linimasa gabungan",
"column.status": "Toot",
"column_back_button.label": "Kembali", "column_back_button.label": "Kembali",
"column_header.hide_settings": "Sembunyikan pengaturan", "column_header.hide_settings": "Sembunyikan pengaturan",
"column_header.moveLeft_settings": "Pindahkan kolom ke kiri", "column_header.moveLeft_settings": "Pindahkan kolom ke kiri",

@ -63,6 +63,7 @@
"column.notifications": "Savigi", "column.notifications": "Savigi",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "Federata tempolineo", "column.public": "Federata tempolineo",
"column.status": "Toot",
"column_back_button.label": "Retro", "column_back_button.label": "Retro",
"column_header.hide_settings": "Hide settings", "column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left", "column_header.moveLeft_settings": "Move column to the left",

@ -63,6 +63,7 @@
"column.notifications": "Notifiche", "column.notifications": "Notifiche",
"column.pins": "Toot fissati in cima", "column.pins": "Toot fissati in cima",
"column.public": "Timeline federata", "column.public": "Timeline federata",
"column.status": "Toot",
"column_back_button.label": "Indietro", "column_back_button.label": "Indietro",
"column_header.hide_settings": "Nascondi impostazioni", "column_header.hide_settings": "Nascondi impostazioni",
"column_header.moveLeft_settings": "Sposta colonna a sinistra", "column_header.moveLeft_settings": "Sposta colonna a sinistra",

@ -63,6 +63,7 @@
"column.notifications": "通知", "column.notifications": "通知",
"column.pins": "固定されたトゥート", "column.pins": "固定されたトゥート",
"column.public": "連合タイムライン", "column.public": "連合タイムライン",
"column.status": "Toot",
"column_back_button.label": "戻る", "column_back_button.label": "戻る",
"column_header.hide_settings": "設定を隠す", "column_header.hide_settings": "設定を隠す",
"column_header.moveLeft_settings": "カラムを左に移動する", "column_header.moveLeft_settings": "カラムを左に移動する",

@ -63,6 +63,7 @@
"column.notifications": "შეტყობინებები", "column.notifications": "შეტყობინებები",
"column.pins": "აპინული ტუტები", "column.pins": "აპინული ტუტები",
"column.public": "ფედერალური თაიმლაინი", "column.public": "ფედერალური თაიმლაინი",
"column.status": "Toot",
"column_back_button.label": "უკან", "column_back_button.label": "უკან",
"column_header.hide_settings": "პარამეტრების დამალვა", "column_header.hide_settings": "პარამეტრების დამალვა",
"column_header.moveLeft_settings": "სვეტის მარცხნივ გადატანა", "column_header.moveLeft_settings": "სვეტის მარცხნივ გადატანა",

@ -63,6 +63,7 @@
"column.notifications": "Ескертпелер", "column.notifications": "Ескертпелер",
"column.pins": "Жабыстырылған жазбалар", "column.pins": "Жабыстырылған жазбалар",
"column.public": "Жаһандық желі", "column.public": "Жаһандық желі",
"column.status": "Toot",
"column_back_button.label": "Артқа", "column_back_button.label": "Артқа",
"column_header.hide_settings": "Баптауларды жасыр", "column_header.hide_settings": "Баптауларды жасыр",
"column_header.moveLeft_settings": "Бағананы солға жылжыту", "column_header.moveLeft_settings": "Бағананы солға жылжыту",

@ -63,6 +63,7 @@
"column.notifications": "알림", "column.notifications": "알림",
"column.pins": "고정된 툿", "column.pins": "고정된 툿",
"column.public": "연합 타임라인", "column.public": "연합 타임라인",
"column.status": "Toot",
"column_back_button.label": "돌아가기", "column_back_button.label": "돌아가기",
"column_header.hide_settings": "설정 숨기기", "column_header.hide_settings": "설정 숨기기",
"column_header.moveLeft_settings": "왼쪽으로 이동", "column_header.moveLeft_settings": "왼쪽으로 이동",
@ -292,7 +293,7 @@
"notifications.group": "{count} 개의 알림", "notifications.group": "{count} 개의 알림",
"poll.closed": "마감됨", "poll.closed": "마감됨",
"poll.refresh": "새로고침", "poll.refresh": "새로고침",
"poll.total_votes": "{count} 명 참여", "poll.total_votes": "{count} ",
"poll.vote": "투표", "poll.vote": "투표",
"poll_button.add_poll": "투표 추가", "poll_button.add_poll": "투표 추가",
"poll_button.remove_poll": "투표 삭제", "poll_button.remove_poll": "투표 삭제",

@ -63,6 +63,7 @@
"column.notifications": "Notifications", "column.notifications": "Notifications",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "Federated timeline", "column.public": "Federated timeline",
"column.status": "Toot",
"column_back_button.label": "Back", "column_back_button.label": "Back",
"column_header.hide_settings": "Hide settings", "column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left", "column_header.moveLeft_settings": "Move column to the left",

@ -63,6 +63,7 @@
"column.notifications": "Paziņojumi", "column.notifications": "Paziņojumi",
"column.pins": "Piespraustie ziņojumi", "column.pins": "Piespraustie ziņojumi",
"column.public": "Federatīvā laika līnija", "column.public": "Federatīvā laika līnija",
"column.status": "Toot",
"column_back_button.label": "Atpakaļ", "column_back_button.label": "Atpakaļ",
"column_header.hide_settings": "Paslēpt iestatījumus", "column_header.hide_settings": "Paslēpt iestatījumus",
"column_header.moveLeft_settings": "Pārvietot kolonu pa kreisi", "column_header.moveLeft_settings": "Pārvietot kolonu pa kreisi",

@ -63,6 +63,7 @@
"column.notifications": "Notifications", "column.notifications": "Notifications",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "Federated timeline", "column.public": "Federated timeline",
"column.status": "Toot",
"column_back_button.label": "Back", "column_back_button.label": "Back",
"column_header.hide_settings": "Hide settings", "column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left", "column_header.moveLeft_settings": "Move column to the left",

@ -63,6 +63,7 @@
"column.notifications": "Meldingen", "column.notifications": "Meldingen",
"column.pins": "Vastgezette toots", "column.pins": "Vastgezette toots",
"column.public": "Globale tijdlijn", "column.public": "Globale tijdlijn",
"column.status": "Toot",
"column_back_button.label": "Terug", "column_back_button.label": "Terug",
"column_header.hide_settings": "Instellingen verbergen", "column_header.hide_settings": "Instellingen verbergen",
"column_header.moveLeft_settings": "Kolom naar links verplaatsen", "column_header.moveLeft_settings": "Kolom naar links verplaatsen",

@ -0,0 +1,414 @@
{
"account.add_or_remove_from_list": "Legg til eller ta vekk fra liste",
"account.badges.bot": "Robot",
"account.block": "Blokkér @{name}",
"account.block_domain": "Gøyme alt innhald for domenet {domain}",
"account.blocked": "Blokkert",
"account.cancel_follow_request": "Avslutt føljar-førespurnad",
"account.direct": "Direkte meld @{name}",
"account.domain_blocked": "Domenet er gøymt",
"account.edit_profile": "Rediger profil",
"account.endorse": "Framhev på profilen din",
"account.follow": "Følj",
"account.followers": "Føljare",
"account.followers.empty": "Er ikkje nokon som føljar denne brukaren ennå.",
"account.follows": "Føljingar",
"account.follows.empty": "Denne brukaren foljer ikkje nokon ennå.",
"account.follows_you": "Føljar deg",
"account.hide_reblogs": "Gøym robotar for @{name}",
"account.last_status": "Sist aktiv",
"account.link_verified_on": "Eigerskap for denne linken er sist sjekket den {date}",
"account.locked_info": "Brukarens privat-status er satt til lukka. Eigaren må manuelt døme kvem som kan følje honom.",
"account.media": "Media",
"account.mention": "Nemne @{name}",
"account.moved_to": "{name} har flytta til:",
"account.mute": "Målbind @{name}",
"account.mute_notifications": "Målbind notifikasjoner ifrå @{name}",
"account.muted": "Målbindt",
"account.never_active": "Aldri",
"account.posts": "Tutar",
"account.posts_with_replies": "Tutar og svar",
"account.report": "Rapporter @{name}",
"account.requested": "Venter på samtykke. Klikk for å avbryte føljar-førespurnad",
"account.share": "Del @{name} sin profil",
"account.show_reblogs": "Sjå framhevingar ifrå @{name}",
"account.unblock": "Avblokker @{name}",
"account.unblock_domain": "Vis {domain}",
"account.unendorse": "Ikkje framhev på profil",
"account.unfollow": "Avfølja",
"account.unmute": "Av-demp @{name}",
"account.unmute_notifications": "Av-demp notifikasjoner ifrå @{name}",
"alert.rate_limited.message": "Ver vennlig og prøv igjen {retry_time, time, medium}.",
"alert.rate_limited.title": "Bregrensa rate",
"alert.unexpected.message": "Eit uforventa problem har hendt.",
"alert.unexpected.title": "Oops!",
"autosuggest_hashtag.per_week": "{count} per veke",
"boost_modal.combo": "Du kan trykke {combo} for å hoppe over dette neste gong",
"bundle_column_error.body": "Noko gikk gale mens komponent ble nedlasta.",
"bundle_column_error.retry": "Prøv igjen",
"bundle_column_error.title": "Tenarmaskin feil",
"bundle_modal_error.close": "Lukk",
"bundle_modal_error.message": "Noko gikk gale mens komponent var i ferd med å bli nedlasta.",
"bundle_modal_error.retry": "Prøv igjen",
"column.blocks": "Blokka brukare",
"column.community": "Lokal samtid",
"column.direct": "Direkte meldingar",
"column.directory": "Sjå gjennom profiler",
"column.domain_blocks": "Gøymte domener",
"column.favourites": "Favorittar",
"column.follow_requests": "Føljarførespurnad",
"column.home": "Heim",
"column.lists": "Lister",
"column.mutes": "Målbindte brukare",
"column.notifications": "Varslinger",
"column.pins": "Festa tuter",
"column.public": "Federert samtid",
"column.status": "Toot",
"column_back_button.label": "Tilbake",
"column_header.hide_settings": "Skjul innstillingar",
"column_header.moveLeft_settings": "Flytt feltet til venstre",
"column_header.moveRight_settings": "Flytt feltet til høgre",
"column_header.pin": "Fest",
"column_header.show_settings": "Vis innstillingar",
"column_header.unpin": "Løys",
"column_subheading.settings": "Innstillingar",
"community.column_settings.media_only": "Kun medie",
"compose_form.direct_message_warning": "Denne tuten vil kun verte synleg for nemnde brukarar.",
"compose_form.direct_message_warning_learn_more": "Lær meir",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "What is on your mind?",
"compose_form.poll.add_option": "Add a choice",
"compose_form.poll.duration": "Poll duration",
"compose_form.poll.option_placeholder": "Choice {number}",
"compose_form.poll.remove_option": "Remove this choice",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Mark media as sensitive",
"compose_form.sensitive.marked": "Media is marked as sensitive",
"compose_form.sensitive.unmarked": "Media is not marked as sensitive",
"compose_form.spoiler.marked": "Text is hidden behind warning",
"compose_form.spoiler.unmarked": "Text is not hidden",
"compose_form.spoiler_placeholder": "Write your warning here",
"confirmation_modal.cancel": "Cancel",
"confirmations.block.block_and_report": "Block & Report",
"confirmations.block.confirm": "Block",
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Er du ordentleg, ordentleg sikker på at du vill blokkere heile {domain}? I dei tilfeller er det bedre med ein målretta blokkering eller demping av individuelle brukare.",
"confirmations.logout.confirm": "Logg ut",
"confirmations.logout.message": "Er du sikker på at du vill logge ut?",
"confirmations.mute.confirm": "Målbind",
"confirmations.mute.message": "Er du sikker på at d vill målbinde {name}?",
"confirmations.redraft.confirm": "Slett & gjennopprett",
"confirmations.redraft.message": "Er du sikker på at du vill slette statusen og gjennoprette den? Favoritter og framhevinger vill bli borte, og svar til den originale posten vill bli einstøing.",
"confirmations.reply.confirm": "Svar",
"confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
"directory.federated": "From known fediverse",
"directory.local": "From {domain} only",
"directory.new_arrivals": "New arrivals",
"directory.recently_active": "Recently active",
"embed.instructions": "Embed this status on your website by copying the code below.",
"embed.preview": "Here is what it will look like:",
"emoji_button.activity": "Activity",
"emoji_button.custom": "Custom",
"emoji_button.flags": "Flags",
"emoji_button.food": "Food & Drink",
"emoji_button.label": "Insert emoji",
"emoji_button.nature": "Nature",
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objects",
"emoji_button.people": "People",
"emoji_button.recent": "Frequently used",
"emoji_button.search": "Search...",
"emoji_button.search_results": "Search results",
"emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places",
"empty_column.account_timeline": "No toots here!",
"empty_column.account_unavailable": "Profile unavailable",
"empty_column.blocks": "You haven't blocked any users yet.",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.domain_blocks": "There are no hidden domains yet.",
"empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
"empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
"empty_column.home.public_timeline": "the public timeline",
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
"empty_column.mutes": "You haven't muted any users yet.",
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"getting_started.developers": "Developers",
"getting_started.directory": "Profile directory",
"getting_started.documentation": "Documentation",
"getting_started.heading": "Getting started",
"getting_started.invite": "Invite people",
"getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.",
"getting_started.security": "Security",
"getting_started.terms": "Terms of service",
"hashtag.column_header.tag_mode.all": "and {additional}",
"hashtag.column_header.tag_mode.any": "or {additional}",
"hashtag.column_header.tag_mode.none": "without {additional}",
"hashtag.column_settings.select.no_options_message": "No suggestions found",
"hashtag.column_settings.select.placeholder": "Enter hashtags…",
"hashtag.column_settings.tag_mode.all": "All of these",
"hashtag.column_settings.tag_mode.any": "Any of these",
"hashtag.column_settings.tag_mode.none": "None of these",
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
"home.column_settings.basic": "Basic",
"home.column_settings.show_reblogs": "Show boosts",
"home.column_settings.show_replies": "Show replies",
"home.column_settings.update_live": "Update in real-time",
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
"intervals.full.minutes": "{number, plural, one {# minute} other {# minutes}}",
"introduction.federation.action": "Next",
"introduction.federation.federated.headline": "Federated",
"introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
"introduction.federation.home.headline": "Home",
"introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
"introduction.federation.local.headline": "Local",
"introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
"introduction.interactions.action": "Finish toot-orial!",
"introduction.interactions.favourite.headline": "Favourite",
"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.reblog.headline": "Boost",
"introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
"introduction.interactions.reply.headline": "Reply",
"introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
"introduction.welcome.action": "Let's go!",
"introduction.welcome.headline": "First steps",
"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": "to navigate back",
"keyboard_shortcuts.blocked": "to open blocked users list",
"keyboard_shortcuts.boost": "to boost",
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.direct": "to open direct messages column",
"keyboard_shortcuts.down": "to move down in the list",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "to favourite",
"keyboard_shortcuts.favourites": "to open favourites list",
"keyboard_shortcuts.federated": "to open federated timeline",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.home": "to open home timeline",
"keyboard_shortcuts.hotkey": "Hotkey",
"keyboard_shortcuts.legend": "to display this legend",
"keyboard_shortcuts.local": "to open local timeline",
"keyboard_shortcuts.mention": "to mention author",
"keyboard_shortcuts.muted": "to open muted users list",
"keyboard_shortcuts.my_profile": "to open your profile",
"keyboard_shortcuts.notifications": "to open notifications column",
"keyboard_shortcuts.pinned": "to open pinned toots list",
"keyboard_shortcuts.profile": "to open author's profile",
"keyboard_shortcuts.reply": "to reply",
"keyboard_shortcuts.requests": "to open follow requests list",
"keyboard_shortcuts.search": "to focus search",
"keyboard_shortcuts.start": "to open \"get started\" column",
"keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW",
"keyboard_shortcuts.toggle_sensitivity": "to show/hide media",
"keyboard_shortcuts.toot": "to start a brand new toot",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "to move up in the list",
"lightbox.close": "Close",
"lightbox.next": "Next",
"lightbox.previous": "Previous",
"lightbox.view_context": "View context",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.delete": "Delete list",
"lists.edit": "Edit list",
"lists.edit.submit": "Change title",
"lists.new.create": "Add list",
"lists.new.title_placeholder": "New list title",
"lists.search": "Search among people you follow",
"lists.subheading": "Your lists",
"load_pending": "{count, plural, one {# new item} other {# new items}}",
"loading_indicator.label": "Loading...",
"media_gallery.toggle_visible": "Toggle visibility",
"missing_indicator.label": "Not found",
"missing_indicator.sublabel": "This resource could not be found",
"mute_modal.hide_notifications": "Hide notifications from this user?",
"navigation_bar.apps": "Mobile apps",
"navigation_bar.blocks": "Blocked users",
"navigation_bar.community_timeline": "Local timeline",
"navigation_bar.compose": "Compose new toot",
"navigation_bar.direct": "Direct messages",
"navigation_bar.discover": "Discover",
"navigation_bar.domain_blocks": "Hidden domains",
"navigation_bar.edit_profile": "Edit profile",
"navigation_bar.favourites": "Favourites",
"navigation_bar.filters": "Muted words",
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.info": "About this server",
"navigation_bar.keyboard_shortcuts": "Hotkeys",
"navigation_bar.lists": "Lists",
"navigation_bar.logout": "Logout",
"navigation_bar.mutes": "Muted users",
"navigation_bar.personal": "Personal",
"navigation_bar.pins": "Pinned toots",
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
"navigation_bar.security": "Security",
"notification.and_n_others": "and {count, plural, one {# other} other {# others}}",
"notification.favourite": "{name} favourited your status",
"notification.follow": "{name} followed you",
"notification.mention": "{name} mentioned you",
"notification.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} boosted your status",
"notifications.clear": "Clear notifications",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
"notifications.column_settings.alert": "Desktop notifications",
"notifications.column_settings.favourite": "Favourites:",
"notifications.column_settings.filter_bar.advanced": "Display all categories",
"notifications.column_settings.filter_bar.category": "Quick filter bar",
"notifications.column_settings.filter_bar.show": "Show",
"notifications.column_settings.follow": "New followers:",
"notifications.column_settings.mention": "Mentions:",
"notifications.column_settings.poll": "Poll results:",
"notifications.column_settings.push": "Push notifications",
"notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "Show in column",
"notifications.column_settings.sound": "Play sound",
"notifications.filter.all": "All",
"notifications.filter.boosts": "Boosts",
"notifications.filter.favourites": "Favourites",
"notifications.filter.follows": "Follows",
"notifications.filter.mentions": "Mentions",
"notifications.filter.polls": "Poll results",
"notifications.group": "{count} notifications",
"poll.closed": "Closed",
"poll.refresh": "Refresh",
"poll.total_votes": "{count, plural, one {# vote} other {# votes}}",
"poll.vote": "Vote",
"poll_button.add_poll": "Add a poll",
"poll_button.remove_poll": "Remove poll",
"privacy.change": "Adjust status privacy",
"privacy.direct.long": "Post to mentioned users only",
"privacy.direct.short": "Direct",
"privacy.private.long": "Post to followers only",
"privacy.private.short": "Followers-only",
"privacy.public.long": "Post to public timelines",
"privacy.public.short": "Public",
"privacy.unlisted.long": "Do not show in public timelines",
"privacy.unlisted.short": "Unlisted",
"regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "now",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancel",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Additional comments",
"report.submit": "Submit",
"report.target": "Report {target}",
"search.placeholder": "Search",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "Block @{name}",
"status.cancel_reblog_private": "Unboost",
"status.cannot_reblog": "This post cannot be boosted",
"status.copy": "Copy link to status",
"status.delete": "Delete",
"status.detailed_status": "Detailed conversation view",
"status.direct": "Direct message @{name}",
"status.embed": "Embed",
"status.favourite": "Favourite",
"status.filtered": "Filtered",
"status.load_more": "Load more",
"status.media_hidden": "Media hidden",
"status.mention": "Mention @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.read_more": "Read more",
"status.reblog": "Boost",
"status.reblog_private": "Boost to original audience",
"status.reblogged_by": "{name} boosted",
"status.reblogs.empty": "No one has boosted this toot yet. When someone does, they will show up here.",
"status.redraft": "Delete & re-draft",
"status.reply": "Reply",
"status.replyAll": "Reply to thread",
"status.report": "Report @{name}",
"status.sensitive_warning": "Sensitive content",
"status.share": "Share",
"status.show_less": "Show less",
"status.show_less_all": "Show less for all",
"status.show_more": "Show more",
"status.show_more_all": "Show more for all",
"status.show_thread": "Show thread",
"status.uncached_media_warning": "Not available",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"suggestions.dismiss": "Dismiss suggestion",
"suggestions.header": "You might be interested in…",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Local",
"tabs_bar.notifications": "Notifications",
"tabs_bar.search": "Search",
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
"time_remaining.moments": "Moments remaining",
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
"trends.trending_now": "Trending now",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
"upload_area.title": "Drag & drop to upload",
"upload_button.label": "Add media ({formats})",
"upload_error.limit": "File upload limit exceeded.",
"upload_error.poll": "File upload not allowed with polls.",
"upload_form.description": "Describe for the visually impaired",
"upload_form.edit": "Edit",
"upload_form.undo": "Delete",
"upload_modal.analyzing_picture": "Analyzing picture…",
"upload_modal.apply": "Apply",
"upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
"upload_modal.detect_text": "Detect text from picture",
"upload_modal.edit_media": "Edit media",
"upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
"upload_modal.preview_label": "Preview ({ratio})",
"upload_progress.label": "Uploading...",
"video.close": "Close video",
"video.exit_fullscreen": "Exit full screen",
"video.expand": "Expand video",
"video.fullscreen": "Full screen",
"video.hide": "Hide video",
"video.mute": "Mute sound",
"video.pause": "Pause",
"video.play": "Play",
"video.unmute": "Unmute sound"
}

@ -63,6 +63,7 @@
"column.notifications": "Varsler", "column.notifications": "Varsler",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "Felles tidslinje", "column.public": "Felles tidslinje",
"column.status": "Toot",
"column_back_button.label": "Tilbake", "column_back_button.label": "Tilbake",
"column_header.hide_settings": "Gjem innstillinger", "column_header.hide_settings": "Gjem innstillinger",
"column_header.moveLeft_settings": "Flytt feltet til venstre", "column_header.moveLeft_settings": "Flytt feltet til venstre",

@ -63,6 +63,7 @@
"column.notifications": "Notificacions", "column.notifications": "Notificacions",
"column.pins": "Tuts penjats", "column.pins": "Tuts penjats",
"column.public": "Flux public global", "column.public": "Flux public global",
"column.status": "Toot",
"column_back_button.label": "Tornar", "column_back_button.label": "Tornar",
"column_header.hide_settings": "Amagar los paramètres", "column_header.hide_settings": "Amagar los paramètres",
"column_header.moveLeft_settings": "Desplaçar la colomna a man drecha", "column_header.moveLeft_settings": "Desplaçar la colomna a man drecha",

@ -63,6 +63,7 @@
"column.notifications": "Powiadomienia", "column.notifications": "Powiadomienia",
"column.pins": "Przypięte wpisy", "column.pins": "Przypięte wpisy",
"column.public": "Globalna oś czasu", "column.public": "Globalna oś czasu",
"column.status": "Toot",
"column_back_button.label": "Wróć", "column_back_button.label": "Wróć",
"column_header.hide_settings": "Ukryj ustawienia", "column_header.hide_settings": "Ukryj ustawienia",
"column_header.moveLeft_settings": "Przesuń kolumnę w lewo", "column_header.moveLeft_settings": "Przesuń kolumnę w lewo",

@ -63,6 +63,7 @@
"column.notifications": "Notificações", "column.notifications": "Notificações",
"column.pins": "Postagens fixadas", "column.pins": "Postagens fixadas",
"column.public": "Global", "column.public": "Global",
"column.status": "Toot",
"column_back_button.label": "Voltar", "column_back_button.label": "Voltar",
"column_header.hide_settings": "Esconder configurações", "column_header.hide_settings": "Esconder configurações",
"column_header.moveLeft_settings": "Mover coluna para a esquerda", "column_header.moveLeft_settings": "Mover coluna para a esquerda",

@ -63,6 +63,7 @@
"column.notifications": "Notificações", "column.notifications": "Notificações",
"column.pins": "Publicações fixas", "column.pins": "Publicações fixas",
"column.public": "Cronologia federada", "column.public": "Cronologia federada",
"column.status": "Toot",
"column_back_button.label": "Voltar", "column_back_button.label": "Voltar",
"column_header.hide_settings": "Esconder configurações", "column_header.hide_settings": "Esconder configurações",
"column_header.moveLeft_settings": "Mover coluna para a esquerda", "column_header.moveLeft_settings": "Mover coluna para a esquerda",

@ -63,6 +63,7 @@
"column.notifications": "Notificări", "column.notifications": "Notificări",
"column.pins": "Postări fixate", "column.pins": "Postări fixate",
"column.public": "Flux global", "column.public": "Flux global",
"column.status": "Toot",
"column_back_button.label": "Înapoi", "column_back_button.label": "Înapoi",
"column_header.hide_settings": "Ascunde setările", "column_header.hide_settings": "Ascunde setările",
"column_header.moveLeft_settings": "Mută coloana la stânga", "column_header.moveLeft_settings": "Mută coloana la stânga",

@ -63,6 +63,7 @@
"column.notifications": "Уведомления", "column.notifications": "Уведомления",
"column.pins": "Закреплённый пост", "column.pins": "Закреплённый пост",
"column.public": "Глобальная лента", "column.public": "Глобальная лента",
"column.status": "Toot",
"column_back_button.label": "Назад", "column_back_button.label": "Назад",
"column_header.hide_settings": "Скрыть настройки", "column_header.hide_settings": "Скрыть настройки",
"column_header.moveLeft_settings": "Передвинуть колонку влево", "column_header.moveLeft_settings": "Передвинуть колонку влево",
@ -260,7 +261,7 @@
"navigation_bar.mutes": "Список скрытых пользователей", "navigation_bar.mutes": "Список скрытых пользователей",
"navigation_bar.personal": "Личное", "navigation_bar.personal": "Личное",
"navigation_bar.pins": "Закреплённые посты", "navigation_bar.pins": "Закреплённые посты",
"navigation_bar.preferences": "Опции", "navigation_bar.preferences": "Настройки",
"navigation_bar.public_timeline": "Глобальная лента", "navigation_bar.public_timeline": "Глобальная лента",
"navigation_bar.security": "Безопасность", "navigation_bar.security": "Безопасность",
"notification.and_n_others": "and {count, plural, one {# other} other {# others}}", "notification.and_n_others": "and {count, plural, one {# other} other {# others}}",
@ -383,8 +384,8 @@
"time_remaining.minutes": "{number, plural, one {осталась # минута} few {осталось # минуты} many {осталось # минут} other {осталось # минут}}", "time_remaining.minutes": "{number, plural, one {осталась # минута} few {осталось # минуты} many {осталось # минут} other {осталось # минут}}",
"time_remaining.moments": "остались считанные мгновения", "time_remaining.moments": "остались считанные мгновения",
"time_remaining.seconds": "{number, plural, one {осталась # секунду} few {осталось # секунды} many {осталось # секунд} other {осталось # секунд}}", "time_remaining.seconds": "{number, plural, one {осталась # секунду} few {осталось # секунды} many {осталось # секунд} other {осталось # секунд}}",
"trends.count_by_accounts": "Популярно у {count} {rawCount, plural, one {человека} few {человек} many {человек} other {человек}}", "trends.count_by_accounts": "{count} {rawCount, plural, one {человек говорит} few {человека говорят} other {человек говорят}} про это",
"trends.trending_now": "Trending now", "trends.trending_now": "Самое актуальное",
"ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.", "ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.",
"upload_area.title": "Перетащите сюда, чтобы загрузить", "upload_area.title": "Перетащите сюда, чтобы загрузить",
"upload_button.label": "Добавить медиаконтент", "upload_button.label": "Добавить медиаконтент",

@ -63,6 +63,7 @@
"column.notifications": "Oboznámenia", "column.notifications": "Oboznámenia",
"column.pins": "Pripnuté príspevky", "column.pins": "Pripnuté príspevky",
"column.public": "Federovaná časová os", "column.public": "Federovaná časová os",
"column.status": "Toot",
"column_back_button.label": "Späť", "column_back_button.label": "Späť",
"column_header.hide_settings": "Skryť nastavenia", "column_header.hide_settings": "Skryť nastavenia",
"column_header.moveLeft_settings": "Presuň stĺpec doľava", "column_header.moveLeft_settings": "Presuň stĺpec doľava",

@ -63,6 +63,7 @@
"column.notifications": "Obvestila", "column.notifications": "Obvestila",
"column.pins": "Pripeti tuti", "column.pins": "Pripeti tuti",
"column.public": "Združena časovnica", "column.public": "Združena časovnica",
"column.status": "Toot",
"column_back_button.label": "Nazaj", "column_back_button.label": "Nazaj",
"column_header.hide_settings": "Skrij nastavitve", "column_header.hide_settings": "Skrij nastavitve",
"column_header.moveLeft_settings": "Premakni stolpec na levo", "column_header.moveLeft_settings": "Premakni stolpec na levo",

@ -63,6 +63,7 @@
"column.notifications": "Njoftime", "column.notifications": "Njoftime",
"column.pins": "Mesazhe të fiksuar", "column.pins": "Mesazhe të fiksuar",
"column.public": "Rrjedhë kohore e federuar", "column.public": "Rrjedhë kohore e federuar",
"column.status": "Toot",
"column_back_button.label": "Mbrapsht", "column_back_button.label": "Mbrapsht",
"column_header.hide_settings": "Fshihi rregullimet", "column_header.hide_settings": "Fshihi rregullimet",
"column_header.moveLeft_settings": "Shpjere shtyllën majtas", "column_header.moveLeft_settings": "Shpjere shtyllën majtas",

@ -63,6 +63,7 @@
"column.notifications": "Obaveštenja", "column.notifications": "Obaveštenja",
"column.pins": "Prikačeni tutovi", "column.pins": "Prikačeni tutovi",
"column.public": "Federisana lajna", "column.public": "Federisana lajna",
"column.status": "Toot",
"column_back_button.label": "Nazad", "column_back_button.label": "Nazad",
"column_header.hide_settings": "Sakrij postavke", "column_header.hide_settings": "Sakrij postavke",
"column_header.moveLeft_settings": "Pomeri kolonu ulevo", "column_header.moveLeft_settings": "Pomeri kolonu ulevo",

@ -63,6 +63,7 @@
"column.notifications": "Обавештења", "column.notifications": "Обавештења",
"column.pins": "Прикачене трубе", "column.pins": "Прикачене трубе",
"column.public": "Здружена временска линија", "column.public": "Здружена временска линија",
"column.status": "Toot",
"column_back_button.label": "Назад", "column_back_button.label": "Назад",
"column_header.hide_settings": "Сакриј поставке", "column_header.hide_settings": "Сакриј поставке",
"column_header.moveLeft_settings": "Помери колону улево", "column_header.moveLeft_settings": "Помери колону улево",

@ -63,6 +63,7 @@
"column.notifications": "Meddelanden", "column.notifications": "Meddelanden",
"column.pins": "Nålade toots", "column.pins": "Nålade toots",
"column.public": "Förenad tidslinje", "column.public": "Förenad tidslinje",
"column.status": "Toot",
"column_back_button.label": "Tillbaka", "column_back_button.label": "Tillbaka",
"column_header.hide_settings": "Dölj inställningar", "column_header.hide_settings": "Dölj inställningar",
"column_header.moveLeft_settings": "Flytta kolumnen till vänster", "column_header.moveLeft_settings": "Flytta kolumnen till vänster",

@ -63,6 +63,7 @@
"column.notifications": "Notifications", "column.notifications": "Notifications",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "கூட்டாட்சி காலக்கெடு", "column.public": "கூட்டாட்சி காலக்கெடு",
"column.status": "Toot",
"column_back_button.label": "ஆதரி", "column_back_button.label": "ஆதரி",
"column_header.hide_settings": "அமைப்புகளை மறை", "column_header.hide_settings": "அமைப்புகளை மறை",
"column_header.moveLeft_settings": "நெடுவரிசையை இடதுபுறமாக நகர்த்தவும்", "column_header.moveLeft_settings": "நெடுவரிசையை இடதுபுறமாக நகர்த்தவும்",

@ -63,6 +63,7 @@
"column.notifications": "ప్రకటనలు", "column.notifications": "ప్రకటనలు",
"column.pins": "Pinned toot", "column.pins": "Pinned toot",
"column.public": "సమాఖ్య కాలక్రమం", "column.public": "సమాఖ్య కాలక్రమం",
"column.status": "Toot",
"column_back_button.label": "వెనక్కి", "column_back_button.label": "వెనక్కి",
"column_header.hide_settings": "అమర్పులను దాచిపెట్టు", "column_header.hide_settings": "అమర్పులను దాచిపెట్టు",
"column_header.moveLeft_settings": "నిలువు వరుసను ఎడమకి తరలించు", "column_header.moveLeft_settings": "నిలువు వరుసను ఎడమకి తరలించు",

@ -16,7 +16,7 @@
"account.follows.empty": "ผู้ใช้นี้ยังไม่ได้ติดตามใคร", "account.follows.empty": "ผู้ใช้นี้ยังไม่ได้ติดตามใคร",
"account.follows_you": "ติดตามคุณ", "account.follows_you": "ติดตามคุณ",
"account.hide_reblogs": "ซ่อนการดันจาก @{name}", "account.hide_reblogs": "ซ่อนการดันจาก @{name}",
"account.last_status": "Last active", "account.last_status": "ใช้งานล่าสุด",
"account.link_verified_on": "ตรวจสอบความเป็นเจ้าของของลิงก์นี้เมื่อ {date}", "account.link_verified_on": "ตรวจสอบความเป็นเจ้าของของลิงก์นี้เมื่อ {date}",
"account.locked_info": "มีการตั้งสถานะความเป็นส่วนตัวของบัญชีนี้เป็นล็อคอยู่ เจ้าของตรวจทานผู้ที่สามารถติดตามเขาด้วยตนเอง", "account.locked_info": "มีการตั้งสถานะความเป็นส่วนตัวของบัญชีนี้เป็นล็อคอยู่ เจ้าของตรวจทานผู้ที่สามารถติดตามเขาด้วยตนเอง",
"account.media": "สื่อ", "account.media": "สื่อ",
@ -25,7 +25,7 @@
"account.mute": "ปิดเสียง @{name}", "account.mute": "ปิดเสียง @{name}",
"account.mute_notifications": "ปิดเสียงการแจ้งเตือนจาก @{name}", "account.mute_notifications": "ปิดเสียงการแจ้งเตือนจาก @{name}",
"account.muted": "ปิดเสียงอยู่", "account.muted": "ปิดเสียงอยู่",
"account.never_active": "Never", "account.never_active": "ไม่เลย",
"account.posts": "โพสต์", "account.posts": "โพสต์",
"account.posts_with_replies": "โพสต์และการตอบกลับ", "account.posts_with_replies": "โพสต์และการตอบกลับ",
"account.report": "รายงาน @{name}", "account.report": "รายงาน @{name}",
@ -53,7 +53,7 @@
"column.blocks": "ผู้ใช้ที่ปิดกั้นอยู่", "column.blocks": "ผู้ใช้ที่ปิดกั้นอยู่",
"column.community": "เส้นเวลาในเว็บ", "column.community": "เส้นเวลาในเว็บ",
"column.direct": "ข้อความโดยตรง", "column.direct": "ข้อความโดยตรง",
"column.directory": "Browse profiles", "column.directory": "เรียกดูโปรไฟล์",
"column.domain_blocks": "โดเมนที่ซ่อนอยู่", "column.domain_blocks": "โดเมนที่ซ่อนอยู่",
"column.favourites": "รายการโปรด", "column.favourites": "รายการโปรด",
"column.follow_requests": "คำขอติดตาม", "column.follow_requests": "คำขอติดตาม",
@ -63,6 +63,7 @@
"column.notifications": "การแจ้งเตือน", "column.notifications": "การแจ้งเตือน",
"column.pins": "โพสต์ที่ปักหมุด", "column.pins": "โพสต์ที่ปักหมุด",
"column.public": "เส้นเวลาที่ติดต่อกับภายนอก", "column.public": "เส้นเวลาที่ติดต่อกับภายนอก",
"column.status": "Toot",
"column_back_button.label": "ย้อนกลับ", "column_back_button.label": "ย้อนกลับ",
"column_header.hide_settings": "ซ่อนการตั้งค่า", "column_header.hide_settings": "ซ่อนการตั้งค่า",
"column_header.moveLeft_settings": "ย้ายคอลัมน์ไปทางซ้าย", "column_header.moveLeft_settings": "ย้ายคอลัมน์ไปทางซ้าย",
@ -100,8 +101,8 @@
"confirmations.delete_list.message": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการนี้อย่างถาวร?", "confirmations.delete_list.message": "คุณแน่ใจหรือไม่ว่าต้องการลบรายการนี้อย่างถาวร?",
"confirmations.domain_block.confirm": "ซ่อนทั้งโดเมน", "confirmations.domain_block.confirm": "ซ่อนทั้งโดเมน",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.logout.confirm": "Log out", "confirmations.logout.confirm": "ออกจากระบบ",
"confirmations.logout.message": "Are you sure you want to log out?", "confirmations.logout.message": "คุณแน่ใจหรือไม่ว่าต้องการออกจากระบบ?",
"confirmations.mute.confirm": "ปิดเสียง", "confirmations.mute.confirm": "ปิดเสียง",
"confirmations.mute.message": "คุณแน่ใจหรือไม่ว่าต้องการปิดเสียง {name}?", "confirmations.mute.message": "คุณแน่ใจหรือไม่ว่าต้องการปิดเสียง {name}?",
"confirmations.redraft.confirm": "ลบแล้วร่างใหม่", "confirmations.redraft.confirm": "ลบแล้วร่างใหม่",

@ -63,6 +63,7 @@
"column.notifications": "Bildirimler", "column.notifications": "Bildirimler",
"column.pins": "Sabitlenmiş gönderi", "column.pins": "Sabitlenmiş gönderi",
"column.public": "Federe zaman tüneli", "column.public": "Federe zaman tüneli",
"column.status": "Toot",
"column_back_button.label": "Geri", "column_back_button.label": "Geri",
"column_header.hide_settings": "Ayarları gizle", "column_header.hide_settings": "Ayarları gizle",
"column_header.moveLeft_settings": "Sütunu sola taşı", "column_header.moveLeft_settings": "Sütunu sola taşı",

@ -63,6 +63,7 @@
"column.notifications": "Сповіщення", "column.notifications": "Сповіщення",
"column.pins": "Закріплені дмухи", "column.pins": "Закріплені дмухи",
"column.public": "Глобальна стрічка", "column.public": "Глобальна стрічка",
"column.status": "Toot",
"column_back_button.label": "Назад", "column_back_button.label": "Назад",
"column_header.hide_settings": "Приховати налаштування", "column_header.hide_settings": "Приховати налаштування",
"column_header.moveLeft_settings": "Змістити колонку вліво", "column_header.moveLeft_settings": "Змістити колонку вліво",

@ -63,6 +63,7 @@
"column.notifications": "通知", "column.notifications": "通知",
"column.pins": "置顶嘟文", "column.pins": "置顶嘟文",
"column.public": "跨站公共时间轴", "column.public": "跨站公共时间轴",
"column.status": "Toot",
"column_back_button.label": "返回", "column_back_button.label": "返回",
"column_header.hide_settings": "隐藏设置", "column_header.hide_settings": "隐藏设置",
"column_header.moveLeft_settings": "将此栏左移", "column_header.moveLeft_settings": "将此栏左移",

@ -63,6 +63,7 @@
"column.notifications": "通知", "column.notifications": "通知",
"column.pins": "置頂文章", "column.pins": "置頂文章",
"column.public": "跨站時間軸", "column.public": "跨站時間軸",
"column.status": "Toot",
"column_back_button.label": "返回", "column_back_button.label": "返回",
"column_header.hide_settings": "隱藏設定", "column_header.hide_settings": "隱藏設定",
"column_header.moveLeft_settings": "將欄左移", "column_header.moveLeft_settings": "將欄左移",

@ -63,6 +63,7 @@
"column.notifications": "通知", "column.notifications": "通知",
"column.pins": "釘選的嘟文", "column.pins": "釘選的嘟文",
"column.public": "聯邦時間軸", "column.public": "聯邦時間軸",
"column.status": "Toot",
"column_back_button.label": "上一頁", "column_back_button.label": "上一頁",
"column_header.hide_settings": "隱藏設定", "column_header.hide_settings": "隱藏設定",
"column_header.moveLeft_settings": "將欄位向左移動", "column_header.moveLeft_settings": "將欄位向左移動",

@ -35,12 +35,12 @@ const notificationToMap = notification => ImmutableMap({
}); });
const normalizeNotification = (state, notification, usePendingItems) => { const normalizeNotification = (state, notification, usePendingItems) => {
if (usePendingItems) {
return state.update('pendingItems', list => list.unshift(notificationToMap(notification)));
}
const top = state.get('top'); const top = state.get('top');
if (usePendingItems || !top || !state.get('pendingItems').isEmpty()) {
return state.update('pendingItems', list => list.unshift(notificationToMap(notification))).update('unread', unread => unread + 1);
}
if (!top) { if (!top) {
state = state.update('unread', unread => unread + 1); state = state.update('unread', unread => unread + 1);
} }
@ -54,7 +54,7 @@ const normalizeNotification = (state, notification, usePendingItems) => {
}); });
}; };
const expandNormalizedNotifications = (state, notifications, next, usePendingItems) => { const expandNormalizedNotifications = (state, notifications, next, isLoadingRecent, usePendingItems) => {
let items = ImmutableList(); let items = ImmutableList();
notifications.forEach((n, i) => { notifications.forEach((n, i) => {
@ -63,6 +63,8 @@ const expandNormalizedNotifications = (state, notifications, next, usePendingIte
return state.withMutations(mutable => { return state.withMutations(mutable => {
if (!items.isEmpty()) { if (!items.isEmpty()) {
usePendingItems = isLoadingRecent && (usePendingItems || !mutable.get('top') || !mutable.get('pendingItems').isEmpty());
mutable.update(usePendingItems ? 'pendingItems' : 'items', list => { mutable.update(usePendingItems ? 'pendingItems' : 'items', list => {
const lastIndex = 1 + list.findLastIndex( const lastIndex = 1 + list.findLastIndex(
item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id')) item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id'))
@ -91,7 +93,7 @@ const filterNotifications = (state, accountIds) => {
const updateTop = (state, top) => { const updateTop = (state, top) => {
if (top) { if (top) {
state = state.set('unread', 0); state = state.set('unread', state.get('pendingItems').size);
} }
return state.set('top', top); return state.set('top', top);
@ -117,7 +119,7 @@ export default function notifications(state = initialState, action) {
case NOTIFICATIONS_UPDATE: case NOTIFICATIONS_UPDATE:
return normalizeNotification(state, action.notification, action.usePendingItems); return normalizeNotification(state, action.notification, action.usePendingItems);
case NOTIFICATIONS_EXPAND_SUCCESS: case NOTIFICATIONS_EXPAND_SUCCESS:
return expandNormalizedNotifications(state, action.notifications, action.next, action.usePendingItems); return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingRecent, action.usePendingItems);
case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_BLOCK_SUCCESS:
return filterNotifications(state, [action.relationship.id]); return filterNotifications(state, [action.relationship.id]);
case ACCOUNT_MUTE_SUCCESS: case ACCOUNT_MUTE_SUCCESS:

@ -40,6 +40,7 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is
if (timeline.endsWith(':pinned')) { if (timeline.endsWith(':pinned')) {
mMap.set('items', statuses.map(status => status.get('id'))); mMap.set('items', statuses.map(status => status.get('id')));
} else if (!statuses.isEmpty()) { } else if (!statuses.isEmpty()) {
usePendingItems = isLoadingRecent && (usePendingItems || !mMap.get('top') || !mMap.get('pendingItems').isEmpty());
mMap.update(usePendingItems ? 'pendingItems' : 'items', ImmutableList(), oldIds => { mMap.update(usePendingItems ? 'pendingItems' : 'items', ImmutableList(), oldIds => {
const newIds = statuses.map(status => status.get('id')); const newIds = statuses.map(status => status.get('id'));
@ -60,15 +61,16 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is
}; };
const updateTimeline = (state, timeline, status, usePendingItems) => { const updateTimeline = (state, timeline, status, usePendingItems) => {
if (usePendingItems) { const top = state.getIn([timeline, 'top']);
if (usePendingItems || !top || !state.getIn([timeline, 'pendingItems']).isEmpty()) {
if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) { if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) {
return state; return state;
} }
return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id')))); return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id'))).update('unread', unread => unread + 1));
} }
const top = state.getIn([timeline, 'top']);
const ids = state.getIn([timeline, 'items'], ImmutableList()); const ids = state.getIn([timeline, 'items'], ImmutableList());
const includesId = ids.includes(status.get('id')); const includesId = ids.includes(status.get('id'));
const unread = state.getIn([timeline, 'unread'], 0); const unread = state.getIn([timeline, 'unread'], 0);
@ -128,7 +130,7 @@ const filterTimeline = (timeline, state, relationship, statuses) => {
const updateTop = (state, timeline, top) => { const updateTop = (state, timeline, top) => {
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
if (top) mMap.set('unread', 0); if (top) mMap.set('unread', mMap.get('pendingItems').size);
mMap.set('top', top); mMap.set('top', top);
})); }));
}; };

@ -14,10 +14,10 @@ function main() {
const React = require('react'); const React = require('react');
const ReactDOM = require('react-dom'); const ReactDOM = require('react-dom');
const Rellax = require('rellax'); const Rellax = require('rellax');
const createHistory = require('history').createBrowserHistory; const { createBrowserHistory } = require('history');
const scrollToDetailedStatus = () => { const scrollToDetailedStatus = () => {
const history = createHistory(); const history = createBrowserHistory();
const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status'); const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status');
const location = history.location; const location = history.location;

@ -224,6 +224,7 @@
} }
.account__header__fields { .account__header__fields {
max-width: 100vw;
padding: 0; padding: 0;
margin: 15px -15px -15px; margin: 15px -15px -15px;
border: 0 none; border: 0 none;

@ -79,6 +79,9 @@
top: -1px; top: -1px;
border-radius: 50%; border-radius: 50%;
vertical-align: middle; vertical-align: middle;
margin-top: auto;
margin-bottom: auto;
flex: 0 0 18px;
&.checkbox { &.checkbox {
border-radius: 4px; border-radius: 4px;

@ -408,15 +408,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end end
def check_for_spam def check_for_spam
spam_check = SpamCheck.new(@status) SpamCheck.perform(@status)
return if spam_check.skip?
if spam_check.spam?
spam_check.flag!
else
spam_check.remember!
end
end end
def forward_for_reply def forward_for_reply

@ -10,10 +10,13 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri) target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri)
return if target_account.nil? || !target_account.also_known_as.include?(origin_account.uri) if target_account.nil? || target_account.suspended? || !target_account.also_known_as.include?(origin_account.uri)
unmark_as_processing!
return
end
# In case for some reason we didn't have a redirect for the profile already, set it # In case for some reason we didn't have a redirect for the profile already, set it
origin_account.update(moved_to_account: target_account) if origin_account.moved_to_account_id.nil? origin_account.update(moved_to_account: target_account)
# Initiate a re-follow for each follower # Initiate a re-follow for each follower
origin_account.followers.local.select(:id).find_in_batches do |follower_accounts| origin_account.followers.local.select(:id).find_in_batches do |follower_accounts|
@ -40,4 +43,8 @@ class ActivityPub::Activity::Move < ActivityPub::Activity
def mark_as_processing! def mark_as_processing!
redis.setex("move_in_progress:#{@account.id}", PROCESSING_COOLDOWN, true) redis.setex("move_in_progress:#{@account.id}", PROCESSING_COOLDOWN, true)
end end
def unmark_as_processing!
redis.del("move_in_progress:#{@account.id}")
end
end end

@ -4,9 +4,25 @@ class SpamCheck
include Redisable include Redisable
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
# Threshold over which two Nilsimsa values are considered
# to refer to the same text
NILSIMSA_COMPARE_THRESHOLD = 95 NILSIMSA_COMPARE_THRESHOLD = 95
NILSIMSA_MIN_SIZE = 10
EXPIRE_SET_AFTER = 1.week.seconds # Nilsimsa doesn't work well on small inputs, so below
# this size, we check only for exact matches with MD5
NILSIMSA_MIN_SIZE = 10
# How long to keep the trail of digests between updates,
# there is no reason to store it forever
EXPIRE_SET_AFTER = 1.week.seconds
# How many digests to keep in an account's trail. If it's
# too small, spam could rotate around different message templates
MAX_TRAIL_SIZE = 10
# How many detected duplicates to allow through before
# considering the message as spam
THRESHOLD = 5
def initialize(status) def initialize(status)
@account = status.account @account = status.account
@ -21,9 +37,9 @@ class SpamCheck
if insufficient_data? if insufficient_data?
false false
elsif nilsimsa? elsif nilsimsa?
any_other_digest?('nilsimsa') { |_, other_digest| nilsimsa_compare_value(digest, other_digest) >= NILSIMSA_COMPARE_THRESHOLD } digests_over_threshold?('nilsimsa') { |_, other_digest| nilsimsa_compare_value(digest, other_digest) >= NILSIMSA_COMPARE_THRESHOLD }
else else
any_other_digest?('md5') { |_, other_digest| other_digest == digest } digests_over_threshold?('md5') { |_, other_digest| other_digest == digest }
end end
end end
@ -38,7 +54,7 @@ class SpamCheck
# get the correct status ID back, we have to save it in the string value # get the correct status ID back, we have to save it in the string value
redis.zadd(redis_key, @status.id, digest_with_algorithm) redis.zadd(redis_key, @status.id, digest_with_algorithm)
redis.zremrangebyrank(redis_key, '0', '-10') redis.zremrangebyrank(redis_key, 0, -(MAX_TRAIL_SIZE + 1))
redis.expire(redis_key, EXPIRE_SET_AFTER) redis.expire(redis_key, EXPIRE_SET_AFTER)
end end
@ -78,6 +94,20 @@ class SpamCheck
end end
end end
class << self
def perform(status)
spam_check = new(status)
return if spam_check.skip?
if spam_check.spam?
spam_check.flag!
else
spam_check.remember!
end
end
end
private private
def disabled? def disabled?
@ -149,14 +179,14 @@ class SpamCheck
redis.zrange(redis_key, 0, -1) redis.zrange(redis_key, 0, -1)
end end
def any_other_digest?(filter_algorithm) def digests_over_threshold?(filter_algorithm)
other_digests.any? do |record| other_digests.select do |record|
algorithm, other_digest, status_id = record.split(':') algorithm, other_digest, status_id = record.split(':')
next unless algorithm == filter_algorithm next unless algorithm == filter_algorithm
yield algorithm, other_digest, status_id yield algorithm, other_digest, status_id
end end.size >= THRESHOLD
end end
def matching_status_ids def matching_status_ids

@ -4,7 +4,7 @@ module Omniauthable
extend ActiveSupport::Concern extend ActiveSupport::Concern
TEMP_EMAIL_PREFIX = 'change@me' TEMP_EMAIL_PREFIX = 'change@me'
TEMP_EMAIL_REGEX = /\Achange@me/ TEMP_EMAIL_REGEX = /\A#{TEMP_EMAIL_PREFIX}/.freeze
included do included do
devise :omniauthable devise :omniauthable
@ -28,8 +28,8 @@ module Omniauthable
# to prevent the identity being locked with accidentally created accounts. # to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which # Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date. # can be cleaned up at a later date.
user = signed_in_resource || identity.user user = signed_in_resource || identity.user
user = create_for_oauth(auth) if user.nil? user ||= create_for_oauth(auth)
if identity.user.nil? if identity.user.nil?
identity.user = user identity.user = user
@ -45,7 +45,18 @@ module Omniauthable
# exists, we assign a temporary email and ask the user to verify it on # exists, we assign a temporary email and ask the user to verify it on
# the next step via Auth::SetupController.show # the next step via Auth::SetupController.show
user = User.new(user_params_from_auth(auth)) strategy = Devise.omniauth_configs[auth.provider.to_sym].strategy
assume_verified = strategy&.security&.assume_email_is_verified
email_is_verified = auth.info.verified || auth.info.verified_email || assume_verified
email = auth.info.verified_email || auth.info.email
email = nil unless email_is_verified
user = User.find_by(email: email) if email_is_verified
return user unless user.nil?
user = User.new(user_params_from_auth(email, auth))
user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/ user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/
user.skip_confirmation! user.skip_confirmation!
user.save! user.save!
@ -54,14 +65,7 @@ module Omniauthable
private private
def user_params_from_auth(auth) def user_params_from_auth(email, auth)
strategy = Devise.omniauth_configs[auth.provider.to_sym].strategy
assume_verified = strategy.try(:security).try(:assume_email_is_verified)
email_is_verified = auth.info.verified || auth.info.verified_email || assume_verified
email = auth.info.verified_email || auth.info.email
email = email_is_verified && !User.exists?(email: auth.info.email) && email
display_name = auth.info.full_name || [auth.info.first_name, auth.info.last_name].join(' ')
{ {
email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com", email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0, 20], password: Devise.friendly_token[0, 20],
@ -69,7 +73,7 @@ module Omniauthable
external: true, external: true,
account_attributes: { account_attributes: {
username: ensure_unique_username(auth.uid), username: ensure_unique_username(auth.uid),
display_name: display_name, display_name: auth.info.full_name || [auth.info.first_name, auth.info.last_name].join(' '),
}, },
} }
end end

@ -63,7 +63,7 @@ class CustomEmoji < ApplicationRecord
def copy! def copy!
copy = self.class.find_or_initialize_by(domain: nil, shortcode: shortcode) copy = self.class.find_or_initialize_by(domain: nil, shortcode: shortcode)
copy.image = image copy.image = image
copy.save! copy.tap(&:save!)
end end
class << self class << self

@ -3,5 +3,5 @@
class Form::DeleteConfirmation class Form::DeleteConfirmation
include ActiveModel::Model include ActiveModel::Model
attr_accessor :password attr_accessor :password, :username
end end

@ -3,5 +3,5 @@
class Form::TwoFactorConfirmation class Form::TwoFactorConfirmation
include ActiveModel::Model include ActiveModel::Model
attr_accessor :code attr_accessor :otp_attempt
end end

@ -20,7 +20,7 @@
class Tag < ApplicationRecord class Tag < ApplicationRecord
has_and_belongs_to_many :statuses has_and_belongs_to_many :statuses
has_and_belongs_to_many :accounts has_and_belongs_to_many :accounts
has_and_belongs_to_many :sample_accounts, -> { searchable.discoverable.popular.limit(3) }, class_name: 'Account' has_and_belongs_to_many :sample_accounts, -> { local.discoverable.popular.limit(3) }, class_name: 'Account'
has_many :featured_tags, dependent: :destroy, inverse_of: :tag has_many :featured_tags, dependent: :destroy, inverse_of: :tag
has_one :account_tag_stat, dependent: :destroy has_one :account_tag_stat, dependent: :destroy
@ -39,6 +39,7 @@ class Tag < ApplicationRecord
scope :listable, -> { where(listable: [true, nil]) } scope :listable, -> { where(listable: [true, nil]) }
scope :discoverable, -> { listable.joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).order(Arel.sql('account_tag_stats.accounts_count desc')) } scope :discoverable, -> { listable.joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).order(Arel.sql('account_tag_stats.accounts_count desc')) }
scope :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('count(*) desc')) } scope :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('count(*) desc')) }
scope :matches_name, ->(value) { where(arel_table[:name].matches("#{value}%")) }
delegate :accounts_count, delegate :accounts_count,
:accounts_count=, :accounts_count=,

@ -0,0 +1,44 @@
# frozen_string_literal: true
class TagFilter
attr_reader :params
def initialize(params)
@params = params
end
def results
scope = Tag.unscoped
params.each do |key, value|
next if key.to_s == 'page'
scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
end
scope.order(id: :desc)
end
private
def scope_for(key, value)
case key.to_s
when 'directory'
Tag.discoverable
when 'reviewed'
Tag.reviewed.order(reviewed_at: :desc)
when 'unreviewed'
Tag.unreviewed
when 'pending_review'
Tag.pending_review.order(requested_review_at: :desc)
when 'popular'
Tag.order('max_score DESC NULLS LAST')
when 'active'
Tag.order('last_status_at DESC NULLS LAST')
when 'name'
Tag.matches_name(value)
else
raise "Unknown filter: #{key}"
end
end
end

@ -33,7 +33,7 @@ class InstancePresenter
end end
def sample_accounts def sample_accounts
Rails.cache.fetch('sample_accounts', expires_in: 12.hours) { Account.discoverable.popular.limit(3) } Rails.cache.fetch('sample_accounts', expires_in: 12.hours) { Account.local.discoverable.popular.limit(3) }
end end
def version_number def version_number

@ -33,6 +33,7 @@ class ProcessMentionsService < BaseService
end end
status.save! status.save!
check_for_spam(status)
mentions.each { |mention| create_notification(mention) } mentions.each { |mention| create_notification(mention) }
end end
@ -61,4 +62,8 @@ class ProcessMentionsService < BaseService
def resolve_account_service def resolve_account_service
ResolveAccountService.new ResolveAccountService.new
end end
def check_for_spam(status)
SpamCheck.perform(status)
end
end end

@ -6,9 +6,12 @@ class UnfollowService < BaseService
# Unfollow and notify the remote user # Unfollow and notify the remote user
# @param [Account] source_account Where to unfollow from # @param [Account] source_account Where to unfollow from
# @param [Account] target_account Which to unfollow # @param [Account] target_account Which to unfollow
def call(source_account, target_account) # @param [Hash] options
# @option [Boolean] :skip_unmerge
def call(source_account, target_account, options = {})
@source_account = source_account @source_account = source_account
@target_account = target_account @target_account = target_account
@options = options
unfollow! || undo_follow_request! unfollow! || undo_follow_request!
end end
@ -21,9 +24,11 @@ class UnfollowService < BaseService
return unless follow return unless follow
follow.destroy! follow.destroy!
create_notification(follow) if !@target_account.local? && @target_account.activitypub? create_notification(follow) if !@target_account.local? && @target_account.activitypub?
create_reject_notification(follow) if @target_account.local? && !@source_account.local? && @source_account.activitypub? create_reject_notification(follow) if @target_account.local? && !@source_account.local? && @source_account.activitypub?
UnmergeWorker.perform_async(@target_account.id, @source_account.id) UnmergeWorker.perform_async(@target_account.id, @source_account.id) unless @options[:skip_unmerge]
follow follow
end end
@ -33,7 +38,9 @@ class UnfollowService < BaseService
return unless follow_request return unless follow_request
follow_request.destroy! follow_request.destroy!
create_notification(follow_request) unless @target_account.local? create_notification(follow_request) unless @target_account.local?
follow_request follow_request
end end

@ -4,35 +4,35 @@
.dashboard__counters .dashboard__counters
%div %div
= link_to admin_accounts_url(local: 1, recent: 1) do = link_to admin_accounts_url(local: 1, recent: 1) do
.dashboard__counters__num= number_with_delimiter @users_count .dashboard__counters__num= number_to_human @users_count, strip_insignificant_zeros: true
.dashboard__counters__label= t 'admin.dashboard.total_users' .dashboard__counters__label= t 'admin.dashboard.total_users'
%div %div
%div %div
.dashboard__counters__num= number_with_delimiter @registrations_week .dashboard__counters__num= number_to_human @registrations_week, strip_insignificant_zeros: true
.dashboard__counters__label= t 'admin.dashboard.week_users_new' .dashboard__counters__label= t 'admin.dashboard.week_users_new'
%div %div
%div %div
.dashboard__counters__num= number_with_delimiter @logins_week .dashboard__counters__num= number_to_human @logins_week, strip_insignificant_zeros: true
.dashboard__counters__label= t 'admin.dashboard.week_users_active' .dashboard__counters__label= t 'admin.dashboard.week_users_active'
%div %div
= link_to admin_pending_accounts_path do = link_to admin_pending_accounts_path do
.dashboard__counters__num= number_with_delimiter @pending_users_count .dashboard__counters__num= number_to_human @pending_users_count, strip_insignificant_zeros: true
.dashboard__counters__label= t 'admin.dashboard.pending_users' .dashboard__counters__label= t 'admin.dashboard.pending_users'
%div %div
= link_to admin_reports_url do = link_to admin_reports_url do
.dashboard__counters__num= number_with_delimiter @reports_count .dashboard__counters__num= number_to_human @reports_count, strip_insignificant_zeros: true
.dashboard__counters__label= t 'admin.dashboard.open_reports' .dashboard__counters__label= t 'admin.dashboard.open_reports'
%div %div
= link_to admin_tags_path(review: 'pending_review') do = link_to admin_tags_path(pending_review: '1') do
.dashboard__counters__num= number_with_delimiter @pending_tags_count .dashboard__counters__num= number_to_human @pending_tags_count, strip_insignificant_zeros: true
.dashboard__counters__label= t 'admin.dashboard.pending_tags' .dashboard__counters__label= t 'admin.dashboard.pending_tags'
%div %div
%div %div
.dashboard__counters__num= number_with_delimiter @interactions_week .dashboard__counters__num= number_to_human @interactions_week, strip_insignificant_zeros: true
.dashboard__counters__label= t 'admin.dashboard.week_interactions' .dashboard__counters__label= t 'admin.dashboard.week_interactions'
%div %div
= link_to sidekiq_url do = link_to sidekiq_url do
.dashboard__counters__num= number_with_delimiter @queue_backlog .dashboard__counters__num= number_to_human @queue_backlog, strip_insignificant_zeros: true
.dashboard__counters__label= t 'admin.dashboard.backlog' .dashboard__counters__label= t 'admin.dashboard.backlog'
.dashboard__widgets .dashboard__widgets

@ -5,16 +5,36 @@
.filter-subset .filter-subset
%strong= t('admin.tags.context') %strong= t('admin.tags.context')
%ul %ul
%li= filter_link_to t('generic.all'), context: nil %li= filter_link_to t('generic.all'), directory: nil
%li= filter_link_to t('admin.tags.directory'), context: 'directory' %li= filter_link_to t('admin.tags.directory'), directory: '1'
.filter-subset .filter-subset
%strong= t('admin.tags.review') %strong= t('admin.tags.review')
%ul %ul
%li= filter_link_to t('generic.all'), review: nil %li= filter_link_to t('generic.all'), reviewed: nil, unreviewed: nil, pending_review: nil
%li= filter_link_to t('admin.tags.unreviewed'), review: 'unreviewed' %li= filter_link_to t('admin.tags.unreviewed'), unreviewed: '1', reviewed: nil, pending_review: nil
%li= filter_link_to t('admin.tags.reviewed'), review: 'reviewed' %li= filter_link_to t('admin.tags.reviewed'), reviewed: '1', unreviewed: nil, pending_review: nil
%li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{Tag.pending_review.count})"], ' '), review: 'pending_review' %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{Tag.pending_review.count})"], ' '), pending_review: '1', reviewed: nil, unreviewed: nil
.filter-subset
%strong= t('generic.order_by')
%ul
%li= filter_link_to t('admin.tags.most_recent'), popular: nil, active: nil
%li= filter_link_to t('admin.tags.most_popular'), popular: '1', active: nil
%li= filter_link_to t('admin.tags.last_active'), active: '1', popular: nil
= form_tag admin_tags_url, method: 'GET', class: 'simple_form' do
.fields-group
- Admin::FilterHelper::TAGS_FILTERS.each do |key|
= hidden_field_tag key, params[key] if params[key].present?
- %i(name).each do |key|
.input.string.optional
= text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.tags.#{key}")
.actions
%button= t('admin.accounts.search')
= link_to t('admin.accounts.reset'), admin_tags_path, class: 'button negative'
%hr.spacer/ %hr.spacer/
@ -43,7 +63,7 @@
= paginate @tags = paginate @tags
- if params[:review] == 'pending_review' - if params[:pending_review] == '1'
%hr.spacer/ %hr.spacer/
%div{ style: 'overflow: hidden' } %div{ style: 'overflow: hidden' }

@ -3,7 +3,7 @@
.dashboard__counters .dashboard__counters
%div %div
= link_to web_url("timelines/tag/#{@tag.name}") do = link_to tag_url(@tag), target: '_blank', rel: 'noopener' do
.dashboard__counters__num= number_with_delimiter @accounts_today .dashboard__counters__num= number_with_delimiter @accounts_today
.dashboard__counters__label= t 'admin.tags.accounts_today' .dashboard__counters__label= t 'admin.tags.accounts_today'
%div %div

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

Loading…
Cancel
Save