Merge pull request #1848 from ClearlyClaire/glitch-soc/merge-upstream

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

@ -20,7 +20,7 @@
"forwardPorts": [3000, 4000],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "bundle install --path vendor/bundle && yarn install && ./bin/rails db:setup",
"postCreateCommand": "bundle install --path vendor/bundle && yarn install && git checkout -- Gemfile.lock && ./bin/rails db:setup",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"

@ -27,6 +27,7 @@ services:
ES_ENABLED: 'true'
ES_HOST: es
ES_PORT: '9200'
LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000
# Overrides default command so things don't shut down after the process ends.
command: sleep infinity
networks:
@ -72,6 +73,12 @@ services:
soft: -1
hard: -1
libretranslate:
image: libretranslate/libretranslate:v1.2.9
restart: unless-stopped
networks:
- internal_network
volumes:
postgres-data:
redis-data:

@ -10,6 +10,9 @@ on:
paths:
- .github/workflows/build-image.yml
- Dockerfile
permissions:
contents: read
jobs:
build-image:
runs-on: ubuntu-latest

@ -9,6 +9,9 @@ on:
env:
RAILS_ENV: test
permissions:
contents: read
jobs:
check-i18n:
runs-on: ubuntu-latest

@ -55,7 +55,7 @@ jobs:
with:
node-version: 16.x
cache: yarn
- name: Intall dependencies
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Set-up RuboCop Problem Mathcher
uses: r7kamura/rubocop-problem-matchers-action@v1

@ -5,7 +5,7 @@ SHELL ["/bin/bash", "-c"]
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
# Install Node v16 (LTS)
ENV NODE_VER="16.16.0"
ENV NODE_VER="16.17.1"
RUN ARCH= && \
dpkgArch="$(dpkg --print-architecture)" && \
case "${dpkgArch##*-}" in \
@ -19,7 +19,7 @@ RUN ARCH= && \
esac && \
echo "Etc/UTC" > /etc/localtime && \
apt-get update && \
apt-get install -y --no-install-recommends ca-certificates wget python apt-utils && \
apt-get install -y --no-install-recommends ca-certificates wget python3 apt-utils && \
cd ~ && \
wget -q https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \

@ -7,7 +7,7 @@ gem 'pkg-config', '~> 1.4'
gem 'rexml', '~> 3.2'
gem 'puma', '~> 5.6'
gem 'rails', '~> 6.1.6'
gem 'rails', '~> 6.1.7'
gem 'sprockets', '~> 3.7.2'
gem 'thor', '~> 1.2'
gem 'rack', '~> 2.2.4'
@ -46,7 +46,7 @@ gem 'omniauth-rails_csrf_protection', '~> 0.1'
gem 'color_diff', '~> 0.1'
gem 'discard', '~> 1.2'
gem 'doorkeeper', '~> 5.5'
gem 'doorkeeper', '~> 5.6'
gem 'ed25519', '~> 1.3'
gem 'fast_blank', '~> 1.0'
gem 'fastimage'
@ -55,7 +55,7 @@ gem 'redis-namespace', '~> 1.9'
gem 'htmlentities', '~> 4.3'
gem 'http', '~> 5.1'
gem 'http_accept_language', '~> 2.1'
gem 'httplog', '~> 1.5.0'
gem 'httplog', '~> 1.6.0'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.2'
gem 'link_header', '~> 0.0'
@ -116,7 +116,7 @@ end
group :test do
gem 'capybara', '~> 3.37'
gem 'climate_control', '~> 0.2'
gem 'faker', '~> 2.22'
gem 'faker', '~> 2.23'
gem 'microformats', '~> 4.4'
gem 'rails-controller-testing', '~> 1.0'
gem 'rspec-sidekiq', '~> 3.1'

@ -10,40 +10,40 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (6.1.6)
actionpack (= 6.1.6)
activesupport (= 6.1.6)
actioncable (6.1.7)
actionpack (= 6.1.7)
activesupport (= 6.1.7)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.1.6)
actionpack (= 6.1.6)
activejob (= 6.1.6)
activerecord (= 6.1.6)
activestorage (= 6.1.6)
activesupport (= 6.1.6)
actionmailbox (6.1.7)
actionpack (= 6.1.7)
activejob (= 6.1.7)
activerecord (= 6.1.7)
activestorage (= 6.1.7)
activesupport (= 6.1.7)
mail (>= 2.7.1)
actionmailer (6.1.6)
actionpack (= 6.1.6)
actionview (= 6.1.6)
activejob (= 6.1.6)
activesupport (= 6.1.6)
actionmailer (6.1.7)
actionpack (= 6.1.7)
actionview (= 6.1.7)
activejob (= 6.1.7)
activesupport (= 6.1.7)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.1.6)
actionview (= 6.1.6)
activesupport (= 6.1.6)
actionpack (6.1.7)
actionview (= 6.1.7)
activesupport (= 6.1.7)
rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.1.6)
actionpack (= 6.1.6)
activerecord (= 6.1.6)
activestorage (= 6.1.6)
activesupport (= 6.1.6)
actiontext (6.1.7)
actionpack (= 6.1.7)
activerecord (= 6.1.7)
activestorage (= 6.1.7)
activesupport (= 6.1.7)
nokogiri (>= 1.8.5)
actionview (6.1.6)
activesupport (= 6.1.6)
actionview (6.1.7)
activesupport (= 6.1.7)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -54,22 +54,22 @@ GEM
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
active_record_query_trace (1.8)
activejob (6.1.6)
activesupport (= 6.1.6)
activejob (6.1.7)
activesupport (= 6.1.7)
globalid (>= 0.3.6)
activemodel (6.1.6)
activesupport (= 6.1.6)
activerecord (6.1.6)
activemodel (= 6.1.6)
activesupport (= 6.1.6)
activestorage (6.1.6)
actionpack (= 6.1.6)
activejob (= 6.1.6)
activerecord (= 6.1.6)
activesupport (= 6.1.6)
activemodel (6.1.7)
activesupport (= 6.1.7)
activerecord (6.1.7)
activemodel (= 6.1.7)
activesupport (= 6.1.7)
activestorage (6.1.7)
actionpack (= 6.1.7)
activejob (= 6.1.7)
activerecord (= 6.1.7)
activesupport (= 6.1.7)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (6.1.6)
activesupport (6.1.7)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -110,12 +110,11 @@ GEM
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
better_html (1.0.16)
actionview (>= 4.0)
activesupport (>= 4.0)
better_html (2.0.1)
actionview (>= 6.0)
activesupport (>= 6.0)
ast (~> 2.0)
erubi (~> 1.4)
html_tokenizer (~> 0.0.6)
parser (>= 2.4)
smart_properties
bindata (2.4.10)
@ -176,7 +175,7 @@ GEM
coderay (1.1.3)
color_diff (0.1)
concurrent-ruby (1.1.10)
connection_pool (2.2.5)
connection_pool (2.3.0)
cose (1.2.1)
cbor (~> 0.5.9)
openssl-signature_algorithm (~> 1.0)
@ -207,7 +206,7 @@ GEM
docile (1.3.4)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.5.4)
doorkeeper (5.6.0)
railties (>= 5)
dotenv (2.8.1)
dotenv-rails (2.8.1)
@ -224,12 +223,12 @@ GEM
faraday (~> 1)
multi_json
encryptor (3.0.0)
erubi (1.10.0)
erubi (1.11.0)
et-orbi (1.2.7)
tzinfo
excon (0.76.0)
fabrication (2.30.0)
faker (2.22.0)
faker (2.23.0)
i18n (>= 1.8.11, < 2)
faraday (1.9.3)
faraday-em_http (~> 1.0)
@ -301,7 +300,6 @@ GEM
highline (2.0.3)
hiredis (0.6.3)
hkdf (0.3.0)
html_tokenizer (0.0.7)
htmlentities (4.3.4)
http (5.1.0)
addressable (~> 2.8)
@ -313,15 +311,15 @@ GEM
http-form_data (2.3.0)
http_accept_language (2.1.1)
httpclient (2.8.3)
httplog (1.5.0)
rack (>= 1.0)
httplog (1.6.0)
rack (>= 2.0)
rainbow (>= 2.0.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
i18n-tasks (1.0.11)
i18n-tasks (1.0.12)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
better_html (~> 1.0)
better_html (>= 1.0, < 3.0)
erubi
highline (>= 2.0.0)
i18n
@ -386,7 +384,7 @@ GEM
activesupport (>= 4)
railties (>= 4)
request_store (~> 1.0)
loofah (2.18.0)
loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -407,7 +405,7 @@ GEM
mime-types-data (3.2022.0105)
mini_mime (1.1.2)
mini_portile2 (2.8.0)
minitest (5.16.2)
minitest (5.16.3)
msgpack (1.5.4)
multi_json (1.15.0)
multipart-post (2.1.1)
@ -454,7 +452,7 @@ GEM
orm_adapter (0.5.0)
ox (2.14.11)
parallel (1.22.1)
parser (3.1.2.0)
parser (3.1.2.1)
ast (~> 2.4.1)
parslet (2.0.0)
pastel (0.8.0)
@ -502,20 +500,20 @@ GEM
rack
rack-test (2.0.2)
rack (>= 1.3)
rails (6.1.6)
actioncable (= 6.1.6)
actionmailbox (= 6.1.6)
actionmailer (= 6.1.6)
actionpack (= 6.1.6)
actiontext (= 6.1.6)
actionview (= 6.1.6)
activejob (= 6.1.6)
activemodel (= 6.1.6)
activerecord (= 6.1.6)
activestorage (= 6.1.6)
activesupport (= 6.1.6)
rails (6.1.7)
actioncable (= 6.1.7)
actionmailbox (= 6.1.7)
actionmailer (= 6.1.7)
actionpack (= 6.1.7)
actiontext (= 6.1.7)
actionview (= 6.1.7)
activejob (= 6.1.7)
activemodel (= 6.1.7)
activerecord (= 6.1.7)
activestorage (= 6.1.7)
activesupport (= 6.1.7)
bundler (>= 1.15.0)
railties (= 6.1.6)
railties (= 6.1.7)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
@ -531,9 +529,9 @@ GEM
railties (>= 6.0.0, < 7)
rails-settings-cached (0.6.6)
rails (>= 4.2.0)
railties (6.1.6)
actionpack (= 6.1.6)
activesupport (= 6.1.6)
railties (6.1.7)
actionpack (= 6.1.7)
activesupport (= 6.1.7)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -613,10 +611,10 @@ GEM
activerecord (>= 4.0.0)
railties (>= 4.0.0)
semantic_range (3.0.0)
sidekiq (6.5.5)
connection_pool (>= 2.2.2)
sidekiq (6.5.7)
connection_pool (>= 2.2.5)
rack (~> 2.0)
redis (>= 4.5.0)
redis (>= 4.5.0, < 5)
sidekiq-bulk (0.2.0)
sidekiq
sidekiq-scheduler (4.0.2)
@ -686,12 +684,12 @@ GEM
unf (~> 0.1.0)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
tzinfo-data (1.2022.3)
tzinfo-data (1.2022.4)
tzinfo (>= 1.0.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.8.2)
unicode-display_width (2.1.0)
unicode-display_width (2.3.0)
uniform_notifier (1.16.0)
validate_email (0.1.6)
activemodel (>= 3.0)
@ -764,11 +762,11 @@ DEPENDENCIES
devise-two-factor (~> 4.0)
devise_pam_authenticatable2 (~> 9.2)
discard (~> 1.2)
doorkeeper (~> 5.5)
doorkeeper (~> 5.6)
dotenv-rails (~> 2.8)
ed25519 (~> 1.3)
fabrication (~> 2.30)
faker (~> 2.22)
faker (~> 2.23)
fast_blank (~> 1.0)
fastimage
fog-core (<= 2.1.0)
@ -781,7 +779,7 @@ DEPENDENCIES
htmlentities (~> 4.3)
http (~> 5.1)
http_accept_language (~> 2.1)
httplog (~> 1.5.0)
httplog (~> 1.6.0)
i18n-tasks (~> 1.0)
idn-ruby
json-ld
@ -820,7 +818,7 @@ DEPENDENCIES
rack (~> 2.2.4)
rack-attack (~> 6.6)
rack-cors (~> 1.1)
rails (~> 6.1.6)
rails (~> 6.1.7)
rails-controller-testing (~> 1.0)
rails-i18n (~> 6.0)
rails-settings-cached (~> 0.6)

@ -10,10 +10,10 @@ class AboutController < ApplicationController
before_action :require_open_federation!, only: [:show, :more]
before_action :set_body_classes, only: :show
before_action :set_instance_presenter
before_action :set_expires_in, only: [:more, :terms]
before_action :set_expires_in, only: [:more]
before_action :set_registration_form_time, only: :show
skip_before_action :require_functional!, only: [:more, :terms]
skip_before_action :require_functional!, only: [:more]
def show; end
@ -28,8 +28,6 @@ class AboutController < ApplicationController
@blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
end
def terms; end
helper_method :display_blocks?
helper_method :display_blocks_rationale?
helper_method :public_fetch_mode?

@ -7,7 +7,7 @@ class AccountsController < ApplicationController
include AccountControllerConcern
include SignatureAuthentication
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
before_action :set_body_classes

@ -6,7 +6,7 @@ class ActivityPub::ClaimsController < ActivityPub::BaseController
skip_before_action :authenticate_user!
before_action :require_signature!
before_action :require_account_signature!
before_action :set_claim_result
def create

@ -4,7 +4,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
include SignatureVerification
include AccountOwnedConcern
before_action :require_signature!, if: :authorized_fetch_mode?
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_items
before_action :set_size
before_action :set_type

@ -4,7 +4,7 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro
include SignatureVerification
include AccountOwnedConcern
before_action :require_signature!
before_action :require_account_signature!
before_action :set_items
before_action :set_cache_headers

@ -6,7 +6,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
include AccountOwnedConcern
before_action :skip_unknown_actor_activity
before_action :require_signature!
before_action :require_actor_signature!
skip_before_action :authenticate_user!
def create
@ -49,17 +49,17 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
end
def upgrade_account
if signed_request_account.ostatus?
if signed_request_account&.ostatus?
signed_request_account.update(last_webfingered_at: nil)
ResolveAccountWorker.perform_async(signed_request_account.acct)
end
DeliveryFailureTracker.reset!(signed_request_account.inbox_url)
DeliveryFailureTracker.reset!(signed_request_actor.inbox_url)
end
def process_collection_synchronization
raw_params = request.headers['Collection-Synchronization']
return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true'
return if raw_params.blank? || ENV['DISABLE_FOLLOWERS_SYNCHRONIZATION'] == 'true' || signed_request_account.nil?
# Re-using the syntax for signature parameters
tree = SignatureParamsParser.new.parse(raw_params)
@ -71,6 +71,6 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
end
def process_payload
ActivityPub::ProcessingWorker.perform_async(signed_request_account.id, body, @account&.id)
ActivityPub::ProcessingWorker.perform_async(signed_request_actor.id, body, @account&.id, signed_request_actor.class.name)
end
end

@ -6,7 +6,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
include SignatureVerification
include AccountOwnedConcern
before_action :require_signature!, if: :authorized_fetch_mode?
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_statuses
before_action :set_cache_headers

@ -7,7 +7,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
DESCENDANTS_LIMIT = 60
before_action :require_signature!, if: :authorized_fetch_mode?
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_status
before_action :set_cache_headers
before_action :set_replies

@ -131,4 +131,10 @@ class Api::BaseController < ApplicationController
def disallow_unauthenticated_api_access?
authorized_fetch_mode?
end
private
def respond_with_error(code)
render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code
end
end

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
before_action :set_account
after_action :insert_pagination_headers

@ -1,7 +1,7 @@
# frozen_string_literal: true
class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
before_action -> { authorize_if_got_token! :read, :'read:accounts' }
before_action :set_account
after_action :insert_pagination_headers

@ -30,12 +30,12 @@ class Api::V1::AccountsController < Api::BaseController
self.response_body = Oj.dump(response.body)
self.status = response.status
rescue ActiveRecord::RecordInvalid => e
render json: ValidationErrorFormatter.new(e, :'account.username' => :username, :'invite_request.text' => :reason).as_json, status: :unprocessable_entity
render json: ValidationErrorFormatter.new(e, 'account.username': :username, 'invite_request.text': :reason).as_json, status: :unprocessable_entity
end
def follow
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, with_rate_limit: true)
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify? } }, requested_map: { @account.id => false } }
follow = FollowService.new.call(current_user.account, @account, reblogs: params.key?(:reblogs) ? truthy_param?(:reblogs) : nil, notify: params.key?(:notify) ? truthy_param?(:notify) : nil, languages: params.key?(:languages) ? params[:languages] : nil, with_rate_limit: true)
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: follow.show_reblogs?, notify: follow.notify?, languages: follow.languages } }, requested_map: { @account.id => false } }
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(**options)
end

@ -0,0 +1,29 @@
# frozen_string_literal: true
class Api::V1::Statuses::TranslationsController < Api::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
before_action :set_status
before_action :set_translation
rescue_from TranslationService::NotConfiguredError, with: :not_found
rescue_from TranslationService::UnexpectedResponseError, TranslationService::QuotaExceededError, TranslationService::TooManyRequestsError, with: :service_unavailable
def create
render json: @translation, serializer: REST::TranslationSerializer
end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def set_translation
@translation = TranslateStatusService.new.call(@status, content_locale)
end
end

@ -45,10 +45,14 @@ module SignatureVerification
end
end
def require_signature!
def require_account_signature!
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_account
end
def require_actor_signature!
render plain: signature_verification_failure_reason, status: signature_verification_failure_code unless signed_request_actor
end
def signed_request?
request.headers['Signature'].present?
end
@ -68,7 +72,11 @@ module SignatureVerification
end
def signed_request_account
return @signed_request_account if defined?(@signed_request_account)
signed_request_actor.is_a?(Account) ? signed_request_actor : nil
end
def signed_request_actor
return @signed_request_actor if defined?(@signed_request_actor)
raise SignatureVerificationError, 'Request not signed' unless signed_request?
raise SignatureVerificationError, 'Incompatible request signature. keyId and signature are required' if missing_required_signature_parameters?
@ -78,26 +86,30 @@ module SignatureVerification
verify_signature_strength!
verify_body_digest!
account = account_from_key_id(signature_params['keyId'])
actor = actor_from_key_id(signature_params['keyId'])
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if account.nil?
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
signature = Base64.decode64(signature_params['signature'])
compare_signed_string = build_signed_string
return account unless verify_signature(account, signature, compare_signed_string).nil?
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
account = stoplight_wrap_request { account.possibly_stale? ? account.refresh! : account_refresh_key(account) }
actor = stoplight_wrap_request { actor_refresh_key!(actor) }
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if account.nil?
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
return account unless verify_signature(account, signature, compare_signed_string).nil?
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
@signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)"
@signed_request_account = nil
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)"
rescue SignatureVerificationError => e
@signature_verification_failure_reason = e.message
@signed_request_account = nil
fail_with! e.message
rescue HTTP::Error, OpenSSL::SSL::SSLError => e
fail_with! "Failed to fetch remote data: #{e.message}"
rescue Mastodon::UnexpectedResponseError
fail_with! 'Failed to fetch remote data (got unexpected reply from server)'
rescue Stoplight::Error::RedLight
fail_with! 'Fetching attempt skipped because of recent connection failure'
end
def request_body
@ -106,6 +118,11 @@ module SignatureVerification
private
def fail_with!(message)
@signature_verification_failure_reason = message
@signed_request_actor = nil
end
def signature_params
@signature_params ||= begin
raw_signature = request.headers['Signature']
@ -138,13 +155,23 @@ module SignatureVerification
digests = request.headers['Digest'].split(',').map { |digest| digest.split('=', 2) }.map { |key, value| [key.downcase, value] }
sha256 = digests.assoc('sha-256')
raise SignatureVerificationError, "Mastodon only supports SHA-256 in Digest header. Offered algorithms: #{digests.map(&:first).join(', ')}" if sha256.nil?
raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}" if body_digest != sha256[1]
return if body_digest == sha256[1]
digest_size = begin
Base64.strict_decode64(sha256[1].strip).length
rescue ArgumentError
raise SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a valid base64 string. Given digest: #{sha256[1]}"
end
raise SignatureVerificationError, "Invalid Digest value. The provided Digest value is not a SHA-256 digest. Given digest: #{sha256[1]}" if digest_size != 32
raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}"
end
def verify_signature(account, signature, compare_signed_string)
if account.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string)
@signed_request_account = account
@signed_request_account
def verify_signature(actor, signature, compare_signed_string)
if actor.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string)
@signed_request_actor = actor
@signed_request_actor
end
rescue OpenSSL::PKey::RSAError
nil
@ -207,7 +234,7 @@ module SignatureVerification
signature_params['keyId'].blank? || signature_params['signature'].blank?
end
def account_from_key_id(key_id)
def actor_from_key_id(key_id)
domain = key_id.start_with?('acct:') ? key_id.split('@').last : key_id
if domain_not_allowed?(domain)
@ -216,27 +243,34 @@ module SignatureVerification
end
if key_id.start_with?('acct:')
stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, '')) }
stoplight_wrap_request { ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, ''), suppress_errors: false) }
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
account = ActivityPub::TagManager.instance.uri_to_resource(key_id, Account)
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false) }
account = ActivityPub::TagManager.instance.uri_to_actor(key_id)
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false, suppress_errors: false) }
account
end
rescue Mastodon::HostValidationError
nil
rescue Mastodon::PrivateNetworkAddressError => e
raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, ActivityPub::FetchRemoteKeyService::Error, Webfinger::Error => e
raise SignatureVerificationError, e.message
end
def stoplight_wrap_request(&block)
Stoplight("source:#{request.remote_ip}", &block)
.with_fallback { nil }
.with_threshold(1)
.with_cool_off_time(5.minutes.seconds)
.with_error_handler { |error, handle| error.is_a?(HTTP::Error) || error.is_a?(OpenSSL::SSL::SSLError) ? handle.call(error) : raise(error) }
.run
end
def account_refresh_key(account)
return if account.local? || !account.activitypub?
ActivityPub::FetchRemoteAccountService.new.call(account.uri, only_key: true)
def actor_refresh_key!(actor)
return if actor.local? || !actor.activitypub?
return actor.refresh! if actor.respond_to?(:refresh!) && actor.possibly_stale?
ActivityPub::FetchRemoteActorService.new.call(actor.uri, only_key: true, suppress_errors: false)
rescue Mastodon::PrivateNetworkAddressError => e
raise SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, Webfinger::Error => e
raise SignatureVerificationError, e.message
end
end

@ -4,7 +4,7 @@ class FollowerAccountsController < ApplicationController
include AccountControllerConcern
include SignatureVerification
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
skip_around_action :set_locale, if: -> { request.format == :json }

@ -4,7 +4,7 @@ class FollowingAccountsController < ApplicationController
include AccountControllerConcern
include SignatureVerification
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
skip_around_action :set_locale, if: -> { request.format == :json }

@ -0,0 +1,22 @@
# frozen_string_literal: true
class PrivacyController < ApplicationController
layout 'public'
before_action :set_instance_presenter
before_action :set_expires_in
skip_before_action :require_functional!
def show; end
private
def set_instance_presenter
@instance_presenter = InstancePresenter.new
end
def set_expires_in
expires_in 0, public: true
end
end

