Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Dalite 2024-05-21 11:19:43 +02:00
commit eae50b2cb3
323 changed files with 5604 additions and 1568 deletions
.env.test
.github
.nvmrc.rubocop.yml.rubocop_todo.yml.simplecovGemfileGemfile.lockREADME.md
app
controllers
helpers
javascript

View file

@ -4,7 +4,8 @@ NODE_ENV=production
LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true
# Required by ActiveRecord encryption feature
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr
# Secret values required by ActiveRecord encryption feature
# Use `bin/rails db:encryption:init` to generate fresh secrets
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=test_determinist_key_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=test_salt_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=test_primary_key_DO_NOT_USE_IN_PRODUCTION

View file

@ -141,6 +141,13 @@
matchUpdateTypes: ['patch', 'minor'],
groupName: 'RSpec (non-major)',
},
{
// Group all opentelemetry-ruby packages in the same PR
matchManagers: ['bundler'],
matchPackagePrefixes: ['opentelemetry-'],
matchUpdateTypes: ['patch', 'minor'],
groupName: 'opentelemetry-ruby (non-major)',
},
// Add labels depending on package manager
{ matchManagers: ['npm', 'nvm'], addLabels: ['javascript'] },
{ matchManagers: ['bundler', 'ruby-version'], addLabels: ['ruby'] },

View file

@ -1,21 +0,0 @@
{
"problemMatcher": [
{
"owner": "stylelint",
"pattern": [
{
"regexp": "^([^\\s].*)$",
"file": 1
},
{
"regexp": "^\\s+((\\d+):(\\d+))?\\s+(✖|×)\\s+(.*)\\s{2,}(.*)$",
"line": 2,
"column": 3,
"message": 5,
"code": 6,
"loop": true
}
]
}
]
}

View file

@ -38,9 +38,5 @@ jobs:
- name: Set up Javascript environment
uses: ./.github/actions/setup-javascript
- uses: xt0rted/stylelint-problem-matcher@v1
- run: echo "::add-matcher::.github/stylelint-matcher.json"
- name: Stylelint
run: yarn lint:css
run: yarn lint:css -f github

View file

@ -145,6 +145,8 @@ jobs:
uses: codecov/codecov-action@v4
with:
files: coverage/lcov/mastodon.lcov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-e2e:
name: End to End testing
@ -184,6 +186,8 @@ jobs:
DISABLE_SIMPLECOV: true
RAILS_ENV: test
BUNDLE_WITH: test
LOCAL_DOMAIN: localhost:3000
LOCAL_HTTPS: false
strategy:
fail-fast: false
@ -213,7 +217,7 @@ jobs:
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
- run: bundle exec rake spec:system
- run: bin/rspec spec/system --tag streaming --tag js
- name: Archive logs
uses: actions/upload-artifact@v4
@ -260,8 +264,8 @@ jobs:
ports:
- 6379:6379
search:
image: ${{ matrix.search-image }}
elasticsearch:
image: ${{ contains(matrix.search-image, 'elasticsearch') && matrix.search-image || '' }}
env:
discovery.type: single-node
xpack.security.enabled: false
@ -273,6 +277,20 @@ jobs:
ports:
- 9200:9200
opensearch:
image: ${{ contains(matrix.search-image, 'opensearch') && matrix.search-image || '' }}
env:
discovery.type: single-node
DISABLE_INSTALL_DEMO_CONFIG: true
DISABLE_SECURITY_PLUGIN: true
options: >-
--health-cmd "curl http://localhost:9200/_cluster/health"
--health-interval 10s
--health-timeout 5s
--health-retries 10
ports:
- 9200:9200
env:
DB_HOST: localhost
DB_USER: postgres
@ -296,6 +314,8 @@ jobs:
include:
- ruby-version: '.ruby-version'
search-image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2
- ruby-version: '.ruby-version'
search-image: opensearchproject/opensearch:2
steps:
- uses: actions/checkout@v4

2
.nvmrc
View file

@ -1 +1 @@
20.12
20.13

View file

@ -211,6 +211,11 @@ Style/PercentLiteralDelimiters:
Style/RedundantBegin:
Enabled: false
# Reason: Prevailing style choice
# https://docs.rubocop.org/rubocop/cops_style.html#styleredundantfetchblock
Style/RedundantFetchBlock:
Enabled: false
# Reason: Overridden to reduce implicit StandardError rescues
# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror
Style/RescueStandardError:

View file

@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
# using RuboCop version 1.62.1.
# using RuboCop version 1.63.5.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@ -42,14 +42,6 @@ RSpec/MultipleMemoizedHelpers:
RSpec/NestedGroups:
Max: 6
# Configuration parameters: Include.
# Include: app/models/**/*.rb
Rails/HasAndBelongsToMany:
Exclude:
- 'app/models/concerns/account/associations.rb'
- 'app/models/status.rb'
- 'app/models/tag.rb'
Rails/OutputSafety:
Exclude:
- 'config/initializers/simple_form.rb'
@ -62,10 +54,6 @@ Style/ClassEqualityComparison:
- 'app/helpers/jsonld_helper.rb'
- 'app/serializers/activitypub/outbox_serializer.rb'
Style/ClassVars:
Exclude:
- 'config/initializers/devise.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowedVars.
Style/FetchEnvVar:
@ -82,7 +70,7 @@ Style/FetchEnvVar:
- 'config/initializers/vapid.rb'
- 'lib/mastodon/redis_config.rb'
- 'lib/tasks/repo.rake'
- 'spec/features/profile_spec.rb'
- 'spec/system/profile_spec.rb'
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns.
@ -134,13 +122,6 @@ Style/HashTransformValues:
- 'app/serializers/rest/web_push_subscription_serializer.rb'
- 'app/services/import_service.rb'
# This cop supports safe autocorrection (--autocorrect).
Style/IfUnlessModifier:
Exclude:
- 'config/environments/production.rb'
- 'config/initializers/devise.rb'
- 'config/initializers/ffmpeg.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/MapToHash:
Exclude:
@ -188,16 +169,6 @@ Style/RedundantConstantBase:
- 'config/environments/production.rb'
- 'config/initializers/sidekiq.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: SafeForConstants.
Style/RedundantFetchBlock:
Exclude:
- 'config/initializers/1_hosts.rb'
- 'config/initializers/chewy.rb'
- 'config/initializers/devise.rb'
- 'config/initializers/paperclip.rb'
- 'config/puma.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength.
# AllowedMethods: present?, blank?, presence, try, try!

View file

@ -1,22 +0,0 @@
# frozen_string_literal: true
if ENV['CI']
require 'simplecov-lcov'
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
else
SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
end
SimpleCov.start 'rails' do
enable_coverage :branch
add_filter 'lib/linter'
add_group 'Libraries', 'lib'
add_group 'Policies', 'app/policies'
add_group 'Presenters', 'app/presenters'
add_group 'Serializers', 'app/serializers'
add_group 'Services', 'app/services'
add_group 'Validators', 'app/validators'
end

26
Gemfile
View file

@ -31,7 +31,7 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'chewy', '~> 7.3'
gem 'devise', '~> 4.9'
gem 'devise-two-factor', '~> 4.1'
gem 'devise-two-factor'
group :pam_authentication, optional: true do
gem 'devise_pam_authenticatable2', '~> 9.2'
@ -57,7 +57,7 @@ gem 'htmlentities', '~> 4.3'
gem 'http', '~> 5.2.0'
gem 'http_accept_language', '~> 2.1'
gem 'httplog', '~> 1.6.2'
gem 'i18n', '1.14.1' # TODO: Remove version when resolved: https://github.com/glebm/i18n-tasks/issues/552 / https://github.com/ruby-i18n/i18n/pull/688
gem 'i18n'
gem 'idn-ruby', require: 'idn'
gem 'inline_svg'
gem 'kaminari', '~> 1.2'
@ -103,6 +103,24 @@ gem 'rdf-normalize', '~> 0.5'
gem 'private_address_check', '~> 0.5'
group :opentelemetry do
gem 'opentelemetry-exporter-otlp', '~> 0.26.3', require: false
gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.20.1', require: false
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false
gem 'opentelemetry-instrumentation-excon', '~> 0.22.0', require: false
gem 'opentelemetry-instrumentation-faraday', '~> 0.24.1', require: false
gem 'opentelemetry-instrumentation-http', '~> 0.23.2', require: false
gem 'opentelemetry-instrumentation-http_client', '~> 0.22.3', require: false
gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false
gem 'opentelemetry-instrumentation-pg', '~> 0.27.1', require: false
gem 'opentelemetry-instrumentation-rack', '~> 0.24.1', require: false
gem 'opentelemetry-instrumentation-rails', '~> 0.30.0', require: false
gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false
gem 'opentelemetry-sdk', '~> 1.4', require: false
end
group :test do
# Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab
gem 'rspec-github', '~> 2.4', require: false
@ -114,7 +132,7 @@ group :test do
gem 'email_spec'
# Extra RSpec extension methods and helpers for sidekiq
gem 'rspec-sidekiq', '~> 4.0'
gem 'rspec-sidekiq', '~> 5.0'
# Browser integration testing
gem 'capybara', '~> 3.39'
@ -160,7 +178,7 @@ group :development do
# Preview mail in the browser
gem 'letter_opener', '~> 1.8'
gem 'letter_opener_web', '~> 2.0'
gem 'letter_opener_web', '~> 3.0'
# Security analysis CLI tools
gem 'brakeman', '~> 6.0', require: false

View file