@ -8,7 +8,7 @@ class StatusesController < ApplicationController
layout 'public'
before_action :require_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_status
before_action :set_instance_presenter
before_action :set_link_headers

@ -8,7 +8,7 @@ class TagsController < ApplicationController
layout 'public'
before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :authenticate_user!, if: :whitelist_mode?
before_action :set_local
before_action :set_tag

@ -51,6 +51,7 @@ const messages = defineMessages({
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
add_account_note: { id: 'account.add_account_note', defaultMessage: 'Add note for @{name}' },
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
});
const dateFormatOptions = {
@ -85,6 +86,7 @@ class Header extends ImmutablePureComponent {
onEndorseToggle: PropTypes.func.isRequired,
onAddToList: PropTypes.func.isRequired,
onEditAccountNote: PropTypes.func.isRequired,
onChangeLanguages: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
domain: PropTypes.string.isRequired,
hidden: PropTypes.bool,
@ -215,6 +217,9 @@ class Header extends ImmutablePureComponent {
} else {
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
}
menu.push({ text: intl.formatMessage(messages.languages), action: this.props.onChangeLanguages });
menu.push(null);
}
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });

@ -23,6 +23,7 @@ export default class Header extends ImmutablePureComponent {
onUnblockDomain: PropTypes.func.isRequired,
onEndorseToggle: PropTypes.func.isRequired,
onAddToList: PropTypes.func.isRequired,
onChangeLanguages: PropTypes.func.isRequired,
hideTabs: PropTypes.bool,
domain: PropTypes.string.isRequired,
hidden: PropTypes.bool,
@ -92,6 +93,10 @@ export default class Header extends ImmutablePureComponent {
this.props.onEditAccountNote(this.props.account);
}
handleChangeLanguages = () => {
this.props.onChangeLanguages(this.props.account);
}
render () {
const { account, hidden, hideTabs } = this.props;
@ -118,6 +123,7 @@ export default class Header extends ImmutablePureComponent {
onEndorseToggle={this.handleEndorseToggle}
onAddToList={this.handleAddToList}
onEditAccountNote={this.handleEditAccountNote}
onChangeLanguages={this.handleChangeLanguages}
domain={this.props.domain}
hidden={hidden}
/>

@ -130,12 +130,18 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(unblockDomain(domain));
},
onAddToList(account){
onAddToList (account) {
dispatch(openModal('LIST_ADDER', {
accountId: account.get('id'),
}));
},
onChangeLanguages (account) {
dispatch(openModal('SUBSCRIBED_LANGUAGES', {
accountId: account.get('id'),
}));
},
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));

@ -7,6 +7,7 @@ import DisplayName from 'flavours/glitch/components/display_name';
import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
import Option from './option';
import MediaAttachments from 'flavours/glitch/components/media_attachments';
import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
export default class StatusCheckBox extends React.PureComponent {
@ -36,7 +37,7 @@ export default class StatusCheckBox extends React.PureComponent {
<Avatar account={status.get('account')} size={46} />
</div>
<div><DisplayName account={status.get('account')} /> · <RelativeTimestamp timestamp={status.get('created_at')} /></div>
<div><DisplayName account={status.get('account')} /> · <VisibilityIcon visibility={status.get('visibility')} /><RelativeTimestamp timestamp={status.get('created_at')} /></div>
</div>
<StatusContent status={status} media={<MediaAttachments status={status} revealed={false} />} />

@ -0,0 +1,125 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { is, List as ImmutableList, Set as ImmutableSet } from 'immutable';
import { languages as preloadedLanguages } from 'flavours/glitch/util/initial_state';
import Option from 'flavours/glitch/features/report/components/option';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import IconButton from 'flavours/glitch/components/icon_button';
import Button from 'flavours/glitch/components/button';
import { followAccount } from 'flavours/glitch/actions/accounts';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
});
const getAccountLanguages = createSelector([
(state, accountId) => state.getIn(['timelines', `account:${accountId}`, 'items'], ImmutableList()),
state => state.get('statuses'),
], (statusIds, statuses) =>
new ImmutableSet(statusIds.map(statusId => statuses.get(statusId)).filter(status => !status.get('reblog')).map(status => status.get('language'))));
const mapStateToProps = (state, { accountId }) => ({
acct: state.getIn(['accounts', accountId, 'acct']),
availableLanguages: getAccountLanguages(state, accountId),
selectedLanguages: ImmutableSet(state.getIn(['relationships', accountId, 'languages']) || ImmutableList()),
});
const mapDispatchToProps = (dispatch, { accountId }) => ({
onSubmit (languages) {
dispatch(followAccount(accountId, { languages }));
},
});
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class SubscribedLanguagesModal extends ImmutablePureComponent {
static propTypes = {
accountId: PropTypes.string.isRequired,
acct: PropTypes.string.isRequired,
availableLanguages: ImmutablePropTypes.setOf(PropTypes.string),
selectedLanguages: ImmutablePropTypes.setOf(PropTypes.string),
onClose: PropTypes.func.isRequired,
languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
intl: PropTypes.object.isRequired,
submit: PropTypes.func.isRequired,
};
static defaultProps = {
languages: preloadedLanguages,
};
state = {
selectedLanguages: this.props.selectedLanguages,
};
handleLanguageToggle = (value, checked) => {
const { selectedLanguages } = this.state;
if (checked) {
this.setState({ selectedLanguages: selectedLanguages.add(value) });
} else {
this.setState({ selectedLanguages: selectedLanguages.delete(value) });
}
};
handleSubmit = () => {
this.props.onSubmit(this.state.selectedLanguages.toArray());
this.props.onClose();
}
renderItem (value) {
const language = this.props.languages.find(language => language[0] === value);
const checked = this.state.selectedLanguages.includes(value);
if (!language) {
return null;
}
return (
<Option
key={value}
name='languages'
value={value}
label={language[1]}
checked={checked}
onToggle={this.handleLanguageToggle}
multiple
/>
);
}
render () {
const { acct, availableLanguages, selectedLanguages, intl, onClose } = this.props;
return (
<div className='modal-root__modal report-dialog-modal'>
<div className='report-modal__target'>
<IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
<FormattedMessage id='subscribed_languages.target' defaultMessage='Change subscribed languages for {target}' values={{ target: <strong>{acct}</strong> }} />
</div>
<div className='report-dialog-modal__container'>
<p className='report-dialog-modal__lead'><FormattedMessage id='subscribed_languages.lead' defaultMessage='Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.' /></p>
<div>
{availableLanguages.union(selectedLanguages).delete(null).map(value => this.renderItem(value))}
</div>
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button disabled={is(this.state.selectedLanguages, this.props.selectedLanguages)} onClick={this.handleSubmit}><FormattedMessage id='subscribed_languages.save' defaultMessage='Save changes' /></Button>
</div>
</div>
</div>
);
}
}

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { limitedFederationMode, version, repository, source_url } from 'flavours/glitch/util/initial_state';
import { signOutLink, securityLink } from 'flavours/glitch/util/backend_links';
import { signOutLink, securityLink, privacyPolicyLink } from 'flavours/glitch/util/backend_links';
import { logOut } from 'flavours/glitch/util/log_out';
import { openModal } from 'flavours/glitch/actions/modal';
import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions';
@ -55,7 +55,7 @@ class LinkFooter extends React.PureComponent {
{!!securityLink && <li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>}
{!limitedFederationMode && <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>}
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href={privacyPolicyLink} target='_blank'><FormattedMessage id='getting_started.privacy_policy' defaultMessage='Privacy Policy' /></a> · </li>
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
<li><a href={signOutLink} onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>

@ -13,6 +13,7 @@ import FavouriteModal from './favourite_modal';
import AudioModal from './audio_modal';
import DoodleModal from './doodle_modal';
import ConfirmationModal from './confirmation_modal';
import SubscribedLanguagesModal from 'flavours/glitch/features/subscribed_languages_modal';
import FocalPointModal from './focal_point_modal';
import DeprecatedSettingsModal from './deprecated_settings_modal';
import {
@ -51,6 +52,7 @@ const MODAL_COMPONENTS = {
'PINNED_ACCOUNTS_EDITOR': PinnedAccountsEditor,
'COMPARE_HISTORY': CompareHistoryModal,
'FILTER': FilterModal,
'SUBSCRIBED_LANGUAGES': () => Promise.resolve({ default: SubscribedLanguagesModal }),
};
export default class ModalRoot extends React.PureComponent {

@ -1,7 +1,7 @@
export const preferencesLink = '/settings/preferences';
export const profileLink = '/settings/profile';
export const signOutLink = '/auth/sign_out';
export const termsLink = '/terms';
export const privacyPolicyLink = '/privacy-policy';
export const accountAdminLink = (id) => `/admin/accounts/${id}`;
export const statusAdminLink = (account_id, status_id) => `/admin/accounts/${account_id}/statuses?id=${status_id}`;
export const filterEditLink = (id) => `/filters/${id}/edit`;

@ -536,10 +536,12 @@ export function expandFollowingFail(id, error) {
export function fetchRelationships(accountIds) {
return (dispatch, getState) => {
const loadedRelationships = getState().get('relationships');
const state = getState();
const loadedRelationships = state.get('relationships');
const newAccountIds = accountIds.filter(id => loadedRelationships.get(id, null) === null);
const signedIn = !!state.getIn(['meta', 'me']);
if (newAccountIds.length === 0) {
if (!signedIn || newAccountIds.length === 0) {
return;
}

@ -1,6 +1,7 @@
import api from '../api';
import { debounce } from 'lodash';
import compareId from '../compare_id';
import { List as ImmutableList } from 'immutable';
export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST';
export const MARKERS_FETCH_SUCCESS = 'MARKERS_FETCH_SUCCESS';
@ -11,7 +12,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
const accessToken = getState().getIn(['meta', 'access_token'], '');
const params = _buildParams(getState());
if (Object.keys(params).length === 0) {
if (Object.keys(params).length === 0 || accessToken === '') {
return;
}
@ -63,7 +64,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
const _buildParams = (state) => {
const params = {};
const lastHomeId = state.getIn(['timelines', 'home', 'items']).find(item => item !== null);
const lastHomeId = state.getIn(['timelines', 'home', 'items'], ImmutableList()).find(item => item !== null);
const lastNotificationId = state.getIn(['notifications', 'lastReadId']);
if (lastHomeId && compareId(lastHomeId, state.getIn(['markers', 'home'])) > 0) {
@ -82,9 +83,10 @@ const _buildParams = (state) => {
};
const debouncedSubmitMarkers = debounce((dispatch, getState) => {
const params = _buildParams(getState());
const accessToken = getState().getIn(['meta', 'access_token'], '');
const params = _buildParams(getState());
if (Object.keys(params).length === 0) {
if (Object.keys(params).length === 0 || accessToken === '') {
return;
}

@ -34,6 +34,11 @@ export const STATUS_FETCH_SOURCE_REQUEST = 'STATUS_FETCH_SOURCE_REQUEST';
export const STATUS_FETCH_SOURCE_SUCCESS = 'STATUS_FETCH_SOURCE_SUCCESS';
export const STATUS_FETCH_SOURCE_FAIL = 'STATUS_FETCH_SOURCE_FAIL';
export const STATUS_TRANSLATE_REQUEST = 'STATUS_TRANSLATE_REQUEST';
export const STATUS_TRANSLATE_SUCCESS = 'STATUS_TRANSLATE_SUCCESS';
export const STATUS_TRANSLATE_FAIL = 'STATUS_TRANSLATE_FAIL';
export const STATUS_TRANSLATE_UNDO = 'STATUS_TRANSLATE_UNDO';
export function fetchStatusRequest(id, skipLoading) {
return {
type: STATUS_FETCH_REQUEST,
@ -309,4 +314,36 @@ export function toggleStatusCollapse(id, isCollapsed) {
id,
isCollapsed,
};
}
};
export const translateStatus = id => (dispatch, getState) => {
dispatch(translateStatusRequest(id));
api(getState).post(`/api/v1/statuses/${id}/translate`).then(response => {
dispatch(translateStatusSuccess(id, response.data));
}).catch(error => {
dispatch(translateStatusFail(id, error));
});
};
export const translateStatusRequest = id => ({
type: STATUS_TRANSLATE_REQUEST,
id,
});
export const translateStatusSuccess = (id, translation) => ({
type: STATUS_TRANSLATE_SUCCESS,
id,
translation,
});
export const translateStatusFail = (id, error) => ({
type: STATUS_TRANSLATE_FAIL,
id,
error,
});
export const undoStatusTranslation = id => ({
type: STATUS_TRANSLATE_UNDO,
id,
});

@ -6,7 +6,7 @@ exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
onMouseEnter={[Function]}
onMouseLeave={[Function]}
style={
Object {
{
"backgroundImage": "url(/animated/alice.gif)",
"backgroundSize": "100px 100px",
"height": "100px",
@ -22,7 +22,7 @@ exports[`<Avatar /> Still renders a still avatar 1`] = `
onMouseEnter={[Function]}
onMouseLeave={[Function]}
style={
Object {
{
"backgroundImage": "url(/static/alice.jpg)",
"backgroundSize": "100px 100px",
"height": "100px",

@ -7,7 +7,7 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
<div
className="account__avatar-overlay-base"
style={
Object {
{
"backgroundImage": "url(/static/alice.jpg)",
}
}
@ -15,7 +15,7 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
<div
className="account__avatar-overlay-overlay"
style={
Object {
{
"backgroundImage": "url(/static/eve.jpg)",
}
}

@ -10,7 +10,7 @@ exports[`<DisplayName /> renders display name + account name 1`] = `
<strong
className="display-name__html"
dangerouslySetInnerHTML={
Object {
{
"__html": "<p>Foo</p>",
}
}

@ -17,6 +17,7 @@ class ColumnHeader extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
identity: PropTypes.object,
};
static propTypes = {
@ -145,7 +146,7 @@ class ColumnHeader extends React.PureComponent {
collapsedContent.push(moveButtons);
}
if (children || (multiColumn && this.props.onPin)) {
if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
collapseButton = (
<button
className={collapsibleButtonClassName}

@ -1,7 +1,7 @@
// @ts-check
import React from 'react';
import { Sparklines, SparklinesCurve } from 'react-sparklines';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Permalink from './permalink';
@ -9,10 +9,6 @@ import ShortNumber from 'mastodon/components/short_number';
import Skeleton from 'mastodon/components/skeleton';
import classNames from 'classnames';
const messages = defineMessages({
totalVolume: { id: 'hashtag.total_volume', defaultMessage: 'Total volume in the last {days, plural, one {day} other {{days} days}}' },
});
class SilentErrorBoundary extends React.Component {
static propTypes = {
@ -69,7 +65,7 @@ ImmutableHashtag.propTypes = {
hashtag: ImmutablePropTypes.map.isRequired,
};
const Hashtag = injectIntl(({ name, href, to, people, uses, history, className, intl }) => (
const Hashtag = ({ name, href, to, people, history, className }) => (
<div className={classNames('trends__item', className)}>
<div className='trends__item__name'>
<Permalink href={href} to={to}>
@ -79,11 +75,6 @@ const Hashtag = injectIntl(({ name, href, to, people, uses, history, className,
{typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}
</div>
<abbr className='trends__item__current' title={intl.formatMessage(messages.totalVolume, { days: 2 })}>
{typeof uses !== 'undefined' ? <ShortNumber value={uses} /> : <Skeleton width={42} height={36} />}
<span className='trends__item__current__asterisk'>*</span>
</abbr>
<div className='trends__item__sparkline'>
<SilentErrorBoundary>
<Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}>
@ -92,7 +83,7 @@ const Hashtag = injectIntl(({ name, href, to, people, uses, history, className,
</SilentErrorBoundary>
</div>
</div>
));
);
Hashtag.propTypes = {
name: PropTypes.string,

@ -1,8 +1,8 @@
import React from 'react';
const Logo = () => (
<svg viewBox='0 0 216.4144 232.00976' className='logo'>
<use xlinkHref='#mastodon-svg-logo' />
<svg viewBox='0 0 261 66' className='logo'>
<use xlinkHref='#logo-symbol-wordmark' />
</svg>
);

@ -34,6 +34,10 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
export default @injectIntl
class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
poll: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
@ -217,7 +221,7 @@ class Poll extends ImmutablePureComponent {
</ul>
<div className='poll__footer'>
{!showResults && <button className='button button-secondary' disabled={disabled} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{showResults && !this.props.disabled && <span><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </span>}
{votesCount}
{poll.get('expires_at') && <span> · {timeRemaining}</span>}

@ -85,6 +85,7 @@ class Status extends ImmutablePureComponent {
onHeightChange: PropTypes.func,
onToggleHidden: PropTypes.func,
onToggleCollapsed: PropTypes.func,
onTranslate: PropTypes.func,
muted: PropTypes.bool,
hidden: PropTypes.bool,
unread: PropTypes.bool,
@ -171,6 +172,10 @@ class Status extends ImmutablePureComponent {
this.props.onToggleCollapsed(this._properStatus(), isCollapsed);
}
handleTranslate = () => {
this.props.onTranslate(this._properStatus());
}
renderLoadingMediaGallery () {
return <div className='media-gallery' style={{ height: '110px' }} />;
}
@ -512,7 +517,16 @@ class Status extends ImmutablePureComponent {
</a>
</div>
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} showThread={showThread} onExpandedToggle={this.handleExpandedToggle} collapsable onCollapsedToggle={this.handleCollapsedToggle} />
<StatusContent
status={status}
onClick={this.handleClick}
expanded={!status.get('hidden')}
showThread={showThread}
onExpandedToggle={this.handleExpandedToggle}
onTranslate={this.handleTranslate}
collapsable
onCollapsedToggle={this.handleCollapsedToggle}
/>
{media}

@ -1,19 +1,21 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { FormattedMessage, injectIntl } from 'react-intl';
import Permalink from './permalink';
import classnames from 'classnames';
import PollContainer from 'mastodon/containers/poll_container';
import Icon from 'mastodon/components/icon';
import { autoPlayGif } from 'mastodon/initial_state';
import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top)
export default class StatusContent extends React.PureComponent {
export default @injectIntl
class StatusContent extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
identity: PropTypes.object,
};
static propTypes = {
@ -21,9 +23,11 @@ export default class StatusContent extends React.PureComponent {
expanded: PropTypes.bool,
showThread: PropTypes.bool,
onExpandedToggle: PropTypes.func,
onTranslate: PropTypes.func,
onClick: PropTypes.func,
collapsable: PropTypes.bool,
onCollapsedToggle: PropTypes.func,
intl: PropTypes.object,
};
state = {
@ -163,20 +167,27 @@ export default class StatusContent extends React.PureComponent {
}
}
handleTranslate = () => {
this.props.onTranslate();
}
setRef = (c) => {
this.node = c;
}
render () {
const { status } = this.props;
const { status, intl } = this.props;
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const renderReadMore = this.props.onClick && status.get('collapsed');
const renderViewThread = this.props.showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
const renderTranslate = this.context.identity.signedIn && this.props.onTranslate && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && intl.locale !== status.get('language');
const language = preloadedLanguages.find(lang => lang[0] === status.get('language'));
const languageName = language ? language[2] : status.get('language');
const content = { __html: status.get('contentHtml') };
const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') };
const lang = status.get('language');
const lang = status.get('translation') ? intl.locale : status.get('language');
const classNames = classnames('status__content', {
'status__content--with-action': this.props.onClick && this.context.router,
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
@ -195,6 +206,12 @@ export default class StatusContent extends React.PureComponent {
</button>
);
const translateButton = (
<button className='status__content__read-more-button' onClick={this.handleTranslate}>
{status.get('translation') ? <span><FormattedMessage id='status.translated_from' defaultMessage='Translated from {lang}' values={{ lang: languageName }} /> · <FormattedMessage id='status.show_original' defaultMessage='Show original' /></span> : <FormattedMessage id='status.translate' defaultMessage='Translate' />}
</button>
);
if (status.get('spoiler_text').length > 0) {
let mentionsPlaceholder = '';
@ -223,7 +240,7 @@ export default class StatusContent extends React.PureComponent {
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''} translate`} lang={lang} dangerouslySetInnerHTML={content} />
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{!hidden && renderTranslate && translateButton}
{renderViewThread && showThreadButton}
</div>
);
@ -233,7 +250,7 @@ export default class StatusContent extends React.PureComponent {
<div className='status__content__text status__content__text--visible translate' lang={lang} dangerouslySetInnerHTML={content} />
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderTranslate && translateButton}
{renderViewThread && showThreadButton}
</div>,
];
@ -249,7 +266,7 @@ export default class StatusContent extends React.PureComponent {
<div className='status__content__text status__content__text--visible translate' lang={lang} dangerouslySetInnerHTML={content} />
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderTranslate && translateButton}
{renderViewThread && showThreadButton}
</div>
);

@ -26,7 +26,7 @@ const createIdentityContext = state => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
accessToken: state.meta.access_token,
permissions: state.role.permissions,
permissions: state.role ? state.role.permissions : 0,
});
export default class Mastodon extends React.PureComponent {

@ -25,6 +25,8 @@ import {
revealStatus,
toggleStatusCollapse,
editStatus,
translateStatus,
undoStatusTranslation,
} from '../actions/statuses';
import {
unmuteAccount,
@ -150,6 +152,14 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
dispatch(editStatus(status.get('id'), history));
},
onTranslate (status) {
if (status.get('translation')) {
dispatch(undoStatusTranslation(status.get('id')));
} else {
dispatch(translateStatus(status.get('id')));
}
},
onDirect (account, router) {
dispatch(directCompose(account, router));
},

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Button from 'mastodon/components/button';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { autoPlayGif, me } from 'mastodon/initial_state';
import { autoPlayGif, me, title, domain } from 'mastodon/initial_state';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';
import IconButton from 'mastodon/components/icon_button';
@ -15,6 +15,7 @@ import { NavLink } from 'react-router-dom';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import AccountNoteContainer from '../containers/account_note_container';
import { PERMISSION_MANAGE_USERS } from 'mastodon/permissions';
import { Helmet } from 'react-helmet';
const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@ -51,8 +52,17 @@ const messages = defineMessages({
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
});
const titleFromAccount = account => {
const displayName = account.get('display_name');
const acct = account.get('acct') === account.get('username') ? `${account.get('username')}@${domain}` : account.get('acct');
const prefix = displayName.trim().length === 0 ? account.get('username') : displayName;
return `${prefix} (@${acct})`;
};
const dateFormatOptions = {
month: 'short',
day: 'numeric',
@ -85,6 +95,7 @@ class Header extends ImmutablePureComponent {
onEndorseToggle: PropTypes.func.isRequired,
onAddToList: PropTypes.func.isRequired,
onEditAccountNote: PropTypes.func.isRequired,
onChangeLanguages: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
domain: PropTypes.string.isRequired,
hidden: PropTypes.bool,
@ -130,6 +141,7 @@ class Header extends ImmutablePureComponent {
render () {
const { account, hidden, intl, domain } = this.props;
const { signedIn } = this.context.identity;
if (!account) {
return null;
@ -160,12 +172,12 @@ class Header extends ImmutablePureComponent {
}
if (me !== account.get('id')) {
if (!account.get('relationship')) { // Wait until the relationship is loaded
if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded
actionBtn = '';
} else if (account.getIn(['relationship', 'requested'])) {
actionBtn = <Button className={classNames('logo-button', { 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
} else if (!account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']), 'button--with-bell': bellBtn !== '' })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={signedIn ? this.props.onFollow : undefined} />;
} else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
}
@ -181,7 +193,7 @@ class Header extends ImmutablePureComponent {
lockedIcon = <Icon id='lock' title={intl.formatMessage(messages.account_locked)} />;
}
if (account.get('id') !== me) {
if (signedIn && account.get('id') !== me) {
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
menu.push(null);
@ -204,7 +216,7 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
} else {
} else if (signedIn) {
if (account.getIn(['relationship', 'following'])) {
if (!account.getIn(['relationship', 'muting'])) {
if (account.getIn(['relationship', 'showing_reblogs'])) {
@ -212,6 +224,9 @@ class Header extends ImmutablePureComponent {
} else {
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
}
menu.push({ text: intl.formatMessage(messages.languages), action: this.props.onChangeLanguages });
menu.push(null);
}
menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
@ -234,7 +249,7 @@ class Header extends ImmutablePureComponent {
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
}
if (account.get('acct') !== account.get('username')) {
if (signedIn && account.get('acct') !== account.get('username')) {
const domain = account.get('acct').split('@')[1];
menu.push(null);
@ -293,7 +308,7 @@ class Header extends ImmutablePureComponent {
</React.Fragment>
)}
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' size={24} direction='right' />
</div>
)}
</div>
@ -322,7 +337,7 @@ class Header extends ImmutablePureComponent {
</div>
)}
{account.get('id') !== me && <AccountNoteContainer account={account} />}
{(account.get('id') !== me && signedIn) && <AccountNoteContainer account={account} />}
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
@ -354,6 +369,10 @@ class Header extends ImmutablePureComponent {
</div>
)}
</div>
<Helmet>
<title>{titleFromAccount(account)} - {title}</title>
</Helmet>
</div>
);
}

@ -22,6 +22,7 @@ export default class Header extends ImmutablePureComponent {
onUnblockDomain: PropTypes.func.isRequired,
onEndorseToggle: PropTypes.func.isRequired,
onAddToList: PropTypes.func.isRequired,
onChangeLanguages: PropTypes.func.isRequired,
hideTabs: PropTypes.bool,
domain: PropTypes.string.isRequired,
hidden: PropTypes.bool,
@ -91,6 +92,10 @@ export default class Header extends ImmutablePureComponent {
this.props.onEditAccountNote(this.props.account);
}
handleChangeLanguages = () => {
this.props.onChangeLanguages(this.props.account);
}
render () {
const { account, hidden, hideTabs } = this.props;
@ -117,6 +122,7 @@ export default class Header extends ImmutablePureComponent {
onEndorseToggle={this.handleEndorseToggle}
onAddToList={this.handleAddToList}
onEditAccountNote={this.handleEditAccountNote}
onChangeLanguages={this.handleChangeLanguages}
domain={this.props.domain}
hidden={hidden}
/>

@ -121,12 +121,18 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(unblockDomain(domain));
},
onAddToList(account){
onAddToList (account) {
dispatch(openModal('LIST_ADDER', {
accountId: account.get('id'),
}));
},
onChangeLanguages (account) {
dispatch(openModal('SUBSCRIBED_LANGUAGES', {
accountId: account.get('id'),
}));
},
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));

@ -9,6 +9,8 @@ import { expandCommunityTimeline } from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container';
import { connectCommunityStream } from '../../actions/streaming';
import { Helmet } from 'react-helmet';
import { title } from 'mastodon/initial_state';
const messages = defineMessages({
title: { id: 'column.community', defaultMessage: 'Local timeline' },
@ -128,6 +130,10 @@ class CommunityTimeline extends React.PureComponent {
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
bindToDocument={!multiColumn}
/>
<Helmet>
<title>{intl.formatMessage(messages.title)} - {title}</title>
</Helmet>
</Column>
);
}

@ -13,6 +13,8 @@ import RadioButton from 'mastodon/components/radio_button';
import LoadMore from 'mastodon/components/load_more';
import ScrollContainer from 'mastodon/containers/scroll_container';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { title } from 'mastodon/initial_state';
import { Helmet } from 'react-helmet';
const messages = defineMessages({
title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
@ -165,6 +167,10 @@ class Directory extends React.PureComponent {
/>
{multiColumn && !pinned ? <ScrollContainer scrollKey='directory'>{scrollableArea}</ScrollContainer> : scrollableArea}
<Helmet>
<title>{intl.formatMessage(messages.title)} - {title}</title>
</Helmet>
</Column>
);
}

@ -11,6 +11,8 @@ import Statuses from './statuses';
import Suggestions from './suggestions';
import Search from 'mastodon/features/compose/containers/search_container';
import SearchResults from './results';
import { Helmet } from 'react-helmet';
import { title } from 'mastodon/initial_state';
const messages = defineMessages({
title: { id: 'explore.title', defaultMessage: 'Explore' },
@ -81,6 +83,10 @@ class Explore extends React.PureComponent {
<Route path='/explore/suggestions' component={Suggestions} />
<Route exact path={['/explore', '/explore/posts', '/search']} component={Statuses} componentParams={{ multiColumn }} />
</Switch>
<Helmet>
<title>{intl.formatMessage(messages.title)} - {title}</title>
</Helmet>
</React.Fragment>
)}
</div>

@ -5,6 +5,7 @@ import Story from './components/story';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { connect } from 'react-redux';
import { fetchTrendingLinks } from 'mastodon/actions/trends';
import { FormattedMessage } from 'react-intl';
const mapStateToProps = state => ({
links: state.getIn(['trends', 'links', 'items']),
@ -28,6 +29,16 @@ class Links extends React.PureComponent {
render () {
const { isLoading, links } = this.props;
if (!isLoading && links.isEmpty()) {
return (
<div className='explore__links scrollable scrollable--flex'>
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />
</div>
</div>
);
}
return (
<div className='explore__links'>
{isLoading ? (<LoadingIndicator />) : links.map(link => (

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { expandSearch } from 'mastodon/actions/search';
import Account from 'mastodon/containers/account_container';
@ -10,10 +10,17 @@ import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
import { List as ImmutableList } from 'immutable';
import LoadMore from 'mastodon/components/load_more';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { title } from 'mastodon/initial_state';
import { Helmet } from 'react-helmet';
const messages = defineMessages({
title: { id: 'search_results.title', defaultMessage: 'Search for {q}' },
});
const mapStateToProps = state => ({
isLoading: state.getIn(['search', 'isLoading']),
results: state.getIn(['search', 'results']),
q: state.getIn(['search', 'searchTerm']),
});
const appendLoadMore = (id, list, onLoadMore) => {
@ -37,6 +44,7 @@ const renderStatuses = (results, onLoadMore) => appendLoadMore('statuses', resul
)), onLoadMore);
export default @connect(mapStateToProps)
@injectIntl
class Results extends React.PureComponent {
static propTypes = {
@ -44,6 +52,8 @@ class Results extends React.PureComponent {
isLoading: PropTypes.bool,
multiColumn: PropTypes.bool,
dispatch: PropTypes.func.isRequired,
q: PropTypes.string,
intl: PropTypes.object,
};
state = {
@ -64,7 +74,7 @@ class Results extends React.PureComponent {
}
render () {
const { isLoading, results } = this.props;
const { intl, isLoading, q, results } = this.props;
const { type } = this.state;
let filteredResults = ImmutableList();
@ -106,6 +116,10 @@ class Results extends React.PureComponent {
<div className='explore__search-results'>
{isLoading ? <LoadingIndicator /> : filteredResults}
</div>
<Helmet>
<title>{intl.formatMessage(messages.title, { q })} - {title}</title>
</Helmet>
</React.Fragment>
);
}

@ -5,6 +5,7 @@ import AccountCard from 'mastodon/features/directory/components/account_card';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { connect } from 'react-redux';
import { fetchSuggestions } from 'mastodon/actions/suggestions';
import { FormattedMessage } from 'react-intl';
const mapStateToProps = state => ({
suggestions: state.getIn(['suggestions', 'items']),
@ -28,6 +29,16 @@ class Suggestions extends React.PureComponent {
render () {
const { isLoading, suggestions } = this.props;
if (!isLoading && suggestions.isEmpty()) {
return (
<div className='explore__suggestions scrollable scrollable--flex'>
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />
</div>
</div>
);
}
return (
<div className='explore__suggestions'>
{isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => (

@ -5,6 +5,7 @@ import { ImmutableHashtag as Hashtag } from 'mastodon/components/hashtag';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import { connect } from 'react-redux';
import { fetchTrendingHashtags } from 'mastodon/actions/trends';
import { FormattedMessage } from 'react-intl';
const mapStateToProps = state => ({
hashtags: state.getIn(['trends', 'tags', 'items']),
@ -28,6 +29,16 @@ class Tags extends React.PureComponent {
render () {
const { isLoading, hashtags } = this.props;
if (!isLoading && hashtags.isEmpty()) {
return (
<div className='explore__links scrollable scrollable--flex'>
<div className='empty-column-indicator'>
<FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />
</div>
</div>
);
}
return (
<div className='explore__links'>
{isLoading ? (<LoadingIndicator />) : hashtags.map(hashtag => (

@ -10,7 +10,6 @@ import { requestBrowserPermission } from 'mastodon/actions/notifications';
import { markAsPartial } from 'mastodon/actions/timelines';
import Column from 'mastodon/features/ui/components/column';
import Account from './components/account';
import Logo from 'mastodon/components/logo';
import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg';
import Button from 'mastodon/components/button';
@ -78,7 +77,10 @@ class FollowRecommendations extends ImmutablePureComponent {
<Column>
<div className='scrollable follow-recommendations-container'>
<div className='column-title'>
<Logo />
<svg viewBox='0 0 79 79' className='logo'>
<use xlinkHref='#logo-symbol-icon' />
</svg>
<h3><FormattedMessage id='follow_recommendations.heading' defaultMessage="Follow people you'd like to see posts from! Here are some suggestions." /></h3>
<p><FormattedMessage id='follow_recommendations.lead' defaultMessage="Posts from people you follow will show up in chronological order on your home feed. Don't be afraid to make mistakes, you can unfollow people just as easily any time!" /></p>
</div>

@ -14,6 +14,8 @@ import { isEqual } from 'lodash';
import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/tags';
import Icon from 'mastodon/components/icon';
import classNames from 'classnames';
import { title } from 'mastodon/initial_state';
import { Helmet } from 'react-helmet';
const messages = defineMessages({
followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' },
@ -31,6 +33,10 @@ class HashtagTimeline extends React.PureComponent {
disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
params: PropTypes.object.isRequired,
columnId: PropTypes.string,
@ -158,6 +164,11 @@ class HashtagTimeline extends React.PureComponent {
handleFollow = () => {
const { dispatch, params, tag } = this.props;
const { id } = params;
const { signedIn } = this.context.identity;
if (!signedIn) {
return;
}
if (tag.get('following')) {
dispatch(unfollowHashtag(id));
@ -170,6 +181,7 @@ class HashtagTimeline extends React.PureComponent {
const { hasUnread, columnId, multiColumn, tag, intl } = this.props;
const { id, local } = this.props.params;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
let followButton;
@ -177,7 +189,7 @@ class HashtagTimeline extends React.PureComponent {
const following = tag.get('following');
followButton = (
<button className={classNames('column-header__button')} onClick={this.handleFollow} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-pressed={following ? 'true' : 'false'}>
<button className={classNames('column-header__button')} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-pressed={following ? 'true' : 'false'}>
<Icon id={following ? 'user-times' : 'user-plus'} fixedWidth className='column-header__icon' />
</button>
);
@ -208,6 +220,10 @@ class HashtagTimeline extends React.PureComponent {
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
bindToDocument={!multiColumn}
/>
<Helmet>
<title>{`#${id}`} - {title}</title>
</Helmet>
</Column>
);
}

@ -9,6 +9,8 @@ import { expandPublicTimeline } from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container';
import { connectPublicStream } from '../../actions/streaming';
import { Helmet } from 'react-helmet';
import { title } from 'mastodon/initial_state';
const messages = defineMessages({
title: { id: 'column.public', defaultMessage: 'Federated timeline' },
@ -131,6 +133,10 @@ class PublicTimeline extends React.PureComponent {
emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
bindToDocument={!multiColumn}
/>
<Helmet>
<title>{intl.formatMessage(messages.title)} - {title}</title>
</Helmet>
</Column>
);
}

@ -7,14 +7,25 @@ import DisplayName from 'mastodon/components/display_name';
import RelativeTimestamp from 'mastodon/components/relative_timestamp';
import Option from './option';
import MediaAttachments from 'mastodon/components/media_attachments';
import { injectIntl, defineMessages } from 'react-intl';
import Icon from 'mastodon/components/icon';
export default class StatusCheckBox extends React.PureComponent {
const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
});
export default @injectIntl
class StatusCheckBox extends React.PureComponent {
static propTypes = {
id: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired,
checked: PropTypes.bool,
onToggle: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleStatusesToggle = (value, checked) => {
@ -23,12 +34,21 @@ export default class StatusCheckBox extends React.PureComponent {
};
render () {
const { status, checked } = this.props;
const { status, checked, intl } = this.props;
if (status.get('reblog')) {
return null;
}
const visibilityIconInfo = {
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
};
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
const labelComponent = (
<div className='status-check-box__status poll__option__text'>
<div className='detailed-status__display-name'>
@ -37,7 +57,7 @@ export default class StatusCheckBox extends React.PureComponent {
</div>
<div>
<DisplayName account={status.get('account')} /> · <RelativeTimestamp timestamp={status.get('created_at')} />
<DisplayName account={status.get('account')} /> · <span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span> <RelativeTimestamp timestamp={status.get('created_at')} />
</div>
</div>

@ -37,6 +37,7 @@ class DetailedStatus extends ImmutablePureComponent {
onOpenMedia: PropTypes.func.isRequired,
onOpenVideo: PropTypes.func.isRequired,
onToggleHidden: PropTypes.func.isRequired,
onTranslate: PropTypes.func.isRequired,
measureHeight: PropTypes.bool,
onHeightChange: PropTypes.func,
domain: PropTypes.string.isRequired,
@ -103,6 +104,11 @@ class DetailedStatus extends ImmutablePureComponent {
window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
}
handleTranslate = () => {
const { onTranslate, status } = this.props;
onTranslate(status);
}
render () {
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
const outerStyle = { boxSizing: 'border-box' };
@ -260,7 +266,12 @@ class DetailedStatus extends ImmutablePureComponent {
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
</a>
<StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
<StatusContent
status={status}
expanded={!status.get('hidden')}
onExpandedToggle={this.handleExpandedToggle}
onTranslate={this.handleTranslate}
/>
{media}

@ -32,6 +32,8 @@ import {
editStatus,
hideStatus,
revealStatus,
translateStatus,
undoStatusTranslation,
} from '../../actions/statuses';
import {
unblockAccount,
@ -54,10 +56,11 @@ import { openModal } from '../../actions/modal';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import { boostModal, deleteModal } from '../../initial_state';
import { boostModal, deleteModal, title } from '../../initial_state';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
import Icon from 'mastodon/components/icon';
import { Helmet } from 'react-helmet';
const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
@ -154,6 +157,23 @@ const makeMapStateToProps = () => {
return mapStateToProps;
};
const truncate = (str, num) => {
if (str.length > num) {
return str.slice(0, num) + '…';
} else {
return str;
}
};
const titleFromStatus = status => {
const displayName = status.getIn(['account', 'display_name']);
const username = status.getIn(['account', 'username']);
const prefix = displayName.trim().length === 0 ? username : displayName;
const text = status.get('search_index');
return `${prefix}: "${truncate(text, 30)}"`;
};
export default @injectIntl
@connect(makeMapStateToProps)
class Status extends ImmutablePureComponent {
@ -339,6 +359,16 @@ class Status extends ImmutablePureComponent {
}
}
handleTranslate = status => {
const { dispatch } = this.props;
if (status.get('translation')) {
dispatch(undoStatusTranslation(status.get('id')));
} else {
dispatch(translateStatus(status.get('id')));
}
}
handleBlockClick = (status) => {
const { dispatch } = this.props;
const account = status.get('account');
@ -558,6 +588,7 @@ class Status extends ImmutablePureComponent {
onOpenVideo={this.handleOpenVideo}
onOpenMedia={this.handleOpenMedia}
onToggleHidden={this.handleToggleHidden}
onTranslate={this.handleTranslate}
domain={domain}
showMedia={this.state.showMedia}
onToggleMediaVisibility={this.handleToggleMediaVisibility}
@ -592,6 +623,10 @@ class Status extends ImmutablePureComponent {
{descendants}
</div>
</ScrollContainer>
<Helmet>
<title>{titleFromStatus(status)} - {title}</title>
</Helmet>
</Column>
);
}

@ -0,0 +1,121 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { is, List as ImmutableList, Set as ImmutableSet } from 'immutable';
import { languages as preloadedLanguages } from 'mastodon/initial_state';
import Option from 'mastodon/features/report/components/option';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import IconButton from 'mastodon/components/icon_button';
import Button from 'mastodon/components/button';
import { followAccount } from 'mastodon/actions/accounts';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
});
const getAccountLanguages = createSelector([
(state, accountId) => state.getIn(['timelines', `account:${accountId}`, 'items'], ImmutableList()),
state => state.get('statuses'),
], (statusIds, statuses) =>
new ImmutableSet(statusIds.map(statusId => statuses.get(statusId)).filter(status => !status.get('reblog')).map(status => status.get('language'))));
const mapStateToProps = (state, { accountId }) => ({
acct: state.getIn(['accounts', accountId, 'acct']),
availableLanguages: getAccountLanguages(state, accountId),
selectedLanguages: ImmutableSet(state.getIn(['relationships', accountId, 'languages']) || ImmutableList()),
});
const mapDispatchToProps = (dispatch, { accountId }) => ({
onSubmit (languages) {
dispatch(followAccount(accountId, { languages }));
},
});
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class SubscribedLanguagesModal extends ImmutablePureComponent {
static propTypes = {
accountId: PropTypes.string.isRequired,
acct: PropTypes.string.isRequired,
availableLanguages: ImmutablePropTypes.setOf(PropTypes.string),
selectedLanguages: ImmutablePropTypes.setOf(PropTypes.string),
onClose: PropTypes.func.isRequired,
languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
intl: PropTypes.object.isRequired,
submit: PropTypes.func.isRequired,
};
static defaultProps = {
languages: preloadedLanguages,
};
state = {
selectedLanguages: this.props.selectedLanguages,
};
handleLanguageToggle = (value, checked) => {
const { selectedLanguages } = this.state;
if (checked) {
this.setState({ selectedLanguages: selectedLanguages.add(value) });
} else {
this.setState({ selectedLanguages: selectedLanguages.delete(value) });
}
};
handleSubmit = () => {
this.props.onSubmit(this.state.selectedLanguages.toArray());
this.props.onClose();
}
renderItem (value) {
const language = this.props.languages.find(language => language[0] === value);
const checked = this.state.selectedLanguages.includes(value);
return (
<Option
key={value}
name='languages'
value={value}
label={language[1]}
checked={checked}
onToggle={this.handleLanguageToggle}
multiple
/>
);
}
render () {
const { acct, availableLanguages, selectedLanguages, intl, onClose } = this.props;
return (
<div className='modal-root__modal report-dialog-modal'>
<div className='report-modal__target'>
<IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
<FormattedMessage id='subscribed_languages.target' defaultMessage='Change subscribed languages for {target}' values={{ target: <strong>{acct}</strong> }} />
</div>
<div className='report-dialog-modal__container'>
<p className='report-dialog-modal__lead'><FormattedMessage id='subscribed_languages.lead' defaultMessage='Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.' /></p>
<div>
{availableLanguages.union(selectedLanguages).map(value => this.renderItem(value))}
</div>
<div className='flex-spacer' />
<div className='report-dialog-modal__actions'>
<Button disabled={is(this.state.selectedLanguages, this.props.selectedLanguages)} onClick={this.handleSubmit}><FormattedMessage id='subscribed_languages.save' defaultMessage='Save changes' /></Button>
</div>
</div>
</div>
);
}
}

@ -60,6 +60,7 @@ class ColumnsArea extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
identity: PropTypes.object.isRequired,
};
static propTypes = {
@ -212,11 +213,12 @@ class ColumnsArea extends ImmutablePureComponent {
render () {
const { columns, children, singleColumn, isModalOpen, intl } = this.props;
const { shouldAnimate, renderComposePanel } = this.state;
const { signedIn } = this.context.identity;
const columnIndex = getIndex(this.context.router.history.location.pathname);
if (singleColumn) {
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
const floatingActionButton = (!signedIn || shouldHideFAB(this.context.router.history.location.pathname)) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
const content = columnIndex !== -1 ? (
<ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}>

@ -10,6 +10,10 @@ import { changeComposing } from 'mastodon/actions/compose';
export default @connect()
class ComposePanel extends React.PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
dispatch: PropTypes.func.isRequired,
};
@ -23,11 +27,25 @@ class ComposePanel extends React.PureComponent {
}
render() {
const { signedIn } = this.context.identity;
return (
<div className='compose-panel' onFocus={this.onFocus}>
<SearchContainer openInRoute />
<NavigationContainer onClose={this.onBlur} />
<ComposeFormContainer singleColumn />
{!signedIn && (
<React.Fragment>
<div className='flex-spacer' />
</React.Fragment>
)}
{signedIn && (
<React.Fragment>
<NavigationContainer onClose={this.onBlur} />
<ComposeFormContainer singleColumn />
</React.Fragment>
)}
<LinkFooter withHotkeys />
</div>
);

@ -1,41 +0,0 @@
import { PureComponent } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { title } from 'mastodon/initial_state';
const mapStateToProps = state => ({
unread: state.getIn(['missed_updates', 'unread']),
});
export default @connect(mapStateToProps)
class DocumentTitle extends PureComponent {
static propTypes = {
unread: PropTypes.number.isRequired,
};
componentDidMount () {
this._sideEffects();
}
componentDidUpdate() {
this._sideEffects();
}
_sideEffects () {
const { unread } = this.props;
if (unread > 99) {
document.title = `(*) ${title}`;
} else if (unread > 0) {
document.title = `(${unread}) ${title}`;
} else {
document.title = title;
}
}
render () {
return null;
}
}

@ -49,20 +49,46 @@ class LinkFooter extends React.PureComponent {
render () {
const { withHotkeys } = this.props;
const { signedIn, permissions } = this.context.identity;
const items = [];
if ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) {
items.push(<a key='invites' href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a>);
}
if (signedIn && withHotkeys) {
items.push(<Link key='hotkeys' to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link>);
}
if (signedIn) {
items.push(<a key='security' href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a>);
}
if (!limitedFederationMode) {
items.push(<a key='about' href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a>);
}
if (profileDirectory) {
items.push(<Link key='directory' to='/directory'><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></Link>);
}
items.push(<a key='apps' href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a>);
items.push(<a key='privacy-policy' href='/privacy-policy' target='_blank'><FormattedMessage id='getting_started.privacy_policy' defaultMessage='Privacy Policy' /></a>);
if (signedIn) {
items.push(<a key='developers' href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a>);
}
items.push(<a key='docs' href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a>);
if (signedIn) {
items.push(<a key='logout' href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a>);
}
return (
<div className='getting-started__footer'>
<ul>
{((this.context.identity.permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
{withHotkeys && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>
{!limitedFederationMode && <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>}
{profileDirectory && <li><Link to='/directory'><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></Link> · </li>}
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
<li><a href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>
<li>{items.reduce((prev, curr) => [prev, ' · ', curr])}</li>
</ul>
<p>

@ -11,6 +11,7 @@ import VideoModal from './video_modal';
import BoostModal from './boost_modal';
import AudioModal from './audio_modal';
import ConfirmationModal from './confirmation_modal';
import SubscribedLanguagesModal from 'mastodon/features/subscribed_languages_modal';
import FocalPointModal from './focal_point_modal';
import {
MuteModal,
@ -39,6 +40,7 @@ const MODAL_COMPONENTS = {
'LIST_ADDER': ListAdder,
'COMPARE_HISTORY': CompareHistoryModal,
'FILTER': FilterModal,
'SUBSCRIBED_LANGUAGES': () => Promise.resolve({ default: SubscribedLanguagesModal }),
};
export default class ModalRoot extends React.PureComponent {

@ -1,5 +1,6 @@
import React from 'react';
import { NavLink, withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { NavLink, Link } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import Icon from 'mastodon/components/icon';
import { showTrends } from 'mastodon/initial_state';
@ -7,30 +8,68 @@ import NotificationsCounterIcon from './notifications_counter_icon';
import FollowRequestsNavLink from './follow_requests_nav_link';
import ListPanel from './list_panel';
import TrendsContainer from 'mastodon/features/getting_started/containers/trends_container';
import Logo from 'mastodon/components/logo';
import SignInBanner from './sign_in_banner';
const NavigationPanel = () => (
<div className='navigation-panel'>
<NavLink className='column-link column-link--transparent' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>
<FollowRequestsNavLink />
<NavLink className='column-link column-link--transparent' to='/explore' data-preview-title-id='explore.title' data-preview-icon='hashtag'><Icon className='column-link__icon' id='hashtag' fixedWidth /><FormattedMessage id='explore.title' defaultMessage='Explore' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>
<NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/conversations'><Icon className='column-link__icon' id='at' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>
export default class NavigationPanel extends React.Component {
<ListPanel />
static contextTypes = {
router: PropTypes.object.isRequired,
identity: PropTypes.object.isRequired,
};
<hr />
render () {
const { signedIn } = this.context.identity;
<a className='column-link column-link--transparent' href='/settings/preferences'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a>
<a className='column-link column-link--transparent' href='/relationships'><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>
return (
<div className='navigation-panel'>
<Link to='/' className='column-link column-link--logo'><Logo /></Link>
{showTrends && <div className='flex-spacer' />}
{showTrends && <TrendsContainer />}
</div>
);
<hr />
export default withRouter(NavigationPanel);
{signedIn && (
<React.Fragment>
<NavLink className='column-link column-link--transparent' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>
<FollowRequestsNavLink />
</React.Fragment>
)}
<NavLink className='column-link column-link--transparent' to='/explore' data-preview-title-id='explore.title' data-preview-icon='hashtag'><Icon className='column-link__icon' id='hashtag' fixedWidth /><FormattedMessage id='explore.title' defaultMessage='Explore' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>
<NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>
{!signedIn && (
<React.Fragment>
<hr />
<SignInBanner />
</React.Fragment>
)}
{signedIn && (
<React.Fragment>
<NavLink className='column-link column-link--transparent' to='/conversations'><Icon className='column-link__icon' id='at' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink>
<NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink>
<ListPanel />
<hr />
<a className='column-link column-link--transparent' href='/settings/preferences'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a>
<a className='column-link column-link--transparent' href='/relationships'><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>
</React.Fragment>
)}
{showTrends && (
<React.Fragment>
<div className='flex-spacer' />
<TrendsContainer />
</React.Fragment>
)}
</div>
);
}
}

@ -0,0 +1,13 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { registrationsOpen } from 'mastodon/initial_state';
const SignInBanner = () => (
<div className='sign-in-banner'>
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.' /></p>
<a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
<a href={registrationsOpen ? '/auth/sign_up' : 'https://joinmastodon.org/servers'} className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /></a>
</div>
);
export default SignInBanner;

@ -20,7 +20,6 @@ import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodo
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
import UploadArea from './components/upload_area';
import ColumnsAreaContainer from './containers/columns_area_container';
import DocumentTitle from './components/document_title';
import PictureInPicture from 'mastodon/features/picture_in_picture';
import {
Compose,
@ -53,8 +52,9 @@ import {
Explore,
FollowRecommendations,
} from './util/async-components';
import { me } from '../../initial_state';
import { me, title } from '../../initial_state';
import { closeOnboarding, INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import { Helmet } from 'react-helmet';
// Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
@ -110,6 +110,10 @@ const keyMap = {
class SwitchingColumnsArea extends React.PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
children: PropTypes.node,
location: PropTypes.object,
@ -145,12 +149,25 @@ class SwitchingColumnsArea extends React.PureComponent {
render () {
const { children, mobile } = this.props;
const redirect = mobile ? <Redirect from='/' to='/home' exact /> : <Redirect from='/' to='/getting-started' exact />;
const { signedIn } = this.context.identity;
let redirect;
if (signedIn) {
if (mobile) {
redirect = <Redirect from='/' to='/home' exact />;
} else {
redirect = <Redirect from='/' to='/getting-started' exact />;
}
} else {
redirect = <Redirect from='/' to='/explore' exact />;
}
return (
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
<WrappedSwitch>
{redirect}
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
@ -208,6 +225,7 @@ class UI extends React.PureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
identity: PropTypes.object.isRequired,
};
static propTypes = {
@ -343,6 +361,8 @@ class UI extends React.PureComponent {
}
componentDidMount () {
const { signedIn } = this.context.identity;
window.addEventListener('focus', this.handleWindowFocus, false);
window.addEventListener('blur', this.handleWindowBlur, false);
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
@ -359,16 +379,18 @@ class UI extends React.PureComponent {
}
// On first launch, redirect to the follow recommendations page
if (this.props.firstLaunch) {
if (signedIn && this.props.firstLaunch) {
this.context.router.history.replace('/start');
this.props.dispatch(closeOnboarding());
}
this.props.dispatch(fetchMarkers());
this.props.dispatch(expandHomeTimeline());
this.props.dispatch(expandNotifications());
if (signedIn) {
this.props.dispatch(fetchMarkers());
this.props.dispatch(expandHomeTimeline());
this.props.dispatch(expandNotifications());
setTimeout(() => this.props.dispatch(fetchRules()), 3000);
setTimeout(() => this.props.dispatch(fetchRules()), 3000);
}
this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
@ -546,7 +568,10 @@ class UI extends React.PureComponent {
<LoadingBarContainer className='loading-bar' />
<ModalContainer />
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
<DocumentTitle />
<Helmet>
<title>{title}</title>
</Helmet>
</div>
</HotKeys>
);

@ -3,6 +3,7 @@ const initialState = element && JSON.parse(element.textContent);
const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop];
export const domain = getMeta('domain');
export const reduceMotion = getMeta('reduce_motion');
export const autoPlayGif = getMeta('auto_play_gif');
export const displayMedia = getMeta('display_media');
@ -14,6 +15,7 @@ export const me = getMeta('me');
export const searchEnabled = getMeta('search_enabled');
export const maxChars = (initialState && initialState.max_toot_chars) || 500;
export const limitedFederationMode = getMeta('limited_federation_mode');
export const registrationsOpen = getMeta('registrations_open');
export const repository = getMeta('repository');
export const source_url = getMeta('source_url');
export const version = getMeta('version');
@ -27,5 +29,6 @@ export const title = getMeta('title');
export const cropImages = getMeta('crop_images');
export const disableSwiping = getMeta('disable_swiping');
export const languages = initialState && initialState.languages;
export const server = initialState && initialState.server;
export default initialState;

@ -24,6 +24,7 @@
"account.follows_you": "Volg jou",
"account.hide_reblogs": "Versteek hupstoot vanaf @{name}",
"account.joined": "{date} aangesluit",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "Eienaarskap van die skakel was getoets op {date}",
"account.locked_info": "Die rekening se privaatheidstatus is gesluit. Die eienaar hersien handmatig wie hom/haar kan volg.",
"account.media": "Media",
@ -226,8 +227,8 @@
"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.privacy_policy": "Privacy Policy",
"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}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.statuses": "Toots",
"search_results.statuses_fts_disabled": "Searching toots by their content is not enabled on this Mastodon server.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "Block @{name}",
@ -518,10 +523,16 @@
"status.show_less_all": "Show less for all",
"status.show_more": "Show more",
"status.show_more_all": "Show more for all",
"status.show_original": "Show original",
"status.show_thread": "Show thread",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Not available",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Dismiss suggestion",
"suggestions.header": "You might be interested in…",
"tabs_bar.federated_timeline": "Federated",

@ -24,6 +24,7 @@
"account.follows_you": "يُتابِعُك",
"account.hide_reblogs": "إخفاء مشاركات @{name}",
"account.joined": "انضم في {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "تمَّ التَّحقق مِن مِلْكيّة هذا الرابط بتاريخ {date}",
"account.locked_info": "تمَّ تعيين حالة خصوصية هذا الحساب إلى مُقفَل. يُراجع المالك يدويًا من يمكنه متابعته.",
"account.media": "وسائط",
@ -59,7 +60,7 @@
"alert.unexpected.title": "المعذرة!",
"announcement.announcement": "إعلان",
"attachments_list.unprocessed": "(غير معالَج)",
"audio.hide": "Hide audio",
"audio.hide": "إخفاء المقطع الصوتي",
"autosuggest_hashtag.per_week": "{count} في الأسبوع",
"boost_modal.combo": "يُمكنك الضّغط على {combo} لتخطي هذا في المرة المُقبلة",
"bundle_column_error.body": "لقد حدث خطأ ما أثناء تحميل هذا العنصر.",
@ -203,7 +204,7 @@
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.settings_link": "صفحة الإعدادات",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
@ -226,8 +227,8 @@
"getting_started.heading": "استعدّ للبدء",
"getting_started.invite": "دعوة أشخاص",
"getting_started.open_source_notice": "ماستدون برنامج مفتوح المصدر. يمكنك المساهمة، أو الإبلاغ عن تقارير الأخطاء، على جيت هب {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "الأمان",
"getting_started.terms": "شروط الخدمة",
"hashtag.column_header.tag_mode.all": "و {additional}",
"hashtag.column_header.tag_mode.any": "أو {additional}",
"hashtag.column_header.tag_mode.none": "بدون {additional}",
@ -347,7 +348,7 @@
"notification.update": "عدّلَ {name} منشورًا",
"notifications.clear": "امسح الإخطارات",
"notifications.clear_confirmation": "أمتأكد من أنك تود مسح جل الإخطارات الخاصة بك و المتلقاة إلى حد الآن ؟",
"notifications.column_settings.admin.report": "New reports:",
"notifications.column_settings.admin.report": "التقارير الجديدة:",
"notifications.column_settings.admin.sign_up": "التسجيلات الجديدة:",
"notifications.column_settings.alert": "إشعارات سطح المكتب",
"notifications.column_settings.favourite": "المُفَضَّلة:",
@ -454,8 +455,8 @@
"report.unfollow": "إلغاء متابعة @{name}",
"report.unfollow_explanation": "أنت تتابع هذا الحساب، لإزالة مَنشوراته من تغذيَتِكَ الرئيسة ألغ متابعته.",
"report_notification.attached_statuses": "{count, plural, one {{count} post} other {{count} posts}} attached",
"report_notification.categories.other": "Other",
"report_notification.categories.spam": "Spam",
"report_notification.categories.other": "آخر",
"report_notification.categories.spam": "مزعج",
"report_notification.categories.violation": "Rule violation",
"report_notification.open": "Open report",
"search.placeholder": "ابحث",
@ -471,7 +472,11 @@
"search_results.nothing_found": "تعذر العثور على نتائج تتضمن هذه المصطلحات",
"search_results.statuses": "المنشورات",
"search_results.statuses_fts_disabled": "البحث عن المنشورات عن طريق المحتوى ليس مفعل في خادم ماستدون هذا.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, zero {} one {نتيجة} two {نتيجتين} few {نتائج} many {نتائج} other {نتائج}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "افتح الواجهة الإدارية لـ @{name}",
"status.admin_status": "افتح هذا المنشور على واجهة الإشراف",
"status.block": "احجب @{name}",
@ -489,7 +494,7 @@
"status.favourite": "أضف إلى المفضلة",
"status.filter": "Filter this post",
"status.filtered": "مُصفّى",
"status.hide": "Hide toot",
"status.hide": "اخف التبويق",
"status.history.created": "أنشأه {name} {date}",
"status.history.edited": "عدله {name} {date}",
"status.load_more": "حمّل المزيد",
@ -513,15 +518,21 @@
"status.report": "ابلِغ عن @{name}",
"status.sensitive_warning": "محتوى حساس",
"status.share": "مشاركة",
"status.show_filter_reason": "Show anyway",
"status.show_filter_reason": "إظهار على أي حال",
"status.show_less": "اعرض أقلّ",
"status.show_less_all": "طي الكل",
"status.show_more": "أظهر المزيد",
"status.show_more_all": "توسيع الكل",
"status.show_original": "Show original",
"status.show_thread": "الكشف عن المحادثة",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "غير متوفر",
"status.unmute_conversation": "فك الكتم عن المحادثة",
"status.unpin": "فك التدبيس من الصفحة التعريفية",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "حفظ التغييرات",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "إلغاء الاقتراح",
"suggestions.header": "يمكن أن يهمك…",
"tabs_bar.federated_timeline": "الموحَّد",

@ -24,6 +24,7 @@
"account.follows_you": "Síguete",
"account.hide_reblogs": "Anubrir les comparticiones de @{name}",
"account.joined": "Joined {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "La propiedá d'esti enllaz foi comprobada'l {date}",
"account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
"account.media": "Media",
@ -93,8 +94,8 @@
"community.column_settings.local_only": "Local only",
"community.column_settings.media_only": "Namái multimedia",
"community.column_settings.remote_only": "Remote only",
"compose.language.change": "Change language",
"compose.language.search": "Search languages...",
"compose.language.change": "Camudar la llingua",
"compose.language.search": "Buscar llingües…",
"compose_form.direct_message_warning_learn_more": "Saber más",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
@ -149,7 +150,7 @@
"directory.recently_active": "Actividá recién",
"embed.instructions": "Empotra esti estáu nun sitiu web copiando'l códigu d'embaxo.",
"embed.preview": "Asina ye cómo va vese:",
"emoji_button.activity": "Actividaes",
"emoji_button.activity": "Actividá",
"emoji_button.clear": "Clear",
"emoji_button.custom": "Custom",
"emoji_button.flags": "Banderes",
@ -170,7 +171,7 @@
"empty_column.blocks": "Entá nun bloquiesti a nengún usuariu.",
"empty_column.bookmarked_statuses": "Entá nun tienes nengún barritu en Marcadores. Cuando amiestes unu, va amosase equí.",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "Entá nun tienes nengún mensaxe direutu. Cuando unvies o recibas dalgún, apaez equí.",
"empty_column.direct": "Entá nun tienes nengún mensaxe direutu. Cuando unvies o recibas dalgún, apaecen equí.",
"empty_column.domain_blocks": "Entá nun hai dominios anubríos.",
"empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
"empty_column.favourited_statuses": "Entá nun tienes nengún barritu en Favoritos. Cuando amiestes unu, va amosase equí.",
@ -226,8 +227,8 @@
"getting_started.heading": "Entamu",
"getting_started.invite": "Convidar a persones",
"getting_started.open_source_notice": "Mastodon ye software de códigu abiertu. Pues collaborar o informar de fallos en GitHub: {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Axustes de la cuenta",
"getting_started.terms": "Términos del serviciu",
"hashtag.column_header.tag_mode.all": "y {additional}",
"hashtag.column_header.tag_mode.any": "o {additional}",
"hashtag.column_header.tag_mode.none": "ensin {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Nun se pudo atopar nada con esos términos de busca",
"search_results.statuses": "Barritos",
"search_results.statuses_fts_disabled": "Esti sirvidor de Mastodon tien activada la gueta de barritos pol so conteníu.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {resultáu} other {resultaos}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "Bloquiar a @{name}",
@ -518,10 +523,16 @@
"status.show_less_all": "Amosar menos en too",
"status.show_more": "Amosar más",
"status.show_more_all": "Amosar más en too",
"status.show_original": "Show original",
"status.show_thread": "Amosar el filu",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Non disponible",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Desfixar del perfil",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Dismiss suggestion",
"suggestions.header": "Quiciabes t'interese…",
"tabs_bar.federated_timeline": "Fediversu",

@ -18,12 +18,13 @@
"account.followers": "Последователи",
"account.followers.empty": "Все още никой не следва този потребител.",
"account.followers_counter": "{count, plural, one {{counter} Последовател} other {{counter} Последователи}}",
"account.following": "Following",
"account.following": "Последвани",
"account.following_counter": "{count, plural, one {{counter} Последван} other {{counter} Последвани}}",
"account.follows.empty": "Този потребител все още не следва никого.",
"account.follows_you": "Твой последовател",
"account.hide_reblogs": "Скриване на споделяния от @{name}",
"account.joined": "Joined {date}",
"account.joined": "Присъединил се на {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "Собствеността върху тази връзка е проверена на {date}",
"account.locked_info": "Този акаунт е поверително заключен. Собственикът преглежда ръчно кой може да го следва.",
"account.media": "Мултимедия",
@ -41,25 +42,25 @@
"account.statuses_counter": "{count, plural, one {{counter} Публикация} other {{counter} Публикации}}",
"account.unblock": "Не блокирай",
"account.unblock_domain": "Unhide {domain}",
"account.unblock_short": "Unblock",
"account.unblock_short": "Отблокирай",
"account.unendorse": "Не включвайте в профила",
"account.unfollow": "Не следвай",
"account.unmute": "Раззаглушаване на @{name}",
"account.unmute_notifications": "Раззаглушаване на известия от @{name}",
"account.unmute_short": "Unmute",
"account_note.placeholder": "Click to add a note",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
"admin.dashboard.retention.average": "Average",
"admin.dashboard.retention.cohort": "Sign-up month",
"admin.dashboard.retention.cohort_size": "New users",
"admin.dashboard.daily_retention": "Ниво на задържани на потребители след регистрация, в дни",
"admin.dashboard.monthly_retention": "Ниво на задържани на потребители след регистрация, в месеци",
"admin.dashboard.retention.average": "Средно",
"admin.dashboard.retention.cohort": "Месец на регистрацията",
"admin.dashboard.retention.cohort_size": "Нови потребители",
"alert.rate_limited.message": "Моля, опитайте отново след {retry_time, time, medium}.",
"alert.rate_limited.title": "Скоростта е ограничена",
"alert.unexpected.message": "Възникна неочаквана грешка.",
"alert.unexpected.title": "Опаа!",
"announcement.announcement": "Оповестяване",
"attachments_list.unprocessed": "(unprocessed)",
"audio.hide": "Hide audio",
"attachments_list.unprocessed": "(необработен)",
"audio.hide": "Скриване на видеото",
"autosuggest_hashtag.per_week": "{count} на седмица",
"boost_modal.combo": "Можете да натиснете {combo}, за да пропуснете това следващия път",
"bundle_column_error.body": "Нещо се обърка при зареждането на този компонент.",
@ -71,7 +72,7 @@
"column.blocks": "Блокирани потребители",
"column.bookmarks": "Отметки",
"column.community": "Локална емисия",
"column.direct": "Direct messages",
"column.direct": "Лични съобщения",
"column.directory": "Преглед на профили",
"column.domain_blocks": "Hidden domains",
"column.favourites": "Любими",
@ -93,10 +94,10 @@
"community.column_settings.local_only": "Само локално",
"community.column_settings.media_only": "Media only",
"community.column_settings.remote_only": "Само дистанционно",
"compose.language.change": "Change language",
"compose.language.search": "Search languages...",
"compose.language.change": "Смяна на езика",
"compose.language.search": "Търсене на езици...",
"compose_form.direct_message_warning_learn_more": "Още информация",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
"compose_form.encryption_warning": "Поставете в Мастодон не са криптирани от край до край. Не споделяйте никаква чувствителна информация.",
"compose_form.hashtag_warning": "Тази публикация няма да бъде изброена под нито един хаштаг, тъй като е скрита. Само публични публикации могат да се търсят по хаштаг.",
"compose_form.lock_disclaimer": "Вашият акаунт не е {locked}. Всеки може да ви последва, за да прегледа вашите публикации само за последователи.",
"compose_form.lock_disclaimer.lock": "заключено",
@ -107,9 +108,9 @@
"compose_form.poll.remove_option": "Премахване на този избор",
"compose_form.poll.switch_to_multiple": "Промяна на анкетата, за да се позволят множество възможни избора",
"compose_form.poll.switch_to_single": "Промяна на анкетата, за да се позволи един възможен избор",
"compose_form.publish": "Publish",
"compose_form.publish": "Публикувай",
"compose_form.publish_loud": "{publish}!",
"compose_form.save_changes": "Save changes",
"compose_form.save_changes": "Запази промените",
"compose_form.sensitive.hide": "{count, plural, one {Маркиране на мултимедията като деликатна} other {Маркиране на мултимедиите като деликатни}}",
"compose_form.sensitive.marked": "{count, plural, one {Мултимедията е маркирана като деликатна} other {Мултимедиите са маркирани като деликатни}}",
"compose_form.sensitive.unmarked": "{count, plural, one {Мултимедията не е маркирана като деликатна} other {Мултимедиите не са маркирани като деликатни}}",
@ -124,8 +125,8 @@
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete_list.confirm": "Изтриване",
"confirmations.delete_list.message": "Сигурни ли сте, че искате да изтриете окончателно този списък?",
"confirmations.discard_edit_media.confirm": "Discard",
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
"confirmations.discard_edit_media.confirm": "Отмени",
"confirmations.discard_edit_media.message": "Имате незапазени промени на описанието или прегледа на медията, отмяна въпреки това?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.logout.confirm": "Излизане",
@ -143,14 +144,14 @@
"conversation.mark_as_read": "Маркиране като прочетено",
"conversation.open": "Преглед на разговор",
"conversation.with": "С {names}",
"directory.federated": "From known fediverse",
"directory.federated": "От познат федивърс",
"directory.local": "Само от {domain}",
"directory.new_arrivals": "Новодошли",
"directory.recently_active": "Наскоро активни",
"embed.instructions": "Embed this status on your website by copying the code below.",
"embed.preview": "Ето как ще изглежда:",
"emoji_button.activity": "Дейност",
"emoji_button.clear": "Clear",
"emoji_button.clear": "Изчисти",
"emoji_button.custom": "Персонализирано",
"emoji_button.flags": "Знамена",
"emoji_button.food": "Храна и напитки",
@ -170,16 +171,16 @@
"empty_column.blocks": "Не сте блокирали потребители все още.",
"empty_column.bookmarked_statuses": "Все още нямате отметнати публикации. Когато отметнете някоя, тя ще се покаже тук.",
"empty_column.community": "Локалната емисия е празна. Напишете нещо публично, за да започнете!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.direct": "Все още нямате лични съобщения. Когато изпратите или получите ще се покаже тук.",
"empty_column.domain_blocks": "There are no hidden domains yet.",
"empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
"empty_column.explore_statuses": "Няма нищо популярно в момента. Проверете пак по-късно!",
"empty_column.favourited_statuses": "Все още нямате любими публикации. Когато поставите някоя в любими, тя ще се покаже тук.",
"empty_column.favourites": "Все още никой не е поставил тази публикация в любими. Когато някой го направи, ще се покаже тук.",
"empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.",
"empty_column.follow_recommendations": "Изглежда, че няма генерирани предложения за вас. Можете да опитате да търсите за хора, които знаете или да разгледате популярните тагове.",
"empty_column.follow_requests": "Все още нямате заявки за последване. Когато получите такава, тя ще се покаже тук.",
"empty_column.hashtag": "В този хаштаг няма нищо все още.",
"empty_column.home": "Вашата начална емисия е празна! Посетете {public} или използвайте търсене, за да започнете и да се запознаете с други потребители.",
"empty_column.home.suggestions": "See some suggestions",
"empty_column.home.suggestions": "Виж някои предложения",
"empty_column.list": "There is nothing in this list yet.",
"empty_column.lists": "Все още нямате списъци. Когато създадете такъв, той ще се покаже тук.",
"empty_column.mutes": "Не сте заглушавали потребители все още.",
@ -189,31 +190,31 @@
"error.unexpected_crash.explanation_addons": "Тази страница не може да се покаже правилно. Тази грешка вероятно е причинена от добавка на браузъра или инструменти за автоматичен превод.",
"error.unexpected_crash.next_steps": "Опитайте да опресните страницата. Ако това не помогне, все още можете да използвате Mastodon чрез различен браузър или приложение.",
"error.unexpected_crash.next_steps_addons": "Опитайте да ги деактивирате и да опресните страницата. Ако това не помогне, може все още да използвате Mastodon чрез различен браузър или приложение.",
"errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
"errors.unexpected_crash.copy_stacktrace": "Копиране на stacktrace-а в клипборда",
"errors.unexpected_crash.report_issue": "Сигнал за проблем",
"explore.search_results": "Search results",
"explore.suggested_follows": "For you",
"explore.title": "Explore",
"explore.trending_links": "News",
"explore.trending_statuses": "Posts",
"explore.trending_tags": "Hashtags",
"explore.search_results": "Резултати от търсенето",
"explore.suggested_follows": "За вас",
"explore.title": "Разглеждане",
"explore.trending_links": "Новини",
"explore.trending_statuses": "Публикации",
"explore.trending_tags": "Тагове",
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
"filter_modal.added.context_mismatch_title": "Context mismatch!",
"filter_modal.added.context_mismatch_title": "Несъвпадение на контекста!",
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.expired_title": "Изтекал филтър!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.review_and_configure_title": "Настройки на филтър",
"filter_modal.added.settings_link": "страница с настройки",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "expired",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"follow_recommendations.done": "Done",
"filter_modal.added.title": "Филтърът е добавен!",
"filter_modal.select_filter.context_mismatch": "не е приложимо за този контекст",
"filter_modal.select_filter.expired": "изтекло",
"filter_modal.select_filter.prompt_new": "Нова категория: {name}",
"filter_modal.select_filter.search": "Търси или създай",
"filter_modal.select_filter.subtitle": "Изберете съществуваща категория или създайте нова",
"filter_modal.select_filter.title": "Филтриране на поста",
"filter_modal.title.status": "Филтрирай пост",
"follow_recommendations.done": "Готово",
"follow_recommendations.heading": "Следвайте хора, които харесвате, за да виждате техните съобщения! Ето някои предложения.",
"follow_recommendations.lead": "Съобщения от хора, които следвате, ще се показват в хронологичен ред на вашата главна страница. Не се страхувайте, че ще сгрешите, по всяко време много лесно можете да спрете да ги следвате!",
"follow_request.authorize": "Упълномощаване",
@ -226,8 +227,8 @@
"getting_started.heading": "Първи стъпки",
"getting_started.invite": "Поканване на хора",
"getting_started.open_source_notice": "Mastodon е софтуер с отворен код. Можеш да помогнеш или да докладваш за проблеми в Github: {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Security",
"getting_started.terms": "Условия за ползване",
"hashtag.column_header.tag_mode.all": "и {additional}",
"hashtag.column_header.tag_mode.any": "или {additional}",
"hashtag.column_header.tag_mode.none": "без {additional}",
@ -237,9 +238,9 @@
"hashtag.column_settings.tag_mode.any": "Някое от тези",
"hashtag.column_settings.tag_mode.none": "Никое от тези",
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
"hashtag.follow": "Follow hashtag",
"hashtag.total_volume": "Total volume in the last {days, plural, one {day} other {{days} days}}",
"hashtag.unfollow": "Unfollow hashtag",
"hashtag.follow": "Следване на хаштаг",
"hashtag.total_volume": "Пълно количество в {days, plural,one {последния ден} other {последните {days} дни}}",
"hashtag.unfollow": "Спиране на следване на хаштаг",
"home.column_settings.basic": "Основно",
"home.column_settings.show_reblogs": "Показване на споделяния",
"home.column_settings.show_replies": "Показване на отговори",
@ -287,8 +288,8 @@
"lightbox.expand": "Разгъване на полето за преглед на изображение",
"lightbox.next": "Напред",
"lightbox.previous": "Назад",
"limited_account_hint.action": "Show profile anyway",
"limited_account_hint.title": "This profile has been hidden by the moderators of your server.",
"limited_account_hint.action": "Покажи профила въпреки това",
"limited_account_hint.title": "Този профил е скрит от модераторите на сървъра Ви.",
"lists.account.add": "Добавяне към списък",
"lists.account.remove": "Премахване от списък",
"lists.delete": "Изтриване на списък",
@ -315,11 +316,11 @@
"navigation_bar.bookmarks": "Отметки",
"navigation_bar.community_timeline": "Локална емисия",
"navigation_bar.compose": "Композиране на нова публикация",
"navigation_bar.direct": "Direct messages",
"navigation_bar.direct": "Директни съобщения",
"navigation_bar.discover": "Откриване",
"navigation_bar.domain_blocks": "Hidden domains",
"navigation_bar.edit_profile": "Редактирай профил",
"navigation_bar.explore": "Explore",
"navigation_bar.explore": "Разглеждане",
"navigation_bar.favourites": "Любими",
"navigation_bar.filters": "Заглушени думи",
"navigation_bar.follow_requests": "Заявки за последване",
@ -334,8 +335,8 @@
"navigation_bar.preferences": "Предпочитания",
"navigation_bar.public_timeline": "Публичен канал",
"navigation_bar.security": "Сигурност",
"notification.admin.report": "{name} reported {target}",
"notification.admin.sign_up": "{name} signed up",
"notification.admin.report": "{name} докладва {target}",
"notification.admin.sign_up": "{name} се регистрира",
"notification.favourite": "{name} хареса твоята публикация",
"notification.follow": "{name} те последва",
"notification.follow_request": "{name} поиска да ви последва",
@ -344,16 +345,16 @@
"notification.poll": "Анкета, в която сте гласували, приключи",
"notification.reblog": "{name} сподели твоята публикация",
"notification.status": "{name} току-що публикува",
"notification.update": "{name} edited a post",
"notification.update": "{name} промени публикация",
"notifications.clear": "Изчистване на известия",
"notifications.clear_confirmation": "Сигурни ли сте, че искате да изчистите окончателно всичките си известия?",
"notifications.column_settings.admin.report": "New reports:",
"notifications.column_settings.admin.sign_up": "New sign-ups:",
"notifications.column_settings.admin.report": "Нови доклади:",
"notifications.column_settings.admin.sign_up": "Нови регистрации:",
"notifications.column_settings.alert": "Десктоп известия",
"notifications.column_settings.favourite": "Предпочитани:",
"notifications.column_settings.filter_bar.advanced": "Показване на всички категории",
"notifications.column_settings.filter_bar.category": "Лента за бърз филтър",
"notifications.column_settings.filter_bar.show_bar": "Show filter bar",
"notifications.column_settings.filter_bar.show_bar": "Покажи лентата с филтри",
"notifications.column_settings.follow": "Нови последователи:",
"notifications.column_settings.follow_request": "Нови заявки за последване:",
"notifications.column_settings.mention": "Споменавания:",
@ -363,9 +364,9 @@
"notifications.column_settings.show": "Покажи в колона",
"notifications.column_settings.sound": "Пускане на звук",
"notifications.column_settings.status": "Нови публикации:",
"notifications.column_settings.unread_notifications.category": "Unread notifications",
"notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.unread_notifications.category": "Непрочетени известия",
"notifications.column_settings.unread_notifications.highlight": "Отбележи непрочетените уведомления",
"notifications.column_settings.update": "Редакции:",
"notifications.filter.all": "Всичко",
"notifications.filter.boosts": "Споделяния",
"notifications.filter.favourites": "Любими",
@ -389,15 +390,15 @@
"poll.total_votes": "{count, plural, one {# глас} other {# гласа}}",
"poll.vote": "Гласуване",
"poll.voted": "Вие гласувахте за този отговор",
"poll.votes": "{votes, plural, one {# vote} other {# votes}}",
"poll.votes": "{votes, plural, one {# глас} other {# гласа}}",
"poll_button.add_poll": "Добавяне на анкета",
"poll_button.remove_poll": "Премахване на анкета",
"privacy.change": "Adjust status privacy",
"privacy.direct.long": "Post to mentioned users only",
"privacy.direct.short": "Direct",
"privacy.direct.short": "Само споменатите хора",
"privacy.private.long": "Post to followers only",
"privacy.private.short": "Followers-only",
"privacy.public.long": "Visible for all",
"privacy.private.short": "Само последователи",
"privacy.public.long": "Видимо за всички",
"privacy.public.short": "Публично",
"privacy.unlisted.long": "Visible for all, but opted-out of discovery features",
"privacy.unlisted.short": "Скрито",
@ -468,10 +469,14 @@
"search_results.accounts": "Хора",
"search_results.all": "All",
"search_results.hashtags": "Хаштагове",
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.nothing_found": "Не е намерено нищо за това търсене",
"search_results.statuses": "Публикации",
"search_results.statuses_fts_disabled": "Търсенето на публикации по тяхното съдържание не е активирано за този Mastodon сървър.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {резултат} other {резултата}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Отваряне на интерфейс за модериране за @{name}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "Блокиране на @{name}",
@ -482,16 +487,16 @@
"status.delete": "Изтриване",
"status.detailed_status": "Подробен изглед на разговор",
"status.direct": "Директно съобщение към @{name}",
"status.edit": "Edit",
"status.edited": "Edited {date}",
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
"status.edit": "Редакция",
"status.edited": "Редактирано на {date}",
"status.edited_x_times": "Редактирано {count, plural,one {{count} път} other {{count} пъти}}",
"status.embed": "Вграждане",
"status.favourite": "Предпочитани",
"status.filter": "Filter this post",
"status.filter": "Филтриране на поста",
"status.filtered": "Филтрирано",
"status.hide": "Hide toot",
"status.history.created": "{name} created {date}",
"status.history.edited": "{name} edited {date}",
"status.hide": "Скриване на поста",
"status.history.created": "{name} създаде {date}",
"status.history.edited": "{name} редактира {date}",
"status.load_more": "Зареждане на още",
"status.media_hidden": "Мултимедията е скрита",
"status.mention": "Споменаване",
@ -513,15 +518,21 @@
"status.report": "Докладване на @{name}",
"status.sensitive_warning": "Деликатно съдържание",
"status.share": "Споделяне",
"status.show_filter_reason": "Show anyway",
"status.show_filter_reason": "Покажи въпреки това",
"status.show_less": "Покажи по-малко",
"status.show_less_all": "Покажи по-малко за всички",
"status.show_more": "Покажи повече",
"status.show_more_all": "Покажи повече за всички",
"status.show_original": "Show original",
"status.show_thread": "Показване на тема",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Не е налично",
"status.unmute_conversation": "Раззаглушаване на разговор",
"status.unpin": "Разкачане от профил",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Отхвърляне на предложение",
"suggestions.header": "Може да се интересувате от…",
"tabs_bar.federated_timeline": "Обединен",
@ -550,14 +561,14 @@
"upload_error.poll": "Качването на файлове не е позволено с анкети.",
"upload_form.audio_description": "Опишете за хора със загуба на слуха",
"upload_form.description": "Опишете за хора със зрителни увреждания",
"upload_form.description_missing": "No description added",
"upload_form.description_missing": "Без добавено описание",
"upload_form.edit": "Редакция",
"upload_form.thumbnail": "Промяна на миниизображението",
"upload_form.undo": "Отмяна",
"upload_form.video_description": "Опишете за хора със загуба на слуха или зрително увреждане",
"upload_modal.analyzing_picture": "Анализ на снимка…",
"upload_modal.apply": "Прилагане",
"upload_modal.applying": "Applying…",
"upload_modal.applying": "Прилагане…",
"upload_modal.choose_image": "Избор на изображение",
"upload_modal.description_placeholder": "Ах, чудна българска земьо, полюшвай цъфтящи жита",
"upload_modal.detect_text": "Откриване на текст от картина",

@ -24,6 +24,7 @@
"account.follows_you": "তোমাকে অনুসরণ করে",
"account.hide_reblogs": "@{name}'র সমর্থনগুলি লুকিয়ে ফেলুন",
"account.joined": "Joined {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "এই লিংকের মালিকানা চেক করা হয়েছে {date} তারিখে",
"account.locked_info": "এই নিবন্ধনের গোপনীয়তার ক্ষেত্র তালা দেওয়া আছে। নিবন্ধনকারী অনুসরণ করার অনুমতি যাদেরকে দেবেন, শুধু তারাই অনুসরণ করতে পারবেন।",
"account.media": "মিডিয়া",
@ -226,8 +227,8 @@
"getting_started.heading": "শুরু করা",
"getting_started.invite": "অন্যদের আমন্ত্রণ করুন",
"getting_started.open_source_notice": "মাস্টাডন একটি মুক্ত সফটওয়্যার। তৈরিতে সাহায্য করতে বা কোনো সমস্যা সম্পর্কে জানাতে আমাদের গিটহাবে যেতে পারেন {github}।",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "নিরাপত্তা",
"getting_started.terms": "ব্যবহারের নিয়মাবলী",
"hashtag.column_header.tag_mode.all": "এবং {additional}",
"hashtag.column_header.tag_mode.any": "অথবা {additional}",
"hashtag.column_header.tag_mode.none": "বাদ দিয়ে {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.statuses": "টুট",
"search_results.statuses_fts_disabled": "তাদের সামগ্রী দ্বারা টুটগুলি অনুসন্ধান এই মস্তোডন সার্ভারে সক্ষম নয়।",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {ফলাফল} other {ফলাফল}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "@{name} র জন্য পরিচালনার ইন্টারফেসে ঢুকুন",
"status.admin_status": "যায় লেখাটি পরিচালনার ইন্টারফেসে খুলুন",
"status.block": "@{name} কে ব্লক করুন",
@ -518,10 +523,16 @@
"status.show_less_all": "সবগুলোতে কম দেখতে",
"status.show_more": "আরো দেখাতে",
"status.show_more_all": "সবগুলোতে আরো দেখতে",
"status.show_original": "Show original",
"status.show_thread": "আলোচনা দেখতে",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "পাওয়া যাচ্ছে না",
"status.unmute_conversation": "আলোচনার প্রজ্ঞাপন চালু করতে",
"status.unpin": "নিজের পাতা থেকে পিন করে রাখাটির পিন খুলতে",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "সাহায্যের পরামর্শগুলো সরাতে",
"suggestions.header": "আপনি হয়তোবা এগুলোতে আগ্রহী হতে পারেন…",
"tabs_bar.federated_timeline": "যুক্তবিশ্ব",

@ -24,6 +24,7 @@
"account.follows_you": "Ho heul",
"account.hide_reblogs": "Kuzh toudoù rannet gant @{name}",
"account.joined": "Amañ abaoe {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "Gwiriet eo bet perc'hennidigezh al liamm d'an deiziad-mañ : {date}",
"account.locked_info": "Prennet eo ar gont-mañ. Dibab a ra ar perc'henn ar re a c'hall heuliañ anezhi pe anezhañ.",
"account.media": "Media",
@ -226,8 +227,8 @@
"getting_started.heading": "Loc'hañ",
"getting_started.invite": "Pediñ tud",
"getting_started.open_source_notice": "Mastodoñ zo ur meziant digor e darzh. Gallout a rit kenoberzhiañ dezhañ pe danevellañ kudennoù war GitHub e {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Arventennoù ar gont",
"getting_started.terms": "Divizoù gwerzhañ hollek",
"hashtag.column_header.tag_mode.all": "ha {additional}",
"hashtag.column_header.tag_mode.any": "pe {additional}",
"hashtag.column_header.tag_mode.none": "hep {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.statuses": "a doudoù",
"search_results.statuses_fts_disabled": "Klask toudoù dre oc'h endalc'h n'eo ket aotreet war ar servijer-mañ.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {disoc'h} other {a zisoc'h}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Digeriñ etrefas evezherezh evit @{name}",
"status.admin_status": "Digeriñ an toud e-barzh an etrefas evezherezh",
"status.block": "Berzañ @{name}",
@ -518,10 +523,16 @@
"status.show_less_all": "Diskouez nebeutoc'h evit an holl",
"status.show_more": "Diskouez muioc'h",
"status.show_more_all": "Diskouez miuoc'h evit an holl",
"status.show_original": "Show original",
"status.show_thread": "Diskouez ar gaozeadenn",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Dihegerz",
"status.unmute_conversation": "Diguzhat ar gaozeadenn",
"status.unpin": "Dispilhennañ eus ar profil",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Dilezel damvenegoù",
"suggestions.header": "Marteze e vefec'h dedenet gant…",
"tabs_bar.federated_timeline": "Kevredet",

@ -24,6 +24,7 @@
"account.follows_you": "Et segueix",
"account.hide_reblogs": "Amaga els impulsos de @{name}",
"account.joined": "Membre des de {date}",
"account.languages": "Canviar les llengües subscrits",
"account.link_verified_on": "La propietat d'aquest enllaç es va verificar el dia {date}",
"account.locked_info": "Aquest estat de privadesa del compte està definit com a bloquejat. El propietari revisa manualment qui pot seguir-lo.",
"account.media": "Multimèdia",
@ -197,22 +198,22 @@
"explore.trending_links": "Notícies",
"explore.trending_statuses": "Publicacions",
"explore.trending_tags": "Etiquetes",
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
"filter_modal.added.context_mismatch_title": "Context mismatch!",
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "expired",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"filter_modal.added.context_mismatch_explanation": "Aquesta categoria del filtr no aplica al context en el que has accedit a aquest apunt. Si vols que l'apunt sigui filtrat també en aquest context, hauràs d'editar el filtre.",
"filter_modal.added.context_mismatch_title": "El context no coincideix!",
"filter_modal.added.expired_explanation": "La categoria d'aquest filtre ha caducat, necesitaràs canviar la seva data de caducitat per a aplicar-la.",
"filter_modal.added.expired_title": "Filtre caducat!",
"filter_modal.added.review_and_configure": "Per a revisar i configurar aquesta categoria de filtre, ves a {settings_link}.",
"filter_modal.added.review_and_configure_title": "Configuració del filtre",
"filter_modal.added.settings_link": "pàgina de configuració",
"filter_modal.added.short_explanation": "Aquest apunt s'ha afegit a la següent categoria de filtre: {title}.",
"filter_modal.added.title": "Filtre afegit!",
"filter_modal.select_filter.context_mismatch": "no aplica en aquest context",
"filter_modal.select_filter.expired": "caducat",
"filter_modal.select_filter.prompt_new": "Nova categoria: {name}",
"filter_modal.select_filter.search": "Cerca o crea",
"filter_modal.select_filter.subtitle": "Usa una categoria existent o crea una nova",
"filter_modal.select_filter.title": "Filtra aquest apunt",
"filter_modal.title.status": "Filtre un apunt",
"follow_recommendations.done": "Fet",
"follow_recommendations.heading": "Segueix a la gent de la que t'agradaria veure les seves publicacions! Aquí hi ha algunes recomanacions.",
"follow_recommendations.lead": "Les publicacions del usuaris que segueixes es mostraran en ordre cronològic en la teva línia de temps Inici. No tinguis por en cometre errors, pots fàcilment deixar de seguir-los en qualsevol moment!",
@ -226,8 +227,8 @@
"getting_started.heading": "Primers passos",
"getting_started.invite": "Convidar gent",
"getting_started.open_source_notice": "Mastodon és un programari de codi obert. Pots contribuir-hi o informar de problemes a GitHub a {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Configuració del compte",
"getting_started.terms": "Condicions de servei",
"hashtag.column_header.tag_mode.all": "i {additional}",
"hashtag.column_header.tag_mode.any": "o {additional}",
"hashtag.column_header.tag_mode.none": "sense {additional}",
@ -238,7 +239,7 @@
"hashtag.column_settings.tag_mode.none": "Cap daquests",
"hashtag.column_settings.tag_toggle": "Inclou etiquetes addicionals per a aquesta columna",
"hashtag.follow": "Segueix etiqueta",
"hashtag.total_volume": "Total volume in the last {days, plural, one {day} other {{days} days}}",
"hashtag.total_volume": "Volumen total en els darrers {days, plural, one {day} other {{days} dies}}",
"hashtag.unfollow": "Deixa de seguir etiqueta",
"home.column_settings.basic": "Bàsic",
"home.column_settings.show_reblogs": "Mostra els impulsos",
@ -471,7 +472,11 @@
"search_results.nothing_found": "No s'ha pogut trobar res per a aquests termes de cerca",
"search_results.statuses": "Publicacions",
"search_results.statuses_fts_disabled": "La cerca de publicacions pel seu contingut no està habilitada en aquest servidor Mastodon.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Obre l'interfície de moderació per a @{name}",
"status.admin_status": "Obrir aquesta publicació a la interfície de moderació",
"status.block": "Bloqueja @{name}",
@ -487,7 +492,7 @@
"status.edited_x_times": "Editat {count, plural, one {{count} vegada} other {{count} vegades}}",
"status.embed": "Incrusta",
"status.favourite": "Favorit",
"status.filter": "Filter this post",
"status.filter": "Filtre aquest apunt",
"status.filtered": "Filtrat",
"status.hide": "Amaga publicació",
"status.history.created": "{name} ha creat {date}",
@ -518,10 +523,16 @@
"status.show_less_all": "Mostrar-ne menys per a tot",
"status.show_more": "Mostrar-ne més",
"status.show_more_all": "Mostrar-ne més per a tot",
"status.show_original": "Mostra l'original",
"status.show_thread": "Mostra el fil",
"status.translate": "Tradueix",
"status.translated_from": "Traduït del: {lang}",
"status.uncached_media_warning": "No està disponible",
"status.unmute_conversation": "No silenciïs la conversa",
"status.unpin": "No fixis al perfil",
"subscribed_languages.lead": "Només els apunts en les llengües seleccionades apareixeran en le teves línies de temps Inici i llista després del canvi. No en seleccionis cap per a rebre apunts en totes les llengües.",
"subscribed_languages.save": "Desa els canvis",
"subscribed_languages.target": "Canvia les llengües subscrites per a {target}",
"suggestions.dismiss": "Ignora el suggeriment",
"suggestions.header": "És possible que estiguis interessat en…",
"tabs_bar.federated_timeline": "Federat",
@ -538,7 +549,7 @@
"timeline_hint.resources.followers": "Seguidors",
"timeline_hint.resources.follows": "Seguiments",
"timeline_hint.resources.statuses": "Publicacions més antigues",
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}",
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} persones}} en els passats {days, plural, one {day} other {{days} dies}}",
"trends.trending_now": "En tendència",
"ui.beforeunload": "El teu esborrany es perdrà si surts de Mastodon.",
"units.short.billion": "{count}B",

@ -24,6 +24,7 @@
"account.follows_you": "شوێنکەوتووەکانت",
"account.hide_reblogs": "داشاردنی بووستەکان لە @{name}",
"account.joined": "بەشداری {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "خاوەنداریەتی ئەم لینکە لە {date} چێک کراوە",
"account.locked_info": "تایبەتمەندی ئەم هەژمارەیە ڕیکخراوە بۆ قوفڵدراوە. خاوەنەکە بە دەستی پێداچوونەوە دەکات کە کێ دەتوانێت شوێنیان بکەوێت.",
"account.media": "میدیا",
@ -226,8 +227,8 @@
"getting_started.heading": "دەست پێکردن",
"getting_started.invite": "بانگهێشتکردنی خەڵک",
"getting_started.open_source_notice": "ماستۆدۆن نەرمەکالایەکی سەرچاوەی کراوەیە. دەتوانیت بەشداری بکەیت یان گوزارشت بکەیت لەسەر کێشەکانی لە پەڕەی گیتهاب {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "ڕێکخستنەکانی هەژمارە",
"getting_started.terms": "مەرجەکانی خزمەتگوزاری",
"hashtag.column_header.tag_mode.all": "و {additional}",
"hashtag.column_header.tag_mode.any": "یا {additional}",
"hashtag.column_header.tag_mode.none": "بەبێ {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.statuses": "توتەکان",
"search_results.statuses_fts_disabled": "گەڕانی توتەکان بە ناوەڕۆکیان لەسەر ئەم ڕاژەی ماستۆدۆن چالاک نەکراوە.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {دەرئەنجام} other {دەرئەنجام}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "کردنەوەی میانڕەوی بەڕێوەبەر بۆ @{name}",
"status.admin_status": "ئەم توتە بکەوە لە ناو ڕووکاری بەڕیوەبەر",
"status.block": "@{name} ئاستەنگ بکە",
@ -518,10 +523,16 @@
"status.show_less_all": "هەمووی بچووک بکەوە",
"status.show_more": "زیاتر نیشان بدە",
"status.show_more_all": "زیاتر نیشان بدە بۆ هەمووی",
"status.show_original": "Show original",
"status.show_thread": "نیشاندانی گفتوگۆ",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "بەردەست نیە",
"status.unmute_conversation": "گفتوگۆی بێدەنگ",
"status.unpin": "لە سەرەوە لایبە",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "ڕەتکردنەوەی پێشنیار",
"suggestions.header": "لەوانەیە حەزت لەمەش بێت…",
"tabs_bar.federated_timeline": "گشتی",

@ -24,6 +24,7 @@
"account.follows_you": "Vi seguita",
"account.hide_reblogs": "Piattà spartere da @{name}",
"account.joined": "Quì dapoi {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "A prupietà di stu ligame hè stata verificata u {date}",
"account.locked_info": "U statutu di vita privata di u contu hè chjosu. U pruprietariu esamina manualmente e dumande d'abbunamentu.",
"account.media": "Media",
@ -226,8 +227,8 @@
"getting_started.heading": "Per principià",
"getting_started.invite": "Invità ghjente",
"getting_started.open_source_notice": "Mastodon ghjè un lugiziale liberu. Pudete cuntribuisce à u codice o a traduzione, o palisà un bug, nant'à GitHub: {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Sicurità",
"getting_started.terms": "Cundizione di u serviziu",
"hashtag.column_header.tag_mode.all": "è {additional}",
"hashtag.column_header.tag_mode.any": "o {additional}",
"hashtag.column_header.tag_mode.none": "senza {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.statuses": "Statuti",
"search_results.statuses_fts_disabled": "A ricerca di i cuntinuti di i statuti ùn hè micca attivata nant'à stu servore Mastodon.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {risultatu} other {risultati}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Apre l'interfaccia di muderazione per @{name}",
"status.admin_status": "Apre stu statutu in l'interfaccia di muderazione",
"status.block": "Bluccà @{name}",
@ -518,10 +523,16 @@
"status.show_less_all": "Ripiegà tuttu",
"status.show_more": "Slibrà",
"status.show_more_all": "Slibrà tuttu",
"status.show_original": "Show original",
"status.show_thread": "Vede u filu",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Micca dispunibule",
"status.unmute_conversation": "Ùn piattà più a cunversazione",
"status.unpin": "Spuntarulà da u prufile",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Righjittà a pruposta",
"suggestions.header": "Site forse interessatu·a da…",
"tabs_bar.federated_timeline": "Glubale",

@ -24,6 +24,7 @@
"account.follows_you": "Sleduje vás",
"account.hide_reblogs": "Skrýt boosty od @{name}",
"account.joined": "Založen {date}",
"account.languages": "Změnit odebírané jazyky",
"account.link_verified_on": "Vlastnictví tohoto odkazu bylo zkontrolováno {date}",
"account.locked_info": "Stav soukromí tohoto účtu je nastaven na zamčeno. Jeho vlastník ručně posuzuje, kdo ho může sledovat.",
"account.media": "Média",
@ -59,7 +60,7 @@
"alert.unexpected.title": "Jejda!",
"announcement.announcement": "Oznámení",
"attachments_list.unprocessed": "(nezpracováno)",
"audio.hide": "Hide audio",
"audio.hide": "Skrýt zvuk",
"autosuggest_hashtag.per_week": "{count} za týden",
"boost_modal.combo": "Příště můžete pro přeskočení stisknout {combo}",
"bundle_column_error.body": "Při načítání této komponenty se něco pokazilo.",
@ -197,22 +198,22 @@
"explore.trending_links": "Zprávy",
"explore.trending_statuses": "Příspěvky",
"explore.trending_tags": "Hashtagy",
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
"filter_modal.added.context_mismatch_title": "Context mismatch!",
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "expired",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"filter_modal.added.context_mismatch_explanation": "Tato kategorie filtru se nevztahuje na kontext, ve kterém jste tento příspěvek otevřeli. Pokud chcete, aby byl příspěvek filtrován i v tomto kontextu, budete muset filtr upravit.",
"filter_modal.added.context_mismatch_title": "Kontext se neshoduje!",
"filter_modal.added.expired_explanation": "Tato kategorie filtrů vypršela, budete muset změnit datum vypršení platnosti, aby mohla být použita.",
"filter_modal.added.expired_title": "Vypršel filtr!",
"filter_modal.added.review_and_configure": "Chcete-li zkontrolovat a dále konfigurovat tuto kategorii filtru, přejděte na {settings_link}.",
"filter_modal.added.review_and_configure_title": "Nastavení filtru",
"filter_modal.added.settings_link": "stránka nastavení",
"filter_modal.added.short_explanation": "Tento příspěvek byl přidán do následující kategorie filtrů: {title}.",
"filter_modal.added.title": "Filtr přidán!",
"filter_modal.select_filter.context_mismatch": "nevztahuje se na tento kontext",
"filter_modal.select_filter.expired": "vypršela platnost",
"filter_modal.select_filter.prompt_new": "Nová kategorie: {name}",
"filter_modal.select_filter.search": "Vyhledat nebo vytvořit",
"filter_modal.select_filter.subtitle": "Použít existující kategorii nebo vytvořit novou kategorii",
"filter_modal.select_filter.title": "Filtrovat tento příspěvek",
"filter_modal.title.status": "Filtrovat příspěvek",
"follow_recommendations.done": "Hotovo",
"follow_recommendations.heading": "Sledujte lidi, jejichž příspěvky chcete vidět! Tady jsou nějaké návrhy.",
"follow_recommendations.lead": "Příspěvky od lidí, které sledujete, se budou objevovat v chronologickém pořadí ve vaší domovské ose. Nebojte se, že uděláte chybu, můžete lidi stejně snadno kdykoliv přestat sledovat!",
@ -226,8 +227,8 @@
"getting_started.heading": "Začínáme",
"getting_started.invite": "Pozvat lidi",
"getting_started.open_source_notice": "Mastodon je otevřený software. Přispět do jeho vývoje nebo hlásit chyby můžete na GitHubu {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Nastavení účtu",
"getting_started.terms": "Podmínky používání",
"hashtag.column_header.tag_mode.all": "a {additional}",
"hashtag.column_header.tag_mode.any": "nebo {additional}",
"hashtag.column_header.tag_mode.none": "bez {additional}",
@ -237,9 +238,9 @@
"hashtag.column_settings.tag_mode.any": "Jakékoliv z těchto",
"hashtag.column_settings.tag_mode.none": "Žádné z těchto",
"hashtag.column_settings.tag_toggle": "Zahrnout v tomto sloupci dodatečné tagy",
"hashtag.follow": "Follow hashtag",
"hashtag.follow": "Sledovat hashtag",
"hashtag.total_volume": "Total volume in the last {days, plural, one {day} other {{days} days}}",
"hashtag.unfollow": "Unfollow hashtag",
"hashtag.unfollow": "Zrušit sledování hashtagu",
"home.column_settings.basic": "Základní",
"home.column_settings.show_reblogs": "Zobrazit boosty",
"home.column_settings.show_replies": "Zobrazit odpovědi",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Pro tyto hledané výrazy nebylo nic nenalezeno",
"search_results.statuses": "Příspěvky",
"search_results.statuses_fts_disabled": "Vyhledávání příspěvků podle jejich obsahu není na tomto Mastodon serveru povoleno.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {výsledek} few {výsledky} many {výsledků} other {výsledků}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Otevřít moderátorské rozhraní pro @{name}",
"status.admin_status": "Otevřít tento příspěvek v moderátorském rozhraní",
"status.block": "Zablokovat @{name}",
@ -487,7 +492,7 @@
"status.edited_x_times": "Upraven {count, plural, one {{count}krát} few {{count}krát} many {{count}krát} other {{count}krát}}",
"status.embed": "Vložit na web",
"status.favourite": "Oblíbit",
"status.filter": "Filter this post",
"status.filter": "Filtrovat tento příspěvek",
"status.filtered": "Filtrováno",
"status.hide": "Skrýt příspěvek",
"status.history.created": "Uživatel {name} vytvořil {date}",
@ -518,10 +523,16 @@
"status.show_less_all": "Zobrazit méně pro všechny",
"status.show_more": "Zobrazit více",
"status.show_more_all": "Zobrazit více pro všechny",
"status.show_original": "Zobrazit původní",
"status.show_thread": "Zobrazit vlákno",
"status.translate": "Přeložit",
"status.translated_from": "Přeloženo z {lang}",
"status.uncached_media_warning": "Nedostupné",
"status.unmute_conversation": "Odkrýt konverzaci",
"status.unpin": "Odepnout z profilu",
"subscribed_languages.lead": "Po změně se objeví pouze příspěvky ve vybraných jazycích na vašem domě a zobrazí se seznam časových os. Pro příjem příspěvků ve všech jazycích nevyber žádnou.",
"subscribed_languages.save": "Uložit změny",
"subscribed_languages.target": "Změnit odebírané jazyky na {target}",
"suggestions.dismiss": "Odmítnout návrh",
"suggestions.header": "Mohlo by vás zajímat…",
"tabs_bar.federated_timeline": "Federovaná",

@ -24,6 +24,7 @@
"account.follows_you": "Yn eich dilyn chi",
"account.hide_reblogs": "Cuddio bwstiau o @{name}",
"account.joined": "Ymunodd {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "Gwiriwyd perchnogaeth y ddolen yma ar {date}",
"account.locked_info": "Mae'r statws preifatrwydd cyfrif hwn wedi'i osod i gloi. Mae'r perchennog yn adolygu'r sawl sy'n gallu eu dilyn.",
"account.media": "Cyfryngau",
@ -226,8 +227,8 @@
"getting_started.heading": "Dechrau",
"getting_started.invite": "Gwahodd pobl",
"getting_started.open_source_notice": "Mae Mastodon yn feddalwedd côd agored. Mae modd cyfrannu neu adrodd materion ar GitHUb ar {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Diogelwch",
"getting_started.terms": "Telerau Gwasanaeth",
"hashtag.column_header.tag_mode.all": "a {additional}",
"hashtag.column_header.tag_mode.any": "neu {additional}",
"hashtag.column_header.tag_mode.none": "heb {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Methu dod o hyd i unrhyw beth ar gyfer y termau chwilio hyn",
"search_results.statuses": "Postiadau",
"search_results.statuses_fts_disabled": "Nid yw chwilio postiadau yn ôl eu cynnwys wedi'i alluogi ar y gweinydd Mastodon hwn.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, zero {canlyniad} one {canlyniad} two {ganlyniad} other {o ganlyniadau}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Agor rhyngwyneb goruwchwylio ar gyfer @{name}",
"status.admin_status": "Agor y post hwn yn y rhyngwyneb goruwchwylio",
"status.block": "Blocio @{name}",
@ -518,10 +523,16 @@
"status.show_less_all": "Dangos llai i bawb",
"status.show_more": "Dangos mwy",
"status.show_more_all": "Dangos mwy i bawb",
"status.show_original": "Show original",
"status.show_thread": "Dangos edefyn",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Dim ar gael",
"status.unmute_conversation": "Dad-dawelu sgwrs",
"status.unpin": "Dadbinio o'r proffil",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Diswyddo",
"suggestions.header": "Efallai y bydd gennych ddiddordeb mewn…",
"tabs_bar.federated_timeline": "Ffederasiwn",

@ -24,6 +24,7 @@
"account.follows_you": "Følger dig",
"account.hide_reblogs": "Skjul boosts fra @{name}",
"account.joined": "Tilmeldt {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "Ejerskab af dette link blev tjekket {date}",
"account.locked_info": "Denne kontos fortrolighedsstatus er sat til låst. Ejeren bedømmer manuelt, hvem der kan følge vedkommende.",
"account.media": "Medier",
@ -197,22 +198,22 @@
"explore.trending_links": "Nyheder",
"explore.trending_statuses": "Indlæg",
"explore.trending_tags": "Hashtags",
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
"filter_modal.added.context_mismatch_title": "Context mismatch!",
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "expired",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"filter_modal.added.context_mismatch_explanation": "Denne filterkategori omfatter ikke konteksten, hvorunder dette indlæg er tilgået. Redigér filteret, hvis indlægget også ønskes filtreret i denne kontekst.",
"filter_modal.added.context_mismatch_title": "Kontekstmisforhold!",
"filter_modal.added.expired_explanation": "Denne filterkategori er udløbet. Ændr dens udløbsdato, for at anvende den.",
"filter_modal.added.expired_title": "Udløbet filter!",
"filter_modal.added.review_and_configure": "Gå til {settings_link} for at gennemse og yderligere opsætte denne filterkategori.",
"filter_modal.added.review_and_configure_title": "Filterindstillinger",
"filter_modal.added.settings_link": "indstillingsside",
"filter_modal.added.short_explanation": "Dette indlæg er nu føjet til flg. filterkategori: {title}.",
"filter_modal.added.title": "Filter tilføjet!",
"filter_modal.select_filter.context_mismatch": "gælder ikke for denne kontekst",
"filter_modal.select_filter.expired": "udløbet",
"filter_modal.select_filter.prompt_new": "Ny kategori: {name}",
"filter_modal.select_filter.search": "Søg eller opret",
"filter_modal.select_filter.subtitle": "Vælg en eksisterende kategori eller opret en ny",
"filter_modal.select_filter.title": "Filtrér dette indlæg",
"filter_modal.title.status": "Filtrér et indlæg",
"follow_recommendations.done": "Udført",
"follow_recommendations.heading": "Følg personer du gerne vil se indlæg fra! Her er nogle forslag.",
"follow_recommendations.lead": "Indlæg, fra personer du følger, vil fremgå kronologisk ordnet i dit hjemmefeed. Vær ikke bange for at begå fejl, da du altid og meget nemt kan ændre dit valg!",
@ -226,8 +227,8 @@
"getting_started.heading": "Startmenu",
"getting_started.invite": "Invitér folk",
"getting_started.open_source_notice": "Mastodon er open-source software. Du kan bidrage eller anmelde fejl via GitHub {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Kontoindstillinger",
"getting_started.terms": "Tjenestevilkår",
"hashtag.column_header.tag_mode.all": "og {additional}",
"hashtag.column_header.tag_mode.any": "eller {additional}",
"hashtag.column_header.tag_mode.none": "uden {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Ingen resultater for disse søgeord",
"search_results.statuses": "Indlæg",
"search_results.statuses_fts_disabled": "Søgning på indlæg efter deres indhold ikke aktiveret på denne Mastodon-server.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Åbn modereringsbrugerflade for @{name}",
"status.admin_status": "Åbn dette indlæg i modereringsbrugerfladen",
"status.block": "Blokér @{name}",
@ -487,7 +492,7 @@
"status.edited_x_times": "Redigeret {count, plural, one {{count} gang} other {{count} gange}}",
"status.embed": "Indlejr",
"status.favourite": "Favorit",
"status.filter": "Filter this post",
"status.filter": "Filtrér dette indlæg",
"status.filtered": "Filtreret",
"status.hide": "Skjul indlæg",
"status.history.created": "{name} oprettet {date}",
@ -518,10 +523,16 @@
"status.show_less_all": "Vis mindre for alle",
"status.show_more": "Vis mere",
"status.show_more_all": "Vis mere for alle",
"status.show_original": "Vis original",
"status.show_thread": "Vis tråd",
"status.translate": "Oversæt",
"status.translated_from": "Oversat fra {lang}",
"status.uncached_media_warning": "Utilgængelig",
"status.unmute_conversation": "Genaktivér samtale",
"status.unpin": "Frigør fra profil",
"subscribed_languages.lead": "Kun indlæg på udvalgte sprog vil fremgå på Hjem og listetidslinjer efter ændringen. Vælg ingen for at modtage indlæg på alle sprog.",
"subscribed_languages.save": "Gem ændringer",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Afvis foreslag",
"suggestions.header": "Du er måske interesseret i…",
"tabs_bar.federated_timeline": "Fælles",

@ -24,6 +24,7 @@
"account.follows_you": "Folgt dir",
"account.hide_reblogs": "Geteilte Beiträge von @{name} verbergen",
"account.joined": "Beigetreten am {date}",
"account.languages": "Abonnierte Sprachen ändern",
"account.link_verified_on": "Diesem Profil folgt niemand",
"account.locked_info": "Der Privatsphärenstatus dieses Accounts wurde auf „gesperrt“ gesetzt. Die Person bestimmt manuell, wer ihm/ihr folgen darf.",
"account.media": "Medien",
@ -106,7 +107,7 @@
"compose_form.poll.option_placeholder": "Wahl {number}",
"compose_form.poll.remove_option": "Wahl entfernen",
"compose_form.poll.switch_to_multiple": "Umfrage ändern, um mehrere Optionen zu erlauben",
"compose_form.poll.switch_to_single": "Umfrage ändern, um eine einzige Wahl zu erlauben",
"compose_form.poll.switch_to_single": "Umfrage ändern, sodass nur eine einzige Auswahl erlaubt ist",
"compose_form.publish": "Veröffentlichen",
"compose_form.publish_loud": "{publish}!",
"compose_form.save_changes": "Änderungen speichern",
@ -175,20 +176,20 @@
"empty_column.explore_statuses": "Momentan ist nichts im Trend. Schau später wieder vorbei!",
"empty_column.favourited_statuses": "Du hast noch keine favorisierten Tröts. Wenn du einen favorisierst, wird er hier erscheinen.",
"empty_column.favourites": "Noch niemand hat diesen Beitrag favorisiert. Sobald es jemand tut, wird das hier angezeigt.",
"empty_column.follow_recommendations": "Es sieht so aus, als könnten keine Vorschläge für dich generiert werden. Du kannst versuchen nach Leuten zu suchen, die du vielleicht kennst oder du kannst angesagte Hashtags erkunden.",
"empty_column.follow_recommendations": "Es sieht so aus, als könnten keine Vorschläge für dich generiert werden. Du kannst versuchen, nach Leuten zu suchen, die du vielleicht kennst, oder du kannst angesagte Hashtags erkunden.",
"empty_column.follow_requests": "Du hast noch keine Folge-Anfragen. Sobald du eine erhältst, wird sie hier angezeigt.",
"empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.",
"empty_column.home": "Deine Startseite ist leer! Folge mehr Leuten, um sie zu füllen. {suggestions}",
"empty_column.home.suggestions": "Ein paar Vorschläge ansehen",
"empty_column.list": "Diese Liste ist derzeit leer. Wenn Konten auf dieser Liste neue Beiträge veröffentlichen werden sie hier erscheinen.",
"empty_column.lists": "Du hast noch keine Listen. Wenn du eine anlegst, wird sie hier angezeigt.",
"empty_column.lists": "Du hast noch keine Listen. Wenn du eine anlegst, wird sie hier angezeigt werden.",
"empty_column.mutes": "Du hast keine Profile stummgeschaltet.",
"empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um ins Gespräch zu kommen.",
"empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Profilen von anderen Servern, um die Zeitleiste aufzufüllen",
"error.unexpected_crash.explanation": "Aufgrund eines Fehlers in unserem Code oder einer Browsereinkompatibilität konnte diese Seite nicht korrekt angezeigt werden.",
"error.unexpected_crash.explanation": "Aufgrund eines Fehlers in unserem Code oder einer Browser-Inkompatibilität konnte diese Seite nicht korrekt angezeigt werden.",
"error.unexpected_crash.explanation_addons": "Diese Seite konnte nicht korrekt angezeigt werden. Dieser Fehler wird wahrscheinlich durch ein Browser-Add-on oder automatische Übersetzungswerkzeuge verursacht.",
"error.unexpected_crash.next_steps": "Versuche die Seite zu aktualisieren. Wenn das nicht hilft, kannst du Mastodon über einen anderen Browser oder eine native App verwenden.",
"error.unexpected_crash.next_steps_addons": "Versuche sie zu deaktivieren und lade dann die Seite neu. Wenn das Problem weiterhin besteht, solltest du Mastodon über einen anderen Browser oder eine native App nutzen.",
"error.unexpected_crash.next_steps": "Versuche, die Seite zu aktualisieren. Wenn das nicht hilft, kannst du Mastodon über einen anderen Browser oder eine native App verwenden.",
"error.unexpected_crash.next_steps_addons": "Versuche, sie zu deaktivieren, und lade dann die Seite neu. Wenn das Problem weiterhin besteht, solltest du Mastodon über einen anderen Browser oder eine native App nutzen.",
"errors.unexpected_crash.copy_stacktrace": "Fehlerlog in die Zwischenablage kopieren",
"errors.unexpected_crash.report_issue": "Problem melden",
"explore.search_results": "Suchergebnisse",
@ -197,22 +198,22 @@
"explore.trending_links": "Nachrichten",
"explore.trending_statuses": "Beiträge",
"explore.trending_tags": "Hashtags",
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
"filter_modal.added.context_mismatch_title": "Context mismatch!",
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "expired",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"filter_modal.added.context_mismatch_explanation": "Diese Filterkategorie gilt nicht für den Kontext, in welchem du auf diesen Beitrag zugegriffen hast. Wenn der Beitrag auch in diesem Kontext gefiltert werden soll, musst du den Filter bearbeiten.",
"filter_modal.added.context_mismatch_title": "Kontext stimmt nicht überein!",
"filter_modal.added.expired_explanation": "Diese Filterkategrie ist abgelaufen, du musst das Ablaufdatum für diese Kategorie ändern.",
"filter_modal.added.expired_title": "Abgelaufener Filter!",
"filter_modal.added.review_and_configure": "Um diese Filterkategorie zu überprüfen und weiter zu konfigurieren, gehe zu {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filtereinstellungen",
"filter_modal.added.settings_link": "Einstellungsseite",
"filter_modal.added.short_explanation": "Dieser Post wurde zu folgender Filterkategorie hinzugefügt: {title}.",
"filter_modal.added.title": "Filter hinzugefügt!",
"filter_modal.select_filter.context_mismatch": "gilt nicht für diesen Kontext",
"filter_modal.select_filter.expired": "abgelaufen",
"filter_modal.select_filter.prompt_new": "Neue Kategorie: {name}",
"filter_modal.select_filter.search": "Suchen oder Erstellen",
"filter_modal.select_filter.subtitle": "Eine existierende Kategorie benutzen oder eine erstellen",
"filter_modal.select_filter.title": "Diesen Beitrag filtern",
"filter_modal.title.status": "Einen Beitrag filtern",
"follow_recommendations.done": "Fertig",
"follow_recommendations.heading": "Folge Leuten, von denen du Beiträge sehen möchtest! Hier sind einige Vorschläge.",
"follow_recommendations.lead": "Beiträge von Personen, denen du folgst, werden in chronologischer Reihenfolge auf deiner Startseite angezeigt. Hab keine Angst, Fehler zu machen, du kannst den Leuten jederzeit wieder entfolgen!",
@ -226,16 +227,16 @@
"getting_started.heading": "Erste Schritte",
"getting_started.invite": "Leute einladen",
"getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf GitHub unter {github} dazu beitragen oder Probleme melden.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Konto & Sicherheit",
"getting_started.terms": "Nutzungsbedingungen",
"hashtag.column_header.tag_mode.all": "und {additional}",
"hashtag.column_header.tag_mode.any": "oder {additional}",
"hashtag.column_header.tag_mode.none": "ohne {additional}",
"hashtag.column_settings.select.no_options_message": "Keine Vorschläge gefunden",
"hashtag.column_settings.select.placeholder": "Hashtags eintragen…",
"hashtag.column_settings.tag_mode.all": "All diese",
"hashtag.column_settings.tag_mode.any": "Eins von diesen",
"hashtag.column_settings.tag_mode.none": "Keins von diesen",
"hashtag.column_settings.tag_mode.any": "Eines von diesen",
"hashtag.column_settings.tag_mode.none": "Keines von diesen",
"hashtag.column_settings.tag_toggle": "Zusätzliche Hashtags für diese Spalte einfügen",
"hashtag.follow": "Hashtag folgen",
"hashtag.total_volume": "Gesamtes Aufkommen {days, plural, one {am letzten Tag} other {in den letzten {days} Tagen}}",
@ -300,7 +301,7 @@
"lists.replies_policy.list": "Mitglieder der Liste",
"lists.replies_policy.none": "Niemand",
"lists.replies_policy.title": "Antworten anzeigen für:",
"lists.search": "Suche nach Leuten denen du folgst",
"lists.search": "Suche nach Leuten, denen du folgst",
"lists.subheading": "Deine Listen",
"load_pending": "{count, plural, one {# neuer Beitrag} other {# neue Beiträge}}",
"loading_indicator.label": "Wird geladen …",
@ -341,7 +342,7 @@
"notification.follow_request": "{name} möchte dir folgen",
"notification.mention": "{name} hat dich erwähnt",
"notification.own_poll": "Deine Umfrage ist beendet",
"notification.poll": "Eine Umfrage in der du abgestimmt hast ist vorbei",
"notification.poll": "Eine Umfrage, an der du teilgenommen hast, ist vorbei",
"notification.reblog": "{name} hat deinen Beitrag geteilt",
"notification.status": "{name} hat gerade etwas gepostet",
"notification.update": "{name} bearbeitete einen Beitrag",
@ -430,7 +431,7 @@
"report.forward": "An {target} weiterleiten",
"report.forward_hint": "Dieses Konto gehört zu einem anderen Server. Soll eine anonymisierte Kopie der Meldung auch dorthin geschickt werden?",
"report.mute": "Stummschalten",
"report.mute_explanation": "Du wirst die Beiträge vom Konto nicht mehr sehen. Das Konto kann dir immernoch folgen und die Person hinter dem Konto wird deine Beiträge sehen können und nicht wissen, dass du sie stumm geschaltet hast.",
"report.mute_explanation": "Du wirst die Beiträge vom Konto nicht mehr sehen. Das Konto kann dir immer noch folgen, und die Person hinter dem Konto wird deine Beiträge sehen können und nicht wissen, dass du sie stummgeschaltet hast.",
"report.next": "Weiter",
"report.placeholder": "Zusätzliche Kommentare",
"report.reasons.dislike": "Das gefällt mir nicht",
@ -447,7 +448,7 @@
"report.statuses.title": "Gibt es Beiträge, die diesen Bericht unterstützen?",
"report.submit": "Absenden",
"report.target": "{target} melden",
"report.thanks.take_action": "Das sind deine Möglichkeiten, zu bestimmen, was du auf Mastodon sehen möchtest:",
"report.thanks.take_action": "Das sind deine Möglichkeiten zu bestimmen, was du auf Mastodon sehen möchtest:",
"report.thanks.take_action_actionable": "Während wir dies überprüfen, kannst du gegen @{name} vorgehen:",
"report.thanks.title": "Möchtest du das nicht sehen?",
"report.thanks.title_actionable": "Vielen Dank für die Meldung, wir werden uns das ansehen.",
@ -460,7 +461,7 @@
"report_notification.open": "Meldung öffnen",
"search.placeholder": "Suche",
"search_popout.search_format": "Fortgeschrittenes Suchformat",
"search_popout.tips.full_text": "Einfache Texteingabe gibt Beiträge, die du geschrieben, favorisiert und geteilt hast zurück. Außerdem auch Beiträge in denen du erwähnt wurdest, aber auch passende Nutzernamen, Anzeigenamen oder Hashtags.",
"search_popout.tips.full_text": "Einfache Texteingabe gibt Beiträge, die du geschrieben, favorisiert und geteilt hast, zurück; außerdem auch Beiträge, in denen du erwähnt wurdest, aber auch passende Nutzernamen, Anzeigenamen oder Hashtags.",
"search_popout.tips.hashtag": "Hashtag",
"search_popout.tips.status": "Tröt",
"search_popout.tips.text": "Einfache Texteingabe gibt Anzeigenamen, Benutzernamen und Hashtags zurück",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Nichts für diese Suchbegriffe gefunden",
"search_results.statuses": "Beiträge",
"search_results.statuses_fts_disabled": "Die Suche für Beiträge nach ihrem Inhalt ist auf diesem Mastodon-Server deaktiviert.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Öffne Moderationsoberfläche für @{name}",
"status.admin_status": "Öffne Beitrag in der Moderationsoberfläche",
"status.block": "Blockiere @{name}",
@ -487,7 +492,7 @@
"status.edited_x_times": "{count, plural, one {{count} mal} other {{count} mal}} bearbeitet",
"status.embed": "Einbetten",
"status.favourite": "Favorisieren",
"status.filter": "Filter this post",
"status.filter": "Diesen Beitrag filtern",
"status.filtered": "Gefiltert",
"status.hide": "Tröt verbergen",
"status.history.created": "{name} erstellte {date}",
@ -518,10 +523,16 @@
"status.show_less_all": "Alle Inhaltswarnungen zuklappen",
"status.show_more": "Mehr anzeigen",
"status.show_more_all": "Alle Inhaltswarnungen aufklappen",
"status.show_original": "Original anzeigen",
"status.show_thread": "Zeige Konversation",
"status.translate": "Übersetzen",
"status.translated_from": "Aus {lang} übersetzt",
"status.uncached_media_warning": "Nicht verfügbar",
"status.unmute_conversation": "Stummschaltung von Konversation aufheben",
"status.unpin": "Vom Profil lösen",
"subscribed_languages.lead": "Nur Beiträge in ausgewählten Sprachen werden nach der Änderung auf deiner Startseite und den Listen angezeigt. Wähle keine aus, um Beiträge in allen Sprachen zu erhalten.",
"subscribed_languages.save": "Änderungen speichern",
"subscribed_languages.target": "Abonnierte Sprachen für {target} ändern",
"suggestions.dismiss": "Empfehlung ausblenden",
"suggestions.header": "Du bist vielleicht interessiert an…",
"tabs_bar.federated_timeline": "Föderation",

@ -653,6 +653,18 @@
"defaultMessage": "Read more",
"id": "status.read_more"
},
{
"defaultMessage": "Translated from {lang}",
"id": "status.translated_from"
},
{
"defaultMessage": "Show original",
"id": "status.show_original"
},
{
"defaultMessage": "Translate",
"id": "status.translate"
},
{
"defaultMessage": "Show more",
"id": "status.show_more"
@ -1030,6 +1042,10 @@
"defaultMessage": "Open moderation interface for @{name}",
"id": "status.admin_account"
},
{
"defaultMessage": "Change subscribed languages",
"id": "account.languages"
},
{
"defaultMessage": "Follows you",
"id": "account.follows_you"
@ -1826,6 +1842,19 @@
},
{
"descriptors": [
{
"defaultMessage": "Nothing is trending right now. Check back later!",
"id": "empty_column.explore_statuses"
}
],
"path": "app/javascript/mastodon/features/explore/links.json"
},
{
"descriptors": [
{
"defaultMessage": "Search for {q}",
"id": "search_results.title"
},
{
"defaultMessage": "Could not find anything for these search terms",
"id": "search_results.nothing_found"
@ -1858,6 +1887,24 @@
],
"path": "app/javascript/mastodon/features/explore/statuses.json"
},
{
"descriptors": [
{
"defaultMessage": "Nothing is trending right now. Check back later!",
"id": "empty_column.explore_statuses"
}
],
"path": "app/javascript/mastodon/features/explore/suggestions.json"
},
{
"descriptors": [
{
"defaultMessage": "Nothing is trending right now. Check back later!",
"id": "empty_column.explore_statuses"
}
],
"path": "app/javascript/mastodon/features/explore/tags.json"
},
{
"descriptors": [
{
@ -3018,6 +3065,27 @@
],
"path": "app/javascript/mastodon/features/report/comment.json"
},
{
"descriptors": [
{
"defaultMessage": "Public",
"id": "privacy.public.short"
},
{
"defaultMessage": "Unlisted",
"id": "privacy.unlisted.short"
},
{
"defaultMessage": "Followers-only",
"id": "privacy.private.short"
},
{
"defaultMessage": "Mentioned people only",
"id": "privacy.direct.short"
}
],
"path": "app/javascript/mastodon/features/report/components/status_check_box.json"
},
{
"descriptors": [
{
@ -3350,6 +3418,27 @@
],
"path": "app/javascript/mastodon/features/status/index.json"
},
{
"descriptors": [
{
"defaultMessage": "Close",
"id": "lightbox.close"
},
{
"defaultMessage": "Change subscribed languages for {target}",
"id": "subscribed_languages.target"
},
{
"defaultMessage": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"id": "subscribed_languages.lead"
},
{
"defaultMessage": "Save changes",
"id": "subscribed_languages.save"
}
],
"path": "app/javascript/mastodon/features/subscribed_languages_modal/index.json"
},
{
"descriptors": [
{
@ -3633,8 +3722,8 @@
"id": "navigation_bar.apps"
},
{
"defaultMessage": "Terms of service",
"id": "getting_started.terms"
"defaultMessage": "Privacy Policy",
"id": "getting_started.privacy_policy"
},
{
"defaultMessage": "Developers",
@ -3779,6 +3868,23 @@
],
"path": "app/javascript/mastodon/features/ui/components/report_modal.json"
},
{
"descriptors": [
{
"defaultMessage": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"id": "sign_in_banner.text"
},
{
"defaultMessage": "Sign in",
"id": "sign_in_banner.sign_in"
},
{
"defaultMessage": "Create account",
"id": "sign_in_banner.create_account"
}
],
"path": "app/javascript/mastodon/features/ui/components/sign_in_banner.json"
},
{
"descriptors": [
{

@ -24,6 +24,7 @@
"account.follows_you": "Σε ακολουθεί",
"account.hide_reblogs": "Απόκρυψη προωθήσεων από @{name}",
"account.joined": "Μέλος από τις {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "Η ιδιοκτησία αυτού του συνδέσμου ελέχθηκε την {date}",
"account.locked_info": "Η κατάσταση απορρήτου αυτού του λογαριασμού είναι κλειδωμένη. Ο ιδιοκτήτης επιβεβαιώνει χειροκίνητα ποιος μπορεί να τον ακολουθήσει.",
"account.media": "Πολυμέσα",
@ -226,8 +227,8 @@
"getting_started.heading": "Αφετηρία",
"getting_started.invite": "Προσκάλεσε κόσμο",
"getting_started.open_source_notice": "Το Mastodon είναι ελεύθερο λογισμικό. Μπορείς να συνεισφέρεις ή να αναφέρεις ζητήματα στο GitHub στο {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Ασφάλεια",
"getting_started.terms": "Όροι χρήσης",
"hashtag.column_header.tag_mode.all": "και {additional}",
"hashtag.column_header.tag_mode.any": "ή {additional}",
"hashtag.column_header.tag_mode.none": "χωρίς {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.statuses": "Τουτ",
"search_results.statuses_fts_disabled": "Η αναζήτηση τουτ βάσει του περιεχόμενού τους δεν είναι ενεργοποιημένη σε αυτό τον κόμβο.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, zero {αποτελέσματα} one {αποτέλεσμα} other {αποτελέσματα}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Άνοιγμα λειτουργίας διαμεσολάβησης για τον/την @{name}",
"status.admin_status": "Άνοιγμα αυτής της δημοσίευσης στη λειτουργία διαμεσολάβησης",
"status.block": "Αποκλεισμός @{name}",
@ -518,10 +523,16 @@
"status.show_less_all": "Δείξε λιγότερα για όλα",
"status.show_more": "Δείξε περισσότερα",
"status.show_more_all": "Δείξε περισσότερα για όλα",
"status.show_original": "Εμφάνιση αρχικού",
"status.show_thread": "Εμφάνιση νήματος",
"status.translate": "Μετάφραση",
"status.translated_from": "Μεταφράστηκε από {lang}",
"status.uncached_media_warning": "Μη διαθέσιμα",
"status.unmute_conversation": "Διέκοψε την αποσιώπηση της συζήτησης",
"status.unpin": "Ξεκαρφίτσωσε από το προφίλ",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Αποθήκευση αλλαγών",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Απόρριψη πρότασης",
"suggestions.header": "Ίσως να ενδιαφέρεσαι για…",
"tabs_bar.federated_timeline": "Ομοσπονδιακή",

@ -24,6 +24,7 @@
"account.follows_you": "Follows you",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.joined": "Joined {date}",
"account.languages": "Change subscribed languages",
"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",
@ -226,8 +227,8 @@
"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.privacy_policy": "Privacy Policy",
"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}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.statuses": "Posts",
"search_results.statuses_fts_disabled": "Searching posts by their content is not enabled on this Mastodon server.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_status": "Open this status in the moderation interface",
"status.block": "Block @{name}",
@ -518,10 +523,16 @@
"status.show_less_all": "Show less for all",
"status.show_more": "Show more",
"status.show_more_all": "Show more for all",
"status.show_original": "Show original",
"status.show_thread": "Show thread",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Not available",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Dismiss suggestion",
"suggestions.header": "You might be interested in…",
"tabs_bar.federated_timeline": "Federated",

@ -24,6 +24,7 @@
"account.follows_you": "Follows you",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.joined": "Joined {date}",
"account.languages": "Change subscribed languages",
"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",
@ -230,8 +231,8 @@
"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.privacy_policy": "Privacy Policy",
"getting_started.security": "Account settings",
"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}",
@ -476,7 +477,11 @@
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.statuses": "Posts",
"search_results.statuses_fts_disabled": "Searching posts by their content is not enabled on this Mastodon server.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Open moderation interface for @{name}",
"status.admin_status": "Open this post in the moderation interface",
"status.block": "Block @{name}",
@ -523,10 +528,16 @@
"status.show_less_all": "Show less for all",
"status.show_more": "Show more",
"status.show_more_all": "Show more for all",
"status.show_original": "Show original",
"status.show_thread": "Show thread",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Not available",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Dismiss suggestion",
"suggestions.header": "You might be interested in…",
"tabs_bar.federated_timeline": "Federated",

@ -24,6 +24,7 @@
"account.follows_you": "Sekvas vin",
"account.hide_reblogs": "Kaŝi la plusendojn de @{name}",
"account.joined": "Kuniĝis {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "La posedanto de tiu ligilo estis kontrolita je {date}",
"account.locked_info": "La privateco de tiu konto estas elektita kiel fermita. La posedanto povas mane akcepti tiun, kiu povas sekvi rin.",
"account.media": "Aŭdovidaĵoj",
@ -59,7 +60,7 @@
"alert.unexpected.title": "Aj!",
"announcement.announcement": "Anonco",
"attachments_list.unprocessed": "(neprilaborita)",
"audio.hide": "Hide audio",
"audio.hide": "Kaŝi aŭdion",
"autosuggest_hashtag.per_week": "{count} semajne",
"boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje",
"bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
@ -202,16 +203,16 @@
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.review_and_configure_title": "Filtrilopcioj",
"filter_modal.added.settings_link": "opciopaĝo",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "expired",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.expired": "eksvalidiĝinta",
"filter_modal.select_filter.prompt_new": "Nova klaso: {name}",
"filter_modal.select_filter.search": "Serĉi aŭ krei",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.select_filter.title": "Filtri ĉi afiŝo",
"filter_modal.title.status": "Filter a post",
"follow_recommendations.done": "Farita",
"follow_recommendations.heading": "Sekvi la personojn kies mesaĝojn vi volas vidi! Jen iom da sugestoj.",
@ -226,8 +227,8 @@
"getting_started.heading": "Por komenci",
"getting_started.invite": "Inviti homojn",
"getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Sekureco",
"getting_started.terms": "Kondiĉoj de la servo",
"hashtag.column_header.tag_mode.all": "kaj {additional}",
"hashtag.column_header.tag_mode.any": "aŭ {additional}",
"hashtag.column_header.tag_mode.none": "sen {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Povis trovi nenion por ĉi tiuj serĉaj terminoj",
"search_results.statuses": "Mesaĝoj",
"search_results.statuses_fts_disabled": "Serĉi mesaĝojn laŭ enhavo ne estas ebligita en ĉi tiu Mastodon-servilo.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Malfermi la kontrolan interfacon por @{name}",
"status.admin_status": "Malfermi ĉi tiun mesaĝon en la kontrola interfaco",
"status.block": "Bloki @{name}",
@ -487,7 +492,7 @@
"status.edited_x_times": "Redactita {count, plural, one {{count} fojon} other {{count} fojojn}}",
"status.embed": "Enkorpigi",
"status.favourite": "Aldoni al viaj preferaĵoj",
"status.filter": "Filter this post",
"status.filter": "Filtri ĉi afiŝo",
"status.filtered": "Filtrita",
"status.hide": "Kaŝi la mesaĝon",
"status.history.created": "{name} kreis {date}",
@ -518,10 +523,16 @@
"status.show_less_all": "Montri malpli ĉiun",
"status.show_more": "Montri pli",
"status.show_more_all": "Montri pli ĉiun",
"status.show_original": "Show original",
"status.show_thread": "Montri la mesaĝaron",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Nedisponebla",
"status.unmute_conversation": "Malsilentigi la konversacion",
"status.unpin": "Depingli de profilo",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Forigi la proponon",
"suggestions.header": "Vi povus interesiĝi pri…",
"tabs_bar.federated_timeline": "Fratara",

@ -24,6 +24,7 @@
"account.follows_you": "Te sigue",
"account.hide_reblogs": "Ocultar adhesiones de @{name}",
"account.joined": "En este servidor desde {date}",
"account.languages": "Cambiar idiomas suscritos",
"account.link_verified_on": "La propiedad de este enlace fue verificada el {date}",
"account.locked_info": "Esta cuenta es privada. El propietario manualmente revisa quién puede seguirle.",
"account.media": "Medios",
@ -197,22 +198,22 @@
"explore.trending_links": "Noticias",
"explore.trending_statuses": "Mensajes",
"explore.trending_tags": "Etiquetas",
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
"filter_modal.added.context_mismatch_title": "Context mismatch!",
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "expired",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que accediste a este mensaje. Si querés que el mensaje sea filtrado también en este contexto, vas a tener que editar el filtro.",
"filter_modal.added.context_mismatch_title": "¡El contexto no coincide!",
"filter_modal.added.expired_explanation": "Esta categoría de filtro caducó; vas a necesitar cambiar la fecha de caducidad para que se aplique.",
"filter_modal.added.expired_title": "¡Filtro caducado!",
"filter_modal.added.review_and_configure": "Para revisar y configurar esta categoría de filtros, visitá a la {settings_link}.",
"filter_modal.added.review_and_configure_title": "Configuración de filtro",
"filter_modal.added.settings_link": "página de configuración",
"filter_modal.added.short_explanation": "Este mensaje fue agregado a la siguiente categoría de filtros: {title}.",
"filter_modal.added.title": "¡Filtro agregado!",
"filter_modal.select_filter.context_mismatch": "no aplica a este contexto",
"filter_modal.select_filter.expired": "expirado",
"filter_modal.select_filter.prompt_new": "Nueva categoría: {name}",
"filter_modal.select_filter.search": "Buscar o crear",
"filter_modal.select_filter.subtitle": "Usar una categoría existente o crear una nueva",
"filter_modal.select_filter.title": "Filtrar este mensaje",
"filter_modal.title.status": "Filtrar un mensaje",
"follow_recommendations.done": "Listo",
"follow_recommendations.heading": "¡Seguí cuentas cuyos mensajes te gustaría ver! Acá tenés algunas sugerencias.",
"follow_recommendations.lead": "Los mensajes de las cuentas que seguís aparecerán en orden cronológico en la columna \"Inicio\". No tengás miedo de meter la pata, ¡podés dejar de seguir cuentas fácilmente en cualquier momento!",
@ -226,8 +227,8 @@
"getting_started.heading": "Introducción",
"getting_started.invite": "Invitar gente",
"getting_started.open_source_notice": "Mastodon es software libre. Podés contribuir o informar errores en {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Configuración de la cuenta",
"getting_started.terms": "Términos del servicio",
"hashtag.column_header.tag_mode.all": "y {additional}",
"hashtag.column_header.tag_mode.any": "o {additional}",
"hashtag.column_header.tag_mode.none": "sin {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "No se pudo encontrar nada para estos términos de búsqueda",
"search_results.statuses": "Mensajes",
"search_results.statuses_fts_disabled": "No se pueden buscar mensajes por contenido en este servidor de Mastodon.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Abrir interface de moderación para @{name}",
"status.admin_status": "Abrir este mensaje en la interface de moderación",
"status.block": "Bloquear a @{name}",
@ -487,7 +492,7 @@
"status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}",
"status.embed": "Insertar",
"status.favourite": "Marcar como favorito",
"status.filter": "Filter this post",
"status.filter": "Filtrar este mensaje",
"status.filtered": "Filtrado",
"status.hide": "Ocultar mensaje",
"status.history.created": "Creado por {name} el {date}",
@ -518,10 +523,16 @@
"status.show_less_all": "Mostrar menos para todo",
"status.show_more": "Mostrar más",
"status.show_more_all": "Mostrar más para todo",
"status.show_original": "Mostrar original",
"status.show_thread": "Mostrar hilo",
"status.translate": "Traducir",
"status.translated_from": "Traducido desde el {lang}",
"status.uncached_media_warning": "No disponible",
"status.unmute_conversation": "Dejar de silenciar conversación",
"status.unpin": "Dejar de fijar",
"subscribed_languages.lead": "Después del cambio, sólo los mensajes en los idiomas seleccionados aparecerán en tu línea temporal Principal y en las líneas de tiempo de lista. No seleccionés ningún idioma para poder recibir mensajes en todos los idiomas.",
"subscribed_languages.save": "Guardar cambios",
"subscribed_languages.target": "Cambiar idiomas suscritos para {target}",
"suggestions.dismiss": "Descartar sugerencia",
"suggestions.header": "Es posible que te interese…",
"tabs_bar.federated_timeline": "Federada",

@ -24,6 +24,7 @@
"account.follows_you": "Te sigue",
"account.hide_reblogs": "Ocultar retoots de @{name}",
"account.joined": "Se unió el {date}",
"account.languages": "Cambiar idiomas suscritos",
"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.media": "Multimedia",
@ -59,7 +60,7 @@
"alert.unexpected.title": "¡Ups!",
"announcement.announcement": "Anuncio",
"attachments_list.unprocessed": "(sin procesar)",
"audio.hide": "Hide audio",
"audio.hide": "Ocultar audio",
"autosuggest_hashtag.per_week": "{count} por semana",
"boost_modal.combo": "Puedes hacer clic en {combo} para saltar este aviso la próxima vez",
"bundle_column_error.body": "Algo salió mal al cargar este componente.",
@ -197,22 +198,22 @@
"explore.trending_links": "Noticias",
"explore.trending_statuses": "Publicaciones",
"explore.trending_tags": "Hashtags",
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
"filter_modal.added.context_mismatch_title": "Context mismatch!",
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "expired",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que ha accedido a esta publlicación. Si quieres que la publicación sea filtrada también en este contexto, tendrás que editar el filtro.",
"filter_modal.added.context_mismatch_title": "¡El contexto no coincide!",
"filter_modal.added.expired_explanation": "Esta categoría de filtro ha caducado, necesitará cambiar la fecha de caducidad para que se aplique.",
"filter_modal.added.expired_title": "¡Filtro caducado!",
"filter_modal.added.review_and_configure": "Para revisar y configurar esta categoría de filtros, vaya a {settings_link}.",
"filter_modal.added.review_and_configure_title": "Ajustes de filtro",
"filter_modal.added.settings_link": "página de ajustes",
"filter_modal.added.short_explanation": "Esta publicación ha sido añadida a la siguiente categoría de filtros: {title}.",
"filter_modal.added.title": "¡Filtro añadido!",
"filter_modal.select_filter.context_mismatch": "no se aplica a este contexto",
"filter_modal.select_filter.expired": "expirado",
"filter_modal.select_filter.prompt_new": "Nueva categoría: {name}",
"filter_modal.select_filter.search": "Buscar o crear",
"filter_modal.select_filter.subtitle": "Usar una categoría existente o crear una nueva",
"filter_modal.select_filter.title": "Filtrar esta publicación",
"filter_modal.title.status": "Filtrar una publicación",
"follow_recommendations.done": "Hecho",
"follow_recommendations.heading": "¡Sigue a gente que publique cosas que te gusten! Aquí tienes algunas sugerencias.",
"follow_recommendations.lead": "Las publicaciones de la gente a la que sigas aparecerán ordenadas cronológicamente en Inicio. No tengas miedo de cometer errores, ¡puedes dejarles de seguir en cualquier momento con la misma facilidad!",
@ -226,8 +227,8 @@
"getting_started.heading": "Primeros pasos",
"getting_started.invite": "Invitar usuarios",
"getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Seguridad",
"getting_started.terms": "Términos de servicio",
"hashtag.column_header.tag_mode.all": "y {additional}",
"hashtag.column_header.tag_mode.any": "o {additional}",
"hashtag.column_header.tag_mode.none": "sin {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "No se pudo encontrar nada para estos términos de busqueda",
"search_results.statuses": "Toots",
"search_results.statuses_fts_disabled": "Buscar toots por su contenido no está disponible en este servidor de Mastodon.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Abrir interfaz de moderación para @{name}",
"status.admin_status": "Abrir este estado en la interfaz de moderación",
"status.block": "Bloquear a @{name}",
@ -487,7 +492,7 @@
"status.edited_x_times": "Editado {count, plural, one {{count} time} other {{count} veces}}",
"status.embed": "Incrustado",
"status.favourite": "Favorito",
"status.filter": "Filter this post",
"status.filter": "Filtrar esta publicación",
"status.filtered": "Filtrado",
"status.hide": "Ocultar publicación",
"status.history.created": "{name} creó {date}",
@ -518,10 +523,16 @@
"status.show_less_all": "Mostrar menos para todo",
"status.show_more": "Mostrar más",
"status.show_more_all": "Mostrar más para todo",
"status.show_original": "Mostrar original",
"status.show_thread": "Mostrar hilo",
"status.translate": "Traducir",
"status.translated_from": "Traducido del {lang}",
"status.uncached_media_warning": "No disponible",
"status.unmute_conversation": "Dejar de silenciar conversación",
"status.unpin": "Dejar de fijar",
"subscribed_languages.lead": "Sólo los mensajes en los idiomas seleccionados aparecerán en su inicio y otras líneas de tiempo después del cambio. Seleccione ninguno para recibir mensajes en todos los idiomas.",
"subscribed_languages.save": "Guardar cambios",
"subscribed_languages.target": "Cambiar idiomas suscritos para {target}",
"suggestions.dismiss": "Descartar sugerencia",
"suggestions.header": "Es posible que te interese…",
"tabs_bar.federated_timeline": "Federado",

@ -24,6 +24,7 @@
"account.follows_you": "Te sigue",
"account.hide_reblogs": "Ocultar retoots de @{name}",
"account.joined": "Se unió el {date}",
"account.languages": "Cambiar idiomas suscritos",
"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.media": "Multimedia",
@ -197,22 +198,22 @@
"explore.trending_links": "Noticias",
"explore.trending_statuses": "Publicaciones",
"explore.trending_tags": "Etiquetas",
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
"filter_modal.added.context_mismatch_title": "Context mismatch!",
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "expired",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que ha accedido a esta publlicación. Si quieres que la publicación sea filtrada también en este contexto, tendrás que editar el filtro.",
"filter_modal.added.context_mismatch_title": "¡El contexto no coincide!",
"filter_modal.added.expired_explanation": "Esta categoría de filtro ha caducado, necesitará cambiar la fecha de caducidad para que se aplique.",
"filter_modal.added.expired_title": "¡Filtro caducado!",
"filter_modal.added.review_and_configure": "Para revisar y configurar esta categoría de filtros, vaya a {settings_link}.",
"filter_modal.added.review_and_configure_title": "Ajustes de filtro",
"filter_modal.added.settings_link": "página de ajustes",
"filter_modal.added.short_explanation": "Esta publicación ha sido añadida a la siguiente categoría de filtros: {title}.",
"filter_modal.added.title": "¡Filtro añadido!",
"filter_modal.select_filter.context_mismatch": "no se aplica a este contexto",
"filter_modal.select_filter.expired": "expirado",
"filter_modal.select_filter.prompt_new": "Nueva categoría: {name}",
"filter_modal.select_filter.search": "Buscar o crear",
"filter_modal.select_filter.subtitle": "Usar una categoría existente o crear una nueva",
"filter_modal.select_filter.title": "Filtrar esta publicación",
"filter_modal.title.status": "Filtrar una publicación",
"follow_recommendations.done": "Hecho",
"follow_recommendations.heading": "¡Sigue a gente que publique cosas que te gusten! Aquí tienes algunas sugerencias.",
"follow_recommendations.lead": "Las publicaciones de la gente a la que sigas aparecerán ordenadas cronológicamente en Inicio. No tengas miedo de cometer errores, ¡puedes dejarles de seguir en cualquier momento con la misma facilidad!",
@ -226,8 +227,8 @@
"getting_started.heading": "Primeros pasos",
"getting_started.invite": "Invitar usuarios",
"getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Seguridad",
"getting_started.terms": "Términos de servicio",
"hashtag.column_header.tag_mode.all": "y {additional}",
"hashtag.column_header.tag_mode.any": "o {additional}",
"hashtag.column_header.tag_mode.none": "sin {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "No se pudo encontrar nada para estos términos de búsqueda",
"search_results.statuses": "Publicaciones",
"search_results.statuses_fts_disabled": "Buscar publicaciones por su contenido no está disponible en este servidor de Mastodon.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Abrir interfaz de moderación para @{name}",
"status.admin_status": "Abrir este estado en la interfaz de moderación",
"status.block": "Bloquear a @{name}",
@ -487,7 +492,7 @@
"status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}",
"status.embed": "Incrustado",
"status.favourite": "Favorito",
"status.filter": "Filter this post",
"status.filter": "Filtrar esta publicación",
"status.filtered": "Filtrado",
"status.hide": "Ocultar publicación",
"status.history.created": "{name} creó {date}",
@ -518,10 +523,16 @@
"status.show_less_all": "Mostrar menos para todo",
"status.show_more": "Mostrar más",
"status.show_more_all": "Mostrar más para todo",
"status.show_original": "Mostrar original",
"status.show_thread": "Mostrar hilo",
"status.translate": "Traducir",
"status.translated_from": "Traducido del {lang}",
"status.uncached_media_warning": "No disponible",
"status.unmute_conversation": "Dejar de silenciar conversación",
"status.unpin": "Dejar de fijar",
"subscribed_languages.lead": "Sólo los mensajes en los idiomas seleccionados aparecerán en su inicio y otras líneas de tiempo después del cambio. Seleccione ninguno para recibir mensajes en todos los idiomas.",
"subscribed_languages.save": "Guardar cambios",
"subscribed_languages.target": "Cambiar idiomas suscritos para {target}",
"suggestions.dismiss": "Descartar sugerencia",
"suggestions.header": "Es posible que te interese…",
"tabs_bar.federated_timeline": "Federada",

@ -24,6 +24,7 @@
"account.follows_you": "Jälgib Teid",
"account.hide_reblogs": "Peida upitused kasutajalt @{name}",
"account.joined": "Liitus {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "Selle lingi autorsust kontrolliti {date}",
"account.locked_info": "Selle konto privaatsussätteks on lukustatud. Omanik vaatab manuaalselt üle, kes teda jägida saab.",
"account.media": "Meedia",
@ -226,8 +227,8 @@
"getting_started.heading": "Alustamine",
"getting_started.invite": "Kutsu inimesi",
"getting_started.open_source_notice": "Mastodon on avatud lähtekoodiga tarkvara. Saate panustada või teatada probleemidest GitHubis {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Turvalisus",
"getting_started.terms": "Kasutustingimused",
"hashtag.column_header.tag_mode.all": "ja {additional}",
"hashtag.column_header.tag_mode.any": "või {additional}",
"hashtag.column_header.tag_mode.none": "ilma {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.statuses": "Tuudid",
"search_results.statuses_fts_disabled": "Tuutsude otsimine nende sisu järgi ei ole sellel Mastodoni serveril sisse lülitatud.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {tulemus} other {tulemust}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Ava moderaatoriliides kasutajale @{name}",
"status.admin_status": "Ava see staatus moderaatoriliites",
"status.block": "Blokeeri @{name}",
@ -518,10 +523,16 @@
"status.show_less_all": "Näita vähem kõigile",
"status.show_more": "Näita veel",
"status.show_more_all": "Näita enam kõigile",
"status.show_original": "Show original",
"status.show_thread": "Kuva lõim",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Pole saadaval",
"status.unmute_conversation": "Ära vaigista vestlust",
"status.unpin": "Kinnita profiililt lahti",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Eira soovitust",
"suggestions.header": "Teid võib huvitada…",
"tabs_bar.federated_timeline": "Föderatiivne",

@ -24,6 +24,7 @@
"account.follows_you": "Jarraitzen dizu",
"account.hide_reblogs": "Ezkutatu @{name}(r)en bultzadak",
"account.joined": "{date}(e)an elkartua",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "Esteka honen jabetzaren egiaztaketa data: {date}",
"account.locked_info": "Kontu honen pribatutasun egoera blokeatuta gisa ezarri da. Jabeak eskuz erabakitzen du nork jarraitu diezaioken.",
"account.media": "Multimedia",
@ -226,8 +227,8 @@
"getting_started.heading": "Menua",
"getting_started.invite": "Gonbidatu jendea",
"getting_started.open_source_notice": "Mastodon software librea da. Ekarpenak egin ditzakezu edo akatsen berri eman GitHub bidez: {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Segurtasuna",
"getting_started.terms": "Erabilera baldintzak",
"hashtag.column_header.tag_mode.all": "eta {osagarria}",
"hashtag.column_header.tag_mode.any": "edo {osagarria}",
"hashtag.column_header.tag_mode.none": "gabe {osagarria}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Ez da emaitzarik aurkitu bilaketa-termino horientzat",
"search_results.statuses": "Bidalketak",
"search_results.statuses_fts_disabled": "Mastodon zerbitzari honek ez du bidalketen edukiaren bilaketa gaitu.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {emaitza} other {emaitza}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Ireki @{name} erabiltzailearen moderazio interfazea",
"status.admin_status": "Ireki bidalketa hau moderazio interfazean",
"status.block": "Blokeatu @{name}",
@ -518,10 +523,16 @@
"status.show_less_all": "Erakutsi denetarik gutxiago",
"status.show_more": "Erakutsi gehiago",
"status.show_more_all": "Erakutsi denetarik gehiago",
"status.show_original": "Show original",
"status.show_thread": "Erakutsi haria",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Ez eskuragarri",
"status.unmute_conversation": "Desmututu elkarrizketa",
"status.unpin": "Desfinkatu profiletik",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Errefusatu proposamena",
"suggestions.header": "Hau interesatu dakizuke…",
"tabs_bar.federated_timeline": "Federatua",

@ -24,6 +24,7 @@
"account.follows_you": "پی می‌گیردتان",
"account.hide_reblogs": "نهفتن تقویت‌های @{name}",
"account.joined": "پیوسته از {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "مالکیت این پیوند در {date} بررسی شد",
"account.locked_info": "این حساب خصوصی است. صاحبش تصمیم می‌گیرد که چه کسی پی‌گیرش باشد.",
"account.media": "رسانه",
@ -226,8 +227,8 @@
"getting_started.heading": "آغاز کنید",
"getting_started.invite": "دعوت از دیگران",
"getting_started.open_source_notice": "ماستودون نرم‌افزاری آزاد است. می‌توانید روی {github} در آن مشارکت کرده یا مشکلاتش را گزارش دهید.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "تنظیمات حساب",
"getting_started.terms": "شرایط خدمات",
"hashtag.column_header.tag_mode.all": "و {additional}",
"hashtag.column_header.tag_mode.any": "یا {additional}",
"hashtag.column_header.tag_mode.none": "بدون {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "چیزی برای این عبارت جست‌وجو یافت نشد",
"search_results.statuses": "فرسته‌ها",
"search_results.statuses_fts_disabled": "جست‌وجوی محتوای فرسته‌ها در این کارساز ماستودون به کار انداخته نشده است.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "گشودن واسط مدیریت برای @{name}",
"status.admin_status": "گشودن این فرسته در واسط مدیریت",
"status.block": "مسدود کردن @{name}",
@ -518,10 +523,16 @@
"status.show_less_all": "نمایش کمتر همه",
"status.show_more": "نمایش بیشتر",
"status.show_more_all": "نمایش بیشتر همه",
"status.show_original": "Show original",
"status.show_thread": "نمایش رشته",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "ناموجود",
"status.unmute_conversation": "رفع خموشی گفت‌وگو",
"status.unpin": "برداشتن سنجاق از نمایه",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "نادیده گرفتن پیشنهاد",
"suggestions.header": "شاید این هم برایتان جالب باشد…",
"tabs_bar.federated_timeline": "همگانی",

@ -24,6 +24,7 @@
"account.follows_you": "Seuraa sinua",
"account.hide_reblogs": "Piilota buustaukset käyttäjältä @{name}",
"account.joined": "Liittynyt {date}",
"account.languages": "Change subscribed languages",
"account.link_verified_on": "Tämän linkin omistaja tarkistettiin {date}",
"account.locked_info": "Tämän tilin yksityisyyden tila on asetettu lukituksi. Omistaja arvioi manuaalisesti, kuka voi seurata niitä.",
"account.media": "Media",
@ -197,22 +198,22 @@
"explore.trending_links": "Uutiset",
"explore.trending_statuses": "Viestit",
"explore.trending_tags": "Aihetunnisteet",
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
"filter_modal.added.context_mismatch_title": "Context mismatch!",
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
"filter_modal.added.expired_title": "Expired filter!",
"filter_modal.added.review_and_configure": "To review and further configure this filter category, go to the {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filter settings",
"filter_modal.added.settings_link": "settings page",
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
"filter_modal.added.title": "Filter added!",
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
"filter_modal.select_filter.expired": "expired",
"filter_modal.select_filter.prompt_new": "New category: {name}",
"filter_modal.select_filter.search": "Search or create",
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
"filter_modal.select_filter.title": "Filter this post",
"filter_modal.title.status": "Filter a post",
"filter_modal.added.context_mismatch_explanation": "Tämä suodatinluokka ei koske asiayhteyttä, jossa olet käyttänyt tätä viestiä. Jos haluat, että viesti suodatetaan myös tässä yhteydessä, sinun on muokattava suodatinta.",
"filter_modal.added.context_mismatch_title": "Asiayhteys ei täsmää!",
"filter_modal.added.expired_explanation": "Tämä suodatinluokka on vanhentunut ja sinun on muutettava viimeistä voimassaolon päivää, jotta sitä voidaan käyttää.",
"filter_modal.added.expired_title": "Vanhentunut suodatin!",
"filter_modal.added.review_and_configure": "Voit tarkastella tätä suodatinluokkaa ja määrittää sen tarkemmin siirtymällä {settings_link}.",
"filter_modal.added.review_and_configure_title": "Suodattimen asetukset",
"filter_modal.added.settings_link": "asetukset sivu",
"filter_modal.added.short_explanation": "Tämä viesti on lisätty seuraavaan suodatinluokkaan: {title}.",
"filter_modal.added.title": "Suodatin lisätty!",
"filter_modal.select_filter.context_mismatch": "ei sovellu tähän asiayhteyteen",
"filter_modal.select_filter.expired": "vanhentunut",
"filter_modal.select_filter.prompt_new": "Uusi luokka: {name}",
"filter_modal.select_filter.search": "Etsi tai luo",
"filter_modal.select_filter.subtitle": "Käytä olemassa olevaa luokkaa tai luo uusi luokka",
"filter_modal.select_filter.title": "Suodata tämä viesti",
"filter_modal.title.status": "Suodata viesti",
"follow_recommendations.done": "Valmis",
"follow_recommendations.heading": "Seuraa ihmisiä, joilta haluaisit nähdä julkaisuja! Tässä on muutamia ehdotuksia.",
"follow_recommendations.lead": "Seuraamiesi julkaisut näkyvät aikajärjestyksessä kotisyötteessä. Älä pelkää seurata vahingossa, voit lopettaa seuraamisen yhtä helposti!",
@ -226,8 +227,8 @@
"getting_started.heading": "Näin pääset alkuun",
"getting_started.invite": "Kutsu ihmisiä",
"getting_started.open_source_notice": "Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHubissa: {github}.",
"getting_started.privacy_policy": "Privacy Policy",
"getting_started.security": "Tiliasetukset",
"getting_started.terms": "Käyttöehdot",
"hashtag.column_header.tag_mode.all": "ja {additional}",
"hashtag.column_header.tag_mode.any": "tai {additional}",
"hashtag.column_header.tag_mode.none": "ilman {additional}",
@ -471,7 +472,11 @@
"search_results.nothing_found": "Näille hakusanoille ei löytynyt mitään",
"search_results.statuses": "Viestit",
"search_results.statuses_fts_disabled": "Viestien haku sisällön perusteella ei ole käytössä tällä Mastodon-palvelimella.",
"search_results.title": "Search for {q}",
"search_results.total": "{count, number} {count, plural, one {tulos} other {tulokset}}",
"sign_in_banner.create_account": "Create account",
"sign_in_banner.sign_in": "Sign in",
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts, or interact from your account on a different server.",
"status.admin_account": "Avaa moderaattorinäkymä tilistä @{name}",
"status.admin_status": "Avaa julkaisu moderointinäkymässä",
"status.block": "Estä @{name}",
@ -487,7 +492,7 @@
"status.edited_x_times": "Muokattu {count, plural, one {{count} aika} other {{count} kertaa}}",
"status.embed": "Upota",
"status.favourite": "Tykkää",
"status.filter": "Filter this post",
"status.filter": "Suodata tämä viesti",
"status.filtered": "Suodatettu",
"status.hide": "Piilota toot",
"status.history.created": "{name} luotu {date}",
@ -518,10 +523,16 @@
"status.show_less_all": "Näytä vähemmän kaikista",
"status.show_more": "Näytä lisää",
"status.show_more_all": "Näytä lisää kaikista",
"status.show_original": "Show original",
"status.show_thread": "Näytä ketju",
"status.translate": "Translate",
"status.translated_from": "Translated from {lang}",
"status.uncached_media_warning": "Ei saatavilla",
"status.unmute_conversation": "Poista keskustelun mykistys",
"status.unpin": "Irrota profiilista",
"subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
"subscribed_languages.save": "Save changes",
"subscribed_languages.target": "Change subscribed languages for {target}",
"suggestions.dismiss": "Hylkää ehdotus",
"suggestions.header": "Saatat olla kiinnostunut myös…",
"tabs_bar.federated_timeline": "Yleinen",

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

Loading…
Cancel
Save