@ -10,35 +10,35 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.3.2)
actionpack (= 7.1.3.2)
activesupport (= 7.1.3.2)
actioncable (7.1.3.3)
actionpack (= 7.1.3.3)
activesupport (= 7.1.3.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.3.2)
actionpack (= 7.1.3.2)
activejob (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionmailbox (7.1.3.3)
actionpack (= 7.1.3.3)
activejob (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.3.2)
actionpack (= 7.1.3.2)
actionview (= 7.1.3.2)
activejob (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionmailer (7.1.3.3)
actionpack (= 7.1.3.3)
actionview (= 7.1.3.3)
activejob (= 7.1.3.3)
activesupport (= 7.1.3.3)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.2)
actionpack (7.1.3.2)
actionview (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionpack (7.1.3.3)
actionview (= 7.1.3.3)
activesupport (= 7.1.3.3)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
@ -46,15 +46,15 @@ GEM
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.2)
actionpack (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
actiontext (7.1.3.3)
actionpack (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.3.2)
activesupport (= 7.1.3.2)
actionview (7.1.3.3)
activesupport (= 7.1.3.3)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
@ -64,22 +64,22 @@ GEM
activemodel (>= 4.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.3.2)
activesupport (= 7.1.3.2)
activejob (7.1.3.3)
activesupport (= 7.1.3.3)
globalid (>= 0.3.6)
activemodel (7.1.3.2)
activesupport (= 7.1.3.2)
activerecord (7.1.3.2)
activemodel (= 7.1.3.2)
activesupport (= 7.1.3.2)
activemodel (7.1.3.3)
activesupport (= 7.1.3.3)
activerecord (7.1.3.3)
activemodel (= 7.1.3.3)
activesupport (= 7.1.3.3)
timeout (>= 0.4.0)
activestorage (7.1.3.2)
actionpack (= 7.1.3.2)
activejob (= 7.1.3.2)
activerecord (= 7.1.3.2)
activesupport (= 7.1.3.2)
activestorage (7.1.3.3)
actionpack (= 7.1.3.3)
activejob (= 7.1.3.3)
activerecord (= 7.1.3.3)
activesupport (= 7.1.3.3)
marcel (~> 1.0)
activesupport (7.1.3.2)
activesupport (7.1.3.3)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
@ -97,22 +97,20 @@ GEM
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
attr_encrypted (4.0.0)
encryptor (~> 3.0.0)
attr_required (1.0.2)
awrence (1.2.1)
aws-eventstream (1.3.0)
aws-partitions (1.920.0)
aws-sdk-core (3.193.0)
aws-partitions (1.929.0)
aws-sdk-core (3.196.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.80.0)
aws-sdk-kms (1.81.0)
aws-sdk-core (~> 3, >= 3.193.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.148.0)
aws-sdk-core (~> 3, >= 3.193.0)
aws-sdk-s3 (1.151.0)
aws-sdk-core (~> 3, >= 3.194.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
@ -132,14 +130,7 @@ GEM
erubi (>= 1.0.0)
rack (>= 0.9.0)
rouge (>= 1.0.0)
better_html (2.1.1)
actionview (>= 6.0)
activesupport (>= 6.0)
ast (~> 2.0)
erubi (~> 1.4)
parser (>= 2.4)
smart_properties
bigdecimal (3.1.7)
bigdecimal (3.1.8)
bindata (2.5.0)
binding_of_caller (1.0.1)
debug_inspector (>= 1.2.0)
@ -169,9 +160,9 @@ GEM
activesupport
cbor (0.5.9.8)
charlock_holmes (0.7.7)
chewy (7.5.1)
chewy (7.6.0)
activesupport (>= 5.2)
elasticsearch (>= 7.12.0, < 7.14.0)
elasticsearch (>= 7.14.0, < 8)
elasticsearch-dsl
chunky_png (1.4.0)
climate_control (1.2.0)
@ -204,9 +195,8 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-two-factor (4.1.1)
devise-two-factor (5.0.0)
activesupport (~> 7.0)
attr_encrypted (>= 1.3, < 5, != 2)
devise (~> 4.0)
railties (~> 7.0)
rotp (~> 6.0)
@ -220,23 +210,22 @@ GEM
domain_name (0.6.20240107)
doorkeeper (5.6.9)
railties (>= 5)
dotenv (3.1.0)
dotenv (3.1.2)
drb (2.2.1)
ed25519 (1.3.0)
elasticsearch (7.13.3)
elasticsearch-api (= 7.13.3)
elasticsearch-transport (= 7.13.3)
elasticsearch-api (7.13.3)
elasticsearch (7.17.10)
elasticsearch-api (= 7.17.10)
elasticsearch-transport (= 7.17.10)
elasticsearch-api (7.17.10)
multi_json
elasticsearch-dsl (0.1.10)
elasticsearch-transport (7.13.3)
faraday (~> 1)
elasticsearch-transport (7.17.10)
faraday (>= 1, < 3)
multi_json
email_spec (2.2.2)
htmlentities (~> 4.3.3)
launchy (~> 2.1)
mail (~> 2.7)
encryptor (3.0.0)
erubi (1.12.0)
et-orbi (1.2.11)
tzinfo
@ -283,7 +272,7 @@ GEM
fog-json (1.2.0)
fog-core
multi_json (~> 1.10)
fog-openstack (1.1.0)
fog-openstack (1.1.1)
fog-core (~> 2.1)
fog-json (>= 1.0)
formatador (1.1.0)
@ -295,6 +284,9 @@ GEM
ruby-progressbar (~> 1.4)
globalid (1.2.1)
activesupport (>= 6.1)
google-protobuf (3.25.3)
googleapis-common-protos-types (1.14.0)
google-protobuf (~> 3.18)
haml (6.3.0)
temple (>= 0.8.2)
thor
@ -332,12 +324,11 @@ GEM
httplog (1.6.3)
rack (>= 2.0)
rainbow (>= 2.0.0)
i18n (1.14.1)
i18n (1.14.5)
concurrent-ruby (~> 1.0)
i18n-tasks (1.0.13)
i18n-tasks (1.0.14)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
better_html (>= 1.0, < 3.0)
erubi
highline (>= 2.0.0)
i18n
@ -350,8 +341,8 @@ GEM
activesupport (>= 3.0)
nokogiri (>= 1.6)
io-console (0.7.2)
irb (1.12.0)
rdoc
irb (1.13.1)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jmespath (1.6.2)
json (2.7.2)
@ -398,10 +389,10 @@ GEM
addressable (~> 2.8)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
letter_opener_web (2.0.0)
actionmailer (>= 5.2)
letter_opener (~> 1.7)
railties (>= 5.2)
letter_opener_web (3.0.0)
actionmailer (>= 6.1)
letter_opener (~> 1.9)
railties (>= 6.1)
rexml
link_header (0.0.8)
llhttp-ffi (0.5.0)
@ -431,10 +422,10 @@ GEM
memory_profiler (1.0.1)
mime-types (3.5.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2024.0305)
mime-types-data (3.2024.0507)
mini_mime (1.1.5)
mini_portile2 (2.8.6)
minitest (5.22.3)
minitest (5.23.0)
msgpack (1.7.2)
multi_json (1.15.0)
multipart-post (2.4.0)
@ -443,7 +434,7 @@ GEM
uri
net-http-persistent (4.0.2)
connection_pool (~> 2.2)
net-imap (0.4.10)
net-imap (0.4.11)
date
net-protocol
net-ldap (0.19.0)
@ -453,8 +444,8 @@ GEM
timeout
net-smtp (0.5.0)
net-protocol
nio4r (2.7.1)
nokogiri (1.16.4)
nio4r (2.7.3)
nokogiri (1.16.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nsa (0.3.0)
@ -495,6 +486,96 @@ GEM
openssl (3.2.0)
openssl-signature_algorithm (1.3.0)
openssl (> 2.0)
opentelemetry-api (1.2.5)
opentelemetry-common (0.20.1)
opentelemetry-api (~> 1.0)
opentelemetry-exporter-otlp (0.26.3)
google-protobuf (~> 3.14)
googleapis-common-protos-types (~> 1.3)
opentelemetry-api (~> 1.1)
opentelemetry-common (~> 0.20)
opentelemetry-sdk (~> 1.2)
opentelemetry-semantic_conventions
opentelemetry-helpers-sql-obfuscation (0.1.0)
opentelemetry-common (~> 0.20)
opentelemetry-instrumentation-action_pack (0.9.0)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-rack (~> 0.21)
opentelemetry-instrumentation-action_view (0.7.0)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-active_support (~> 0.1)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-active_job (0.7.1)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-active_model_serializers (0.20.1)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-active_record (0.7.2)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-active_support (0.5.1)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-base (0.22.3)
opentelemetry-api (~> 1.0)
opentelemetry-registry (~> 0.1)
opentelemetry-instrumentation-concurrent_ruby (0.21.3)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-excon (0.22.1)
opentelemetry-api (~> 1.0)
opentelemetry-common (~> 0.20.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-faraday (0.24.2)
opentelemetry-api (~> 1.0)
opentelemetry-common (~> 0.20.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-http (0.23.3)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-http_client (0.22.4)
opentelemetry-api (~> 1.0)
opentelemetry-common (~> 0.20.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-net_http (0.22.4)
opentelemetry-api (~> 1.0)
opentelemetry-common (~> 0.20.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-pg (0.27.3)
opentelemetry-api (~> 1.0)
opentelemetry-helpers-sql-obfuscation
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-rack (0.24.3)
opentelemetry-api (~> 1.0)
opentelemetry-common (~> 0.20.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-rails (0.30.1)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-action_pack (~> 0.9.0)
opentelemetry-instrumentation-action_view (~> 0.7.0)
opentelemetry-instrumentation-active_job (~> 0.7.0)
opentelemetry-instrumentation-active_record (~> 0.7.0)
opentelemetry-instrumentation-active_support (~> 0.5.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-redis (0.25.4)
opentelemetry-api (~> 1.0)
opentelemetry-common (~> 0.20.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-sidekiq (0.25.3)
opentelemetry-api (~> 1.0)
opentelemetry-common (~> 0.20.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-registry (0.3.1)
opentelemetry-api (~> 1.1)
opentelemetry-sdk (1.4.1)
opentelemetry-api (~> 1.1)
opentelemetry-common (~> 0.20)
opentelemetry-registry (~> 0.2)
opentelemetry-semantic_conventions
opentelemetry-semantic_conventions (1.10.0)
opentelemetry-api (~> 1.0)
orm_adapter (0.5.0)
ox (2.14.18)
parallel (1.24.0)
@ -526,10 +607,10 @@ GEM
public_suffix (5.0.5)
puma (6.4.2)
nio4r (~> 2.0)
pundit (2.3.1)
pundit (2.3.2)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.7.3)
racc (1.8.0)
rack (2.2.9)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
@ -553,20 +634,20 @@ GEM
rackup (1.0.0)
rack (< 3)
webrick
rails (7.1.3.2)
actioncable (= 7.1.3.2)
actionmailbox (= 7.1.3.2)
actionmailer (= 7.1.3.2)
actionpack (= 7.1.3.2)
actiontext (= 7.1.3.2)
actionview (= 7.1.3.2)
activejob (= 7.1.3.2)
activemodel (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
rails (7.1.3.3)
actioncable (= 7.1.3.3)
actionmailbox (= 7.1.3.3)
actionmailer (= 7.1.3.3)
actionpack (= 7.1.3.3)
actiontext (= 7.1.3.3)
actionview (= 7.1.3.3)
activejob (= 7.1.3.3)
activemodel (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
bundler (>= 1.15.0)
railties (= 7.1.3.2)
railties (= 7.1.3.3)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@ -581,9 +662,9 @@ GEM
rails-i18n (7.0.9)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (7.1.3.2)
actionpack (= 7.1.3.2)
activesupport (= 7.1.3.2)
railties (7.1.3.3)
actionpack (= 7.1.3.3)
activesupport (= 7.1.3.3)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
@ -604,15 +685,16 @@ GEM
redis (>= 4)
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
regexp_parser (2.9.0)
reline (0.5.2)
regexp_parser (2.9.2)
reline (0.5.7)
io-console (~> 0.5)
request_store (1.6.0)
rack (>= 1.4)
responders (3.1.1)
actionpack (>= 5.2)
railties (>= 5.2)
rexml (3.2.6)
rexml (3.2.8)
strscan (>= 3.0.9)
rotp (6.3.0)
rouge (4.2.1)
rpam2 (4.0.2)
@ -627,7 +709,7 @@ GEM
rspec-support (~> 3.13.0)
rspec-github (2.4.0)
rspec-core (~> 3.0)
rspec-mocks (3.13.0)
rspec-mocks (3.13.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (6.1.2)
@ -638,13 +720,13 @@ GEM
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-sidekiq (4.2.0)
rspec-sidekiq (5.0.0)
rspec-core (~> 3.0)
rspec-expectations (~> 3.0)
rspec-mocks (~> 3.0)
sidekiq (>= 5, < 8)
rspec-support (3.13.1)
rubocop (1.63.4)
rubocop (1.63.5)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
@ -655,8 +737,8 @@ GEM
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.2)
parser (>= 3.3.0.4)
rubocop-ast (1.31.3)
parser (>= 3.3.1.0)
rubocop-capybara (2.20.0)
rubocop (~> 1.41)
rubocop-factory_bot (2.25.1)
@ -664,12 +746,12 @@ GEM
rubocop-performance (1.21.0)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.24.1)
rubocop-rails (2.25.0)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (2.29.1)
rubocop-rspec (2.29.2)
rubocop (~> 1.40)
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
@ -693,7 +775,7 @@ GEM
scenic (1.8.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
selenium-webdriver (4.20.1)
selenium-webdriver (4.21.1)
base64 (~> 0.2)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
@ -727,7 +809,6 @@ GEM
simplecov-html (0.12.3)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
smart_properties (1.17.0)
stackprof (0.2.26)
statsd-ruby (1.5.0)
stoplight (4.1.0)
@ -735,6 +816,7 @@ GEM
stringio (3.1.0)
strong_migrations (1.8.0)
activerecord (>= 5.2)
strscan (3.1.0)
swd (1.3.0)
activesupport (>= 3)
attr_required (>= 0.0.5)
@ -813,7 +895,7 @@ GEM
xorcist (1.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.13)
zeitwerk (2.6.14)
PLATFORMS
ruby
@ -842,7 +924,7 @@ DEPENDENCIES
database_cleaner-active_record
debug (~> 1.8)
devise (~> 4.9)
devise-two-factor (~> 4.1)
devise-two-factor
devise_pam_authenticatable2 (~> 9.2)
discard (~> 1.2)
doorkeeper (~> 5.6)
@ -864,7 +946,7 @@ DEPENDENCIES
http (~> 5.2.0)
http_accept_language (~> 2.1)
httplog (~> 1.6.2)
i18n (= 1.14.1)
i18n
i18n-tasks (~> 1.0)
idn-ruby
inline_svg
@ -875,7 +957,7 @@ DEPENDENCIES
kaminari (~> 1.2)
kt-paperclip (~> 7.2)
letter_opener (~> 1.8)
letter_opener_web (~> 2.0)
letter_opener_web (~> 3.0)
link_header (~> 0.0)
lograge (~> 0.12)
mail (~> 2.8)
@ -893,6 +975,21 @@ DEPENDENCIES
omniauth-rails_csrf_protection (~> 1.0)
omniauth-saml (~> 2.0)
omniauth_openid_connect (~> 0.6.1)
opentelemetry-exporter-otlp (~> 0.26.3)
opentelemetry-instrumentation-active_job (~> 0.7.1)
opentelemetry-instrumentation-active_model_serializers (~> 0.20.1)
opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2)
opentelemetry-instrumentation-excon (~> 0.22.0)
opentelemetry-instrumentation-faraday (~> 0.24.1)
opentelemetry-instrumentation-http (~> 0.23.2)
opentelemetry-instrumentation-http_client (~> 0.22.3)
opentelemetry-instrumentation-net_http (~> 0.22.4)
opentelemetry-instrumentation-pg (~> 0.27.1)
opentelemetry-instrumentation-rack (~> 0.24.1)
opentelemetry-instrumentation-rails (~> 0.30.0)
opentelemetry-instrumentation-redis (~> 0.25.3)
opentelemetry-instrumentation-sidekiq (~> 0.25.2)
opentelemetry-sdk (~> 1.4)
ox (~> 2.14)
parslet
pg (~> 1.5)
@ -917,7 +1014,7 @@ DEPENDENCIES
rqrcode (~> 2.2)
rspec-github (~> 2.4)
rspec-rails (~> 6.0)
rspec-sidekiq (~> 4.0)
rspec-sidekiq (~> 5.0)
rubocop
rubocop-capybara
rubocop-performance

View file

@ -70,7 +70,7 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
- **PostgreSQL** 12+
- **Redis** 4+
- **Ruby** 3.1+
- **Node.js** 16+
- **Node.js** 18+
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
@ -91,10 +91,12 @@ A **Vagrant** configuration is included for development purposes. To use it, com
To set up **MacOS** for native development, complete the following steps:
- Use a Ruby version manager to install the specified version from `.ruby-version`
- Run `bundle` to install required gems
- Run `brew install postgresql@14 redis imagemagick libidn` to install required dependencies
- Navigate to Mastodon's root directory and run `brew install nvm` then `nvm use` to use the version from `.nvmrc`
- Run `yarn` to install required packages
- Run `corepack enable && corepack prepare`
- Run `bundle exec rails db:setup` (optionally prepend `RAILS_ENV=development` to target the dev environment)
- Run `RAILS_ENV=development bundle exec rails db:setup`
- Finally, run `bin/dev` which will launch the local services via `overmind` (if installed) or `foreman`
### Docker

View file

@ -25,7 +25,7 @@ class AccountsController < ApplicationController
limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
@statuses = filtered_statuses.without_reblogs.limit(limit)
@statuses = cache_collection(@statuses, Status)
@statuses = preload_collection(@statuses, Status)
end
format.json do

View file

@ -18,7 +18,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_items
case params[:id]
when 'featured'
@items = for_signed_account { cache_collection(@account.pinned_statuses, Status) }
@items = for_signed_account { preload_collection(@account.pinned_statuses, Status) }
@items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) }
when 'tags'
@items = for_signed_account { @account.featured_tags }

View file

@ -60,7 +60,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def set_statuses
return unless page_requested?
@statuses = cache_collection_paginated_by_id(
@statuses = preload_collection_paginated_by_id(
AccountStatusesFilter.new(@account, signed_request_account).results,
Status,
LIMIT,

View file

@ -128,7 +128,7 @@ module Admin
def unblock_email
authorize @account, :unblock_email?
CanonicalEmailBlock.matching_account(@account).delete_all
CanonicalEmailBlock.where(reference_account: @account).delete_all
log_action :unblock_email, @account

View file

@ -9,7 +9,7 @@ module Admin
@site_upload.destroy!
redirect_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
redirect_back fallback_location: admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
end
private

View file

@ -19,11 +19,11 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end
def load_statuses
@account.unavailable? ? [] : cached_account_statuses
@account.unavailable? ? [] : preloaded_account_statuses
end
def cached_account_statuses
cache_collection_paginated_by_id(
def preloaded_account_statuses
preload_collection_paginated_by_id(
AccountStatusesFilter.new(@account, current_account, params).results,
Status,
limit_param(DEFAULT_STATUSES_LIMIT),

View file

@ -9,16 +9,22 @@ class Api::V1::AccountsController < Api::BaseController
before_action -> { doorkeeper_authorize! :follow, :write, :'write:blocks' }, only: [:block, :unblock]
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create]
before_action :require_user!, except: [:show, :create]
before_action :set_account, except: [:create]
before_action :check_account_approval, except: [:create]
before_action :check_account_confirmation, except: [:create]
before_action :require_user!, except: [:index, :show, :create]
before_action :set_account, except: [:index, :create]
before_action :set_accounts, only: [:index]
before_action :check_account_approval, except: [:index, :create]
before_action :check_account_confirmation, except: [:index, :create]
before_action :check_enabled_registrations, only: [:create]
before_action :check_accounts_limit, only: [:index]
skip_before_action :require_authenticated_user!, only: :create
override_rate_limit_headers :follow, family: :follows
def index
render json: @accounts, each_serializer: REST::AccountSerializer
end
def show
cache_if_unauthenticated!
render json: @account, serializer: REST::AccountSerializer
@ -79,6 +85,10 @@ class Api::V1::AccountsController < Api::BaseController
@account = Account.find(params[:id])
end
def set_accounts
@accounts = Account.where(id: account_ids).without_unapproved
end
def check_account_approval
raise(ActiveRecord::RecordNotFound) if @account.local? && @account.user_pending?
end
@ -87,10 +97,22 @@ class Api::V1::AccountsController < Api::BaseController
raise(ActiveRecord::RecordNotFound) if @account.local? && !@account.user_confirmed?
end
def check_accounts_limit
raise(Mastodon::ValidationError) if account_ids.size > DEFAULT_ACCOUNTS_LIMIT
end
def relationships(**options)
AccountRelationshipsPresenter.new([@account], current_user.account_id, **options)
end
def account_ids
Array(accounts_params[:ids]).uniq.map(&:to_i)
end
def accounts_params
params.permit(ids: [])
end
def account_params
params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code)
end

View file

@ -29,10 +29,11 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
def create
authorize :domain_block, :create?
@domain_block = DomainBlock.new(resource_params)
existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if existing_domain_block.present?
return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if conflicts_with_existing_block?(@domain_block, existing_domain_block)
@domain_block = DomainBlock.create!(resource_params)
@domain_block.save!
DomainBlockWorker.perform_async(@domain_block.id)
log_action :create, @domain_block
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
@ -55,6 +56,10 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
private
def conflicts_with_existing_block?(domain_block, existing_domain_block)
existing_domain_block.present? && (existing_domain_block.domain == TagManager.instance.normalize_domain(domain_block.domain) || !domain_block.stricter_than?(existing_domain_block))
end
def set_domain_blocks
@domain_blocks = filtered_domain_blocks.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
end

View file

@ -4,6 +4,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
def show
return doorkeeper_render_error unless valid_doorkeeper_token?
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes)
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer
end
end

View file

@ -5,7 +5,7 @@ class Api::V1::AppsController < Api::BaseController
def create
@app = Doorkeeper::Application.create!(application_options)
render json: @app, serializer: REST::ApplicationSerializer
render json: @app, serializer: REST::CredentialApplicationSerializer
end
private
@ -24,6 +24,6 @@ class Api::V1::AppsController < Api::BaseController
end
def app_params
params.permit(:client_name, :redirect_uris, :scopes, :website)
params.permit(:client_name, :scopes, :website, :redirect_uris, redirect_uris: [])
end
end

View file

@ -13,11 +13,11 @@ class Api::V1::BookmarksController < Api::BaseController
private
def load_statuses
cached_bookmarks
preloaded_bookmarks
end
def cached_bookmarks
cache_collection(results.map(&:status), Status)
def preloaded_bookmarks
preload_collection(results.map(&:status), Status)
end
def results

View file

@ -13,11 +13,11 @@ class Api::V1::FavouritesController < Api::BaseController
private
def load_statuses
cached_favourites
preloaded_favourites
end
def cached_favourites
cache_collection(results.map(&:status), Status)
def preloaded_favourites
preload_collection(results.map(&:status), Status)
end
def results

View file

@ -41,7 +41,7 @@ class Api::V1::Notifications::RequestsController < Api::BaseController
)
NotificationRequest.preload_cache_collection(requests) do |statuses|
cache_collection(statuses, Status)
preload_collection(statuses, Status)
end
end

View file

@ -41,7 +41,7 @@ class Api::V1::NotificationsController < Api::BaseController
)
Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses|
cache_collection(target_statuses, Status)
preload_collection(target_statuses, Status)
end
end

View file

@ -1,9 +1,12 @@
# frozen_string_literal: true
class Api::V1::Push::SubscriptionsController < Api::BaseController
include Redisable
include Lockable
before_action -> { doorkeeper_authorize! :push }
before_action :require_user!
before_action :set_push_subscription
before_action :set_push_subscription, only: [:show, :update]
before_action :check_push_subscription, only: [:show, :update]
def show
@ -11,16 +14,18 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
end
def create
@push_subscription&.destroy!
with_redis_lock("push_subscription:#{current_user.id}") do
destroy_web_push_subscriptions!
@push_subscription = Web::PushSubscription.create!(
endpoint: subscription_params[:endpoint],
key_p256dh: subscription_params[:keys][:p256dh],
key_auth: subscription_params[:keys][:auth],
data: data_params,
user_id: current_user.id,
access_token_id: doorkeeper_token.id
)
@push_subscription = Web::PushSubscription.create!(
endpoint: subscription_params[:endpoint],
key_p256dh: subscription_params[:keys][:p256dh],
key_auth: subscription_params[:keys][:auth],
data: data_params,
user_id: current_user.id,
access_token_id: doorkeeper_token.id
)
end
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
end
@ -31,14 +36,18 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
end
def destroy
@push_subscription&.destroy!
destroy_web_push_subscriptions!
render_empty
end
private
def destroy_web_push_subscriptions!
doorkeeper_token.web_push_subscriptions.destroy_all
end
def set_push_subscription
@push_subscription = Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
@push_subscription = doorkeeper_token.web_push_subscriptions.first
end
def check_push_subscription

View file

@ -5,9 +5,11 @@ class Api::V1::StatusesController < Api::BaseController
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy]
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy]
before_action :require_user!, except: [:show, :context]
before_action :set_status, only: [:show, :context]
before_action :set_thread, only: [:create]
before_action :require_user!, except: [:index, :show, :context]
before_action :set_statuses, only: [:index]
before_action :set_status, only: [:show, :context]
before_action :set_thread, only: [:create]
before_action :check_statuses_limit, only: [:index]
override_rate_limit_headers :create, family: :statuses
override_rate_limit_headers :update, family: :statuses
@ -23,9 +25,14 @@ class Api::V1::StatusesController < Api::BaseController
DESCENDANTS_LIMIT = 60
DESCENDANTS_DEPTH_LIMIT = 20
def index
@statuses = preload_collection(@statuses, Status)
render json: @statuses, each_serializer: REST::StatusSerializer
end
def show
cache_if_unauthenticated!
@status = cache_collection([@status], Status).first
@status = preload_collection([@status], Status).first
render json: @status, serializer: REST::StatusSerializer
end
@ -44,8 +51,8 @@ class Api::V1::StatusesController < Api::BaseController
ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account)
descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit)
loaded_ancestors = cache_collection(ancestors_results, Status)
loaded_descendants = cache_collection(descendants_results, Status)
loaded_ancestors = preload_collection(ancestors_results, Status)
loaded_descendants = preload_collection(descendants_results, Status)
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
statuses = [@status] + @context.ancestors + @context.descendants
@ -111,6 +118,10 @@ class Api::V1::StatusesController < Api::BaseController
private
def set_statuses
@statuses = Status.permitted_statuses_from_ids(status_ids, current_account)
end
def set_status
@status = Status.find(params[:id])
authorize @status, :show?
@ -125,6 +136,18 @@ class Api::V1::StatusesController < Api::BaseController
render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404
end
def check_statuses_limit
raise(Mastodon::ValidationError) if status_ids.size > DEFAULT_STATUSES_LIMIT
end
def status_ids
Array(statuses_params[:ids]).uniq.map(&:to_i)
end
def statuses_params
params.permit(ids: [])
end
def status_params
params.permit(
:status,

View file

@ -21,11 +21,11 @@ class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController
private
def load_statuses
cached_home_statuses
preloaded_home_statuses
end
def cached_home_statuses
cache_collection home_statuses, Status
def preloaded_home_statuses
preload_collection home_statuses, Status
end
def home_statuses

View file

@ -21,11 +21,11 @@ class Api::V1::Timelines::ListController < Api::V1::Timelines::BaseController
end
def set_statuses
@statuses = cached_list_statuses
@statuses = preloaded_list_statuses
end
def cached_list_statuses
cache_collection list_statuses, Status
def preloaded_list_statuses
preload_collection list_statuses, Status
end
def list_statuses

View file

@ -18,11 +18,11 @@ class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController
end
def load_statuses
cached_public_statuses_page
preloaded_public_statuses_page
end
def cached_public_statuses_page
cache_collection(public_statuses, Status)
def preloaded_public_statuses_page
preload_collection(public_statuses, Status)
end
def public_statuses

View file

@ -23,11 +23,11 @@ class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController
end
def load_statuses
cached_tagged_statuses
preloaded_tagged_statuses
end
def cached_tagged_statuses
@tag.nil? ? [] : cache_collection(tag_timeline_statuses, Status)
def preloaded_tagged_statuses
@tag.nil? ? [] : preload_collection(tag_timeline_statuses, Status)
end
def tag_timeline_statuses

View file

@ -20,7 +20,7 @@ class Api::V1::Trends::StatusesController < Api::BaseController
def set_statuses
@statuses = if enabled?
cache_collection(statuses_from_trends.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status)
preload_collection(statuses_from_trends.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status)
else
[]
end

View file

@ -9,6 +9,7 @@ class ApplicationController < ActionController::Base
include UserTrackingConcern
include SessionTrackingConcern
include CacheConcern
include PreloadingConcern
include DomainControlHelper
include DatabaseHelper
include AuthorizedFetchHelper

View file

@ -45,20 +45,4 @@ module CacheConcern
Rails.cache.write(key, response.body, expires_in: expires_in, raw: true)
end
end
# TODO: Rename this method, as it does not perform any caching anymore.
def cache_collection(raw, klass)
return raw unless klass.respond_to?(:preload_cacheable_associations)
records = raw.to_a
klass.preload_cacheable_associations(records)
records
end
# TODO: Rename this method, as it does not perform any caching anymore.
def cache_collection_paginated_by_id(raw, klass, limit, options)
cache_collection raw.to_a_paginated_by_id(limit, options), klass
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module PreloadingConcern
extend ActiveSupport::Concern
def preload_collection(scope, klass)
return scope unless klass.respond_to?(:preload_cacheable_associations)
scope.to_a.tap do |records|
klass.preload_cacheable_associations(records)
end
end
def preload_collection_paginated_by_id(scope, klass, limit, options)
preload_collection scope.to_a_paginated_by_id(limit, options), klass
end
end

View file

@ -13,7 +13,7 @@ class Settings::ApplicationsController < Settings::BaseController
def new
@application = Doorkeeper::Application.new(
redirect_uri: Doorkeeper.configuration.native_redirect_uri,
scopes: 'read write follow'
scopes: 'read:me'
)
end

View file

@ -45,7 +45,7 @@ class TagsController < ApplicationController
end
def set_statuses
@statuses = cache_collection(TagFeed.new(@tag, nil, local: @local).get(limit_param), Status)
@statuses = preload_collection(TagFeed.new(@tag, nil, local: @local).get(limit_param), Status)
end
def limit_param

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
module WellKnown
class OauthMetadataController < ActionController::Base # rubocop:disable Rails/ApplicationController
include CacheConcern
# Prevent `active_model_serializer`'s `ActionController::Serialization` from calling `current_user`
# and thus re-issuing session cookies
serialization_scope nil
def show
# Due to this document potentially changing between Mastodon versions (as
# new OAuth scopes are added), we don't use expires_in to cache upstream,
# instead just caching in the rails cache:
render_with_cache(
json: ::OauthMetadataPresenter.new,
serializer: ::OauthMetadataSerializer,
content_type: 'application/json',
expires_in: 15.minutes
)
end
end
end

View file

@ -240,6 +240,22 @@ module ApplicationHelper
EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s
end
def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
end
def instance_presenter
@instance_presenter ||= InstancePresenter.new
end
def favicon_path(size = '48')
instance_presenter.favicon&.file&.url(size)
end
def app_icon_path(size = '48')
instance_presenter.app_icon&.file&.url(size)
end
private
def storage_host_var

View file

@ -1,11 +0,0 @@
# frozen_string_literal: true
module MascotHelper
def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
end
def instance_presenter
@instance_presenter ||= InstancePresenter.new
end
end

View file

@ -1,119 +0,0 @@
import * as WebAuthnJSON from '@github/webauthn-json';
import axios from 'axios';
import ready from '../mastodon/ready';
import 'regenerator-runtime/runtime';
function getCSRFToken() {
var CSRFSelector = document.querySelector('meta[name="csrf-token"]');
if (CSRFSelector) {
return CSRFSelector.getAttribute('content');
} else {
return null;
}
}
function hideFlashMessages() {
Array.from(document.getElementsByClassName('flash-message')).forEach(function(flashMessage) {
flashMessage.classList.add('hidden');
});
}
function callback(url, body) {
axios.post(url, JSON.stringify(body), {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-Token': getCSRFToken(),
},
credentials: 'same-origin',
}).then(function(response) {
window.location.replace(response.data.redirect_path);
}).catch(function(error) {
if (error.response.status === 422) {
const errorMessage = document.getElementById('security-key-error-message');
errorMessage.classList.remove('hidden');
console.error(error.response.data.error);
} else {
console.error(error);
}
});
}
ready(() => {
if (!WebAuthnJSON.supported()) {
const unsupported_browser_message = document.getElementById('unsupported-browser-message');
if (unsupported_browser_message) {
unsupported_browser_message.classList.remove('hidden');
document.querySelector('.btn.js-webauthn').disabled = true;
}
}
const webAuthnCredentialRegistrationForm = document.getElementById('new_webauthn_credential');
if (webAuthnCredentialRegistrationForm) {
webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => {
event.preventDefault();
var nickname = event.target.querySelector('input[name="new_webauthn_credential[nickname]"]');
if (nickname.value) {
axios.get('/settings/security_keys/options')
.then((response) => {
const credentialOptions = response.data;
WebAuthnJSON.create({ 'publicKey': credentialOptions }).then((credential) => {
var params = { 'credential': credential, 'nickname': nickname.value };
callback('/settings/security_keys', params);
}).catch((error) => {
const errorMessage = document.getElementById('security-key-error-message');
errorMessage.classList.remove('hidden');
console.error(error);
});
}).catch((error) => {
console.error(error.response.data.error);
});
} else {
nickname.focus();
}
});
}
const webAuthnCredentialAuthenticationForm = document.getElementById('webauthn-form');
if (webAuthnCredentialAuthenticationForm) {
webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => {
event.preventDefault();
axios.get('sessions/security_key_options')
.then((response) => {
const credentialOptions = response.data;
WebAuthnJSON.get({ 'publicKey': credentialOptions }).then((credential) => {
var params = { 'user': { 'credential': credential } };
callback('sign_in', params);
}).catch((error) => {
const errorMessage = document.getElementById('security-key-error-message');
errorMessage.classList.remove('hidden');
console.error(error);
});
}).catch((error) => {
console.error(error.response.data.error);
});
});
const otpAuthenticationForm = document.getElementById('otp-authentication-form');
const linkToOtp = document.getElementById('link-to-otp');
linkToOtp.addEventListener('click', () => {
webAuthnCredentialAuthenticationForm.classList.add('hidden');
otpAuthenticationForm.classList.remove('hidden');
hideFlashMessages();
});
const linkToWebAuthn = document.getElementById('link-to-webauthn');
linkToWebAuthn.addEventListener('click', () => {
otpAuthenticationForm.classList.add('hidden');
webAuthnCredentialAuthenticationForm.classList.remove('hidden');
hideFlashMessages();
});
}
});

View file

@ -0,0 +1,197 @@
import * as WebAuthnJSON from '@github/webauthn-json';
import axios, { AxiosError } from 'axios';
import ready from '../mastodon/ready';
import 'regenerator-runtime/runtime';
type PublicKeyCredentialCreationOptionsJSON =
WebAuthnJSON.CredentialCreationOptionsJSON['publicKey'];
function exceptionHasAxiosError(
error: unknown,
): error is AxiosError<{ error: unknown }> {
return (
error instanceof AxiosError &&
typeof error.response?.data === 'object' &&
'error' in error.response.data
);
}
function logAxiosResponseError(error: unknown) {
if (exceptionHasAxiosError(error)) console.error(error);
}
function getCSRFToken() {
return document
.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')
?.getAttribute('content');
}
function hideFlashMessages() {
document.querySelectorAll('.flash-message').forEach((flashMessage) => {
flashMessage.classList.add('hidden');
});
}
async function callback(
url: string,
body:
| {
credential: WebAuthnJSON.PublicKeyCredentialWithAttestationJSON;
nickname: string;
}
| {
user: { credential: WebAuthnJSON.PublicKeyCredentialWithAssertionJSON };
},
) {
try {
const response = await axios.post<{ redirect_path: string }>(
url,
JSON.stringify(body),
{
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
'X-CSRF-Token': getCSRFToken(),
},
},
);
window.location.replace(response.data.redirect_path);
} catch (error) {
if (error instanceof AxiosError && error.response?.status === 422) {
const errorMessage = document.getElementById(
'security-key-error-message',
);
errorMessage?.classList.remove('hidden');
logAxiosResponseError(error);
} else {
console.error(error);
}
}
}
async function handleWebauthnCredentialRegistration(nickname: string) {
try {
const response = await axios.get<PublicKeyCredentialCreationOptionsJSON>(
'/settings/security_keys/options',
);
const credentialOptions = response.data;
try {
const credential = await WebAuthnJSON.create({
publicKey: credentialOptions,
});
const params = {
credential: credential,
nickname: nickname,
};
await callback('/settings/security_keys', params);
} catch (error) {
const errorMessage = document.getElementById(
'security-key-error-message',
);
errorMessage?.classList.remove('hidden');
console.error(error);
}
} catch (error) {
logAxiosResponseError(error);
}
}
async function handleWebauthnCredentialAuthentication() {
try {
const response = await axios.get<PublicKeyCredentialCreationOptionsJSON>(
'sessions/security_key_options',
);
const credentialOptions = response.data;
try {
const credential = await WebAuthnJSON.get({
publicKey: credentialOptions,
});
const params = { user: { credential: credential } };
void callback('sign_in', params);
} catch (error) {
const errorMessage = document.getElementById(
'security-key-error-message',
);
errorMessage?.classList.remove('hidden');
console.error(error);
}
} catch (error) {
logAxiosResponseError(error);
}
}
ready(() => {
if (!WebAuthnJSON.supported()) {
const unsupported_browser_message = document.getElementById(
'unsupported-browser-message',
);
if (unsupported_browser_message) {
unsupported_browser_message.classList.remove('hidden');
const button = document.querySelector<HTMLButtonElement>(
'button.btn.js-webauthn',
);
if (button) button.disabled = true;
}
}
const webAuthnCredentialRegistrationForm =
document.querySelector<HTMLFormElement>('form#new_webauthn_credential');
if (webAuthnCredentialRegistrationForm) {
webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => {
event.preventDefault();
if (!(event.target instanceof HTMLFormElement)) return;
const nickname = event.target.querySelector<HTMLInputElement>(
'input[name="new_webauthn_credential[nickname]"]',
);
if (nickname?.value) {
void handleWebauthnCredentialRegistration(nickname.value);
} else {
nickname?.focus();
}
});
}
const webAuthnCredentialAuthenticationForm =
document.getElementById('webauthn-form');
if (webAuthnCredentialAuthenticationForm) {
webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => {
event.preventDefault();
void handleWebauthnCredentialAuthentication();
});
const otpAuthenticationForm = document.getElementById(
'otp-authentication-form',
);
const linkToOtp = document.getElementById('link-to-otp');
linkToOtp?.addEventListener('click', () => {
webAuthnCredentialAuthenticationForm.classList.add('hidden');
otpAuthenticationForm?.classList.remove('hidden');
hideFlashMessages();
});
const linkToWebAuthn = document.getElementById('link-to-webauthn');
linkToWebAuthn?.addEventListener('click', () => {
otpAuthenticationForm?.classList.add('hidden');
webAuthnCredentialAuthenticationForm.classList.remove('hidden');
hideFlashMessages();
});
}
}).catch((e: unknown) => {
throw e;
});

View file

@ -20,7 +20,7 @@ export function changeSetting(path, value) {
}
const debouncedSave = debounce((dispatch, getState) => {
if (getState().getIn(['settings', 'saved'])) {
if (getState().getIn(['settings', 'saved']) || !getState().getIn(['meta', 'me'])) {
return;
}

View file

@ -172,7 +172,6 @@ Account.propTypes = {
onBlock: PropTypes.func,
onMute: PropTypes.func,
onMuteNotifications: PropTypes.func,
intl: PropTypes.object.isRequired,
hidden: PropTypes.bool,
minimal: PropTypes.bool,
defaultAction: PropTypes.string,

View file

@ -14,8 +14,10 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
import { Icon } from 'mastodon/components/icon';
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { useAppHistory } from './router';
const messages = defineMessages({
@ -51,12 +53,8 @@ BackButton.propTypes = {
};
class ColumnHeader extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
title: PropTypes.node,
icon: PropTypes.string,
@ -171,7 +169,7 @@ class ColumnHeader extends PureComponent {
);
}
if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
if (this.props.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
collapseButton = (
<button
className={collapsibleButtonClassName}
@ -232,4 +230,4 @@ class ColumnHeader extends PureComponent {
}
export default injectIntl(withRouter(ColumnHeader));
export default injectIntl(withIdentity(withRouter(ColumnHeader)));

View file

@ -14,6 +14,7 @@ import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import { Icon } from 'mastodon/components/icon';
import emojify from 'mastodon/features/emoji/emoji';
import Motion from 'mastodon/features/ui/util/optional_motion';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { RelativeTimestamp } from './relative_timestamp';
@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
}, {});
class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
poll: ImmutablePropTypes.map,
lang: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
</ul>
<div className='poll__footer'>
{!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <button className='button button-secondary' disabled={disabled || !this.props.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
{showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount}
@ -247,4 +244,4 @@ class Poll extends ImmutablePureComponent {
}
export default injectIntl(Poll);
export default injectIntl(withIdentity(Poll));

View file

@ -22,6 +22,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -74,12 +75,8 @@ const mapStateToProps = (state, { status }) => ({
});
class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record,
onReply: PropTypes.func,
@ -118,7 +115,7 @@ class StatusActionBar extends ImmutablePureComponent {
];
handleReplyClick = () => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReply(this.props.status, this.props.history);
@ -136,7 +133,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleFavouriteClick = () => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onFavourite(this.props.status);
@ -146,7 +143,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleReblogClick = e => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReblog(this.props.status, e);
@ -250,7 +247,7 @@ class StatusActionBar extends ImmutablePureComponent {
render () {
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
@ -410,4 +407,4 @@ class StatusActionBar extends ImmutablePureComponent {
}
export default withRouter(connect(mapStateToProps)(injectIntl(StatusActionBar)));
export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusActionBar))));

View file

@ -12,8 +12,10 @@ import { connect } from 'react-redux';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
import { Icon } from 'mastodon/components/icon';
import PollContainer from 'mastodon/containers/poll_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
/**
@ -67,12 +69,8 @@ const mapStateToProps = state => ({
});
class StatusContent extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string,
expanded: PropTypes.bool,
@ -245,7 +243,7 @@ class StatusContent extends PureComponent {
const renderReadMore = this.props.onClick && status.get('collapsed');
const contentLocale = intl.locale.replace(/[_-].*/, '');
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale);
const content = { __html: statusContent ?? getStatusContent(status) };
const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') };
@ -328,4 +326,4 @@ class StatusContent extends PureComponent {
}
export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent)));
export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusContent))));

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { Helmet } from 'react-helmet';
@ -14,6 +13,7 @@ import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary';
import { Router } from 'mastodon/components/router';
import UI from 'mastodon/features/ui';
import { IdentityContext, createIdentityContext } from 'mastodon/identity_context';
import initialState, { title as siteTitle } from 'mastodon/initial_state';
import { IntlProvider } from 'mastodon/locales';
import { store } from 'mastodon/store';
@ -28,33 +28,9 @@ if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis());
}
const createIdentityContext = state => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role ? state.role.permissions : 0,
});
export default class Mastodon extends PureComponent {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() {
if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream());
@ -74,19 +50,21 @@ export default class Mastodon extends PureComponent {
render () {
return (
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<IdentityContext.Provider value={this.identity}>
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
</IdentityContext.Provider>
);
}

View file

@ -25,6 +25,7 @@ import { IconButton } from 'mastodon/components/icon_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { ShortNumber } from 'mastodon/components/short_number';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -111,6 +112,7 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent {
static propTypes = {
identity: identityContextPropShape,
account: ImmutablePropTypes.record,
identity_props: ImmutablePropTypes.list,
onFollow: PropTypes.func.isRequired,
@ -136,10 +138,6 @@ class Header extends ImmutablePureComponent {
...WithRouterPropTypes,
};
static contextTypes = {
identity: PropTypes.object,
};
setRef = c => {
this.node = c;
};
@ -255,7 +253,7 @@ class Header extends ImmutablePureComponent {
render () {
const { account, hidden, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
if (!account) {
return null;
@ -516,4 +514,4 @@ class Header extends ImmutablePureComponent {
}
export default withRouter(injectIntl(Header));
export default withRouter(withIdentity(injectIntl(Header)));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -38,16 +39,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class CommunityTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -77,7 +74,7 @@ class CommunityTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandCommunityTimeline({ onlyMedia }));
@ -87,7 +84,7 @@ class CommunityTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props;
@ -161,4 +158,4 @@ class CommunityTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(CommunityTimeline));
export default withIdentity(connect(mapStateToProps)(injectIntl(CommunityTimeline)));

View file

@ -12,6 +12,7 @@ import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'mastodon/components/icon';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, searchEnabled } from 'mastodon/initial_state';
import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -33,12 +34,8 @@ const labelForRecentSearch = search => {
};
class Search extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
value: PropTypes.string.isRequired,
recent: ImmutablePropTypes.orderedSet,
submitted: PropTypes.bool,
@ -276,7 +273,7 @@ class Search extends PureComponent {
}
_calculateOptions (value) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const trimmedValue = value.trim();
const options = [];
@ -318,7 +315,7 @@ class Search extends PureComponent {
render () {
const { intl, value, submitted, recent } = this.props;
const { expanded, options, selectedOption } = this.state;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const hasValue = value.length > 0 || submitted;
@ -402,4 +399,4 @@ class Search extends PureComponent {
}
export default withRouter(injectIntl(Search));
export default withRouter(withIdentity(injectIntl(Search)));

View file

@ -13,6 +13,7 @@ import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import Search from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { trendsEnabled } from 'mastodon/initial_state';
import Links from './links';
@ -32,12 +33,8 @@ const mapStateToProps = state => ({
});
class Explore extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
isSearching: PropTypes.bool,
@ -53,7 +50,7 @@ class Explore extends PureComponent {
render() {
const { intl, multiColumn, isSearching } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
@ -114,4 +111,4 @@ class Explore extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(Explore));
export default withIdentity(connect(mapStateToProps)(injectIntl(Explore)));

View file

@ -6,13 +6,14 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom';
import { useIdentity } from '@/mastodon/identity_context';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { addColumn } from 'mastodon/actions/columns';
import { changeSetting } from 'mastodon/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import initialState, { domain } from 'mastodon/initial_state';
import { domain } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import Column from '../../components/column';
@ -24,15 +25,6 @@ const messages = defineMessages({
title: { id: 'column.firehose', defaultMessage: 'Live feeds' },
});
// TODO: use a proper React context later on
const useIdentity = () => ({
signedIn: !!initialState.meta.me,
accountId: initialState.meta.me,
disabledAccountId: initialState.meta.disabled_account_id,
accessToken: initialState.meta.access_token,
permissions: initialState.role ? initialState.role.permissions : 0,
});
const ColumnSettings = () => {
const dispatch = useAppDispatch();
const settings = useAppSelector((state) => state.getIn(['settings', 'firehose']));

View file

@ -24,6 +24,7 @@ import { fetchFollowRequests } from 'mastodon/actions/accounts';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import LinkFooter from 'mastodon/features/ui/components/link_footer';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, showTrends } from '../../initial_state';
import { NavigationBar } from '../compose/components/navigation_bar';
@ -75,12 +76,8 @@ const badgeDisplay = (number, limit) => {
};
class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.record,
multiColumn: PropTypes.bool,
@ -91,7 +88,7 @@ class GettingStarted extends ImmutablePureComponent {
componentDidMount () {
const { fetchFollowRequests } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -102,7 +99,7 @@ class GettingStarted extends ImmutablePureComponent {
render () {
const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const navItems = [];
@ -167,4 +164,4 @@ class GettingStarted extends ImmutablePureComponent {
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted));
export default withIdentity(connect(mapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted)));

View file

@ -17,6 +17,7 @@ import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/t
import { expandHashtagTimeline, clearTimeline } from 'mastodon/actions/timelines';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import StatusListContainer from '../ui/containers/status_list_container';
@ -29,14 +30,10 @@ const mapStateToProps = (state, props) => ({
});
class HashtagTimeline extends PureComponent {
disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
@ -94,7 +91,7 @@ class HashtagTimeline extends PureComponent {
};
_subscribe (dispatch, id, tags = {}, local) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -168,7 +165,7 @@ class HashtagTimeline extends PureComponent {
handleFollow = () => {
const { dispatch, params, tag } = this.props;
const { id } = params;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -185,7 +182,7 @@ class HashtagTimeline extends PureComponent {
const { hasUnread, columnId, multiColumn, tag } = this.props;
const { id, local } = this.props.params;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
@ -225,4 +222,4 @@ class HashtagTimeline extends PureComponent {
}
export default connect(mapStateToProps)(HashtagTimeline);
export default connect(mapStateToProps)(withIdentity(HashtagTimeline));

View file

@ -14,6 +14,7 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/an
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { criticalUpdatesPending } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,12 +41,8 @@ const mapStateToProps = state => ({
});
class HomeTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
@ -126,7 +123,7 @@ class HomeTimeline extends PureComponent {
render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const banners = [];
let announcementsButton;
@ -190,4 +187,4 @@ class HomeTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(HomeTimeline));
export default connect(mapStateToProps)(withIdentity(injectIntl(HomeTimeline)));

View file

@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions';
import { CheckboxWithLabel } from './checkbox_with_label';
@ -12,13 +13,9 @@ import ClearColumnButton from './clear_column_button';
import GrantPermissionButton from './grant_permission_button';
import SettingToggle from './setting_toggle';
export default class ColumnSettings extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
class ColumnSettings extends PureComponent {
static propTypes = {
identity: identityContextPropShape,
settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
@ -215,7 +212,7 @@ export default class ColumnSettings extends PureComponent {
</div>
</section>
{((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
{((this.props.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
<section role='group' aria-labelledby='notifications-admin-sign-up'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></h3>
@ -228,7 +225,7 @@ export default class ColumnSettings extends PureComponent {
</section>
)}
{((this.context.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (
{((this.props.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (
<section role='group' aria-labelledby='notifications-admin-report'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></h3>
@ -245,3 +242,5 @@ export default class ColumnSettings extends PureComponent {
}
}
export default withIdentity(ColumnSettings);

View file

@ -41,7 +41,7 @@ const messages = defineMessages({
adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' },
relationshipsSevered: { id: 'notification.relationships_severance_event', defaultMessage: 'Lost connections with {name}' },
moderationWarning: { id: 'notification.moderation_warning', defaultMessage: 'Your have received a moderation warning' },
moderationWarning: { id: 'notification.moderation_warning', defaultMessage: 'You have received a moderation warning' },
});
const notificationForScreenReader = (intl, message, timestamp) => {

View file

@ -17,6 +17,7 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?
import { compareId } from 'mastodon/compare_id';
import { Icon } from 'mastodon/components/icon';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { submitMarkers } from '../../actions/markers';
@ -77,12 +78,8 @@ const mapStateToProps = state => ({
});
class Notifications extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
columnId: PropTypes.string,
notifications: ImmutablePropTypes.list.isRequired,
dispatch: PropTypes.func.isRequired,
@ -190,7 +187,7 @@ class Notifications extends PureComponent {
const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
let scrollableContent = null;
@ -299,4 +296,4 @@ class Notifications extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(Notifications));
export default connect(mapStateToProps)(withIdentity(injectIntl(Notifications)));

View file

@ -18,6 +18,7 @@ import { replyCompose } from 'mastodon/actions/compose';
import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions';
import { openModal } from 'mastodon/actions/modal';
import { IconButton } from 'mastodon/components/icon_button';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, boostModal } from 'mastodon/initial_state';
import { makeGetStatus } from 'mastodon/selectors';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -47,12 +48,8 @@ const makeMapStateToProps = () => {
};
class Footer extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
statusId: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
@ -75,7 +72,7 @@ class Footer extends ImmutablePureComponent {
handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -104,7 +101,7 @@ class Footer extends ImmutablePureComponent {
handleFavouriteClick = () => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -131,7 +128,7 @@ class Footer extends ImmutablePureComponent {
handleReblogClick = e => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('reblogged')) {
@ -209,4 +206,4 @@ class Footer extends ImmutablePureComponent {
}
export default connect(makeMapStateToProps)(withRouter(injectIntl(Footer)));
export default connect(makeMapStateToProps)(withIdentity(withRouter(injectIntl(Footer))));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class PublicTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
@ -80,7 +77,7 @@ class PublicTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia, onlyRemote } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
@ -90,7 +87,7 @@ class PublicTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
const { dispatch, onlyMedia, onlyRemote } = this.props;
@ -164,4 +161,4 @@ class PublicTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(PublicTimeline));
export default connect(mapStateToProps)(withIdentity(injectIntl(PublicTimeline)));

View file

@ -21,6 +21,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -67,12 +68,8 @@ const mapStateToProps = (state, { status }) => ({
});
class ActionBar extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record,
onReply: PropTypes.func.isRequired,
@ -198,7 +195,7 @@ class ActionBar extends PureComponent {
render () {
const { status, relationship, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
@ -326,4 +323,4 @@ class ActionBar extends PureComponent {
}
export default withRouter(connect(mapStateToProps)(injectIntl(ActionBar)));
export default withRouter(connect(mapStateToProps)(withIdentity(injectIntl(ActionBar))));

View file

@ -20,6 +20,7 @@ import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import {
@ -189,12 +190,8 @@ const titleFromStatus = (intl, status) => {
};
class Status extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
@ -244,7 +241,7 @@ class Status extends ImmutablePureComponent {
handleFavouriteClick = (status) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -274,7 +271,7 @@ class Status extends ImmutablePureComponent {
handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -307,7 +304,7 @@ class Status extends ImmutablePureComponent {
handleReblogClick = (status, e) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('reblogged')) {
@ -745,4 +742,4 @@ class Status extends ImmutablePureComponent {
}
export default withRouter(injectIntl(connect(makeMapStateToProps)(Status)));
export default withRouter(injectIntl(connect(makeMapStateToProps)(withIdentity(Status))));

View file

@ -7,16 +7,13 @@ import { changeComposing, mountCompose, unmountCompose } from 'mastodon/actions/
import ServerBanner from 'mastodon/components/server_banner';
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
import SearchContainer from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import LinkFooter from './link_footer';
class ComposePanel extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
};
@ -41,7 +38,7 @@ class ComposePanel extends PureComponent {
}
render() {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<div className='compose-panel' onFocus={this.onFocus}>
@ -65,4 +62,4 @@ class ComposePanel extends PureComponent {
}
export default connect()(ComposePanel);
export default connect()(withIdentity(ComposePanel));

View file

@ -13,6 +13,7 @@ import { fetchServer } from 'mastodon/actions/server';
import { Avatar } from 'mastodon/components/avatar';
import { Icon } from 'mastodon/components/icon';
import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { registrationsOpen, me, sso_redirect } from 'mastodon/initial_state';
const Account = connect(state => ({
@ -41,12 +42,8 @@ const mapDispatchToProps = (dispatch) => ({
});
class Header extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object,
signupUrl: PropTypes.string.isRequired,
@ -60,7 +57,7 @@ class Header extends PureComponent {
}
render () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
let content;
@ -121,4 +118,4 @@ class Header extends PureComponent {
}
export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header)));
export default injectIntl(withRouter(withIdentity(connect(mapStateToProps, mapDispatchToProps)(Header))));

View file

@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { openModal } from 'mastodon/actions/modal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'mastodon/initial_state';
import { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
import { logOut } from 'mastodon/utils/log_out';
@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
class LinkFooter extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
multiColumn: PropTypes.bool,
onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
@ -53,7 +50,7 @@ class LinkFooter extends PureComponent {
};
render () {
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const { multiColumn } = this.props;
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS);
@ -113,4 +110,4 @@ class LinkFooter extends PureComponent {
}
export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter));
export default injectIntl(withIdentity(connect(null, mapDispatchToProps)(LinkFooter)));

View file

@ -32,6 +32,7 @@ import { fetchFollowRequests } from 'mastodon/actions/accounts';
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { WordmarkLogo } from 'mastodon/components/logo';
import { NavigationPortal } from 'mastodon/components/navigation_portal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { timelinePreview, trendsEnabled } from 'mastodon/initial_state';
import { transientSingleColumn } from 'mastodon/is_mobile';
@ -99,12 +100,8 @@ const FollowRequestsLink = () => {
};
class NavigationPanel extends Component {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
};
@ -114,7 +111,7 @@ class NavigationPanel extends Component {
render () {
const { intl } = this.props;
const { signedIn, disabledAccountId } = this.context.identity;
const { signedIn, disabledAccountId } = this.props.identity;
let banner = undefined;
@ -194,4 +191,4 @@ class NavigationPanel extends Component {
}
export default injectIntl(NavigationPanel);
export default injectIntl(withIdentity(NavigationPanel));

View file

@ -15,6 +15,7 @@ import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import { PictureInPicture } from 'mastodon/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { layoutFromWindow } from 'mastodon/is_mobile';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -120,12 +121,8 @@ const keyMap = {
};
class SwitchingColumnsArea extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
children: PropTypes.node,
location: PropTypes.object,
singleColumn: PropTypes.bool,
@ -160,7 +157,7 @@ class SwitchingColumnsArea extends PureComponent {
render () {
const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const pathName = this.props.location.pathname;
let redirect;
@ -252,12 +249,8 @@ class SwitchingColumnsArea extends PureComponent {
}
class UI extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
isComposing: PropTypes.bool,
@ -309,7 +302,7 @@ class UI extends PureComponent {
this.dragTargets.push(e.target);
}
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) {
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.props.identity.signedIn) {
this.setState({ draggingOver: true });
}
};
@ -337,7 +330,7 @@ class UI extends PureComponent {
this.setState({ draggingOver: false });
this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.context.identity.signedIn) {
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.props.identity.signedIn) {
this.props.dispatch(uploadCompose(e.dataTransfer.files));
}
};
@ -389,7 +382,7 @@ class UI extends PureComponent {
};
componentDidMount () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
window.addEventListener('focus', this.handleWindowFocus, false);
window.addEventListener('blur', this.handleWindowBlur, false);
@ -586,7 +579,7 @@ class UI extends PureComponent {
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
<Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
<SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children}
</SwitchingColumnsArea>
@ -602,4 +595,4 @@ class UI extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(withRouter(UI)));
export default connect(mapStateToProps)(injectIntl(withRouter(withIdentity(UI))));

View file

@ -0,0 +1,74 @@
import PropTypes from 'prop-types';
import { createContext, useContext } from 'react';
import hoistStatics from 'hoist-non-react-statics';
import type { InitialState } from 'mastodon/initial_state';
export interface IdentityContextType {
signedIn: boolean;
accountId: string | undefined;
disabledAccountId: string | undefined;
accessToken: string | undefined;
permissions: number;
}
export const identityContextPropShape = PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired;
export const createIdentityContext = (state: InitialState) => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role?.permissions ?? 0,
});
export const IdentityContext = createContext<IdentityContextType>({
signedIn: false,
permissions: 0,
accountId: undefined,
disabledAccountId: undefined,
accessToken: undefined,
});
export const useIdentity = () => useContext(IdentityContext);
export interface IdentityProps {
ref?: unknown;
wrappedComponentRef?: unknown;
}
/* Injects an `identity` props into the wrapped component to be able to use the new context in class components */
export function withIdentity<
ComponentType extends React.ComponentType<IdentityProps>,
>(Component: ComponentType) {
const displayName = `withIdentity(${Component.displayName ?? Component.name})`;
const C = (props: React.ComponentProps<ComponentType>) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<IdentityContext.Consumer>
{(context) => {
return (
// @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component
{...remainingProps}
identity={context}
ref={wrappedComponentRef}
/>
);
}}
</IdentityContext.Consumer>
);
};
C.displayName = displayName;
C.WrappedComponent = Component;
return hoistStatics(C, Component);
}

View file

@ -44,12 +44,22 @@
* @property {string} sso_redirect
*/
/**
* @typedef Role
* @property {string} id
* @property {string} name
* @property {string} permissions
* @property {string} color
* @property {boolean} highlighted
*/
/**
* @typedef InitialState
* @property {Record<string, import("./api_types/accounts").ApiAccountJSON>} accounts
* @property {InitialStateLanguage[]} languages
* @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta
* @property {Role?} role
*/
const element = document.getElementById('initial-state');

View file

@ -5,7 +5,7 @@
"about.domain_blocks.no_reason_available": "السبب غير متوفر",
"about.domain_blocks.preamble": "يسمح لك ماستدون عموماً بعرض المحتوى من المستخدمين من أي خادم آخر في الفدرالية والتفاعل معهم. وهذه هي الاستثناءات التي وضعت على هذا الخادم بالذات.",
"about.domain_blocks.silenced.explanation": "عموماً، لن ترى ملفات التعريف والمحتوى من هذا الخادم، إلا إذا كنت تبحث عنه بشكل صريح أو تختار أن تتابعه.",
"about.domain_blocks.silenced.title": "تم كتمه",
"about.domain_blocks.silenced.title": "محدود",
"about.domain_blocks.suspended.explanation": "لن يتم معالجة أي بيانات من هذا الخادم أو تخزينها أو تبادلها، مما يجعل أي تفاعل أو اتصال مع المستخدمين من هذا الخادم مستحيلا.",
"about.domain_blocks.suspended.title": "مُعلّق",
"about.not_available": "لم يتم توفير هذه المعلومات على هذا الخادم.",
@ -21,7 +21,7 @@
"account.blocked": "محظور",
"account.browse_more_on_origin_server": "تصفح المزيد في الملف الشخصي الأصلي",
"account.cancel_follow_request": "إلغاء طلب المتابعة",
"account.copy": "نسخ الرابط إلى الملف الشخصي",
"account.copy": "نسخ الرابط إلى الحساب",
"account.direct": "إشارة خاصة لـ @{name}",
"account.disable_notifications": "توقف عن إشعاري عندما ينشر @{name}",
"account.domain_blocked": "اسم النِّطاق محظور",
@ -32,7 +32,7 @@
"account.featured_tags.last_status_never": "لا توجد رسائل",
"account.featured_tags.title": "وسوم {name} المميَّزة",
"account.follow": "متابعة",
"account.follow_back": "تابعه بدورك",
"account.follow_back": "رد المتابعة",
"account.followers": "مُتابِعون",
"account.followers.empty": "لا أحدَ يُتابع هذا المُستخدم إلى حد الآن.",
"account.followers_counter": "{count, plural, zero{لا مُتابع} one {مُتابعٌ واحِد} two {مُتابعانِ اِثنان} few {{counter} مُتابِعين} many {{counter} مُتابِعًا} other {{counter} مُتابع}}",
@ -89,12 +89,12 @@
"announcement.announcement": "إعلان",
"attachments_list.unprocessed": "(غير معالَج)",
"audio.hide": "إخفاء المقطع الصوتي",
"block_modal.remote_users_caveat": "Do ti kërkojmë shërbyesit {domain} të respektojë vendimin tuaj. Por, pajtimi sështë i garantuar, ngaqë disa shërbyes mund ti trajtojnë ndryshe bllokimet. Psotimet publike mundet të jenë ende të dukshme për përdorues pa bërë hyrje në llogari.",
"block_modal.show_less": "اعرض أقلّ",
"block_modal.remote_users_caveat": "سوف نطلب من الخادم {domain} أن يحترم قرارك، لكن الالتزام غير مضمون لأن بعض الخواديم قد تتعامل مع نصوص الكتل بشكل مختلف. قد تظل المنشورات العامة مرئية للمستخدمين غير المسجلين الدخول.",
"block_modal.show_less": "أظهر الأقل",
"block_modal.show_more": "أظهر المزيد",
"block_modal.they_cant_mention": "لن يستطيع ذِكرك أو متابعتك.",
"block_modal.they_cant_see_posts": "لن يستطيع رؤية منشوراتك ولن ترى منشوراته.",
"block_modal.they_will_know": "يمكنه أن يرى أنه قد تم حجبه.",
"block_modal.they_will_know": "يمكنه أن يرى أنه قد تم حظره.",
"block_modal.title": "أتريد حظر المستخدم؟",
"block_modal.you_wont_see_mentions": "لن تر المنشورات التي يُشار فيهم إليه.",
"boost_modal.combo": "يُمكنك الضّغط على {combo} لتخطي هذا في المرة المُقبلة",
@ -220,7 +220,7 @@
"domain_pill.activitypub_lets_connect": "يتيح لك التواصل والتفاعل مع الناس ليس فقط على ماستدون، ولكن عبر تطبيقات اجتماعية مختلفة أيضا.",
"domain_pill.activitypub_like_language": "إنّ ActivityPub مثل لغة ماستدون التي يتحدث بها مع شبكات اجتماعية أخرى.",
"domain_pill.server": "الخادِم",
"domain_pill.their_handle": "مُعرِّفُه:",
"domain_pill.their_handle": "مُعرفه:",
"domain_pill.their_server": "بيتهم الرقمي، حيث تُستضاف كافة منشوراتهم.",
"domain_pill.their_username": "مُعرّفُهم الفريد على الخادم. من الممكن العثور على مستخدمين بنفس اسم المستخدم على خوادم مختلفة.",
"domain_pill.username": "اسم المستخدم",
@ -308,6 +308,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "هذا الملف الشخصي مشابه للملفات الشخصية التي تابعتها مؤخرا.",
"follow_suggestions.personalized_suggestion": "توصية مخصصة",
"follow_suggestions.popular_suggestion": "توصية رائجة",
"follow_suggestions.popular_suggestion_longer": "رائج على {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "مشابهة لمواصفات الملفات الشخصية التي تابعتَها حديثًا",
"follow_suggestions.view_all": "عرض الكل",
"follow_suggestions.who_to_follow": "حسابات للمُتابَعة",
"followed_tags": "الوسوم المتابَعة",
@ -360,8 +362,8 @@
"interaction_modal.title.reply": "الرد على منشور {name}",
"intervals.full.days": "{number, plural, one {# يوم} other {# أيام}}",
"intervals.full.hours": "{number, plural, one {# ساعة} other {# ساعات}}",
"intervals.full.minutes": "{number, plural, one {# دقيقة} other {# دقائق}}",
"keyboard_shortcuts.back": "للعودة",
"intervals.full.minutes": "{number, plural, one {دقيقة واحدة}two {دقيقتان} other {# دقائق}}",
"keyboard_shortcuts.back": "للرجوع",
"keyboard_shortcuts.blocked": "لفتح قائمة المستخدمين المحظورين",
"keyboard_shortcuts.boost": "لإعادة النشر",
"keyboard_shortcuts.column": "للتركيز على منشور على أحد الأعمدة",
@ -421,7 +423,9 @@
"loading_indicator.label": "جاري التحميل…",
"media_gallery.toggle_visible": "{number, plural, zero {} one {اخف الصورة} two {اخف الصورتين} few {اخف الصور} many {اخف الصور} other {اخف الصور}}",
"moved_to_account_banner.text": "حسابك {disabledAccount} معطل حاليًا لأنك انتقلت إلى {movedToAccount}.",
"mute_modal.hide_from_notifications": "إخفاء من قائمة الإشعارات",
"mute_modal.hide_options": "إخفاء الخيارات",
"mute_modal.indefinite": "إلى أن أفسخ كتمها",
"mute_modal.show_options": "إظهار الخيارات",
"mute_modal.they_can_mention_and_follow": "سيكون بإمكانه الإشارة إليك ومتابعتك، لكنك لن تره.",
"mute_modal.they_wont_know": "لن يَعرف أنه قد تم كتمه.",
@ -460,10 +464,20 @@
"notification.follow": "يتابعك {name}",
"notification.follow_request": "لقد طلب {name} متابعتك",
"notification.mention": "{name} ذكرك",
"notification.moderation-warning.learn_more": "اعرف المزيد",
"notification.moderation_warning.action_disable": "تم تعطيل حسابك.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "بعض من منشوراتك تم تصنيفها على أنها حساسة.",
"notification.moderation_warning.action_none": "لقد تلقى حسابك تحذيرا بالإشراف.",
"notification.moderation_warning.action_sensitive": "سيتم وضع علامة على منشوراتك على أنها حساسة من الآن فصاعدا.",
"notification.moderation_warning.action_suspend": "لقد تم تعليق حسابك.",
"notification.own_poll": "انتهى استطلاعك للرأي",
"notification.poll": "لقد انتهى استطلاع رأي شاركتَ فيه",
"notification.reblog": "قام {name} بمشاركة منشورك",
"notification.relationships_severance_event": "فقدت الاتصالات مع {name}",
"notification.relationships_severance_event.account_suspension": "قام مشرف من {from} بتعليق {target}، مما يعني أنك لم يعد بإمكانك تلقي التحديثات منهم أو التفاعل معهم.",
"notification.relationships_severance_event.domain_block": "قام مشرف من {from} بحظر {target}، بما في ذلك {followersCount} من متابعينك و {followingCount, plural, one {# حساب} other {# حسابات}} تتابعها.",
"notification.relationships_severance_event.learn_more": "اعرف المزيد",
"notification.relationships_severance_event.user_domain_block": "لقد قمت بحظر {target}، مما أدى إلى إزالة {followersCount} من متابعينك و {followingCount, plural, one {# حساب} other {# حسابات}} تتابعها.",
"notification.status": "{name} نشر للتو",
"notification.update": "عدّلَ {name} منشورًا",
"notification_requests.accept": "موافقة",
@ -503,10 +517,15 @@
"notifications.permission_denied": "تنبيهات سطح المكتب غير متوفرة بسبب رفض أذونات المتصفح مسبقاً",
"notifications.permission_denied_alert": "لا يمكن تفعيل إشعارات سطح المكتب، لأن إذن المتصفح قد تم رفضه سابقاً",
"notifications.permission_required": "إشعارات سطح المكتب غير متوفرة لأنه لم يتم منح الإذن المطلوب.",
"notifications.policy.filter_new_accounts.hint": "تم إنشاؤها منذ {days, plural, zero {}one {يوم واحد} two {يومان} few {# أيام} many {# أيام} other {# أيام}}",
"notifications.policy.filter_new_accounts_title": "حسابات جديدة",
"notifications.policy.filter_not_followers_hint": "بما في ذلك الأشخاص الذين يتابعونك أقل من {days, plural, zero {}one {يوم واحد} two {يومان} few {# أيام} many {# أيام} other {# أيام}}",
"notifications.policy.filter_not_followers_title": "أشخاص لا يتابعونك",
"notifications.policy.filter_not_following_hint": "حتى توافق عليهم يدويا",
"notifications.policy.filter_not_following_title": "أشخاص لا تتابعهم",
"notifications.policy.filter_private_mentions_hint": "تمت تصفيته إلا إذا أن يكون ردًا على ذكرك أو إذا كنت تتابع الحساب",
"notifications.policy.filter_private_mentions_title": "إشارات خاصة غير مرغوب فيها",
"notifications.policy.title": "تصفية الإشعارات من…",
"notifications_permission_banner.enable": "تفعيل إشعارات سطح المكتب",
"notifications_permission_banner.how_to_control": "لتلقي الإشعارات عندما لا يكون ماستدون مفتوح، قم بتفعيل إشعارات سطح المكتب، يمكنك التحكم بدقة في أنواع التفاعلات التي تولد إشعارات سطح المكتب من خلال زر الـ{icon} أعلاه بمجرد تفعيلها.",
"notifications_permission_banner.title": "لا تفوت شيئاً أبداً",
@ -687,6 +706,7 @@
"status.edited_x_times": "عُدّل {count, plural, zero {} one {مرةً واحدة} two {مرّتان} few {{count} مرات} many {{count} مرة} other {{count} مرة}}",
"status.embed": "إدماج",
"status.favourite": "فضّل",
"status.favourites": "{count, plural, zero {}one {مفضلة واحدة} two {مفضلتان} few {# مفضلات} many {# مفضلات} other {# مفضلات}}",
"status.filter": "تصفية هذه الرسالة",
"status.filtered": "مُصفّى",
"status.hide": "إخفاء المنشور",
@ -707,6 +727,7 @@
"status.reblog": "إعادة النشر",
"status.reblog_private": "إعادة النشر إلى الجمهور الأصلي",
"status.reblogged_by": "شارَكَه {name}",
"status.reblogs": "{count, plural, one {تعزيز واحد} two {تعزيزتان} few {# تعزيزات} many {# تعزيزات} other {# تعزيزات}}",
"status.reblogs.empty": "لم يقم أي أحد بمشاركة هذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.",
"status.redraft": "إزالة وإعادة الصياغة",
"status.remove_bookmark": "احذفه مِن الفواصل المرجعية",

View file

@ -318,7 +318,7 @@
"follow_suggestions.personalized_suggestion": "Suggeriment personalitzat",
"follow_suggestions.popular_suggestion": "Suggeriment popular",
"follow_suggestions.popular_suggestion_longer": "Popular a {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Semblant a perfils que seguiu fa poc",
"follow_suggestions.similar_to_recently_followed_longer": "Semblant a perfils que seguiu de fa poc",
"follow_suggestions.view_all": "Mostra-ho tot",
"follow_suggestions.who_to_follow": "A qui seguir",
"followed_tags": "Etiquetes seguides",
@ -473,6 +473,7 @@
"notification.follow": "{name} et segueix",
"notification.follow_request": "{name} ha sol·licitat de seguir-te",
"notification.mention": "{name} t'ha esmentat",
"notification.moderation-warning.learn_more": "Per a saber-ne més",
"notification.moderation_warning": "Heu rebut un avís de moderació",
"notification.moderation_warning.action_delete_statuses": "S'han eliminat algunes de les vostres publicacions.",
"notification.moderation_warning.action_disable": "S'ha desactivat el vostre compte.",

View file

@ -474,7 +474,7 @@
"notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn",
"notification.mention": "Crybwyllodd {name} amdanoch chi",
"notification.moderation-warning.learn_more": "Dysgu mwy",
"notification.moderation_warning": "Rydych wedi derbyn rhybudd cymedroli",
"notification.moderation_warning": "Rydych wedi derbyn rhybudd gan gymedrolwr",
"notification.moderation_warning.action_delete_statuses": "Mae rhai o'ch postiadau wedi'u dileu.",
"notification.moderation_warning.action_disable": "Mae eich cyfrif wedi'i analluogi.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Mae rhai o'ch postiadau wedi'u marcio'n sensitif.",

View file

@ -331,7 +331,7 @@
"footer.source_code": "Quellcode anzeigen",
"footer.status": "Status",
"generic.saved": "Gespeichert",
"getting_started.heading": "Auf gehts!",
"getting_started.heading": "Auf gehts!",
"hashtag.column_header.tag_mode.all": "und {additional}",
"hashtag.column_header.tag_mode.any": "oder {additional}",
"hashtag.column_header.tag_mode.none": "ohne {additional}",
@ -400,7 +400,7 @@
"keyboard_shortcuts.requests": "Liste der Follower-Anfragen aufrufen",
"keyboard_shortcuts.search": "Suchleiste fokussieren",
"keyboard_shortcuts.spoilers": "Feld für Inhaltswarnung anzeigen/ausblenden",
"keyboard_shortcuts.start": "„Auf gehts!“ öffnen",
"keyboard_shortcuts.start": "„Auf gehts!“ öffnen",
"keyboard_shortcuts.toggle_hidden": "Beitragstext hinter der Inhaltswarnung anzeigen/ausblenden",
"keyboard_shortcuts.toggle_sensitivity": "Medien anzeigen/ausblenden",
"keyboard_shortcuts.toot": "Neuen Beitrag erstellen",

View file

@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.",
"follow_suggestions.curated_suggestion": "Staff pick",
"follow_suggestions.dismiss": "Don't show again",
"follow_suggestions.featured_longer": "Hand-picked by the {domain} team",
"follow_suggestions.friends_of_friends_longer": "Popular among people you follow",
"follow_suggestions.hints.featured": "This profile has been hand-picked by the {domain} team.",
"follow_suggestions.hints.friends_of_friends": "This profile is popular among the people you follow.",
"follow_suggestions.hints.most_followed": "This profile is one of the most followed on {domain}.",
@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "This profile is similar to the profiles you have most recently followed.",
"follow_suggestions.personalized_suggestion": "Personalised suggestion",
"follow_suggestions.popular_suggestion": "Popular suggestion",
"follow_suggestions.popular_suggestion_longer": "Popular on {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Similar to profiles you recently followed",
"follow_suggestions.view_all": "View all",
"follow_suggestions.who_to_follow": "Who to follow",
"followed_tags": "Followed hashtags",
@ -469,6 +473,15 @@
"notification.follow": "{name} followed you",
"notification.follow_request": "{name} has requested to follow you",
"notification.mention": "{name} mentioned you",
"notification.moderation-warning.learn_more": "Learn more",
"notification.moderation_warning": "You have received a moderation warning",
"notification.moderation_warning.action_delete_statuses": "Some of your posts have been removed.",
"notification.moderation_warning.action_disable": "Your account has been disabled.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Some of your posts have been marked as sensitive.",
"notification.moderation_warning.action_none": "Your account has received a moderation warning.",
"notification.moderation_warning.action_sensitive": "Your posts will be marked as sensitive from now on.",
"notification.moderation_warning.action_silence": "Your account has been limited.",
"notification.moderation_warning.action_suspend": "Your account has been suspended.",
"notification.own_poll": "Your poll has ended",
"notification.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} boosted your status",

View file

@ -474,7 +474,7 @@
"notification.follow_request": "{name} has requested to follow you",
"notification.mention": "{name} mentioned you",
"notification.moderation-warning.learn_more": "Learn more",
"notification.moderation_warning": "Your have received a moderation warning",
"notification.moderation_warning": "You have received a moderation warning",
"notification.moderation_warning.action_delete_statuses": "Some of your posts have been removed.",
"notification.moderation_warning.action_disable": "Your account has been disabled.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Some of your posts have been marked as sensitive.",

View file

@ -476,12 +476,12 @@
"notification.moderation-warning.learn_more": "Saber más",
"notification.moderation_warning": "Has recibido una advertencia de moderación",
"notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.",
"notification.moderation_warning.action_disable": "Se ha desactivado su cuenta.",
"notification.moderation_warning.action_disable": "Tu cuenta ha sido desactivada.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.",
"notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderación.",
"notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarán como sensibles.",
"notification.moderation_warning.action_silence": "Se ha limitado tu cuenta.",
"notification.moderation_warning.action_suspend": "Se ha suspendido tu cuenta.",
"notification.moderation_warning.action_silence": "Tu cuenta ha sido limitada.",
"notification.moderation_warning.action_suspend": "Tu cuenta ha sido suspendida.",
"notification.own_poll": "Tu encuesta ha terminado",
"notification.poll": "Una encuesta en la que has votado ha terminado",
"notification.reblog": "{name} ha retooteado tu estado",

View file

@ -476,12 +476,12 @@
"notification.moderation-warning.learn_more": "Saber más",
"notification.moderation_warning": "Has recibido una advertencia de moderación",
"notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.",
"notification.moderation_warning.action_disable": "Se ha desactivado su cuenta.",
"notification.moderation_warning.action_disable": "Tu cuenta ha sido desactivada.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.",
"notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderación.",
"notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarán como sensibles.",
"notification.moderation_warning.action_silence": "Se ha limitado tu cuenta.",
"notification.moderation_warning.action_suspend": "Se ha suspendido tu cuenta.",
"notification.moderation_warning.action_silence": "Tu cuenta ha sido limitada.",
"notification.moderation_warning.action_suspend": "Tu cuenta ha sido suspendida.",
"notification.own_poll": "Tu encuesta ha terminado",
"notification.poll": "Una encuesta en la que has votado ha terminado",
"notification.reblog": "{name} ha impulsado tu publicación",

View file

@ -298,7 +298,7 @@
"filter_modal.select_filter.title": "Suodata tämä julkaisu",
"filter_modal.title.status": "Suodata julkaisu",
"filtered_notifications_banner.mentions": "{count, plural, one {maininta} other {mainintaa}}",
"filtered_notifications_banner.pending_requests": "Sinulle on ilmoituksia mahdollisesti tuntemiltasi henkilöiltä seuraavasti: {count, plural, =0 {Ei keltään} one {Yhdeltä henkilöltä} other {# henkilöltä}}",
"filtered_notifications_banner.pending_requests": "Ilmoituksia {count, plural, =0 {ei ole} one {1 henkilöltä} other {# henkilöltä}}, jonka saatat tuntea",
"filtered_notifications_banner.title": "Suodatetut ilmoitukset",
"firehose.all": "Kaikki",
"firehose.local": "Tämä palvelin",
@ -308,7 +308,7 @@
"follow_requests.unlocked_explanation": "Vaikkei tiliäsi ole lukittu, palvelimen {domain} ylläpito on arvioinut, että saatat olla halukas tarkistamaan nämä seuraamispyynnöt erikseen.",
"follow_suggestions.curated_suggestion": "Ehdotus ylläpidolta",
"follow_suggestions.dismiss": "Älä näytä uudelleen",
"follow_suggestions.featured_longer": "Käsin valinnut palvelimen {domain} tiimi",
"follow_suggestions.featured_longer": "Valinnut käsin palvelimen {domain} tiimi",
"follow_suggestions.friends_of_friends_longer": "Suosittu seuraamiesi ihmisten keskuudessa",
"follow_suggestions.hints.featured": "Tämän profiilin on valinnut palvelimen {domain} tiimi.",
"follow_suggestions.hints.friends_of_friends": "Seuraamasi käyttäjät suosivat tätä profiilia.",

View file

@ -50,6 +50,8 @@
"admin.dashboard.retention.cohort_size": "Mga bagong tagagamit",
"alert.rate_limited.message": "Mangyaring subukan muli pagkatapos ng {retry_time, time, medium}.",
"audio.hide": "Itago ang tunog",
"block_modal.show_less": "Magpakita ng mas kaunti",
"block_modal.show_more": "Magpakita ng higit pa",
"block_modal.title": "Harangan ang tagagamit?",
"bundle_column_error.error.title": "Naku!",
"bundle_column_error.network.body": "Nagkaroon ng kamalian habang sinusubukang i-karga ang pahinang ito. Maaaring dahil ito sa pansamantalang problema ng iyong koneksyon sa internet o ang server na ito.",
@ -102,6 +104,7 @@
"compose_form.encryption_warning": "Ang mga post sa Mastodon ay hindi naka-encrypt nang dulo-dulo. Huwag magbahagi ng anumang sensitibong impormasyon sa Mastodon.",
"compose_form.hashtag_warning": "Hindi maililista ang post na ito sa anumang hashtag dahil hindi ito nakapubliko. Mga nakapublikong post lamang ang mahahanap ayon sa hashtag.",
"compose_form.placeholder": "Anong nangyari?",
"compose_form.poll.duration": "Tagal ng botohan",
"compose_form.poll.multiple": "Maraming pagpipilian",
"compose_form.poll.single": "Piliin ang isa",
"compose_form.reply": "Tumugon",
@ -173,6 +176,7 @@
"empty_column.list": "Wala pang laman ang listahang ito. Kapag naglathala ng mga bagong post ang mga miyembro ng listahang ito, makikita iyon dito.",
"empty_column.lists": "Wala ka pang mga listahan. Kapag gumawa ka ng isa, makikita yun dito.",
"explore.search_results": "Mga resulta ng paghahanap",
"explore.suggested_follows": "Mga tao",
"explore.title": "Tuklasin",
"explore.trending_links": "Mga balita",
"filter_modal.select_filter.search": "Hanapin o gumawa",
@ -186,9 +190,13 @@
"follow_suggestions.who_to_follow": "Sinong maaaring sundan",
"footer.about": "Tungkol dito",
"footer.get_app": "Kunin ang app",
"footer.status": "Katayuan",
"generic.saved": "Nakaimbak",
"hashtag.column_header.tag_mode.all": "at {additional}",
"hashtag.column_header.tag_mode.any": "o {additional}",
"hashtag.column_settings.tag_mode.all": "Lahat ng nandito",
"hashtag.column_settings.tag_mode.any": "Ilan sa nandito",
"hashtag.column_settings.tag_mode.none": "Wala dito",
"home.column_settings.show_replies": "Ipakita ang mga tugon",
"home.pending_critical_update.body": "Mangyaring i-update ang iyong serbiro ng Mastodon sa lalong madaling panahon!",
"interaction_modal.login.action": "Iuwi mo ako",
@ -199,6 +207,7 @@
"intervals.full.days": "{number, plural, one {# araw} other {# na araw}}",
"intervals.full.hours": "{number, plural, one {# oras} other {# na oras}}",
"intervals.full.minutes": "{number, plural, one {# minuto} other {# na minuto}}",
"keyboard_shortcuts.blocked": "Buksan ang talaan ng mga nakaharang na mga tagagamit",
"keyboard_shortcuts.description": "Paglalarawan",
"keyboard_shortcuts.down": "Ilipat pababa sa talaan",
"keyboard_shortcuts.mention": "Banggitin ang may-akda",
@ -218,7 +227,10 @@
"navigation_bar.about": "Tungkol dito",
"navigation_bar.blocks": "Nakaharang na mga tagagamit",
"navigation_bar.direct": "Mga palihim na banggit",
"navigation_bar.discover": "Tuklasin",
"navigation_bar.explore": "Tuklasin",
"navigation_bar.favourites": "Mga paborito",
"navigation_bar.follow_requests": "Mga hiling sa pagsunod",
"navigation_bar.follows_and_followers": "Mga sinusundan at tagasunod",
"navigation_bar.lists": "Mga listahan",
"navigation_bar.search": "Maghanap",
@ -226,6 +238,7 @@
"notification.follow": "Sinundan ka ni {name}",
"notification.follow_request": "Hinihiling ni {name} na sundan ka",
"notification.mention": "Binanggit ka ni {name}",
"notification.moderation_warning": "Mayroong kang natanggap na babala sa pagtitimpi",
"notification.relationships_severance_event.learn_more": "Matuto nang higit pa",
"notification_requests.accept": "Tanggapin",
"notification_requests.notifications_from": "Mga abiso mula kay/sa {name}",
@ -246,6 +259,7 @@
"onboarding.profile.note_hint": "Maaari mong @bangitin ang ibang mga tao o mga #hashtag…",
"onboarding.profile.save_and_continue": "Iimbak at magpatuloy",
"onboarding.share.next_steps": "Mga posibleng susunod na hakbang:",
"picture_in_picture.restore": "Ilagay ito pabalik",
"poll.closed": "Sarado",
"poll.reveal": "Ipakita ang mga resulta",
"poll.voted": "Binoto mo para sa sagot na ito",

View file

@ -474,11 +474,11 @@
"notification.follow_request": "{name} biður um at fylgja tær",
"notification.mention": "{name} nevndi teg",
"notification.moderation-warning.learn_more": "Lær meira",
"notification.moderation_warning": "Tú hevur móttikið eina umsjónarávarðing",
"notification.moderation_warning": "Tú hevur móttikið eina umsjónarávaring",
"notification.moderation_warning.action_delete_statuses": "Onkrir av tínum postum eru strikaðir.",
"notification.moderation_warning.action_disable": "Konta tín er gjørd óvirkin.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Nakrir av postum tínum eru merktir sum viðkvæmir.",
"notification.moderation_warning.action_none": "Konta tín hevur móttikið eina umsjónarávarðing.",
"notification.moderation_warning.action_none": "Konta tín hevur móttikið eina umsjónarávaring.",
"notification.moderation_warning.action_sensitive": "Postar tínir verða merktir sum viðkvæmir frá nú av.",
"notification.moderation_warning.action_silence": "Konta tín er avmarkað.",
"notification.moderation_warning.action_suspend": "Konta tín er ógildað.",

View file

@ -2,7 +2,7 @@
"about.blocks": "Servidores suxeitos a moderación",
"about.contact": "Contacto:",
"about.disclaimer": "Mastodon é software libre, de código aberto, e unha marca comercial de Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Motivo non indicado. ",
"about.domain_blocks.no_reason_available": "Motivo non indicado",
"about.domain_blocks.preamble": "Mastodon de xeito xeral permíteche ver contidos doutros servidores do fediverso e interactuar coas súas usuarias. Estas son as excepcións que se estabeleceron neste servidor en particular.",
"about.domain_blocks.silenced.explanation": "Por defecto non verás perfís e contido desde este servidor, a menos que mires de xeito explícito ou optes por seguir ese contido ou usuaria.",
"about.domain_blocks.silenced.title": "Limitado",
@ -92,7 +92,7 @@
"block_modal.remote_users_caveat": "Ímoslle pedir ao servidor {domain} que respecte a túa decisión. Emporiso, non hai garantía de que atenda a petición xa que os servidores xestionan os bloqueos de formas diferentes. As publicacións públicas poderían aínda ser visibles para usuarias que non iniciaron sesión.",
"block_modal.show_less": "Mostrar menos",
"block_modal.show_more": "Mostrar máis",
"block_modal.they_cant_mention": "Non te pode seguir nin mencionar.",
"block_modal.they_cant_mention": "Non te poden seguir nin mencionar.",
"block_modal.they_cant_see_posts": "Non pode ver as túas publicacións nin ti as de ela.",
"block_modal.they_will_know": "Pode ver que a bloqueaches.",
"block_modal.title": "Bloquear usuaria?",
@ -115,7 +115,7 @@
"closed_registrations_modal.find_another_server": "Atopa outro servidor",
"closed_registrations_modal.preamble": "Mastodon é descentralizado, así que non importa onde crees a conta, poderás seguir e interactuar con calquera conta deste servidor. Incluso podes ter o teu servidor!",
"closed_registrations_modal.title": "Crear conta en Mastodon",
"column.about": "Acerca de",
"column.about": "Sobre",
"column.blocks": "Usuarias bloqueadas",
"column.bookmarks": "Marcadores",
"column.community": "Cronoloxía local",
@ -322,7 +322,7 @@
"follow_suggestions.view_all": "Ver todas",
"follow_suggestions.who_to_follow": "A quen seguir",
"followed_tags": "Cancelos seguidos",
"footer.about": "Acerca de",
"footer.about": "Sobre",
"footer.directory": "Directorio de perfís",
"footer.get_app": "Descarga a app",
"footer.invite": "Convidar persoas",
@ -441,7 +441,7 @@
"mute_modal.title": "Acalar usuaria?",
"mute_modal.you_wont_see_mentions": "Non verás as publicacións que a mencionen.",
"mute_modal.you_wont_see_posts": "Seguirá podendo ler as túas publicacións, pero non verás as súas.",
"navigation_bar.about": "Acerca de",
"navigation_bar.about": "Sobre",
"navigation_bar.advanced_interface": "Abrir coa interface web avanzada",
"navigation_bar.blocks": "Usuarias bloqueadas",
"navigation_bar.bookmarks": "Marcadores",

View file

@ -205,6 +205,10 @@
"dismissable_banner.dismiss": "डिसमिस",
"dismissable_banner.explore_links": "इन समाचारों के बारे में लोगों द्वारा इस पर और डेसेंट्रलीसेड नेटवर्क के अन्य सर्वरों पर अभी बात की जा रही है।",
"dismissable_banner.explore_tags": "ये हैशटैग अभी इस पर और डेसेंट्रलीसेड नेटवर्क के अन्य सर्वरों पर लोगों के बीच कर्षण प्राप्त कर रहे हैं।",
"domain_block_modal.block": "सर्वर ब्लॉक करें",
"domain_block_modal.title": "डोमेन ब्लॉक करें",
"domain_pill.server": "सर्वर",
"domain_pill.username": "यूज़रनेम",
"embed.instructions": "अपने वेबसाइट पर, निचे दिए कोड को कॉपी करके, इस स्टेटस को एम्बेड करें",
"embed.preview": "यह ऐसा दिखेगा :",
"emoji_button.activity": "गतिविधि",
@ -274,6 +278,7 @@
"follow_request.authorize": "अधिकार दें",
"follow_request.reject": "अस्वीकार करें",
"follow_requests.unlocked_explanation": "हालाँकि आपका खाता लॉक नहीं है, फिर भी {domain} डोमेन स्टाफ ने सोचा कि आप इन खातों के मैन्युअल अनुरोधों की समीक्षा करना चाहते हैं।",
"follow_suggestions.dismiss": "दोबारा न दिखाएं",
"followed_tags": "फॉलो किए गए हैशटैग्स",
"footer.about": "अबाउट",
"footer.directory": "प्रोफाइल्स डायरेक्टरी",

View file

@ -19,7 +19,7 @@
"account.block_domain": "Blocar dominio {domain}",
"account.block_short": "Blocar",
"account.blocked": "Blocate",
"account.browse_more_on_origin_server": "Navigar plus sur le profilo original",
"account.browse_more_on_origin_server": "Percurrer plus sur le profilo original",
"account.cancel_follow_request": "Cancellar sequimento",
"account.copy": "Copiar ligamine a profilo",
"account.direct": "Mentionar privatemente @{name}",
@ -122,7 +122,7 @@
"column.direct": "Mentiones private",
"column.directory": "Navigar profilos",
"column.domain_blocks": "Dominios blocate",
"column.favourites": "Favoritos",
"column.favourites": "Favorites",
"column.firehose": "Fluxos in directo",
"column.follow_requests": "Requestas de sequimento",
"column.home": "Initio",
@ -204,7 +204,7 @@
"disabled_account_banner.account_settings": "Parametros de conto",
"disabled_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate.",
"dismissable_banner.community_timeline": "Ecce le messages public le plus recente del personas con contos sur {domain}.",
"dismissable_banner.dismiss": "Dimitter",
"dismissable_banner.dismiss": "Clauder",
"dismissable_banner.explore_links": "Istes es le articulos de novas que se condivide le plus sur le rete social hodie. Le articulos de novas le plus recente, publicate per plus personas differente, se classifica plus in alto.",
"dismissable_banner.explore_statuses": "Ecce le messages de tote le rete social que gania popularitate hodie. Le messages plus nove con plus impulsos e favorites se classifica plus in alto.",
"dismissable_banner.explore_tags": "Ecce le hashtags que gania popularitate sur le rete social hodie. Le hashtags usate per plus personas differente se classifica plus in alto.",
@ -212,8 +212,8 @@
"domain_block_modal.block": "Blocar le servitor",
"domain_block_modal.block_account_instead": "Blocar @{name} in su loco",
"domain_block_modal.they_can_interact_with_old_posts": "Le personas de iste servitor pote interager con tu messages ancian.",
"domain_block_modal.they_cant_follow": "Nulle persona ab iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Illes non sapera que illes ha essite blocate.",
"domain_block_modal.they_cant_follow": "Necuno de iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Ille non sapera que ille ha essite blocate.",
"domain_block_modal.title": "Blocar dominio?",
"domain_block_modal.you_will_lose_followers": "Omne sequitores ab iste servitor essera removite.",
"domain_block_modal.you_wont_see_posts": "Tu non videra messages e notificationes ab usatores sur iste servitor.",
@ -307,7 +307,9 @@
"follow_request.reject": "Rejectar",
"follow_requests.unlocked_explanation": "Benque tu conto non es serrate, le personal de {domain} pensa que es un bon idea que tu revide manualmente le sequente requestas de iste contos.",
"follow_suggestions.curated_suggestion": "Selection del equipa",
"follow_suggestions.dismiss": "Non monstrar novemente",
"follow_suggestions.dismiss": "Non monstrar de novo",
"follow_suggestions.featured_longer": "Seligite con cura per le equipa de {domain}",
"follow_suggestions.friends_of_friends_longer": "Popular inter le gente que tu seque",
"follow_suggestions.hints.featured": "Iste profilo ha essite seligite manualmente per le equipa de {domain}.",
"follow_suggestions.hints.friends_of_friends": "Iste profilo es popular inter le gente que tu seque.",
"follow_suggestions.hints.most_followed": "Iste profilo es un del plus sequites sur {domain}.",
@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Iste profilo es similar al profilos que tu ha recentemente sequite.",
"follow_suggestions.personalized_suggestion": "Suggestion personalisate",
"follow_suggestions.popular_suggestion": "Suggestion personalisate",
"follow_suggestions.popular_suggestion_longer": "Popular sur {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Similar al profilos que tu ha sequite recentemente",
"follow_suggestions.view_all": "Vider toto",
"follow_suggestions.who_to_follow": "Qui sequer",
"followed_tags": "Hashtags sequite",
@ -408,7 +412,7 @@
"lightbox.next": "Sequente",
"lightbox.previous": "Precedente",
"limited_account_hint.action": "Monstrar profilo in omne caso",
"limited_account_hint.title": "Iste profilo esseva celate per le moderatores de {domain}.",
"limited_account_hint.title": "Iste profilo ha essite celate per le moderatores de {domain}.",
"link_preview.author": "Per {name}",
"lists.account.add": "Adder al lista",
"lists.account.remove": "Remover del lista",
@ -428,12 +432,12 @@
"loading_indicator.label": "Cargante…",
"media_gallery.toggle_visible": "{number, plural, one {Celar imagine} other {Celar imagines}}",
"moved_to_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate perque tu ha cambiate de conto a {movedToAccount}.",
"mute_modal.hide_from_notifications": "Celar ab notificationes",
"mute_modal.hide_from_notifications": "Celar in notificationes",
"mute_modal.hide_options": "Celar optiones",
"mute_modal.indefinite": "Usque io dissilentia iste persona",
"mute_modal.show_options": "Monstrar optiones",
"mute_modal.they_can_mention_and_follow": "Illes pote mentionar te e sequer te, ma tu non potera vider los.",
"mute_modal.they_wont_know": "Illes non sapera que illes ha essite silentiate.",
"mute_modal.they_can_mention_and_follow": "Ille pote mentionar te e sequer te, ma tu non potera vider le.",
"mute_modal.they_wont_know": "Ille non sapera que ille ha essite silentiate.",
"mute_modal.title": "Silentiar le usator?",
"mute_modal.you_wont_see_mentions": "Tu non videra le messages que mentiona iste persona.",
"mute_modal.you_wont_see_posts": "Iste persona pote totevia vider tu messages, ma tu non videra le sues.",
@ -447,13 +451,13 @@
"navigation_bar.discover": "Discoperir",
"navigation_bar.domain_blocks": "Dominios blocate",
"navigation_bar.explore": "Explorar",
"navigation_bar.favourites": "Favoritos",
"navigation_bar.favourites": "Favorites",
"navigation_bar.filters": "Parolas silentiate",
"navigation_bar.follow_requests": "Requestas de sequimento",
"navigation_bar.followed_tags": "Hashtags sequite",
"navigation_bar.follows_and_followers": "Sequites e sequitores",
"navigation_bar.lists": "Listas",
"navigation_bar.logout": "Clauder le session",
"navigation_bar.logout": "Clauder session",
"navigation_bar.mutes": "Usatores silentiate",
"navigation_bar.opened_in_classic_interface": "Messages, contos e altere paginas specific es aperite per predefinition in le interfacie web classic.",
"navigation_bar.personal": "Personal",
@ -470,6 +474,14 @@
"notification.follow_request": "{name} ha requestate de sequer te",
"notification.mention": "{name} te ha mentionate",
"notification.moderation-warning.learn_more": "Apprender plus",
"notification.moderation_warning": "Tu ha recepite un aviso de moderation",
"notification.moderation_warning.action_delete_statuses": "Alcunes de tu messages ha essite removite.",
"notification.moderation_warning.action_disable": "Tu conto ha essite disactivate.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcunes de tu messages ha essite marcate como sensibile.",
"notification.moderation_warning.action_none": "Tu conto ha recipite un advertimento de moderation.",
"notification.moderation_warning.action_sensitive": "Tu messages essera marcate como sensibile a partir de ora.",
"notification.moderation_warning.action_silence": "Tu conto ha essite limitate.",
"notification.moderation_warning.action_suspend": "Tu conto ha essite suspendite.",
"notification.own_poll": "Tu sondage ha finite",
"notification.poll": "Un sondage in le qual tu ha votate ha finite",
"notification.reblog": "{name} ha impulsate tu message",
@ -489,7 +501,7 @@
"notifications.column_settings.admin.report": "Nove signalationes:",
"notifications.column_settings.admin.sign_up": "Nove inscriptiones:",
"notifications.column_settings.alert": "Notificationes de scriptorio",
"notifications.column_settings.favourite": "Favoritos:",
"notifications.column_settings.favourite": "Favorites:",
"notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias",
"notifications.column_settings.filter_bar.category": "Barra de filtro rapide",
"notifications.column_settings.follow": "Nove sequitores:",
@ -506,7 +518,7 @@
"notifications.column_settings.update": "Modificationes:",
"notifications.filter.all": "Toto",
"notifications.filter.boosts": "Impulsos",
"notifications.filter.favourites": "Favoritos",
"notifications.filter.favourites": "Favorites",
"notifications.filter.follows": "Sequites",
"notifications.filter.mentions": "Mentiones",
"notifications.filter.polls": "Resultatos del sondage",
@ -705,7 +717,7 @@
"status.edited": "Ultime modification le {date}",
"status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}",
"status.embed": "Incastrar",
"status.favourite": "Adder al favoritos",
"status.favourite": "Adder al favorites",
"status.favourites": "{count, plural, one {favorite} other {favorites}}",
"status.filter": "Filtrar iste message",
"status.filtered": "Filtrate",

View file

@ -89,6 +89,7 @@
"announcement.announcement": "Proclamation",
"attachments_list.unprocessed": "(íntractat)",
"audio.hide": "Celar audio",
"block_modal.remote_users_caveat": "Noi va petir que li servitor {domain} mey respecter tui decision. Támen, obedientie ne es garantit pro que chascun servitor gere bloccas diferentmen. Possibilmen public postas va restar visibil a usatores de inloggat.",
"block_modal.show_less": "Monstrar minu",
"block_modal.show_more": "Monstrar plu",
"block_modal.they_cant_mention": "Ne posse mentionar ni sequer te.",
@ -212,13 +213,23 @@
"domain_block_modal.block_account_instead": "Altrimen, bloccar @{name}",
"domain_block_modal.they_can_interact_with_old_posts": "Persones de ti servitor posse interacter con tui old postas.",
"domain_block_modal.they_cant_follow": "Nequi de ti-ci servitor posse sequer te.",
"domain_block_modal.they_wont_know": "Ne va esser conscient pri li bloccada.",
"domain_block_modal.title": "Bloccar dominia?",
"domain_block_modal.you_will_lose_followers": "Omni tui sequitores de ti-ci servitor va esser efaciat.",
"domain_block_modal.you_wont_see_posts": "Tu ne va vider postas ni notificationes de usatores sur ti-ci servitor.",
"domain_pill.activitypub_lets_connect": "It possibilisa tui conexiones e interactiones con persones ne solmen sur Mastodon, ma anc tra diferent social aplis.",
"domain_pill.activitypub_like_language": "ActivityPub es li lingue usat de Mastodon por parlar con altri social retages.",
"domain_pill.server": "Servitor",
"domain_pill.their_handle": "Identificator:",
"domain_pill.their_server": "Su digital hem e omni su postas.",
"domain_pill.their_username": "Su unic identificator sur su servitor. It es possibil que altri servitores va haver usatores con li sam nómine.",
"domain_pill.username": "Usator-nómine",
"domain_pill.whats_in_a_handle": "Ex quo consiste un identificator?",
"domain_pill.who_they_are": "Pro que identificatores informa qui e u un person is, tu posse interacter con persones tra li rete social de <button>ActivityPub-usant platformes</button>.",
"domain_pill.who_you_are": "Pro que tui identificator informa qui e u tu es, persones posse interacter con te tra li rete social de <button>ActivityPub-usant platformes</button>.",
"domain_pill.your_handle": "Tui identificator:",
"domain_pill.your_server": "Tui digital hem, u trova se omni tui postas. Si it ne plese te, tu posse transferer ad un altri servitor quandecunc e tui sequitores con te.",
"domain_pill.your_username": "Tui unic identificator sur ti-ci servitor. It es possibil que altri servitores va haver usatores con li sam nómine.",
"embed.instructions": "Inbedar ti-ci posta per copiar li code in infra.",
"embed.preview": "Vi qualmen it va aspecter:",
"emoji_button.activity": "Activitá",
@ -286,6 +297,7 @@
"filter_modal.select_filter.subtitle": "Usar un existent categorie o crear nov",
"filter_modal.select_filter.title": "Filtrar ti-ci posta",
"filter_modal.title.status": "Filtrar un posta",
"filtered_notifications_banner.mentions": "{count, plural, one {mention} other {mentiones}}",
"filtered_notifications_banner.pending_requests": "Notificationes de {count, plural, =0 {nequi} one {un person} other {# persones}} quel tu possibilmen conosse",
"filtered_notifications_banner.title": "Filtrat notificationes",
"firehose.all": "Omno",
@ -296,6 +308,8 @@
"follow_requests.unlocked_explanation": "Benque tu conto ne es cludet, li administratores de {domain} pensat que tu fórsan vell voler tractar seque-petitiones de tis-ci contos manualmen.",
"follow_suggestions.curated_suggestion": "Selection del employates",
"follow_suggestions.dismiss": "Ne monstrar plu",
"follow_suggestions.featured_longer": "Selectet manualmen del equip de {domain}",
"follow_suggestions.friends_of_friends_longer": "Populari ínter li persones queles tu seque",
"follow_suggestions.hints.featured": "Ti-ci profil ha esset selectet directmen del equip de {domain}.",
"follow_suggestions.hints.friends_of_friends": "Ti-ci profil es populari ínter tis qui tu seque.",
"follow_suggestions.hints.most_followed": "Ti-ci profil es un del max sequet sur {domain}.",
@ -303,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Ti-ci profil es simil al profiles queles tu ha recentmen sequet.",
"follow_suggestions.personalized_suggestion": "Personalisat suggestion",
"follow_suggestions.popular_suggestion": "Populari suggestion",
"follow_suggestions.popular_suggestion_longer": "Populari sur {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Simil a profiles queles tu sequet recentmen",
"follow_suggestions.view_all": "Vider omnicos",
"follow_suggestions.who_to_follow": "Persones a sequer",
"followed_tags": "Sequet hashtags",
@ -423,6 +439,8 @@
"mute_modal.they_can_mention_and_follow": "Posse mentionar e sequer te, ma va esser ínvisibil a te.",
"mute_modal.they_wont_know": "Ne va esser conscient pri li silentation.",
"mute_modal.title": "Silentiar usator?",
"mute_modal.you_wont_see_mentions": "Tu ne va vider postas mentionant li usator.",
"mute_modal.you_wont_see_posts": "Ne posse vider tui postas e inversi.",
"navigation_bar.about": "Information",
"navigation_bar.advanced_interface": "Aperter in li web-interfacie avansat",
"navigation_bar.blocks": "Bloccat usatores",
@ -455,10 +473,23 @@
"notification.follow": "{name} sequet te",
"notification.follow_request": "{name} ha petit sequer te",
"notification.mention": "{name} mentionat te",
"notification.moderation-warning.learn_more": "Aprender plu",
"notification.moderation_warning": "Tu ha recivet un moderatori advertiment",
"notification.moderation_warning.action_delete_statuses": "Alcun de tui postas ha esset efaciat.",
"notification.moderation_warning.action_disable": "Tui conto ha esset desactivisat.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcun de tui postas ha esset marcat quam sensitiv.",
"notification.moderation_warning.action_none": "Tui conto ha recivet un moderatori advertiment.",
"notification.moderation_warning.action_sensitive": "Desde nu tui postas va esser marcat quam sensitiv.",
"notification.moderation_warning.action_silence": "Tui conto ha esset limitat.",
"notification.moderation_warning.action_suspend": "Tui conto ha esset suspendet.",
"notification.own_poll": "Tui balotation ha finit",
"notification.poll": "Un balotation in quel tu votat ha finit",
"notification.reblog": "{name} boostat tui posta",
"notification.relationships_severance_event": "Perdit conexiones con {name}",
"notification.relationships_severance_event.account_suspension": "Un admin de {from} ha suspendet {target}, dunc con ti person tu ne plu posse reciver actualisationes ni far interactiones.",
"notification.relationships_severance_event.domain_block": "Un admin de {from} ha bloccat {target}, includente {followersCount} de tui sequitores e {followingCount, plural, one {# conto} other {# contos}} sequet de te.",
"notification.relationships_severance_event.learn_more": "Aprender plu",
"notification.relationships_severance_event.user_domain_block": "Tu ha bloccat {target}, efaciante {followersCount} de tui sequitores e {followingCount, plural, one {# conto} other {# contos}} sequet de te.",
"notification.status": "{name} just postat",
"notification.update": "{name} modificat un posta",
"notification_requests.accept": "Acceptar",
@ -472,6 +503,7 @@
"notifications.column_settings.alert": "Notificationes sur li computator",
"notifications.column_settings.favourite": "Favorites:",
"notifications.column_settings.filter_bar.advanced": "Monstrar omni categories",
"notifications.column_settings.filter_bar.category": "Rapid filtre-barre",
"notifications.column_settings.follow": "Nov sequitores:",
"notifications.column_settings.follow_request": "Nov petitiones de sequer:",
"notifications.column_settings.mention": "Mentiones:",
@ -707,6 +739,7 @@
"status.reblog": "Boostar",
"status.reblog_private": "Boostar con li original visibilitá",
"status.reblogged_by": "{name} boostat",
"status.reblogs": "{count, plural, one {boost} other {boosts}}",
"status.reblogs.empty": "Ancor nequi ha boostat ti-ci posta. Quande alqui fa it, ilu va aparir ci.",
"status.redraft": "Deleter & redacter",
"status.remove_bookmark": "Remover marcator",

View file

@ -473,6 +473,15 @@
"notification.follow": "{name}さんにフォローされました",
"notification.follow_request": "{name}さんがあなたにフォローリクエストしました",
"notification.mention": "{name}さんがあなたに返信しました",
"notification.moderation-warning.learn_more": "さらに詳しく",
"notification.moderation_warning": "管理者から警告が来ています",
"notification.moderation_warning.action_delete_statuses": "あなたによるいくつかの投稿が削除されました。",
"notification.moderation_warning.action_disable": "あなたのアカウントは無効になりました。",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "あなたの投稿のいくつかは閲覧注意として判定されています。",
"notification.moderation_warning.action_none": "あなたのアカウントは管理者からの警告を受けています。",
"notification.moderation_warning.action_sensitive": "あなたの投稿はこれから閲覧注意としてマークされます。",
"notification.moderation_warning.action_silence": "あなたのアカウントは制限されています。",
"notification.moderation_warning.action_suspend": "あなたのアカウントは停止されました。",
"notification.own_poll": "アンケートが終了しました",
"notification.poll": "アンケートが終了しました",
"notification.reblog": "{name}さんがあなたの投稿をブーストしました",

View file

@ -1,7 +1,7 @@
{
"about.blocks": "Prižiūrimi serveriai",
"about.contact": "Kontaktai:",
"about.disclaimer": "Mastodon nemokama atvirojo kodo programa ir Mastodon gGmbH prekės ženklas.",
"about.disclaimer": "Mastodon tai nemokama atvirojo kodo programinė įranga ir Mastodon gGmbH prekės ženklas.",
"about.domain_blocks.no_reason_available": "Priežastis nepateikta",
"about.domain_blocks.preamble": "Mastodon paprastai leidžia peržiūrėti turinį ir bendrauti su naudotojais iš bet kurio kito fediverse esančio serverio. Šios yra išimtys, kurios buvo padarytos šiame konkrečiame serveryje.",
"about.domain_blocks.silenced.explanation": "Paprastai nematysi profilių ir turinio iš šio serverio, nebent jį aiškiai ieškosi arba pasirinksi jį sekdamas (-a).",
@ -30,7 +30,7 @@
"account.endorse": "Rodyti profilyje",
"account.featured_tags.last_status_at": "Paskutinis įrašas {date}",
"account.featured_tags.last_status_never": "Nėra įrašų",
"account.featured_tags.title": "{name} rekomenduojami saitažodžiai",
"account.featured_tags.title": "{name} rodomi saitažodžiai",
"account.follow": "Sekti",
"account.follow_back": "Sekti atgal",
"account.followers": "Sekėjai",
@ -38,13 +38,13 @@
"account.followers_counter": "{count, plural, one {{counter} sekėjas} few {{counter} sekėjai} many {{counter} sekėjo} other {{counter} sekėjų}}",
"account.following": "Sekama",
"account.following_counter": "{count, plural, one {{counter} sekimas} few {{counter} sekimai} many {{counter} sekimo} other {{counter} sekimų}}",
"account.follows.empty": "Šis (-i) naudotojas (-a) dar nieko neseka.",
"account.follows.empty": "Šis naudotojas dar nieko neseka.",
"account.go_to_profile": "Eiti į profilį",
"account.hide_reblogs": "Slėpti pakėlimus iš @{name}",
"account.in_memoriam": "Atminimui.",
"account.joined_short": "Prisijungė",
"account.languages": "Keisti prenumeruojamas kalbas",
"account.link_verified_on": "Šios nuorodos nuosavybė buvo patikrinta {date}.",
"account.link_verified_on": "Šios nuorodos nuosavybė buvo patikrinta {date}",
"account.locked_info": "Šios paskyros privatumo būsena nustatyta kaip užrakinta. Savininkas (-ė) rankiniu būdu peržiūri, kas gali sekti.",
"account.media": "Medija",
"account.mention": "Paminėti @{name}",
@ -59,7 +59,7 @@
"account.posts": "Įrašai",
"account.posts_with_replies": "Įrašai ir atsakymai",
"account.report": "Pranešti apie @{name}",
"account.requested": "Laukiama patvirtinimo. Spustelėk, jei nori atšaukti sekimo prašymą.",
"account.requested": "Laukiama patvirtinimo. Spustelėk, jei nori atšaukti sekimo prašymą",
"account.requested_follow": "{name} paprašė tave sekti",
"account.share": "Bendrinti @{name} profilį",
"account.show_reblogs": "Rodyti pakėlimus iš @{name}",
@ -82,7 +82,7 @@
"admin.impact_report.instance_followers": "Sekėjai, kuriuos prarastų mūsų naudotojai",
"admin.impact_report.instance_follows": "Sekėjai, kuriuos prarastų jų naudotojai",
"admin.impact_report.title": "Poveikio apibendrinimas",
"alert.rate_limited.message": "Pabandyk vėliau po {retry_time, time, medium}.",
"alert.rate_limited.message": "Bandyk vėliau po {retry_time, time, medium}.",
"alert.rate_limited.title": "Sparta ribota.",
"alert.unexpected.message": "Įvyko netikėta klaida.",
"alert.unexpected.title": "Ups!",
@ -92,7 +92,12 @@
"block_modal.remote_users_caveat": "Paprašysime serverio {domain} gerbti tavo sprendimą. Tačiau atitiktis negarantuojama, nes kai kurie serveriai gali skirtingai tvarkyti blokavimus. Vieši įrašai vis tiek gali būti matomi neprisijungusiems naudotojams.",
"block_modal.show_less": "Rodyti mažiau",
"block_modal.show_more": "Rodyti daugiau",
"boost_modal.combo": "Galima paspausti {combo}, kad praleisti kitą kartą.",
"block_modal.they_cant_mention": "Jie negali tave paminėti ar sekti.",
"block_modal.they_cant_see_posts": "Jie negali matyti tavo įrašus, o tu nematysi jų.",
"block_modal.they_will_know": "Jie mato, kad yra užblokuoti.",
"block_modal.title": "Blokuoti naudotoją?",
"block_modal.you_wont_see_mentions": "Nematysi įrašus, kuriuose jie paminimi.",
"boost_modal.combo": "Galima paspausti {combo}, kad praleisti tai kitą kartą",
"bundle_column_error.copy_stacktrace": "Kopijuoti klaidos ataskaitą",
"bundle_column_error.error.body": "Paprašytos puslapio nepavyko atvaizduoti. Tai gali būti dėl mūsų kodo klaidos arba naršyklės suderinamumo problemos.",
"bundle_column_error.error.title": "O, ne!",
@ -117,7 +122,7 @@
"column.direct": "Privatūs paminėjimai",
"column.directory": "Naršyti profilius",
"column.domain_blocks": "Užblokuoti domenai",
"column.favourites": "Mėgstamiausi",
"column.favourites": "Mėgstami",
"column.firehose": "Tiesioginiai srautai",
"column.follow_requests": "Sekimo prašymai",
"column.home": "Pagrindinis",
@ -143,8 +148,8 @@
"compose.published.open": "Atidaryti",
"compose.saved.body": "Įrašas išsaugotas.",
"compose_form.direct_message_warning_learn_more": "Sužinoti daugiau",
"compose_form.encryption_warning": "Mastodon įrašai nėra šifruojami nuo galo iki galo. Per Mastodon nesidalyk jokia slapta informacija.",
"compose_form.hashtag_warning": "Šis įrašas nebus įtraukta į jokį saitažodį, nes ji nėra vieša. Tik viešų įrašų galima ieškoti pagal saitažodį.",
"compose_form.encryption_warning": "Mastodon įrašai nėra visapusiškai šifruojami. Per Mastodon nesidalyk jokia slapta informacija.",
"compose_form.hashtag_warning": "Šis įrašas nebus įtrauktas į jokį saitažodį, nes ji nėra vieša. Tik viešų įrašų galima ieškoti pagal saitažodį.",
"compose_form.lock_disclaimer": "Tavo paskyra nėra {locked}. Bet kas gali sekti tave ir peržiūrėti tik sekėjams skirtus įrašus.",
"compose_form.lock_disclaimer.lock": "užrakinta",
"compose_form.placeholder": "Kas tavo mintyse?",
@ -152,7 +157,7 @@
"compose_form.poll.multiple": "Keli pasirinkimai",
"compose_form.poll.option_placeholder": "{number} parinktis",
"compose_form.poll.single": "Pasirinkti vieną",
"compose_form.poll.switch_to_multiple": "Keisti apklausą, kad būtų galima pasirinkti kelis pasirinkimus.",
"compose_form.poll.switch_to_multiple": "Keisti apklausą, kad būtų galima pasirinkti kelis pasirinkimus",
"compose_form.poll.switch_to_single": "Keisti apklausą, kad būtų galima pasirinkti vieną pasirinkimą",
"compose_form.poll.type": "Stilius",
"compose_form.publish": "Skelbti",
@ -172,16 +177,17 @@
"confirmations.delete_list.message": "Ar tikrai nori visam laikui ištrinti šį sąrašą?",
"confirmations.discard_edit_media.confirm": "Atmesti",
"confirmations.discard_edit_media.message": "Turi neišsaugotų medijos aprašymo ar peržiūros pakeitimų, vis tiek juos atmesti?",
"confirmations.domain_block.confirm": "Blokuoti serverį",
"confirmations.domain_block.message": "Ar tikrai, tikrai nori užblokuoti visą {domain}? Daugeliu atvejų užtenka kelių tikslinių blokavimų arba nutildymų. Šio domeno turinio nematysi jokiose viešose laiko skalėse ar pranešimuose. Tavo sekėjai iš to domeno bus pašalinti.",
"confirmations.edit.confirm": "Redaguoti",
"confirmations.edit.message": "Redaguojant dabar, bus perrašyta šiuo metu kuriama žinutė. Ar tikrai nori tęsti?",
"confirmations.logout.confirm": "Atsijungti",
"confirmations.logout.message": "Ar tikrai nori atsijungti?",
"confirmations.mute.confirm": "Nutildyti",
"confirmations.redraft.confirm": "Ištrinti ir parengti iš naujo",
"confirmations.redraft.message": "Ar tikrai nori ištrinti šį įrašą ir parengti jį iš naujo kaip juodraštį? Bus prarastos mėgstamiausios ir pakėlimai, o atsakymai į originalinį įrašą taps liekamojais.",
"confirmations.redraft.confirm": "Ištrinti ir perrašyti",
"confirmations.redraft.message": "Ar tikrai nori ištrinti šį įrašą ir parašyti jį iš naujo? Bus prarastos mėgstamai ir pakėlimai, o atsakymai į originalinį įrašą taps liekamojais.",
"confirmations.reply.confirm": "Atsakyti",
"confirmations.reply.message": "Atsakant dabar, bus perrašyta metu kuriama žinutė. Ar tikrai nori tęsti?",
"confirmations.reply.message": "Atsakant dabar, bus perrašyta šiuo metu kuriama žinutė. Ar tikrai nori tęsti?",
"confirmations.unfollow.confirm": "Nebesekti",
"confirmations.unfollow.message": "Ar tikrai nori nebesekti {name}?",
"conversation.delete": "Ištrinti pokalbį",
@ -196,34 +202,42 @@
"directory.new_arrivals": "Nauji atvykėliai",
"directory.recently_active": "Neseniai aktyvus (-i)",
"disabled_account_banner.account_settings": "Paskyros nustatymai",
"disabled_account_banner.text": "Tavo paskyra {disabledAccount} šiuo metu išjungta.",
"dismissable_banner.community_timeline": "Tai naujausi vieši įrašai, kuriuos paskelbė žmonės, kurių paskyros talpinamos {domain}.",
"disabled_account_banner.text": "Tavo paskyra {disabledAccount} šiuo metu yra išjungta.",
"dismissable_banner.community_timeline": "Tai naujausi vieši įrašai iš žmonių, kurių paskyros talpinamos {domain}.",
"dismissable_banner.dismiss": "Atmesti",
"dismissable_banner.explore_links": "Tai naujienos, kuriomis šiandien daugiausiai bendrinamasi socialiniame žiniatinklyje. Naujesnės naujienų istorijos, kurias paskelbė daugiau skirtingų žmonių, vertinamos aukščiau.",
"dismissable_banner.explore_statuses": "Tai įrašai iš viso socialinio žiniatinklio, kurie šiandien sulaukia daug dėmesio. Naujesni įrašai, turintys daugiau pakėlimų ir mėgstamų, vertinami aukščiau.",
"dismissable_banner.explore_tags": "Tai saitažodžiai, kurie šiandien sulaukia daug dėmesio socialiniame žiniatinklyje. Saitažodžiai, kuriuos naudoja daugiau skirtingų žmonių, vertinami aukščiau.",
"dismissable_banner.public_timeline": "Tai naujausi vieši įrašai, kuriuos socialiniame žiniatinklyje paskelbė žmonės, sekantys {domain}.",
"domain_pill.activitypub_lets_connect": "Tai leidžia tau bendrauti su žmonėmis ne tik Mastodon, bet ir įvairiose socialinėse programėlėse.",
"domain_pill.activitypub_like_language": "ActivityPub tarsi kalba, kuria Mastodon kalba su kitais socialiniais tinklais.",
"dismissable_banner.public_timeline": "Tai naujausi vieši įrašai iš žmonių socialiniame žiniatinklyje, kuriuos seka {domain} žmonės.",
"domain_block_modal.block": "Blokuoti serverį",
"domain_block_modal.block_account_instead": "Blokuoti {name} vietoj to",
"domain_block_modal.they_can_interact_with_old_posts": "Žmonės iš šio serverio gali sąveikauti su tavo senomis įrašomis.",
"domain_block_modal.they_cant_follow": "Niekas iš šio serverio negali tavęs sekti.",
"domain_block_modal.they_wont_know": "Jie nežinos, kad buvo užblokuoti.",
"domain_block_modal.title": "Blokuoti domeną?",
"domain_block_modal.you_will_lose_followers": "Visi tavo sekėjai iš šio serverio bus pašalinti.",
"domain_block_modal.you_wont_see_posts": "Nematysi naudotojų įrašų ar pranešimų šiame serveryje.",
"domain_pill.activitypub_lets_connect": "Tai leidžia tau sąveikauti su žmonėmis ne tik Mastodon, bet ir įvairiose socialinėse programėlėse.",
"domain_pill.activitypub_like_language": "ActivityPub tai tarsi kalba, kuria Mastodon kalba su kitais socialiniais tinklais.",
"domain_pill.server": "Serveris",
"domain_pill.their_handle": "Jų socialinis medijos vardas:",
"domain_pill.their_server": "Jų skaitmeniniai namai, kuriuose saugomi visi jų įrašai.",
"domain_pill.their_username": "Jų unikalus identifikatorius jų serveryje. Skirtinguose serveriuose galima rasti naudotojų, turinčių tą patį naudotojo vardą.",
"domain_pill.username": "Naudotojo vardas",
"domain_pill.whats_in_a_handle": "Kas yra socialiniame medijos varde?",
"domain_pill.who_they_are": "Kadangi socialines medijos vardai nurodo, kas ir kur jie yra, galima bendrauti su žmonėmis visame socialiniame tinkle, kuriame yra <button> ActivityPub valdomos platformos</button>.",
"domain_pill.who_you_are": "Kadangi tavo socialinis medijos vardas nurodo, kas esi ir kur esi, žmonės gali bendrauti su tavimi visame socialiniame tinkle, kurį sudaro <button> ActivityPub valdomos platformos</button>.",
"domain_pill.who_they_are": "Kadangi socialines medijos vardai nurodo, kas žmogus yra ir kur jie yra, gali sąveikauti su žmonėmis visame socialiniame žiniatinklyje, kurį sudaro <button>ActivityPub veikiančios platformos</button>.",
"domain_pill.who_you_are": "Kadangi tavo socialinis medijos vardas nurodo, kas esi ir kur esi, žmonės gali sąveikauti su tavimi visame socialiniame tinkle, kurį sudaro <button>ActivityPub veikiančios platformos</button>.",
"domain_pill.your_handle": "Tavo socialinis medijos vardas:",
"domain_pill.your_server": "Tavo skaitmeniniai namai, kuriuose saugomi visi tavo įrašai. Nepatinka šis? Bet kada perkelk serverius ir atsivesk ir savo sekėjus.",
"domain_pill.your_username": "Tavo unikalus identifikatorius šiame serveryje. Skirtinguose serveriuose galima rasti naudotojų, turinčių tą patį naudotojo vardą.",
"domain_pill.your_username": "Tavo unikalus identifikatorius šiame serveryje. Skirtinguose serveriuose galima rasti naudotojų su tuo pačiu naudotojo vardu.",
"embed.instructions": "Įterpk šį įrašą į savo svetainę nukopijavus (-usi) toliau pateiktą kodą.",
"embed.preview": "Štai, kaip tai atrodys:",
"embed.preview": "Štai kaip tai atrodys:",
"emoji_button.activity": "Veikla",
"emoji_button.clear": "Išvalyti",
"emoji_button.custom": "Pasirinktinis",
"emoji_button.flags": "Vėliavos",
"emoji_button.food": "Maistas ir gėrimai",
"emoji_button.label": "Įterpti veidelius",
"emoji_button.label": "Įterpti jaustuką",
"emoji_button.nature": "Gamta",
"emoji_button.not_found": "Nerasta jokių tinkamų jaustukų.",
"emoji_button.objects": "Objektai",
@ -234,26 +248,27 @@
"emoji_button.symbols": "Simboliai",
"emoji_button.travel": "Kelionės ir vietos",
"empty_column.account_hides_collections": "Šis (-i) naudotojas (-a) pasirinko nepadaryti šią informaciją prieinamą.",
"empty_column.account_suspended": "Paskyra sustabdyta.",
"empty_column.account_timeline": "Nėra įrašų čia.",
"empty_column.account_suspended": "Paskyra pristabdyta.",
"empty_column.account_timeline": "Nėra čia įrašų.",
"empty_column.account_unavailable": "Profilis neprieinamas.",
"empty_column.blocks": "Dar neužblokavai nė vieno naudotojo.",
"empty_column.bookmarked_statuses": "Dar neturi nė vienos įrašo žymės. Kai vieną iš jų pridėsi į žymes, jis bus rodomas čia.",
"empty_column.community": "Vietinė laiko skalė tuščia. Parašyk ką nors viešai, kad pradėtum bendrauti!",
"empty_column.bookmarked_statuses": "Dar neturi nė vienos įrašo pridėtos žymės. Kai vieną iš jų pridėsi į žymes, jis bus rodomas čia.",
"empty_column.community": "Vietinė laiko skalė yra tuščia. Parašyk ką nors viešai, kad pradėtum sąveikauti.",
"empty_column.direct": "Dar neturi jokių privačių paminėjimų. Kai išsiųsi arba gausi vieną iš jų, jis bus rodomas čia.",
"empty_column.domain_blocks": "Dar nėra užblokuotų domenų.",
"empty_column.explore_statuses": "Šiuo metu niekas nėra tendencinga. Patikrink vėliau.",
"empty_column.explore_statuses": "Šiuo metu niekas nėra tendencinga. Patikrink vėliau!",
"empty_column.favourited_statuses": "Dar neturi mėgstamų įrašų. Kai vieną iš jų pamėgsi, jis bus rodomas čia.",
"empty_column.favourites": "Šio įrašo dar niekas nepamėgo. Kai kas nors tai padarys, jie bus rodomi čia.",
"empty_column.follow_requests": "Dar neturi jokių sekimo prašymų. Kai gausi tokį prašymą, jis bus rodomas čia.",
"empty_column.followed_tags": "Dar neseki jokių saitažodžių. Kai tai padarysi, jie bus rodomi čia.",
"empty_column.hashtag": "Nėra nieko šiame saitažodyje kol kas.",
"empty_column.home": "Tavo pagrindinio laiko skalė tuščia! Sek daugiau žmonių, kad ją užpildytum.",
"empty_column.home": "Tavo pagrindinio laiko skalė tuščia. Sek daugiau žmonių, kad ją užpildytum.",
"empty_column.list": "Nėra nieko šiame sąraše kol kas. Kai šio sąrašo nariai paskelbs naujų įrašų, jie bus rodomi čia.",
"empty_column.lists": "Dar neturi jokių sąrašų. Kai jį sukursi, jis bus rodomas čia.",
"empty_column.mutes": "Dar nesi nutildęs (-usi) nė vieno naudotojo.",
"empty_column.notifications": "Dar neturi jokių pranešimų. Kai kiti žmonės su tavimi bendraus, matysi tai čia.",
"empty_column.public": "Čia nieko nėra! Parašyk ką nors viešai arba rankiniu būdu sek naudotojus iš kitų serverių, kad jį užpildytum.",
"empty_column.notification_requests": "Viskas švaru! Čia nieko nėra. Kai gausi naujų pranešimų, jie bus rodomi čia pagal tavo nustatymus.",
"empty_column.notifications": "Dar neturi jokių pranešimų. Kai kiti žmonės su tavimi sąveikaus, matysi tai čia.",
"empty_column.public": "Čia nieko nėra. Parašyk ką nors viešai arba rankiniu būdu sek naudotojus iš kitų serverių, kad jį užpildytum.",
"error.unexpected_crash.explanation": "Dėl mūsų kodo riktos arba naršyklės suderinamumo problemos šis puslapis negalėjo būti rodomas teisingai.",
"error.unexpected_crash.explanation_addons": "Šį puslapį nepavyko parodyti teisingai. Šią klaidą greičiausiai sukėlė naršyklės priedas arba automatinio vertimo įrankiai.",
"error.unexpected_crash.next_steps": "Pabandyk atnaujinti puslapį. Jei tai nepadeda, galbūt vis dar galėsi naudotis Mastodon per kitą naršyklę arba savąją programėlę.",
@ -270,9 +285,9 @@
"filter_modal.added.context_mismatch_title": "Konteksto neatitikimas.",
"filter_modal.added.expired_explanation": "Ši filtro kategorija nustojo galioti. Kad ji būtų taikoma, turėsi pakeisti galiojimo datą.",
"filter_modal.added.expired_title": "Baigėsi filtro galiojimas.",
"filter_modal.added.review_and_configure": "Norint peržiūrėti ir toliau konfigūruoti šią filtro kategoriją, eik į nuorodą {settings_link}.",
"filter_modal.added.review_and_configure": "Norint peržiūrėti ir toliau konfigūruoti šią filtro kategoriją, eik į {settings_link}.",
"filter_modal.added.review_and_configure_title": "Filtro nustatymai",
"filter_modal.added.settings_link": "nustatymų puslapis",
"filter_modal.added.settings_link": "nustatymų puslapį",
"filter_modal.added.short_explanation": "Šis įrašas buvo pridėtas į šią filtro kategoriją: {title}.",
"filter_modal.added.title": "Pridėtas filtras.",
"filter_modal.select_filter.context_mismatch": "netaikoma šiame kontekste.",
@ -283,6 +298,8 @@
"filter_modal.select_filter.title": "Filtruoti šį įrašą",
"filter_modal.title.status": "Filtruoti įrašą",
"filtered_notifications_banner.mentions": "{count, plural, one {paminėjimas} few {paminėjimai} many {paminėjimo} other {paminėjimų}}",
"filtered_notifications_banner.pending_requests": "Pranešimai iš {count, plural, =0 {nė vieno} one {vienos žmogaus} few {# žmonių} many {# žmonių} other {# žmonių}}, kuriuos galbūt pažįsti",
"filtered_notifications_banner.title": "Filtruojami pranešimai",
"firehose.all": "Visi",
"firehose.local": "Šis serveris",
"firehose.remote": "Kiti serveriai",
@ -295,8 +312,8 @@
"follow_suggestions.friends_of_friends_longer": "Populiarus tarp žmonių, kurių seki",
"follow_suggestions.hints.featured": "Šį profilį atrinko {domain} komanda.",
"follow_suggestions.hints.friends_of_friends": "Šis profilis yra populiarus tarp žmonių, kuriuos seki.",
"follow_suggestions.hints.most_followed": "Šis profilis yra vienas iš labiausiai sekamų {domain}.",
"follow_suggestions.hints.most_interactions": "Pastaruoju metu šis profilis sulaukia daug dėmesio šiame {domain}.",
"follow_suggestions.hints.most_followed": "Šis profilis yra vienas iš labiausiai sekamų domene {domain}.",
"follow_suggestions.hints.most_interactions": "Pastaruoju metu šis profilis sulaukia daug dėmesio domane {domain}.",
"follow_suggestions.hints.similar_to_recently_followed": "Šis profilis panašus į profilius, kuriuos neseniai sekei.",
"follow_suggestions.personalized_suggestion": "Suasmenintas pasiūlymas",
"follow_suggestions.popular_suggestion": "Populiarus pasiūlymas",
@ -312,8 +329,8 @@
"footer.keyboard_shortcuts": "Spartieji klavišai",
"footer.privacy_policy": "Privatumo politika",
"footer.source_code": "Peržiūrėti šaltinio kodą",
"footer.status": "Būsena",
"generic.saved": "Išsaugoti",
"footer.status": "Statusas",
"generic.saved": "Išsaugota",
"getting_started.heading": "Kaip pradėti",
"hashtag.column_header.tag_mode.all": "ir {additional}",
"hashtag.column_header.tag_mode.any": "ar {additional}",
@ -333,7 +350,7 @@
"home.column_settings.show_reblogs": "Rodyti pakėlimus",
"home.column_settings.show_replies": "Rodyti atsakymus",
"home.hide_announcements": "Slėpti skelbimus",
"home.pending_critical_update.body": "Kuo greičiau atnaujink savo Mastodon serverį!",
"home.pending_critical_update.body": "Kuo greičiau atnaujink savo Mastodon serverį.",
"home.pending_critical_update.link": "Žiūrėti naujinimus",
"home.pending_critical_update.title": "Galimas kritinis saugumo naujinimas.",
"home.show_announcements": "Rodyti skelbimus",
@ -415,6 +432,7 @@
"loading_indicator.label": "Kraunama…",
"media_gallery.toggle_visible": "{number, plural, one {Slėpti vaizdą} few {Slėpti vaizdus} many {Slėpti vaizdo} other {Slėpti vaizdų}}",
"moved_to_account_banner.text": "Tavo paskyra {disabledAccount} šiuo metu išjungta, nes persikėlei į {movedToAccount}.",
"mute_modal.show_options": "Rodyti parinktis",
"navigation_bar.about": "Apie",
"navigation_bar.advanced_interface": "Atidaryti išplėstinę žiniatinklio sąsają",
"navigation_bar.blocks": "Užblokuoti naudotojai",
@ -448,7 +466,6 @@
"notification.follow_request": "{name} paprašė tave sekti",
"notification.mention": "{name} paminėjo tave",
"notification.moderation-warning.learn_more": "Sužinoti daugiau",
"notification.moderation_warning": "Gavai prižiūrėjimo įspėjimą",
"notification.moderation_warning.action_delete_statuses": "Kai kurie tavo įrašai buvo pašalintos.",
"notification.moderation_warning.action_disable": "Tavo paskyra buvo išjungta.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Kai kurie tavo įrašai buvo pažymėtos kaip jautrios.",
@ -479,7 +496,7 @@
"notifications.column_settings.follow_request": "Nauji sekimo prašymai:",
"notifications.column_settings.mention": "Paminėjimai:",
"notifications.column_settings.poll": "Balsavimo rezultatai:",
"notifications.column_settings.push": "Stumdomieji pranešimai",
"notifications.column_settings.push": "Tiesioginiai pranešimai",
"notifications.column_settings.reblog": "Pakėlimai:",
"notifications.column_settings.show": "Rodyti stulpelyje",
"notifications.column_settings.sound": "Paleisti garsą",
@ -518,7 +535,7 @@
"onboarding.follows.lead": "Tavo pagrindinis srautas pagrindinis būdas patirti Mastodon. Kuo daugiau žmonių seksi, tuo jis bus aktyvesnis ir įdomesnis. Norint pradėti, pateikiame keletą pasiūlymų:",
"onboarding.follows.title": "Suasmenink savo pagrindinį srautą",
"onboarding.profile.discoverable": "Padaryti mano profilį atrandamą",
"onboarding.profile.discoverable_hint": "Kai pasirenki Mastodon atrandamumą, tavo įrašai gali būti rodomi paieškos rezultatuose ir tendencijose, o profilis gali būti siūlomas panašių pomėgių turintiems žmonėms.",
"onboarding.profile.discoverable_hint": "Kai sutinki su Mastodon atrandamumu, tavo įrašai gali būti rodomi paieškos rezultatuose ir tendencijose, o profilis gali būti siūlomas panašių pomėgių turintiems žmonėms.",
"onboarding.profile.display_name": "Rodomas vardas",
"onboarding.profile.display_name_hint": "Tavo pilnas vardas arba linksmas vardas…",
"onboarding.profile.lead": "Gali visada tai užbaigti vėliau nustatymuose, kur yra dar daugiau pritaikymo parinkčių.",

View file

@ -8,7 +8,7 @@
"about.domain_blocks.silenced.title": "Ierobežotie",
"about.domain_blocks.suspended.explanation": "Nekādi dati no šī servera netiks apstrādāti, uzglabāti vai apmainīti, padarot neiespējamu mijiedarbību vai saziņu ar lietotājiem no šī servera.",
"about.domain_blocks.suspended.title": "Apturētie",
"about.not_available": "Šī informācija šajā serverī nav bijusi pieejama.",
"about.not_available": "Šī informācija nav padarīta pieejama šajā serverī.",
"about.powered_by": "Decentralizētu sociālo tīklu nodrošina {mastodon}",
"about.rules": "Servera noteikumi",
"account.account_note_header": "Piezīme",
@ -89,6 +89,9 @@
"announcement.announcement": "Paziņojums",
"attachments_list.unprocessed": "(neapstrādāti)",
"audio.hide": "Slēpt audio",
"block_modal.remote_users_caveat": "Mēs vaicāsim serverim {domain} ņemt vērā Tavu lēmumu. Tomēr atbilstība nav nodrošināta, jo atsevišķi serveri var apstrādāt bloķēšanu citādi. Publiski ieraksti joprojām var būt redzami lietotājiem, kuri nav pieteikušies.",
"block_modal.show_less": "Parādīt vairāk",
"block_modal.show_more": "Parādīt mazāk",
"boost_modal.combo": "Nospied {combo}, lai nākamreiz šo izlaistu",
"bundle_column_error.copy_stacktrace": "Kopēt kļūdu ziņojumu",
"bundle_column_error.error.body": "Pieprasīto lapu nevarēja atveidot. Tas varētu būt saistīts ar kļūdu mūsu kodā, vai tā ir pārlūkprogrammas saderības problēma.",
@ -190,7 +193,7 @@
"directory.federated": "No pazīstamas federācijas",
"directory.local": "Tikai no {domain}",
"directory.new_arrivals": "Jaunpienācēji",
"directory.recently_active": "Nesen aktīvie",
"directory.recently_active": "Nesen aktīvi",
"disabled_account_banner.account_settings": "Konta iestatījumi",
"disabled_account_banner.text": "Tavs konts {disabledAccount} pašlaik ir atspējots.",
"dismissable_banner.community_timeline": "Šie ir jaunākie publiskie ieraksti no cilvēkiem, kuru konti ir mitināti {domain}.",
@ -199,6 +202,9 @@
"dismissable_banner.explore_statuses": "Šie ir ieraksti, kas šodien gūst arvien lielāku ievērību visā sociālajā tīklā. Augstāk tiek kārtoti jaunāki ieraksti, kuri tiek vairāk pastiprināti un ievietoti izlasēs.",
"dismissable_banner.explore_tags": "Šie tēmturi šobrīd kļūst arvien populārāki cilvēku vidū šajā un citos decentralizētā tīkla serveros.",
"dismissable_banner.public_timeline": "Šie ir jaunākie publiskie ieraksti no lietotājiem sociālajā tīmeklī, kuriem {domain} seko cilvēki.",
"domain_block_modal.they_cant_follow": "Neviens šajā serverī nevar Tev sekot.",
"domain_pill.server": "Serveris",
"domain_pill.username": "Lietotājvārds",
"embed.instructions": "Iestrādā šo ziņu savā mājaslapā, kopējot zemāk redzamo kodu.",
"embed.preview": "Tas izskatīsies šādi:",
"emoji_button.activity": "Aktivitāte",
@ -275,6 +281,7 @@
"follow_suggestions.curated_suggestion": "Darbinieku izvēle",
"follow_suggestions.dismiss": "Vairs nerādīt",
"follow_suggestions.personalized_suggestion": "Pielāgots ieteikums",
"follow_suggestions.similar_to_recently_followed_longer": "Līdzīgi profieliem, kuriem nesen sāki sekot",
"follow_suggestions.view_all": "Skatīt visu",
"follow_suggestions.who_to_follow": "Kam sekot",
"followed_tags": "Sekojamie tēmturi",
@ -388,6 +395,10 @@
"loading_indicator.label": "Ielādē…",
"media_gallery.toggle_visible": "{number, plural, one {Slēpt attēlu} other {Slēpt attēlus}}",
"moved_to_account_banner.text": "Tavs konts {disabledAccount} pašlaik ir atspējots, jo Tu pārcēlies uz kontu {movedToAccount}.",
"mute_modal.hide_from_notifications": "Paslēpt paziņojumos",
"mute_modal.hide_options": "Paslēpt iespējas",
"mute_modal.show_options": "Parādīt iespējas",
"mute_modal.title": "Apklusināt lietotāju?",
"navigation_bar.about": "Par",
"navigation_bar.advanced_interface": "Atvērt paplašinātā tīmekļa saskarnē",
"navigation_bar.blocks": "Bloķētie lietotāji",
@ -420,11 +431,23 @@
"notification.follow": "{name} uzsāka Tev sekot",
"notification.follow_request": "{name} nosūtīja Tev sekošanas pieprasījumu",
"notification.mention": "{name} pieminēja Tevi",
"notification.moderation-warning.learn_more": "Uzzināt vairāk",
"notification.moderation_warning.action_delete_statuses": "Daži no Taviem ierakstiem tika noņemti.",
"notification.moderation_warning.action_disable": "Tavs konts tika atspējots.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Daži no Taviem ierakstiem tika atzīmēti kā jutīgi.",
"notification.moderation_warning.action_sensitive": "Tavi ieraksti turpmāk tiks atzīmēti kā jutīgi.",
"notification.moderation_warning.action_silence": "Tavs konts tika ierobežots.",
"notification.moderation_warning.action_suspend": "Tava konta darbība tika apturēta.",
"notification.own_poll": "Tava aptauja ir noslēgusies",
"notification.poll": "Aptauja, kurā tu piedalījies, ir noslēgusies",
"notification.reblog": "{name} pastiprināja Tavu ierakstu",
"notification.relationships_severance_event": "Zaudēti savienojumi ar {name}",
"notification.relationships_severance_event.learn_more": "Uzzināt vairāk",
"notification.status": "{name} tikko publicēja",
"notification.update": "{name} rediģēja ierakstu",
"notification_requests.accept": "Pieņemt",
"notification_requests.dismiss": "Noraidīt",
"notification_requests.notifications_from": "Paziņojumi no {name}",
"notifications.clear": "Notīrīt paziņojumus",
"notifications.clear_confirmation": "Vai tiešām vēlies neatgriezeniski notīrīt visus savus paziņojumus?",
"notifications.column_settings.admin.report": "Jauni ziņojumi:",
@ -456,6 +479,9 @@
"notifications.permission_denied": "Darbvirsmas paziņojumi nav pieejami, jo iepriekš tika noraidīts pārlūka atļauju pieprasījums",
"notifications.permission_denied_alert": "Darbvirsmas paziņojumus nevar iespējot, jo pārlūkprogrammai atļauja tika iepriekš atteikta",
"notifications.permission_required": "Darbvirsmas paziņojumi nav pieejami, jo nav piešķirta nepieciešamā atļauja.",
"notifications.policy.filter_new_accounts_title": "Jauni konti",
"notifications.policy.filter_not_followers_title": "Cilvēki, kuri Tev neseko",
"notifications.policy.filter_not_following_title": "Cilvēki, kuriem Tu neseko",
"notifications_permission_banner.enable": "Iespējot darbvirsmas paziņojumus",
"notifications_permission_banner.how_to_control": "Lai saņemtu paziņojumus, kad Mastodon nav atvērts, iespējo darbvirsmas paziņojumus. Vari precīzi kontrolēt, kāda veida mijiedarbības rada darbvirsmas paziņojumus, izmantojot augstāk redzamo pogu {icon}, kad tie būs iespējoti.",
"notifications_permission_banner.title": "Nekad nepalaid neko garām",
@ -485,7 +511,7 @@
"onboarding.start.title": "Tev tas izdevās!",
"onboarding.steps.follow_people.body": "Tu pats veido savu plūsmu. Piepildīsim to ar interesantiem cilvēkiem.",
"onboarding.steps.follow_people.title": "Pielāgo savu mājas barotni",
"onboarding.steps.publish_status.body": "Sveicini pasauli ar tekstu, fotoattēliem, video, vai aptaujām {emoji}",
"onboarding.steps.publish_status.body": "Pasveicini pasauli ar tekstu, attēliem, video vai aptaujām {emoji}",
"onboarding.steps.publish_status.title": "Izveido savu pirmo ziņu",
"onboarding.steps.setup_profile.body": "Palielini mijiedarbību ar aptverošu profilu!",
"onboarding.steps.setup_profile.title": "Pielāgo savu profilu",
@ -603,7 +629,7 @@
"search_results.statuses": "Ieraksti",
"search_results.title": "Meklēt {q}",
"server_banner.about_active_users": "Cilvēki, kas izmantojuši šo serveri pēdējo 30 dienu laikā (aktīvie lietotāji mēnesī)",
"server_banner.active_users": "aktīvie lietotāji",
"server_banner.active_users": "aktīvi lietotāji",
"server_banner.administered_by": "Administrē:",
"server_banner.introduction": "{domain} ir daļa no decentralizētā sociālā tīkla, ko nodrošina {mastodon}.",
"server_banner.learn_more": "Uzzināt vairāk",
@ -625,6 +651,7 @@
"status.direct": "Pieminēt @{name} privāti",
"status.direct_indicator": "Pieminēts privāti",
"status.edit": "Labot",
"status.edited": "Pēdējoreiz labots {date}",
"status.edited_x_times": "Labots {count, plural, one {{count} reizi} other {{count} reizes}}",
"status.embed": "Iegult",
"status.favourite": "Izlasē",

View file

@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Apesar de seu perfil não ser trancado, {domain} exige que você revise a solicitação para te seguir destes perfis manualmente.",
"follow_suggestions.curated_suggestion": "Escolha da equipe",
"follow_suggestions.dismiss": "Não mostrar novamente",
"follow_suggestions.featured_longer": "Escolhido à mão pela equipe de {domain}",
"follow_suggestions.friends_of_friends_longer": "Popular entre as pessoas que você segue",
"follow_suggestions.hints.featured": "Este perfil foi escolhido a dedo pela equipe {domain}.",
"follow_suggestions.hints.friends_of_friends": "Este perfil é popular entre as pessoas que você segue.",
"follow_suggestions.hints.most_followed": "Este perfil é um dos mais seguidos em {domain}.",
@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Este perfil é semelhante aos perfis que você seguiu recentemente.",
"follow_suggestions.personalized_suggestion": "Sugestão personalizada",
"follow_suggestions.popular_suggestion": "Sugestão popular",
"follow_suggestions.popular_suggestion_longer": "Popular em {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Similar a perfis que você seguiu recentemente",
"follow_suggestions.view_all": "Visualizar tudo",
"follow_suggestions.who_to_follow": "Quem seguir",
"followed_tags": "Hashtags seguidas",
@ -469,6 +473,14 @@
"notification.follow": "{name} te seguiu",
"notification.follow_request": "{name} quer te seguir",
"notification.mention": "{name} te mencionou",
"notification.moderation-warning.learn_more": "Aprender mais",
"notification.moderation_warning.action_delete_statuses": "Algumas das suas publicações foram removidas.",
"notification.moderation_warning.action_disable": "Sua conta foi desativada.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Algumas de suas publicações foram marcadas por ter conteúdo sensível.",
"notification.moderation_warning.action_none": "Sua conta recebeu um aviso de moderação.",
"notification.moderation_warning.action_sensitive": "Suas publicações serão marcadas como sensíveis a partir de agora.",
"notification.moderation_warning.action_silence": "Sua conta foi limitada.",
"notification.moderation_warning.action_suspend": "Sua conta foi suspensa.",
"notification.own_poll": "Sua enquete terminou",
"notification.poll": "Uma enquete que você votou terminou",
"notification.reblog": "{name} deu boost no teu toot",

View file

@ -468,6 +468,7 @@
"notification.follow": "{name} подписался (-лась) на вас",
"notification.follow_request": "{name} отправил запрос на подписку",
"notification.mention": "{name} упомянул(а) вас",
"notification.moderation_warning.action_delete_statuses": "Некоторые из ваших публикаций были удалены.",
"notification.own_poll": "Ваш опрос закончился",
"notification.poll": "Опрос, в котором вы приняли участие, завершился",
"notification.reblog": "{name} продвинул(а) ваш пост",

View file

@ -295,6 +295,7 @@
"follow_suggestions.personalized_suggestion": "Prispôsobený návrh",
"follow_suggestions.popular_suggestion": "Obľúbený návrh",
"follow_suggestions.popular_suggestion_longer": "Populárne na {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Podobné profilom, ktoré si nedávno nasledoval/a",
"follow_suggestions.view_all": "Zobraziť všetky",
"follow_suggestions.who_to_follow": "Koho sledovať",
"followed_tags": "Sledované hashtagy",
@ -445,9 +446,14 @@
"notification.follow_request": "{name} vás žiada sledovať",
"notification.mention": "{name} vás spomína",
"notification.moderation-warning.learn_more": "Zisti viac",
"notification.moderation_warning.action_disable": "Tvoj účet bol vypnutý.",
"notification.moderation_warning.action_silence": "Tvoj účet bol obmedzený.",
"notification.moderation_warning.action_suspend": "Tvoj účet bol pozastavený.",
"notification.own_poll": "Vaša anketa sa skončila",
"notification.poll": "Anketa, v ktorej ste hlasovali, sa skončila",
"notification.reblog": "{name} zdieľa váš príspevok",
"notification.relationships_severance_event": "Stratené prepojenia s {name}",
"notification.relationships_severance_event.account_suspension": "Správca z {from} pozastavil/a {target}, čo znamená, že od nich viac nemôžeš dostávať aktualizácie, alebo s nimi interaktovať.",
"notification.relationships_severance_event.learn_more": "Zisti viac",
"notification.status": "{name} uverejňuje niečo nové",
"notification.update": "{name} upravuje príspevok",
@ -490,6 +496,7 @@
"notifications.policy.filter_new_accounts_title": "Nové účty",
"notifications.policy.filter_not_followers_title": "Ľudia, ktorí ťa nenasledujú",
"notifications.policy.filter_not_following_title": "Ľudia, ktorých nenasleduješ",
"notifications.policy.filter_private_mentions_title": "Nevyžiadané priame spomenutia",
"notifications.policy.title": "Filtrovať oznámenia od…",
"notifications_permission_banner.enable": "Povoliť upozornenia na ploche",
"notifications_permission_banner.how_to_control": "Ak chcete dostávať upozornenia, keď Mastodon nie je otvorený, povoľte upozornenia na ploche. Po ich zapnutí môžete presne kontrolovať, ktoré typy interakcií generujú upozornenia na ploche, a to prostredníctvom tlačidla {icon} vyššie.",

View file

@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Čeprav vaš račun ni zaklenjen, zaposleni pri {domain} menijo, da bi morda želeli pregledati zahteve za sledenje teh računov ročno.",
"follow_suggestions.curated_suggestion": "Izbor osebja",
"follow_suggestions.dismiss": "Ne pokaži več",
"follow_suggestions.featured_longer": "Osebno izbrala ekipa {domain}",
"follow_suggestions.friends_of_friends_longer": "Priljubljeno med osebami, ki jim sledite",
"follow_suggestions.hints.featured": "Ta profil so izbrali skrbniki strežnika {domain}.",
"follow_suggestions.hints.friends_of_friends": "Ta profil je priljubljen med osebami, ki jim sledite.",
"follow_suggestions.hints.most_followed": "Ta profil na strežniku {domain} je en izmed najbolj sledenih.",
@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Ta profil je podoben profilom, ki ste jim nedavno začeli slediti.",
"follow_suggestions.personalized_suggestion": "Osebno prilagojen predlog",
"follow_suggestions.popular_suggestion": "Priljubljen predlog",
"follow_suggestions.popular_suggestion_longer": "Priljubljeno na {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Podobno profilom, ki ste jim pred kratkim sledili",
"follow_suggestions.view_all": "Pokaži vse",
"follow_suggestions.who_to_follow": "Komu slediti",
"followed_tags": "Sledeni ključniki",
@ -469,6 +473,15 @@
"notification.follow": "{name} vam sledi",
"notification.follow_request": "{name} vam želi slediti",
"notification.mention": "{name} vas je omenil/a",
"notification.moderation-warning.learn_more": "Več o tem",
"notification.moderation_warning": "Prejeli ste opozorilo moderatorjev",
"notification.moderation_warning.action_delete_statuses": "Nekatere vaše objave so odstranjene.",
"notification.moderation_warning.action_disable": "Vaš račun je bil onemogočen.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Nekatere vaše objave so bile označene kot občutljive.",
"notification.moderation_warning.action_none": "Vaš račun je prejel opozorilo moderatorjev.",
"notification.moderation_warning.action_sensitive": "Vaše objave bodo odslej označene kot občutljive.",
"notification.moderation_warning.action_silence": "Vaš račun je bil omejen.",
"notification.moderation_warning.action_suspend": "Vaš račun je bil suspendiran.",
"notification.own_poll": "Vaša anketa je zaključena",
"notification.poll": "Anketa, v kateri ste sodelovali, je zaključena",
"notification.reblog": "{name} je izpostavila/a vašo objavo",

View file

@ -297,6 +297,7 @@
"filter_modal.select_filter.subtitle": "Përdorni një kategori ekzistuese, ose krijoni një të re",
"filter_modal.select_filter.title": "Filtroje këtë postim",
"filter_modal.title.status": "Filtroni një postim",
"filtered_notifications_banner.mentions": "{count, plural, one {përmendje} other {përmendje}}",
"filtered_notifications_banner.pending_requests": "Njoftime prej {count, plural, =0 {askujt} one {një personi} other {# vetësh}} që mund të njihni",
"filtered_notifications_banner.title": "Njoftime të filtruar",
"firehose.all": "Krejt",
@ -307,6 +308,8 @@
"follow_requests.unlocked_explanation": "Edhe pse llogaria juaj sështë e kyçur, ekipi i {domain} mendoi se mund të donit të shqyrtonit dorazi kërkesa ndjekjeje prej këtyre llogarive.",
"follow_suggestions.curated_suggestion": "Zgjedhur nga ekipi",
"follow_suggestions.dismiss": "Mos shfaq më",
"follow_suggestions.featured_longer": "Zgjedhur enkas nga ekipi {domain}",
"follow_suggestions.friends_of_friends_longer": "Popullore mes personash që ndiqni",
"follow_suggestions.hints.featured": "Ky profil është zgjedhur nga ekipi {domain}.",
"follow_suggestions.hints.friends_of_friends": "Ky profil është popullor mes personave që ndiqni.",
"follow_suggestions.hints.most_followed": "Ky profil është një nga më të ndjekur në {domain}.",
@ -314,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Ky profil është i ngjashëm me profile që keni ndjekur tani afër.",
"follow_suggestions.personalized_suggestion": "Sugjerim i personalizuar",
"follow_suggestions.popular_suggestion": "Sugjerim popullor",
"follow_suggestions.popular_suggestion_longer": "Popullore në {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "I ngjashëm me profile që keni zënë të ndiqni së fundi",
"follow_suggestions.view_all": "Shihni krejt",
"follow_suggestions.who_to_follow": "Cilët të ndiqen",
"followed_tags": "Hashtag-ë të ndjekur",
@ -468,6 +473,15 @@
"notification.follow": "{name} zuri tju ndjekë",
"notification.follow_request": "{name} ka kërkuar tju ndjekë",
"notification.mention": "{name} ju ka përmendur",
"notification.moderation-warning.learn_more": "Mësoni më tepër",
"notification.moderation_warning": "Ju është dhënë një sinjalizim moderimi",
"notification.moderation_warning.action_delete_statuses": "Disa nga postimet tuaja janë hequr.",
"notification.moderation_warning.action_disable": "Llogaria juaj është çaktivizuar.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Disa prej postimeve tuaja u është vënë shenjë si me spec.",
"notification.moderation_warning.action_none": "Llogaria juaj ka marrë një sinjalizim moderimi.",
"notification.moderation_warning.action_sensitive": "Postimeve tuaja do tu vihet shenjë si me spec, nga tani e tutje.",
"notification.moderation_warning.action_silence": "Llogaria juaj është kufizuar.",
"notification.moderation_warning.action_suspend": "Llogaria juaj është pezulluar.",
"notification.own_poll": "Pyetësori juaj ka përfunduar",
"notification.poll": "Ka përfunduar një pyetësor ku keni votuar",
"notification.reblog": "{name} përforcoi mesazhin tuaj",

View file

@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Iako vaš nalog nije zaključan, osoblje {domain} smatra da biste možda želeli da ručno pregledate zahteve za praćenje sa ovih naloga.",
"follow_suggestions.curated_suggestion": "Izbor osoblja",
"follow_suggestions.dismiss": "Ne prikazuj ponovo",
"follow_suggestions.featured_longer": "Ručno odabrao tim {domain}",
"follow_suggestions.friends_of_friends_longer": "Popularno među ljudima koje pratite",
"follow_suggestions.hints.featured": "Ovaj profil je ručno izabrao tim {domain}.",
"follow_suggestions.hints.friends_of_friends": "Ovaj profil je popularan među ljudima koje pratite.",
"follow_suggestions.hints.most_followed": "Ovaj profil je jedan od najpraćenijih na {domain}.",
@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Ovaj profil je sličan profilima koje ste nedavno zapratili.",
"follow_suggestions.personalized_suggestion": "Personalizovani predlog",
"follow_suggestions.popular_suggestion": "Popularni predlog",
"follow_suggestions.popular_suggestion_longer": "Popularno na {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Slično profilima koje ste nedavno zapratili",
"follow_suggestions.view_all": "Prikaži sve",
"follow_suggestions.who_to_follow": "Koga pratiti",
"followed_tags": "Praćene heš oznake",
@ -469,6 +473,15 @@
"notification.follow": "{name} vas je zapratio",
"notification.follow_request": "{name} je zatražio da vas prati",
"notification.mention": "{name} vas je pomenuo",
"notification.moderation-warning.learn_more": "Saznajte više",
"notification.moderation_warning": "Dobili ste moderatorsko upozorenje",
"notification.moderation_warning.action_delete_statuses": "Neke od vaših objava su uklonjene.",
"notification.moderation_warning.action_disable": "Vaš nalog je onemogućen.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Neke od vaših objava su obeležene kao osetljive.",
"notification.moderation_warning.action_none": "Vaš nalog je dobio moderatorsko upozorenje.",
"notification.moderation_warning.action_sensitive": "Vaše objave će ubuduće biti označene kao osetljive.",
"notification.moderation_warning.action_silence": "Vaš nalog je ograničen.",
"notification.moderation_warning.action_suspend": "Vaš nalog je suspendovan.",
"notification.own_poll": "Vaša anketa je završena",
"notification.poll": "Završena je anketa u kojoj ste glasali",
"notification.reblog": "{name} je podržao vašu objavu",

View file

@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Иако ваш налог није закључан, особље {domain} сматра да бисте можда желели да ручно прегледате захтеве за праћење са ових налога.",
"follow_suggestions.curated_suggestion": "Избор особља",
"follow_suggestions.dismiss": "Не приказуј поново",
"follow_suggestions.featured_longer": "Ручно одабрао тим {domain}",
"follow_suggestions.friends_of_friends_longer": "Популарно међу људима које пратите",
"follow_suggestions.hints.featured": "Овај профил је ручно изабрао тим {domain}.",
"follow_suggestions.hints.friends_of_friends": "Овај профил је популаран међу људима које пратите.",
"follow_suggestions.hints.most_followed": "Овај профил је један од најпраћенијих на {domain}.",
@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Овај профил је сличан профилима које сте недавно запратили.",
"follow_suggestions.personalized_suggestion": "Персонализовани предлог",
"follow_suggestions.popular_suggestion": "Популарни предлог",
"follow_suggestions.popular_suggestion_longer": "Популарно на {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Слично профилима које сте недавно запратили",
"follow_suggestions.view_all": "Прикажи све",
"follow_suggestions.who_to_follow": "Кога пратити",
"followed_tags": "Праћене хеш ознаке",
@ -469,6 +473,15 @@
"notification.follow": "{name} вас је запратио",
"notification.follow_request": "{name} је затражио да вас прати",
"notification.mention": "{name} вас је поменуо",
"notification.moderation-warning.learn_more": "Сазнајте више",
"notification.moderation_warning": "Добили сте модераторско упозорење",
"notification.moderation_warning.action_delete_statuses": "Неке од ваших објава су уклоњене.",
"notification.moderation_warning.action_disable": "Ваш налог је онемогућен.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Неке од ваших објава су обележене као осетљиве.",
"notification.moderation_warning.action_none": "Ваш налог је добио модераторско упозорење.",
"notification.moderation_warning.action_sensitive": "Ваше објаве ће убудуће бити означене као осетљиве.",
"notification.moderation_warning.action_silence": "Ваш налог је ограничен.",
"notification.moderation_warning.action_suspend": "Ваш налог је суспендован.",
"notification.own_poll": "Ваша анкета је завршена",
"notification.poll": "Завршена је анкета у којој сте гласали",
"notification.reblog": "{name} је подржао вашу објаву",

View file

@ -474,7 +474,7 @@
"notification.follow_request": "{name} har begärt att följa dig",
"notification.mention": "{name} nämnde dig",
"notification.moderation-warning.learn_more": "Läs mer",
"notification.moderation_warning": "Du har mottagit en modereringsvarning",
"notification.moderation_warning": "Du har fått en moderationsvarning",
"notification.moderation_warning.action_delete_statuses": "Några av dina inlägg har tagits bort.",
"notification.moderation_warning.action_disable": "Ditt konto har inaktiverats.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Några av dina inlägg har markerats som känsliga.",

View file

@ -158,7 +158,7 @@
"compose_form.poll.option_placeholder": "ตัวเลือก {number}",
"compose_form.poll.single": "เลือกอย่างใดอย่างหนึ่ง",
"compose_form.poll.switch_to_multiple": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตหลายตัวเลือก",
"compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตตัวเลือกเดียว",
"compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตตัวเลือกเดียว",
"compose_form.poll.type": "ลักษณะ",
"compose_form.publish": "โพสต์",
"compose_form.publish_form": "โพสต์ใหม่",

View file

@ -474,7 +474,7 @@
"notification.follow_request": "{name} size takip isteği gönderdi",
"notification.mention": "{name} senden bahsetti",
"notification.moderation-warning.learn_more": "Daha fazlası",
"notification.moderation_warning": "Bir denetim uyarısı aldınız",
"notification.moderation_warning": "Hesabınız bir denetim uyarısı aldı",
"notification.moderation_warning.action_delete_statuses": "Bazı gönderileriniz kaldırıldı.",
"notification.moderation_warning.action_disable": "Hesabınız devre dışı bırakıldı.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Bazı gönderileriniz hassas olarak işaretlendi.",

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