diff options
436 files changed, 6011 insertions, 1853 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 000000000..70d03f6b9 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,191 @@ +version: 2 + +aliases: + - &defaults + docker: + - image: circleci/ruby:2.5.1-stretch-node + environment: &ruby_environment + BUNDLE_APP_CONFIG: ./.bundle/ + DB_HOST: localhost + DB_USER: root + RAILS_ENV: test + PARALLEL_TEST_PROCESSORS: 4 + ALLOW_NOPAM: true + working_directory: ~/projects/mastodon/ + + - &attach_workspace + attach_workspace: + at: ~/projects/ + + - &persist_to_workspace + persist_to_workspace: + root: ~/projects/ + paths: + - ./mastodon/ + + - &restore_ruby_dependencies + restore_cache: + keys: + - v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} + - v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}- + - v2-ruby-dependencies- + + - &install_steps + steps: + - checkout + - *attach_workspace + + - restore_cache: + keys: + - v1-node-dependencies-{{ checksum "yarn.lock" }} + - v1-node-dependencies- + - run: yarn install --frozen-lockfile + - save_cache: + key: v1-node-dependencies-{{ checksum "yarn.lock" }} + paths: + - ./node_modules/ + + - *persist_to_workspace + + - &install_system_dependencies + run: + name: Install system dependencies + command: | + sudo apt-get update + sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler + + - &install_ruby_dependencies + steps: + - *attach_workspace + + - *install_system_dependencies + + - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version + - *restore_ruby_dependencies + - run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production + - save_cache: + key: v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} + paths: + - ./.bundle/ + - ./vendor/bundle/ + + - &test_steps + steps: + - *attach_workspace + + - *install_system_dependencies + - run: sudo apt-get install -y ffmpeg + + - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version + - *restore_ruby_dependencies + + - restore_cache: + keys: + - precompiled-assets-{{ .Branch }}-{{ .Revision }} + - precompiled-assets-{{ .Branch }}- + - precompiled-assets- + + - run: + name: Prepare Tests + command: ./bin/rails parallel:create parallel:load_schema parallel:prepare + - run: + name: Run Tests + command: bundle exec parallel_test ./spec/ --group-by filesize --type rspec + +jobs: + install: + <<: *defaults + <<: *install_steps + + install-ruby2.5: + <<: *defaults + <<: *install_ruby_dependencies + + install-ruby2.4: + <<: *defaults + docker: + - image: circleci/ruby:2.4.4-stretch-node + environment: *ruby_environment + <<: *install_ruby_dependencies + + build: + <<: *defaults + steps: + - *attach_workspace + - *install_system_dependencies + - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version + - *restore_ruby_dependencies + - run: ./bin/rails assets:precompile + - save_cache: + key: precompiled-assets-{{ .Branch }}-{{ .Revision }} + paths: + - ./public/assets + - ./public/packs-test/ + + test-ruby2.5: + <<: *defaults + docker: + - image: circleci/ruby:2.5.1-stretch-node + environment: *ruby_environment + - image: circleci/postgres:10.3-alpine + environment: + POSTGRES_USER: root + - image: circleci/redis:4.0.9-alpine + <<: *test_steps + + test-ruby2.4: + <<: *defaults + docker: + - image: circleci/ruby:2.4.4-stretch-node + environment: *ruby_environment + - image: circleci/postgres:10.3-alpine + environment: + POSTGRES_USER: root + - image: circleci/redis:4.0.9-alpine + <<: *test_steps + + test-webui: + <<: *defaults + docker: + - image: circleci/node:8.11.1-stretch + steps: + - *attach_workspace + - run: yarn test:jest + + check-i18n: + <<: *defaults + steps: + - *attach_workspace + - run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version + - *restore_ruby_dependencies + - run: bundle exec i18n-tasks check-normalized + - run: bundle exec i18n-tasks unused + +workflows: + version: 2 + build-and-test: + jobs: + - install + - install-ruby2.5: + requires: + - install + - install-ruby2.4: + requires: + - install + - build: + requires: + - install-ruby2.5 + - test-ruby2.5: + requires: + - install-ruby2.5 + - build + - test-ruby2.4: + requires: + - install-ruby2.4 + - build + - test-webui: + requires: + - install + - check-i18n: + requires: + - install-ruby2.5 diff --git a/.env.production.sample b/.env.production.sample index 0c158b06e..eddd38d90 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -217,3 +217,10 @@ STREAMING_CLUSTER_NUM=1 # SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1" # SAML_ATTRIBUTES_STATEMENTS_VERIFIED= # SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL= + +# Use HTTP proxy for outgoing request (optional) +# http_proxy=http://gateway.local:8118 +# Access control for hidden service. +# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true +# If you use transparent proxy to access to hidden service, uncomment following for skipping private address check. +# HIDDEN_SERVICE_VIA_TRANSPARENT_PROXY=true diff --git a/.env.test b/.env.test index 7da76f8ef..726351c5e 100644 --- a/.env.test +++ b/.env.test @@ -1,3 +1,5 @@ +# Node.js +NODE_ENV=test # Federation LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_HTTPS=true diff --git a/Gemfile b/Gemfile index 068b4874d..c3f4a62f2 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' ruby '>= 2.3.0', '< 2.6.0' -gem 'pkg-config', '~> 1.2' +gem 'pkg-config', '~> 1.3' gem 'puma', '~> 3.11' gem 'rails', '~> 5.2.0' @@ -11,11 +11,11 @@ gem 'rails', '~> 5.2.0' gem 'hamlit-rails', '~> 0.2' gem 'pg', '~> 1.0' gem 'pghero', '~> 2.1' -gem 'dotenv-rails', '~> 2.2' +gem 'dotenv-rails', '~> 2.2', '< 2.3' -gem 'aws-sdk-s3', '~> 1.8', require: false +gem 'aws-sdk-s3', '~> 1.9', require: false gem 'fog-core', '~> 1.45' -gem 'fog-local', '~> 0.4', require: false +gem 'fog-local', '~> 0.5', require: false gem 'fog-openstack', '~> 0.1', require: false gem 'paperclip', '~> 6.0' gem 'paperclip-av-transcoder', '~> 0.6' @@ -31,7 +31,7 @@ gem 'iso-639' gem 'chewy', '~> 5.0' gem 'cld3', '~> 3.2.0' gem 'devise', '~> 4.4' -gem 'devise-two-factor', '~> 3.0', git: 'https://github.com/ykzts/devise-two-factor.git', branch: 'rails-5.2' +gem 'devise-two-factor', '~> 3.0' group :pam_authentication, optional: true do gem 'devise_pam_authenticatable2', '~> 9.1' @@ -50,18 +50,18 @@ gem 'hiredis', '~> 0.6' gem 'redis-namespace', '~> 1.5' gem 'html2text' gem 'htmlentities', '~> 4.3' -gem 'http', '~> 3.0' +gem 'http', '~> 3.2' gem 'http_accept_language', '~> 2.1' gem 'httplog', '~> 1.0' gem 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.1' gem 'link_header', '~> 0.0' -gem 'mime-types', '~> 3.1' +gem 'mime-types', '~> 3.1', require: 'mime/types/columnar' gem 'nokogiri', '~> 1.8' gem 'nsa', '~> 0.2' -gem 'oj', '~> 3.4' +gem 'oj', '~> 3.5' gem 'ostatus2', '~> 2.0' -gem 'ox', '~> 2.8' +gem 'ox', '~> 2.9' gem 'pundit', '~> 1.1' gem 'premailer-rails' gem 'rack-attack', '~> 5.2' @@ -72,7 +72,6 @@ gem 'rails-settings-cached', '~> 0.6' gem 'redis', '~> 4.0', require: ['redis', 'redis/connection/hiredis'] gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'rqrcode', '~> 0.10' -gem 'ruby-oembed', '~> 0.12', require: 'oembed' gem 'ruby-progressbar', '~> 1.4' gem 'sanitize', '~> 4.6' gem 'sidekiq', '~> 5.1' @@ -84,20 +83,21 @@ gem 'simple_form', '~> 4.0' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'stoplight', '~> 2.1.3' gem 'strong_migrations', '~> 0.2' -gem 'tty-command' -gem 'tty-prompt' +gem 'tty-command', '~> 0.8', require: false +gem 'tty-prompt', '~> 0.16', require: false gem 'twitter-text', '~> 1.14' gem 'tzinfo-data', '~> 1.2018' gem 'webpacker', '~> 3.4' gem 'webpush' -gem 'json-ld-preloaded', '~> 2.2' +gem 'json-ld', '~> 2.2' gem 'rdf-normalize', '~> 0.3' group :development, :test do gem 'fabrication', '~> 2.20' gem 'fuubar', '~> 2.2' gem 'i18n-tasks', '~> 0.9', require: false + gem 'pry-byebug', '~> 3.6' gem 'pry-rails', '~> 0.3' gem 'rspec-rails', '~> 3.7' end @@ -113,7 +113,8 @@ group :test do gem 'microformats', '~> 4.0' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.0' - gem 'simplecov', '~> 0.14', require: false + gem 'rspec-retry', '~> 0.5', require: false + gem 'simplecov', '~> 0.16', require: false gem 'webmock', '~> 3.3' gem 'parallel_tests', '~> 2.21' end @@ -127,18 +128,21 @@ group :development do gem 'letter_opener', '~> 1.4' gem 'letter_opener_web', '~> 1.3' gem 'memory_profiler' - gem 'rubocop', require: false + gem 'rubocop', '~> 0.55', require: false gem 'brakeman', '~> 4.2', require: false gem 'bundler-audit', '~> 0.6', require: false - gem 'scss_lint', '~> 0.55', require: false + gem 'scss_lint', '~> 0.57', require: false gem 'capistrano', '~> 3.10' gem 'capistrano-rails', '~> 1.3' gem 'capistrano-rbenv', '~> 2.1' gem 'capistrano-yarn', '~> 2.0' + + gem 'derailed_benchmarks' + gem 'stackprof' end group :production do - gem 'lograge', '~> 0.9' + gem 'lograge', '~> 0.10' gem 'redis-rails', '~> 5.0' end diff --git a/Gemfile.lock b/Gemfile.lock index 09ee34f89..2e2cf1f3d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,15 +1,3 @@ -GIT - remote: https://github.com/ykzts/devise-two-factor.git - revision: f60492b29c174d4c959ac02406392f8eb9c4d374 - branch: rails-5.2 - specs: - devise-two-factor (3.0.2) - activesupport (< 5.3) - attr_encrypted (>= 1.3, < 4, != 2) - devise (~> 4.0) - railties (< 5.3) - rotp (~> 2.0) - GEM remote: https://rubygems.org/ specs: @@ -64,7 +52,7 @@ GEM public_suffix (>= 2.0.2, < 4.0) airbrussh (1.3.0) sshkit (>= 1.6.1, != 1.7.0) - annotate (2.7.2) + annotate (2.7.3) activerecord (>= 3.2, < 6.0) rake (>= 10.4, < 13.0) arel (9.0.0) @@ -73,20 +61,21 @@ GEM encryptor (~> 3.0.0) av (0.9.0) cocaine (~> 0.5.3) - aws-partitions (1.70.0) - aws-sdk-core (3.17.0) + aws-partitions (1.80.0) + aws-sdk-core (3.19.0) aws-partitions (~> 1.0) aws-sigv4 (~> 1.0) jmespath (~> 1.0) aws-sdk-kms (1.5.0) aws-sdk-core (~> 3) aws-sigv4 (~> 1.0) - aws-sdk-s3 (1.8.2) + aws-sdk-s3 (1.9.1) aws-sdk-core (~> 3) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.0) aws-sigv4 (1.0.2) bcrypt (3.1.11) + benchmark-ips (2.7.2) better_errors (2.4.0) coderay (>= 1.0.0) erubi (>= 1.0.0) @@ -96,7 +85,7 @@ GEM bootsnap (1.3.0) msgpack (~> 1.0) brakeman (4.2.1) - browser (2.5.2) + browser (2.5.3) builder (3.2.3) bullet (5.7.5) activesupport (>= 3.0.0) @@ -104,7 +93,8 @@ GEM bundler-audit (0.6.0) bundler (~> 1.2) thor (~> 0.18) - capistrano (3.10.1) + byebug (10.0.2) + capistrano (3.10.2) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) @@ -150,18 +140,32 @@ GEM css_parser (1.6.0) addressable debug_inspector (0.0.3) + derailed_benchmarks (1.3.4) + benchmark-ips (~> 2) + get_process_mem (~> 0) + heapy (~> 0) + memory_profiler (~> 0) + rack (>= 1) + rake (> 10, < 13) + thor (~> 0.19) devise (4.4.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0, < 6.0) responders warden (~> 1.2.3) + devise-two-factor (3.0.3) + activesupport (< 5.3) + attr_encrypted (>= 1.3, < 4, != 2) + devise (~> 4.0) + railties (< 5.3) + rotp (~> 2.0) devise_pam_authenticatable2 (9.1.0) devise (>= 4.0.0) rpam2 (~> 4.0) diff-lcs (1.3) - docile (1.1.5) - domain_name (0.5.20170404) + docile (1.3.0) + domain_name (0.5.20180417) unf (>= 0.0.5, < 1.0.0) doorkeeper (4.3.2) railties (>= 4.2) @@ -172,29 +176,29 @@ GEM easy_translate (0.5.1) thread thread_safe - elasticsearch (6.0.1) - elasticsearch-api (= 6.0.1) - elasticsearch-transport (= 6.0.1) - elasticsearch-api (6.0.1) + elasticsearch (6.0.2) + elasticsearch-api (= 6.0.2) + elasticsearch-transport (= 6.0.2) + elasticsearch-api (6.0.2) multi_json elasticsearch-dsl (0.1.5) - elasticsearch-transport (6.0.1) + elasticsearch-transport (6.0.2) faraday multi_json encryptor (3.0.0) equatable (0.5.0) erubi (1.7.1) - et-orbi (1.0.9) + et-orbi (1.1.0) tzinfo - excon (0.60.0) + excon (0.62.0) fabrication (2.20.1) faker (1.8.7) i18n (>= 0.7) - faraday (0.14.0) + faraday (0.15.0) multipart-post (>= 1.2, < 3) fast_blank (1.0.0) fastimage (2.1.1) - ffi (1.9.21) + ffi (1.9.23) fog-core (1.45.0) builder excon (~> 0.58) @@ -202,9 +206,9 @@ GEM fog-json (1.0.2) fog-core (~> 1.0) multi_json (~> 1.10) - fog-local (0.4.0) - fog-core (~> 1.27) - fog-openstack (0.1.23) + fog-local (0.5.0) + fog-core (>= 1.27, < 3.0) + fog-openstack (0.1.25) fog-core (~> 1.40) fog-json (>= 1.0) ipaddress (>= 0.8) @@ -212,6 +216,7 @@ GEM fuubar (2.3.1) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) + get_process_mem (0.2.1) globalid (0.4.1) activesupport (>= 4.2.0) goldfinger (2.1.0) @@ -232,6 +237,7 @@ GEM concurrent-ruby (~> 1.0) hashdiff (0.3.7) hashie (3.5.7) + heapy (0.1.3) highline (1.7.10) hiredis (0.6.1) hitimes (1.2.6) @@ -239,20 +245,20 @@ GEM html2text (0.2.1) nokogiri (~> 1.6) htmlentities (4.3.4) - http (3.0.0) + http (3.2.0) addressable (~> 2.3) http-cookie (~> 1.0) - http-form_data (>= 2.0.0.pre.pre2, < 3) + http-form_data (~> 2.0) http_parser.rb (~> 0.6.0) http-cookie (1.0.3) domain_name (~> 0.5) - http-form_data (2.0.0) + http-form_data (2.1.0) http_accept_language (2.1.1) http_parser.rb (0.6.0) httplog (1.0.2) colorize (~> 0.8) rack (>= 1.0) - i18n (1.0.0) + i18n (1.0.1) concurrent-ruby (~> 1.0) i18n-tasks (0.9.21) activesupport (>= 4.0.2) @@ -267,15 +273,11 @@ GEM idn-ruby (0.1.0) ipaddress (0.8.3) iso-639 (0.2.8) - jmespath (1.3.1) + jmespath (1.4.0) json (2.1.0) json-ld (2.2.1) multi_json (~> 1.12) rdf (>= 2.2.8, < 4.0) - json-ld-preloaded (2.2.3) - json-ld (>= 2.2, < 4.0) - multi_json (~> 1.12) - rdf (>= 2.2, < 4.0) jsonapi-renderer (0.2.0) jwt (2.1.0) kaminari (1.1.1) @@ -299,7 +301,7 @@ GEM letter_opener (~> 1.0) railties (>= 3.2) link_header (0.0.8) - lograge (0.9.0) + lograge (0.10.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) @@ -343,7 +345,7 @@ GEM concurrent-ruby (~> 1.0.0) sidekiq (>= 3.5.0) statsd-ruby (~> 1.2.0) - oj (3.4.0) + oj (3.5.1) omniauth (1.8.1) hashie (>= 3.4.6, < 3.6.0) rack (>= 1.6.2, < 3) @@ -359,7 +361,7 @@ GEM addressable (~> 2.5) http (~> 3.0) nokogiri (~> 1.8) - ox (2.8.2) + ox (2.9.2) paperclip (6.0.0) activemodel (>= 4.2.0) activesupport (>= 4.2.0) @@ -370,7 +372,7 @@ GEM av (~> 0.9.0) paperclip (>= 2.5.2) parallel (1.12.1) - parallel_tests (2.21.1) + parallel_tests (2.21.3) parallel parser (2.5.1.0) ast (~> 2.4.0) @@ -380,7 +382,7 @@ GEM pg (1.0.0) pghero (2.1.0) activerecord - pkg-config (1.2.9) + pkg-config (1.3.0) posix-spawn (0.3.13) powerpack (0.1.1) premailer (1.11.1) @@ -394,10 +396,13 @@ GEM pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) + pry-byebug (3.6.0) + byebug (~> 10.0) + pry (~> 0.10) pry-rails (0.3.6) pry (>= 0.10.4) public_suffix (3.0.2) - puma (3.11.3) + puma (3.11.4) pundit (1.1.0) activesupport (>= 3.0.0) rack (2.0.4) @@ -446,10 +451,10 @@ GEM thor (>= 0.18.1, < 2.0) rainbow (3.0.0) rake (12.3.1) - rb-fsevent (0.10.2) + rb-fsevent (0.10.3) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) - rdf (3.0.1) + rdf (3.0.2) hamster (~> 3.0) link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.3.3) @@ -471,9 +476,9 @@ GEM redis-actionpack (>= 5.0, < 6) redis-activesupport (>= 5.0, < 6) redis-store (>= 1.2, < 2) - redis-store (1.4.1) + redis-store (1.5.0) redis (>= 2.2, < 5) - request_store (1.4.0) + request_store (1.4.1) rack (>= 1.4) responders (2.4.0) actionpack (>= 4.2.0, < 5.3) @@ -498,18 +503,19 @@ GEM rspec-expectations (~> 3.7.0) rspec-mocks (~> 3.7.0) rspec-support (~> 3.7.0) + rspec-retry (0.5.7) + rspec-core (> 3.3) rspec-sidekiq (3.0.3) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) rspec-support (3.7.1) - rubocop (0.52.1) + rubocop (0.55.0) parallel (~> 1.10) - parser (>= 2.4.0.2, < 3.0) + parser (>= 2.5) powerpack (~> 0.1) rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) - ruby-oembed (0.12.0) ruby-progressbar (1.9.0) ruby-saml (1.7.2) nokogiri (>= 1.5.10) @@ -520,14 +526,14 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.4.4) nokogumbo (~> 1.4) - sass (3.5.5) + sass (3.5.6) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - scss_lint (0.56.0) + scss_lint (0.57.0) rake (>= 0.9, < 13) - sass (~> 3.5.3) + sass (~> 3.5.5) sidekiq (5.1.3) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) @@ -549,8 +555,8 @@ GEM simple_form (4.0.0) actionpack (> 4) activemodel (> 4) - simplecov (0.15.1) - docile (~> 1.1.0) + simplecov (0.16.1) + docile (~> 1.1) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) @@ -564,6 +570,7 @@ GEM sshkit (1.16.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) + stackprof (0.2.11) statsd-ruby (1.2.1) stoplight (2.1.3) streamio-ffmpeg (3.0.2) @@ -582,10 +589,10 @@ GEM timers (4.1.2) hitimes tty-color (0.4.2) - tty-command (0.7.0) + tty-command (0.8.0) pastel (~> 0.7.0) tty-cursor (0.5.0) - tty-prompt (0.15.0) + tty-prompt (0.16.0) necromancer (~> 0.4.0) pastel (~> 0.7.0) timers (~> 4.0) @@ -605,7 +612,7 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.5) - unicode-display_width (1.3.0) + unicode-display_width (1.3.2) uniform_notifier (1.11.0) warden (1.2.7) rack (>= 1.0) @@ -635,7 +642,7 @@ DEPENDENCIES active_record_query_trace (~> 1.5) addressable (~> 2.5) annotate (~> 2.7) - aws-sdk-s3 (~> 1.8) + aws-sdk-s3 (~> 1.9) better_errors (~> 2.4) binding_of_caller (~> 0.7) bootsnap (~> 1.3) @@ -652,17 +659,18 @@ DEPENDENCIES chewy (~> 5.0) cld3 (~> 3.2.0) climate_control (~> 0.2) + derailed_benchmarks devise (~> 4.4) - devise-two-factor (~> 3.0)! + devise-two-factor (~> 3.0) devise_pam_authenticatable2 (~> 9.1) doorkeeper (~> 4.3) - dotenv-rails (~> 2.2) + dotenv-rails (~> 2.2, < 2.3) fabrication (~> 2.20) faker (~> 1.8) fast_blank (~> 1.0) fastimage fog-core (~> 1.45) - fog-local (~> 0.4) + fog-local (~> 0.5) fog-openstack (~> 0.1) fuubar (~> 2.2) goldfinger (~> 2.1) @@ -670,18 +678,18 @@ DEPENDENCIES hiredis (~> 0.6) html2text htmlentities (~> 4.3) - http (~> 3.0) + http (~> 3.2) http_accept_language (~> 2.1) httplog (~> 1.0) i18n-tasks (~> 0.9) idn-ruby iso-639 - json-ld-preloaded (~> 2.2) + json-ld (~> 2.2) kaminari (~> 1.1) letter_opener (~> 1.4) letter_opener_web (~> 1.3) link_header (~> 0.0) - lograge (~> 0.9) + lograge (~> 0.10) mario-redis-lock (~> 1.2) memory_profiler microformats (~> 4.0) @@ -689,21 +697,22 @@ DEPENDENCIES net-ldap (~> 0.10) nokogiri (~> 1.8) nsa (~> 0.2) - oj (~> 3.4) + oj (~> 3.5) omniauth (~> 1.2) omniauth-cas (~> 1.1) omniauth-saml (~> 1.10) ostatus2 (~> 2.0) - ox (~> 2.8) + ox (~> 2.9) paperclip (~> 6.0) paperclip-av-transcoder (~> 0.6) parallel_tests (~> 2.21) pg (~> 1.0) pghero (~> 2.1) - pkg-config (~> 1.2) + pkg-config (~> 1.3) posix-spawn premailer-rails private_address_check (~> 0.4.1) + pry-byebug (~> 3.6) pry-rails (~> 0.3) puma (~> 3.11) pundit (~> 1.1) @@ -720,25 +729,26 @@ DEPENDENCIES redis-rails (~> 5.0) rqrcode (~> 0.10) rspec-rails (~> 3.7) + rspec-retry (~> 0.5) rspec-sidekiq (~> 3.0) - rubocop - ruby-oembed (~> 0.12) + rubocop (~> 0.55) ruby-progressbar (~> 1.4) sanitize (~> 4.6) - scss_lint (~> 0.55) + scss_lint (~> 0.57) sidekiq (~> 5.1) sidekiq-bulk (~> 0.1.1) sidekiq-scheduler (~> 2.2) sidekiq-unique-jobs (~> 5.0) simple-navigation (~> 4.0) simple_form (~> 4.0) - simplecov (~> 0.14) + simplecov (~> 0.16) sprockets-rails (~> 3.2) + stackprof stoplight (~> 2.1.3) streamio-ffmpeg (~> 3.0) strong_migrations (~> 0.2) - tty-command - tty-prompt + tty-command (~> 0.8) + tty-prompt (~> 0.16) twitter-text (~> 1.14) tzinfo-data (~> 1.2018) webmock (~> 3.3) diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 1efaf619b..50f5d0b11 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -21,9 +21,10 @@ class AccountsController < ApplicationController @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? @statuses = filtered_status_page(params) @statuses = cache_collection(@statuses, Status) + unless @statuses.empty? - @older_url = older_url if @statuses.last.id > filtered_statuses.last.id - @newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id + @older_url = older_url if @statuses.last.id > filtered_statuses.last.id + @newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id end end @@ -32,6 +33,11 @@ class AccountsController < ApplicationController render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? })) end + format.rss do + @statuses = cache_collection(default_statuses.without_reblogs.without_replies.limit(PAGE_SIZE), Status) + render xml: RSS::AccountSerializer.render(@account, @statuses) + end + format.json do skip_session! diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb index 535bd11d4..522f68c98 100644 --- a/app/controllers/admin/reported_statuses_controller.rb +++ b/app/controllers/admin/reported_statuses_controller.rb @@ -8,7 +8,7 @@ module Admin def create authorize :status, :update? - @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account)) + @form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button)) flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save redirect_to admin_report_path(@report) @@ -35,7 +35,17 @@ module Admin end def form_status_batch_params - params.require(:form_status_batch).permit(:action, status_ids: []) + params.require(:form_status_batch).permit(status_ids: []) + end + + def action_from_button + if params[:nsfw_on] + 'nsfw_on' + elsif params[:nsfw_off] + 'nsfw_off' + elsif params[:delete] + 'delete' + end end def set_report diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb index a4ae9507d..d00b3d222 100644 --- a/app/controllers/admin/reports_controller.rb +++ b/app/controllers/admin/reports_controller.rb @@ -11,10 +11,10 @@ module Admin def show authorize @report, :show? - @report_note = @report.notes.new - @report_notes = @report.notes.latest - @report_history = @report.history - @form = Form::StatusBatch.new + + @report_note = @report.notes.new + @report_notes = (@report.notes.latest + @report.history).sort_by(&:created_at) + @form = Form::StatusBatch.new end def update diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 7b5168b31..b5c084e14 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -66,8 +66,10 @@ class Api::BaseController < ApplicationController end def require_user! - if current_user + if current_user && !current_user.disabled? set_user_activity + elsif current_user + render json: { error: 'Your login is currently disabled' }, status: 403 else render json: { error: 'This method requires an authenticated user' }, status: 422 end diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index 062d490a7..a3c4008e6 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -21,7 +21,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController private def account_params - params.permit(:display_name, :note, :avatar, :header, :locked) + params.permit(:display_name, :note, :avatar, :header, :locked, fields_attributes: [:name, :value]) end def user_settings_params diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index d64325944..b7133ca8e 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -5,6 +5,7 @@ class Api::V1::AccountsController < Api::BaseController before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock, :mute, :unmute] before_action :require_user!, except: [:show] before_action :set_account + before_action :check_account_suspension, only: [:show] respond_to :json @@ -54,4 +55,8 @@ class Api::V1::AccountsController < Api::BaseController def relationships(**options) AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options) end + + def check_account_suspension + gone if @account.suspended? + end end diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index e98241323..01880565c 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -18,7 +18,7 @@ class Api::V1::StatusesController < Api::BaseController def context ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(DEFAULT_STATUSES_LIMIT, current_account) - descendants_results = @status.descendants(current_account) + descendants_results = @status.descendants(DEFAULT_STATUSES_LIMIT, current_account) loaded_ancestors = cache_collection(ancestors_results, Status) loaded_descendants = cache_collection(descendants_results, Status) diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb index f2fe74b17..987290a14 100644 --- a/app/controllers/api/web/embeds_controller.rb +++ b/app/controllers/api/web/embeds_controller.rb @@ -9,9 +9,12 @@ class Api::Web::EmbedsController < Api::Web::BaseController status = StatusFinder.new(params[:url]).status render json: status, serializer: OEmbedSerializer, width: 400 rescue ActiveRecord::RecordNotFound - oembed = OEmbed::Providers.get(params[:url]) - render json: Oj.dump(oembed.fields) - rescue OEmbed::NotFound - render json: {}, status: :not_found + oembed = FetchOEmbedService.new.call(params[:url]) + + if oembed + render json: oembed + else + render json: {}, status: :not_found + end end end diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb index abd85ea27..145549bcd 100644 --- a/app/controllers/concerns/localized.rb +++ b/app/controllers/concerns/localized.rb @@ -29,10 +29,14 @@ module Localized end def preferred_locale - http_accept_language.preferred_language_from(I18n.available_locales) + http_accept_language.preferred_language_from(available_locales) end def compatible_locale - http_accept_language.compatible_language_from(I18n.available_locales) + http_accept_language.compatible_language_from(available_locales) + end + + def available_locales + I18n.available_locales.reverse end end diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 3237a15b9..2e9cf14e0 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -4,7 +4,9 @@ class StatusesController < ApplicationController include SignatureAuthentication include Authorization - ANCESTORS_LIMIT = 20 + ANCESTORS_LIMIT = 40 + DESCENDANTS_LIMIT = 60 + DESCENDANTS_DEPTH_LIMIT = 20 layout 'public' @@ -20,9 +22,8 @@ class StatusesController < ApplicationController respond_to do |format| format.html do use_pack 'public' - @ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : [] - @next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift - @descendants = cache_collection(@status.descendants(current_account), Status) + set_ancestors + set_descendants render 'stream_entries/show' end @@ -53,10 +54,77 @@ class StatusesController < ApplicationController private + def create_descendant_thread(depth, statuses) + if depth < DESCENDANTS_DEPTH_LIMIT + { statuses: statuses } + else + next_status = statuses.pop + { statuses: statuses, next_status: next_status } + end + end + def set_account @account = Account.find_local!(params[:account_username]) end + def set_ancestors + @ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : [] + @next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift + end + + def set_descendants + @max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i + @since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i + + descendants = cache_collection( + @status.descendants( + DESCENDANTS_LIMIT, + current_account, + @max_descendant_thread_id, + @since_descendant_thread_id, + DESCENDANTS_DEPTH_LIMIT + ), + Status + ) + + @descendant_threads = [] + + if descendants.present? + statuses = [descendants.first] + depth = 1 + + descendants.drop(1).each_with_index do |descendant, index| + if descendants[index].id == descendant.in_reply_to_id + depth += 1 + statuses << descendant + else + @descendant_threads << create_descendant_thread(depth, statuses) + + @descendant_threads.reverse_each do |descendant_thread| + statuses = descendant_thread[:statuses] + + index = statuses.find_index do |thread_status| + thread_status.id == descendant.in_reply_to_id + end + + if index.present? + depth += index - statuses.size + break + end + + depth -= statuses.size + end + + statuses = [descendant] + end + end + + @descendant_threads << create_descendant_thread(depth, statuses) + end + + @max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT + end + def set_link_headers response.headers['Link'] = LinkHeader.new( [ diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb index 44e9c0bb8..8cb54a148 100644 --- a/app/controllers/stream_entries_controller.rb +++ b/app/controllers/stream_entries_controller.rb @@ -24,6 +24,7 @@ class StreamEntriesController < ApplicationController skip_session! expires_in 3.minutes, public: true end + render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true)) end end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 5d11a8139..a76be26e5 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class TagsController < ApplicationController + PAGE_SIZE = 20 + before_action :set_body_classes before_action :set_instance_presenter @@ -14,8 +16,15 @@ class TagsController < ApplicationController @initial_state_json = serializable_resource.to_json end + format.rss do + @statuses = Status.as_tag_timeline(@tag).limit(PAGE_SIZE) + @statuses = cache_collection(@statuses, Status) + + render xml: RSS::TagSerializer.render(@tag, @statuses) + end + format.json do - @statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(20, params[:max_id]) + @statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(PAGE_SIZE, params[:max_id]) @statuses = cache_collection(@statuses, Status) render json: collection_presenter, diff --git a/app/helpers/admin/account_moderation_notes_helper.rb b/app/helpers/admin/account_moderation_notes_helper.rb index b17c52264..fdfadef08 100644 --- a/app/helpers/admin/account_moderation_notes_helper.rb +++ b/app/helpers/admin/account_moderation_notes_helper.rb @@ -1,4 +1,20 @@ # frozen_string_literal: true module Admin::AccountModerationNotesHelper + def admin_account_link_to(account) + link_to admin_account_path(account.id), class: name_tag_classes(account) do + safe_join([ + image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), + content_tag(:span, account.acct, class: 'username'), + ], ' ') + end + end + + private + + def name_tag_classes(account) + classes = ['name-tag'] + classes << 'suspended' if account.suspended? + classes.join(' ') + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bab4615a1..95863ab1f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -63,4 +63,8 @@ module ApplicationHelper def opengraph(property, content) tag(:meta, content: content, property: property) end + + def react_component(name, props = {}) + content_tag(:div, nil, data: { component: name.to_s.camelcase, props: Oj.dump(props) }) + end end diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index dfb8fcb8b..e9056166c 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -5,6 +5,10 @@ module JsonLdHelper haystack.is_a?(Array) ? haystack.include?(needle) : haystack == needle end + def equals_or_includes_any?(haystack, needles) + needles.any? { |needle| equals_or_includes?(haystack, needle) } + end + def first_of_value(value) value.is_a?(Array) ? value.first : value end @@ -44,7 +48,7 @@ module JsonLdHelper end def canonicalize(json) - graph = RDF::Graph.new << JSON::LD::API.toRdf(json) + graph = RDF::Graph.new << JSON::LD::API.toRdf(json, documentLoader: method(:load_jsonld_context)) graph.dump(:normalize) end @@ -86,4 +90,19 @@ module JsonLdHelper request.add_headers('Accept' => 'application/activity+json, application/ld+json') request end + + def load_jsonld_context(url, _options = {}, &_block) + json = Rails.cache.fetch("jsonld:context:#{url}", expires_in: 30.days, raw: true) do + request = Request.new(:get, url) + request.add_headers('Accept' => 'application/ld+json') + + request.perform do |res| + raise JSON::LD::JsonLdError::LoadingDocumentFailed unless res.code == 200 && res.mime_type == 'application/ld+json' + res.body_with_limit + end + end + + doc = JSON::LD::API::RemoteDocument.new(url, json) + block_given? ? yield(doc) : doc + end end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index a2f5917f9..f78e5fbc3 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -7,12 +7,14 @@ module SettingsHelper bg: 'Български', ca: 'Català', de: 'Deutsch', + el: 'Ελληνικά', eo: 'Esperanto', es: 'Español', + eu: 'Euskara', fa: 'فارسی', - gl: 'Galego', fi: 'Suomi', fr: 'Français', + gl: 'Galego', he: 'עברית', hr: 'Hrvatski', hu: 'Magyar', @@ -33,6 +35,7 @@ module SettingsHelper sr: 'Српски', 'sr-Latn': 'Srpski (latinica)', sv: 'Svenska', + te: 'తెలుగు', th: 'ภาษาไทย', tr: 'Türkçe', uk: 'Українська', diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb index 3992432db..c6f12ecd4 100644 --- a/app/helpers/stream_entries_helper.rb +++ b/app/helpers/stream_entries_helper.rb @@ -12,17 +12,17 @@ module StreamEntriesHelper prepend_str = [ [ number_to_human(account.statuses_count, strip_insignificant_zeros: true), - t('accounts.posts'), + I18n.t('accounts.posts'), ].join(' '), [ number_to_human(account.following_count, strip_insignificant_zeros: true), - t('accounts.following'), + I18n.t('accounts.following'), ].join(' '), [ number_to_human(account.followers_count, strip_insignificant_zeros: true), - t('accounts.followers'), + I18n.t('accounts.followers'), ].join(' '), ].join(', ') @@ -40,16 +40,16 @@ module StreamEntriesHelper end end - text = attachments.to_a.reject { |_, value| value.zero? }.map { |key, value| t("statuses.attached.#{key}", count: value) }.join(' · ') + text = attachments.to_a.reject { |_, value| value.zero? }.map { |key, value| I18n.t("statuses.attached.#{key}", count: value) }.join(' · ') return if text.blank? - t('statuses.attached.description', attached: text) + I18n.t('statuses.attached.description', attached: text) end def status_text_summary(status) return if status.spoiler_text.blank? - t('statuses.content_warning', warning: status.spoiler_text) + I18n.t('statuses.content_warning', warning: status.spoiler_text) end def status_description(status) @@ -113,6 +113,19 @@ module StreamEntriesHelper end end + def fa_visibility_icon(status) + case status.visibility + when 'public' + fa_icon 'globe fw' + when 'unlisted' + fa_icon 'unlock-alt fw' + when 'private' + fa_icon 'lock fw' + when 'direct' + fa_icon 'envelope fw' + end + end + private def simplified_text(text) diff --git a/app/javascript/core/admin.js b/app/javascript/core/admin.js index b4125e84e..28f27fbc6 100644 --- a/app/javascript/core/admin.js +++ b/app/javascript/core/admin.js @@ -26,6 +26,7 @@ delegate(document, batchCheckboxClassName, 'change', () => { const checkAllElement = document.querySelector('#batch_checkbox_all'); if (checkAllElement) { checkAllElement.checked = [].every.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked); + checkAllElement.indeterminate = !checkAllElement.checked && [].some.call(document.querySelectorAll(batchCheckboxClassName), (content) => content.checked); } }); diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index eee9c6928..fe3e831d5 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -4,6 +4,7 @@ import { throttle } from 'lodash'; import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; import { tagHistory } from '../settings'; import { useEmoji } from './emojis'; +import resizeImage from '../utils/resize_image'; import { importFetchedAccounts } from './importer'; import { updateTimeline } from './timelines'; import { showAlertForError } from './alerts'; @@ -182,18 +183,14 @@ export function uploadCompose(files) { dispatch(uploadComposeRequest()); - let data = new FormData(); - data.append('file', files[0]); + resizeImage(files[0]).then(file => { + const data = new FormData(); + data.append('file', file); - api(getState).post('/api/v1/media', data, { - onUploadProgress: function (e) { - dispatch(uploadComposeProgress(e.loaded, e.total)); - }, - }).then(function (response) { - dispatch(uploadComposeSuccess(response.data)); - }).catch(function (error) { - dispatch(uploadComposeFail(error)); - }); + return api(getState).post('/api/v1/media', data, { + onUploadProgress: ({ loaded, total }) => dispatch(uploadComposeProgress(loaded, total)), + }).then(({ data }) => dispatch(uploadComposeSuccess(data))); + }).catch(error => dispatch(uploadComposeFail(error))); }; }; diff --git a/app/javascript/mastodon/actions/push_notifications/registerer.js b/app/javascript/mastodon/actions/push_notifications/registerer.js index 60b215f02..82fe4519a 100644 --- a/app/javascript/mastodon/actions/push_notifications/registerer.js +++ b/app/javascript/mastodon/actions/push_notifications/registerer.js @@ -1,4 +1,5 @@ import api from '../../api'; +import { decode as decodeBase64 } from '../../utils/base64'; import { pushNotificationsSetting } from '../../settings'; import { setBrowserSupport, setSubscription, clearSubscription } from './setter'; import { me } from '../../initial_state'; @@ -10,13 +11,7 @@ const urlBase64ToUint8Array = (base64String) => { .replace(/\-/g, '+') .replace(/_/g, '/'); - const rawData = window.atob(base64); - const outputArray = new Uint8Array(rawData.length); - - for (let i = 0; i < rawData.length; ++i) { - outputArray[i] = rawData.charCodeAt(i); - } - return outputArray; + return decodeBase64(base64); }; const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content'); diff --git a/app/javascript/mastodon/base_polyfills.js b/app/javascript/mastodon/base_polyfills.js index 8fbb17785..997813a04 100644 --- a/app/javascript/mastodon/base_polyfills.js +++ b/app/javascript/mastodon/base_polyfills.js @@ -5,6 +5,7 @@ import includes from 'array-includes'; import assign from 'object-assign'; import values from 'object.values'; import isNaN from 'is-nan'; +import { decode as decodeBase64 } from './utils/base64'; if (!Array.prototype.includes) { includes.shim(); @@ -21,3 +22,23 @@ if (!Object.values) { if (!Number.isNaN) { Number.isNaN = isNaN; } + +if (!HTMLCanvasElement.prototype.toBlob) { + const BASE64_MARKER = ';base64,'; + + Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { + value(callback, type = 'image/png', quality) { + const dataURL = this.toDataURL(type, quality); + let data; + + if (dataURL.indexOf(BASE64_MARKER) >= 0) { + const [, base64] = dataURL.split(BASE64_MARKER); + data = decodeBase64(base64); + } else { + [, data] = dataURL.split(','); + } + + callback(new Blob([data], { type })); + }, + }); +} diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js index 34904194f..a4f5cf50c 100644 --- a/app/javascript/mastodon/components/autosuggest_textarea.js +++ b/app/javascript/mastodon/components/autosuggest_textarea.js @@ -84,9 +84,17 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { return; } + if (e.which === 229 || e.isComposing) { + // Ignore key events during text composition + // e.key may be a name of the physical key even in this case (e.x. Safari / Chrome on Mac) + return; + } + switch(e.key) { case 'Escape': - if (!suggestionsHidden) { + if (suggestions.size === 0 || suggestionsHidden) { + document.querySelector('.ui').parentElement.focus(); + } else { e.preventDefault(); this.setState({ suggestionsHidden: true }); } @@ -125,16 +133,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { this.props.onKeyDown(e); } - onKeyUp = e => { - if (e.key === 'Escape' && this.state.suggestionsHidden) { - document.querySelector('.ui').parentElement.focus(); - } - - if (this.props.onKeyUp) { - this.props.onKeyUp(e); - } - } - onBlur = () => { this.setState({ suggestionsHidden: true }); } @@ -186,7 +184,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { } render () { - const { value, suggestions, disabled, placeholder, autoFocus } = this.props; + const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props; const { suggestionsHidden } = this.state; const style = { direction: 'ltr' }; @@ -208,7 +206,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { value={value} onChange={this.onChange} onKeyDown={this.onKeyDown} - onKeyUp={this.onKeyUp} + onKeyUp={onKeyUp} onBlur={this.onBlur} onPaste={this.onPaste} style={style} diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index c5c6f73b3..982d34718 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -63,7 +63,7 @@ class DropdownMenu extends React.PureComponent { if (typeof action === 'function') { e.preventDefault(); - action(); + action(e); } else if (to) { e.preventDefault(); this.context.router.history.push(to); diff --git a/app/javascript/mastodon/components/relative_timestamp.js b/app/javascript/mastodon/components/relative_timestamp.js index 51588e78c..3c8db7092 100644 --- a/app/javascript/mastodon/components/relative_timestamp.js +++ b/app/javascript/mastodon/components/relative_timestamp.js @@ -20,7 +20,7 @@ const dateFormatOptions = { }; const shortDateFormatOptions = { - month: 'numeric', + month: 'short', day: 'numeric', }; @@ -66,12 +66,17 @@ export default class RelativeTimestamp extends React.Component { static propTypes = { intl: PropTypes.object.isRequired, timestamp: PropTypes.string.isRequired, + year: PropTypes.number.isRequired, }; state = { now: this.props.intl.now(), }; + static defaultProps = { + year: (new Date()).getFullYear(), + }; + shouldComponentUpdate (nextProps, nextState) { // As of right now the locale doesn't change without a new page load, // but we might as well check in case that ever changes. @@ -114,7 +119,7 @@ export default class RelativeTimestamp extends React.Component { } render () { - const { timestamp, intl } = this.props; + const { timestamp, intl, year } = this.props; const date = new Date(timestamp); const delta = this.state.now - date.getTime(); @@ -123,7 +128,7 @@ export default class RelativeTimestamp extends React.Component { if (delta < 10 * SECOND) { relativeTime = intl.formatMessage(messages.just_now); - } else if (delta < 3 * DAY) { + } else if (delta < 7 * DAY) { if (delta < MINUTE) { relativeTime = intl.formatMessage(messages.seconds, { number: Math.floor(delta / SECOND) }); } else if (delta < HOUR) { @@ -133,8 +138,10 @@ export default class RelativeTimestamp extends React.Component { } else { relativeTime = intl.formatMessage(messages.days, { number: Math.floor(delta / DAY) }); } - } else { + } else if (date.getFullYear() === year) { relativeTime = intl.formatDate(date, shortDateFormatOptions); + } else { + relativeTime = intl.formatDate(date, { ...shortDateFormatOptions, year: 'numeric' }); } return ( diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js index fd6858d05..7cdd63910 100644 --- a/app/javascript/mastodon/components/scrollable_list.js +++ b/app/javascript/mastodon/components/scrollable_list.js @@ -35,6 +35,7 @@ export default class ScrollableList extends PureComponent { state = { fullscreen: null, + mouseOver: false, }; intersectionObserverWrapper = new IntersectionObserverWrapper(); @@ -71,7 +72,7 @@ export default class ScrollableList extends PureComponent { const someItemInserted = React.Children.count(prevProps.children) > 0 && React.Children.count(prevProps.children) < React.Children.count(this.props.children) && this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props); - if (someItemInserted && this.node.scrollTop > 0) { + if (someItemInserted && this.node.scrollTop > 0 || (this.state.mouseOver && !prevProps.isLoading)) { return this.node.scrollHeight - this.node.scrollTop; } else { return null; @@ -139,6 +140,14 @@ export default class ScrollableList extends PureComponent { this.props.onLoadMore(); } + handleMouseEnter = () => { + this.setState({ mouseOver: true }); + } + + handleMouseLeave = () => { + this.setState({ mouseOver: false }); + } + render () { const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage, onLoadMore } = this.props; const { fullscreen } = this.state; @@ -149,7 +158,7 @@ export default class ScrollableList extends PureComponent { if (isLoading || childrenCount > 0 || !emptyMessage) { scrollableArea = ( - <div className={classNames('scrollable', { fullscreen })} ref={this.setRef}> + <div className={classNames('scrollable', { fullscreen })} ref={this.setRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> <div role='feed' className='item-list'> {prepend} diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index e5f7c9399..402d558c4 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -114,12 +114,12 @@ export default class Status extends ImmutablePureComponent { this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`); } - handleHotkeyMoveUp = () => { - this.props.onMoveUp(this.props.status.get('id')); + handleHotkeyMoveUp = e => { + this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured')); } - handleHotkeyMoveDown = () => { - this.props.onMoveDown(this.props.status.get('id')); + handleHotkeyMoveDown = e => { + this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured')); } handleHotkeyToggleHidden = () => { @@ -233,7 +233,7 @@ export default class Status extends ImmutablePureComponent { return ( <HotKeys handlers={handlers}> - <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0}> + <div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null}> {prepend} <div className={classNames('status', `status-${status.get('visibility')}`, { muted: this.props.muted })} data-id={status.get('id')}> diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index e58625582..d605dbc8a 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -153,7 +153,9 @@ export default class StatusActionBar extends ImmutablePureComponent { if (publicStatus) { menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); } else { - menu.push({ text: intl.formatMessage(status.get('reblog') ? messages.reblog_private : messages.cancel_reblog_private), action: this.handleReblogClick }); + if (status.get('visibility') === 'private') { + menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick }); + } } menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index c98d4564e..0c971ceb0 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -30,13 +30,25 @@ export default class StatusList extends ImmutablePureComponent { trackScroll: true, }; - handleMoveUp = id => { - const elementIndex = this.props.statusIds.indexOf(id) - 1; + getFeaturedStatusCount = () => { + return this.props.featuredStatusIds ? this.props.featuredStatusIds.size : 0; + } + + getCurrentStatusIndex = (id, featured) => { + if (featured) { + return this.props.featuredStatusIds.indexOf(id); + } else { + return this.props.statusIds.indexOf(id) + this.getFeaturedStatusCount(); + } + } + + handleMoveUp = (id, featured) => { + const elementIndex = this.getCurrentStatusIndex(id, featured) - 1; this._selectChild(elementIndex); } - handleMoveDown = id => { - const elementIndex = this.props.statusIds.indexOf(id) + 1; + handleMoveDown = (id, featured) => { + const elementIndex = this.getCurrentStatusIndex(id, featured) + 1; this._selectChild(elementIndex); } diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js index dc8fc02ba..84665a7e8 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js @@ -162,12 +162,12 @@ class EmojiPickerMenu extends React.PureComponent { static defaultProps = { style: {}, loading: true, - placement: 'bottom', frequentlyUsedEmojis: [], }; state = { modifierOpen: false, + placement: null, }; handleDocumentClick = e => { @@ -298,7 +298,7 @@ export default class EmojiPickerDropdown extends React.PureComponent { this.dropdown = c; } - onShowDropdown = () => { + onShowDropdown = ({ target }) => { this.setState({ active: true }); if (!EmojiPicker) { @@ -313,6 +313,9 @@ export default class EmojiPickerDropdown extends React.PureComponent { this.setState({ loading: false }); }); } + + const { top } = target.getBoundingClientRect(); + this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' }); } onHideDropdown = () => { @@ -324,7 +327,7 @@ export default class EmojiPickerDropdown extends React.PureComponent { if (this.state.active) { this.onHideDropdown(); } else { - this.onShowDropdown(); + this.onShowDropdown(e); } } } @@ -346,7 +349,7 @@ export default class EmojiPickerDropdown extends React.PureComponent { render () { const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props; const title = intl.formatMessage(messages.emoji); - const { active, loading } = this.state; + const { active, loading, placement } = this.state; return ( <div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}> @@ -358,7 +361,7 @@ export default class EmojiPickerDropdown extends React.PureComponent { /> </div> - <Overlay show={active} placement='bottom' target={this.findTarget}> + <Overlay show={active} placement={placement} target={this.findTarget}> <EmojiPickerMenu custom_emojis={this.props.custom_emojis} loading={loading} diff --git a/app/javascript/mastodon/features/compose/components/reply_indicator.js b/app/javascript/mastodon/features/compose/components/reply_indicator.js index d8cda96f3..5b4b81eac 100644 --- a/app/javascript/mastodon/features/compose/components/reply_indicator.js +++ b/app/javascript/mastodon/features/compose/components/reply_indicator.js @@ -51,7 +51,7 @@ export default class ReplyIndicator extends ImmutablePureComponent { return ( <div className='reply-indicator'> <div className='reply-indicator__header'> - <div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div> + <div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted /></div> <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name'> <div className='reply-indicator__display-avatar'><Avatar account={status.get('account')} size={24} /></div> diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index fc34c8cdc..bb9b75505 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -123,7 +123,9 @@ export default class ActionBar extends React.PureComponent { if (publicStatus) { menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); } else { - menu.push({ text: intl.formatMessage(status.get('reblog') ? messages.reblog_private : messages.cancel_reblog_private), action: this.handleReblogClick }); + if (status.get('visibility') === 'private') { + menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick }); + } } menu.push(null); diff --git a/app/javascript/mastodon/load_polyfills.js b/app/javascript/mastodon/load_polyfills.js index 815e1905b..8cb81c1a6 100644 --- a/app/javascript/mastodon/load_polyfills.js +++ b/app/javascript/mastodon/load_polyfills.js @@ -12,12 +12,13 @@ function importExtraPolyfills() { function loadPolyfills() { const needsBasePolyfills = !( + Array.prototype.includes && + HTMLCanvasElement.prototype.toBlob && window.Intl && + Number.isNaN && Object.assign && Object.values && - Number.isNaN && - window.Symbol && - Array.prototype.includes + window.Symbol ); // Latest version of Firefox and Safari do not have IntersectionObserver. diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index 24c8a5b54..947348f70 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -2,7 +2,7 @@ "account.block": "حظر @{name}", "account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}", "account.blocked": "محظور", - "account.direct": "Direct message @{name}", + "account.direct": "رسالة خاصة إلى @{name}", "account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.", "account.domain_blocked": "النطاق مخفي", "account.edit_profile": "تعديل الملف الشخصي", @@ -18,7 +18,7 @@ "account.mute_notifications": "كتم إخطارات @{name}", "account.muted": "مكتوم", "account.posts": "التبويقات", - "account.posts_with_replies": "تبويقات تحتوي على رُدود", + "account.posts_with_replies": "التبويقات و الردود", "account.report": "أبلغ عن @{name}", "account.requested": "في انتظار الموافقة", "account.share": "مشاركة @{name}'s profile", @@ -29,8 +29,8 @@ "account.unmute": "إلغاء الكتم عن @{name}", "account.unmute_notifications": "إلغاء كتم إخطارات @{name}", "account.view_full_profile": "عرض الملف الشخصي كاملا", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "لقد طرأ هناك خطأ غير متوقّع.", + "alert.unexpected.title": "المعذرة !", "boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة", "bundle_column_error.body": "لقد وقع هناك خطأ أثناء عملية تحميل هذا العنصر.", "bundle_column_error.retry": "إعادة المحاولة", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "إعادة المحاولة", "column.blocks": "الحسابات المحجوبة", "column.community": "الخيط العام المحلي", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "الرسائل المباشرة", + "column.domain_blocks": "النطاقات المخفية", "column.favourites": "المفضلة", "column.follow_requests": "طلبات المتابعة", "column.home": "الرئيسية", @@ -59,7 +59,7 @@ "column_header.unpin": "فك التدبيس", "column_subheading.navigation": "التصفح", "column_subheading.settings": "الإعدادات", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "لن يَظهر هذا التبويق إلا للمستخدمين المذكورين.", "compose_form.hashtag_warning": "هذا التبويق لن يُدرَج تحت أي وسم كان بما أنه غير مُدرَج. لا يُسمح بالبحث إلّا عن التبويقات العمومية عن طريق الوسوم.", "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.", "compose_form.lock_disclaimer.lock": "مقفل", @@ -101,7 +101,7 @@ "emoji_button.symbols": "رموز", "emoji_button.travel": "أماكن و أسفار", "empty_column.community": "الخط الزمني المحلي فارغ. أكتب شيئا ما للعامة كبداية !", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.", "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.", "empty_column.home": "إنك لا تتبع بعد أي شخص إلى حد الآن. زر {public} أو استخدام حقل البحث لكي تبدأ على التعرف على مستخدمين آخرين.", "empty_column.home.public_timeline": "الخيط العام", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "لذِكر الناشر", "keyboard_shortcuts.reply": "للردّ", "keyboard_shortcuts.search": "للتركيز على البحث", + "keyboard_shortcuts.toggle_hidden": "لعرض أو إخفاء النص مِن وراء التحذير", "keyboard_shortcuts.toot": "لتحرير تبويق جديد", "keyboard_shortcuts.unfocus": "لإلغاء التركيز على حقل النص أو نافذة البحث", "keyboard_shortcuts.up": "للإنتقال إلى أعلى القائمة", @@ -156,8 +157,8 @@ "mute_modal.hide_notifications": "هل تود إخفاء الإخطارات القادمة من هذا المستخدم ؟", "navigation_bar.blocks": "الحسابات المحجوبة", "navigation_bar.community_timeline": "الخيط العام المحلي", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "الرسائل المباشِرة", + "navigation_bar.domain_blocks": "النطاقات المخفية", "navigation_bar.edit_profile": "تعديل الملف الشخصي", "navigation_bar.favourites": "المفضلة", "navigation_bar.follow_requests": "طلبات المتابعة", @@ -241,10 +242,10 @@ "search_results.total": "{count, number} {count, plural, one {result} و {results}}", "standalone.public_title": "نظرة على ...", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "إلغاء الترقية", "status.cannot_reblog": "تعذرت ترقية هذا المنشور", "status.delete": "إحذف", - "status.direct": "Direct message @{name}", + "status.direct": "رسالة خاصة إلى @{name}", "status.embed": "إدماج", "status.favourite": "أضف إلى المفضلة", "status.load_more": "حمّل المزيد", @@ -275,7 +276,7 @@ "tabs_bar.home": "الرئيسية", "tabs_bar.local_timeline": "المحلي", "tabs_bar.notifications": "الإخطارات", - "tabs_bar.search": "Search", + "tabs_bar.search": "البحث", "ui.beforeunload": "سوف تفقد مسودتك إن تركت ماستدون.", "upload_area.title": "إسحب ثم أفلت للرفع", "upload_button.label": "إضافة وسائط", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 25ef6db65..971475114 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 6a44808e0..f2e3699d5 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -2,7 +2,7 @@ "account.block": "Bloca @{name}", "account.block_domain": "Amaga-ho tot de {domain}", "account.blocked": "Bloquejat", - "account.direct": "Direct message @{name}", + "account.direct": "Missatge directe @{name}", "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.", "account.domain_blocked": "Domini ocult", "account.edit_profile": "Edita el perfil", @@ -18,7 +18,7 @@ "account.mute_notifications": "Notificacions desactivades de @{name}", "account.muted": "Silenciat", "account.posts": "Toots", - "account.posts_with_replies": "Toots amb respostes", + "account.posts_with_replies": "Toots i respostes", "account.report": "Informe @{name}", "account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment", "account.share": "Comparteix el perfil de @{name}", @@ -29,8 +29,8 @@ "account.unmute": "Treure silenci de @{name}", "account.unmute_notifications": "Activar notificacions de @{name}", "account.view_full_profile": "Mostra el perfil complet", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "S'ha produït un error inesperat.", + "alert.unexpected.title": "Vaja!", "boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop", "bundle_column_error.body": "S'ha produït un error en carregar aquest component.", "bundle_column_error.retry": "Torna-ho a provar", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Torna-ho a provar", "column.blocks": "Usuaris blocats", "column.community": "Línia de temps local", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Missatges directes", + "column.domain_blocks": "Dominis ocults", "column.favourites": "Favorits", "column.follow_requests": "Peticions per seguir-te", "column.home": "Inici", @@ -59,7 +59,7 @@ "column_header.unpin": "No fixis", "column_subheading.navigation": "Navegació", "column_subheading.settings": "Configuració", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Aquest toot només serà visible per a tots els usuaris esmentats.", "compose_form.hashtag_warning": "Aquest toot no es mostrarà en cap etiqueta ja que no està llistat. Només els toots públics poden ser cercats per etiqueta.", "compose_form.lock_disclaimer": "El teu compte no està bloquejat {locked}. Tothom pot seguir-te i veure els teus missatges a seguidors.", "compose_form.lock_disclaimer.lock": "blocat", @@ -68,7 +68,7 @@ "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.marked": "Mèdia marcat com a sensible", "compose_form.sensitive.unmarked": "Mèdia no està marcat com a sensible", - "compose_form.spoiler.marked": "Text ocult sota l'avís", + "compose_form.spoiler.marked": "Text es ocult sota l'avís", "compose_form.spoiler.unmarked": "Text no ocult", "compose_form.spoiler_placeholder": "Escriu l'avís aquí", "confirmation_modal.cancel": "Cancel·la", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Símbols", "emoji_button.travel": "Viatges i Llocs", "empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per fer rodar la pilota!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Encara no tens missatges directes. Quan enviïs o rebis un, es mostrarà aquí.", "empty_column.hashtag": "Encara no hi ha res amb aquesta etiqueta.", "empty_column.home": "Encara no segueixes ningú. Visita {public} o fes cerca per començar i conèixer altres usuaris.", "empty_column.home.public_timeline": "la línia de temps pública", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "per esmentar l'autor", "keyboard_shortcuts.reply": "respondre", "keyboard_shortcuts.search": "per centrar la cerca", + "keyboard_shortcuts.toggle_hidden": "per a mostrar/amagar text sota CW", "keyboard_shortcuts.toot": "per a començar un toot nou de trinca", "keyboard_shortcuts.unfocus": "descentrar l'area de composició de text/cerca", "keyboard_shortcuts.up": "moure amunt en la llista", @@ -156,8 +157,8 @@ "mute_modal.hide_notifications": "Amagar notificacions d'aquest usuari?", "navigation_bar.blocks": "Usuaris bloquejats", "navigation_bar.community_timeline": "Línia de temps Local", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "Missatges directes", + "navigation_bar.domain_blocks": "Dominis ocults", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favorits", "navigation_bar.follow_requests": "Sol·licituds de seguiment", @@ -241,10 +242,10 @@ "search_results.total": "{count, number} {count, plural, un {result} altres {results}}", "standalone.public_title": "Una mirada a l'interior ...", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Desfer l'impuls", "status.cannot_reblog": "Aquesta publicació no pot ser retootejada", "status.delete": "Esborrar", - "status.direct": "Direct message @{name}", + "status.direct": "Missatge directe @{name}", "status.embed": "Incrustar", "status.favourite": "Favorit", "status.load_more": "Carrega més", @@ -257,7 +258,7 @@ "status.pin": "Fixat en el perfil", "status.pinned": "Toot fixat", "status.reblog": "Impuls", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Impulsar a l'audiència original", "status.reblogged_by": "{name} ha retootejat", "status.reply": "Respondre", "status.replyAll": "Respondre al tema", @@ -275,7 +276,7 @@ "tabs_bar.home": "Inici", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificacions", - "tabs_bar.search": "Search", + "tabs_bar.search": "Cerca", "ui.beforeunload": "El vostre esborrany es perdrà si sortiu de Mastodon.", "upload_area.title": "Arrossega i deixa anar per carregar", "upload_button.label": "Afegir multimèdia", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 69c2ae8d8..f442e0675 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -18,7 +18,7 @@ "account.mute_notifications": "Benachrichtigungen von @{name} verbergen", "account.muted": "Stummgeschaltet", "account.posts": "Beiträge", - "account.posts_with_replies": "Beiträge mit Antworten", + "account.posts_with_replies": "Beiträge und Antworten", "account.report": "@{name} melden", "account.requested": "Warte auf Erlaubnis. Klicke zum Abbrechen", "account.share": "Profil von @{name} teilen", @@ -29,8 +29,8 @@ "account.unmute": "@{name} nicht mehr stummschalten", "account.unmute_notifications": "Benachrichtigungen von @{name} einschalten", "account.view_full_profile": "Vollständiges Profil anzeigen", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "Ein unerwarteter Fehler ist aufgetreten.", + "alert.unexpected.title": "Hoppla!", "boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen", "bundle_column_error.body": "Etwas ist beim Laden schiefgelaufen.", "bundle_column_error.retry": "Erneut versuchen", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Erneut versuchen", "column.blocks": "Blockierte Profile", "column.community": "Lokale Zeitleiste", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Direktnachrichten", + "column.domain_blocks": "Versteckte Domains", "column.favourites": "Favoriten", "column.follow_requests": "Folgeanfragen", "column.home": "Startseite", @@ -59,17 +59,17 @@ "column_header.unpin": "Lösen", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Einstellungen", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Dieser Beitrag wird nur für die erwähnten Nutzer sichtbar sein.", "compose_form.hashtag_warning": "Dieser Beitrag wird nicht unter einen dieser Hashtags sichtbar sein, solange er ungelistet ist. Bei einer Suche kann er nicht gefunden werden.", "compose_form.lock_disclaimer": "Dein Profil ist nicht {locked}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.", "compose_form.lock_disclaimer.lock": "gesperrt", "compose_form.placeholder": "Worüber möchtest du schreiben?", "compose_form.publish": "Tröt", "compose_form.publish_loud": "{publish}!", - "compose_form.sensitive.marked": "Media is marked as sensitive", - "compose_form.sensitive.unmarked": "Media is not marked as sensitive", - "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.sensitive.marked": "Medien sind als heikel markiert", + "compose_form.sensitive.unmarked": "Medien sind nicht als heikel markiert", + "compose_form.spoiler.marked": "Text ist hinter einer Warnung versteckt", + "compose_form.spoiler.unmarked": "Text ist nicht versteckt", "compose_form.spoiler_placeholder": "Inhaltswarnung", "confirmation_modal.cancel": "Abbrechen", "confirmations.block.confirm": "Blockieren", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Symbole", "emoji_button.travel": "Reisen und Orte", "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe einen öffentlichen Beitrag, um den Ball ins Rollen zu bringen!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Du hast noch keine Direktnachrichten erhalten. Wenn du eine sendest oder empfängst, wird sie hier zu sehen sein.", "empty_column.hashtag": "Unter diesem Hashtag gibt es noch nichts.", "empty_column.home": "Deine Startseite ist leer! Besuche {public} oder nutze die Suche, um loszulegen und andere Leute zu finden.", "empty_column.home.public_timeline": "die öffentliche Zeitleiste", @@ -130,11 +130,12 @@ "keyboard_shortcuts.enter": "um den Status zu öffnen", "keyboard_shortcuts.favourite": "um zu favorisieren", "keyboard_shortcuts.heading": "Tastenkombinationen", - "keyboard_shortcuts.hotkey": "Hotkey", + "keyboard_shortcuts.hotkey": "Tastenkürzel", "keyboard_shortcuts.legend": "um diese Übersicht anzuzeigen", "keyboard_shortcuts.mention": "um Autor_in zu erwähnen", "keyboard_shortcuts.reply": "um zu antworten", "keyboard_shortcuts.search": "um die Suche zu fokussieren", + "keyboard_shortcuts.toggle_hidden": "um den Text hinter einer Inhaltswarnung zu verstecken oder ihn anzuzeigen", "keyboard_shortcuts.toot": "um einen neuen Toot zu beginnen", "keyboard_shortcuts.unfocus": "um das Textfeld/die Suche nicht mehr zu fokussieren", "keyboard_shortcuts.up": "sich in der Liste hinauf bewegen", @@ -156,8 +157,8 @@ "mute_modal.hide_notifications": "Benachrichtigungen von diesem Account verbergen?", "navigation_bar.blocks": "Blockierte Profile", "navigation_bar.community_timeline": "Lokale Zeitleiste", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "Direktnachrichten", + "navigation_bar.domain_blocks": "Versteckte Domains", "navigation_bar.edit_profile": "Profil bearbeiten", "navigation_bar.favourites": "Favoriten", "navigation_bar.follow_requests": "Folgeanfragen", @@ -190,8 +191,8 @@ "onboarding.page_four.home": "Die Startseite zeigt dir Beiträge von Leuten, denen du folgst.", "onboarding.page_four.notifications": "Wenn jemand mit dir interagiert, bekommst du eine Mitteilung.", "onboarding.page_one.federation": "Mastodon ist ein soziales Netzwerk, das aus unabhängigen Servern besteht. Diese Server nennen wir auch Instanzen.", - "onboarding.page_one.full_handle": "Your full handle", - "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.", + "onboarding.page_one.full_handle": "Dein vollständiger Benutzername", + "onboarding.page_one.handle_hint": "Das ist das, was du deinen Freunden sagst, um nach dir zu suchen.", "onboarding.page_one.welcome": "Willkommen bei Mastodon!", "onboarding.page_six.admin": "Für deine Instanz ist {admin} zuständig.", "onboarding.page_six.almost_done": "Fast fertig …", @@ -214,50 +215,50 @@ "privacy.public.short": "Öffentlich", "privacy.unlisted.long": "Nicht in öffentlichen Zeitleisten anzeigen", "privacy.unlisted.short": "Nicht gelistet", - "regeneration_indicator.label": "Loading…", - "regeneration_indicator.sublabel": "Your home feed is being prepared!", + "regeneration_indicator.label": "Laden…", + "regeneration_indicator.sublabel": "Deine Heimzeitleiste wird gerade vorbereitet!", "relative_time.days": "{number}d", "relative_time.hours": "{number}h", - "relative_time.just_now": "now", + "relative_time.just_now": "jetzt", "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "reply_indicator.cancel": "Abbrechen", - "report.forward": "Forward to {target}", - "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", - "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.forward": "An {target} weiterleiten", + "report.forward_hint": "Dieses Konto ist von einem anderen Server. Soll eine anonymisierte Kopie des Berichts auch dorthin geschickt werden?", + "report.hint": "Der Bericht wird an die Moderatoren deiner Instanz geschickt. Du kannst hier eine Erklärung angeben, warum du dieses Konto meldest:", "report.placeholder": "Zusätzliche Kommentare", "report.submit": "Absenden", "report.target": "{target} melden", "search.placeholder": "Suche", - "search_popout.search_format": "Advanced search format", - "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", - "search_popout.tips.hashtag": "hashtag", + "search_popout.search_format": "Fortgeschrittenes Suchformat", + "search_popout.tips.full_text": "Simpler Text gibt Beiträge, die du geschrieben, favorisiert und geteilt hast zurück. Außerdem auch Beiträge in denen du erwähnt wurdest, als auch passende Nutzernamen, Anzeigenamen oder Hashtags.", + "search_popout.tips.hashtag": "Hashtag", "search_popout.tips.status": "status", - "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", - "search_popout.tips.user": "user", - "search_results.accounts": "People", + "search_popout.tips.text": "Einfacher Text gibt Anzeigenamen, Benutzernamen und Hashtags zurück", + "search_popout.tips.user": "Nutzer", + "search_results.accounts": "Personen", "search_results.hashtags": "Hashtags", - "search_results.statuses": "Toots", + "search_results.statuses": "Beiträge", "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}", "standalone.public_title": "Ein kleiner Einblick …", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Nicht mehr teilen", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", "status.delete": "Löschen", - "status.direct": "Direct message @{name}", + "status.direct": "Direktnachricht @{name}", "status.embed": "Einbetten", "status.favourite": "Favorisieren", "status.load_more": "Weitere laden", "status.media_hidden": "Medien versteckt", "status.mention": "@{name} erwähnen", "status.more": "Mehr", - "status.mute": "Mute @{name}", + "status.mute": "@{name} stummschalten", "status.mute_conversation": "Thread stummschalten", "status.open": "Diesen Beitrag öffnen", "status.pin": "Im Profil anheften", - "status.pinned": "Pinned toot", + "status.pinned": "Angehefteter Beitrag", "status.reblog": "Teilen", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "An das eigentliche Publikum teilen", "status.reblogged_by": "{name} teilte", "status.reply": "Antworten", "status.replyAll": "Auf Thread antworten", @@ -266,21 +267,21 @@ "status.sensitive_warning": "Heikle Inhalte", "status.share": "Teilen", "status.show_less": "Weniger anzeigen", - "status.show_less_all": "Show less for all", + "status.show_less_all": "Zeige weniger für alles", "status.show_more": "Mehr anzeigen", - "status.show_more_all": "Show more for all", + "status.show_more_all": "Zeige mehr für alles", "status.unmute_conversation": "Stummschaltung von Thread aufheben", "status.unpin": "Vom Profil lösen", "tabs_bar.federated_timeline": "Föderation", "tabs_bar.home": "Startseite", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Mitteilungen", - "tabs_bar.search": "Search", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "tabs_bar.search": "Suchen", + "ui.beforeunload": "Dein Entwurf geht verloren, wenn du Mastodon verlässt.", "upload_area.title": "Zum Hochladen hereinziehen", "upload_button.label": "Mediendatei hinzufügen", "upload_form.description": "Für Menschen mit Sehbehinderung beschreiben", - "upload_form.focus": "Crop", + "upload_form.focus": "Zuschneiden", "upload_form.undo": "Entfernen", "upload_progress.label": "Wird hochgeladen …", "video.close": "Video schließen", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json new file mode 100644 index 000000000..a7e1c408f --- /dev/null +++ b/app/javascript/mastodon/locales/el.json @@ -0,0 +1,296 @@ +{ + "account.block": "Απόκλεισε τον/την @{name}", + "account.block_domain": "Απόκρυψε τα πάντα από τον/την", + "account.blocked": "Αποκλεισμένος/η", + "account.direct": "Απευθείας μήνυμα προς @{name}", + "account.disclaimer_full": "Οι παρακάτω πληροφορίες μπορει να μην αντανακλούν το προφίλ του χρήστη επαρκως.", + "account.domain_blocked": "Domain hidden", + "account.edit_profile": "Επεξεργάσου το προφίλ", + "account.follow": "Ακολούθησε", + "account.followers": "Ακόλουθοι", + "account.follows": "Ακολουθεί", + "account.follows_you": "Σε ακολουθεί", + "account.hide_reblogs": "Απόκρυψη προωθήσεων από τον/την @{name}", + "account.media": "Πολυμέσα", + "account.mention": "Ανέφερε τον/την @{name}", + "account.moved_to": "{name} μετακόμισε στο:", + "account.mute": "Σώπασε τον/την @{name}", + "account.mute_notifications": "Σώπασε τις ειδοποιήσεις από τον/την @{name}", + "account.muted": "Αποσιωπημένος/η", + "account.posts": "Τουτ", + "account.posts_with_replies": "Τουτ και απαντήσεις", + "account.report": "Ανέφερε τον/την @{name}", + "account.requested": "Εκκρεμεί έγκριση. Κάνε κλικ για να ακυρώσεις το αίτημα ακολούθησης", + "account.share": "Μοιράσου το προφίλ του/της @{name}", + "account.show_reblogs": "Δείξε τις προωθήσεις του/της @{name}", + "account.unblock": "Unblock @{name}", + "account.unblock_domain": "Αποκάλυψε το {domain}", + "account.unfollow": "Unfollow", + "account.unmute": "Unmute @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", + "account.view_full_profile": "Δες το πλήρες προφίλ", + "alert.unexpected.message": "Προέκυψε απροσδόκητο σφάλμα.", + "alert.unexpected.title": "Εεπ!", + "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Κάτι πήγε στραβά ενώ φορτωνόταν αυτό το στοιχείο.", + "bundle_column_error.retry": "Δοκίμασε ξανά", + "bundle_column_error.title": "Σφάλμα δικτύου", + "bundle_modal_error.close": "Κλείσε", + "bundle_modal_error.message": "Κάτι πήγε στραβά ενώ φορτωνόταν αυτό το στοιχείο.", + "bundle_modal_error.retry": "Δοκίμασε ξανά", + "column.blocks": "Αποκλεισμένοι χρήστες", + "column.community": "Τοπική ροή", + "column.direct": "Απευθείας μηνύματα", + "column.domain_blocks": "Hidden domains", + "column.favourites": "Αγαπημένα", + "column.follow_requests": "Αιτήματα παρακολούθησης", + "column.home": "Αρχική", + "column.lists": "Λίστες", + "column.mutes": "Αποσιωπημένοι χρήστες", + "column.notifications": "Ειδοποιήσεις", + "column.pins": "Καρφιτσωμένα τουτ", + "column.public": "Ομοσπονδιακή ροή", + "column_back_button.label": "Πίσω", + "column_header.hide_settings": "Απόκρυψη ρυθμίσεων", + "column_header.moveLeft_settings": "Μεταφορά κολώνας αριστερά", + "column_header.moveRight_settings": "Μεταφορά κολώνας δεξιά", + "column_header.pin": "Καρφίτσωμα", + "column_header.show_settings": "Εμφάνιση ρυθμίσεων", + "column_header.unpin": "Ξεκαρφίτσωμα", + "column_subheading.navigation": "Πλοήγηση", + "column_subheading.settings": "Ρυθμίσεις", + "compose_form.direct_message_warning": "Αυτό το τουτ θα εμφανίζεται μόνο σε όλους τους αναφερόμενους χρήστες.", + "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", + "compose_form.lock_disclaimer": "Ο λογαριασμός σου δεν είναι {locked}. Οποιοσδήποτε μπορεί να σε ακολουθήσει για να δει τις δημοσιεύσεις σας προς τους ακολούθους σας.", + "compose_form.lock_disclaimer.lock": "κλειδωμένος", + "compose_form.placeholder": "Τι σκέφτεσαι;", + "compose_form.publish": "Τουτ", + "compose_form.publish_loud": "{publish}!", + "compose_form.sensitive.marked": "Το πολυμέσο έχει σημειωθεί ως ευαίσθητο", + "compose_form.sensitive.unmarked": "Το πολυμέσο δεν έχει σημειωθεί ως ευαίσθητο", + "compose_form.spoiler.marked": "Κείμενο κρυμμένο πίσω από προειδοποίηση", + "compose_form.spoiler.unmarked": "Κείμενο μη κρυμμένο", + "compose_form.spoiler_placeholder": "Γράψε την προειδοποίησή σου εδώ", + "confirmation_modal.cancel": "Άκυρο", + "confirmations.block.confirm": "Απόκλεισε", + "confirmations.block.message": "Σίγουρα θες να αποκλείσεις τον/την {name};", + "confirmations.delete.confirm": "Διέγραψε", + "confirmations.delete.message": "Σίγουρα θες να διαγράψεις αυτή την κατάσταση;", + "confirmations.delete_list.confirm": "Διέγραψε", + "confirmations.delete_list.message": "Σίγουρα θες να διαγράψεις οριστικά αυτή τη λίστα;", + "confirmations.domain_block.confirm": "Hide entire domain", + "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", + "confirmations.mute.confirm": "Mute", + "confirmations.mute.message": "Are you sure you want to mute {name}?", + "confirmations.unfollow.confirm": "Unfollow", + "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", + "embed.instructions": "Embed this status on your website by copying the code below.", + "embed.preview": "Here is what it will look like:", + "emoji_button.activity": "Activity", + "emoji_button.custom": "Custom", + "emoji_button.flags": "Flags", + "emoji_button.food": "Food & Drink", + "emoji_button.label": "Insert emoji", + "emoji_button.nature": "Nature", + "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.objects": "Objects", + "emoji_button.people": "People", + "emoji_button.recent": "Frequently used", + "emoji_button.search": "Search...", + "emoji_button.search_results": "Search results", + "emoji_button.symbols": "Symbols", + "emoji_button.travel": "Travel & Places", + "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.hashtag": "There is nothing in this hashtag yet.", + "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", + "empty_column.home.public_timeline": "the public timeline", + "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", + "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", + "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "follow_request.authorize": "Authorize", + "follow_request.reject": "Reject", + "getting_started.appsshort": "Apps", + "getting_started.faq": "FAQ", + "getting_started.heading": "Getting started", + "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.", + "getting_started.userguide": "User Guide", + "home.column_settings.advanced": "Advanced", + "home.column_settings.basic": "Basic", + "home.column_settings.filter_regex": "Filter out by regular expressions", + "home.column_settings.show_reblogs": "Show boosts", + "home.column_settings.show_replies": "Show replies", + "home.settings": "Column settings", + "keyboard_shortcuts.back": "to navigate back", + "keyboard_shortcuts.boost": "to boost", + "keyboard_shortcuts.column": "to focus a status in one of the columns", + "keyboard_shortcuts.compose": "to focus the compose textarea", + "keyboard_shortcuts.description": "Description", + "keyboard_shortcuts.down": "to move down in the list", + "keyboard_shortcuts.enter": "to open status", + "keyboard_shortcuts.favourite": "to favourite", + "keyboard_shortcuts.heading": "Keyboard Shortcuts", + "keyboard_shortcuts.hotkey": "Hotkey", + "keyboard_shortcuts.legend": "to display this legend", + "keyboard_shortcuts.mention": "to mention author", + "keyboard_shortcuts.reply": "to reply", + "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", + "keyboard_shortcuts.toot": "to start a brand new toot", + "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", + "keyboard_shortcuts.up": "to move up in the list", + "lightbox.close": "Close", + "lightbox.next": "Next", + "lightbox.previous": "Previous", + "lists.account.add": "Add to list", + "lists.account.remove": "Remove from list", + "lists.delete": "Delete list", + "lists.edit": "Edit list", + "lists.new.create": "Add list", + "lists.new.title_placeholder": "New list title", + "lists.search": "Search among people you follow", + "lists.subheading": "Your lists", + "loading_indicator.label": "Loading...", + "media_gallery.toggle_visible": "Toggle visibility", + "missing_indicator.label": "Not found", + "missing_indicator.sublabel": "This resource could not be found", + "mute_modal.hide_notifications": "Hide notifications from this user?", + "navigation_bar.blocks": "Blocked users", + "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.direct": "Direct messages", + "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.edit_profile": "Edit profile", + "navigation_bar.favourites": "Favourites", + "navigation_bar.follow_requests": "Follow requests", + "navigation_bar.info": "Extended information", + "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", + "navigation_bar.lists": "Lists", + "navigation_bar.logout": "Logout", + "navigation_bar.mutes": "Muted users", + "navigation_bar.pins": "Pinned toots", + "navigation_bar.preferences": "Preferences", + "navigation_bar.public_timeline": "Federated timeline", + "notification.favourite": "{name} favourited your status", + "notification.follow": "{name} followed you", + "notification.mention": "{name} mentioned you", + "notification.reblog": "{name} boosted your status", + "notifications.clear": "Clear notifications", + "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", + "notifications.column_settings.alert": "Desktop notifications", + "notifications.column_settings.favourite": "Favourites:", + "notifications.column_settings.follow": "New followers:", + "notifications.column_settings.mention": "Mentions:", + "notifications.column_settings.push": "Push notifications", + "notifications.column_settings.push_meta": "This device", + "notifications.column_settings.reblog": "Boosts:", + "notifications.column_settings.show": "Show in column", + "notifications.column_settings.sound": "Play sound", + "onboarding.done": "Done", + "onboarding.next": "Next", + "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.", + "onboarding.page_four.home": "The home timeline shows posts from people you follow.", + "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.", + "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", + "onboarding.page_one.full_handle": "Your full handle", + "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.", + "onboarding.page_one.welcome": "Welcome to Mastodon!", + "onboarding.page_six.admin": "Your instance's admin is {admin}.", + "onboarding.page_six.almost_done": "Almost done...", + "onboarding.page_six.appetoot": "Bon Appetoot!", + "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.", + "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", + "onboarding.page_six.guidelines": "community guidelines", + "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!", + "onboarding.page_six.various_app": "mobile apps", + "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.", + "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.", + "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.", + "onboarding.skip": "Skip", + "privacy.change": "Adjust status privacy", + "privacy.direct.long": "Post to mentioned users only", + "privacy.direct.short": "Direct", + "privacy.private.long": "Post to followers only", + "privacy.private.short": "Followers-only", + "privacy.public.long": "Post to public timelines", + "privacy.public.short": "Public", + "privacy.unlisted.long": "Do not show in public timelines", + "privacy.unlisted.short": "Unlisted", + "regeneration_indicator.label": "Loading…", + "regeneration_indicator.sublabel": "Your home feed is being prepared!", + "relative_time.days": "{number}d", + "relative_time.hours": "{number}h", + "relative_time.just_now": "now", + "relative_time.minutes": "{number}m", + "relative_time.seconds": "{number}s", + "reply_indicator.cancel": "Cancel", + "report.forward": "Forward to {target}", + "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", + "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.placeholder": "Additional comments", + "report.submit": "Submit", + "report.target": "Report {target}", + "search.placeholder": "Search", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", + "search_results.accounts": "People", + "search_results.hashtags": "Hashtags", + "search_results.statuses": "Toots", + "search_results.total": "{count, number} {count, plural, one {result} other {results}}", + "standalone.public_title": "A look inside...", + "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", + "status.cannot_reblog": "This post cannot be boosted", + "status.delete": "Delete", + "status.direct": "Direct message @{name}", + "status.embed": "Embed", + "status.favourite": "Favourite", + "status.load_more": "Load more", + "status.media_hidden": "Media hidden", + "status.mention": "Mention @{name}", + "status.more": "More", + "status.mute": "Mute @{name}", + "status.mute_conversation": "Mute conversation", + "status.open": "Expand this status", + "status.pin": "Pin on profile", + "status.pinned": "Pinned toot", + "status.reblog": "Boost", + "status.reblog_private": "Boost to original audience", + "status.reblogged_by": "{name} boosted", + "status.reply": "Reply", + "status.replyAll": "Reply to thread", + "status.report": "Report @{name}", + "status.sensitive_toggle": "Click to view", + "status.sensitive_warning": "Sensitive content", + "status.share": "Share", + "status.show_less": "Show less", + "status.show_less_all": "Show less for all", + "status.show_more": "Show more", + "status.show_more_all": "Show more for all", + "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", + "tabs_bar.federated_timeline": "Federated", + "tabs_bar.home": "Home", + "tabs_bar.local_timeline": "Local", + "tabs_bar.notifications": "Notifications", + "tabs_bar.search": "Search", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "upload_area.title": "Drag & drop to upload", + "upload_button.label": "Add media", + "upload_form.description": "Describe for the visually impaired", + "upload_form.focus": "Crop", + "upload_form.undo": "Undo", + "upload_progress.label": "Uploading...", + "video.close": "Close video", + "video.exit_fullscreen": "Exit full screen", + "video.expand": "Expand video", + "video.fullscreen": "Full screen", + "video.hide": "Hide video", + "video.mute": "Mute sound", + "video.pause": "Pause", + "video.play": "Play", + "video.unmute": "Unmute sound" +} diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index ad6f3b712..d8e69fd3c 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -138,6 +138,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index e51163971..37587c14c 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "por mencii la aŭtoron", "keyboard_shortcuts.reply": "por respondi", "keyboard_shortcuts.search": "por fokusigi la serĉilon", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "por komenci tute novan mesaĝon", "keyboard_shortcuts.unfocus": "por malfokusigi la tekstujon aŭ la serĉilon", "keyboard_shortcuts.up": "por iri supren en la listo", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 61ea0588d..41d7db9da 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "para mencionar al autor", "keyboard_shortcuts.reply": "para responder", "keyboard_shortcuts.search": "para poner el foco en la búsqueda", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "para comenzar un nuevo toot", "keyboard_shortcuts.unfocus": "para retirar el foco de la caja de redacción/búsqueda", "keyboard_shortcuts.up": "para ir hacia arriba en la lista", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json new file mode 100644 index 000000000..49cdf5630 --- /dev/null +++ b/app/javascript/mastodon/locales/eu.json @@ -0,0 +1,296 @@ +{ + "account.block": "Blokeatu @{name}", + "account.block_domain": "{domain}(e)ko guztia ezkutatu", + "account.blocked": "Blokeatuta", + "account.direct": "@{name}(e)ri mezu zuzena bidali", + "account.disclaimer_full": "Baliteke beheko informazioak erabiltzailearen profilaren zati bat baino ez erakustea.", + "account.domain_blocked": "Ezkutatutako domeinua", + "account.edit_profile": "Profila aldatu", + "account.follow": "Jarraitu", + "account.followers": "Jarraitzaileak", + "account.follows": "Jarraitzen", + "account.follows_you": "Jarraitzen dizu", + "account.hide_reblogs": "@{name}(e)k sustatutakoak ezkutatu", + "account.media": "Media", + "account.mention": "@{name} aipatu", + "account.moved_to": "{name} hona lekualdatu da:", + "account.mute": "@{name} isilarazi", + "account.mute_notifications": "@{name}(e)ren jakinarazpenak isilarazi", + "account.muted": "Isilarazita", + "account.posts": "Toots", + "account.posts_with_replies": "Toots and replies", + "account.report": "@{name} salatu", + "account.requested": "Onarpenaren zain. Klikatu jarraitzeko eskaera ezeztatzeko", + "account.share": "@{name}(e)ren profila elkarbanatu", + "account.show_reblogs": "@{name}(e)k sustatutakoak erakutsi", + "account.unblock": "@{name} desblokeatu", + "account.unblock_domain": "Berriz erakutsi {domain}", + "account.unfollow": "Jarraitzeari utzi", + "account.unmute": "Unmute @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", + "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", + "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", + "column.blocks": "Blocked users", + "column.community": "Local timeline", + "column.direct": "Direct messages", + "column.domain_blocks": "Hidden domains", + "column.favourites": "Favourites", + "column.follow_requests": "Follow requests", + "column.home": "Home", + "column.lists": "Lists", + "column.mutes": "Muted users", + "column.notifications": "Notifications", + "column.pins": "Pinned toot", + "column.public": "Federated timeline", + "column_back_button.label": "Back", + "column_header.hide_settings": "Hide settings", + "column_header.moveLeft_settings": "Move column to the left", + "column_header.moveRight_settings": "Move column to the right", + "column_header.pin": "Pin", + "column_header.show_settings": "Show settings", + "column_header.unpin": "Unpin", + "column_subheading.navigation": "Navigation", + "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", + "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", + "compose_form.lock_disclaimer.lock": "locked", + "compose_form.placeholder": "What is on your mind?", + "compose_form.publish": "Toot", + "compose_form.publish_loud": "{publish}!", + "compose_form.sensitive.marked": "Media is marked as sensitive", + "compose_form.sensitive.unmarked": "Media is not marked as sensitive", + "compose_form.spoiler.marked": "Text is hidden behind warning", + "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.spoiler_placeholder": "Write your warning here", + "confirmation_modal.cancel": "Cancel", + "confirmations.block.confirm": "Block", + "confirmations.block.message": "Are you sure you want to block {name}?", + "confirmations.delete.confirm": "Delete", + "confirmations.delete.message": "Are you sure you want to delete this status?", + "confirmations.delete_list.confirm": "Delete", + "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.domain_block.confirm": "Hide entire domain", + "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", + "confirmations.mute.confirm": "Mute", + "confirmations.mute.message": "Are you sure you want to mute {name}?", + "confirmations.unfollow.confirm": "Unfollow", + "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", + "embed.instructions": "Embed this status on your website by copying the code below.", + "embed.preview": "Here is what it will look like:", + "emoji_button.activity": "Activity", + "emoji_button.custom": "Custom", + "emoji_button.flags": "Flags", + "emoji_button.food": "Food & Drink", + "emoji_button.label": "Insert emoji", + "emoji_button.nature": "Nature", + "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.objects": "Objects", + "emoji_button.people": "People", + "emoji_button.recent": "Frequently used", + "emoji_button.search": "Search...", + "emoji_button.search_results": "Search results", + "emoji_button.symbols": "Symbols", + "emoji_button.travel": "Travel & Places", + "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.hashtag": "There is nothing in this hashtag yet.", + "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", + "empty_column.home.public_timeline": "the public timeline", + "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", + "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", + "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "follow_request.authorize": "Authorize", + "follow_request.reject": "Reject", + "getting_started.appsshort": "Apps", + "getting_started.faq": "FAQ", + "getting_started.heading": "Getting started", + "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.", + "getting_started.userguide": "User Guide", + "home.column_settings.advanced": "Advanced", + "home.column_settings.basic": "Basic", + "home.column_settings.filter_regex": "Filter out by regular expressions", + "home.column_settings.show_reblogs": "Show boosts", + "home.column_settings.show_replies": "Show replies", + "home.settings": "Column settings", + "keyboard_shortcuts.back": "to navigate back", + "keyboard_shortcuts.boost": "to boost", + "keyboard_shortcuts.column": "to focus a status in one of the columns", + "keyboard_shortcuts.compose": "to focus the compose textarea", + "keyboard_shortcuts.description": "Description", + "keyboard_shortcuts.down": "to move down in the list", + "keyboard_shortcuts.enter": "to open status", + "keyboard_shortcuts.favourite": "to favourite", + "keyboard_shortcuts.heading": "Keyboard Shortcuts", + "keyboard_shortcuts.hotkey": "Hotkey", + "keyboard_shortcuts.legend": "to display this legend", + "keyboard_shortcuts.mention": "to mention author", + "keyboard_shortcuts.reply": "to reply", + "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", + "keyboard_shortcuts.toot": "to start a brand new toot", + "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", + "keyboard_shortcuts.up": "to move up in the list", + "lightbox.close": "Close", + "lightbox.next": "Next", + "lightbox.previous": "Previous", + "lists.account.add": "Add to list", + "lists.account.remove": "Remove from list", + "lists.delete": "Delete list", + "lists.edit": "Edit list", + "lists.new.create": "Add list", + "lists.new.title_placeholder": "New list title", + "lists.search": "Search among people you follow", + "lists.subheading": "Your lists", + "loading_indicator.label": "Loading...", + "media_gallery.toggle_visible": "Toggle visibility", + "missing_indicator.label": "Not found", + "missing_indicator.sublabel": "This resource could not be found", + "mute_modal.hide_notifications": "Hide notifications from this user?", + "navigation_bar.blocks": "Blocked users", + "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.direct": "Direct messages", + "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.edit_profile": "Edit profile", + "navigation_bar.favourites": "Favourites", + "navigation_bar.follow_requests": "Follow requests", + "navigation_bar.info": "Extended information", + "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", + "navigation_bar.lists": "Lists", + "navigation_bar.logout": "Logout", + "navigation_bar.mutes": "Muted users", + "navigation_bar.pins": "Pinned toots", + "navigation_bar.preferences": "Preferences", + "navigation_bar.public_timeline": "Federated timeline", + "notification.favourite": "{name} favourited your status", + "notification.follow": "{name} followed you", + "notification.mention": "{name} mentioned you", + "notification.reblog": "{name} boosted your status", + "notifications.clear": "Clear notifications", + "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", + "notifications.column_settings.alert": "Desktop notifications", + "notifications.column_settings.favourite": "Favourites:", + "notifications.column_settings.follow": "New followers:", + "notifications.column_settings.mention": "Mentions:", + "notifications.column_settings.push": "Push notifications", + "notifications.column_settings.push_meta": "This device", + "notifications.column_settings.reblog": "Boosts:", + "notifications.column_settings.show": "Show in column", + "notifications.column_settings.sound": "Play sound", + "onboarding.done": "Done", + "onboarding.next": "Next", + "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.", + "onboarding.page_four.home": "The home timeline shows posts from people you follow.", + "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.", + "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", + "onboarding.page_one.full_handle": "Your full handle", + "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.", + "onboarding.page_one.welcome": "Welcome to Mastodon!", + "onboarding.page_six.admin": "Your instance's admin is {admin}.", + "onboarding.page_six.almost_done": "Almost done...", + "onboarding.page_six.appetoot": "Bon Appetoot!", + "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.", + "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", + "onboarding.page_six.guidelines": "community guidelines", + "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!", + "onboarding.page_six.various_app": "mobile apps", + "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.", + "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.", + "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.", + "onboarding.skip": "Skip", + "privacy.change": "Adjust status privacy", + "privacy.direct.long": "Post to mentioned users only", + "privacy.direct.short": "Direct", + "privacy.private.long": "Post to followers only", + "privacy.private.short": "Followers-only", + "privacy.public.long": "Post to public timelines", + "privacy.public.short": "Public", + "privacy.unlisted.long": "Do not show in public timelines", + "privacy.unlisted.short": "Unlisted", + "regeneration_indicator.label": "Loading…", + "regeneration_indicator.sublabel": "Your home feed is being prepared!", + "relative_time.days": "{number}d", + "relative_time.hours": "{number}h", + "relative_time.just_now": "now", + "relative_time.minutes": "{number}m", + "relative_time.seconds": "{number}s", + "reply_indicator.cancel": "Cancel", + "report.forward": "Forward to {target}", + "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", + "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.placeholder": "Additional comments", + "report.submit": "Submit", + "report.target": "Report {target}", + "search.placeholder": "Search", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", + "search_results.accounts": "People", + "search_results.hashtags": "Hashtags", + "search_results.statuses": "Toots", + "search_results.total": "{count, number} {count, plural, one {result} other {results}}", + "standalone.public_title": "A look inside...", + "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", + "status.cannot_reblog": "This post cannot be boosted", + "status.delete": "Delete", + "status.direct": "Direct message @{name}", + "status.embed": "Embed", + "status.favourite": "Favourite", + "status.load_more": "Load more", + "status.media_hidden": "Media hidden", + "status.mention": "Mention @{name}", + "status.more": "More", + "status.mute": "Mute @{name}", + "status.mute_conversation": "Mute conversation", + "status.open": "Expand this status", + "status.pin": "Pin on profile", + "status.pinned": "Pinned toot", + "status.reblog": "Boost", + "status.reblog_private": "Boost to original audience", + "status.reblogged_by": "{name} boosted", + "status.reply": "Reply", + "status.replyAll": "Reply to thread", + "status.report": "Report @{name}", + "status.sensitive_toggle": "Click to view", + "status.sensitive_warning": "Sensitive content", + "status.share": "Share", + "status.show_less": "Show less", + "status.show_less_all": "Show less for all", + "status.show_more": "Show more", + "status.show_more_all": "Show more for all", + "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", + "tabs_bar.federated_timeline": "Federated", + "tabs_bar.home": "Home", + "tabs_bar.local_timeline": "Local", + "tabs_bar.notifications": "Notifications", + "tabs_bar.search": "Search", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "upload_area.title": "Drag & drop to upload", + "upload_button.label": "Add media", + "upload_form.description": "Describe for the visually impaired", + "upload_form.focus": "Crop", + "upload_form.undo": "Undo", + "upload_progress.label": "Uploading...", + "video.close": "Close video", + "video.exit_fullscreen": "Exit full screen", + "video.expand": "Expand video", + "video.fullscreen": "Full screen", + "video.hide": "Hide video", + "video.mute": "Mute sound", + "video.pause": "Pause", + "video.play": "Play", + "video.unmute": "Unmute sound" +} diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index cfe93007d..99aba00c3 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "برای نامبردن از نویسنده", "keyboard_shortcuts.reply": "برای پاسخدادن", "keyboard_shortcuts.search": "برای فعالکردن جستجو", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "برای آغاز یک بوق تازه", "keyboard_shortcuts.unfocus": "برای برداشتن توجه از نوشتن/جستجو", "keyboard_shortcuts.up": "برای بالا رفتن در فهرست", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 1677c3c6c..07d4d9aa5 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "mainitse julkaisija", "keyboard_shortcuts.reply": "vastaa", "keyboard_shortcuts.search": "siirry hakukenttään", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "ala kirjoittaa uutta tuuttausta", "keyboard_shortcuts.unfocus": "siirry pois tekstikentästä tai hakukentästä", "keyboard_shortcuts.up": "siirry listassa ylöspäin", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 98c1c43d2..a4af97dda 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -2,7 +2,7 @@ "account.block": "Bloquer @{name}", "account.block_domain": "Tout masquer venant de {domain}", "account.blocked": "Bloqué", - "account.direct": "Direct message @{name}", + "account.direct": "Message direct @{name}", "account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.", "account.domain_blocked": "Domaine caché", "account.edit_profile": "Modifier le profil", @@ -18,7 +18,7 @@ "account.mute_notifications": "Ignorer les notifications de @{name}", "account.muted": "Silencé", "account.posts": "Pouets", - "account.posts_with_replies": "Pouets avec réponses", + "account.posts_with_replies": "Pouets et réponses", "account.report": "Signaler", "account.requested": "En attente d'approbation. Cliquez pour annuler la requête", "account.share": "Partager le profil de @{name}", @@ -29,8 +29,8 @@ "account.unmute": "Ne plus masquer", "account.unmute_notifications": "Réactiver les notifications de @{name}", "account.view_full_profile": "Afficher le profil complet", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "Une erreur non-attendue s'est produite.", + "alert.unexpected.title": "Oups !", "boost_modal.combo": "Vous pouvez appuyer sur {combo} pour pouvoir passer ceci, la prochaine fois", "bundle_column_error.body": "Une erreur s’est produite lors du chargement de ce composant.", "bundle_column_error.retry": "Réessayer", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Réessayer", "column.blocks": "Comptes bloqués", "column.community": "Fil public local", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Messages directs", + "column.domain_blocks": "Domaines cachés", "column.favourites": "Favoris", "column.follow_requests": "Demandes de suivi", "column.home": "Accueil", @@ -59,7 +59,7 @@ "column_header.unpin": "Retirer", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Paramètres", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Ce pouet sera uniquement visible à tous les utilisateurs mentionnés.", "compose_form.hashtag_warning": "Ce pouet ne sera pas listé dans les recherches par hashtag car sa visibilité est réglée sur \"non-listé\". Seuls les pouets avec une visibilité \"publique\" peuvent être recherchés par hashtag.", "compose_form.lock_disclaimer": "Votre compte n’est pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.", "compose_form.lock_disclaimer.lock": "verrouillé", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Symboles", "emoji_button.travel": "Lieux & Voyages", "empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Vous n'avez pas encore de messages directs. Lorsque vous en enverrez ou recevrez un, il s'affichera ici.", "empty_column.hashtag": "Il n’y a encore aucun contenu associé à ce hashtag.", "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres personnes.", "empty_column.home.public_timeline": "le fil public", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "pour mentionner l'auteur", "keyboard_shortcuts.reply": "pour répondre", "keyboard_shortcuts.search": "pour cibler la recherche", + "keyboard_shortcuts.toggle_hidden": "pour afficher/cacher un texte derrière CW", "keyboard_shortcuts.toot": "pour démarrer un tout nouveau pouet", "keyboard_shortcuts.unfocus": "pour recentrer composer textarea/search", "keyboard_shortcuts.up": "pour remonter dans la liste", @@ -156,8 +157,8 @@ "mute_modal.hide_notifications": "Masquer les notifications de cette personne ?", "navigation_bar.blocks": "Comptes bloqués", "navigation_bar.community_timeline": "Fil public local", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "Messages directs", + "navigation_bar.domain_blocks": "Domaines cachés", "navigation_bar.edit_profile": "Modifier le profil", "navigation_bar.favourites": "Favoris", "navigation_bar.follow_requests": "Demandes de suivi", @@ -241,10 +242,10 @@ "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}", "standalone.public_title": "Un aperçu …", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Dé-booster", "status.cannot_reblog": "Cette publication ne peut être boostée", "status.delete": "Effacer", - "status.direct": "Direct message @{name}", + "status.direct": "Message direct @{name}", "status.embed": "Intégrer", "status.favourite": "Ajouter aux favoris", "status.load_more": "Charger plus", @@ -257,7 +258,7 @@ "status.pin": "Épingler sur le profil", "status.pinned": "Pouet épinglé", "status.reblog": "Partager", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Booster vers l'audience originale", "status.reblogged_by": "{name} a partagé :", "status.reply": "Répondre", "status.replyAll": "Répondre au fil", @@ -275,7 +276,7 @@ "tabs_bar.home": "Accueil", "tabs_bar.local_timeline": "Fil public local", "tabs_bar.notifications": "Notifications", - "tabs_bar.search": "Search", + "tabs_bar.search": "Chercher", "ui.beforeunload": "Votre brouillon sera perdu si vous quittez Mastodon.", "upload_area.title": "Glissez et déposez pour envoyer", "upload_button.label": "Joindre un média", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index fca42374d..652ca31d1 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -18,7 +18,7 @@ "account.mute_notifications": "Acalar as notificacións de @{name}", "account.muted": "Muted", "account.posts": "Toots", - "account.posts_with_replies": "Toots with replies", + "account.posts_with_replies": "Toots e respostas", "account.report": "Informar sobre @{name}", "account.requested": "Agardando aceptación. Pulse para cancelar a solicitude de seguimento", "account.share": "Compartir o perfil de @{name}", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "para mencionar o autor", "keyboard_shortcuts.reply": "para responder", "keyboard_shortcuts.search": "para centrar a busca", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "escribir un toot novo", "keyboard_shortcuts.unfocus": "quitar o foco do área de escritura/busca", "keyboard_shortcuts.up": "ir hacia arriba na lista", @@ -242,7 +243,7 @@ "standalone.public_title": "Ollada dentro...", "status.block": "Block @{name}", "status.cancel_reblog_private": "Unboost", - "status.cannot_reblog": "Esta mensaxe non pode ser promocionada", + "status.cannot_reblog": "Esta mensaxe non pode ser promovida", "status.delete": "Eliminar", "status.direct": "Direct message @{name}", "status.embed": "Incrustar", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index e3e87f1d0..0ffbb14f3 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "לאזכר את המחבר(ת)", "keyboard_shortcuts.reply": "לענות", "keyboard_shortcuts.search": "להתמקד בחלון החיפוש", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "להתחיל חיצרוץ חדש", "keyboard_shortcuts.unfocus": "לצאת מתיבת חיבור/חיפוש", "keyboard_shortcuts.up": "לנוע במעלה הרשימה", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index b41c98394..c41cc3ea1 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 956accc67..a0c186184 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "szerző megjelenítése", "keyboard_shortcuts.reply": "válaszolás", "keyboard_shortcuts.search": "kereső kiemelése", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "új tülk megkezdése", "keyboard_shortcuts.unfocus": "tülk szerkesztés/keresés fókuszpontból való kivétele", "keyboard_shortcuts.up": "fennebb helyezés a listában", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index 33e079201..a0442bad4 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "հեղինակին նշելու համար", "keyboard_shortcuts.reply": "պատասխանելու համար", "keyboard_shortcuts.search": "որոնման դաշտին սեւեռվելու համար", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "թարմ թութ սկսելու համար", "keyboard_shortcuts.unfocus": "տեքստի/որոնման տիրույթից ապասեւեռվելու համար", "keyboard_shortcuts.up": "ցանկով վերեւ շարժվելու համար", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 412ffd3a0..2fd922544 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "untuk fokus mencari", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 9730bf934..ed45ee11e 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 5146d7ca2..a7ca62015 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -1,7 +1,7 @@ { "account.block": "Blocca @{name}", "account.block_domain": "Hide everything from {domain}", - "account.blocked": "Blocked", + "account.blocked": "Bloccato", "account.direct": "Direct Message @{name}", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.domain_blocked": "Domain hidden", @@ -17,8 +17,8 @@ "account.mute": "Silenzia @{name}", "account.mute_notifications": "Mute notifications from @{name}", "account.muted": "Muted", - "account.posts": "Posts", - "account.posts_with_replies": "Toots with replies", + "account.posts": "Toot", + "account.posts_with_replies": "Toot con risposte", "account.report": "Segnala @{name}", "account.requested": "In attesa di approvazione", "account.share": "Share @{name}'s profile", @@ -105,7 +105,7 @@ "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.", "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.", "empty_column.home.public_timeline": "la timeline pubblica", - "empty_column.list": "There is nothing in this list yet.", + "empty_column.list": "Non c'è niente in questo elenco ancora. Quando i membri di questo elenco postano nuovi stati, questi appariranno qui.", "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.", "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio.", "follow_request.authorize": "Autorizza", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index abd18742a..dbb4562de 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -29,8 +29,8 @@ "account.unmute": "@{name}さんのミュートを解除", "account.unmute_notifications": "@{name}さんからの通知を受け取るようにする", "account.view_full_profile": "全ての情報を見る", - "alert.unexpected.message": "不明なエラーが発生しました", - "alert.unexpected.title": "エラー", + "alert.unexpected.message": "不明なエラーが発生しました。", + "alert.unexpected.title": "エラー!", "boost_modal.combo": "次からは{combo}を押せばスキップできます", "bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。", "bundle_column_error.retry": "再試行", @@ -104,7 +104,7 @@ "emoji_button.symbols": "記号", "emoji_button.travel": "旅行と場所", "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!", - "empty_column.direct": "あなたはまだダイレクトメッセージを受け取っていません。あなたが送ったり受け取ったりすると、ここに表示されます。", + "empty_column.direct": "ダイレクトメッセージはまだありません。ダイレクトメッセージをやりとりすると、ここに表示されます。", "empty_column.hashtag": "このハッシュタグはまだ使われていません。", "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。", "empty_column.home.public_timeline": "連合タイムライン", @@ -138,6 +138,7 @@ "keyboard_shortcuts.mention": "メンション", "keyboard_shortcuts.reply": "返信", "keyboard_shortcuts.search": "検索欄に移動", + "keyboard_shortcuts.toggle_hidden": "CWで隠れた文を見る/隠す", "keyboard_shortcuts.toot": "新規トゥート", "keyboard_shortcuts.unfocus": "トゥート入力欄・検索欄から離れる", "keyboard_shortcuts.up": "カラム内一つ上に移動", @@ -159,7 +160,7 @@ "mute_modal.hide_notifications": "このユーザーからの通知を隠しますか?", "navigation_bar.blocks": "ブロックしたユーザー", "navigation_bar.community_timeline": "ローカルタイムライン", - "navigation_bar.direct": "Direct messages", + "navigation_bar.direct": "ダイレクトメッセージ", "navigation_bar.domain_blocks": "非表示にしたドメイン", "navigation_bar.edit_profile": "プロフィールを編集", "navigation_bar.favourites": "お気に入り", @@ -245,7 +246,7 @@ "search_results.total": "{count, number}件の結果", "standalone.public_title": "今こんな話をしています...", "status.block": "@{name}さんをブロック", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "ブースト解除", "status.cannot_reblog": "この投稿はブーストできません", "status.delete": "削除", "status.direct": "@{name}さんにダイレクトメッセージ", @@ -261,7 +262,7 @@ "status.pin": "プロフィールに固定表示", "status.pinned": "固定されたトゥート", "status.reblog": "ブースト", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "ブースト", "status.reblogged_by": "{name}さんがブースト", "status.reply": "返信", "status.replyAll": "全員に返信", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 92367dc95..2a2734673 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -2,7 +2,7 @@ "account.block": "@{name}을 차단", "account.block_domain": "{domain} 전체를 숨김", "account.blocked": "차단 됨", - "account.direct": "Direct message @{name}", + "account.direct": "@{name}으로부터의 다이렉트 메시지", "account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.", "account.domain_blocked": "도메인 숨겨짐", "account.edit_profile": "프로필 편집", @@ -12,7 +12,7 @@ "account.follows_you": "날 팔로우합니다", "account.hide_reblogs": "@{name}의 부스트를 숨기기", "account.media": "미디어", - "account.mention": "답장", + "account.mention": "@{name}에게 글쓰기", "account.moved_to": "{name}는 계정을 이동했습니다:", "account.mute": "@{name} 뮤트", "account.mute_notifications": "@{name}의 알림을 뮤트", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "멘션", "keyboard_shortcuts.reply": "답장", "keyboard_shortcuts.search": "검색창에 포커스", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "새 툿 작성", "keyboard_shortcuts.unfocus": "작성창에서 포커스 해제", "keyboard_shortcuts.up": "리스트에서 위로 이동", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index c18ddbd01..adc1d19a7 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -18,7 +18,7 @@ "account.mute_notifications": "Negeer meldingen van @{name}", "account.muted": "Genegeerd", "account.posts": "Toots", - "account.posts_with_replies": "Toots met reacties", + "account.posts_with_replies": "Toots en reacties", "account.report": "Rapporteer @{name}", "account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren", "account.share": "Profiel van @{name} delen", @@ -29,8 +29,8 @@ "account.unmute": "@{name} niet meer negeren", "account.unmute_notifications": "@{name} meldingen niet meer negeren", "account.view_full_profile": "Volledig profiel tonen", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "Er deed zich een onverwachte fout voor", + "alert.unexpected.title": "Oeps!", "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan", "bundle_column_error.body": "Tijdens het laden van dit onderdeel is er iets fout gegaan.", "bundle_column_error.retry": "Opnieuw proberen", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Opnieuw proberen", "column.blocks": "Geblokkeerde gebruikers", "column.community": "Lokale tijdlijn", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Directe berichten", + "column.domain_blocks": "Verborgen domeinen", "column.favourites": "Favorieten", "column.follow_requests": "Volgverzoeken", "column.home": "Start", @@ -59,7 +59,7 @@ "column_header.unpin": "Losmaken", "column_subheading.navigation": "Navigatie", "column_subheading.settings": "Instellingen", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Deze toot zal alleen zichtbaar zijn voor alle vermelde gebruikers.", "compose_form.hashtag_warning": "Deze toot valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare toots kunnen via hashtags gevonden worden.", "compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en toots zien die je alleen aan volgers hebt gericht.", "compose_form.lock_disclaimer.lock": "besloten", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Symbolen", "emoji_button.travel": "Reizen en plekken", "empty_column.community": "De lokale tijdlijn is nog leeg. Toot iets in het openbaar om de bal aan het rollen te krijgen!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Je hebt nog geen directe berichten. Wanneer je er een verzend of ontvangt, zijn deze hier te zien.", "empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.", "empty_column.home": "Jij volgt nog niemand. Bezoek {public} of gebruik het zoekvenster om andere mensen te ontmoeten.", "empty_column.home.public_timeline": "de globale tijdlijn", @@ -127,7 +127,7 @@ "keyboard_shortcuts.compose": "om het tekstvak voor toots te focussen", "keyboard_shortcuts.description": "Omschrijving", "keyboard_shortcuts.down": "om naar beneden door de lijst te bewegen", - "keyboard_shortcuts.enter": "to open status", + "keyboard_shortcuts.enter": "om toot volledig te tonen", "keyboard_shortcuts.favourite": "om als favoriet te markeren", "keyboard_shortcuts.heading": "Sneltoetsen", "keyboard_shortcuts.hotkey": "Sneltoets", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "om de auteur te vermelden", "keyboard_shortcuts.reply": "om te reageren", "keyboard_shortcuts.search": "om het zoekvak te focussen", + "keyboard_shortcuts.toggle_hidden": "om tekst achter een waarschuwing (CW) te tonen/verbergen", "keyboard_shortcuts.toot": "om een nieuwe toot te starten", "keyboard_shortcuts.unfocus": "om het tekst- en zoekvak te ontfocussen", "keyboard_shortcuts.up": "om omhoog te bewegen in de lijst", @@ -156,8 +157,8 @@ "mute_modal.hide_notifications": "Verberg meldingen van deze persoon?", "navigation_bar.blocks": "Geblokkeerde gebruikers", "navigation_bar.community_timeline": "Lokale tijdlijn", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "Directe berichten", + "navigation_bar.domain_blocks": "Verborgen domeinen", "navigation_bar.edit_profile": "Profiel bewerken", "navigation_bar.favourites": "Favorieten", "navigation_bar.follow_requests": "Volgverzoeken", @@ -241,10 +242,10 @@ "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}", "standalone.public_title": "Een kijkje binnenin...", "status.block": "Blokkeer @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Niet meer boosten", "status.cannot_reblog": "Deze toot kan niet geboost worden", "status.delete": "Verwijderen", - "status.direct": "Direct message @{name}", + "status.direct": "Directe toot @{name}", "status.embed": "Embed", "status.favourite": "Favoriet", "status.load_more": "Meer laden", @@ -257,7 +258,7 @@ "status.pin": "Aan profielpagina vastmaken", "status.pinned": "Vastgemaakte toot", "status.reblog": "Boost", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Boost naar oorspronkelijke ontvangers", "status.reblogged_by": "{name} boostte", "status.reply": "Reageren", "status.replyAll": "Reageer op iedereen", @@ -275,7 +276,7 @@ "tabs_bar.home": "Start", "tabs_bar.local_timeline": "Lokaal", "tabs_bar.notifications": "Meldingen", - "tabs_bar.search": "Search", + "tabs_bar.search": "Zoeken", "ui.beforeunload": "Je concept zal verloren gaan als je Mastodon verlaat.", "upload_area.title": "Hierin slepen om te uploaden", "upload_button.label": "Media toevoegen", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 282a72acb..0ee6d0722 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "å nevne forfatter", "keyboard_shortcuts.reply": "for å svare", "keyboard_shortcuts.search": "å fokusere søk", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "å starte en helt ny tut", "keyboard_shortcuts.unfocus": "å ufokusere komponerings-/søkefeltet", "keyboard_shortcuts.up": "å flytte opp i listen", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 7170aefb8..d4836e9fe 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -18,7 +18,7 @@ "account.mute_notifications": "Rescondre las notificacions de @{name}", "account.muted": "Mes en silenci", "account.posts": "Tuts", - "account.posts_with_replies": "Tuts amb responsas", + "account.posts_with_replies": "Tuts e responsas", "account.report": "Senhalar @{name}", "account.requested": "Invitacion mandada. Clicatz per anullar", "account.share": "Partejar lo perfil a @{name}", @@ -29,8 +29,8 @@ "account.unmute": "Quitar de rescondre @{name}", "account.unmute_notifications": "Mostrar las notificacions de @{name}", "account.view_full_profile": "Veire lo perfil complèt", - "alert.unexpected.message": "An unexpected error occurred.", - "alert.unexpected.title": "Oops!", + "alert.unexpected.message": "Una error s’es producha.", + "alert.unexpected.title": "Ops !", "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven", "bundle_column_error.body": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.", "bundle_column_error.retry": "Tornar ensajar", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Tornar ensajar", "column.blocks": "Personas blocadas", "column.community": "Flux public local", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Messatges dirèctes", + "column.domain_blocks": "Domenis blocats", "column.favourites": "Favorits", "column.follow_requests": "Demandas d’abonament", "column.home": "Acuèlh", @@ -59,7 +59,7 @@ "column_header.unpin": "Despenjar", "column_subheading.navigation": "Navigacion", "column_subheading.settings": "Paramètres", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Aqueste tut serà pas que visibile pel monde mencionat.", "compose_form.hashtag_warning": "Aqueste tut serà pas ligat a cap etiqueta estant qu’es pas listat. Òm pas cercar que los tuts publics per etiqueta.", "compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als seguidors.", "compose_form.lock_disclaimer.lock": "clavat", @@ -73,13 +73,13 @@ "compose_form.spoiler_placeholder": "Escrivètz l’avertiment aquí", "confirmation_modal.cancel": "Anullar", "confirmations.block.confirm": "Blocar", - "confirmations.block.message": "Sètz segur de voler blocar {name} ?", + "confirmations.block.message": "Volètz vertadièrament blocar {name} ?", "confirmations.delete.confirm": "Escafar", - "confirmations.delete.message": "Sètz segur de voler escafar l’estatut ?", + "confirmations.delete.message": "Volètz vertadièrament escafar l’estatut ?", "confirmations.delete_list.confirm": "Suprimir", - "confirmations.delete_list.message": "Sètz segur de voler suprimir aquesta lista per totjorn ?", + "confirmations.delete_list.message": "Volètz vertadièrament suprimir aquesta lista per totjorn ?", "confirmations.domain_block.confirm": "Amagar tot lo domeni", - "confirmations.domain_block.message": "Sètz segur segur de voler blocar complètament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.", + "confirmations.domain_block.message": "Volètz vertadièrament blocar complètament {domain} ? De còps cal pas que blocar o rescondre unas personas solament.", "confirmations.mute.confirm": "Rescondre", "confirmations.mute.message": "Sètz segur de voler rescondre {name} ?", "confirmations.unfollow.confirm": "Quitar de sègre", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Simbòls", "emoji_button.travel": "Viatges & lòcs", "empty_column.community": "Lo flux public local es void. Escrivètz quicòm per lo garnir !", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Avètz pas encara de messatges. Quand ne mandatz un o que ne recebètz un, serà mostrat aquí.", "empty_column.hashtag": "I a pas encara de contengut ligat a aquesta etiqueta.", "empty_column.home": "Vòstre flux d’acuèlh es void. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.", "empty_column.home.public_timeline": "lo flux public", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "mencionar l’autor", "keyboard_shortcuts.reply": "respondre", "keyboard_shortcuts.search": "anar a la recèrca", + "keyboard_shortcuts.toggle_hidden": "mostrar/amagar lo tèxte dels avertiments", "keyboard_shortcuts.toot": "començar un estatut tot novèl", "keyboard_shortcuts.unfocus": "quitar lo camp tèxte/de recèrca", "keyboard_shortcuts.up": "far montar dins la lista", @@ -156,7 +157,7 @@ "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?", "navigation_bar.blocks": "Personas blocadas", "navigation_bar.community_timeline": "Flux public local", - "navigation_bar.direct": "Direct messages", + "navigation_bar.direct": "Messatges dirèctes", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Modificar lo perfil", "navigation_bar.favourites": "Favorits", @@ -216,7 +217,7 @@ "privacy.unlisted.short": "Pas-listat", "regeneration_indicator.label": "Cargament…", "regeneration_indicator.sublabel": "Sèm a preparar vòstre flux d’acuèlh !", - "relative_time.days": "fa {number} d", + "relative_time.days": "fa {number}d", "relative_time.hours": "fa {number}h", "relative_time.just_now": "ara", "relative_time.minutes": "fa {number} min", @@ -235,16 +236,16 @@ "search_popout.tips.status": "estatut", "search_popout.tips.text": "Lo tèxt brut tòrna escais, noms d’utilizaire e etiquetas correspondents", "search_popout.tips.user": "utilizaire", - "search_results.accounts": "Monde", + "search_results.accounts": "Gents", "search_results.hashtags": "Etiquetas", "search_results.statuses": "Tuts", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}", "standalone.public_title": "Una ulhada dedins…", "status.block": "Blocar @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Quitar de partejar", "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat", "status.delete": "Escafar", - "status.direct": "Direct message @{name}", + "status.direct": "Messatge per @{name}", "status.embed": "Embarcar", "status.favourite": "Apondre als favorits", "status.load_more": "Cargar mai", @@ -257,7 +258,7 @@ "status.pin": "Penjar al perfil", "status.pinned": "Tut penjat", "status.reblog": "Partejar", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Partejar al l’audiéncia d’origina", "status.reblogged_by": "{name} a partejat", "status.reply": "Respondre", "status.replyAll": "Respondre a la conversacion", @@ -275,7 +276,7 @@ "tabs_bar.home": "Acuèlh", "tabs_bar.local_timeline": "Flux public local", "tabs_bar.notifications": "Notificacions", - "tabs_bar.search": "Search", + "tabs_bar.search": "Recèrcas", "ui.beforeunload": "Vòstre brolhon serà perdut se quitatz Mastodon.", "upload_area.title": "Lisatz e depausatz per mandar", "upload_button.label": "Ajustar un mèdia", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 08aea797d..6d6db7c82 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -106,8 +106,8 @@ "empty_column.community": "Lokalna oś czasu jest pusta. Napisz coś publicznie, aby zagaić!", "empty_column.direct": "Nie masz żadnych wiadomości bezpośrednich. Jeżeli wyślesz lub otrzymasz jakąś, będzie tu widoczna.", "empty_column.hashtag": "Nie ma wpisów oznaczonych tym hashtagiem. Możesz napisać pierwszy!", - "empty_column.home": "Nie śledzisz nikogo. Odwiedź publiczną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.", - "empty_column.home.public_timeline": "publiczna oś czasu", + "empty_column.home": "Nie śledzisz nikogo. Odwiedź globalną oś czasu lub użyj wyszukiwarki, aby znaleźć interesujące Cię profile.", + "empty_column.home.public_timeline": "globalna oś czasu", "empty_column.list": "Nie ma nic na tej liście. Kiedy członkowie listy dodadzą nowe wpisy, pojawia się one tutaj.", "empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.", "empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić", @@ -173,7 +173,7 @@ "navigation_bar.mutes": "Wyciszeni użytkownicy", "navigation_bar.pins": "Przypięte wpisy", "navigation_bar.preferences": "Preferencje", - "navigation_bar.public_timeline": "Oś czasu federacji", + "navigation_bar.public_timeline": "Globalna oś czasu", "notification.favourite": "{name} dodał Twój wpis do ulubionych", "notification.follow": "{name} zaczął Cię śledzić", "notification.mention": "{name} wspomniał o tobie", @@ -191,7 +191,7 @@ "notifications.column_settings.sound": "Odtwarzaj dźwięk", "onboarding.done": "Gotowe", "onboarding.next": "Dalej", - "onboarding.page_five.public_timelines": "Lokalna oś czasu zawiera wszystkie publiczne wpisy z {domain}. Federalna oś czasu wyświetla publiczne wpisy śledzonych przez członków {domain}. Są to publiczne osie czasu – najlepszy sposób na poznanie nowych osób.", + "onboarding.page_five.public_timelines": "Lokalna oś czasu zawiera wszystkie publiczne wpisy z {domain}. Globalna oś czasu wyświetla publiczne wpisy śledzonych przez członków {domain}. Są to publiczne osie czasu – najlepszy sposób na poznanie nowych osób.", "onboarding.page_four.home": "Główna oś czasu wyświetla publiczne wpisy.", "onboarding.page_four.notifications": "Kolumna powiadomień wyświetla, gdy ktoś dokonuje interakcji z tobą.", "onboarding.page_one.federation": "Mastodon jest siecią niezależnych serwerów połączonych w jeden portal społecznościowy. Nazywamy te serwery instancjami.", @@ -251,7 +251,7 @@ "status.delete": "Usuń", "status.direct": "Wyślij wiadomość bezpośrednią do @{name}", "status.embed": "Osadź", - "status.favourite": "Ulubione", + "status.favourite": "Dodaj do ulubionych", "status.load_more": "Załaduj więcej", "status.media_hidden": "Zawartość multimedialna ukryta", "status.mention": "Wspomnij o @{name}", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index c604476c7..7f8690f91 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -29,7 +29,7 @@ "account.unmute": "Não silenciar @{name}", "account.unmute_notifications": "Retirar silêncio das notificações vindas de @{name}", "account.view_full_profile": "Ver perfil completo", - "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.message": "Um erro inesperado ocorreu.", "alert.unexpected.title": "Oops!", "boost_modal.combo": "Você pode pressionar {combo} para ignorar este diálogo na próxima vez", "bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Tente novamente", "column.blocks": "Usuários bloqueados", "column.community": "Local", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Mensagens diretas", + "column.domain_blocks": "Domínios escondidos", "column.favourites": "Favoritos", "column.follow_requests": "Seguidores pendentes", "column.home": "Página inicial", @@ -59,7 +59,7 @@ "column_header.unpin": "Desafixar", "column_subheading.navigation": "Navegação", "column_subheading.settings": "Configurações", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Este toot só será visível a todos os usuários mencionados.", "compose_form.hashtag_warning": "Esse toot não será listado em nenhuma hashtag por ser não listado. Somente toots públicos podem ser pesquisados por hashtag.", "compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar postagens direcionadas a apenas seguidores.", "compose_form.lock_disclaimer.lock": "trancada", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Símbolos", "emoji_button.travel": "Viagens & Lugares", "empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Você não tem nenhuma mensagem direta ainda. Quando você enviar ou receber uma, as mensagens aparecerão por aqui.", "empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag.", "empty_column.home": "Você ainda não segue usuário algum. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.", "empty_column.home.public_timeline": "global", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "para mencionar o autor", "keyboard_shortcuts.reply": "para responder", "keyboard_shortcuts.search": "para focar a pesquisa", + "keyboard_shortcuts.toggle_hidden": "mostrar/esconder o texto com aviso de conteúdo", "keyboard_shortcuts.toot": "para compor um novo toot", "keyboard_shortcuts.unfocus": "para remover o foco da área de composição/pesquisa", "keyboard_shortcuts.up": "para mover para cima na lista", @@ -156,8 +157,8 @@ "mute_modal.hide_notifications": "Esconder notificações deste usuário?", "navigation_bar.blocks": "Usuários bloqueados", "navigation_bar.community_timeline": "Local", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "Mensagens diretas", + "navigation_bar.domain_blocks": "Domínios escondidos", "navigation_bar.edit_profile": "Editar perfil", "navigation_bar.favourites": "Favoritos", "navigation_bar.follow_requests": "Seguidores pendentes", @@ -241,10 +242,10 @@ "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "standalone.public_title": "Dê uma espiada...", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Retirar o compartilhamento", "status.cannot_reblog": "Esta postagem não pode ser compartilhada", "status.delete": "Excluir", - "status.direct": "Direct message @{name}", + "status.direct": "Enviar mensagem direta à @{name}", "status.embed": "Incorporar", "status.favourite": "Adicionar aos favoritos", "status.load_more": "Carregar mais", @@ -257,7 +258,7 @@ "status.pin": "Fixar no perfil", "status.pinned": "Toot fixado", "status.reblog": "Compartilhar", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Compartilhar com a audiência original", "status.reblogged_by": "{name} compartilhou", "status.reply": "Responder", "status.replyAll": "Responder à sequência", @@ -275,7 +276,7 @@ "tabs_bar.home": "Página inicial", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", - "tabs_bar.search": "Search", + "tabs_bar.search": "Buscar", "ui.beforeunload": "Seu rascunho será perdido se você sair do Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar mídia", diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json index 826785aad..ce816dc41 100644 --- a/app/javascript/mastodon/locales/pt.json +++ b/app/javascript/mastodon/locales/pt.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "para mencionar o autor", "keyboard_shortcuts.reply": "para responder", "keyboard_shortcuts.search": "para focar na pesquisa", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "para compor um novo post", "keyboard_shortcuts.unfocus": "para remover o foco da área de publicação/pesquisa", "keyboard_shortcuts.up": "para mover para cima na lista", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index bb3cc1794..8eeebaf73 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "упомянуть автора поста", "keyboard_shortcuts.reply": "ответить", "keyboard_shortcuts.search": "перейти к поиску", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "начать писать новый пост", "keyboard_shortcuts.unfocus": "убрать фокус с поля ввода/поиска", "keyboard_shortcuts.up": "вверх по списку", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 58274fd2d..e5e826c96 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -2,7 +2,7 @@ "account.block": "Blokovať @{name}", "account.block_domain": "Ukryť všetko z {domain}", "account.blocked": "Blokovaný/á", - "account.direct": "Direct message @{name}", + "account.direct": "Súkromná správa pre @{name}", "account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.", "account.domain_blocked": "Doména ukrytá", "account.edit_profile": "Upraviť profil", @@ -29,7 +29,7 @@ "account.unmute": "Prestať ignorovať @{name}", "account.unmute_notifications": "Odtĺmiť notifikácie od @{name}", "account.view_full_profile": "Pozri celý profil", - "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.message": "Vyskytla sa neočakávaná chyba.", "alert.unexpected.title": "Oops!", "boost_modal.combo": "Nabudúce môžete kliknúť {combo} aby ste preskočili", "bundle_column_error.body": "Nastala chyba pri načítaní tohto komponentu.", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Skúsiť znova", "column.blocks": "Blokovaní užívatelia", "column.community": "Lokálna časová os", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Súkromné správy", + "column.domain_blocks": "Skryté domény", "column.favourites": "Obľúbené", "column.follow_requests": "Žiadosti o sledovanie", "column.home": "Domov", @@ -59,7 +59,7 @@ "column_header.unpin": "Odopnúť", "column_subheading.navigation": "Navigácia", "column_subheading.settings": "Nastavenia", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Tento príspevok bude videný výhradne iba spomenutými užívateľmi.", "compose_form.hashtag_warning": "Tento toot nebude zobrazený pod žiadným haštagom lebo nieje listovaný. Iba verejné tooty môžu byť nájdené podľa haštagu.", "compose_form.lock_disclaimer": "Váš účet nie je zamknutý. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.", "compose_form.lock_disclaimer.lock": "zamknutý", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Symboly", "emoji_button.travel": "Cestovanie a miesta", "empty_column.community": "Lokálna časová os je prázdna. Napíšte niečo, aby sa to tu začalo hýbať!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Ešte nemáš žiadne súkromné správy. Keď nejakú pošleš, alebo dostaneš, ukáže sa tu.", "empty_column.hashtag": "Pod týmto hashtagom sa ešte nič nenachádza.", "empty_column.home": "Vaša lokálna osa je zatiaľ prázdna! Pre začiatok pozrite {public} alebo použite vyhľadávanie a nájdite tak ostatných používateľov.", "empty_column.home.public_timeline": "verejná časová os", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "spomenúť autora", "keyboard_shortcuts.reply": "odpovedať", "keyboard_shortcuts.search": "zamerať sa na vyhľadávanie", + "keyboard_shortcuts.toggle_hidden": "ukáž/skry text za CW", "keyboard_shortcuts.toot": "začať úplne novú hlášku", "keyboard_shortcuts.unfocus": "nesústrediť sa na písaciu plochu, alebo hľadanie", "keyboard_shortcuts.up": "posunúť sa vyššie v zozname", @@ -156,8 +157,8 @@ "mute_modal.hide_notifications": "Skryť notifikácie od tohoto užívateľa?", "navigation_bar.blocks": "Blokovaní užívatelia", "navigation_bar.community_timeline": "Lokálna časová os", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "Súkromné správy", + "navigation_bar.domain_blocks": "Skryté domény", "navigation_bar.edit_profile": "Upraviť profil", "navigation_bar.favourites": "Obľúbené", "navigation_bar.follow_requests": "Žiadosti o sledovanie", @@ -244,7 +245,7 @@ "status.cancel_reblog_private": "Unboost", "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý", "status.delete": "Zmazať", - "status.direct": "Direct message @{name}", + "status.direct": "Súkromná správa @{name}", "status.embed": "Vložiť", "status.favourite": "Páči sa mi", "status.load_more": "Zobraz viac", @@ -275,7 +276,7 @@ "tabs_bar.home": "Domov", "tabs_bar.local_timeline": "Lokálna", "tabs_bar.notifications": "Notifikácie", - "tabs_bar.search": "Search", + "tabs_bar.search": "Hľadaj", "ui.beforeunload": "Čo máte rozpísané sa stratí, ak opustíte Mastodon.", "upload_area.title": "Ťahaj a pusti pre nahratie", "upload_button.label": "Pridať médiá", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index e4d07edd1..b1ea0d179 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "da pomenete autora", "keyboard_shortcuts.reply": "da odgovorite", "keyboard_shortcuts.search": "da se prebacite na pretragu", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "da započnete skroz novi tut", "keyboard_shortcuts.unfocus": "da ne budete više na pretrazi/pravljenju novog tuta", "keyboard_shortcuts.up": "da se pomerite na gore u listi", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 60c781e9d..aa978675f 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "да поменете аутора", "keyboard_shortcuts.reply": "да одговорите", "keyboard_shortcuts.search": "да се пребаците на претрагу", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "да започнете скроз нови тут", "keyboard_shortcuts.unfocus": "да не будете више на претрази/прављењу новог тута", "keyboard_shortcuts.up": "да се померите на горе у листи", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 8fa6992f1..4efe88a7e 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -18,7 +18,7 @@ "account.mute_notifications": "Stäng av notifieringar från @{name}", "account.muted": "Nertystad", "account.posts": "Inlägg", - "account.posts_with_replies": "Toots med svar", + "account.posts_with_replies": "Toots och svar", "account.report": "Rapportera @{name}", "account.requested": "Inväntar godkännande. Klicka för att avbryta följförfrågan", "account.share": "Dela @{name}'s profil", @@ -29,7 +29,7 @@ "account.unmute": "Ta bort tystad @{name}", "account.unmute_notifications": "Återaktivera notifikationer från @{name}", "account.view_full_profile": "Visa hela profilen", - "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.message": "Ett oväntat fel uppstod.", "alert.unexpected.title": "Oops!", "boost_modal.combo": "Du kan trycka {combo} för att slippa denna nästa gång", "bundle_column_error.body": "Något gick fel när du laddade denna komponent.", @@ -40,8 +40,8 @@ "bundle_modal_error.retry": "Försök igen", "column.blocks": "Blockerade användare", "column.community": "Lokal tidslinje", - "column.direct": "Direct messages", - "column.domain_blocks": "Hidden domains", + "column.direct": "Direktmeddelande", + "column.domain_blocks": "Dolda domäner", "column.favourites": "Favoriter", "column.follow_requests": "Följ förfrågningar", "column.home": "Hem", @@ -59,7 +59,7 @@ "column_header.unpin": "Ångra fäst", "column_subheading.navigation": "Navigation", "column_subheading.settings": "Inställningar", - "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.direct_message_warning": "Denna toot kommer endast vara synlig för nämnda användare.", "compose_form.hashtag_warning": "Denna toot kommer inte att listas under någon hashtag eftersom den är onoterad. Endast offentliga toots kan sökas med hashtag.", "compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vemsomhelst kan följa dig och även se dina inlägg skrivna för endast dina följare.", "compose_form.lock_disclaimer.lock": "låst", @@ -101,7 +101,7 @@ "emoji_button.symbols": "Symboler", "emoji_button.travel": "Resor & Platser", "empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att få bollen att rulla!", - "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.direct": "Du har inga direktmeddelanden än. När du skickar eller tar emot kommer den att dyka upp här.", "empty_column.hashtag": "Det finns inget i denna hashtag ännu.", "empty_column.home": "Din hemma-tidslinje är tom! Besök {public} eller använd sökning för att komma igång och träffa andra användare.", "empty_column.home.public_timeline": "den publika tidslinjen", @@ -113,7 +113,7 @@ "getting_started.appsshort": "Appar", "getting_started.faq": "FAQ", "getting_started.heading": "Kom igång", - "getting_started.open_source_notice": "Mastodon är programvara med öppen källkod. Du kan bidra eller rapportera problem på GitHub på {github}.", + "getting_started.open_source_notice": "Mastodon är programvara med öppen källkod. Du kan bidra eller rapportera problem via GitHub på {github}.", "getting_started.userguide": "Användarguide", "home.column_settings.advanced": "Avancerad", "home.column_settings.basic": "Grundläggande", @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "att nämna författaren", "keyboard_shortcuts.reply": "att svara", "keyboard_shortcuts.search": "att fokusera sökfältet", + "keyboard_shortcuts.toggle_hidden": "att visa/gömma text bakom CW", "keyboard_shortcuts.toot": "att börja en helt ny toot", "keyboard_shortcuts.unfocus": "att avfokusera komponera text fält / sökfält", "keyboard_shortcuts.up": "att flytta upp i listan", @@ -156,8 +157,8 @@ "mute_modal.hide_notifications": "Dölj notifikationer från denna användare?", "navigation_bar.blocks": "Blockerade användare", "navigation_bar.community_timeline": "Lokal tidslinje", - "navigation_bar.direct": "Direct messages", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.direct": "Direktmeddelanden", + "navigation_bar.domain_blocks": "Dolda domäner", "navigation_bar.edit_profile": "Redigera profil", "navigation_bar.favourites": "Favoriter", "navigation_bar.follow_requests": "Följförfrågningar", @@ -205,7 +206,7 @@ "onboarding.page_three.search": "Använd sökfältet för att hitta personer och titta på hashtags, till exempel {illustration} och {introductions}. För att leta efter en person som inte befinner sig i detta fall använd deras fulla handhavande.", "onboarding.page_two.compose": "Skriv inlägg från skrivkolumnen. Du kan ladda upp bilder, ändra integritetsinställningar och lägga till varningar med ikonerna nedan.", "onboarding.skip": "Hoppa över", - "privacy.change": "Justera status sekretess", + "privacy.change": "Justera sekretess", "privacy.direct.long": "Skicka endast till nämnda användare", "privacy.direct.short": "Direkt", "privacy.private.long": "Skicka endast till följare", @@ -241,10 +242,10 @@ "search_results.total": "{count, number} {count, plural, ett {result} andra {results}}", "standalone.public_title": "En titt inuti...", "status.block": "Block @{name}", - "status.cancel_reblog_private": "Unboost", + "status.cancel_reblog_private": "Ta bort knuff", "status.cannot_reblog": "Detta inlägg kan inte knuffas", "status.delete": "Ta bort", - "status.direct": "Direct message @{name}", + "status.direct": "Direktmeddela @{name}", "status.embed": "Bädda in", "status.favourite": "Favorit", "status.load_more": "Ladda fler", @@ -257,7 +258,7 @@ "status.pin": "Fäst i profil", "status.pinned": "Fäst toot", "status.reblog": "Knuff", - "status.reblog_private": "Boost to original audience", + "status.reblog_private": "Knuffa till de ursprungliga åhörarna", "status.reblogged_by": "{name} knuffade", "status.reply": "Svara", "status.replyAll": "Svara på tråden", @@ -275,7 +276,7 @@ "tabs_bar.home": "Hem", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Meddelanden", - "tabs_bar.search": "Search", + "tabs_bar.search": "Sök", "ui.beforeunload": "Ditt utkast kommer att förloras om du lämnar Mastodon.", "upload_area.title": "Dra & släpp för att ladda upp", "upload_button.label": "Lägg till media", diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json new file mode 100644 index 000000000..a56720fee --- /dev/null +++ b/app/javascript/mastodon/locales/te.json @@ -0,0 +1,296 @@ +{ + "account.block": "Block @{name}", + "account.block_domain": "Hide everything from {domain}", + "account.blocked": "Blocked", + "account.direct": "Direct message @{name}", + "account.disclaimer_full": "Information below may reflect the user's profile incompletely.", + "account.domain_blocked": "Domain hidden", + "account.edit_profile": "Edit profile", + "account.follow": "Follow", + "account.followers": "Followers", + "account.follows": "Follows", + "account.follows_you": "Follows you", + "account.hide_reblogs": "Hide boosts from @{name}", + "account.media": "Media", + "account.mention": "Mention @{name}", + "account.moved_to": "{name} has moved to:", + "account.mute": "Mute @{name}", + "account.mute_notifications": "Mute notifications from @{name}", + "account.muted": "Muted", + "account.posts": "Toots", + "account.posts_with_replies": "Toots and replies", + "account.report": "Report @{name}", + "account.requested": "Awaiting approval. Click to cancel follow request", + "account.share": "Share @{name}'s profile", + "account.show_reblogs": "Show boosts from @{name}", + "account.unblock": "Unblock @{name}", + "account.unblock_domain": "Unhide {domain}", + "account.unfollow": "Unfollow", + "account.unmute": "Unmute @{name}", + "account.unmute_notifications": "Unmute notifications from @{name}", + "account.view_full_profile": "View full profile", + "alert.unexpected.message": "An unexpected error occurred.", + "alert.unexpected.title": "Oops!", + "boost_modal.combo": "You can press {combo} to skip this next time", + "bundle_column_error.body": "Something went wrong while loading this component.", + "bundle_column_error.retry": "Try again", + "bundle_column_error.title": "Network error", + "bundle_modal_error.close": "Close", + "bundle_modal_error.message": "Something went wrong while loading this component.", + "bundle_modal_error.retry": "Try again", + "column.blocks": "Blocked users", + "column.community": "Local timeline", + "column.direct": "Direct messages", + "column.domain_blocks": "Hidden domains", + "column.favourites": "Favourites", + "column.follow_requests": "Follow requests", + "column.home": "Home", + "column.lists": "Lists", + "column.mutes": "Muted users", + "column.notifications": "Notifications", + "column.pins": "Pinned toot", + "column.public": "Federated timeline", + "column_back_button.label": "Back", + "column_header.hide_settings": "Hide settings", + "column_header.moveLeft_settings": "Move column to the left", + "column_header.moveRight_settings": "Move column to the right", + "column_header.pin": "Pin", + "column_header.show_settings": "Show settings", + "column_header.unpin": "Unpin", + "column_subheading.navigation": "Navigation", + "column_subheading.settings": "Settings", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", + "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.", + "compose_form.lock_disclaimer.lock": "locked", + "compose_form.placeholder": "What is on your mind?", + "compose_form.publish": "Toot", + "compose_form.publish_loud": "{publish}!", + "compose_form.sensitive.marked": "Media is marked as sensitive", + "compose_form.sensitive.unmarked": "Media is not marked as sensitive", + "compose_form.spoiler.marked": "Text is hidden behind warning", + "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.spoiler_placeholder": "Write your warning here", + "confirmation_modal.cancel": "Cancel", + "confirmations.block.confirm": "Block", + "confirmations.block.message": "Are you sure you want to block {name}?", + "confirmations.delete.confirm": "Delete", + "confirmations.delete.message": "Are you sure you want to delete this status?", + "confirmations.delete_list.confirm": "Delete", + "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", + "confirmations.domain_block.confirm": "Hide entire domain", + "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.", + "confirmations.mute.confirm": "Mute", + "confirmations.mute.message": "Are you sure you want to mute {name}?", + "confirmations.unfollow.confirm": "Unfollow", + "confirmations.unfollow.message": "Are you sure you want to unfollow {name}?", + "embed.instructions": "Embed this status on your website by copying the code below.", + "embed.preview": "Here is what it will look like:", + "emoji_button.activity": "Activity", + "emoji_button.custom": "Custom", + "emoji_button.flags": "Flags", + "emoji_button.food": "Food & Drink", + "emoji_button.label": "Insert emoji", + "emoji_button.nature": "Nature", + "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.objects": "Objects", + "emoji_button.people": "People", + "emoji_button.recent": "Frequently used", + "emoji_button.search": "Search...", + "emoji_button.search_results": "Search results", + "emoji_button.symbols": "Symbols", + "emoji_button.travel": "Travel & Places", + "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", + "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", + "empty_column.hashtag": "There is nothing in this hashtag yet.", + "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", + "empty_column.home.public_timeline": "the public timeline", + "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", + "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.", + "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up", + "follow_request.authorize": "Authorize", + "follow_request.reject": "Reject", + "getting_started.appsshort": "Apps", + "getting_started.faq": "FAQ", + "getting_started.heading": "Getting started", + "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}.", + "getting_started.userguide": "User Guide", + "home.column_settings.advanced": "Advanced", + "home.column_settings.basic": "Basic", + "home.column_settings.filter_regex": "Filter out by regular expressions", + "home.column_settings.show_reblogs": "Show boosts", + "home.column_settings.show_replies": "Show replies", + "home.settings": "Column settings", + "keyboard_shortcuts.back": "to navigate back", + "keyboard_shortcuts.boost": "to boost", + "keyboard_shortcuts.column": "to focus a status in one of the columns", + "keyboard_shortcuts.compose": "to focus the compose textarea", + "keyboard_shortcuts.description": "Description", + "keyboard_shortcuts.down": "to move down in the list", + "keyboard_shortcuts.enter": "to open status", + "keyboard_shortcuts.favourite": "to favourite", + "keyboard_shortcuts.heading": "Keyboard Shortcuts", + "keyboard_shortcuts.hotkey": "Hotkey", + "keyboard_shortcuts.legend": "to display this legend", + "keyboard_shortcuts.mention": "to mention author", + "keyboard_shortcuts.reply": "to reply", + "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", + "keyboard_shortcuts.toot": "to start a brand new toot", + "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", + "keyboard_shortcuts.up": "to move up in the list", + "lightbox.close": "Close", + "lightbox.next": "Next", + "lightbox.previous": "Previous", + "lists.account.add": "Add to list", + "lists.account.remove": "Remove from list", + "lists.delete": "Delete list", + "lists.edit": "Edit list", + "lists.new.create": "Add list", + "lists.new.title_placeholder": "New list title", + "lists.search": "Search among people you follow", + "lists.subheading": "Your lists", + "loading_indicator.label": "Loading...", + "media_gallery.toggle_visible": "Toggle visibility", + "missing_indicator.label": "Not found", + "missing_indicator.sublabel": "This resource could not be found", + "mute_modal.hide_notifications": "Hide notifications from this user?", + "navigation_bar.blocks": "Blocked users", + "navigation_bar.community_timeline": "Local timeline", + "navigation_bar.direct": "Direct messages", + "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.edit_profile": "Edit profile", + "navigation_bar.favourites": "Favourites", + "navigation_bar.follow_requests": "Follow requests", + "navigation_bar.info": "Extended information", + "navigation_bar.keyboard_shortcuts": "Keyboard shortcuts", + "navigation_bar.lists": "Lists", + "navigation_bar.logout": "Logout", + "navigation_bar.mutes": "Muted users", + "navigation_bar.pins": "Pinned toots", + "navigation_bar.preferences": "Preferences", + "navigation_bar.public_timeline": "Federated timeline", + "notification.favourite": "{name} favourited your status", + "notification.follow": "{name} followed you", + "notification.mention": "{name} mentioned you", + "notification.reblog": "{name} boosted your status", + "notifications.clear": "Clear notifications", + "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?", + "notifications.column_settings.alert": "Desktop notifications", + "notifications.column_settings.favourite": "Favourites:", + "notifications.column_settings.follow": "New followers:", + "notifications.column_settings.mention": "Mentions:", + "notifications.column_settings.push": "Push notifications", + "notifications.column_settings.push_meta": "This device", + "notifications.column_settings.reblog": "Boosts:", + "notifications.column_settings.show": "Show in column", + "notifications.column_settings.sound": "Play sound", + "onboarding.done": "Done", + "onboarding.next": "Next", + "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.", + "onboarding.page_four.home": "The home timeline shows posts from people you follow.", + "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.", + "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.", + "onboarding.page_one.full_handle": "Your full handle", + "onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.", + "onboarding.page_one.welcome": "Welcome to Mastodon!", + "onboarding.page_six.admin": "Your instance's admin is {admin}.", + "onboarding.page_six.almost_done": "Almost done...", + "onboarding.page_six.appetoot": "Bon Appetoot!", + "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.", + "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.", + "onboarding.page_six.guidelines": "community guidelines", + "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!", + "onboarding.page_six.various_app": "mobile apps", + "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.", + "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.", + "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.", + "onboarding.skip": "Skip", + "privacy.change": "Adjust status privacy", + "privacy.direct.long": "Post to mentioned users only", + "privacy.direct.short": "Direct", + "privacy.private.long": "Post to followers only", + "privacy.private.short": "Followers-only", + "privacy.public.long": "Post to public timelines", + "privacy.public.short": "Public", + "privacy.unlisted.long": "Do not show in public timelines", + "privacy.unlisted.short": "Unlisted", + "regeneration_indicator.label": "Loading…", + "regeneration_indicator.sublabel": "Your home feed is being prepared!", + "relative_time.days": "{number}d", + "relative_time.hours": "{number}h", + "relative_time.just_now": "now", + "relative_time.minutes": "{number}m", + "relative_time.seconds": "{number}s", + "reply_indicator.cancel": "Cancel", + "report.forward": "Forward to {target}", + "report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", + "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", + "report.placeholder": "Additional comments", + "report.submit": "Submit", + "report.target": "Report {target}", + "search.placeholder": "Search", + "search_popout.search_format": "Advanced search format", + "search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "status", + "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", + "search_popout.tips.user": "user", + "search_results.accounts": "People", + "search_results.hashtags": "Hashtags", + "search_results.statuses": "Toots", + "search_results.total": "{count, number} {count, plural, one {result} other {results}}", + "standalone.public_title": "A look inside...", + "status.block": "Block @{name}", + "status.cancel_reblog_private": "Unboost", + "status.cannot_reblog": "This post cannot be boosted", + "status.delete": "Delete", + "status.direct": "Direct message @{name}", + "status.embed": "Embed", + "status.favourite": "Favourite", + "status.load_more": "Load more", + "status.media_hidden": "Media hidden", + "status.mention": "Mention @{name}", + "status.more": "More", + "status.mute": "Mute @{name}", + "status.mute_conversation": "Mute conversation", + "status.open": "Expand this status", + "status.pin": "Pin on profile", + "status.pinned": "Pinned toot", + "status.reblog": "Boost", + "status.reblog_private": "Boost to original audience", + "status.reblogged_by": "{name} boosted", + "status.reply": "Reply", + "status.replyAll": "Reply to thread", + "status.report": "Report @{name}", + "status.sensitive_toggle": "Click to view", + "status.sensitive_warning": "Sensitive content", + "status.share": "Share", + "status.show_less": "Show less", + "status.show_less_all": "Show less for all", + "status.show_more": "Show more", + "status.show_more_all": "Show more for all", + "status.unmute_conversation": "Unmute conversation", + "status.unpin": "Unpin from profile", + "tabs_bar.federated_timeline": "Federated", + "tabs_bar.home": "Home", + "tabs_bar.local_timeline": "Local", + "tabs_bar.notifications": "Notifications", + "tabs_bar.search": "Search", + "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "upload_area.title": "Drag & drop to upload", + "upload_button.label": "Add media", + "upload_form.description": "Describe for the visually impaired", + "upload_form.focus": "Crop", + "upload_form.undo": "Undo", + "upload_progress.label": "Uploading...", + "video.close": "Close video", + "video.exit_fullscreen": "Exit full screen", + "video.expand": "Expand video", + "video.fullscreen": "Full screen", + "video.hide": "Hide video", + "video.mute": "Mute sound", + "video.pause": "Pause", + "video.play": "Play", + "video.unmute": "Unmute sound" +} diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 3b91c0d2c..82b44fe30 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index cdf6f46a3..056fbfe8f 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 261e5795e..1a7b58789 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.reply": "to reply", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/locales/whitelist_el.json b/app/javascript/mastodon/locales/whitelist_el.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/app/javascript/mastodon/locales/whitelist_el.json @@ -0,0 +1,2 @@ +[ +] diff --git a/app/javascript/mastodon/locales/whitelist_eu.json b/app/javascript/mastodon/locales/whitelist_eu.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/app/javascript/mastodon/locales/whitelist_eu.json @@ -0,0 +1,2 @@ +[ +] diff --git a/app/javascript/mastodon/locales/whitelist_te.json b/app/javascript/mastodon/locales/whitelist_te.json new file mode 100644 index 000000000..0d4f101c7 --- /dev/null +++ b/app/javascript/mastodon/locales/whitelist_te.json @@ -0,0 +1,2 @@ +[ +] diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index aba0bde83..a3a4de0af 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "提及嘟文作者", "keyboard_shortcuts.reply": "回复嘟文", "keyboard_shortcuts.search": "选择搜索框", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "发送新嘟文", "keyboard_shortcuts.unfocus": "取消输入", "keyboard_shortcuts.up": "在列表中让光标上移", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index b5ebd20fc..7719e08a6 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "提及作者", "keyboard_shortcuts.reply": "回覆", "keyboard_shortcuts.search": "把標示移動到搜索", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "新的推文", "keyboard_shortcuts.unfocus": "把標示移離文字輸入和搜索", "keyboard_shortcuts.up": "在列表往上移動", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 28d634600..84ff25e03 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -135,6 +135,7 @@ "keyboard_shortcuts.mention": "到提到的作者", "keyboard_shortcuts.reply": "到回應", "keyboard_shortcuts.search": "to focus search", + "keyboard_shortcuts.toggle_hidden": "to show/hide text behind CW", "keyboard_shortcuts.toot": "to start a brand new toot", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.up": "to move up in the list", diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index da9b8c420..84d4fc698 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -105,7 +105,7 @@ export default function notifications(state = initialState, action) { return expandNormalizedNotifications(state, action.notifications, action.next); case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_MUTE_SUCCESS: - return filterNotifications(state, action.relationship); + return action.relationship.muting_notifications ? filterNotifications(state, action.relationship) : state; case NOTIFICATIONS_CLEAR: return state.set('items', ImmutableList()).set('hasMore', false); case TIMELINE_DELETE: diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index ad897bcc9..dd675d78f 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -34,7 +34,7 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial) => mMap.update('items', ImmutableList(), oldIds => { const newIds = statuses.map(status => status.get('id')); const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1; - const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) >= 0); + const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) > 0); if (firstIndex < 0) { return (isPartial ? newIds.unshift(null) : newIds).concat(oldIds.skip(lastIndex)); diff --git a/app/javascript/mastodon/utils/__tests__/base64-test.js b/app/javascript/mastodon/utils/__tests__/base64-test.js new file mode 100644 index 000000000..1b3260faa --- /dev/null +++ b/app/javascript/mastodon/utils/__tests__/base64-test.js @@ -0,0 +1,10 @@ +import * as base64 from '../base64'; + +describe('base64', () => { + describe('decode', () => { + it('returns a uint8 array', () => { + const arr = base64.decode('dGVzdA=='); + expect(arr).toEqual(new Uint8Array([116, 101, 115, 116])); + }); + }); +}); diff --git a/app/javascript/mastodon/utils/base64.js b/app/javascript/mastodon/utils/base64.js new file mode 100644 index 000000000..8226e2c54 --- /dev/null +++ b/app/javascript/mastodon/utils/base64.js @@ -0,0 +1,10 @@ +export const decode = base64 => { + const rawData = window.atob(base64); + const outputArray = new Uint8Array(rawData.length); + + for (let i = 0; i < rawData.length; ++i) { + outputArray[i] = rawData.charCodeAt(i); + } + + return outputArray; +}; diff --git a/app/javascript/mastodon/utils/resize_image.js b/app/javascript/mastodon/utils/resize_image.js new file mode 100644 index 000000000..6442eda38 --- /dev/null +++ b/app/javascript/mastodon/utils/resize_image.js @@ -0,0 +1,66 @@ +const MAX_IMAGE_DIMENSION = 1280; + +const getImageUrl = inputFile => new Promise((resolve, reject) => { + if (window.URL && URL.createObjectURL) { + try { + resolve(URL.createObjectURL(inputFile)); + } catch (error) { + reject(error); + } + return; + } + + const reader = new FileReader(); + reader.onerror = (...args) => reject(...args); + reader.onload = ({ target }) => resolve(target.result); + + reader.readAsDataURL(inputFile); +}); + +const loadImage = inputFile => new Promise((resolve, reject) => { + getImageUrl(inputFile).then(url => { + const img = new Image(); + + img.onerror = (...args) => reject(...args); + img.onload = () => resolve(img); + + img.src = url; + }).catch(reject); +}); + +export default inputFile => new Promise((resolve, reject) => { + if (!inputFile.type.match(/image.*/) || inputFile.type === 'image/gif') { + resolve(inputFile); + return; + } + + loadImage(inputFile).then(img => { + const canvas = document.createElement('canvas'); + const { width, height } = img; + + let newWidth, newHeight; + + if (width < MAX_IMAGE_DIMENSION && height < MAX_IMAGE_DIMENSION) { + resolve(inputFile); + return; + } + + if (width > height) { + newHeight = height * MAX_IMAGE_DIMENSION / width; + newWidth = MAX_IMAGE_DIMENSION; + } else if (height > width) { + newWidth = width * MAX_IMAGE_DIMENSION / height; + newHeight = MAX_IMAGE_DIMENSION; + } else { + newWidth = MAX_IMAGE_DIMENSION; + newHeight = MAX_IMAGE_DIMENSION; + } + + canvas.width = newWidth; + canvas.height = newHeight; + + canvas.getContext('2d').drawImage(img, 0, 0, newWidth, newHeight); + + canvas.toBlob(resolve, inputFile.type); + }).catch(reject); +}); diff --git a/app/javascript/styles/contrast.scss b/app/javascript/styles/contrast.scss new file mode 100644 index 000000000..5b43aecbe --- /dev/null +++ b/app/javascript/styles/contrast.scss @@ -0,0 +1,3 @@ +@import 'contrast/variables'; +@import 'application'; +@import 'contrast/diff'; diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss new file mode 100644 index 000000000..eee9ecc3e --- /dev/null +++ b/app/javascript/styles/contrast/diff.scss @@ -0,0 +1,14 @@ +// components.scss +.compose-form { + .compose-form__modifiers { + .compose-form__upload { + &-description { + input { + &::placeholder { + opacity: 1.0; + } + } + } + } + } +} diff --git a/app/javascript/styles/contrast/variables.scss b/app/javascript/styles/contrast/variables.scss new file mode 100644 index 000000000..f6cadf029 --- /dev/null +++ b/app/javascript/styles/contrast/variables.scss @@ -0,0 +1,24 @@ +// Dependent colors +$black: #000000; + +$classic-base-color: #282c37; +$classic-primary-color: #9baec8; +$classic-secondary-color: #d9e1e8; +$classic-highlight-color: #2b90d9; + +$ui-base-color: $classic-base-color !default; +$ui-primary-color: $classic-primary-color !default; +$ui-secondary-color: $classic-secondary-color !default; + +// Differences +$ui-highlight-color: #2b5fd9; + +$darker-text-color: lighten($ui-primary-color, 20%) !default; +$dark-text-color: lighten($ui-primary-color, 12%) !default; +$secondary-text-color: lighten($ui-secondary-color, 6%) !default; +$highlight-text-color: $classic-highlight-color !default; +$action-button-color: #8d9ac2; + +$inverted-text-color: $black !default; +$lighter-text-color: darken($ui-base-color,6%) !default; +$light-text-color: darken($ui-primary-color, 40%) !default; diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index 0a09a38d2..c9c0e3081 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -225,7 +225,7 @@ $small-breakpoint: 960px; font-family: inherit; font-size: inherit; line-height: inherit; - color: transparentize($darker-text-color, 0.1); + color: lighten($darker-text-color, 10%); } h1 { @@ -234,14 +234,14 @@ $small-breakpoint: 960px; line-height: 30px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; small { font-family: 'mastodon-font-sans-serif', sans-serif; display: block; font-size: 18px; font-weight: 400; - color: opacify($darker-text-color, 0.1); + color: lighten($darker-text-color, 10%); } } @@ -251,7 +251,7 @@ $small-breakpoint: 960px; line-height: 26px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; } h3 { @@ -260,7 +260,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; } h4 { @@ -269,7 +269,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; } h5 { @@ -278,7 +278,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; } h6 { @@ -287,7 +287,7 @@ $small-breakpoint: 960px; line-height: 24px; font-weight: 500; margin-bottom: 20px; - color: $primary-text-color; + color: $secondary-text-color; } ul, @@ -405,7 +405,7 @@ $small-breakpoint: 960px; font-size: 14px; &:hover { - color: $darker-text-color; + color: $secondary-text-color; } } @@ -517,7 +517,7 @@ $small-breakpoint: 960px; span { &:last-child { - color: $darker-text-color; + color: $secondary-text-color; } } @@ -559,7 +559,7 @@ $small-breakpoint: 960px; a, span { font-weight: 400; - color: opacify($darker-text-color, 0.1); + color: darken($darker-text-color, 10%); } a { @@ -775,7 +775,7 @@ $small-breakpoint: 960px; } p a { - color: $darker-text-color; + color: $secondary-text-color; } h1 { @@ -787,7 +787,7 @@ $small-breakpoint: 960px; color: $darker-text-color; span { - color: $darker-text-color; + color: $secondary-text-color; } } } @@ -896,7 +896,7 @@ $small-breakpoint: 960px; } a { - color: $darker-text-color; + color: $secondary-text-color; text-decoration: none; } } @@ -980,7 +980,7 @@ $small-breakpoint: 960px; .footer-links { padding-bottom: 50px; text-align: right; - color: $darker-text-color; + color: $dark-text-color; p { font-size: 14px; @@ -995,7 +995,7 @@ $small-breakpoint: 960px; &__footer { margin-top: 10px; text-align: center; - color: $darker-text-color; + color: $dark-text-color; p { font-size: 14px; diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index f9af6f288..c2d0de4b9 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -178,7 +178,7 @@ font-size: 14px; line-height: 18px; padding: 0 15px; - color: $darker-text-color; + color: $secondary-text-color; } @media screen and (max-width: 480px) { @@ -256,7 +256,7 @@ .current { background: $simple-background-color; border-radius: 100px; - color: $lighter-text-color; + color: $inverted-text-color; cursor: default; margin: 0 10px; } @@ -268,7 +268,7 @@ .older, .newer { text-transform: uppercase; - color: $primary-text-color; + color: $secondary-text-color; } .older { @@ -293,7 +293,7 @@ .disabled { cursor: default; - color: opacify($lighter-text-color, 0.1); + color: lighten($inverted-text-color, 10%); } @media screen and (max-width: 700px) { @@ -332,7 +332,7 @@ width: 335px; background: $simple-background-color; border-radius: 4px; - color: $lighter-text-color; + color: $inverted-text-color; margin: 0 5px 10px; position: relative; @@ -344,7 +344,7 @@ overflow: hidden; height: 100px; border-radius: 4px 4px 0 0; - background-color: opacify($lighter-text-color, 0.04); + background-color: lighten($inverted-text-color, 4%); background-size: cover; background-position: center; position: relative; @@ -422,7 +422,7 @@ .account__header__content { padding: 10px 15px; padding-top: 15px; - color: transparentize($lighter-text-color, 0.1); + color: $lighter-text-color; word-wrap: break-word; overflow: hidden; text-overflow: ellipsis; @@ -434,7 +434,7 @@ .nothing-here { width: 100%; display: block; - color: $lighter-text-color; + color: $light-text-color; font-size: 14px; font-weight: 500; text-align: center; @@ -493,7 +493,7 @@ span { font-size: 14px; - color: $inverted-text-color; + color: $light-text-color; } } @@ -508,7 +508,7 @@ .account__header__content { font-size: 14px; - color: $darker-text-color; + color: $inverted-text-color; } } @@ -586,7 +586,7 @@ font-weight: 500; text-align: center; width: 94px; - color: opacify($darker-text-color, 0.1); + color: $secondary-text-color; background: rgba(darken($ui-base-color, 8%), 0.5); } diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 348f72078..a6cc8b62b 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -90,7 +90,7 @@ padding-left: 25px; h2 { - color: $primary-text-color; + color: $secondary-text-color; font-size: 24px; line-height: 28px; font-weight: 400; @@ -98,7 +98,7 @@ } h3 { - color: $primary-text-color; + color: $secondary-text-color; font-size: 20px; line-height: 28px; font-weight: 400; @@ -109,7 +109,7 @@ text-transform: uppercase; font-size: 13px; font-weight: 500; - color: $primary-text-color; + color: $darker-text-color; padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px solid lighten($ui-base-color, 8%); @@ -117,7 +117,7 @@ h6 { font-size: 16px; - color: $primary-text-color; + color: $secondary-text-color; line-height: 28px; font-weight: 400; } @@ -125,7 +125,7 @@ & > p { font-size: 14px; line-height: 18px; - color: $darker-text-color; + color: $secondary-text-color; margin-bottom: 20px; strong { @@ -141,14 +141,15 @@ } hr { - margin: 20px 0; + width: 100%; + height: 0; border: 0; - background: transparent; - border-bottom: 1px solid $ui-base-color; + border-bottom: 1px solid rgba($ui-base-lighter-color, .6); + margin: 20px 0; - &.section-break { - margin: 30px 0; - border-bottom: 2px solid $ui-base-lighter-color; + &.spacer { + height: 1px; + border: 0; } } @@ -291,7 +292,7 @@ font-weight: 500; font-size: 14px; line-height: 18px; - color: $primary-text-color; + color: $secondary-text-color; @each $lang in $cjk-langs { &:lang(#{$lang}) { @@ -335,34 +336,8 @@ } } -.report-note__comment { - margin-bottom: 20px; -} - -.report-note__form { - margin-bottom: 20px; - - .report-note__textarea { - box-sizing: border-box; - border: 0; - padding: 7px 4px; - margin-bottom: 10px; - font-size: 16px; - color: $inverted-text-color; - display: block; - width: 100%; - outline: 0; - font-family: inherit; - resize: vertical; - } - - .report-note__buttons { - text-align: right; - } - - .report-note__button { - margin: 0 0 5px 5px; - } +.simple_form.new_report_note { + max-width: 100%; } .batch-form-box { @@ -390,13 +365,6 @@ } } -.batch-checkbox, -.batch-checkbox-all { - display: flex; - align-items: center; - margin-right: 5px; -} - .back-link { margin-bottom: 10px; font-size: 14px; @@ -416,7 +384,7 @@ } .log-entry { - margin-bottom: 8px; + margin-bottom: 20px; line-height: 20px; &__header { @@ -452,7 +420,7 @@ } &__timestamp { - color: $darker-text-color; + color: $dark-text-color; } &__extras { @@ -469,7 +437,7 @@ &__icon { font-size: 28px; margin-right: 10px; - color: $darker-text-color; + color: $dark-text-color; } &__icon__overlay { @@ -496,7 +464,7 @@ a, .username, .target { - color: $primary-text-color; + color: $secondary-text-color; text-decoration: none; font-weight: 500; } @@ -506,7 +474,7 @@ } .diff-neutral { - color: $darker-text-color; + color: $secondary-text-color; } .diff-new { @@ -514,9 +482,12 @@ } } +a.name-tag, .name-tag { display: flex; align-items: center; + text-decoration: none; + color: $secondary-text-color; .avatar { display: block; @@ -528,4 +499,52 @@ .username { font-weight: 500; } + + &.suspended { + .username { + text-decoration: line-through; + color: lighten($error-red, 12%); + } + + .avatar { + filter: grayscale(100%); + opacity: 0.8; + } + } +} + +.speech-bubble { + margin-bottom: 20px; + border-left: 4px solid $ui-highlight-color; + + &.positive { + border-left-color: $success-green; + } + + &.negative { + border-left-color: lighten($error-red, 12%); + } + + &__bubble { + padding: 16px; + padding-left: 14px; + font-size: 15px; + line-height: 20px; + border-radius: 4px 4px 4px 0; + position: relative; + font-weight: 500; + + a { + color: $darker-text-color; + } + } + + &__owner { + padding: 8px; + padding-left: 12px; + } + + time { + color: $dark-text-color; + } } diff --git a/app/javascript/styles/mastodon/compact_header.scss b/app/javascript/styles/mastodon/compact_header.scss index 83ac7a8d0..4980ab5f1 100644 --- a/app/javascript/styles/mastodon/compact_header.scss +++ b/app/javascript/styles/mastodon/compact_header.scss @@ -2,7 +2,7 @@ h1 { font-size: 24px; line-height: 28px; - color: $primary-text-color; + color: $darker-text-color; font-weight: 500; margin-bottom: 20px; padding: 0 10px; @@ -20,7 +20,7 @@ small { font-weight: 400; - color: $darker-text-color; + color: $secondary-text-color; } img { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index f0fde6666..a982585c3 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -31,7 +31,7 @@ &:active, &:focus, &:hover { - background-color: lighten($ui-highlight-color, 4%); + background-color: lighten($ui-highlight-color, 10%); transition: all 200ms ease-out; } @@ -83,7 +83,7 @@ } &.button-secondary { - color: $ui-primary-color; + color: $darker-text-color; background: transparent; padding: 3px 15px; border: 1px solid $ui-primary-color; @@ -92,7 +92,7 @@ &:focus, &:hover { border-color: lighten($ui-primary-color, 4%); - color: lighten($ui-primary-color, 4%); + color: lighten($darker-text-color, 4%); } } @@ -149,18 +149,18 @@ &:hover, &:active, &:focus { - color: transparentize($lighter-text-color, 0.07); + color: darken($lighter-text-color, 7%); } &.disabled { - color: opacify($lighter-text-color, 0.07); + color: lighten($lighter-text-color, 7%); } &.active { color: $highlight-text-color; &.disabled { - color: opacify($lighter-text-color, 0.13); + color: lighten($highlight-text-color, 13%); } } } @@ -193,12 +193,12 @@ &:hover, &:active, &:focus { - color: opacify($lighter-text-color, 0.07); + color: darken($lighter-text-color, 7%); transition: color 200ms ease-out; } &.disabled { - color: transparentize($lighter-text-color, 0.2); + color: lighten($lighter-text-color, 20%); cursor: default; } @@ -349,7 +349,7 @@ box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4); background: $ui-secondary-color; border-radius: 0 0 4px 4px; - color: $lighter-text-color; + color: $inverted-text-color; font-size: 14px; padding: 6px; @@ -457,7 +457,7 @@ input { background: transparent; - color: $primary-text-color; + color: $secondary-text-color; border: 0; padding: 0; margin: 0; @@ -471,8 +471,8 @@ } &::placeholder { - opacity: 0.54; - color: $darker-text-color; + opacity: 0.75; + color: $secondary-text-color; } } @@ -556,7 +556,6 @@ } .emojione { - display: inline-block; font-size: inherit; vertical-align: middle; object-fit: contain; @@ -588,7 +587,7 @@ } .reply-indicator__display-name { - color: $lighter-text-color; + color: $inverted-text-color; display: block; max-width: 100%; line-height: 24px; @@ -643,14 +642,14 @@ } a { - color: $ui-secondary-color; + color: $secondary-text-color; text-decoration: none; &:hover { text-decoration: underline; .fa { - color: lighten($action-button-color, 7%); + color: lighten($dark-text-color, 7%); } } @@ -665,7 +664,7 @@ } .fa { - color: $action-button-color; + color: $dark-text-color; } } @@ -702,7 +701,7 @@ border-radius: 2px; background: transparent; border: 0; - color: $lighter-text-color; + color: $inverted-text-color; font-weight: 700; font-size: 11px; padding: 0 6px; @@ -769,7 +768,7 @@ &.light { .status__relative-time { - color: $lighter-text-color; + color: $light-text-color; } .status__display-name { @@ -782,7 +781,7 @@ } span { - color: $lighter-text-color; + color: $light-text-color; } } @@ -816,13 +815,13 @@ } .status__relative-time { - color: $darker-text-color; + color: $dark-text-color; float: right; font-size: 14px; } .status__display-name { - color: $darker-text-color; + color: $dark-text-color; } .status__info .status__display-name { @@ -873,14 +872,14 @@ .status__prepend { margin-left: 68px; - color: $darker-text-color; + color: $dark-text-color; padding: 8px 0; padding-bottom: 2px; font-size: 14px; position: relative; .status__display-name strong { - color: $darker-text-color; + color: $dark-text-color; } > span { @@ -942,7 +941,7 @@ .detailed-status__meta { margin-top: 15px; - color: $darker-text-color; + color: $dark-text-color; font-size: 14px; line-height: 18px; } @@ -1006,6 +1005,15 @@ padding: 10px; border-bottom: 1px solid lighten($ui-base-color, 8%); + &.compact { + padding: 0; + border-bottom: 0; + + .account__avatar-wrapper { + margin-left: 0; + } + } + .account__display-name { flex: 1 1 auto; display: block; @@ -1029,7 +1037,6 @@ .account__avatar { @include avatar-radius(); position: relative; - cursor: pointer; &-inline { display: inline-block; @@ -1038,6 +1045,10 @@ } } +a .account__avatar { + cursor: pointer; +} + .account__avatar-overlay { @include avatar-size(48px); @@ -1079,7 +1090,7 @@ } .account__header__username { - color: $darker-text-color; + color: $secondary-text-color; } } @@ -1089,7 +1100,7 @@ } .account__header__content { - color: $darker-text-color; + color: $secondary-text-color; } .account__header__display-name { @@ -1117,7 +1128,7 @@ .account__disclaimer { padding: 10px; border-top: 1px solid lighten($ui-base-color, 8%); - color: $darker-text-color; + color: $dark-text-color; strong { font-weight: 500; @@ -1286,7 +1297,7 @@ .status__display-name, .reply-indicator__display-name, .detailed-status__display-name, -.account__display-name { +a.account__display-name { &:hover strong { text-decoration: underline; } @@ -1304,7 +1315,7 @@ } .detailed-status__display-name { - color: $darker-text-color; + color: $secondary-text-color; display: block; line-height: 24px; margin-bottom: 15px; @@ -1339,11 +1350,11 @@ .muted { .status__content p, .status__content a { - color: $darker-text-color; + color: $dark-text-color; } .status__display-name strong { - color: $darker-text-color; + color: $dark-text-color; } .status__avatar { @@ -1351,11 +1362,11 @@ } a.status__content__spoiler-link { - background: $darker-text-color; - color: lighten($ui-base-color, 4%); + background: $ui-base-lighter-color; + color: $inverted-text-color; &:hover { - background: transparentize($darker-text-color, 0.07); + background: lighten($ui-base-lighter-color, 7%); text-decoration: none; } } @@ -1366,7 +1377,7 @@ padding: 8px 0; padding-bottom: 0; cursor: default; - color: $ui-primary-color; + color: $darker-text-color; font-size: 15px; position: relative; @@ -1477,7 +1488,7 @@ color: $darker-text-color; strong { - color: $primary-text-color; + color: $secondary-text-color; } a { @@ -1591,7 +1602,7 @@ &:hover, &:active { background: $ui-highlight-color; - color: $primary-text-color; + color: $secondary-text-color; outline: 0; } } @@ -1644,7 +1655,7 @@ &:hover { background: $ui-highlight-color; - color: $primary-text-color; + color: $secondary-text-color; } } } @@ -1656,7 +1667,7 @@ .static-content { padding: 10px; padding-top: 20px; - color: $darker-text-color; + color: $dark-text-color; h1 { font-size: 16px; @@ -1743,7 +1754,7 @@ display: block; flex: 1 1 auto; padding: 15px 5px 13px; - color: $ui-primary-color; + color: $darker-text-color; text-decoration: none; text-align: center; font-size: 16px; @@ -2155,7 +2166,7 @@ .column-subheading { background: $ui-base-color; - color: $darker-text-color; + color: $dark-text-color; padding: 8px 20px; font-size: 12px; font-weight: 500; @@ -2178,11 +2189,11 @@ flex: 1 0 auto; p { - color: $darker-text-color; + color: $secondary-text-color; } a { - color: opacify($darker-text-color, 0.07); + color: $dark-text-color; } } @@ -2263,7 +2274,7 @@ font-size: 14px; border: 1px solid lighten($ui-base-color, 8%); border-radius: 4px; - color: $darker-text-color; + color: $dark-text-color; margin-top: 14px; text-decoration: none; overflow: hidden; @@ -2343,7 +2354,7 @@ a.status-card { display: block; font-weight: 500; margin-bottom: 5px; - color: $ui-primary-color; + color: $darker-text-color; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -2357,7 +2368,7 @@ a.status-card { } .status-card__description { - color: $ui-primary-color; + color: $darker-text-color; } .status-card__host { @@ -2401,7 +2412,7 @@ a.status-card { .load-more { display: block; - color: $darker-text-color; + color: $dark-text-color; background-color: transparent; border: 0; font-size: inherit; @@ -2425,7 +2436,7 @@ a.status-card { text-align: center; font-size: 16px; font-weight: 500; - color: opacify($darker-text-color, 0.07); + color: $dark-text-color; background: $ui-base-color; cursor: default; display: flex; @@ -2465,7 +2476,7 @@ a.status-card { strong { display: block; margin-bottom: 10px; - color: $darker-text-color; + color: $dark-text-color; } span { @@ -2553,13 +2564,13 @@ a.status-card { .column-header__button { background: lighten($ui-base-color, 4%); border: 0; - color: $ui-primary-color; + color: $darker-text-color; cursor: pointer; font-size: 16px; padding: 0 15px; &:hover { - color: lighten($ui-primary-color, 7%); + color: lighten($darker-text-color, 7%); } &.active { @@ -2640,7 +2651,7 @@ a.status-card { } .loading-indicator { - color: $darker-text-color; + color: $dark-text-color; font-size: 12px; font-weight: 400; text-transform: uppercase; @@ -2737,7 +2748,7 @@ a.status-card { &:active, &:focus { padding: 0; - color: transparentize($darker-text-color, 0.07); + color: lighten($darker-text-color, 8%); } } @@ -2861,7 +2872,7 @@ a.status-card { .empty-column-indicator, .error-column { - color: $darker-text-color; + color: $dark-text-color; background: $ui-base-color; text-align: center; padding: 20px; @@ -3063,7 +3074,7 @@ a.status-card { display: flex; align-items: center; justify-content: center; - color: $primary-text-color; + color: $secondary-text-color; font-size: 18px; font-weight: 500; border: 2px dashed $ui-base-lighter-color; @@ -3161,7 +3172,7 @@ a.status-card { } .privacy-dropdown__option { - color: $lighter-text-color; + color: $inverted-text-color; padding: 10px; cursor: pointer; display: flex; @@ -3283,7 +3294,7 @@ a.status-card { font-size: 18px; width: 18px; height: 18px; - color: $ui-secondary-color; + color: $secondary-text-color; cursor: default; pointer-events: none; @@ -3319,7 +3330,7 @@ a.status-card { } .search-results__header { - color: $darker-text-color; + color: $dark-text-color; background: lighten($ui-base-color, 2%); border-bottom: 1px solid darken($ui-base-color, 4%); padding: 15px 10px; @@ -3367,13 +3378,13 @@ a.status-card { .search-results__hashtag { display: block; padding: 10px; - color: darken($primary-text-color, 4%); + color: $secondary-text-color; text-decoration: none; &:hover, &:active, &:focus { - color: $primary-text-color; + color: lighten($secondary-text-color, 4%); text-decoration: underline; } } @@ -3638,7 +3649,7 @@ a.status-card { &:hover, &:focus, &:active { - color: transparentize($lighter-text-color, 0.04); + color: darken($lighter-text-color, 4%); background-color: darken($ui-secondary-color, 16%); } @@ -3732,7 +3743,7 @@ a.status-card { strong { font-weight: 500; background: $ui-base-color; - color: $primary-text-color; + color: $secondary-text-color; border-radius: 4px; font-size: 14px; padding: 3px 6px; @@ -3792,7 +3803,7 @@ a.status-card { &__case { background: $ui-base-color; - color: $primary-text-color; + color: $secondary-text-color; font-weight: 500; padding: 10px; border-radius: 4px; @@ -3809,7 +3820,7 @@ a.status-card { .figure { background: darken($ui-base-color, 8%); - color: $darker-text-color; + color: $secondary-text-color; margin-bottom: 20px; border-radius: 4px; padding: 10px; @@ -3921,7 +3932,7 @@ a.status-card { } .status__content__spoiler-link { - color: lighten($ui-secondary-color, 8%); + color: lighten($secondary-text-color, 8%); } } @@ -4026,6 +4037,10 @@ a.status-card { overflow-y: auto; overflow-x: hidden; + .status__content a { + color: $highlight-text-color; + } + @media screen and (max-width: 480px) { max-height: 10vh; } @@ -4151,7 +4166,7 @@ a.status-card { &:hover, &:focus, &:active { - color: transparentize($lighter-text-color, 0.04); + color: darken($lighter-text-color, 4%); } } } @@ -4232,7 +4247,7 @@ a.status-card { &__icon { flex: 0 0 auto; - color: $darker-text-color; + color: $dark-text-color; padding: 8px 18px; cursor: default; border-right: 1px solid lighten($ui-base-color, 8%); @@ -4262,7 +4277,7 @@ a.status-card { a { text-decoration: none; - color: $darker-text-color; + color: $dark-text-color; font-weight: 500; &:hover { @@ -4281,7 +4296,7 @@ a.status-card { } .fa { - color: $darker-text-color; + color: $dark-text-color; } } } @@ -4317,7 +4332,7 @@ a.status-card { cursor: zoom-in; display: block; text-decoration: none; - color: $ui-secondary-color; + color: $secondary-text-color; line-height: 0; &, @@ -4431,6 +4446,8 @@ a.status-card { video { max-width: 100% !important; max-height: 100% !important; + width: 100% !important; + height: 100% !important; } } @@ -4488,7 +4505,7 @@ a.status-card { &:hover, &:active, &:focus { - color: transparentize($darker-text-color, 0.07); + color: lighten($darker-text-color, 7%); } } @@ -4693,7 +4710,7 @@ a.status-card { &:active, &:focus { outline: 0; - color: transparentize($darker-text-color, 0.07); + color: $secondary-text-color; &::before { content: ""; @@ -4733,7 +4750,7 @@ a.status-card { position: relative; &.active { - color: transparentize($darker-text-color, 0.07); + color: $secondary-text-color; &::before, &::after { @@ -4768,12 +4785,12 @@ a.status-card { padding: 10px 14px; padding-bottom: 14px; margin-top: 10px; - color: $lighter-text-color; + color: $light-text-color; box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); h4 { text-transform: uppercase; - color: $lighter-text-color; + color: $light-text-color; font-size: 13px; font-weight: 500; margin-bottom: 10px; @@ -4805,7 +4822,7 @@ noscript { div { font-size: 14px; margin: 30px auto; - color: $primary-text-color; + color: $secondary-text-color; max-width: 400px; a { @@ -4958,7 +4975,7 @@ noscript { &__message { position: relative; margin-left: 58px; - color: $darker-text-color; + color: $dark-text-color; padding: 8px 0; padding-top: 0; padding-bottom: 4px; diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss index 8df2902d2..9d5ab66a4 100644 --- a/app/javascript/styles/mastodon/containers.scss +++ b/app/javascript/styles/mastodon/containers.scss @@ -100,7 +100,7 @@ .name { flex: 1 1 auto; - color: $darker-text-color; + color: $secondary-text-color; width: calc(100% - 88px); .username { diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss index 3620a6f54..cf9547586 100644 --- a/app/javascript/styles/mastodon/emoji_picker.scss +++ b/app/javascript/styles/mastodon/emoji_picker.scss @@ -50,7 +50,7 @@ cursor: pointer; &:hover { - color: opacify($lighter-text-color, 0.04); + color: darken($lighter-text-color, 4%); } } @@ -184,7 +184,7 @@ font-size: 14px; text-align: center; padding-top: 70px; - color: $lighter-text-color; + color: $light-text-color; .emoji-mart-category-label { display: none; diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 3a3b4c326..f97890187 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -248,7 +248,7 @@ code { } &:required:valid { - border-bottom-color: lighten($error-red, 12%); + border-bottom-color: $valid-value-color; } &:active, @@ -266,7 +266,7 @@ code { input[type=text], input[type=email], input[type=password] { - border-bottom-color: lighten($error-red, 12%); + border-bottom-color: $valid-value-color; } .error { @@ -356,7 +356,7 @@ code { padding: 7px 4px; padding-bottom: 9px; font-size: 16px; - color: $darker-text-color; + color: $dark-text-color; font-family: inherit; pointer-events: none; cursor: default; @@ -446,7 +446,7 @@ code { } strong { - color: $primary-text-color; + color: $secondary-text-color; font-weight: 500; @each $lang in $cjk-langs { @@ -483,7 +483,7 @@ code { .qr-alternative { margin-bottom: 20px; - color: $darker-text-color; + color: $secondary-text-color; flex: 150px; samp { diff --git a/app/javascript/styles/mastodon/landing_strip.scss b/app/javascript/styles/mastodon/landing_strip.scss index 651c06ced..86614b89b 100644 --- a/app/javascript/styles/mastodon/landing_strip.scss +++ b/app/javascript/styles/mastodon/landing_strip.scss @@ -45,7 +45,7 @@ padding: 14px; border-radius: 4px; background: rgba(darken($ui-base-color, 7%), 0.8); - color: $darker-text-color; + color: $secondary-text-color; font-weight: 400; margin-bottom: 20px; diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss index c39163ba8..281cbaf83 100644 --- a/app/javascript/styles/mastodon/stream_entries.scss +++ b/app/javascript/styles/mastodon/stream_entries.scss @@ -93,7 +93,7 @@ display: block; max-width: 100%; padding-right: 25px; - color: $lighter-text-color; + color: $inverted-text-color; } .status__avatar { @@ -134,7 +134,7 @@ span { font-size: 14px; - color: $inverted-text-color; + color: $light-text-color; } } @@ -191,7 +191,7 @@ span { font-size: 14px; - color: $lighter-text-color; + color: $light-text-color; } } } @@ -225,7 +225,7 @@ .detailed-status__meta { margin-top: 15px; - color: $lighter-text-color; + color: $light-text-color; font-size: 14px; line-height: 18px; @@ -270,7 +270,7 @@ padding-left: (48px + 14px * 2); padding-bottom: 0; margin-bottom: -4px; - color: $lighter-text-color; + color: $light-text-color; font-size: 14px; position: relative; @@ -280,7 +280,7 @@ } .status__display-name.muted strong { - color: $lighter-text-color; + color: $light-text-color; } } @@ -293,7 +293,7 @@ } .more { - color: $classic-primary-color; + color: $darker-text-color; display: block; padding: 14px; text-align: center; diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index c12d84f1c..fa876e603 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -11,6 +11,7 @@ vertical-align: top; border-top: 1px solid $ui-base-color; text-align: left; + background: darken($ui-base-color, 4%); } & > thead > tr > th { @@ -48,9 +49,38 @@ } } - &.inline-table > tbody > tr:nth-child(odd) > td, - &.inline-table > tbody > tr:nth-child(odd) > th { - background: transparent; + &.inline-table { + & > tbody > tr:nth-child(odd) { + & > td, + & > th { + background: transparent; + } + } + + & > tbody > tr:first-child { + & > td, + & > th { + border-top: 0; + } + } + } + + &.batch-table { + & > thead > tr > th { + background: $ui-base-color; + border-top: 1px solid darken($ui-base-color, 8%); + border-bottom: 1px solid darken($ui-base-color, 8%); + + &:first-child { + border-radius: 4px 0 0; + border-left: 1px solid darken($ui-base-color, 8%); + } + + &:last-child { + border-radius: 0 4px 0 0; + border-right: 1px solid darken($ui-base-color, 8%); + } + } } } @@ -63,6 +93,13 @@ samp { font-family: 'mastodon-font-monospace', monospace; } +button.table-action-link { + background: transparent; + border: 0; + font: inherit; +} + +button.table-action-link, a.table-action-link { text-decoration: none; display: inline-block; @@ -79,4 +116,77 @@ a.table-action-link { font-weight: 400; margin-right: 5px; } + + &:first-child { + padding-left: 0; + } +} + +.batch-table { + &__toolbar, + &__row { + display: flex; + + &__select { + box-sizing: border-box; + padding: 8px 16px; + cursor: pointer; + min-height: 100%; + + input { + margin-top: 8px; + } + } + + &__actions, + &__content { + padding: 8px 0; + padding-right: 16px; + flex: 1 1 auto; + } + } + + &__toolbar { + border: 1px solid darken($ui-base-color, 8%); + background: $ui-base-color; + border-radius: 4px 0 0; + height: 47px; + align-items: center; + + &__actions { + text-align: right; + padding-right: 16px - 5px; + } + } + + &__row { + border: 1px solid darken($ui-base-color, 8%); + border-top: 0; + background: darken($ui-base-color, 4%); + + &:hover { + background: darken($ui-base-color, 2%); + } + + &:nth-child(even) { + background: $ui-base-color; + + &:hover { + background: lighten($ui-base-color, 2%); + } + } + + &__content { + padding-top: 12px; + padding-bottom: 16px; + } + } + + .status__content { + padding-top: 0; + + strong { + font-weight: 700; + } + } } diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index dc4e72a2e..cbefe35b4 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -17,12 +17,6 @@ $base-shadow-color: $black !default; $base-overlay-background: $black !default; $base-border-color: $white !default; $simple-background-color: $white !default; -$primary-text-color: $white !default; -$darker-text-color: rgba($primary-text-color, 0.7) !default; -$highlight-text-color: $classic-highlight-color !default; -$inverted-text-color: $black !default; -$lighter-text-color: rgba($inverted-text-color, 0.7) !default; -$action-button-color: #8d9ac2; $valid-value-color: $success-green !default; $error-value-color: $error-red !default; @@ -31,7 +25,19 @@ $ui-base-color: $classic-base-color !default; // Darkest $ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkest $ui-primary-color: $classic-primary-color !default; // Lighter $ui-secondary-color: $classic-secondary-color !default; // Lightest -$ui-highlight-color: #2b5fd9; +$ui-highlight-color: $classic-highlight-color !default; + +// Variables for texts +$primary-text-color: $white !default; +$darker-text-color: $ui-primary-color !default; +$dark-text-color: $ui-base-lighter-color !default; +$secondary-text-color: $ui-secondary-color !default; +$highlight-text-color: $ui-highlight-color !default; +$action-button-color: $ui-base-lighter-color !default; +// For texts on inverted backgrounds +$inverted-text-color: $ui-base-color !default; +$lighter-text-color: $ui-base-lighter-color !default; +$light-text-color: $ui-primary-color !default; // Language codes that uses CJK fonts $cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW; diff --git a/app/lib/activitypub/activity.rb b/app/lib/activitypub/activity.rb index 9b00f0f52..84d4b1752 100644 --- a/app/lib/activitypub/activity.rb +++ b/app/lib/activitypub/activity.rb @@ -78,8 +78,10 @@ class ActivityPub::Activity notify_about_reblog(status) if reblog_of_local_account?(status) notify_about_mentions(status) - # Only continue if the status is supposed to have - # arrived in real-time + # Only continue if the status is supposed to have arrived in real-time. + # Note that if @options[:override_timestamps] isn't set, the status + # may have a lower snowflake id than other existing statuses, potentially + # "hiding" it from paginated API calls return unless @options[:override_timestamps] || status.within_realtime_window? distribute_to_followers(status) diff --git a/app/lib/activitypub/activity/announce.rb b/app/lib/activitypub/activity/announce.rb index c8a358195..7e146ea8c 100644 --- a/app/lib/activitypub/activity/announce.rb +++ b/app/lib/activitypub/activity/announce.rb @@ -15,7 +15,8 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity account: @account, reblog: original_status, uri: @json['id'], - created_at: @options[:override_timestamps] ? nil : @json['published'], + created_at: @json['published'], + override_timestamps: @options[:override_timestamps], visibility: original_status.visibility ) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 45c0e91cb..8d17a4ebe 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -47,7 +47,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity text: text_from_content || '', language: detected_language, spoiler_text: @object['summary'] || '', - created_at: @options[:override_timestamps] ? nil : @object['published'], + created_at: @object['published'], + override_timestamps: @options[:override_timestamps], reply: @object['inReplyTo'].present?, sensitive: @object['sensitive'] || false, visibility: visibility_from_audience, @@ -61,12 +62,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity return if @object['tag'].nil? as_array(@object['tag']).each do |tag| - case tag['type'] - when 'Hashtag' + if equals_or_includes?(tag['type'], 'Hashtag') process_hashtag tag, status - when 'Mention' + elsif equals_or_includes?(tag['type'], 'Mention') process_mention tag, status - when 'Emoji' + elsif equals_or_includes?(tag['type'], 'Emoji') process_emoji tag, status end end @@ -235,11 +235,11 @@ class ActivityPub::Activity::Create < ActivityPub::Activity end def supported_object_type? - SUPPORTED_TYPES.include?(@object['type']) + equals_or_includes_any?(@object['type'], SUPPORTED_TYPES) end def converted_object_type? - CONVERTED_TYPES.include?(@object['type']) + equals_or_includes_any?(@object['type'], CONVERTED_TYPES) end def skip_download? diff --git a/app/lib/activitypub/activity/update.rb b/app/lib/activitypub/activity/update.rb index 0134b4015..aa5907f03 100644 --- a/app/lib/activitypub/activity/update.rb +++ b/app/lib/activitypub/activity/update.rb @@ -1,11 +1,10 @@ # frozen_string_literal: true class ActivityPub::Activity::Update < ActivityPub::Activity + SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze + def perform - case @object['type'] - when 'Person' - update_account - end + update_account if equals_or_includes_any?(@object['type'], SUPPORTED_TYPES) end private diff --git a/app/lib/entity_cache.rb b/app/lib/entity_cache.rb new file mode 100644 index 000000000..2aa37389c --- /dev/null +++ b/app/lib/entity_cache.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'singleton' + +class EntityCache + include Singleton + + MAX_EXPIRATION = 7.days.freeze + + def mention(username, domain) + Rails.cache.fetch(to_key(:mention, username, domain), expires_in: MAX_EXPIRATION) { Account.select(:username, :domain, :url).find_remote(username, domain) } + end + + def emoji(shortcodes, domain) + shortcodes = [shortcodes] unless shortcodes.is_a?(Array) + cached = Rails.cache.read_multi(*shortcodes.map { |shortcode| to_key(:emoji, shortcode, domain) }) + uncached_ids = [] + + shortcodes.each do |shortcode| + uncached_ids << shortcode unless cached.key?(to_key(:emoji, shortcode, domain)) + end + + unless uncached_ids.empty? + uncached = CustomEmoji.where(shortcode: shortcodes, domain: domain, disabled: false).map { |item| [item.shortcode, item] }.to_h + uncached.each_value { |item| Rails.cache.write(to_key(:emoji, item.shortcode, domain), item, expires_in: MAX_EXPIRATION) } + end + + shortcodes.map { |shortcode| cached[to_key(:emoji, shortcode, domain)] || uncached[shortcode] }.compact + end + + def to_key(type, *ids) + "#{type}:#{ids.compact.map(&:downcase).join(':')}" + end +end diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb index e88e98eae..01346bfe5 100644 --- a/app/lib/exceptions.rb +++ b/app/lib/exceptions.rb @@ -6,6 +6,7 @@ module Mastodon class ValidationError < Error; end class HostValidationError < ValidationError; end class LengthValidationError < ValidationError; end + class DimensionsValidationError < ValidationError; end class RaceConditionError < Error; end class UnexpectedResponseError < Error diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 700fd61c4..3a2dcac68 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -145,10 +145,14 @@ class FeedManager redis.exists("subscribed:#{timeline_id}") end + def blocks_or_mutes?(receiver_id, account_ids, context) + Block.where(account_id: receiver_id, target_account_id: account_ids).any? || + (context == :home ? Mute.where(account_id: receiver_id, target_account_id: account_ids).any? : Mute.where(account_id: receiver_id, target_account_id: account_ids, hide_notifications: true).any?) + end + def filter_from_home?(status, receiver_id) return false if receiver_id == status.account_id return true if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?) - return true if keyword_filter?(status, receiver_id) check_for_mutes = [status.account_id] @@ -158,9 +162,10 @@ class FeedManager return true if Mute.where(account_id: receiver_id, target_account_id: check_for_mutes).any? check_for_blocks = status.mentions.pluck(:account_id) + check_for_blocks.concat([status.account_id]) check_for_blocks.concat([status.reblog.account_id]) if status.reblog? - return true if Block.where(account_id: receiver_id, target_account_id: check_for_blocks).any? + return true if blocks_or_mutes?(receiver_id, check_for_blocks, :home) if status.reply? && !status.in_reply_to_account_id.nil? # Filter out if it's a reply should_filter = !Follow.where(account_id: receiver_id, target_account_id: status.in_reply_to_account_id).exists? # and I'm not following the person it's a reply to @@ -184,11 +189,13 @@ class FeedManager def filter_from_mentions?(status, receiver_id) return true if receiver_id == status.account_id - check_for_blocks = [status.account_id] - check_for_blocks.concat(status.mentions.pluck(:account_id)) + # This filter is called from NotifyService, but already after the sender of + # the notification has been checked for mute/block. Therefore, it's not + # necessary to check the author of the toot for mute/block again + check_for_blocks = status.mentions.pluck(:account_id) check_for_blocks.concat([status.in_reply_to_account]) if status.reply? && !status.in_reply_to_account_id.nil? - should_filter = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).any? # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked + should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted) should_filter ||= (status.account.silenced? && !Follow.where(account_id: receiver_id, target_account_id: status.account_id).exists?) # of if the account is silenced and I'm not following them should_filter ||= keyword_filter?(status, receiver_id) # or if the mention contains a muted keyword diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 4124f1660..050c651ee 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -52,12 +52,8 @@ class Formatter end def simplified_format(account, **options) - html = if account.local? - linkify(account.note) - else - reformat(account.note) - end - html = encode_custom_emojis(html, CustomEmoji.from_text(account.note, account.domain)) if options[:custom_emojify] + html = account.local? ? linkify(account.note) : reformat(account.note) + html = encode_custom_emojis(html, account.emojis) if options[:custom_emojify] html.html_safe # rubocop:disable Rails/OutputSafety end @@ -211,7 +207,7 @@ class Formatter username, domain = acct.split('@') domain = nil if TagManager.instance.local_domain?(domain) - account = Account.find_remote(username, domain) + account = EntityCache.instance.mention(username, domain) account ? mention_html(account) : "@#{acct}" end diff --git a/app/lib/ostatus/activity/creation.rb b/app/lib/ostatus/activity/creation.rb index 6235127b2..1e7f47029 100644 --- a/app/lib/ostatus/activity/creation.rb +++ b/app/lib/ostatus/activity/creation.rb @@ -39,7 +39,8 @@ class OStatus::Activity::Creation < OStatus::Activity::Base reblog: cached_reblog, text: content, spoiler_text: content_warning, - created_at: @options[:override_timestamps] ? nil : published, + created_at: published, + override_timestamps: @options[:override_timestamps], reply: thread?, language: content_language, visibility: visibility_scope, @@ -61,7 +62,14 @@ class OStatus::Activity::Creation < OStatus::Activity::Base Rails.logger.debug "Queuing remote status #{status.id} (#{id}) for distribution" LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text? - DistributionWorker.perform_async(status.id) if @options[:override_timestamps] || status.within_realtime_window? + + # Only continue if the status is supposed to have arrived in real-time. + # Note that if @options[:override_timestamps] isn't set, the status + # may have a lower snowflake id than other existing statuses, potentially + # "hiding" it from paginated API calls + return status unless @options[:override_timestamps] || status.within_realtime_window? + + DistributionWorker.perform_async(status.id) status end diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb index 055b4649c..7c66f2066 100644 --- a/app/lib/ostatus/atom_serializer.rb +++ b/app/lib/ostatus/atom_serializer.rb @@ -364,8 +364,6 @@ class OStatus::AtomSerializer append_element(entry, 'category', nil, term: tag.name) end - append_element(entry, 'category', nil, term: 'nsfw') if status.sensitive? - status.media_attachments.each do |media| append_element(entry, 'link', nil, rel: :enclosure, type: media.file_content_type, length: media.file_file_size, href: full_asset_url(media.file.url(:original, false))) end diff --git a/app/lib/provider_discovery.rb b/app/lib/provider_discovery.rb deleted file mode 100644 index 3bec7211b..000000000 --- a/app/lib/provider_discovery.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -class ProviderDiscovery < OEmbed::ProviderDiscovery - class << self - def get(url, **options) - provider = discover_provider(url, options) - - options.delete(:html) - - provider.get(url, options) - end - - def discover_provider(url, **options) - format = options[:format] - - html = if options[:html] - Nokogiri::HTML(options[:html]) - else - Request.new(:get, url).perform do |res| - raise OEmbed::NotFound, url if res.code != 200 || res.mime_type != 'text/html' - Nokogiri::HTML(res.body_with_limit) - end - end - - if format.nil? || format == :json - provider_endpoint ||= html.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value - format ||= :json if provider_endpoint - end - - if format.nil? || format == :xml - provider_endpoint ||= html.at_xpath('//link[@type="text/xml+oembed"]')&.attribute('href')&.value - format ||= :xml if provider_endpoint - end - - raise OEmbed::NotFound, url if provider_endpoint.nil? - begin - provider_endpoint = Addressable::URI.parse(provider_endpoint) - provider_endpoint.query = nil - provider_endpoint = provider_endpoint.to_s - rescue Addressable::URI::InvalidURIError - raise OEmbed::NotFound, url - end - - OEmbed::Provider.new(provider_endpoint, format) - end - end -end diff --git a/app/lib/request.rb b/app/lib/request.rb index dca93a6e9..00f94dacf 100644 --- a/app/lib/request.rb +++ b/app/lib/request.rb @@ -9,11 +9,15 @@ class Request include RoutingHelper def initialize(verb, url, **options) + raise ArgumentError if url.blank? + @verb = verb @url = Addressable::URI.parse(url).normalize - @options = options.merge(socket_class: Socket) + @options = options.merge(use_proxy? ? Rails.configuration.x.http_client_proxy : { socket_class: Socket }) @headers = {} + raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if block_hidden_service? + set_common_headers! set_digest! if options.key?(:body) end @@ -99,6 +103,14 @@ class Request @http_client ||= HTTP.timeout(:per_operation, timeout).follow(max_hops: 2) end + def use_proxy? + Rails.configuration.x.http_client_proxy.present? + end + + def block_hidden_service? + !Rails.configuration.x.access_to_hidden_service && /\.(onion|i2p)$/.match(@url.host) + end + module ClientLimit def body_with_limit(limit = 1.megabyte) raise Mastodon::LengthValidationError if content_length.present? && content_length > limit @@ -129,6 +141,7 @@ class Request class Socket < TCPSocket class << self def open(host, *args) + return super host, *args if thru_hidden_service? host outer_e = nil Addrinfo.foreach(host, nil, nil, :SOCK_STREAM) do |address| begin @@ -142,6 +155,10 @@ class Request end alias new open + + def thru_hidden_service?(host) + Rails.configuration.x.hidden_service_via_transparent_proxy && /\.(onion|i2p)$/.match(host) + end end end diff --git a/app/lib/rss_builder.rb b/app/lib/rss_builder.rb new file mode 100644 index 000000000..63ddba2e8 --- /dev/null +++ b/app/lib/rss_builder.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +class RSSBuilder + class ItemBuilder + def initialize + @item = Ox::Element.new('item') + end + + def title(str) + @item << (Ox::Element.new('title') << str) + + self + end + + def link(str) + @item << Ox::Element.new('guid').tap do |guid| + guid['isPermalink'] = 'true' + guid << str + end + + @item << (Ox::Element.new('link') << str) + + self + end + + def pub_date(date) + @item << (Ox::Element.new('pubDate') << date.to_formatted_s(:rfc822)) + + self + end + + def description(str) + @item << (Ox::Element.new('description') << str) + + self + end + + def enclosure(url, type, size) + @item << Ox::Element.new('enclosure').tap do |enclosure| + enclosure['url'] = url + enclosure['length'] = size + enclosure['type'] = type + end + + self + end + + def to_element + @item + end + end + + def initialize + @document = Ox::Document.new(version: '1.0') + @channel = Ox::Element.new('channel') + + @document << (rss << @channel) + end + + def title(str) + @channel << (Ox::Element.new('title') << str) + + self + end + + def link(str) + @channel << (Ox::Element.new('link') << str) + + self + end + + def image(str) + @channel << Ox::Element.new('image').tap do |image| + image << (Ox::Element.new('url') << str) + image << (Ox::Element.new('title') << '') + image << (Ox::Element.new('link') << '') + end + + @channel << (Ox::Element.new('webfeeds:icon') << str) + + self + end + + def cover(str) + @channel << Ox::Element.new('webfeeds:cover').tap do |cover| + cover['image'] = str + end + + self + end + + def logo(str) + @channel << (Ox::Element.new('webfeeds:logo') << str) + + self + end + + def accent_color(str) + @channel << (Ox::Element.new('webfeeds:accentColor') << str) + + self + end + + def description(str) + @channel << (Ox::Element.new('description') << str) + + self + end + + def item + @channel << ItemBuilder.new.tap do |item| + yield item + end.to_element + + self + end + + def to_xml + ('<?xml version="1.0" encoding="UTF-8"?>' + Ox.dump(@document, effort: :tolerant)).force_encoding('UTF-8') + end + + private + + def rss + Ox::Element.new('rss').tap do |rss| + rss['version'] = '2.0' + rss['xmlns:webfeeds'] = 'http://webfeeds.org/rss/1.0' + end + end +end diff --git a/app/lib/status_filter.rb b/app/lib/status_filter.rb index 41d4381e5..b6c80b801 100644 --- a/app/lib/status_filter.rb +++ b/app/lib/status_filter.rb @@ -3,9 +3,10 @@ class StatusFilter attr_reader :status, :account - def initialize(status, account) - @status = status - @account = account + def initialize(status, account, preloaded_relations = {}) + @status = status + @account = account + @preloaded_relations = preloaded_relations end def filtered? @@ -24,15 +25,15 @@ class StatusFilter end def blocking_account? - account.blocking? status.account_id + @preloaded_relations[:blocking] ? @preloaded_relations[:blocking][status.account_id] : account.blocking?(status.account_id) end def blocking_domain? - account.domain_blocking? status.account_domain + @preloaded_relations[:domain_blocking_by_domain] ? @preloaded_relations[:domain_blocking_by_domain][status.account_domain] : account.domain_blocking?(status.account_domain) end def muting_account? - account.muting? status.account_id + @preloaded_relations[:muting] ? @preloaded_relations[:muting][status.account_id] : account.muting?(status.account_id) end def silenced_account? @@ -44,7 +45,7 @@ class StatusFilter end def account_following_status_account? - account&.following? status.account_id + @preloaded_relations[:following] ? @preloaded_relations[:following][status.account_id] : account&.following?(status.account_id) end def blocked_by_policy? @@ -52,6 +53,6 @@ class StatusFilter end def policy_allows_show? - StatusPolicy.new(account, status).show? + StatusPolicy.new(account, status, @preloaded_relations).show? end end diff --git a/app/models/account.rb b/app/models/account.rb index db2171102..c1ce1e99e 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -3,7 +3,7 @@ # # Table name: accounts # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # username :string default(""), not null # domain :string # secret :string default(""), not null @@ -42,7 +42,7 @@ # followers_url :string default(""), not null # protocol :integer default("ostatus"), not null # memorial :boolean default(FALSE), not null -# moved_to_account_id :integer +# moved_to_account_id :bigint(8) # featured_collection_url :string # fields :jsonb # @@ -120,6 +120,7 @@ class Account < ApplicationRecord scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) } scope :silenced, -> { where(silenced: true) } scope :suspended, -> { where(suspended: true) } + scope :without_suspended, -> { where(suspended: false) } scope :recent, -> { reorder(id: :desc) } scope :alphabetic, -> { order(domain: :asc, username: :asc) } scope :by_domain_accounts, -> { group(:domain).select(:domain, 'COUNT(*) AS accounts_count').order('accounts_count desc') } @@ -275,6 +276,10 @@ class Account < ApplicationRecord @value = attr['value'] @errors = {} end + + def to_h + { name: @name, value: @value } + end end class << self @@ -393,7 +398,7 @@ class Account < ApplicationRecord end def emojis - CustomEmoji.from_text(note, domain) + @emojis ||= CustomEmoji.from_text(note, domain) end before_create :generate_keys @@ -408,9 +413,9 @@ class Account < ApplicationRecord end def generate_keys - return unless local? + return unless local? && !Rails.env.test? - keypair = OpenSSL::PKey::RSA.new(Rails.env.test? ? 512 : 2048) + keypair = OpenSSL::PKey::RSA.new(2048) self.private_key = keypair.to_pem self.public_key = keypair.public_key.to_pem end diff --git a/app/models/account_domain_block.rb b/app/models/account_domain_block.rb index bc00b4f32..e352000c3 100644 --- a/app/models/account_domain_block.rb +++ b/app/models/account_domain_block.rb @@ -3,11 +3,11 @@ # # Table name: account_domain_blocks # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # domain :string # created_at :datetime not null # updated_at :datetime not null -# account_id :integer +# account_id :bigint(8) # class AccountDomainBlock < ApplicationRecord diff --git a/app/models/account_moderation_note.rb b/app/models/account_moderation_note.rb index 3ac9b1ac1..22e312bb2 100644 --- a/app/models/account_moderation_note.rb +++ b/app/models/account_moderation_note.rb @@ -3,10 +3,10 @@ # # Table name: account_moderation_notes # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # content :text not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint(8) not null +# target_account_id :bigint(8) not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb index 81f278e07..1d1db1b7a 100644 --- a/app/models/admin/action_log.rb +++ b/app/models/admin/action_log.rb @@ -3,11 +3,11 @@ # # Table name: admin_action_logs # -# id :integer not null, primary key -# account_id :integer +# id :bigint(8) not null, primary key +# account_id :bigint(8) # action :string default(""), not null # target_type :string -# target_id :integer +# target_id :bigint(8) # recorded_changes :text default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/backup.rb b/app/models/backup.rb index 5a7e6a14d..c2651313b 100644 --- a/app/models/backup.rb +++ b/app/models/backup.rb @@ -3,8 +3,8 @@ # # Table name: backups # -# id :integer not null, primary key -# user_id :integer +# id :bigint(8) not null, primary key +# user_id :bigint(8) # dump_file_name :string # dump_content_type :string # dump_file_size :integer diff --git a/app/models/block.rb b/app/models/block.rb index d6ecabd3b..df4a6bbac 100644 --- a/app/models/block.rb +++ b/app/models/block.rb @@ -3,11 +3,11 @@ # # Table name: blocks # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint(8) not null +# target_account_id :bigint(8) not null # class Block < ApplicationRecord diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index 3830ba9b0..20fc74ba6 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -20,6 +20,10 @@ module AccountInteractions follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id) end + def blocked_by_map(target_account_ids, account_id) + follow_mapping(Block.where(account_id: target_account_ids, target_account_id: account_id), :account_id) + end + def muting_map(target_account_ids, account_id) Mute.where(target_account_id: target_account_ids, account_id: account_id).each_with_object({}) do |mute, mapping| mapping[mute.target_account_id] = { @@ -38,8 +42,12 @@ module AccountInteractions def domain_blocking_map(target_account_ids, account_id) accounts_map = Account.where(id: target_account_ids).select('id, domain').map { |a| [a.id, a.domain] }.to_h - blocked_domains = AccountDomainBlock.where(account_id: account_id, domain: accounts_map.values).pluck(:domain) - accounts_map.map { |id, domain| [id, blocked_domains.include?(domain)] }.to_h + blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id) + accounts_map.map { |id, domain| [id, blocked_domains[domain]] }.to_h + end + + def domain_blocking_map_by_domain(target_domains, account_id) + follow_mapping(AccountDomainBlock.where(account_id: account_id, domain: target_domains), :domain) end private @@ -93,6 +101,7 @@ module AccountInteractions if mute.hide_notifications? != notifications mute.update!(hide_notifications: notifications) end + mute end def mute_conversation!(conversation) diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb index 90ce88463..6f8489b89 100644 --- a/app/models/concerns/attachmentable.rb +++ b/app/models/concerns/attachmentable.rb @@ -1,10 +1,15 @@ # frozen_string_literal: true +require 'mime/types' + module Attachmentable extend ActiveSupport::Concern + MAX_MATRIX_LIMIT = 16_777_216 # 4096x4096px or approx. 16MB + included do before_post_process :set_file_extensions + before_post_process :check_image_dimensions end private @@ -12,10 +17,31 @@ module Attachmentable def set_file_extensions self.class.attachment_definitions.each_key do |attachment_name| attachment = send(attachment_name) + next if attachment.blank? - extension = Paperclip::Interpolations.content_type_extension(attachment, :original) - basename = Paperclip::Interpolations.basename(attachment, :original) - attachment.instance_write :file_name, [basename, extension].delete_if(&:blank?).join('.') + + attachment.instance_write :file_name, [Paperclip::Interpolations.basename(attachment, :original), appropriate_extension(attachment)].delete_if(&:blank?).join('.') + end + end + + def check_image_dimensions + self.class.attachment_definitions.each_key do |attachment_name| + attachment = send(attachment_name) + + next if attachment.blank? || !attachment.content_type.match?(/image.*/) || attachment.queued_for_write[:original].blank? + + width, height = FastImage.size(attachment.queued_for_write[:original].path) + + raise Mastodon::DimensionsValidationError, "#{width}x#{height} images are not supported" if width.present? && height.present? && (width * height >= MAX_MATRIX_LIMIT) end end + + def appropriate_extension(attachment) + mime_type = MIME::Types[attachment.content_type] + + extensions_for_mime_type = mime_type.empty? ? [] : mime_type.first.extensions + original_extension = Paperclip::Interpolations.extension(attachment, :original) + + extensions_for_mime_type.include?(original_extension) ? original_extension : extensions_for_mime_type.first + end end diff --git a/app/models/concerns/cacheable.rb b/app/models/concerns/cacheable.rb index 51451d260..d7524cdfd 100644 --- a/app/models/concerns/cacheable.rb +++ b/app/models/concerns/cacheable.rb @@ -3,14 +3,19 @@ module Cacheable extend ActiveSupport::Concern - class_methods do + module ClassMethods + @cache_associated = [] + def cache_associated(*associations) @cache_associated = associations end - end - included do - scope :with_includes, -> { includes(@cache_associated) } - scope :cache_ids, -> { select(:id, :updated_at) } + def with_includes + includes(@cache_associated) + end + + def cache_ids + select(:id, :updated_at) + end end end diff --git a/app/models/concerns/remotable.rb b/app/models/concerns/remotable.rb index 3b8c507c3..7f1ef5191 100644 --- a/app/models/concerns/remotable.rb +++ b/app/models/concerns/remotable.rb @@ -38,7 +38,7 @@ module Remotable self[attribute_name] = url if has_attribute?(attribute_name) end - rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError => e + rescue HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}" nil end diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb index fffc095ee..8e817be00 100644 --- a/app/models/concerns/status_threading_concern.rb +++ b/app/models/concerns/status_threading_concern.rb @@ -7,8 +7,8 @@ module StatusThreadingConcern find_statuses_from_tree_path(ancestor_ids(limit), account) end - def descendants(account = nil) - find_statuses_from_tree_path(descendant_ids, account) + def descendants(limit, account = nil, max_child_id = nil, since_child_id = nil, depth = nil) + find_statuses_from_tree_path(descendant_ids(limit, max_child_id, since_child_id, depth), account) end private @@ -46,34 +46,46 @@ module StatusThreadingConcern SQL end - def descendant_ids - descendant_statuses.pluck(:id) + def descendant_ids(limit, max_child_id, since_child_id, depth) + descendant_statuses(limit, max_child_id, since_child_id, depth).pluck(:id) end - def descendant_statuses - Status.find_by_sql([<<-SQL.squish, id: id]) + def descendant_statuses(limit, max_child_id, since_child_id, depth) + Status.find_by_sql([<<-SQL.squish, id: id, limit: limit, max_child_id: max_child_id, since_child_id: since_child_id, depth: depth]) WITH RECURSIVE search_tree(id, path) AS ( SELECT id, ARRAY[id] FROM statuses - WHERE in_reply_to_id = :id + WHERE in_reply_to_id = :id AND COALESCE(id < :max_child_id, TRUE) AND COALESCE(id > :since_child_id, TRUE) UNION ALL SELECT statuses.id, path || statuses.id FROM search_tree JOIN statuses ON statuses.in_reply_to_id = search_tree.id - WHERE NOT statuses.id = ANY(path) + WHERE COALESCE(array_length(path, 1) < :depth, TRUE) AND NOT statuses.id = ANY(path) ) SELECT id FROM search_tree ORDER BY path + LIMIT :limit SQL end def find_statuses_from_tree_path(ids, account) - statuses = statuses_with_accounts(ids).to_a + statuses = statuses_with_accounts(ids).to_a + account_ids = statuses.map(&:account_id).uniq + domains = statuses.map(&:account_domain).compact.uniq + + relations = if account.present? + { + blocking: Account.blocking_map(account_ids, account.id), + blocked_by: Account.blocked_by_map(account_ids, account.id), + muting: Account.muting_map(account_ids, account.id), + following: Account.following_map(account_ids, account.id), + domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id), + } + end - # FIXME: n+1 bonanza - statuses.reject! { |status| filter_from_context?(status, account) } + statuses.reject! { |status| filter_from_context?(status, account, relations) } # Order ancestors/descendants by tree path statuses.sort_by! { |status| ids.index(status.id) } @@ -83,7 +95,7 @@ module StatusThreadingConcern Status.where(id: ids).includes(:account) end - def filter_from_context?(status, account) - StatusFilter.new(status, account).filtered? + def filter_from_context?(status, account, relations) + StatusFilter.new(status, account, relations).filtered? end end diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 08c1ce945..4dfaea889 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -3,7 +3,7 @@ # # Table name: conversations # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # uri :string # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/conversation_mute.rb b/app/models/conversation_mute.rb index 272eb81af..52c1a33e0 100644 --- a/app/models/conversation_mute.rb +++ b/app/models/conversation_mute.rb @@ -3,9 +3,9 @@ # # Table name: conversation_mutes # -# id :integer not null, primary key -# conversation_id :integer not null -# account_id :integer not null +# id :bigint(8) not null, primary key +# conversation_id :bigint(8) not null +# account_id :bigint(8) not null # class ConversationMute < ApplicationRecord diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 1ec21d1a0..b99ed01f0 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -3,7 +3,7 @@ # # Table name: custom_emojis # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # shortcode :string default(""), not null # domain :string # image_file_name :string @@ -40,6 +40,10 @@ class CustomEmoji < ApplicationRecord remotable_attachment :image, LIMIT + include Attachmentable + + after_commit :remove_entity_cache + def local? domain.nil? end @@ -56,11 +60,17 @@ class CustomEmoji < ApplicationRecord return [] if shortcodes.empty? - where(shortcode: shortcodes, domain: domain, disabled: false) + EntityCache.instance.emoji(shortcodes, domain) end def search(shortcode) where('"custom_emojis"."shortcode" ILIKE ?', "%#{shortcode}%") end end + + private + + def remove_entity_cache + Rails.cache.delete(EntityCache.instance.to_key(:emoji, shortcode, domain)) + end end diff --git a/app/models/domain_block.rb b/app/models/domain_block.rb index aea8919af..93658793b 100644 --- a/app/models/domain_block.rb +++ b/app/models/domain_block.rb @@ -3,7 +3,7 @@ # # Table name: domain_blocks # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # domain :string default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/email_domain_block.rb b/app/models/email_domain_block.rb index a104810d1..10490375b 100644 --- a/app/models/email_domain_block.rb +++ b/app/models/email_domain_block.rb @@ -3,7 +3,7 @@ # # Table name: email_domain_blocks # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # domain :string default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/favourite.rb b/app/models/favourite.rb index fa1884b86..c998a67eb 100644 --- a/app/models/favourite.rb +++ b/app/models/favourite.rb @@ -3,11 +3,11 @@ # # Table name: favourites # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# status_id :integer not null +# account_id :bigint(8) not null +# status_id :bigint(8) not null # class Favourite < ApplicationRecord diff --git a/app/models/follow.rb b/app/models/follow.rb index 8e6fe537a..2ca42ff70 100644 --- a/app/models/follow.rb +++ b/app/models/follow.rb @@ -3,11 +3,11 @@ # # Table name: follows # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint(8) not null +# target_account_id :bigint(8) not null # show_reblogs :boolean default(TRUE), not null # diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index cde26ceed..d559a8f62 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -3,11 +3,11 @@ # # Table name: follow_requests # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint(8) not null +# target_account_id :bigint(8) not null # show_reblogs :boolean default(TRUE), not null # diff --git a/app/models/import.rb b/app/models/import.rb index fdb4c6b80..55e970b0d 100644 --- a/app/models/import.rb +++ b/app/models/import.rb @@ -3,7 +3,7 @@ # # Table name: imports # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # type :integer not null # approved :boolean default(FALSE), not null # created_at :datetime not null @@ -12,7 +12,7 @@ # data_content_type :string # data_file_size :integer # data_updated_at :datetime -# account_id :integer not null +# account_id :bigint(8) not null # class Import < ApplicationRecord diff --git a/app/models/invite.rb b/app/models/invite.rb index 4ba5432d2..2250e588e 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -3,8 +3,8 @@ # # Table name: invites # -# id :integer not null, primary key -# user_id :integer not null +# id :bigint(8) not null, primary key +# user_id :bigint(8) not null # code :string default(""), not null # expires_at :datetime # max_uses :integer diff --git a/app/models/list.rb b/app/models/list.rb index a2ec7e84a..c9c94fca1 100644 --- a/app/models/list.rb +++ b/app/models/list.rb @@ -3,8 +3,8 @@ # # Table name: lists # -# id :integer not null, primary key -# account_id :integer not null +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null # title :string default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/list_account.rb b/app/models/list_account.rb index da46cf032..87b498224 100644 --- a/app/models/list_account.rb +++ b/app/models/list_account.rb @@ -3,10 +3,10 @@ # # Table name: list_accounts # -# id :integer not null, primary key -# list_id :integer not null -# account_id :integer not null -# follow_id :integer not null +# id :bigint(8) not null, primary key +# list_id :bigint(8) not null +# account_id :bigint(8) not null +# follow_id :bigint(8) not null # class ListAccount < ApplicationRecord diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 3b16944ce..c041dce51 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -3,8 +3,8 @@ # # Table name: media_attachments # -# id :integer not null, primary key -# status_id :integer +# id :bigint(8) not null, primary key +# status_id :bigint(8) # file_file_name :string # file_content_type :string # file_file_size :integer @@ -15,12 +15,10 @@ # shortcode :string # type :integer default("image"), not null # file_meta :json -# account_id :integer +# account_id :bigint(8) # description :text # -require 'mime/types' - class MediaAttachment < ApplicationRecord self.inheritance_column = nil @@ -90,6 +88,8 @@ class MediaAttachment < ApplicationRecord validates_attachment_size :file, less_than: LIMIT remotable_attachment :file, LIMIT + include Attachmentable + validates :account, presence: true validates :description, length: { maximum: 420 }, if: :local? @@ -200,9 +200,6 @@ class MediaAttachment < ApplicationRecord def set_type_and_extension self.type = VIDEO_MIME_TYPES.include?(file_content_type) ? :video : AUDIO_MIME_TYPES.include?(file_content_type) ? :audio : :image - extension = AUDIO_MIME_TYPES.include?(file_content_type) ? '.mp4' : appropriate_extension - basename = Paperclip::Interpolations.basename(file, :original) - file.instance_write :file_name, [basename, extension].delete_if(&:blank?).join('.') end def set_meta @@ -247,13 +244,4 @@ class MediaAttachment < ApplicationRecord bitrate: movie.bitrate, } end - - def appropriate_extension - mime_type = MIME::Types[file.content_type] - - extensions_for_mime_type = mime_type.empty? ? [] : mime_type.first.extensions - original_extension = Paperclip::Interpolations.extension(file, :original) - - extensions_for_mime_type.include?(original_extension) ? original_extension : extensions_for_mime_type.first - end end diff --git a/app/models/mention.rb b/app/models/mention.rb index f864bf8e1..8ab886b18 100644 --- a/app/models/mention.rb +++ b/app/models/mention.rb @@ -3,11 +3,11 @@ # # Table name: mentions # -# id :integer not null, primary key -# status_id :integer +# id :bigint(8) not null, primary key +# status_id :bigint(8) # created_at :datetime not null # updated_at :datetime not null -# account_id :integer +# account_id :bigint(8) # class Mention < ApplicationRecord diff --git a/app/models/mute.rb b/app/models/mute.rb index ebb3818c7..639120f7d 100644 --- a/app/models/mute.rb +++ b/app/models/mute.rb @@ -3,12 +3,12 @@ # # Table name: mutes # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # created_at :datetime not null # updated_at :datetime not null # hide_notifications :boolean default(TRUE), not null -# account_id :integer not null -# target_account_id :integer not null +# account_id :bigint(8) not null +# target_account_id :bigint(8) not null # class Mute < ApplicationRecord diff --git a/app/models/notification.rb b/app/models/notification.rb index 0b0f01aa8..4f6ec8e8e 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -3,13 +3,13 @@ # # Table name: notifications # -# id :integer not null, primary key -# activity_id :integer not null +# id :bigint(8) not null, primary key +# activity_id :bigint(8) not null # activity_type :string not null # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# from_account_id :integer not null +# account_id :bigint(8) not null +# from_account_id :bigint(8) not null # class Notification < ApplicationRecord diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 0c82f06ce..a792b352b 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -3,7 +3,7 @@ # # Table name: preview_cards # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # url :string default(""), not null # title :string default(""), not null # description :string default(""), not null @@ -34,7 +34,7 @@ class PreviewCard < ApplicationRecord has_and_belongs_to_many :statuses - has_attached_file :image, styles: { original: { geometry: '400x400>', file_geometry_parser: FastGeometryParser } }, convert_options: { all: '-quality 80 -strip' } + has_attached_file :image, styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 80 -strip' } include Attachmentable @@ -52,6 +52,23 @@ class PreviewCard < ApplicationRecord save! end + class << self + private + + def image_styles(f) + styles = { + original: { + geometry: '400x400>', + file_geometry_parser: FastGeometryParser, + convert_options: '-coalesce -strip', + }, + } + + styles[:original][:format] = 'jpg' if f.instance.image_content_type == 'image/gif' + styles + end + end + private def extract_dimensions diff --git a/app/models/report.rb b/app/models/report.rb index 5b90c7bce..efe385b2d 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -3,16 +3,16 @@ # # Table name: reports # -# id :integer not null, primary key -# status_ids :integer default([]), not null, is an Array +# id :bigint(8) not null, primary key +# status_ids :bigint(8) default([]), not null, is an Array # comment :text default(""), not null # action_taken :boolean default(FALSE), not null # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# action_taken_by_account_id :integer -# target_account_id :integer not null -# assigned_account_id :integer +# account_id :bigint(8) not null +# action_taken_by_account_id :bigint(8) +# target_account_id :bigint(8) not null +# assigned_account_id :bigint(8) # class Report < ApplicationRecord diff --git a/app/models/report_note.rb b/app/models/report_note.rb index 6d9dec80a..54b416577 100644 --- a/app/models/report_note.rb +++ b/app/models/report_note.rb @@ -3,10 +3,10 @@ # # Table name: report_notes # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # content :text not null -# report_id :integer not null -# account_id :integer not null +# report_id :bigint(8) not null +# account_id :bigint(8) not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb index d364f03df..34d25c83d 100644 --- a/app/models/session_activation.rb +++ b/app/models/session_activation.rb @@ -3,15 +3,15 @@ # # Table name: session_activations # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # session_id :string not null # created_at :datetime not null # updated_at :datetime not null # user_agent :string default(""), not null # ip :inet -# access_token_id :integer -# user_id :integer not null -# web_push_subscription_id :integer +# access_token_id :bigint(8) +# user_id :bigint(8) not null +# web_push_subscription_id :bigint(8) # class SessionActivation < ApplicationRecord diff --git a/app/models/setting.rb b/app/models/setting.rb index df93590ce..033d09fd5 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -3,13 +3,13 @@ # # Table name: settings # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # var :string not null # value :text # thing_type :string # created_at :datetime # updated_at :datetime -# thing_id :integer +# thing_id :bigint(8) # class Setting < RailsSettings::Base diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb index 641128adf..14d683767 100644 --- a/app/models/site_upload.rb +++ b/app/models/site_upload.rb @@ -3,7 +3,7 @@ # # Table name: site_uploads # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # var :string default(""), not null # file_file_name :string # file_content_type :string diff --git a/app/models/status.rb b/app/models/status.rb index 952661169..0b3a7c0aa 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -3,13 +3,13 @@ # # Table name: statuses # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # uri :string # text :text default(""), not null # created_at :datetime not null # updated_at :datetime not null -# in_reply_to_id :integer -# reblog_of_id :integer +# in_reply_to_id :bigint(8) +# reblog_of_id :bigint(8) # url :string # sensitive :boolean default(FALSE), not null # visibility :integer default("public"), not null @@ -18,11 +18,11 @@ # favourites_count :integer default(0), not null # reblogs_count :integer default(0), not null # language :string -# conversation_id :integer +# conversation_id :bigint(8) # local :boolean -# account_id :integer not null -# application_id :integer -# in_reply_to_account_id :integer +# account_id :bigint(8) not null +# application_id :bigint(8) +# in_reply_to_account_id :bigint(8) # local_only :boolean # full_status_text :text default(""), not null # @@ -33,6 +33,10 @@ class Status < ApplicationRecord include Cacheable include StatusThreadingConcern + # If `override_timestamps` is set at creation time, Snowflake ID creation + # will be based on current time instead of `created_at` + attr_accessor :override_timestamps + update_index('statuses#status', :proper) if Chewy.enabled? enum visibility: [:public, :unlisted, :private, :direct], _suffix: :visibility @@ -62,6 +66,7 @@ class Status < ApplicationRecord validates :uri, uniqueness: true, presence: true, unless: :local? validates :text, presence: true, unless: -> { with_media? || reblog? } validates_with StatusLengthValidator + validates_with DisallowedHashtagsValidator validates :reblog, uniqueness: { scope: :account }, if: :reblog? default_scope { recent } @@ -164,7 +169,7 @@ class Status < ApplicationRecord end def emojis - CustomEmoji.from_text([spoiler_text, text].join(' '), account.domain) + @emojis ||= CustomEmoji.from_text([spoiler_text, text].join(' '), account.domain) end after_create_commit :store_uri, if: :local? diff --git a/app/models/status_pin.rb b/app/models/status_pin.rb index d3a98d8bd..afc76bded 100644 --- a/app/models/status_pin.rb +++ b/app/models/status_pin.rb @@ -3,9 +3,9 @@ # # Table name: status_pins # -# id :integer not null, primary key -# account_id :integer not null -# status_id :integer not null +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# status_id :bigint(8) not null # created_at :datetime not null # updated_at :datetime not null # diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb index 36fe487dc..dd383eb81 100644 --- a/app/models/stream_entry.rb +++ b/app/models/stream_entry.rb @@ -3,13 +3,13 @@ # # Table name: stream_entries # -# id :integer not null, primary key -# activity_id :integer +# id :bigint(8) not null, primary key +# activity_id :bigint(8) # activity_type :string # created_at :datetime not null # updated_at :datetime not null # hidden :boolean default(FALSE), not null -# account_id :integer +# account_id :bigint(8) # class StreamEntry < ApplicationRecord diff --git a/app/models/subscription.rb b/app/models/subscription.rb index ea1173160..79b81828d 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -3,7 +3,7 @@ # # Table name: subscriptions # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # callback_url :string default(""), not null # secret :string # expires_at :datetime @@ -12,7 +12,7 @@ # updated_at :datetime not null # last_successful_delivery_at :datetime # domain :string -# account_id :integer not null +# account_id :bigint(8) not null # class Subscription < ApplicationRecord diff --git a/app/models/tag.rb b/app/models/tag.rb index 9fa9405d7..8b1b02412 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -3,7 +3,7 @@ # # Table name: tags # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # name :string default(""), not null # created_at :datetime not null # updated_at :datetime not null diff --git a/app/models/user.rb b/app/models/user.rb index 803eb8a33..24beb77b2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -3,7 +3,7 @@ # # Table name: users # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # email :string default(""), not null # created_at :datetime not null # updated_at :datetime not null @@ -30,10 +30,10 @@ # last_emailed_at :datetime # otp_backup_codes :string is an Array # filtered_languages :string default([]), not null, is an Array -# account_id :integer not null +# account_id :bigint(8) not null # disabled :boolean default(FALSE), not null # moderator :boolean default(FALSE), not null -# invite_id :integer +# invite_id :bigint(8) # remember_token :string # diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb index 5aee92d27..1736106f7 100644 --- a/app/models/web/push_subscription.rb +++ b/app/models/web/push_subscription.rb @@ -3,7 +3,7 @@ # # Table name: web_push_subscriptions # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # endpoint :string not null # key_p256dh :string not null # key_auth :string not null diff --git a/app/models/web/setting.rb b/app/models/web/setting.rb index 0a5129d17..99588d26c 100644 --- a/app/models/web/setting.rb +++ b/app/models/web/setting.rb @@ -3,11 +3,11 @@ # # Table name: web_settings # -# id :integer not null, primary key +# id :bigint(8) not null, primary key # data :json # created_at :datetime not null # updated_at :datetime not null -# user_id :integer not null +# user_id :bigint(8) not null # class Web::Setting < ApplicationRecord diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb index 307876856..96cdee8c7 100644 --- a/app/policies/status_policy.rb +++ b/app/policies/status_policy.rb @@ -1,6 +1,12 @@ # frozen_string_literal: true class StatusPolicy < ApplicationPolicy + def initialize(current_account, record, preloaded_relations = {}) + super(current_account, record) + + @preloaded_relations = preloaded_relations + end + def index? staff? end @@ -9,16 +15,20 @@ class StatusPolicy < ApplicationPolicy return false if local_only? && current_account.nil? if direct? - owned? || record.mentions.where(account: current_account).exists? + owned? || mention_exists? elsif private? - owned? || current_account&.following?(author) || record.mentions.where(account: current_account).exists? + owned? || following_author? || mention_exists? else - current_account.nil? || !author.blocking?(current_account) + current_account.nil? || !author_blocking? end end def reblog? - !direct? && (!private? || owned?) && show? + !direct? && (!private? || owned?) && show? && !blocking_author? + end + + def favourite? + show? && !blocking_author? end def destroy? @@ -45,6 +55,34 @@ class StatusPolicy < ApplicationPolicy record.private_visibility? end + def mention_exists? + return false if current_account.nil? + + if record.mentions.loaded? + record.mentions.any? { |mention| mention.account_id == current_account.id } + else + record.mentions.where(account: current_account).exists? + end + end + + def blocking_author? + return false if current_account.nil? + + @preloaded_relations[:blocking] ? @preloaded_relations[:blocking][author.id] : current_account.blocking?(author) + end + + def author_blocking? + return false if current_account.nil? + + @preloaded_relations[:blocked_by] ? @preloaded_relations[:blocked_by][author.id] : author.blocking?(current_account) + end + + def following_author? + return false if current_account.nil? + + @preloaded_relations[:following] ? @preloaded_relations[:following][author.id] : current_account.following?(author) + end + def author record.account end diff --git a/app/serializers/rest/credential_account_serializer.rb b/app/serializers/rest/credential_account_serializer.rb index 870d8b71f..56857cba8 100644 --- a/app/serializers/rest/credential_account_serializer.rb +++ b/app/serializers/rest/credential_account_serializer.rb @@ -5,10 +5,12 @@ class REST::CredentialAccountSerializer < REST::AccountSerializer def source user = object.user + { privacy: user.setting_default_privacy, sensitive: user.setting_default_sensitive, note: object.note, + fields: object.fields.map(&:to_h), } end end diff --git a/app/serializers/rss/account_serializer.rb b/app/serializers/rss/account_serializer.rb new file mode 100644 index 000000000..bde360a41 --- /dev/null +++ b/app/serializers/rss/account_serializer.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class RSS::AccountSerializer + include ActionView::Helpers::NumberHelper + include StreamEntriesHelper + include RoutingHelper + + def render(account, statuses) + builder = RSSBuilder.new + + builder.title("#{display_name(account)} (@#{account.local_username_and_domain})") + .description(account_description(account)) + .link(TagManager.instance.url_for(account)) + .logo(full_asset_url(asset_pack_path('logo.svg'))) + .accent_color('2b90d9') + + builder.image(full_asset_url(account.avatar.url(:original))) if account.avatar? + builder.cover(full_asset_url(account.header.url(:original))) if account.header? + + statuses.each do |status| + builder.item do |item| + item.title(status.title) + .link(TagManager.instance.url_for(status)) + .pub_date(status.created_at) + .description(status.spoiler_text.presence || Formatter.instance.format(status).to_str) + + status.media_attachments.each do |media| + item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, length: media.file.size) + end + end + end + + builder.to_xml + end + + def self.render(account, statuses) + new.render(account, statuses) + end +end diff --git a/app/serializers/rss/tag_serializer.rb b/app/serializers/rss/tag_serializer.rb new file mode 100644 index 000000000..7680a8da5 --- /dev/null +++ b/app/serializers/rss/tag_serializer.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class RSS::TagSerializer + include ActionView::Helpers::NumberHelper + include ActionView::Helpers::SanitizeHelper + include StreamEntriesHelper + include RoutingHelper + + def render(tag, statuses) + builder = RSSBuilder.new + + builder.title("##{tag.name}") + .description(strip_tags(I18n.t('about.about_hashtag_html', hashtag: tag.name))) + .link(tag_url(tag)) + .logo(full_asset_url(asset_pack_path('logo.svg'))) + .accent_color('2b90d9') + + statuses.each do |status| + builder.item do |item| + item.title(status.title) + .link(TagManager.instance.url_for(status)) + .pub_date(status.created_at) + .description(status.spoiler_text.presence || Formatter.instance.format(status).to_str) + + status.media_attachments.each do |media| + item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, length: media.file.size) + end + end + end + + builder.to_xml + end + + def self.render(tag, statuses) + new.render(tag, statuses) + end +end diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index 3860a9cbd..7edbd9b47 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -65,9 +65,9 @@ class AccountSearchService < BaseService def exact_match @_exact_match ||= begin if domain_is_local? - search_from.find_local(query_username) + search_from.without_suspended.find_local(query_username) else - search_from.find_remote(query_username, query_domain) + search_from.without_suspended.find_remote(query_username, query_domain) end end end diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb index 40714e980..6a137b520 100644 --- a/app/services/activitypub/fetch_featured_collection_service.rb +++ b/app/services/activitypub/fetch_featured_collection_service.rb @@ -4,6 +4,8 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService include JsonLdHelper def call(account) + return if account.featured_collection_url.blank? + @account = account @json = fetch_resource(@account.featured_collection_url, true) diff --git a/app/services/activitypub/fetch_remote_account_service.rb b/app/services/activitypub/fetch_remote_account_service.rb index 5024853ca..867e70876 100644 --- a/app/services/activitypub/fetch_remote_account_service.rb +++ b/app/services/activitypub/fetch_remote_account_service.rb @@ -56,6 +56,6 @@ class ActivityPub::FetchRemoteAccountService < BaseService end def expected_type? - SUPPORTED_TYPES.include?(@json['type']) + equals_or_includes_any?(@json['type'], SUPPORTED_TYPES) end end diff --git a/app/services/activitypub/fetch_remote_key_service.rb b/app/services/activitypub/fetch_remote_key_service.rb index 41837d462..505baccd4 100644 --- a/app/services/activitypub/fetch_remote_key_service.rb +++ b/app/services/activitypub/fetch_remote_key_service.rb @@ -43,7 +43,7 @@ class ActivityPub::FetchRemoteKeyService < BaseService end def person? - ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(@json['type']) + equals_or_includes_any?(@json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) end def public_key? @@ -55,6 +55,6 @@ class ActivityPub::FetchRemoteKeyService < BaseService end def confirmed_owner? - ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(@owner['type']) && value_or_id(@owner['publicKey']) == @json['id'] + equals_or_includes_any?(@owner['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) && value_or_id(@owner['publicKey']) == @json['id'] end end diff --git a/app/services/activitypub/fetch_remote_status_service.rb b/app/services/activitypub/fetch_remote_status_service.rb index 503c175d8..930fbad1f 100644 --- a/app/services/activitypub/fetch_remote_status_service.rb +++ b/app/services/activitypub/fetch_remote_status_service.rb @@ -42,7 +42,7 @@ class ActivityPub::FetchRemoteStatusService < BaseService end def expected_type? - (ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES).include? @json['type'] + equals_or_includes_any?(@json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES) end def needs_update(actor) diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index da32f9615..f67ebb443 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -201,10 +201,7 @@ class ActivityPub::ProcessAccountService < BaseService return if @json['tag'].blank? as_array(@json['tag']).each do |tag| - case tag['type'] - when 'Emoji' - process_emoji tag - end + process_emoji tag if equals_or_includes?(tag['type'], 'Emoji') end end diff --git a/app/services/fan_out_on_write_service.rb b/app/services/fan_out_on_write_service.rb index 0f77556dc..510b80c82 100644 --- a/app/services/fan_out_on_write_service.rb +++ b/app/services/fan_out_on_write_service.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'sidekiq-bulk' - class FanOutOnWriteService < BaseService # Push a status into home and mentions feeds # @param [Status] status diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb index 44df3ed13..bc2d1547a 100644 --- a/app/services/favourite_service.rb +++ b/app/services/favourite_service.rb @@ -8,7 +8,7 @@ class FavouriteService < BaseService # @param [Status] status # @return [Favourite] def call(account, status) - authorize_with account, status, :show? + authorize_with account, status, :favourite? favourite = Favourite.find_by(account: account, status: status) diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb index 0444baf74..550e75f33 100644 --- a/app/services/fetch_atom_service.rb +++ b/app/services/fetch_atom_service.rb @@ -42,7 +42,7 @@ class FetchAtomService < BaseService elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type) body = response.body_with_limit json = body_to_json(body) - if supported_context?(json) && ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(json['type']) && json['inbox'].present? + if supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) && json['inbox'].present? [json['id'], { prefetched_body: body, id: true }, :activitypub] elsif supported_context?(json) && expected_type?(json) [json['id'], { prefetched_body: body, id: true }, :activitypub] @@ -62,7 +62,7 @@ class FetchAtomService < BaseService end def expected_type?(json) - (ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES).include? json['type'] + equals_or_includes_any?(json['type'], ActivityPub::Activity::Create::SUPPORTED_TYPES + ActivityPub::Activity::Create::CONVERTED_TYPES) end def process_html(response) diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index d5920a417..77d4aa538 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -85,42 +85,40 @@ class FetchLinkCardService < BaseService end def attempt_oembed - embed = OEmbed::Providers.get(@url, html: @html) + embed = FetchOEmbedService.new.call(@url, html: @html) - return false unless embed.respond_to?(:type) + return false if embed.nil? - @card.type = embed.type - @card.title = embed.respond_to?(:title) ? embed.title : '' - @card.author_name = embed.respond_to?(:author_name) ? embed.author_name : '' - @card.author_url = embed.respond_to?(:author_url) ? embed.author_url : '' - @card.provider_name = embed.respond_to?(:provider_name) ? embed.provider_name : '' - @card.provider_url = embed.respond_to?(:provider_url) ? embed.provider_url : '' + @card.type = embed[:type] + @card.title = embed[:title] || '' + @card.author_name = embed[:author_name] || '' + @card.author_url = embed[:author_url] || '' + @card.provider_name = embed[:provider_name] || '' + @card.provider_url = embed[:provider_url] || '' @card.width = 0 @card.height = 0 case @card.type when 'link' - @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url) + @card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present? when 'photo' - return false unless embed.respond_to?(:url) + return false if embed[:url].blank? - @card.embed_url = embed.url - @card.image_remote_url = embed.url - @card.width = embed.width.presence || 0 - @card.height = embed.height.presence || 0 + @card.embed_url = embed[:url] + @card.image_remote_url = embed[:url] + @card.width = embed[:width].presence || 0 + @card.height = embed[:height].presence || 0 when 'video' - @card.width = embed.width.presence || 0 - @card.height = embed.height.presence || 0 - @card.html = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED) - @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url) + @card.width = embed[:width].presence || 0 + @card.height = embed[:height].presence || 0 + @card.html = Formatter.instance.sanitize(embed[:html], Sanitize::Config::MASTODON_OEMBED) + @card.image_remote_url = embed[:thumbnail_url] if embed[:thumbnail_url].present? when 'rich' # Most providers rely on <script> tags, which is a no-no return false end @card.save_with_optional_image! - rescue OEmbed::NotFound - false end def attempt_opengraph diff --git a/app/services/fetch_oembed_service.rb b/app/services/fetch_oembed_service.rb new file mode 100644 index 000000000..998228517 --- /dev/null +++ b/app/services/fetch_oembed_service.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +class FetchOEmbedService + attr_reader :url, :options, :format, :endpoint_url + + def call(url, options = {}) + @url = url + @options = options + + discover_endpoint! + fetch! + end + + private + + def discover_endpoint! + return if html.nil? + + @format = @options[:format] + page = Nokogiri::HTML(html) + + if @format.nil? || @format == :json + @endpoint_url ||= page.at_xpath('//link[@type="application/json+oembed"]')&.attribute('href')&.value + @format ||= :json if @endpoint_url + end + + if @format.nil? || @format == :xml + @endpoint_url ||= page.at_xpath('//link[@type="text/xml+oembed"]')&.attribute('href')&.value + @format ||= :xml if @endpoint_url + end + + return if @endpoint_url.blank? + + @endpoint_url = Addressable::URI.parse(@endpoint_url).to_s + rescue Addressable::URI::InvalidURIError + @endpoint_url = nil + end + + def fetch! + return if @endpoint_url.blank? + + body = Request.new(:get, @endpoint_url).perform do |res| + res.code != 200 ? nil : res.body_with_limit + end + + validate(parse_for_format(body)) unless body.nil? + rescue Oj::ParseError, Ox::ParseError + nil + end + + def parse_for_format(body) + case @format + when :json + Oj.load(body, mode: :strict)&.with_indifferent_access + when :xml + Ox.load(body, mode: :hash_no_attrs)&.with_indifferent_access&.dig(:oembed) + end + end + + def validate(oembed) + oembed if oembed[:version] == '1.0' && oembed[:type].present? + end + + def html + return @html if defined?(@html) + + @html = @options[:html] || Request.new(:get, @url).perform do |res| + res.code != 200 || res.mime_type != 'text/html' ? nil : res.body_with_limit + end + end +end diff --git a/app/services/mute_service.rb b/app/services/mute_service.rb index 547b2efa1..c6122a152 100644 --- a/app/services/mute_service.rb +++ b/app/services/mute_service.rb @@ -3,8 +3,13 @@ class MuteService < BaseService def call(account, target_account, notifications: nil) return if account.id == target_account.id + mute = account.mute!(target_account, notifications: notifications) - BlockWorker.perform_async(account.id, target_account.id) + if mute.hide_notifications? + BlockWorker.perform_async(account.id, target_account.id) + else + FeedManager.instance.clear_from_timeline(account, target_account) + end mute end end diff --git a/app/services/process_hashtags_service.rb b/app/services/process_hashtags_service.rb index 990e01a4b..5b45c865f 100644 --- a/app/services/process_hashtags_service.rb +++ b/app/services/process_hashtags_service.rb @@ -7,7 +7,5 @@ class ProcessHashtagsService < BaseService tags.map { |str| str.mb_chars.downcase }.uniq(&:to_s).each do |tag| status.tags << Tag.where(name: tag).first_or_initialize(name: tag) end - - status.update(sensitive: true) if tags.include?('nsfw') end end diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index dc8df4a9a..2ed6698cf 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -10,55 +10,61 @@ class ProcessMentionsService < BaseService def call(status) return unless status.local? + @status = status + mentions = [] + status.text = status.text.gsub(Account::MENTION_RE) do |match| - username, domain = $1.split('@') + username, domain = Regexp.last_match(1).split('@') mentioned_account = Account.find_remote(username, domain) - if mention_undeliverable?(status, mentioned_account) + if mention_undeliverable?(mentioned_account) begin - mentioned_account = resolve_account_service.call($1) + mentioned_account = resolve_account_service.call(Regexp.last_match(1)) rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::UnexpectedResponseError mentioned_account = nil end end - next match if mention_undeliverable?(status, mentioned_account) + next match if mention_undeliverable?(mentioned_account) + + mentions << mentioned_account.mentions.where(status: status).first_or_create(status: status) - mentioned_account.mentions.where(status: status).first_or_create(status: status) "@#{mentioned_account.acct}" end status.save! - status.mentions.includes(:account).each do |mention| - create_notification(status, mention) - end + mentions.each { |mention| create_notification(mention) } end private - def mention_undeliverable?(status, mentioned_account) - mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus? && status.stream_entry.hidden?) + def mention_undeliverable?(mentioned_account) + mentioned_account.nil? || (!mentioned_account.local? && mentioned_account.ostatus? && @status.stream_entry.hidden?) end - def create_notification(status, mention) + def create_notification(mention) mentioned_account = mention.account if mentioned_account.local? - NotifyService.new.call(mentioned_account, mention) - elsif mentioned_account.ostatus? && !status.stream_entry.hidden? - NotificationWorker.perform_async(stream_entry_to_xml(status.stream_entry), status.account_id, mentioned_account.id) + LocalNotificationWorker.perform_async(mention.id) + elsif mentioned_account.ostatus? && !@status.stream_entry.hidden? + NotificationWorker.perform_async(ostatus_xml, @status.account_id, mentioned_account.id) elsif mentioned_account.activitypub? - ActivityPub::DeliveryWorker.perform_async(build_json(mention.status), mention.status.account_id, mentioned_account.inbox_url) + ActivityPub::DeliveryWorker.perform_async(activitypub_json, mention.status.account_id, mentioned_account.inbox_url) end end - def build_json(status) - Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new( - status, + def ostatus_xml + @ostatus_xml ||= stream_entry_to_xml(@status.stream_entry) + end + + def activitypub_json + @activitypub_json ||= Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new( + @status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter - ).as_json).sign!(status.account)) + ).as_json).sign!(@status.account)) end def resolve_account_service diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb index 8cba88f01..de8d1151d 100644 --- a/app/services/resolve_account_service.rb +++ b/app/services/resolve_account_service.rb @@ -189,7 +189,7 @@ class ResolveAccountService < BaseService return @actor_json if defined?(@actor_json) json = fetch_resource(actor_url, false) - @actor_json = supported_context?(json) && ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(json['type']) ? json : nil + @actor_json = supported_context?(json) && equals_or_includes_any?(json['type'], ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES) ? json : nil end def atom diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb index c19b568cb..a068c1ed8 100644 --- a/app/services/resolve_url_service.rb +++ b/app/services/resolve_url_service.rb @@ -16,10 +16,9 @@ class ResolveURLService < BaseService private def process_url - case type - when 'Application', 'Group', 'Organization', 'Person', 'Service' + if equals_or_includes_any?(type, %w(Application Group Organization Person Service)) FetchRemoteAccountService.new.call(atom_url, body, protocol) - when 'Note', 'Article', 'Image', 'Video' + elsif equals_or_includes_any?(type, %w(Note Article Image Video)) FetchRemoteStatusService.new.call(atom_url, body, protocol) end end diff --git a/app/validators/disallowed_hashtags_validator.rb b/app/validators/disallowed_hashtags_validator.rb new file mode 100644 index 000000000..22c027b0f --- /dev/null +++ b/app/validators/disallowed_hashtags_validator.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class DisallowedHashtagsValidator < ActiveModel::Validator + def validate(status) + return unless status.local? && !status.reblog? + + tags = Extractor.extract_hashtags(status.text) + tags.keep_if { |tag| disallowed_hashtags.include? tag.downcase } + + status.errors.add(:text, I18n.t('statuses.disallowed_hashtags', tags: tags.join(', '), count: tags.size)) unless tags.empty? + end + + private + + def disallowed_hashtags + return @disallowed_hashtags if @disallowed_hashtags + + @disallowed_hashtags = Setting.disallowed_hashtags.nil? ? [] : Setting.disallowed_hashtags + @disallowed_hashtags = @disallowed_hashtags.split(' ') if @disallowed_hashtags.is_a? String + @disallowed_hashtags = @disallowed_hashtags.map(&:downcase) + end +end diff --git a/app/views/admin/action_logs/_action_log.html.haml b/app/views/admin/action_logs/_action_log.html.haml index ec90961cb..f059814bd 100644 --- a/app/views/admin/action_logs/_action_log.html.haml +++ b/app/views/admin/action_logs/_action_log.html.haml @@ -1,4 +1,4 @@ -%li.log-entry +.log-entry .log-entry__header .log-entry__avatar = image_tag action_log.account.avatar.url(:original), alt: '', width: 40, height: 40, class: 'avatar' diff --git a/app/views/admin/action_logs/index.html.haml b/app/views/admin/action_logs/index.html.haml index bb6d7b5d7..a4d3871a9 100644 --- a/app/views/admin/action_logs/index.html.haml +++ b/app/views/admin/action_logs/index.html.haml @@ -1,7 +1,6 @@ - content_for :page_title do = t('admin.action_logs.title') -%ul - = render @action_logs += render @action_logs = paginate @action_logs diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml index 1f621e0d3..d34dc3d15 100644 --- a/app/views/admin/report_notes/_report_note.html.haml +++ b/app/views/admin/report_notes/_report_note.html.haml @@ -1,9 +1,7 @@ -%li - %h4 - = report_note.account.acct - %div{ style: 'float: right' } - %time.formatted{ datetime: report_note.created_at.iso8601, title: l(report_note.created_at) } - = l report_note.created_at - = table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete if can?(:destroy, report_note) - %div{ class: 'report-note__comment' } +.speech-bubble + .speech-bubble__bubble = simple_format(h(report_note.content)) + .speech-bubble__owner + = admin_account_link_to report_note.account + %time.formatted{ datetime: report_note.created_at.iso8601 }= l report_note.created_at + = table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete if can?(:destroy, report_note) diff --git a/app/views/admin/reports/_account.html.haml b/app/views/admin/reports/_account.html.haml new file mode 100644 index 000000000..22b7a0861 --- /dev/null +++ b/app/views/admin/reports/_account.html.haml @@ -0,0 +1,19 @@ +- size ||= 36 + +.account.compact + .account__wrapper + - if account.nil? + .account__display-name + .account__avatar-wrapper + .account__avatar{ style: "background-image: url(#{full_asset_url('avatars/original/missing.png', skip_pipeline: true)}); width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px" } + %span.display-name + %strong= t 'about.contact_missing' + %span.display-name__account= t 'about.contact_unavailable' + - else + = link_to TagManager.instance.url_for(account), class: 'account__display-name' do + .account__avatar-wrapper + .account__avatar{ style: "background-image: url(#{account.avatar.url}); width: #{size}px; height: #{size}px; background-size: #{size}px #{size}px" } + %span.display-name + %bdi + %strong.display-name__html.emojify= display_name(account) + %span.display-name__account @#{account.acct} diff --git a/app/views/admin/reports/_account_details.html.haml b/app/views/admin/reports/_account_details.html.haml deleted file mode 100644 index a8af39bef..000000000 --- a/app/views/admin/reports/_account_details.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -.table-wrapper - %table.table - %tbody - %tr - %td= t('admin.reports.account.created_reports') - %td= link_to pluralize(account.reports.count, t('admin.reports.account.report')), admin_reports_path(account_id: account.id) - %tr - %td= t('admin.reports.account.targeted_reports') - %td= link_to pluralize(account.targeted_reports.count, t('admin.reports.account.report')), admin_reports_path(target_account_id: account.id) - %tr - %td= t('admin.reports.account.moderation_notes') - %td= link_to pluralize(account.targeted_moderation_notes.count, t('admin.reports.account.note')), admin_reports_path(target_account_id: account.id) - - if account.silenced? || account.suspended? - %tr - %td= t('admin.reports.account.moderation.title') - %td - - if account.silenced? - %p= t('admin.reports.account.moderation.silenced') - - if account.suspended? - %p= t('admin.reports.account.moderation.suspended') diff --git a/app/views/admin/reports/_action_log.html.haml b/app/views/admin/reports/_action_log.html.haml new file mode 100644 index 000000000..024078eb9 --- /dev/null +++ b/app/views/admin/reports/_action_log.html.haml @@ -0,0 +1,6 @@ +.speech-bubble.positive + .speech-bubble__bubble + = t("admin.action_logs.actions.#{action_log.action}_#{action_log.target_type.underscore}", name: content_tag(:span, action_log.account.username, class: 'username'), target: content_tag(:span, log_target(action_log), class: 'target')).html_safe + .speech-bubble__owner + = admin_account_link_to(action_log.account) + %time.formatted{ datetime: action_log.created_at.iso8601 }= l action_log.created_at diff --git a/app/views/admin/reports/_report.html.haml b/app/views/admin/reports/_report.html.haml index 84db00ad5..d6c881955 100644 --- a/app/views/admin/reports/_report.html.haml +++ b/app/views/admin/reports/_report.html.haml @@ -2,9 +2,9 @@ %td.id = "##{report.id}" %td.target - = link_to report.target_account.acct, admin_account_path(report.target_account.id) + = admin_account_link_to report.target_account %td.reporter - = link_to report.account.acct, admin_account_path(report.account.id) + = admin_account_link_to report.account %td %div{ title: report.comment } = truncate(report.comment, length: 30, separator: ' ') @@ -21,6 +21,6 @@ - if report.assigned_account.nil? \- - else - = link_to report.assigned_account.acct, admin_account_path(report.assigned_account.id) + = admin_account_link_to report.assigned_account %td = table_link_to 'circle', t('admin.reports.view'), admin_report_path(report) diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml new file mode 100644 index 000000000..137609539 --- /dev/null +++ b/app/views/admin/reports/_status.html.haml @@ -0,0 +1,28 @@ +.batch-table__row + %label.batch-table__row__select.batch-checkbox + = f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id + .batch-table__row__content + .status__content>< + - unless status.spoiler_text.blank? + %p>< + %strong= Formatter.instance.format_spoiler(status) + + = Formatter.instance.format(status) + + - unless status.media_attachments.empty? + - if status.media_attachments.first.video? + - video = status.media_attachments.first + = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true + - else + = react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } + + .detailed-status__meta + = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener' do + %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) + · + = fa_visibility_icon(status) + = t("statuses.visibilities.#{status.visibility}") + - if status.sensitive? + · + = fa_icon('eye-slash fw') + = t('stream_entries.sensitive_content') diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index c3baaf6be..44a531f2c 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -8,20 +8,17 @@ %li= filter_link_to t('admin.reports.unresolved'), resolved: nil %li= filter_link_to t('admin.reports.resolved'), resolved: '1' -= form_tag do - - .table-wrapper - %table.table - %thead - %tr - -# %th - %th= t('admin.reports.id') - %th= t('admin.reports.target') - %th= t('admin.reports.reported_by') - %th= t('admin.reports.report_contents') - %th= t('admin.reports.assigned') - %th - %tbody - = render @reports +.table-wrapper + %table.table + %thead + %tr + %th= t('admin.reports.id') + %th= t('admin.reports.target') + %th= t('admin.reports.reported_by') + %th= t('admin.reports.report_contents') + %th= t('admin.reports.assigned') + %th + %tbody + = render @reports = paginate @reports diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 60a8cab8e..cbfbdcfa9 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -11,16 +11,28 @@ - else = link_to t('admin.reports.mark_as_unresolved'), admin_report_path(@report, outcome: 'reopen'), method: :put, class: 'button' +%hr.spacer + .table-wrapper %table.table.inline-table %tbody %tr + %th= t('admin.reports.reported_account') + %td= admin_account_link_to @report.target_account + %td= table_link_to 'flag', pluralize(@report.target_account.targeted_reports.count, t('admin.reports.account.report')), admin_reports_path(target_account_id: @report.target_account.id) + %td= table_link_to 'file', pluralize(@report.target_account.targeted_moderation_notes.count, t('admin.reports.account.note')), admin_reports_path(target_account_id: @report.target_account.id) + %tr + %th= t('admin.reports.reported_by') + %td= admin_account_link_to @report.account + %td= table_link_to 'flag', pluralize(@report.account.targeted_reports.count, t('admin.reports.account.report')), admin_reports_path(target_account_id: @report.account.id) + %td= table_link_to 'file', pluralize(@report.account.targeted_moderation_notes.count, t('admin.reports.account.note')), admin_reports_path(target_account_id: @report.account.id) + %tr %th= t('admin.reports.created_at') - %td{colspan: 2} + %td{ colspan: 3 } %time.formatted{ datetime: @report.created_at.iso8601 } %tr %th= t('admin.reports.updated_at') - %td{colspan: 2} + %td{ colspan: 3 } %time.formatted{ datetime: @report.updated_at.iso8601 } %tr %th= t('admin.reports.status') @@ -29,14 +41,14 @@ = t('admin.reports.resolved') - else = t('admin.reports.unresolved') - %td{style: "text-align: right; overflow: hidden;"} + %td{ colspan: 2 } - if @report.action_taken? = table_link_to 'envelope-open', t('admin.reports.reopen'), admin_report_path(@report, outcome: 'reopen'), method: :put - if !@report.action_taken_by_account.nil? %tr %th= t('admin.reports.action_taken_by') - %td{colspan: 2} - = @report.action_taken_by_account.acct + %td{ colspan: 3 } + = admin_account_link_to @report.action_taken_by_account - else %tr %th= t('admin.reports.assigned') @@ -44,78 +56,55 @@ - if @report.assigned_account.nil? \- - else - = link_to @report.assigned_account.acct, admin_account_path(@report.assigned_account.id) - %td{style: "text-align: right"} + = admin_account_link_to @report.assigned_account + %td - if @report.assigned_account != current_user.account = table_link_to 'user', t('admin.reports.assign_to_self'), admin_report_path(@report, outcome: 'assign_to_self'), method: :put + %td - if !@report.assigned_account.nil? = table_link_to 'trash', t('admin.reports.unassign'), admin_report_path(@report, outcome: 'unassign'), method: :put -%hr{ class: "section-break"}/ - -.report-accounts - .report-accounts__item - %h3= t('admin.reports.reported_account') - = render 'authorize_follows/card', account: @report.target_account, admin: true - = render 'admin/reports/account_details', account: @report.target_account - .report-accounts__item - %h3= t('admin.reports.reported_by') - = render 'authorize_follows/card', account: @report.account, admin: true - = render 'admin/reports/account_details', account: @report.account - -%h3= t('admin.reports.comment.label') +%hr.spacer -= simple_format(@report.comment.presence || t('admin.reports.comment.none')) +.speech-bubble + .speech-bubble__bubble= simple_format(@report.comment.presence || t('admin.reports.comment.none')) + .speech-bubble__owner + = admin_account_link_to @report.account + %time.formatted{ datetime: @report.created_at.iso8601 } - unless @report.statuses.empty? - %hr/ - - %h3= t('admin.reports.statuses') + %hr.spacer/ = form_for(@form, url: admin_report_reported_statuses_path(@report.id)) do |f| - .batch-form-box - .batch-checkbox-all - = check_box_tag :batch_checkbox_all, nil, false - = f.select :action, Form::StatusBatch::ACTION_TYPE.map{|action| [t("admin.statuses.batch.#{action}"), action]} - = f.submit t('admin.statuses.execute'), data: { confirm: t('admin.reports.are_you_sure') }, class: 'button' - .media-spoiler-toggle-buttons - .media-spoiler-show-button.button= t('admin.statuses.media.show') - .media-spoiler-hide-button.button= t('admin.statuses.media.hide') - - @report.statuses.each do |status| - .report-status{ data: { id: status.id } } - .batch-checkbox - = f.check_box :status_ids, { multiple: true, include_hidden: false }, status.id - .activity-stream.activity-stream-headless - .entry= render 'stream_entries/simple_status', status: status - .report-status__actions - - unless status.media_attachments.empty? - = link_to admin_report_reported_status_path(@report, status, status: { sensitive: !status.sensitive }), method: :put, class: 'icon-button nsfw-button', title: t("admin.reports.nsfw.#{!status.sensitive}") do - = fa_icon status.sensitive? ? 'eye' : 'eye-slash' - = link_to admin_report_reported_status_path(@report, status), method: :delete, class: 'icon-button trash-button', title: t('admin.reports.delete'), data: { confirm: t('admin.reports.are_you_sure') }, remote: true do - = fa_icon 'trash' - -%hr{ class: "section-break"}/ + .batch-table + .batch-table__toolbar + %label.batch-table__toolbar__select.batch-checkbox-all + = check_box_tag :batch_checkbox_all, nil, false + .batch-table__toolbar__actions + = f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + .batch-table__body + = render partial: 'admin/reports/status', collection: @report.statuses, locals: { f: f } -%h3= t('admin.reports.notes.label') +%hr.spacer/ -- if @report_notes.length > 0 - %ul - = render @report_notes +- @report_notes.each do |item| + - if item.is_a?(Admin::ActionLog) + = render partial: 'action_log', locals: { action_log: item } + - elsif item.is_a?(ReportNote) + = render item -%h4= t('admin.reports.notes.new_label') -= form_for @report_note, url: admin_report_notes_path, html: { class: 'report-note__form' } do |f| += simple_form_for @report_note, url: admin_report_notes_path do |f| = render 'shared/error_messages', object: @report_note - = f.text_area :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6, class: 'report-note__textarea' - = f.hidden_field :report_id - %div{ class: 'report-note__buttons' } - - if @report.unresolved? - = f.submit t('admin.reports.notes.create_and_resolve'), name: :create_and_resolve, class: 'button report-note__button' - - else - = f.submit t('admin.reports.notes.create_and_unresolve'), name: :create_and_unresolve, class: 'button report-note__button' - = f.submit t('admin.reports.notes.create'), class: 'button report-note__button' + = f.input :report_id, as: :hidden -- if @report_history.length > 0 - %h3= t('admin.reports.history') + .field-group + = f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6 - %ul - = render @report_history + .actions + - if @report.unresolved? + = f.button :button, t('admin.reports.notes.create_and_resolve'), name: :create_and_resolve, type: :submit + - else + = f.button :button, t('admin.reports.notes.create_and_unresolve'), name: :create_and_unresolve, type: :submit + = f.button :button, t('admin.reports.notes.create'), type: :submit diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml index e8a81656c..789de47d1 100644 --- a/app/views/home/index.html.haml +++ b/app/views/home/index.html.haml @@ -1,4 +1,9 @@ - content_for :header_tags do + = preload_link_tag asset_pack_path('features/getting_started.js'), crossorigin: 'anonymous' + = preload_link_tag asset_pack_path('features/compose.js'), crossorigin: 'anonymous' + = preload_link_tag asset_pack_path('features/home_timeline.js'), crossorigin: 'anonymous' + = preload_link_tag asset_pack_path('features/notifications.js'), crossorigin: 'anonymous' + %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml index e1122d5a2..afc66d148 100644 --- a/app/views/stream_entries/_detailed_status.html.haml +++ b/app/views/stream_entries/_detailed_status.html.haml @@ -22,11 +22,11 @@ - if !status.media_attachments.empty? - if status.media_attachments.first.video? - video = status.media_attachments.first - %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 670, height: 380, detailed: true, inline: true) }} + = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 670, height: 380, detailed: true, inline: true - else - %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 380, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }} + = react_component :media_gallery, height: 380, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, standalone: true, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, 'reduceMotion': current_account&.user&.setting_reduce_motion, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } - elsif status.preview_cards.first - %div{ data: { component: 'Card', props: Oj.dump('maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json) }} + = react_component :card, 'maxDescription': 160, card: ActiveModelSerializers::SerializableResource.new(status.preview_cards.first, serializer: REST::PreviewCardSerializer).as_json .detailed-status__meta %data.dt-published{ value: status.created_at.to_time.iso8601 } diff --git a/app/views/stream_entries/_more.html.haml b/app/views/stream_entries/_more.html.haml new file mode 100644 index 000000000..9b1dfe4a7 --- /dev/null +++ b/app/views/stream_entries/_more.html.haml @@ -0,0 +1,2 @@ += link_to url, class: 'more light' do + = t('statuses.show_more') diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml index 2ad1f5120..cc2b6abe8 100644 --- a/app/views/stream_entries/_simple_status.html.haml +++ b/app/views/stream_entries/_simple_status.html.haml @@ -20,9 +20,10 @@ %a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more') .e-content{ lang: status.language, style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }< = Formatter.instance.format(status, custom_emojify: true) - - unless status.media_attachments.empty? - - if status.media_attachments.first.video? - - video = status.media_attachments.first - %div{ data: { component: 'Video', props: Oj.dump(src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343) }} - - else - %div{ data: { component: 'MediaGallery', props: Oj.dump(height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }) }} + + - unless status.media_attachments.empty? + - if status.media_attachments.first.video? + - video = status.media_attachments.first + = react_component :video, src: video.file.url(:original), preview: video.file.url(:small), sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, width: 610, height: 343, inline: true + - else + = react_component :media_gallery, height: 343, sensitive: status.sensitive? && !current_account&.user&.setting_display_sensitive_media, 'autoPlayGif': current_account&.user&.setting_auto_play_gif, media: status.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json } diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml index 2d0dafcb7..9764bc74d 100644 --- a/app/views/stream_entries/_status.html.haml +++ b/app/views/stream_entries/_status.html.haml @@ -5,19 +5,19 @@ is_successor ||= false direct_reply_id ||= false parent_id ||= false - is_direct_parent = direct_reply_id == status.id - is_direct_child = parent_id == status.in_reply_to_id - centered ||= include_threads && !is_predecessor && !is_successor - h_class = microformats_h_class(status, is_predecessor, is_successor, include_threads) - style_classes = style_classes(status, is_predecessor, is_successor, include_threads) - mf_classes = microformats_classes(status, is_direct_parent, is_direct_child) - entry_classes = h_class + ' ' + mf_classes + ' ' + style_classes + is_direct_parent = direct_reply_id == status.id + is_direct_child = parent_id == status.in_reply_to_id + centered ||= include_threads && !is_predecessor && !is_successor + h_class = microformats_h_class(status, is_predecessor, is_successor, include_threads) + style_classes = style_classes(status, is_predecessor, is_successor, include_threads) + mf_classes = microformats_classes(status, is_direct_parent, is_direct_child) + entry_classes = h_class + ' ' + mf_classes + ' ' + style_classes - if status.reply? && include_threads - if @next_ancestor .entry{ class: entry_classes } - = link_to short_account_status_url(@next_ancestor.account.username, @next_ancestor), class: 'more light' do - = t('statuses.show_more') + = render 'stream_entries/more', url: TagManager.instance.url_for(@next_ancestor) + = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id } .entry{ class: entry_classes } @@ -40,4 +40,15 @@ = render (centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status'), status: status.proper - if include_threads - = render partial: 'stream_entries/status', collection: @descendants, as: :status, locals: { is_successor: true, parent_id: status.id } + - if @since_descendant_thread_id + .entry{ class: entry_classes } + = render 'stream_entries/more', url: short_account_status_url(status.account.username, status, max_descendant_thread_id: @since_descendant_thread_id + 1) + - @descendant_threads.each do |thread| + = render partial: 'stream_entries/status', collection: thread[:statuses], as: :status, locals: { is_successor: true, parent_id: status.id } + + - if thread[:next_status] + .entry{ class: entry_classes } + = render 'stream_entries/more', url: TagManager.instance.url_for(thread[:next_status]) + - if @next_descendant_thread + .entry{ class: entry_classes } + = render 'stream_entries/more', url: short_account_status_url(status.account.username, status, since_descendant_thread_id: @max_descendant_thread_id - 1) diff --git a/app/views/well_known/host_meta/show.xml.ruby b/app/views/well_known/host_meta/show.xml.ruby index 07d026471..0a6bdc322 100644 --- a/app/views/well_known/host_meta/show.xml.ruby +++ b/app/views/well_known/host_meta/show.xml.ruby @@ -1,5 +1,13 @@ -Nokogiri::XML::Builder.new do |xml| - xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do - xml.Link(rel: 'lrdd', type: 'application/xrd+xml', template: @webfinger_template) +doc = Ox::Document.new(version: '1.0') + +doc << Ox::Element.new('XRD').tap do |xrd| + xrd['xmlns'] = 'http://docs.oasis-open.org/ns/xri/xrd-1.0' + + xrd << Ox::Element.new('Link').tap do |link| + link['rel'] = 'lrdd' + link['type'] = 'application/xrd+xml' + link['template'] = @webfinger_template end -end.to_xml +end + +('<?xml version="1.0" encoding="UTF-8"?>' + Ox.dump(doc, effort: :tolerant)).force_encoding('UTF-8') diff --git a/app/views/well_known/webfinger/show.xml.ruby b/app/views/well_known/webfinger/show.xml.ruby index 0c7289d6a..4352a24e9 100644 --- a/app/views/well_known/webfinger/show.xml.ruby +++ b/app/views/well_known/webfinger/show.xml.ruby @@ -1,13 +1,44 @@ -Nokogiri::XML::Builder.new do |xml| - xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do - xml.Subject @account.to_webfinger_s - xml.Alias short_account_url(@account) - xml.Alias account_url(@account) - xml.Link(rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: short_account_url(@account)) - xml.Link(rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: account_url(@account, format: 'atom')) - xml.Link(rel: 'self', type: 'application/activity+json', href: account_url(@account)) - xml.Link(rel: 'salmon', href: api_salmon_url(@account.id)) - xml.Link(rel: 'magic-public-key', href: "data:application/magic-public-key,#{@account.magic_key}") - xml.Link(rel: 'http://ostatus.org/schema/1.0/subscribe', template: "#{authorize_follow_url}?acct={uri}") - end -end.to_xml +doc = Ox::Document.new(version: '1.0') + +doc << Ox::Element.new('XRD').tap do |xrd| + xrd['xmlns'] = 'http://docs.oasis-open.org/ns/xri/xrd-1.0' + + xrd << (Ox::Element.new('Subject') << @account.to_webfinger_s) + xrd << (Ox::Element.new('Alias') << short_account_url(@account)) + xrd << (Ox::Element.new('Alias') << account_url(@account)) + + xrd << Ox::Element.new('Link').tap do |link| + link['rel'] = 'http://webfinger.net/rel/profile-page' + link['type'] = 'text/html' + link['href'] = short_account_url(@account) + end + + xrd << Ox::Element.new('Link').tap do |link| + link['rel'] = 'http://schemas.google.com/g/2010#updates-from' + link['type'] = 'application/atom+xml' + link['href'] = account_url(@account, format: 'atom') + end + + xrd << Ox::Element.new('Link').tap do |link| + link['rel'] = 'self' + link['type'] = 'application/activity+json' + link['href'] = account_url(@account) + end + + xrd << Ox::Element.new('Link').tap do |link| + link['rel'] = 'salmon' + link['href'] = api_salmon_url(@account.id) + end + + xrd << Ox::Element.new('Link').tap do |link| + link['rel'] = 'magic-public-key' + link['href'] = "data:application/magic-public-key,#{@account.magic_key}" + end + + xrd << Ox::Element.new('Link').tap do |link| + link['rel'] = 'http://ostatus.org/schema/1.0/subscribe' + link['template'] = "#{authorize_follow_url}?acct={uri}" + end +end + +('<?xml version="1.0" encoding="UTF-8"?>' + Ox.dump(doc, effort: :tolerant)).force_encoding('UTF-8') diff --git a/app/workers/local_notification_worker.rb b/app/workers/local_notification_worker.rb new file mode 100644 index 000000000..748270563 --- /dev/null +++ b/app/workers/local_notification_worker.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class LocalNotificationWorker + include Sidekiq::Worker + + def perform(mention_id) + mention = Mention.find(mention_id) + NotifyService.new.call(mention.account, mention) + rescue ActiveRecord::RecordNotFound + true + end +end diff --git a/app/workers/scheduler/backup_cleanup_scheduler.rb b/app/workers/scheduler/backup_cleanup_scheduler.rb index 7a9d4f894..5ab16c057 100644 --- a/app/workers/scheduler/backup_cleanup_scheduler.rb +++ b/app/workers/scheduler/backup_cleanup_scheduler.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'sidekiq-scheduler' class Scheduler::BackupCleanupScheduler include Sidekiq::Worker diff --git a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb index 6488798cd..bab4ae886 100644 --- a/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb +++ b/app/workers/scheduler/doorkeeper_cleanup_scheduler.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'sidekiq-scheduler' class Scheduler::DoorkeeperCleanupScheduler include Sidekiq::Worker diff --git a/app/workers/scheduler/email_scheduler.rb b/app/workers/scheduler/email_scheduler.rb index 24d0c0ebe..36866061b 100644 --- a/app/workers/scheduler/email_scheduler.rb +++ b/app/workers/scheduler/email_scheduler.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'sidekiq-scheduler' class Scheduler::EmailScheduler include Sidekiq::Worker diff --git a/app/workers/scheduler/feed_cleanup_scheduler.rb b/app/workers/scheduler/feed_cleanup_scheduler.rb index 23fa7672b..42cf14128 100644 --- a/app/workers/scheduler/feed_cleanup_scheduler.rb +++ b/app/workers/scheduler/feed_cleanup_scheduler.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'sidekiq-scheduler' class Scheduler::FeedCleanupScheduler include Sidekiq::Worker diff --git a/app/workers/scheduler/ip_cleanup_scheduler.rb b/app/workers/scheduler/ip_cleanup_scheduler.rb index a33ca031e..613a5e336 100644 --- a/app/workers/scheduler/ip_cleanup_scheduler.rb +++ b/app/workers/scheduler/ip_cleanup_scheduler.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'sidekiq-scheduler' class Scheduler::IpCleanupScheduler include Sidekiq::Worker diff --git a/app/workers/scheduler/media_cleanup_scheduler.rb b/app/workers/scheduler/media_cleanup_scheduler.rb index ce32ce314..c35686fcb 100644 --- a/app/workers/scheduler/media_cleanup_scheduler.rb +++ b/app/workers/scheduler/media_cleanup_scheduler.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'sidekiq-scheduler' class Scheduler::MediaCleanupScheduler include Sidekiq::Worker diff --git a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb index 3b9211e81..af2ae3120 100644 --- a/app/workers/scheduler/subscriptions_cleanup_scheduler.rb +++ b/app/workers/scheduler/subscriptions_cleanup_scheduler.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'sidekiq-scheduler' - class Scheduler::SubscriptionsCleanupScheduler include Sidekiq::Worker diff --git a/app/workers/scheduler/subscriptions_scheduler.rb b/app/workers/scheduler/subscriptions_scheduler.rb index 469a3d2a6..dc16e85c2 100644 --- a/app/workers/scheduler/subscriptions_scheduler.rb +++ b/app/workers/scheduler/subscriptions_scheduler.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require 'sidekiq-scheduler' -require 'sidekiq-bulk' - class Scheduler::SubscriptionsScheduler include Sidekiq::Worker diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb index a8f8fbd83..245536cea 100644 --- a/app/workers/scheduler/user_cleanup_scheduler.rb +++ b/app/workers/scheduler/user_cleanup_scheduler.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -require 'sidekiq-scheduler' class Scheduler::UserCleanupScheduler include Sidekiq::Worker diff --git a/app/workers/soft_block_domain_followers_worker.rb b/app/workers/soft_block_domain_followers_worker.rb index ce76683c5..85445c7fb 100644 --- a/app/workers/soft_block_domain_followers_worker.rb +++ b/app/workers/soft_block_domain_followers_worker.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require 'sidekiq-bulk' - class SoftBlockDomainFollowersWorker include Sidekiq::Worker diff --git a/config/application.rb b/config/application.rb index fdb534343..77da5cc2e 100644 --- a/config/application.rb +++ b/config/application.rb @@ -42,8 +42,10 @@ module Mastodon :bg, :ca, :de, + :el, :eo, :es, + :eu, :fa, :fi, :fr, @@ -68,6 +70,7 @@ module Mastodon :sr, :'sr-Latn', :sv, + :te, :th, :tr, :uk, diff --git a/config/deploy.rb b/config/deploy.rb index 180dd1c2a..e0cd60f54 100644 --- a/config/deploy.rb +++ b/config/deploy.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -lock '3.10.1' +lock '3.10.2' set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git') set :branch, ENV.fetch('BRANCH', 'master') diff --git a/config/environments/production.rb b/config/environments/production.rb index 2c8471ddd..16c0ef941 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -52,7 +52,7 @@ Rails.application.configure do config.log_tags = [:request_id] # Use a different cache store in production. - config.cache_store = :redis_store, ENV['REDIS_URL'], REDIS_CACHE_PARAMS + config.cache_store = :redis_store, ENV['CACHE_REDIS_URL'], REDIS_CACHE_PARAMS # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. diff --git a/config/initializers/http_client_proxy.rb b/config/initializers/http_client_proxy.rb new file mode 100644 index 000000000..f5026d59e --- /dev/null +++ b/config/initializers/http_client_proxy.rb @@ -0,0 +1,24 @@ +Rails.application.configure do + config.x.http_client_proxy = {} + if ENV['http_proxy'].present? + proxy = URI.parse(ENV['http_proxy']) + raise "Unsupported proxy type: #{proxy.scheme}" unless %w(http https).include? proxy.scheme + raise "No proxy host" unless proxy.host + + host = proxy.host + host = host[1...-1] if host[0] == '[' #for IPv6 address + config.x.http_client_proxy[:proxy] = { proxy_address: host, proxy_port: proxy.port, proxy_username: proxy.user, proxy_password: proxy.password }.compact + end + + config.x.access_to_hidden_service = ENV['ALLOW_ACCESS_TO_HIDDEN_SERVICE'] == 'true' + config.x.hidden_service_via_transparent_proxy = ENV['HIDDEN_SERVICE_VIA_TRANSPARENT_PROXY'] == 'true' +end + +module Goldfinger + def self.finger(uri, opts = {}) + to_hidden = /\.(onion|i2p)(:\d+)?$/.match(uri) + raise Mastodon::HostValidationError, 'Instance does not support hidden service connections' if !Rails.configuration.x.access_to_hidden_service && to_hidden + opts = opts.merge(Rails.configuration.x.http_client_proxy).merge(ssl: !to_hidden) + Goldfinger::Client.new(uri, opts).finger + end +end diff --git a/config/initializers/json_ld.rb b/config/initializers/json_ld.rb deleted file mode 100644 index 2ddc7352d..000000000 --- a/config/initializers/json_ld.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../lib/json_ld/identity' -require_relative '../../lib/json_ld/security' -require_relative '../../lib/json_ld/activitystreams' diff --git a/config/initializers/oembed.rb b/config/initializers/oembed.rb deleted file mode 100644 index 208e586cb..000000000 --- a/config/initializers/oembed.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -require_relative '../../app/lib/provider_discovery' -OEmbed::Providers.register_fallback(ProviderDiscovery) diff --git a/config/initializers/rack_attack.rb b/config/initializers/rack_attack.rb index b35452f04..0ca0a7e7f 100644 --- a/config/initializers/rack_attack.rb +++ b/config/initializers/rack_attack.rb @@ -53,6 +53,10 @@ class Rack::Attack req.ip if req.api_request? end + throttle('throttle_media', limit: 30, period: 30.minutes) do |req| + req.authenticated_user_id if req.post? && req.path.start_with?('/api/v1/media') + end + throttle('protected_paths', limit: 25, period: 5.minutes) do |req| req.ip if req.post? && req.path =~ PROTECTED_PATHS_REGEX end diff --git a/config/locales/activerecord.eu.yml b/config/locales/activerecord.eu.yml new file mode 100644 index 000000000..7b0ebe0b0 --- /dev/null +++ b/config/locales/activerecord.eu.yml @@ -0,0 +1,9 @@ +--- +eu: + activerecord: + errors: + models: + account: + attributes: + username: + invalid: letrak, zenbakiak eta gidoi baxuak besterik ez diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 8b9a6688a..e9ca3038e 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -151,7 +151,7 @@ ar: memorialize_account: لقد قام %{name} بتحويل حساب %{target} إلى صفحة تذكارية promote_user: "%{name} قام بترقية المستخدم %{target}" reset_password_user: "%{name} لقد قام بإعادة تعيين الكلمة السرية الخاصة بـ %{target}" - resolve_report: قام %{name} بإلغاء التقرير المُرسَل مِن طرف %{target} + resolve_report: قام %{name} بحل التقرير %{target} silence_account: لقد قام %{name} بكتم حساب %{target} suspend_account: لقد قام %{name} بتعليق حساب %{target} unsilence_account: لقد قام %{name} بإلغاء الكتم عن حساب %{target} @@ -240,7 +240,6 @@ ar: action_taken_by: تم اتخاذ الإجراء مِن طرف are_you_sure: هل أنت متأكد ؟ comment: - label: تعليق none: لا شيء delete: حذف id: معرّف ID diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 61daddc66..063003218 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -4,6 +4,7 @@ ca: about_hashtag_html: Aquests són toots públics etiquetats amb <strong>#%{hashtag}</strong>. Pots interactuar amb ells si tens un compte a qualsevol lloc del fediverse. about_mastodon_html: Mastodon és una xarxa social basada en protocols web oberts i en programari lliure i de codi obert. Està descentralitzat com el correu electrònic. about_this: Quant a + administered_by: 'Administrat per:' closed_registrations: Actualment, el registre està tancat en aquesta instància. Malgrat això! Pots trobar una altra instància per fer-te un compte i obtenir accés a la mateixa xarxa des d'allà. contact: Contacte contact_missing: No configurat @@ -60,7 +61,15 @@ ca: destroyed_msg: Nota de moderació destruïda amb èxit! accounts: are_you_sure: N'estàs segur? + avatar: Avatar by_domain: Domini + change_email: + changed_msg: El correu electrònic del compte s'ha canviat correctament! + current_email: Correu electrònic actual + label: Canviar l'adreça de correu + new_email: Nou correu + submit: Canviar adreça de correu + title: Canviar adreça de correu de %{username} confirm: Confirma confirmed: Confirmat demote: Degrada @@ -108,6 +117,7 @@ ca: public: Públic push_subscription_expires: La subscripció PuSH expira redownload: Actualitza l'avatar + remove_avatar: Eliminar avatar reset: Reinicialitza reset_password: Restableix la contrasenya resubscribe: Torna a subscriure @@ -128,6 +138,7 @@ ca: statuses: Estats subscribe: Subscriu title: Comptes + unconfirmed_email: Correu electrònic sense confirmar undo_silenced: Deixa de silenciar undo_suspension: Desfés la suspensió unsubscribe: Cancel·la la subscripció @@ -135,6 +146,8 @@ ca: web: Web action_logs: actions: + assigned_to_self_report: "%{name} han assignat l'informe %{target} a ells mateixos" + change_email_user: "%{name} ha canviat l'adreça de correu electrònic del usuari %{target}" confirm_user: "%{name} ha confirmat l'adreça de correu electrònic de l'usuari %{target}" create_custom_emoji: "%{name} ha pujat un nou emoji %{target}" create_domain_block: "%{name} ha blocat el domini %{target}" @@ -150,10 +163,13 @@ ca: enable_user: "%{name} ha activat l'accés per a l'usuari %{target}" memorialize_account: "%{name} ha convertit el compte %{target} en una pàgina de memorial" promote_user: "%{name} ha promogut l'usuari %{target}" + remove_avatar_user: "%{name} ha eliminat l'avatar de %{target}" + reopen_report: "%{name} ha reobert l'informe %{target}" reset_password_user: "%{name} ha restablert la contrasenya de l'usuari %{target}" - resolve_report: "%{name} ha descartat l'informe %{target}" + resolve_report: "%{name} ha resolt l'informe %{target}" silence_account: "%{name} ha silenciat el compte de %{target}" suspend_account: "%{name} ha suspès el compte de %{target}" + unassigned_report: "%{name} ha des-assignat l'informe %{target}" unsilence_account: "%{name} ha silenciat el compte de %{target}" unsuspend_account: "%{name} ha llevat la suspensió del compte de %{target}" update_custom_emoji: "%{name} ha actualitzat l'emoji %{target}" @@ -239,29 +255,48 @@ ca: expired: Caducat title: Filtre title: Convida + report_notes: + created_msg: La nota del informe s'ha creat correctament! + destroyed_msg: La nota del informe s'ha esborrat correctament! reports: + account: + note: nota + report: informe action_taken_by: Mesures adoptades per are_you_sure: N'estàs segur? + assign_to_self: Assignar-me + assigned: Moderador assignat comment: - label: Comentari none: Cap + created_at: Reportat delete: Suprimeix id: ID mark_as_resolved: Marca com a resolt + mark_as_unresolved: Marcar sense resoldre + notes: + create: Afegir nota + create_and_resolve: Resoldre amb nota + create_and_unresolve: Reobrir amb nota + delete: Esborrar + placeholder: Descriu les accions que s'han pres o qualsevol altra actualització d'aquest informe… nsfw: 'false': Mostra els fitxers multimèdia adjunts 'true': Amaga els fitxers multimèdia adjunts + reopen: Reobrir informe report: 'Informe #%{id}' report_contents: Contingut reported_account: Compte reportat reported_by: Reportat per resolved: Resolt + resolved_msg: Informe resolt amb èxit! silence_account: Silencia el compte status: Estat suspend_account: Suspèn el compte target: Objectiu title: Informes + unassign: Treure assignació unresolved: No resolt + updated_at: Actualitzat view: Visualització settings: activity_api_enabled: @@ -319,8 +354,8 @@ ca: back_to_account: Torna a la pàgina del compte batch: delete: Suprimeix - nsfw_off: NSFW OFF - nsfw_on: NSFW ON + nsfw_off: Marcar com a no sensible + nsfw_on: Marcar com a sensible execute: Executa failed_to_execute: No s'ha pogut executar media: @@ -382,6 +417,7 @@ ca: security: Seguretat set_new_password: Estableix una contrasenya nova authorize_follow: + already_following: Ja estàs seguint aquest compte error: Malauradament, ha ocorregut un error cercant el compte remot follow: Segueix follow_request: 'Has enviat una sol·licitud de seguiment a:' @@ -474,6 +510,7 @@ ca: '21600': 6 hores '3600': 1 hora '43200': 12 hores + '604800': 1 setmana '86400': 1 dia expires_in_prompt: Mai generate: Genera @@ -577,6 +614,10 @@ ca: missing_resource: No s'ha pogut trobar la URL de redirecció necessaria per al compte proceed: Comença a seguir prompt: 'Seguiràs a:' + remote_unfollow: + error: Error + title: Títol + unfollowed: Sense seguir sessions: activity: Última activitat browser: Navegador @@ -643,6 +684,9 @@ ca: one: "%{count} vídeo" other: "%{count} vídeos" content_warning: 'Avís de contingut: %{warning}' + disallowed_hashtags: + one: 'conté una etiqueta no permesa: %{tags}' + other: 'conté les etiquetes no permeses: %{tags}' open_in_web: Obre en la web over_character_limit: Límit de caràcters de %{max} superat pin_errors: @@ -665,6 +709,83 @@ ca: reblogged: ha impulsat sensitive_content: Contingut sensible terms: + body_html: | + <h2>Privacy Policy</h2> + <h3 id="collect">Quina informació recollim?</h3> + + <ul> + <li><em>Informació bàsica del compte</em>: Si et registres en aquest servidor, se´t pot demanar que introdueixis un nom d'usuari, una adreça de correu electrònic i una contrasenya. També pots introduir informació de perfil addicional, com ara un nom de visualització i una biografia, i carregar una imatge de perfil i de capçalera. El nom d'usuari, el nom de visualització, la biografia, la imatge de perfil i la imatge de capçalera sempre apareixen públicament.</li> + <li><em>Publicacions, seguiment i altra informació pública</em>: La llista de persones que segueixes s'enumeren públicament i el mateix passa amb els teus seguidors. Quan envies un missatge, la data i l'hora s'emmagatzemen, així com l'aplicació que va enviar el missatge. Els missatges poden contenir multimèdia, com ara imatges i vídeos. Els toots públics i no llistats estan disponibles públicament. En quan tinguis un toot en el teu perfil, aquest també és informació pública. Les teves entrades es lliuren als teus seguidors que en alguns casos significa que es lliuren a diferents servidors en els quals s'hi emmagatzemen còpies. Quan suprimeixes publicacions, també es lliuraran als teus seguidors. L'acció d'impulsar o marcar com a favorit una publicació sempre és pública.</li> + <li><em>Toots directes i per a només seguidors</em>: Totes les publicacions s'emmagatzemen i processen al servidor. Els toots per a només seguidors només es lliuren als teus seguidors i als usuaris que s'esmenten en ells i els toots directes només es lliuren als usuaris esmentats. En alguns casos, significa que es lliuren a diferents servidors i s'hi emmagatzemen còpies. Fem un esforç de bona fe per limitar l'accés a aquestes publicacions només a les persones autoritzades, però és possible que altres servidors no ho facin. Per tant, és important revisar els servidors als quals pertanyen els teus seguidors. Pots canviar la opció de aprovar o rebutjar els nous seguidors manualment a la configuració. <em>Tingues en compte que els operadors del servidor i qualsevol servidor receptor poden visualitzar aquests missatges</em> i els destinataris poden fer una captura de pantalla, copiar-los o tornar-los a compartir. <em>No comparteixis cap informació perillosa a Mastodon.</em></li> + <li><em>IPs i altres metadades</em>: Quan inicies sessió registrem l'adreça IP en que l'has iniciat, així com el nom de l'aplicació o navegador. Totes les sessions registrades estan disponibles per a la teva revisió i revocació a la configuració. L'última adreça IP utilitzada s'emmagatzema durant un màxim de 12 mesos. També podrem conservar els registres que inclouen l'adreça IP de cada sol·licitud al nostre servidor.</li> + </ul> + + <hr class="spacer" /> + + <h3 id="use">Per a què utilitzem la teva informació?</h3> + + <p>Qualsevol de la informació que recopilem de tu es pot utilitzar de la manera següent:</p> + + <ul> + <li>Per proporcionar la funcionalitat bàsica de Mastodon. Només pots interactuar amb el contingut d'altres persones i publicar el teu propi contingut quan hàgis iniciat la sessió. Per exemple, pots seguir altres persones per veure les publicacions combinades a la teva pròpia línia de temps personalitzada.</li> + <li>Per ajudar a la moderació de la comunitat, per exemple comparar la teva adreça IP amb altres conegudes per determinar l'evasió de prohibicions o altres infraccions.</li> + <li>L'adreça electrònica que ens proporciones pot utilitzar-se per enviar-te informació, notificacions sobre altres persones que interactuen amb el teu contingut o t'envien missatges, i per respondre a les consultes i / o altres sol·licituds o preguntes.</li> + </ul> + + <hr class="spacer" /> + + <h3 id="protect">Com protegim la teva informació</h3> + + <p>Implementem diverses mesures per mantenir la seguretat quan introdueixes, envies o accedeixes a la teva informació personal. Entre altres mesures, la sessió del teu navegador així com el trànsit entre les teves aplicacions i l'API estan protegides amb SSL i la teva contrasenya es codifica utilitzant un algoritme de direcció única. Pots habilitar l'autenticació de dos factors per a garantir l'accés segur al teu compte.</p> + + <hr class="spacer" /> + + <h3 id="data-retention">Quina és la nostra política de retenció de dades?</h3> + + <p>Farem un esforç de bona fe per:</p> + + <ul> + <li>Conservar els registres del servidor que continguin l'adreça IP de totes les sol·licituds que rebi, tenint em compte que aquests registres es mantenen no més de 90 dies.</li> + <li>Conservar les adreces IP associades als usuaris registrats no més de 12 mesos.</li> + </ul> + + <p>Pots sol·licitar i descarregar un arxiu del teu contingut incloses les publicacions, els fitxers adjunts multimèdia, la imatge de perfil i la imatge de capçalera.</p> + + <p>Pots eliminar el teu compte de forma irreversible en qualsevol moment.</p> + + <hr class="spacer"/> + + <h3 id="cookies">Utilitzem cookies?</h3> + + <p>Sí. Les cookies són petits fitxers que un lloc o el proveïdor de serveis transfereix al disc dur del teu ordinador a través del navegador web (si ho permet). Aquestes galetes permeten al lloc reconèixer el teu navegador i, si tens un compte registrat, associar-lo al teu compte registrat.</p> + + <p>Utilitzem cookies per entendre i guardar les teves preferències per a futures visites.</p> + + <hr class="spacer" /> + + <h3 id="disclose">Revelem informació a terceres parts?</h3> + + <p>No venem, comercialitzem ni transmetem a tercers la teva informació d'identificació personal. Això no inclou tercers de confiança que ens ajuden a operar el nostre lloc, a dur a terme el nostre servei o a servir-te, sempre que aquestes parts acceptin mantenir confidencial aquesta informació. També podem publicar la teva informació quan creiem que l'alliberament és apropiat per complir amb la llei, fer complir les polítiques del nostre lloc o protegir els nostres drets o altres drets, propietat o seguretat.</p> + + <p>Els altres servidors de la teva xarxa poden descarregar contingut públic. Els teus toots públics i per a només seguidors es lliuren als servidors on resideixen els teus seguidors i els missatges directes s'envien als servidors dels destinataris, sempre que aquests seguidors o destinataris resideixin en un servidor diferent d'aquest.</p> + + <p>Quan autoritzes una aplicació a utilitzar el teu compte, segons l'abast dels permisos que aprovis, pot accedir a la teva informació de perfil pública, a la teva llista de seguits, als teus seguidors, a les teves llistes, a totes les teves publicacions i als teus favorits. Les aplicacions mai no poden accedir a la teva adreça de correu electrònic o contrasenya.</p> + + <hr class="spacer" /> + + <h3 id="coppa">Compliment de la Llei de protecció de la privacitat en línia dels nens</h3> + + <p>El nostre lloc, productes i serveis estan dirigits a persones que tenen almenys 13 anys. Si aquest servidor es troba als EUA, i tens menys de 13 anys, segons els requisits de COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) no utilitzis aquest lloc.</p> + + <hr class="spacer" /> + + <h3 id="changes">Canvis a la nostra política de privacitat</h3> + + <p>Si decidim canviar la nostra política de privadesa, publicarem aquests canvis en aquesta pàgina.</p> + + <p> Aquest document és CC-BY-SA. Actualitzat per darrera vegada el 7 de Març del 2018.</p> + + <p>Originalment adaptat des del <a href="https://github.com/discourse/discourse">Discourse privacy policy</a>.</p> title: "%{instance} Condicions del servei i política de privadesa" time: formats: diff --git a/config/locales/de.yml b/config/locales/de.yml index 6233d299e..5fdcb1900 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -4,6 +4,7 @@ de: about_hashtag_html: Dies sind öffentliche Beiträge, die mit <strong>#%{hashtag}</strong> getaggt wurden. Wenn du ein Konto irgendwo im Fediversum besitzt, kannst du mit ihnen interagieren. about_mastodon_html: Mastodon ist ein soziales Netzwerk. Es basiert auf offenen Web-Protokollen und freier, quelloffener Software. Es ist dezentral (so wie E-Mail!). about_this: Über diese Instanz + administered_by: 'Administriert von:' closed_registrations: Die Registrierung auf dieser Instanz ist momentan geschlossen. Aber du kannst dein Konto auch auf einer anderen Instanz erstellen! Von dort hast du genauso Zugriff auf das Mastodon-Netzwerk. contact: Kontakt contact_missing: Nicht angegeben @@ -60,7 +61,15 @@ de: destroyed_msg: Moderationsnotiz erfolgreich gelöscht! accounts: are_you_sure: Bist du sicher? + avatar: Profilbild by_domain: Domäne + change_email: + changed_msg: E-Mail-Adresse des Kontos erfolgreich geändert! + current_email: Aktuelle E-Mail-Adresse + label: E-Mail-Adresse ändern + new_email: Neue E-Mail-Adresse + submit: E-Mail-Adresse ändern + title: E-Mail-Adresse für %{username} ändern confirm: Bestätigen confirmed: Bestätigt demote: Degradieren @@ -75,9 +84,9 @@ de: enabled: Freigegeben feed_url: Feed-URL followers: Folger - followers_url: Followers URL + followers_url: URL des Folgenden follows: Folgt - inbox_url: Inbox URL + inbox_url: Posteingangs-URL ip: IP-Adresse location: all: Alle @@ -100,7 +109,7 @@ de: alphabetic: Alphabetisch most_recent: Neueste title: Sortierung - outbox_url: Outbox URL + outbox_url: Postausgangs-URL perform_full_suspension: Vollständige Sperre durchführen profile_url: Profil-URL promote: Befördern @@ -108,6 +117,7 @@ de: public: Öffentlich push_subscription_expires: PuSH-Abonnement läuft aus redownload: Avatar neu laden + remove_avatar: Profilbild entfernen reset: Zurücksetzen reset_password: Passwort zurücksetzen resubscribe: Wieder abonnieren @@ -128,6 +138,7 @@ de: statuses: Beiträge subscribe: Abonnieren title: Konten + unconfirmed_email: Unbestätigte E-Mail-Adresse undo_silenced: Stummschaltung zurücknehmen undo_suspension: Sperre zurücknehmen unsubscribe: Abbestellen @@ -135,6 +146,8 @@ de: web: Web action_logs: actions: + assigned_to_self_report: "%{name} hat sich die Meldung %{target} selbst zugewiesen" + change_email_user: "%{name} hat die E-Mail-Adresse des Nutzers %{target} geändert" confirm_user: "%{name} hat die E-Mail-Adresse von %{target} bestätigt" create_custom_emoji: "%{name} hat neues Emoji %{target} hochgeladen" create_domain_block: "%{name} hat die Domain %{target} blockiert" @@ -150,10 +163,13 @@ de: enable_user: "%{name} hat die Anmeldung für den Benutzer %{target} aktiviert" memorialize_account: "%{name} hat %{target}s Profil in eine Gedenkseite umgewandelt" promote_user: "%{name} hat %{target} befördert" + remove_avatar_user: "%{name} hat das Profilbild von %{target} entfernt" + reopen_report: "%{name} hat die Meldung %{target} wieder geöffnet" reset_password_user: "%{name} hat das Passwort für den Benutzer %{target} zurückgesetzt" - resolve_report: "%{name} hat die Meldung %{target} abgelehnt" + resolve_report: "%{name} hat die Meldung %{target} bearbeitet" silence_account: "%{name} hat %{target}s Account stummgeschaltet" suspend_account: "%{name} hat %{target}s Account gesperrt" + unassigned_report: "%{name} hat die Zuweisung der Meldung %{target} entfernt" unsilence_account: "%{name} hat die Stummschaltung von %{target}s Account aufgehoben" unsuspend_account: "%{name} hat die Sperrung von %{target}s Account aufgehoben" update_custom_emoji: "%{name} hat das %{target} Emoji aktualisiert" @@ -177,7 +193,7 @@ de: new: title: Eigenes Emoji hinzufügen overwrite: Überschreiben - shortcode: Shortcode + shortcode: Kürzel shortcode_hint: Mindestens 2 Zeichen, nur Buchstaben, Ziffern und Unterstriche title: Eigene Emojis unlisted: Ungelistet @@ -239,29 +255,48 @@ de: expired: Ausgelaufen title: Filter title: Einladungen + report_notes: + created_msg: Meldungs-Kommentar erfolgreich erstellt! + destroyed_msg: Meldungs-Kommentar erfolgreich gelöscht! reports: + account: + note: Notiz + report: Meldung action_taken_by: Maßnahme ergriffen durch are_you_sure: Bist du dir sicher? + assign_to_self: Mir zuweisen + assigned: Zugewiesener Moderator comment: - label: Kommentar none: Kein + created_at: Gemeldet delete: Löschen id: ID mark_as_resolved: Als gelöst markieren + mark_as_unresolved: Als ungelöst markieren + notes: + create: Kommentar hinzufügen + create_and_resolve: Mit Kommentar lösen + create_and_unresolve: Mit Kommentar wieder öffnen + delete: Löschen + placeholder: Beschreibe, welche Maßnahmen ergriffen wurden oder andere Neuigkeiten zu dieser Meldung… nsfw: 'false': Medienanhänge wieder anzeigen 'true': Medienanhänge verbergen + reopen: Meldung wieder öffnen report: 'Meldung #%{id}' report_contents: Inhalt reported_account: Gemeldetes Konto reported_by: Gemeldet von resolved: Gelöst + resolved_msg: Meldung erfolgreich gelöst! silence_account: Konto stummschalten status: Status suspend_account: Konto sperren target: Ziel title: Meldungen + unassign: Zuweisung entfernen unresolved: Ungelöst + updated_at: Aktualisiert view: Ansehen settings: activity_api_enabled: @@ -319,8 +354,8 @@ de: back_to_account: Zurück zum Konto batch: delete: Löschen - nsfw_off: NSFW aus - nsfw_on: NSFW ein + nsfw_off: Als nicht heikel markieren + nsfw_on: Als heikel markieren execute: Ausführen failed_to_execute: Ausführen fehlgeschlagen media: @@ -382,6 +417,7 @@ de: security: Sicherheit set_new_password: Neues Passwort setzen authorize_follow: + already_following: Du folgst diesem Konto bereits error: Das Profil konnte nicht geladen werden follow: Folgen follow_request: 'Du hast eine Folgeanfrage gesendet an:' @@ -429,7 +465,7 @@ de: archive_takeout: date: Datum download: Dein Archiv herunterladen - hint_html: Du kannst ein Archiv deiner <strong>Beiträge und hochgeladenen Medien</strong> anfragen. Die exportieren Daten werden im ActivityPub-Format gespeichert, dass lesbar mit jeder Software ist, die das Format unterstützt. + hint_html: Du kannst ein Archiv deiner <strong>Beiträge und hochgeladenen Medien</strong> anfragen. Die exportierten Daten werden im ActivityPub-Format gespeichert, welches mit jeder Software lesbar ist die das Format unterstützt. in_progress: Stelle dein Archiv zusammen... request: Dein Archiv anfragen size: Größe @@ -474,6 +510,7 @@ de: '21600': 6 Stunden '3600': 1 Stunde '43200': 12 Stunden + '604800': 1 Woche '86400': 1 Tag expires_in_prompt: Nie generate: Generieren @@ -577,6 +614,10 @@ de: missing_resource: Die erforderliche Weiterleitungs-URL für dein Konto konnte nicht gefunden werden proceed: Weiter prompt: 'Du wirst dieser Person folgen:' + remote_unfollow: + error: Fehler + title: Titel + unfollowed: Entfolgt sessions: activity: Letzte Aktivität browser: Browser @@ -634,6 +675,18 @@ de: two_factor_authentication: Zwei-Faktor-Auth your_apps: Deine Anwendungen statuses: + attached: + description: 'Angehängt: %{attached}' + image: + one: "%{count} Bild" + other: "%{count} Bilder" + video: + one: "%{count} Video" + other: "%{count} Videos" + content_warning: 'Inhaltswarnung: %{warning}' + disallowed_hashtags: + one: 'Enthält den unerlaubten Hashtag: %{tags}' + other: 'Enthält die unerlaubten Hashtags: %{tags}' open_in_web: Im Web öffnen over_character_limit: Zeichenlimit von %{max} überschritten pin_errors: @@ -655,6 +708,10 @@ de: pinned: Angehefteter Beitrag reblogged: teilte sensitive_content: Heikle Inhalte + terms: + title: "%{instance} Nutzungsbedingungen und Datenschutzerklärung" + themes: + default: Mastodon time: formats: default: "%d.%m.%Y %H:%M" diff --git a/config/locales/devise.de.yml b/config/locales/devise.de.yml index 77243ba15..0d33af6f1 100644 --- a/config/locales/devise.de.yml +++ b/config/locales/devise.de.yml @@ -3,8 +3,8 @@ de: devise: confirmations: confirmed: Deine E-Mail-Adresse wurde bestätigt. - send_instructions: Du erhältst in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinen Spam-Ordner! - send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank hinterlegt ist, erhältst du in wenigen Minuten eine E-Mail. Darin wird erklärt, wie du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinen Spam-Ordner! + send_instructions: Du wirst in wenigen Minuten eine E-Mail erhalten. Darin wird erklärt, wie du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinem Spam-Ordner nach, wenn du diese E-Mail nicht erhalten hast. + send_paranoid_instructions: Falls deine E-Mail-Adresse in unserer Datenbank hinterlegt ist, wirst du in wenigen Minuten eine E-Mail erhalten. Darin wird erklärt, wie du deine E-Mail-Adresse bestätigen kannst. Schau bitte auch in deinem Spam-Ordner nach, wenn du diese E-Mail nicht erhalten hast. failure: already_authenticated: Du bist bereits angemeldet. inactive: Dein Konto wurde noch nicht aktiviert. @@ -73,10 +73,10 @@ de: errors: messages: already_confirmed: wurde bereits bestätigt, bitte versuche dich anzumelden - confirmation_period_expired: muss innerhalb %{period} bestätigt werden, bitte fordere einen neuen Link an. - expired: ist abgelaufen, bitte neu anfordern. - not_found: wurde nicht gefunden. + confirmation_period_expired: muss innerhalb %{period} bestätigt werden, bitte fordere einen neuen Link an + expired: ist abgelaufen, bitte neu anfordern + not_found: nicht gefunden not_locked: ist nicht gesperrt not_saved: - one: 'Konnte %{resource} nicht speichern: ein Fehler.' - other: 'Konnte %{resource} nicht speichern: %{count} Fehler.' + one: '1 Fehler hat verhindert, dass %{resource} gespeichert wurde:' + other: "%{count} Fehler verhinderten, dass %{resource} gespeichert wurde:" diff --git a/config/locales/devise.eu.yml b/config/locales/devise.eu.yml new file mode 100644 index 000000000..215b72e52 --- /dev/null +++ b/config/locales/devise.eu.yml @@ -0,0 +1,5 @@ +--- +eu: + devise: + failure: + already_authenticated: Saioa hasi duzu jada. diff --git a/config/locales/devise.it.yml b/config/locales/devise.it.yml index e1ba7bb22..0c5d8963c 100644 --- a/config/locales/devise.it.yml +++ b/config/locales/devise.it.yml @@ -17,11 +17,32 @@ it: unconfirmed: Devi confermare il tuo indirizzo email per continuare. mailer: confirmation_instructions: + action: Verifica indirizzo email + explanation: Hai creato un account su %{host} con questo indirizzo email. Sei lonatno solo un clic dall'attivarlo. Se non sei stato tu, per favore ignora questa email. + extra_html: Per favore controlla<a href="%{terms_path}">le regole dell'istanza</a> e <a href="%{policy_path}">i nostri termini di servizio</a>. subject: 'Mastodon: Istruzioni di conferma per %{instance}' + title: Verifica indirizzo email + email_changed: + explanation: 'L''indirizzo email del tuo account sta per essere cambiato in:' + extra: Se non hai cambiato la tua email, è probabile che qualcuno abbia accesso al tuo account. Cambia immediatamente la tua password e contatta l'amministratore dell'istanza se sei bloccato fuori dal tuo account. + subject: 'Mastodon: Email cambiata' + title: Nuovo indirizzo email password_change: + explanation: La password del tuo account è stata cambiata. + extra: Se non hai cambiato la password, è probabile che qualcuno abbia accesso al tuo account. Cambia immediatamente la tua password e contatta l'amministratore dell'istanza se sei bloccato fuori dal tuo account. subject: 'Mastodon: Password modificata' + title: Password cambiata + reconfirmation_instructions: + explanation: Conferma il nuovo indirizzo per cambiare la tua email. + extra: Se questo cambiamento non è stato chiesto da te, ignora questa email. L'indirizzo email per l'account Mastodon non verrà cambiato finché non accedi dal link qui sopra. + subject: 'Mastodon: Email di conferma per %{instance}' + title: Verifica indirizzo email reset_password_instructions: + action: Cambia password + explanation: Hai richiesto una nuova password per il tuo account. + extra: Se questo cambiamento non è stato chiesto da te, ignora questa email. La tua password non verrà cambiata finché non accedi tramite il link qui sopra e ne crei una nuova. subject: 'Mastodon: Istruzioni per il reset della password' + title: Ripristino password unlock_instructions: subject: 'Mastodon: Istruzioni di sblocco' omniauth_callbacks: diff --git a/config/locales/doorkeeper.de.yml b/config/locales/doorkeeper.de.yml index d7d98c6d6..670f5ec2a 100644 --- a/config/locales/doorkeeper.de.yml +++ b/config/locales/doorkeeper.de.yml @@ -29,7 +29,7 @@ de: edit: title: Anwendung bearbeiten form: - error: Hoppla! Bitte überprüfe das Formular auf Fehler! + error: Hoppla! Bitte überprüfe das Formular auf mögliche Fehler help: native_redirect_uri: "%{native_redirect_uri} für lokale Tests benutzen" redirect_uri: Bitte benutze eine Zeile pro URI @@ -59,7 +59,7 @@ de: error: title: Ein Fehler ist aufgetreten new: - able_to: 'Sie wird folgende Befugnisse haben:' + able_to: Es wird in der Lage sein zu prompt: Die Anwendung %{client_name} verlangt Zugriff auf dein Konto title: Autorisierung erforderlich show: @@ -83,7 +83,7 @@ de: invalid_grant: Die beigefügte Autorisierung ist ungültig, abgelaufen, wurde widerrufen, einem anderen Client ausgestellt oder der Weiterleitungs-URI stimmt nicht mit der Autorisierungs-Anfrage überein. invalid_redirect_uri: Der beigefügte Weiterleitungs-URI ist ungültig. invalid_request: Die Anfrage enthält ein nicht-unterstütztes Argument, ein Parameter fehlt, oder sie ist anderweitig fehlerhaft. - invalid_resource_owner: Die angegebenen Zugangsdaten für den »resource owner« sind ungültig, oder dieses Profil existiert nicht. + invalid_resource_owner: Die angegebenen Zugangsdaten für den Ressourcenbesitzer sind ungültig oder der Ressourcenbesitzer kann nicht gefunden werden invalid_scope: Die angeforderte Befugnis ist ungültig, unbekannt oder fehlerhaft. invalid_token: expired: Der Zugriffs-Token ist abgelaufen diff --git a/config/locales/doorkeeper.eu.yml b/config/locales/doorkeeper.eu.yml new file mode 100644 index 000000000..a51b1dc8b --- /dev/null +++ b/config/locales/doorkeeper.eu.yml @@ -0,0 +1,6 @@ +--- +eu: + activerecord: + attributes: + doorkeeper/application: + name: Aplikazioaren izena diff --git a/config/locales/doorkeeper.it.yml b/config/locales/doorkeeper.it.yml index e5a2d3f6e..50b2c9780 100644 --- a/config/locales/doorkeeper.it.yml +++ b/config/locales/doorkeeper.it.yml @@ -3,8 +3,10 @@ it: activerecord: attributes: doorkeeper/application: - name: Nome + name: Nome applicazione redirect_uri: URI di reindirizzamento + scopes: Scopi + website: Sito web applicazione errors: models: doorkeeper/application: @@ -33,9 +35,13 @@ it: redirect_uri: Usa una riga per URI scopes: Dividi gli scopes con spazi. Lascia vuoto per utilizzare gli scopes di default. index: + application: Applicazione callback_url: Callback URL + delete: Elimina name: Nome new: Nuova applicazione + scopes: Scopes + show: Mostra title: Le tue applicazioni new: title: Nuova applicazione @@ -43,7 +49,7 @@ it: actions: Azioni application_id: Id applicazione callback_urls: Callback urls - scopes: Scopes + scopes: Scopi secret: Secret title: 'Applicazione: %{name}' authorizations: @@ -57,7 +63,7 @@ it: prompt: L'applicazione %{client_name} richiede l'accesso al tuo account title: Autorizzazione richiesta show: - title: Copy this authorization code and paste it to the application. + title: Copia questo codice di autorizzazione e incollalo nell'applicazione. authorized_applications: buttons: revoke: Disabilita @@ -67,7 +73,7 @@ it: application: Applicazione created_at: Autorizzato date_format: "%d-%m-%Y %H:%M:%S" - scopes: Scopes + scopes: Scopi title: Applicazioni autorizzate errors: messages: @@ -104,7 +110,7 @@ it: admin: nav: applications: Applicazioni - oauth2_provider: OAuth2 Provider + oauth2_provider: Provider OAuth2 application: title: Autorizzazione OAuth richiesta scopes: diff --git a/config/locales/doorkeeper.zh-HK.yml b/config/locales/doorkeeper.zh-HK.yml index 4f46a416a..6eddcc27b 100644 --- a/config/locales/doorkeeper.zh-HK.yml +++ b/config/locales/doorkeeper.zh-HK.yml @@ -12,10 +12,10 @@ zh-HK: doorkeeper/application: attributes: redirect_uri: - fragment_present: URI 不可包含 "#fragment" 部份 - invalid_uri: 必需有正確的 URI. - relative_uri: 必需為完整 URI. - secured_uri: 必需使用有 HTTPS/SSL 加密的 URI. + fragment_present: URI 不可包含 "#fragment" 部份。 + invalid_uri: 必需有正確的 URI。 + relative_uri: 必需為完整 URI。 + secured_uri: 必需使用有 HTTPS/SSL 加密的 URI。 doorkeeper: applications: buttons: @@ -33,7 +33,7 @@ zh-HK: help: native_redirect_uri: 使用 %{native_redirect_uri} 作局部測試 redirect_uri: 每行輸入一個 URI - scopes: 請用半形空格分開權限範圍 (scope)。留空表示使用預設的權限範圍 + scopes: 請用半形空格分開權限範圍 (scope)。留空表示使用預設的權限範圍。 index: application: 應用 callback_url: 回傳網址 @@ -83,7 +83,7 @@ zh-HK: invalid_grant: 授權申請 (authorization grant) 不正確、過期、已被取消,或者無法對應授權請求 (authorization request) 內的轉接 URI,或者屬於別的用戶程式。 invalid_redirect_uri: 不正確的轉接網址。 invalid_request: 請求缺少了必要的參數、包含了不支援的參數、或者其他輸入錯誤。 - invalid_resource_owner: 資源擁有者的登入資訊錯誤、或者無法找到該資源擁有者。 + invalid_resource_owner: 資源擁有者的登入資訊錯誤、或者無法找到該資源擁有者 invalid_scope: 請求的權限範圍 (scope) 不正確、未有定義、或者輸入錯誤。 invalid_token: expired: access token 已經過期 @@ -94,7 +94,7 @@ zh-HK: temporarily_unavailable: 認證伺服器由於臨時負荷過重或者維護,目前未能處理請求。 unauthorized_client: 用戶程式無權用此方法 (method) 請行這個請求。 unsupported_grant_type: 授權伺服器不支援這個授權類型 (grant type)。 - unsupported_response_type: 授權伺服器不支援這個回應類型 (response type). + unsupported_response_type: 授權伺服器不支援這個回應類型 (response type)。 flash: applications: create: diff --git a/config/locales/el.yml b/config/locales/el.yml new file mode 100644 index 000000000..8741635e1 --- /dev/null +++ b/config/locales/el.yml @@ -0,0 +1,40 @@ +--- +el: + about: + about_mastodon_html: Το Mastodon είναι ένα κοινωνικό δίκτυο που βασίζεται σε ανοιχτά δικτυακά πρωτόκολλα και ελεύθερο λογισμικό ανοιχτού κώδικα. Είναι αποκεντρωμένο όπως το e-mail. + about_this: Σχετικά + administered_by: 'Διαχειρίζεται από:' + closed_registrations: Αυτή τη στιγμή οι εγγραφές σε αυτό τον διακομιστή είναι κλειστές. Αλλά! Μπορείς να βρεις έναν άλλο διακομιστή για να ανοίξεις λογαριασμό και να έχεις πρόσβαση από εκεί στο ίδιο ακριβώς δίκτυο. + contact: Επικοινωνία + contact_missing: Δεν έχει οριστεί + domain_count_after: άλλοι διακομιστές + domain_count_before: Συνδέεται με + extended_description_html: | + <h3>Ένα καλό σημείο για κανόνες</h3> + <p>Η αναλυτική περιγραφή δεν έχει ακόμα οριστεί</p> + features: + humane_approach_body: Μαθαίνοντας από τις αποτυχίες άλλων δικτύων, το Mastodon στοχεύει να κάνει σχεδιαστικά ηθικές επιλογές για να καταπολεμήσει την κακόβουλη χρήση των κοινωνικών δικτύων. + humane_approach_title: Μια πιο ανθρώπινη προσέγγιση + not_a_product_title: Είσαι άτομο, όχι προϊόν + real_conversation_title: Φτιαγμένο για αληθινή συζήτηση + within_reach_body: Οι πολλαπλές εφαρμογές για το iOS, το Android και τις υπόλοιπες πλατφόρμες, χάρη σε ένα φιλικό προς τους προγραμματιστές οικοσύστημα API, σου επιτρέπουν να κρατάς επαφή με τους φίλους και τις φίλες σου οπουδήποτε. + generic_description: "%{domain} είναι ένας εξυπηρετητής στο δίκτυο" + hosted_on: Το Mastodon φιλοξενείται στο %{domain} + learn_more: Μάθε περισσότερα + other_instances: Λίστα διακομιστών + source_code: Πηγαίος κώδικας + status_count_after: καταστάσεις + status_count_before: Ποιός συνέγραψε + user_count_after: χρήστες + what_is_mastodon: Τι είναι το Mastodon; + accounts: + follow: Ακολούθησε + followers: Ακόλουθοι + following: Ακολουθεί + media: Πολυμέσα + moved_html: 'Ο/Η %{name} μετακόμισε στο %{new_profile_link}:' + nothing_here: Δεν υπάρχει τίποτα εδώ! + people_followed_by: Χρήστες που ακολουθεί ο/η %{name} + people_who_follow: Χρήστες που ακολουθούν τον/την %{name} + posts: Τουτ + posts_with_replies: Τουτ και απαντήσεις diff --git a/config/locales/en.yml b/config/locales/en.yml index 645999d66..4c7c5078c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -260,40 +260,29 @@ en: destroyed_msg: Report note successfully deleted! reports: account: - created_reports: Reports created by this account - moderation: - silenced: Silenced - suspended: Suspended - title: Moderation - moderation_notes: Moderation Notes note: note report: report - targeted_reports: Reports made about this account action_taken_by: Action taken by are_you_sure: Are you sure? assign_to_self: Assign to me - assigned: Assigned Moderator + assigned: Assigned moderator comment: - label: Report Comment none: None created_at: Reported delete: Delete - history: Moderation History id: ID mark_as_resolved: Mark as resolved mark_as_unresolved: Mark as unresolved notes: - create: Add Note - create_and_resolve: Resolve with Note - create_and_unresolve: Reopen with Note + create: Add note + create_and_resolve: Resolve with note + create_and_unresolve: Reopen with note delete: Delete - label: Moderator Notes - new_label: Add Moderator Note placeholder: Describe what actions have been taken, or any other updates to this report… nsfw: 'false': Unhide media attachments 'true': Hide media attachments - reopen: Reopen Report + reopen: Reopen report report: 'Report #%{id}' report_contents: Contents reported_account: Reported account @@ -302,7 +291,6 @@ en: resolved_msg: Report successfully resolved! silence_account: Silence account status: Status - statuses: Reported Toots suspend_account: Suspend account target: Target title: Reports @@ -366,8 +354,8 @@ en: back_to_account: Back to account page batch: delete: Delete - nsfw_off: NSFW OFF - nsfw_on: NSFW ON + nsfw_off: Mark as not sensitive + nsfw_on: Mark as sensitive execute: Execute failed_to_execute: Failed to execute media: @@ -707,6 +695,9 @@ en: one: "%{count} video" other: "%{count} videos" content_warning: 'Content warning: %{warning}' + disallowed_hashtags: + one: 'contained a disallowed hashtag: %{tags}' + other: 'contained the disallowed hashtags: %{tags}' open_in_web: Open in web over_character_limit: character limit of %{max} exceeded pin_errors: diff --git a/config/locales/eo.yml b/config/locales/eo.yml index 27c62f899..c768d8a03 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -243,7 +243,6 @@ eo: action_taken_by: Ago farita de are_you_sure: Ĉu vi certas? comment: - label: Komento none: Nenio delete: Forigi id: ID diff --git a/config/locales/es.yml b/config/locales/es.yml index 74045074e..bf449bf92 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -4,6 +4,7 @@ es: about_hashtag_html: Estos son toots públicos etiquetados con <strong>#%{hashtag}</strong>. Puedes interactuar con ellos si tienes una cuenta en cualquier parte del fediverso. about_mastodon_html: Mastodon es un servidor de red social <em>libre y de código abierto</em>. Una alternativa <em>descentralizada</em> a plataformas comerciales, que evita el riesgo de que una única compañía monopolice tu comunicación. Cualquiera puede ejecutar Mastodon y participar sin problemas en la <em>red social</em>. about_this: Acerca de esta instancia + administered_by: 'Administrado por:' closed_registrations: Los registros están actualmente cerrados en esta instancia. contact: Contacto contact_missing: No especificado @@ -60,7 +61,15 @@ es: destroyed_msg: "¡Nota de moderación destruida con éxito!" accounts: are_you_sure: "¿Estás seguro?" + avatar: Avatar by_domain: Dominio + change_email: + changed_msg: "¡El correo electrónico se ha actualizado correctamente!" + current_email: Correo electrónico actual + label: Cambiar el correo electrónico + new_email: Nuevo correo electrónico + submit: Cambiar el correo electrónico + title: Cambiar el correo electrónico de %{username} confirm: Confirmar confirmed: Confirmado demote: Degradar @@ -108,6 +117,7 @@ es: public: Público push_subscription_expires: Expiración de la suscripción PuSH redownload: Refrescar avatar + remove_avatar: Eliminar el avatar reset: Reiniciar reset_password: Reiniciar contraseña resubscribe: Re-suscribir @@ -128,6 +138,7 @@ es: statuses: Estados subscribe: Suscribir title: Cuentas + unconfirmed_email: Correo electrónico sin confirmar undo_silenced: Des-silenciar undo_suspension: Des-suspender unsubscribe: Desuscribir @@ -135,6 +146,8 @@ es: web: Web action_logs: actions: + assigned_to_self_report: "%{name} se ha asignado la denuncia %{target} a sí mismo" + change_email_user: "%{name} ha cambiado la dirección de correo del usuario %{target}" confirm_user: "%{name} confirmó la dirección de correo del usuario %{target}" create_custom_emoji: "%{name} subió un nuevo emoji %{target}" create_domain_block: "%{name} bloqueó el dominio %{target}" @@ -150,10 +163,13 @@ es: enable_user: "%{name} habilitó el acceso del usuario %{target}" memorialize_account: "%{name} convirtió la cuenta de %{target} en una página de memorial" promote_user: "%{name} promoción al usuario %{target}" + remove_avatar_user: "%{name} ha eliminado el avatar de %{target}" + reopen_report: "%{name} ha reabierto la denuncia %{target}" reset_password_user: "%{name} restauró la contraseña del usuario %{target}" - resolve_report: "%{name} desestimó el reporte %{target}" + resolve_report: "%{name} ha resuelto la denuncia %{target}" silence_account: "%{name} silenció la cuenta de %{target}" suspend_account: "%{name} suspendió la cuenta de %{target}" + unassigned_report: "%{name} ha desasignado la denuncia %{target}" unsilence_account: "%{name} desactivó el silenciado de la cuenta de %{target}" unsuspend_account: "%{name} desactivó la suspensión de la cuenta de %{target}" update_custom_emoji: "%{name} actualizó el emoji %{target}" @@ -239,29 +255,48 @@ es: expired: Expiradas title: Filtrar title: Invitaciones + report_notes: + created_msg: "¡El registro de la denuncia se ha creado correctamente!" + destroyed_msg: "¡El registro de la denuncia se ha borrado correctamente!" reports: + account: + note: nota + report: denuncia action_taken_by: Acción tomada por are_you_sure: "¿Estás seguro?" + assign_to_self: Asignármela a mí + assigned: Moderador asignado comment: - label: Comentario none: Ninguno + created_at: Denunciado delete: Eliminar id: ID mark_as_resolved: Marcar como resuelto + mark_as_unresolved: Marcar como no resuelto + notes: + create: Añadir una nota + create_and_resolve: Resolver con una nota + create_and_unresolve: Reabrir con una nota + delete: Eliminar + placeholder: Especificar qué acciones se han tomado o cualquier otra novedad respecto a esta denuncia… nsfw: 'false': Mostrar multimedia 'true': Ocultar multimedia + reopen: Reabrir denuncia report: 'Reportar #%{id}' report_contents: Contenido reported_account: Cuenta reportada reported_by: Reportado por resolved: Resuelto + resolved_msg: "¡La denuncia se ha resuelto correctamente!" silence_account: Silenciar cuenta status: Estado suspend_account: Suspender cuenta target: Objetivo title: Reportes + unassign: Desasignar unresolved: No resuelto + updated_at: Actualizado view: Ver settings: activity_api_enabled: @@ -319,8 +354,8 @@ es: back_to_account: Volver a la cuenta batch: delete: Eliminar - nsfw_off: NSFW OFF - nsfw_on: NSFW ON + nsfw_off: Marcar contenido como no sensible + nsfw_on: Marcar contenido como sensible execute: Ejecutar failed_to_execute: Falló al ejecutar media: @@ -382,6 +417,7 @@ es: security: Cambiar contraseña set_new_password: Establecer nueva contraseña authorize_follow: + already_following: Ya estás siguiendo a esta cuenta error: Desafortunadamente, ha ocurrido un error buscando la cuenta remota follow: Seguir follow_request: 'Tienes una solicitud de seguimiento de:' @@ -474,6 +510,7 @@ es: '21600': 6 horas '3600': 1 hora '43200': 12 horas + '604800': 1 semana '86400': 1 día expires_in_prompt: Nunca generate: Generar @@ -577,6 +614,10 @@ es: missing_resource: No se pudo encontrar la URL de redirección requerida para tu cuenta proceed: Proceder a seguir prompt: 'Vas a seguir a:' + remote_unfollow: + error: Error + title: Título + unfollowed: Ha dejado de seguirse sessions: activity: Última actividad browser: Navegador @@ -643,6 +684,9 @@ es: one: "%{count} vídeo" other: "%{count} vídeos" content_warning: 'Alerta de contenido: %{warning}' + disallowed_hashtags: + one: 'contenía un hashtag no permitido: %{tags}' + other: 'contenía los hashtags no permitidos: %{tags}' open_in_web: Abrir en web over_character_limit: Límite de caracteres de %{max} superado pin_errors: @@ -692,7 +736,7 @@ es: title: Descargar archivo welcome: edit_profile_action: Configurar el perfil - edit_profile_step: Puedes personalizar tu perfil subiendo un avatar, cabecera, cambiando tu nombre para mostrar y más. Si te gustaría revisar seguidores antes de autorizarlos a que te sigan, puedes bloquear tu cuenta. + edit_profile_step: Puedes personalizar tu perfil subiendo un avatar, una cabecera, cambiando tu nombre de usuario y más cosas. Si quieres revisar a tus nuevos seguidores antes de que se les permita seguirte, puedes bloquear tu cuenta. explanation: Aquí hay algunos consejos para empezar final_action: Empezar a publicar final_step: '¡Empieza a publicar! Incluso sin seguidores, tus mensajes públicos pueden ser vistos por otros, por ejemplo en la linea de tiempo local y con "hashtags". Podrías querer introducirte con el "hashtag" #introductions.' @@ -702,7 +746,7 @@ es: review_preferences_step: Asegúrate de poner tus preferencias, como que correos te gustaría recibir, o que nivel de privacidad te gustaría que tus publicaciones tengan por defecto. Si no tienes mareos, podrías elegir habilitar la reproducción automática de "GIFs". subject: Bienvenido a Mastodon tip_bridge_html: Si esta viniendo desde Twitter, puedes encontrar a tus amigos en Mastodon usando la <a href="%{bridge_url}">aplicación puente</a>. Aunque solo funciona si ellos también usaron la aplicación puente! - tip_federated_timeline: La historia federada es una vista de toda la red Mastodon conocida. Sólo incluye gente a la que se han suscrito personas de tu instancia, así que no está completa. + tip_federated_timeline: La línea de tiempo federada es una vista de la red de Mastodon. Pero solo incluye gente que tus vecinos están siguiendo, así que no está completa. tip_following: Sigues a tus administradores de servidor por defecto. Para encontrar más gente interesante, revisa las lineas de tiempo local y federada. tip_local_timeline: La linea de tiempo local is una vista de la gente en %{instance}. Estos son tus vecinos inmediatos! tip_mobile_webapp: Si el navegador de tu dispositivo móvil ofrece agregar Mastodon a tu página de inicio, puedes recibir notificaciones. Actúa como una aplicación nativa en muchas formas! diff --git a/config/locales/eu.yml b/config/locales/eu.yml new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/config/locales/eu.yml @@ -0,0 +1 @@ +{} diff --git a/config/locales/fa.yml b/config/locales/fa.yml index ed25ea8c9..a3005547a 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -243,7 +243,6 @@ fa: action_taken_by: انجامدهنده are_you_sure: آیا مطمئن هستید؟ comment: - label: توضیح none: خالی delete: پاککردن id: شناسه diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 62f6560bf..550ad1805 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -243,7 +243,6 @@ fi: action_taken_by: Toimenpiteen tekijä are_you_sure: Oletko varma? comment: - label: Kommentti none: Ei mitään delete: Poista id: Tunniste diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 1689754a0..0579123dc 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -4,6 +4,7 @@ fr: about_hashtag_html: Figurent ci-dessous les pouets tagués avec <strong>#%{hashtag}</strong>. Vous pouvez interagir avec eux si vous avez un compte n’importe où dans le Fediverse. about_mastodon_html: Mastodon est un réseau social utilisant des formats ouverts et des logiciels libres. Comme le courriel, il est décentralisé. about_this: À propos + administered_by: 'Administré par :' closed_registrations: Les inscriptions sont actuellement fermées sur cette instance. Cependant, vous pouvez trouver une autre instance sur laquelle vous créer un compte et à partir de laquelle vous pourrez accéder au même réseau. contact: Contact contact_missing: Manquant @@ -60,7 +61,15 @@ fr: destroyed_msg: Note de modération supprimée avec succès ! accounts: are_you_sure: Êtes-vous certain⋅e ? + avatar: Avatar by_domain: Domaine + change_email: + changed_msg: Courriel du compte modifié avec succès ! + current_email: Courriel actuel + label: Modifier le courriel + new_email: Nouveau courriel + submit: Modifier le courriel + title: Modifier le courriel pour %{username} confirm: Confirmer confirmed: Confirmé demote: Rétrograder @@ -108,6 +117,7 @@ fr: public: Publique push_subscription_expires: Expiration de l’abonnement PuSH redownload: Rafraîchir les avatars + remove_avatar: Supprimer l'avatar reset: Réinitialiser reset_password: Réinitialiser le mot de passe resubscribe: Se réabonner @@ -128,6 +138,7 @@ fr: statuses: Statuts subscribe: S’abonner title: Comptes + unconfirmed_email: Courriel non-confirmé undo_silenced: Démasquer undo_suspension: Annuler la suspension unsubscribe: Se désabonner @@ -135,6 +146,8 @@ fr: web: Web action_logs: actions: + assigned_to_self_report: "%{name} s'est assigné le signalement de %{target} à eux-même" + change_email_user: "%{name} a modifié l'adresse de courriel de l'utilisateur %{target}" confirm_user: "%{name} adresse courriel confirmée de l'utilisateur %{target}" create_custom_emoji: "%{name} a importé de nouveaux emoji %{target}" create_domain_block: "%{name} a bloqué le domaine %{target}" @@ -150,10 +163,13 @@ fr: enable_user: "%{name} a activé le login pour l'utilisateur %{target}" memorialize_account: "%{name} a transformé le compte de %{target} en une page de mémorial" promote_user: "%{name} a promu l'utilisateur %{target}" + remove_avatar_user: "%{name} a supprimé l'avatar de %{target}'s" + reopen_report: "%{name} a ré-ouvert le signalement %{target}" reset_password_user: "%{name} a réinitialisé le mot de passe de %{target}" - resolve_report: "%{name} n'a pas pris en compte la dénonciation de %{target}" + resolve_report: "%{name} a résolu la dénonciation de %{target}" silence_account: "%{name} a mis le compte %{target} en mode silence" suspend_account: "%{name} a suspendu le compte %{target}" + unassigned_report: "%{name} a dés-assigné le signalement %{target}" unsilence_account: "%{name} a mis fin au mode silence de %{target}" unsuspend_account: "%{name} a réactivé le compte de %{target}" update_custom_emoji: "%{name} a mis à jour l'emoji %{target}" @@ -239,29 +255,48 @@ fr: expired: Expiré title: Filtre title: Invitations + report_notes: + created_msg: Note de signalement créée avec succès ! + destroyed_msg: Note de signalement effacée avec succès ! reports: + account: + note: note + report: signaler action_taken_by: Intervention de are_you_sure: Êtes vous certain⋅e ? + assign_to_self: Me l'assigner + assigned: Modérateur assigné comment: - label: Commentaire none: Aucun + created_at: Signalé delete: Supprimer id: ID mark_as_resolved: Marquer comme résolu + mark_as_unresolved: Marquer comme non-résolu + notes: + create: Ajouter une note + create_and_resolve: Résoudre avec une note + create_and_unresolve: Ré-ouvrir avec une note + delete: Effacer + placeholder: Décrivez quelles actions ont été prises, ou toute autre mise à jour de ce signalement… nsfw: 'false': Ré-afficher les médias 'true': Masquer les médias + reopen: Ré-ouvrir le signalement report: 'Signalement #%{id}' report_contents: Contenu reported_account: Compte signalé reported_by: Signalé par resolved: Résolus + resolved_msg: Signalement résolu avec succès ! silence_account: Masquer le compte status: Statut suspend_account: Suspendre le compte target: Cible title: Signalements + unassign: Dés-assigner unresolved: Non résolus + updated_at: Mis à jour view: Voir settings: activity_api_enabled: @@ -319,8 +354,8 @@ fr: back_to_account: Retour à la page du compte batch: delete: Supprimer - nsfw_off: NSFW OFF - nsfw_on: NSFW ON + nsfw_off: Marquer comme non-sensible + nsfw_on: Marquer comme sensible execute: Exécuter failed_to_execute: Erreur d’exécution media: @@ -382,6 +417,7 @@ fr: security: Sécurité set_new_password: Définir le nouveau mot de passe authorize_follow: + already_following: Vous suivez déjà ce compte error: Malheureusement, il y a eu une erreur en cherchant les détails du compte distant follow: Suivre follow_request: 'Vous avez demandé à suivre :' @@ -474,6 +510,7 @@ fr: '21600': 6 heures '3600': 1 heure '43200': 12 heures + '604800': 1 semaine '86400': 1 jour expires_in_prompt: Jamais generate: Générer @@ -577,6 +614,10 @@ fr: missing_resource: L’URL de redirection n’a pas pu être trouvée proceed: Continuez pour suivre prompt: 'Vous allez suivre :' + remote_unfollow: + error: Erreur + title: Titre + unfollowed: Non-suivi sessions: activity: Dernière activité browser: Navigateur @@ -642,6 +683,10 @@ fr: video: one: "%{count} vidéo" other: "%{count} vidéos" + content_warning: 'Attention au contenu : %{warning}' + disallowed_hashtags: + one: 'contient un hashtag désactivé : %{tags}' + other: 'contient les hashtag désactivés : %{tags}' open_in_web: Ouvrir sur le web over_character_limit: limite de caractères dépassée de %{max} caractères pin_errors: diff --git a/config/locales/gl.yml b/config/locales/gl.yml index f4ca7e8c5..093fa70fe 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -4,6 +4,7 @@ gl: about_hashtag_html: Estas son mensaxes públicas etiquetadas con <strong>#%{hashtag}</strong>. Pode interactuar con elas si ten unha conta nalgures do fediverso. about_mastodon_html: Mastodon é unha rede social que se basea en protocolos web abertos e libres, software de código aberto. É descentralizada como o correo electrónico. about_this: Sobre + administered_by: 'Administrada por:' closed_registrations: O rexistro en esta instancia está pechado en este intre. Porén! Pode atopar unha instancia diferente para obter unha conta e ter acceso exactamente a misma rede desde alí. contact: Contacto contact_missing: Non establecido @@ -60,7 +61,15 @@ gl: destroyed_msg: Nota a moderación destruída con éxito! accounts: are_you_sure: Está segura? + avatar: Avatar by_domain: Dominio + change_email: + changed_msg: Cambiouse correctamente o correo-e da conta! + current_email: Correo-e actual + label: Cambiar correo-e + new_email: Novo correo-e + submit: Cambiar correo-e + title: Cambiar o correo-e de %{username} confirm: Confirmar confirmed: Confirmado demote: Degradar @@ -108,6 +117,7 @@ gl: public: Público push_subscription_expires: A suscrición PuSH caduca redownload: Actualizar avatar + remove_avatar: Eliminar avatar reset: Restablecer reset_password: Restablecer contrasinal resubscribe: Voltar a suscribir @@ -128,6 +138,7 @@ gl: statuses: Estados subscribe: Subscribir title: Contas + unconfirmed_email: Correo-e non confirmado undo_silenced: Desfacer acalar undo_suspension: Desfacer suspensión unsubscribe: Non subscribir @@ -135,6 +146,8 @@ gl: web: Web action_logs: actions: + assigned_to_self_report: "%{name} asignou o informe %{target} a ela misma" + change_email_user: "%{name} cambiou o enderezo de correo-e da usuaria %{target}" confirm_user: "%{name} comfirmou o enderezo de correo da usuaria %{target}" create_custom_emoji: "%{name} subeu un novo emoji %{target}" create_domain_block: "%{name} bloqueou o dominio %{target}" @@ -150,10 +163,13 @@ gl: enable_user: "%{name} habilitou a conexión para a usuaria %{target}" memorialize_account: "%{name} converteu a conta de %{target} nunha páxina para a lembranza" promote_user: "%{name} promoveu a usuaria %{target}" + remove_avatar_user: "%{name} eliminou o avatar de %{target}" + reopen_report: "%{name} voltou abrir informe %{target}" reset_password_user: "%{name} restableceu o contrasinal da usuaria %{target}" - resolve_report: "%{name} rexeitou o informe %{target}" + resolve_report: "%{name} solucionou o informe %{target}" silence_account: "%{name} acalou a conta de %{target}" suspend_account: "%{name} suspendeu a conta de %{target}" + unassigned_report: "%{name} non asignou informe %{target}" unsilence_account: "%{name} deulle voz a conta de %{target}" unsuspend_account: "%{name} activou a conta de %{target}" update_custom_emoji: "%{name} actualizou emoji %{target}" @@ -239,29 +255,48 @@ gl: expired: Cadudado title: Filtro title: Convida + report_notes: + created_msg: Creouse correctamente a nota do informe! + destroyed_msg: Nota do informe eliminouse con éxito! reports: + account: + note: nota + report: informe action_taken_by: Acción tomada por are_you_sure: Está segura? + assign_to_self: Asignarmo + assigned: Moderador asignado comment: - label: Comentario none: Nada + created_at: Reportado delete: Eliminar id: ID mark_as_resolved: Marcar como resolto + mark_as_unresolved: Marcar como non resolto + notes: + create: Engadir nota + create_and_resolve: Resolver con nota + create_and_unresolve: Voltar a abrir con nota + delete: Eliminar + placeholder: Describir qué decisións foron tomadas, ou calquer actualización a este informe… nsfw: 'false': Non agochar anexos de medios 'true': Agochar anexos de medios + reopen: Voltar a abrir o informe report: 'Informe #%{id}' report_contents: Contidos reported_account: Conta reportada reported_by: Reportada por resolved: Resolto + resolved_msg: Resolveuse con éxito o informe! silence_account: Acalar conta status: Estado suspend_account: Suspender conta target: Obxetivo title: Informes + unassign: Non asignar unresolved: Non resolto + updated_at: Actualizado view: Vista settings: activity_api_enabled: @@ -319,8 +354,8 @@ gl: back_to_account: Voltar a páxina da conta batch: delete: Eliminar - nsfw_off: NSFW apagado - nsfw_on: NSFW acendido + nsfw_off: Marcar como non sensible + nsfw_on: Marcar como sensible execute: Executar failed_to_execute: Fallou a execución media: @@ -382,6 +417,7 @@ gl: security: Seguridade set_new_password: Establecer novo contrasinal authorize_follow: + already_following: Xa está a seguir esta conta error: Desgraciadamente, algo fallou ao buscar a conta remota follow: Seguir follow_request: 'Enviou unha petición de seguimento a:' @@ -474,6 +510,7 @@ gl: '21600': 6 horas '3600': 1 hora '43200': 12 horas + '604800': 1 semana '86400': 1 día expires_in_prompt: Nunca generate: Xerar @@ -577,6 +614,10 @@ gl: missing_resource: Non se puido atopar o URL de redirecionamento requerido para a súa conta proceed: Proceda para seguir prompt: 'Vostede vai seguir:' + remote_unfollow: + error: Fallo + title: Título + unfollowed: Deixou de seguir sessions: activity: Última actividade browser: Navegador @@ -643,6 +684,9 @@ gl: one: "%{count} vídeo" other: "%{count} vídeos" content_warning: 'Aviso sobre o contido: %{warning}' + disallowed_hashtags: + one: 'contiña unha etiqueta non permitida: %{tags}' + other: 'contiña etiquetas non permitidas: %{tags}' open_in_web: Abrir na web over_character_limit: Excedeu o límite de caracteres %{max} pin_errors: @@ -662,9 +706,86 @@ gl: stream_entries: click_to_show: Pulse para mostrar pinned: Mensaxe fixada - reblogged: promocionada + reblogged: promovida sensitive_content: Contido sensible terms: + body_html: | + <h2>Intimidade</h2> + <h3 id="collect">Qué información recollemos?</h3> + + <ul> + <li><em>Información básica da conta</em>: Si se rexistra en este servidor, pediráselle un nome de usuaria, un enderezo de correo electrónico e un contrasinal. De xeito adicional tamén poderá introducir información como un nome público e biografía, tamén subir unha fotografía de perfil e unha imaxe para a cabeceira. O nome de usuaria, o nome público, a biografía e as imaxes de perfil e cabeceira sempre se mostran publicamente.</li> + <li><em>Publicacións, seguimento e outra información pública</em>: O listado das persoas que segue é un listado público, o mesmo acontece coas súas seguidoras. Cando evía unha mensaxe, a data e hora gárdanse así como o aplicativo que utilizou para enviar a mensaxe. As publicacións poderían conter ficheiros de medios anexos, como fotografías e vídeos. As publicacións públicas e as non listadas están dispoñibles de xeito público. Cando destaca unha publicación no seu perfil tamén é pública. As publicacións son enviadas as súas seguidoras, en algúns casos pode acontecer que estén en diferentes servidores e gárdanse copias neles. Cando elemina unha publicación tamén se envía as súas seguidoras. A acción de voltar a publicar ou marcar como favorita outra publicación sempre é pública.</li> + <li><em>Mensaxes directas e só para seguidoras</em>: Todas as mensaxes gárdanse e procésanse no servidor. As mensaxes só para seguidoras son entregadas as súas seguidoras e as usuarias que son mencionadas en elas, e as mensaxes directas entréganse só as usuarias mencionadas en elas. En algúns casos esto implica que son entregadas a diferentes servidores e gárdanse copias alí. Facemos un esforzo sincero para limitar o acceso a esas publicacións só as persoas autorizadas, pero outros servidores poderían non ser tan escrupulosos. Polo tanto, é importante revisar os servidores onde se hospedan as súas seguidoras. Nos axustes pode activar a opción de aprovar ou rexeitar novas seguidoras de xeito manual. <em>Teña en conta que a administración do servidor e todos os outros servidores implicados poden ver as mensaxes.</em>, e as destinatarias poderían facer capturas de pantalla, copiar e voltar a compartir as mensaxes. <em>Non comparta información comprometida en Mastodon.</em></li> + <li><em>IPs e outros metadatos</em>: Cando se conecta, gravamos o IP desde onde se conecta, así como o nome do aplicativo desde onde o fai. Todas as sesións conectadas están dispoñibles para revisar e revogar nos axustes. O último enderezo IP utilizado gárdase ate por 12 meses. Tamén poderiamos gardar informes do servidor que inclúan o enderezo IP de cada petición ao servidor.</li> + </ul> + + <hr class="spacer" /> + + <h3 id="use">De qué xeito utilizamos os seus datos?</h3> + + <p>Toda a información que recollemos podería ser utilizada dos seguintes xeitos:</p> + + <ul> + <li>Para proporcionar a funcionabiliade básica de Mastodon. Só pode interactuar co contido de outra xente e publicar o seu propio contido si está conectada. Por exemplo, podería seguir outra xente e ver as súas publicacións combinadas nunha liña temporal inicial personalizada.</li> + <li>Para axudar a moderar a comunidade, por exemplo comparando o seu enderezo IP con outros coñecidos para evitar esquivar os rexeitamentos ou outras infraccións.</li> + <li>O endero de correo electrónico que nos proporciona podería ser utilizado para enviarlle información, notificacións sobre outra xente que interactúa cos seus contidos ou lle envía mensaxes, e para respostar a consultas, e/ou outras cuestións ou peticións.</li> + </ul> + + <hr class="spacer" /> + + <h3 id="protect">Cómo proxetemos os seus datos?</h3> + + <p>Implementamos varias medidas de seguridade para protexer os seus datos personais cando introduce, envía ou accede a súa información personal. Entre outras medidas, a súa sesión de navegación, así como o tráfico entre os seus aplicativos e o API están aseguradas mediante SSL, e o seu contrasinal está camuflado utilizando un algoritmo potente de unha sóa vía. Pode habilitar a autenticación de doble factor para protexer o acceso a súa conta aínda máis.</p> + + <hr class="spacer" /> + + <h3 id="data-retention">Cal é a nosa política de retención de datos?</h3> + + <p>Faremos un sincero esforzo en:</p> + + <ul> + <li>Protexer informes do servidor que conteñan direccións IP das peticións ao servidor, ate a data estos informes gárdanse por non máis de 90 días.</li> + <li>Reter os enderezos IP asociados con usuarias rexistradas non máis de 12 meses.</li> + </ul> + + <p>Pode solicitar e descargar un ficheiro cos seus contidos, incluíndo publicacións, anexos de medios, imaxes de perfil e imaxe da cabeceira.</p> + + <p>En calquer momento pode eliminar de xeito irreversible a súa conta.</p> + + <hr class="spacer"/> + + <h3 id="cookies">Utilizamos testemuños?</h3> + + <p>Si. Os testemuños son pequenos ficheiros que un sitio web ou o provedor de servizo transfiren ao disco duro da súa computadora a través do navegador web (si vostede o permite). Estos testemuños posibilitan ao sitio web recoñecer o seu navegador e, si ten unha conta rexistrada, asocialo con dita conta.</p> + + <p>Utilizamos testemuños para comprender e gardar as súas preferencias para futuras visitas.</p> + + <hr class="spacer" /> + + <h3 id="disclose">Entregamos algunha información a terceiras alleas?</h3> + + <p>Non vendemos, negociamos ou transferimos de algún xeito a terceiras partes alleas a súa información identificativa persoal. Esto non inclúe terceiras partes de confianza que nos axudan a operar o sitio web, a xestionar a empresa, ou darlle servizo si esas partes aceptan manter esa información baixo confidencialidade. Poderiamos liberar esa información si cremos que eso da cumplimento axeitado a lei, reforza as políticas do noso sitio ou protexe os nosos, e de outros, dereitos, propiedade ou seguridade.</p> + + <p>O seu contido público podería ser descargado por outros servidores na rede. As súas publicacións públicas e para só seguidoras son entregadas aos servidores onde residen as súas seguidoras na rede, e as mensaxes directas son entregadas aos servidores das destinatarias sempre que esas seguidoras ou destinatarios residan en servidores distintos de este.</p> + + <p>Cado autoriza a este aplicativo a utilizar a súa conta, dependendo da amplitude dos permisos que autorice, podería acceder a información pública de perfil, ao listado de seguimento, as súas seguidoras, os seus listados, todas as súas publicacións, as publicacións favoritas. Os aplicativos non poden nunca acceder ao seu enderezo de correo nin ao seu contrasinal.</p> + + <hr class="spacer" /> + + <h3 id="coppa">Children's Online Privacy Protection Act Compliance</h3> + + <p>O noso sitio, productos e servizos diríxense a persoas que teñen un mínimo de 13 anos. Si este servidor está en EEUU, e ten vostede menos de 13 anos, a requerimento da COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) non utilice este sitio.</p> + + <hr class="spacer" /> + + <h3 id="changes">Cambios na nosa política de intimidade</h3> + + <p>Si decidimos cambiar a nosa política de intimidade publicaremos os cambios en esta páxina.</p> + + <p>Este documento ten licenza CC-BY-SA. Actualizouse o 7 de Marzo de 2018.</p> + + <p>Adaptado do orixinal <a href="https://github.com/discourse/discourse">Discourse privacy policy</a>.</p> title: "%{instance} Termos do Servizo e Política de Intimidade" themes: default: Mastodon diff --git a/config/locales/he.yml b/config/locales/he.yml index 1a7c84d7c..d641c6e1a 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -180,7 +180,6 @@ he: reports: are_you_sure: 100% על בטוח? comment: - label: הערה none: ללא delete: מחיקה id: ID diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 2560b3816..7fe431d37 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -243,7 +243,6 @@ hu: action_taken_by: 'Kezelte:' are_you_sure: Biztos vagy benne? comment: - label: Hozzászólás none: Egyik sem delete: Törlés id: ID diff --git a/config/locales/id.yml b/config/locales/id.yml index 0ef1d5040..5a63b8038 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -106,7 +106,6 @@ id: title: Server yang diketahui reports: comment: - label: Komentar none: Tidak ada delete: Hapus id: ID diff --git a/config/locales/io.yml b/config/locales/io.yml index 29ab4516b..7c25acc47 100644 --- a/config/locales/io.yml +++ b/config/locales/io.yml @@ -105,7 +105,6 @@ io: title: Known Instances reports: comment: - label: Comment none: None delete: Delete id: ID diff --git a/config/locales/it.yml b/config/locales/it.yml index 7e5bfd20e..0518d20e6 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -3,43 +3,312 @@ it: about: about_mastodon_html: Mastodon è un social network <em>gratuito e open-source</em>. Un'alternativa <em>decentralizzata</em> alle piattaforme commerciali che evita che una singola compagnia monopolizzi il tuo modo di comunicare. Scegli un server di cui ti fidi — qualunque sia la tua scelta, potrai interagire con chiunque altro. Chiunque può sviluppare un suo server Mastodon e partecipare alla vita del <em>social network</em>. about_this: A proposito di questo server - closed_registrations: Al momento le iscrizioni a questo server sono chiuse. + administered_by: 'Amministrato da:' + closed_registrations: Al momento le iscrizioni a questo server sono chiuse. Tuttavia! Puoi provare a cercare un istanza diversa su cui creare un account ed avere accesso alla stessa identica rete di questa. contact: Contatti + contact_missing: Non impostato + contact_unavailable: N/D description_headline: Cos'è %{domain}? domain_count_after: altri server domain_count_before: Connesso a - other_instances: Altri server + features: + humane_approach_body: Imparando dai fallimenti degli altri networks, Mastodon mira a fare scelte di design etico per combattere l'abuso dei social media. + humane_approach_title: Un approccio più umano + not_a_product_body: Mastodon non è una rete commerciale. Niente pubblicità, niente data mining, nessun giardino murato. Non c'è nessuna autorità centrale. + not_a_product_title: Tu sei una persona, non un prodotto + real_conversation_body: Con 500 caratteri a disposizione, un supporto per i contenuti granulari ed avvisi sui media potrai esprimerti nel modo desiderato. + real_conversation_title: Creato per conversazioni reali + within_reach_body: Apps per iOS, Android ed altre piattaforme, realizzate grazie ad un ecosistema di API adatto agli sviluppatori, ti consentono di poter stare ovunque al passo con i tuoi amici. + within_reach_title: Sempre a portata di mano + generic_description: "%{domain} è un server nella rete" + hosted_on: Mastodon ospitato su %{domain} + learn_more: Scopri altro + other_instances: Elenco istanze source_code: Codice sorgente - status_count_after: status + status_count_after: stati status_count_before: Che hanno pubblicato user_count_after: utenti - user_count_before: Casa di + user_count_before: Home di + what_is_mastodon: Che cos'è Mastodon? accounts: follow: Segui followers: Seguaci following: Seguiti + media: Media + moved_html: "%{name} è stato spostato su %{new_profile_link}:" nothing_here: Qui non c'è nulla! people_followed_by: Persone seguite da %{name} people_who_follow: Persone che seguono %{name} posts: Posts + posts_with_replies: Toot e repliche remote_follow: Segui da remoto + reserved_username: Il nome utente è riservato + roles: + admin: Amministratore + moderator: Mod unfollow: Non seguire più + admin: + account_moderation_notes: + account: Moderatore + create: Crea + created_at: Data + created_msg: Nota di moderazione creata con successo! + delete: Elimina + destroyed_msg: Nota di moderazione distrutta con successo! + accounts: + are_you_sure: Sei sicuro? + avatar: Avatar + by_domain: Dominio + change_email: + changed_msg: Account email cambiato con successo! + current_email: Email corrente + label: Cambia email + new_email: Nuova email + submit: Cambia email + title: Cambia email per %{username} + confirm: Conferma + confirmed: Confermato + demote: Declassa + disable: Disabilita + disable_two_factor_authentication: Disabilita 2FA + disabled: Disabilitato + display_name: Nome visualizzato + domain: Dominio + edit: Modifica + email: Email + enable: Abilita + enabled: Abilitato + feed_url: URL Feed + followers: Follower + followers_url: URL follower + follows: Follows + inbox_url: URL inbox + ip: IP + location: + all: Tutto + local: Locale + remote: Remoto + title: Luogo + login_status: Stato login + media_attachments: Media allegati + memorialize: Trasforma in memoriam + moderation: + all: Tutto + silenced: Silenziati + suspended: Sospesi + title: Moderazione + moderation_notes: Note di moderazione + most_recent_activity: Attività più recenti + most_recent_ip: IP più recenti + not_subscribed: Non sottoscritto + order: + alphabetic: Alfabetico + most_recent: Più recente + title: Ordine + outbox_url: URL outbox + perform_full_suspension: Esegui sospensione completa + profile_url: URL profilo + promote: Promuovi + protocol: Protocollo + public: Pubblico + redownload: Aggiorna avatar + remove_avatar: Rimuovi avatar + reset: Reimposta + reset_password: Reimposta password + resubscribe: Riscriversi + role: Permessi + roles: + admin: Amministratore + moderator: Moderatore + staff: Staff + user: Utente + search: Cerca + silence: Silenzia + statuses: Stati + subscribe: Sottoscrivi + title: Account + unconfirmed_email: Email non confermata + undo_silenced: Rimuovi silenzia + undo_suspension: Rimuovi sospensione + unsubscribe: Annulla l'iscrizione + username: Nome utente + web: Web + action_logs: + actions: + change_email_user: "%{name} ha cambiato l'indirizzo e-mail per l'utente %{target}" + confirm_user: "%{name} ha confermato l'indirizzo email per l'utente %{target}" + create_custom_emoji: "%{name} ha caricato un nuovo emoji %{target}" + create_domain_block: "%{name} ha bloccato il dominio %{target}" + custom_emojis: + by_domain: Dominio + copied_msg: Creata con successo una copia locale dell'emoji + copy: Copia + copy_failed_msg: Impossibile creare una copia locale di questo emoji + created_msg: Emoji creato con successo! + delete: Elimina + destroyed_msg: Emoji distrutto con successo! + disable: Disabilita + disabled_msg: Questa emoji è stata disabilitata con successo + emoji: Emoji + enable: Abilita + enabled_msg: Questa emoji è stata abilitata con successo + image_hint: PNG fino a 50KB + listed: Elencato + new: + title: Aggiungi nuovo emoji personalizzato + overwrite: Sovrascrivi + shortcode: Shortcode + title: Emoji personalizzate + unlisted: Non elencato + update_failed_msg: Impossibile aggiornare questa emojii + updated_msg: Emoji aggiornata con successo! + upload: Carica + domain_blocks: + add_new: Aggiungi nuovo + created_msg: Il blocco del dominio sta venendo processato + destroyed_msg: Il blocco del dominio è stato rimosso + domain: Dominio + new: + create: Crea blocco + severity: + noop: Nessuno + silence: Silenzia + suspend: Sospendi + title: Nuovo blocco dominio + severities: + noop: Nessuno + silence: Silenzia + suspend: Sospendi + severity: Severità + show: + undo: Annulla + title: Blocchi dominio + undo: Annulla + email_domain_blocks: + add_new: Aggiungi nuovo + created_msg: Dominio e-mail aggiunto con successo alla lista nera + delete: Elimina + destroyed_msg: Dominio e-mail cancellato con successo dalla lista nera + domain: Dominio + new: + create: Aggiungi dominio + instances: + account_count: Accounts conosciuti + domain_name: Dominio + reset: Reimposta + search: Cerca + title: Istanze conosciute + invites: + filter: + all: Tutto + available: Disponibile + expired: Scaduto + title: Filtro + title: Inviti + reports: + account: + note: note + action_taken_by: Azione intrapresa da + are_you_sure: Sei sicuro? + assign_to_self: Assegna a me + assigned: Moderatore assegnato + comment: + none: Nessuno + delete: Elimina + id: ID + mark_as_resolved: Segna come risolto + mark_as_unresolved: Segna come non risolto + notes: + create: Aggiungi nota + create_and_resolve: Risolvi con nota + create_and_unresolve: Riapri con nota + delete: Elimina + nsfw: + 'false': Mostra gli allegati multimediali + 'true': Nascondi allegati multimediali + report_contents: Contenuti + resolved: Risolto + silence_account: Silenzia account + status: Stato + suspend_account: Sospendi account + target: Obbiettivo + unassign: Non assegnare + unresolved: Non risolto + updated_at: Aggiornato + view: Mostra + settings: + activity_api_enabled: + title: Pubblica statistiche aggregate circa l'attività dell'utente + contact_information: + username: Nome utente del contatto + peers_api_enabled: + title: Pubblica elenco di istanze scoperte + registrations: + deletion: + desc_html: Consenti a chiunque di cancellare il proprio account + title: Apri la cancellazione dell'account + min_invite_role: + disabled: Nessuno + open: + desc_html: Consenti a chiunque di creare un account + show_staff_badge: + title: Mostra badge staff + site_description: + title: Descrizione istanza + site_terms: + title: Termini di servizio personalizzati + site_title: Nome istanza + timeline_preview: + title: Anteprima timeline + title: Impostazioni sito + statuses: + batch: + delete: Elimina + nsfw_off: NSFW OFF + nsfw_on: NSFW ON + execute: Esegui + failed_to_execute: Impossibile eseguire + media: + hide: Nascondi media + show: Mostra media + title: Media + no_media: Nessun media + with_media: con media + subscriptions: + callback_url: URL Callback + confirmed: Confermato + expires_in: Scade in + topic: Argomento + title: Amministrazione application_mailer: + notification_preferences: Cambia preferenze email + salutation: "%{name}," settings: 'Cambia le impostazioni per le e-mail: %{link}' view: 'Guarda:' + view_profile: Mostra profilo + view_status: Mostra stati applications: + created: Applicazione creata con successo + destroyed: Applicazione eliminata con successo invalid_url: L'URL fornito non è valido auth: + change_password: Password + confirm_email: Conferma email + delete_account: Elimina account didnt_get_confirmation: Non hai ricevuto le istruzioni di conferma? forgot_password: Hai dimenticato la tua password? login: Entra logout: Logout + migrate_account: Sposta ad un account differente + or: o register: Iscriviti + register_elsewhere: Iscriviti su un altro server resend_confirmation: Invia di nuovo le istruzioni di conferma reset_password: Resetta la password security: Credenziali set_new_password: Imposta una nuova password authorize_follow: + already_following: Stai già seguendo questo account error: Sfortunatamente c'è stato un errore nel consultare l'account remoto follow: Segui title: Segui %{acct} @@ -161,6 +430,10 @@ it: manual_instructions: 'Se non puoi scannerizzare il QR code e hai bisogno di inserirlo manualmente, questo è il codice segreto in chiaro:' setup: Configura wrong_code: Il codice inserito non è corretto! Assicurati che l'orario del server e l'orario del telefono siano corretti. + user_mailer: + welcome: + tips: Suggerimenti + title: Benvenuto a bordo, %{name}! users: invalid_email: L'indirizzo e-mail inserito non è valido invalid_otp_token: Codice d'accesso non valido diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 01fb9657f..be9e2da2c 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -68,7 +68,7 @@ ja: current_email: 現在のメールアドレス label: メールアドレスを変更 new_email: 新しいメールアドレス - submit: Change Email + submit: メールアドレスの変更 title: "%{username} さんのメールアドレスを変更" confirm: 確認 confirmed: 確認済み @@ -259,16 +259,17 @@ ja: created_msg: レポートメモを書き込みました! destroyed_msg: レポートメモを削除しました! reports: + account: + note: メモ + report: レポート action_taken_by: レポート処理者 are_you_sure: 本当に実行しますか? assign_to_self: 担当になる assigned: 担当者 comment: - label: コメント none: なし created_at: レポート日時 delete: 削除 - history: モデレーション履歴 id: ID mark_as_resolved: 解決済みとしてマーク mark_as_unresolved: 未解決として再び開く @@ -277,9 +278,7 @@ ja: create_and_resolve: 書き込み、解決済みにする create_and_unresolve: 書き込み、未解決として開く delete: 削除 - label: モデレーターメモ - new_label: モデレーターメモの追加 - placeholder: このレポートに取られた措置やその他更新を記述してください + placeholder: このレポートに取られた措置や、その他の更新を記述してください… nsfw: 'false': NSFW オフ 'true': NSFW オン @@ -292,7 +291,6 @@ ja: resolved_msg: レポートを解決済みにしました! silence_account: アカウントをサイレンス status: ステータス - statuses: 通報されたトゥート suspend_account: アカウントを停止 target: ターゲット title: レポート @@ -356,8 +354,8 @@ ja: back_to_account: アカウントページに戻る batch: delete: 削除 - nsfw_off: NSFW オフ - nsfw_on: NSFW オン + nsfw_off: 閲覧注意のマークを取り除く + nsfw_on: 閲覧注意としてマークする execute: 実行 failed_to_execute: 実行に失敗しました media: @@ -697,6 +695,9 @@ ja: one: "%{count} 本の動画" other: "%{count} 本の動画" content_warning: '閲覧注意: %{warning}' + disallowed_hashtags: + one: '許可されていないハッシュタグが含まれています: %{tags}' + other: '許可されていないハッシュタグが含まれています: %{tags}' open_in_web: Webで開く over_character_limit: 上限は %{max}文字までです pin_errors: @@ -804,7 +805,7 @@ ja: default: "%Y年%m月%d日 %H:%M" two_factor_authentication: code_hint: 確認するには認証アプリで表示されたコードを入力してください - description_html: "<strong>二段階認証</strong>を有効にするとログイン時、電話でコードを受け取る必要があります。" + description_html: "<strong>二段階認証</strong>を有効にするとログイン時、認証アプリからコードを入力する必要があります。" disable: 無効 enable: 有効 enabled: 二段階認証は有効になっています diff --git a/config/locales/ko.yml b/config/locales/ko.yml index bbf27d5c3..251c0c3d7 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -4,6 +4,7 @@ ko: about_hashtag_html: "<strong>#%{hashtag}</strong> 라는 해시태그가 붙은 공개 툿 입니다. 같은 연합에 속한 임의의 인스턴스에 계정을 생성하면 당신도 대화에 참여할 수 있습니다." about_mastodon_html: Mastodon은 <em>오픈 소스 기반의</em> 소셜 네트워크 서비스 입니다. 상용 플랫폼의 대체로서 <em>분산형 구조</em>를 채택해, 여러분의 대화가 한 회사에 독점되는 것을 방지합니다. 신뢰할 수 있는 인스턴스를 선택하세요 — 어떤 인스턴스를 고르더라도, 누구와도 대화할 수 있습니다. 누구나 자신만의 Mastodon 인스턴스를 만들 수 있으며, 아주 매끄럽게 <em>소셜 네트워크</em>에 참가할 수 있습니다. about_this: 이 인스턴스에 대해서 + administered_by: '관리자:' closed_registrations: 현재 이 인스턴스에서는 신규 등록을 받고 있지 않습니다. contact: 연락처 contact_missing: 미설정 @@ -60,7 +61,15 @@ ko: destroyed_msg: 모더레이션 기록이 성공적으로 삭제되었습니다! accounts: are_you_sure: 정말로 실행하시겠습니까? + avatar: 아바타 by_domain: 도메인 + change_email: + changed_msg: 이메일이 성공적으로 바뀌었습니다! + current_email: 현재 이메일 주소 + label: 이메일 주소 변경 + new_email: 새 이메일 주소 + submit: 이메일 주소 변경 + title: "%{username}의 이메일 주소 변경" confirm: 확인 confirmed: 확인됨 demote: 모더레이터 강등 @@ -108,6 +117,7 @@ ko: public: 전체 공개 push_subscription_expires: PuSH 구독 기간 만료 redownload: 아바타 업데이트 + remove_avatar: 아바타 지우기 reset: 초기화 reset_password: 비밀번호 초기화 resubscribe: 다시 구독 @@ -128,6 +138,7 @@ ko: statuses: 툿 수 subscribe: 구독하기 title: 계정 + unconfirmed_email: 미확인 된 이메일 주소 undo_silenced: 침묵 해제 undo_suspension: 정지 해제 unsubscribe: 구독 해제 @@ -135,6 +146,8 @@ ko: web: 웹 action_logs: actions: + assigned_to_self_report: "%{name}이 리포트 %{target}을 자신에게 할당했습니다" + change_email_user: "%{name}이 %{target}의 이메일 주소를 변경했습니다" confirm_user: "%{name}이 %{target}의 이메일 주소를 컨펌했습니다" create_custom_emoji: "%{name}이 새로운 에모지 %{target}를 추가했습니다" create_domain_block: "%{name}이 도메인 %{target}를 차단했습니다" @@ -150,10 +163,13 @@ ko: enable_user: "%{name}이 %{target}의 로그인을 활성화 했습니다" memorialize_account: "%{name}이 %{target}의 계정을 메모리엄으로 전환했습니다" promote_user: "%{name}이 %{target}를 승급시켰습니다" + remove_avatar_user: "%{name}이 %{target}의 아바타를 지웠습니다" + reopen_report: "%{name}이 리포트 %{target}을 다시 열었습니다" reset_password_user: "%{name}이 %{target}의 암호를 초기화했습니다" resolve_report: "%{name}이 %{target} 신고를 처리됨으로 변경하였습니다" silence_account: "%{name}이 %{target}의 계정을 뮤트시켰습니다" suspend_account: "%{name}이 %{target}의 계정을 정지시켰습니다" + unassigned_report: "%{name}이 리포트 %{target}을 할당 해제했습니다" unsilence_account: "%{name}이 %{target}에 대한 뮤트를 해제했습니다" unsuspend_account: "%{name}이 %{target}에 대한 정지를 해제했습니다" update_custom_emoji: "%{name}이 에모지 %{target}를 업데이트 했습니다" @@ -241,29 +257,48 @@ ko: expired: 만료됨 title: 필터 title: 초대 + report_notes: + created_msg: 리포트 노트가 성공적으로 작성되었습니다! + destroyed_msg: 리포트 노트가 성공적으로 삭제되었습니다! reports: + account: + note: 노트 + report: 리포트 action_taken_by: 신고 처리자 are_you_sure: 정말로 실행하시겠습니까? + assign_to_self: 나에게 할당 됨 + assigned: 할당 된 모더레이터 comment: - label: 코멘트 none: 없음 + created_at: 리포트 시각 delete: 삭제 id: ID mark_as_resolved: 해결 완료 처리 + mark_as_unresolved: 미해결로 표시 + notes: + create: 노트 추가 + create_and_resolve: 노트를 작성하고 해결됨으로 표시 + create_and_unresolve: 노트 작성과 함께 미해결로 표시 + delete: 삭제 + placeholder: 이 리포트에 대한 조치, 다른 업데이트 사항에 대해 설명합니다… nsfw: 'false': NSFW 꺼짐 'true': NSFW 켜짐 + reopen: 리포트 다시 열기 report: '신고 #%{id}' report_contents: 내용 reported_account: 신고 대상 계정 reported_by: 신고자 resolved: 해결됨 + resolved_msg: 리포트가 성공적으로 해결되었습니다! silence_account: 계정을 침묵 처리 status: 상태 suspend_account: 계정을 정지 target: 대상 title: 신고 + unassign: 할당 해제 unresolved: 미해결 + updated_at: 업데이트 시각 view: 표시 settings: activity_api_enabled: @@ -384,6 +419,7 @@ ko: security: 보안 set_new_password: 새 비밀번호 authorize_follow: + already_following: 이미 이 계정을 팔로우 하고 있습니다 error: 리모트 계정을 확인하는 도중 오류가 발생했습니다 follow: 팔로우 follow_request: '당신은 다음 계정에 팔로우 신청을 했습니다:' @@ -476,6 +512,7 @@ ko: '21600': 6 시간 '3600': 1 시간 '43200': 12 시간 + '604800': 1주일 '86400': 하루 expires_in_prompt: 영원히 generate: 생성 @@ -548,7 +585,7 @@ ko: quadrillion: Q thousand: K trillion: T - unit: '' + unit: "." pagination: newer: 새로운 툿 next: 다음 @@ -579,6 +616,10 @@ ko: missing_resource: 리디렉션 대상을 찾을 수 없습니다 proceed: 팔로우 하기 prompt: '팔로우 하려 하고 있습니다:' + remote_unfollow: + error: 에러 + title: 타이틀 + unfollowed: 언팔로우됨 sessions: activity: 마지막 활동 browser: 브라우저 diff --git a/config/locales/ms.yml b/config/locales/ms.yml new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/config/locales/ms.yml @@ -0,0 +1 @@ +{} diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 16e68fffe..1ccc01a8f 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -4,6 +4,7 @@ nl: about_hashtag_html: Dit zijn openbare toots die getagged zijn met <strong>#%{hashtag}</strong>. Je kunt er op reageren of iets anders mee doen als je op Mastodon (of ergens anders in de fediverse) een account hebt. about_mastodon_html: Mastodon is een sociaal netwerk dat gebruikt maakt van open webprotocollen en vrije software. Het is net zoals e-mail gedecentraliseerd. about_this: Over deze server + administered_by: 'Beheerd door:' closed_registrations: Registreren op deze server is momenteel uitgeschakeld. contact: Contact contact_missing: Niet ingesteld @@ -60,7 +61,15 @@ nl: destroyed_msg: Verwijderen van opmerking voor moderatoren geslaagd! accounts: are_you_sure: Weet je het zeker? + avatar: Avatar by_domain: Domein + change_email: + changed_msg: E-mailadres van account succesvol veranderd! + current_email: Huidig e-mailadres + label: E-mailadres veranderen + new_email: Nieuw e-mailadres + submit: E-mailadres veranderen + title: E-mailadres veranderen voor %{username} confirm: Bevestigen confirmed: Bevestigd demote: Degraderen @@ -108,6 +117,7 @@ nl: public: Openbaar push_subscription_expires: PuSH-abonnement verloopt op redownload: Avatar vernieuwen + remove_avatar: Avatar verwijderen reset: Opnieuw reset_password: Wachtwoord opnieuw instellen resubscribe: Opnieuw abonneren @@ -128,6 +138,7 @@ nl: statuses: Toots subscribe: Abonneren title: Accounts + unconfirmed_email: Onbevestigd e-mailadres undo_silenced: Niet meer negeren undo_suspension: Niet meer opschorten unsubscribe: Opzeggen @@ -135,6 +146,8 @@ nl: web: Webapp action_logs: actions: + assigned_to_self_report: "%{name} heeft gerapporteerde toot %{target} aan zichzelf toegewezen" + change_email_user: "%{name} veranderde het e-mailadres van gebruiker %{target}" confirm_user: E-mailadres van gebruiker %{target} is door %{name} bevestigd create_custom_emoji: Nieuwe emoji %{target} is door %{name} geüpload create_domain_block: Domein %{target} is door %{name} geblokkeerd @@ -150,10 +163,13 @@ nl: enable_user: Inloggen voor %{target} is door %{name} ingeschakeld memorialize_account: Account %{target} is door %{name} in een in-memoriampagina veranderd promote_user: Gebruiker %{target} is door %{name} gepromoveerd + remove_avatar_user: "%{name} verwijderde de avatar van %{target}" + reopen_report: "%{name} heeft gerapporteerde toot %{target} heropend" reset_password_user: Wachtwoord van gebruiker %{target} is door %{name} opnieuw ingesteld - resolve_report: Gerapporteerde toots van %{target} zijn door %{name} verworpen + resolve_report: "%{name} heeft gerapporteerde toot %{target} opgelost" silence_account: Account %{target} is door %{name} genegeerd suspend_account: Account %{target} is door %{name} opgeschort + unassigned_report: "%{name} heeft het toewijzen van gerapporteerde toot %{target} ongedaan gemaakt" unsilence_account: Negeren van account %{target} is door %{name} opgeheven unsuspend_account: Opschorten van account %{target} is door %{name} opgeheven update_custom_emoji: Emoji %{target} is door %{name} bijgewerkt @@ -239,29 +255,48 @@ nl: expired: Verlopen title: Filter title: Uitnodigingen + report_notes: + created_msg: Opmerking bij gerapporteerde toot succesvol aangemaakt! + destroyed_msg: Opmerking bij gerapporteerde toot succesvol verwijderd! reports: + account: + note: opmerking + report: gerapporteerde toot action_taken_by: Actie uitgevoerd door are_you_sure: Weet je het zeker? + assign_to_self: Aan mij toewijzen + assigned: Toegewezen moderator comment: - label: Opmerking none: Geen + created_at: Gerapporteerd op delete: Verwijderen id: ID mark_as_resolved: Markeer als opgelost + mark_as_unresolved: Markeer als onopgelost + notes: + create: Opmerking toevoegen + create_and_resolve: Oplossen met opmerking + create_and_unresolve: Heropenen met opmerking + delete: Verwijderen + placeholder: Beschrijf welke acties zijn ondernomen of andere opmerkingen over deze gerapporteerde toot… nsfw: 'false': Media tonen 'true': Media verbergen + reopen: Gerapporteerde toot heropenen report: 'Gerapporteerde toot #%{id}' report_contents: Inhoud reported_account: Gerapporteerde account reported_by: Gerapporteerd door resolved: Opgelost + resolved_msg: Gerapporteerde toot succesvol opgelost! silence_account: Account negeren status: Toot suspend_account: Account opschorten target: Gerapporteerde account title: Gerapporteerde toots + unassign: Niet meer toewijzen unresolved: Onopgelost + updated_at: Bijgewerkt view: Weergeven settings: activity_api_enabled: @@ -319,8 +354,8 @@ nl: back_to_account: Terug naar accountpagina batch: delete: Verwijderen - nsfw_off: NSFW UIT - nsfw_on: NSFW AAN + nsfw_off: Als niet gevoelig markeren + nsfw_on: Als gevoelig markeren execute: Uitvoeren failed_to_execute: Uitvoeren mislukt media: @@ -382,6 +417,7 @@ nl: security: Beveiliging set_new_password: Nieuw wachtwoord instellen authorize_follow: + already_following: Je volgt dit account al error: Helaas, er is een fout opgetreden bij het opzoeken van de externe account follow: Volgen follow_request: 'Jij hebt een volgverzoek ingediend bij:' @@ -474,6 +510,7 @@ nl: '21600': 6 uur '3600': 1 uur '43200': 12 uur + '604800': 1 week '86400': 1 dag expires_in_prompt: Nooit generate: Genereren @@ -577,6 +614,10 @@ nl: missing_resource: Kon vereiste doorverwijzings-URL voor jouw account niet vinden proceed: Ga door om te volgen prompt: 'Jij gaat volgen:' + remote_unfollow: + error: Fout + title: Titel + unfollowed: Ontvolgd sessions: activity: Laatst actief browser: Webbrowser @@ -643,6 +684,9 @@ nl: one: "%{count} video" other: "%{count} video's" content_warning: 'Tekstwaarschuwing: %{warning}' + disallowed_hashtags: + one: 'bevatte een niet toegestane hashtag: %{tags}' + other: 'bevatte niet toegestane hashtags: %{tags}' open_in_web: In de webapp openen over_character_limit: Limiet van %{max} tekens overschreden pin_errors: @@ -665,6 +709,83 @@ nl: reblogged: boostte sensitive_content: Gevoelige inhoud terms: + body_html: | + <h2>Privacy Policy</h2> + <h3 id="collect">What information do we collect?</h3> + + <ul> + <li><em>Basic account information</em>: If you register on this server, you may be asked to enter a username, an e-mail address and a password. You may also enter additional profile information such as a display name and biography, and upload a profile picture and header image. The username, display name, biography, profile picture and header image are always listed publicly.</li> + <li><em>Posts, following and other public information</em>: The list of people you follow is listed publicly, the same is true for your followers. When you submit a message, the date and time is stored as well as the application you submitted the message from. Messages may contain media attachments, such as pictures and videos. Public and unlisted posts are available publicly. When you feature a post on your profile, that is also publicly available information. Your posts are delivered to your followers, in some cases it means they are delivered to different servers and copies are stored there. When you delete posts, this is likewise delivered to your followers. The action of reblogging or favouriting another post is always public.</li> + <li><em>Direct and followers-only posts</em>: All posts are stored and processed on the server. Followers-only posts are delivered to your followers and users who are mentioned in them, and direct posts are delivered only to users mentioned in them. In some cases it means they are delivered to different servers and copies are stored there. We make a good faith effort to limit the access to those posts only to authorized persons, but other servers may fail to do so. Therefore it's important to review servers your followers belong to. You may toggle an option to approve and reject new followers manually in the settings. <em>Please keep in mind that the operators of the server and any receiving server may view such messages</em>, and that recipients may screenshot, copy or otherwise re-share them. <em>Do not share any dangerous information over Mastodon.</em></li> + <li><em>IPs and other metadata</em>: When you log in, we record the IP address you log in from, as well as the name of your browser application. All the logged in sessions are available for your review and revocation in the settings. The latest IP address used is stored for up to 12 months. We also may retain server logs which include the IP address of every request to our server.</li> + </ul> + + <hr class="spacer" /> + + <h3 id="use">What do we use your information for?</h3> + + <p>Any of the information we collect from you may be used in the following ways:</p> + + <ul> + <li>To provide the core functionality of Mastodon. You can only interact with other people's content and post your own content when you are logged in. For example, you may follow other people to view their combined posts in your own personalized home timeline.</li> + <li>To aid moderation of the community, for example comparing your IP address with other known ones to determine ban evasion or other violations.</li> + <li>The email address you provide may be used to send you information, notifications about other people interacting with your content or sending you messages, and to respond to inquiries, and/or other requests or questions.</li> + </ul> + + <hr class="spacer" /> + + <h3 id="protect">How do we protect your information?</h3> + + <p>We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information. Among other things, your browser session, as well as the traffic between your applications and the API, are secured with SSL, and your password is hashed using a strong one-way algorithm. You may enable two-factor authentication to further secure access to your account.</p> + + <hr class="spacer" /> + + <h3 id="data-retention">What is our data retention policy?</h3> + + <p>We will make a good faith effort to:</p> + + <ul> + <li>Retain server logs containing the IP address of all requests to this server, in so far as such logs are kept, no more than 90 days.</li> + <li>Retain the IP addresses associated with registered users no more than 12 months.</li> + </ul> + + <p>You can request and download an archive of your content, including your posts, media attachments, profile picture, and header image.</p> + + <p>You may irreversibly delete your account at any time.</p> + + <hr class="spacer"/> + + <h3 id="cookies">Do we use cookies?</h3> + + <p>Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account.</p> + + <p>We use cookies to understand and save your preferences for future visits.</p> + + <hr class="spacer" /> + + <h3 id="disclose">Do we disclose any information to outside parties?</h3> + + <p>We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety.</p> + + <p>Your public content may be downloaded by other servers in the network. Your public and followers-only posts are delivered to the servers where your followers reside, and direct messages are delivered to the servers of the recipients, in so far as those followers or recipients reside on a different server than this.</p> + + <p>When you authorize an application to use your account, depending on the scope of permissions you approve, it may access your public profile information, your following list, your followers, your lists, all your posts, and your favourites. Applications can never access your e-mail address or password.</p> + + <hr class="spacer" /> + + <h3 id="coppa">Children's Online Privacy Protection Act Compliance</h3> + + <p>Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) do not use this site.</p> + + <hr class="spacer" /> + + <h3 id="changes">Changes to our Privacy Policy</h3> + + <p>If we decide to change our privacy policy, we will post those changes on this page.</p> + + <p>This document is CC-BY-SA. It was last updated March 7, 2018.</p> + + <p>Originally adapted from the <a href="https://github.com/discourse/discourse">Discourse privacy policy</a>.</p> title: "%{instance} Terms of Service and Privacy Policy" time: formats: diff --git a/config/locales/no.yml b/config/locales/no.yml index d5edb3975..8b84182af 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -243,7 +243,6 @@ action_taken_by: Handling utført av are_you_sure: Er du sikker? comment: - label: Kommentar none: Ingen delete: Slett id: ID diff --git a/config/locales/oc.yml b/config/locales/oc.yml index f8e819c53..d5717c0b5 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -4,6 +4,7 @@ oc: about_hashtag_html: Vaquí los estatuts publics ligats a <strong>#%{hashtag}</strong>. Podètz interagir amb eles s’avètz un compte ont que siasque sul fediverse. about_mastodon_html: Mastodon es un malhum social bastit amb de protocòls liures e gratuits. Es descentralizat coma los corrièls. about_this: A prepaus d’aquesta instància + administered_by: 'Gerida per :' closed_registrations: Las inscripcions son clavadas pel moment sus aquesta instància. contact: Contacte contact_missing: Pas parametrat @@ -60,7 +61,15 @@ oc: destroyed_msg: Nòta de moderacion ben suprimida ! accounts: are_you_sure: Sètz segur ? + avatar: Avatar by_domain: Domeni + change_email: + changed_msg: Adreça corrèctament cambiada ! + current_email: Adreça actuala + label: Cambiar d’adreça + new_email: Novèla adreça + submit: Cambiar + title: Cambiar l’adreça a %{username} confirm: Confirmar confirmed: Confirmat demote: Retrogradar @@ -108,6 +117,7 @@ oc: public: Public push_subscription_expires: Fin de l’abonament PuSH redownload: Actualizar los avatars + remove_avatar: Supriir l’avatar reset: Reïnicializar reset_password: Reïnicializar lo senhal resubscribe: Se tornar abonar @@ -128,6 +138,7 @@ oc: statuses: Estatuts subscribe: S’abonar title: Comptes + unconfirmed_email: Adreça pas confirmada undo_silenced: Levar lo silenci undo_suspension: Levar la suspension unsubscribe: Se desabonar @@ -135,6 +146,8 @@ oc: web: Web action_logs: actions: + assigned_to_self_report: "%{name} s’assignèt lo rapòrt %{target}" + change_email_user: "%{name} cambièt l’adreça de corrièl de %{target}" confirm_user: "%{name} confirmèt l’adreça a %{target}" create_custom_emoji: "%{name} mandèt un nòu emoji %{target}" create_domain_block: "%{name} bloquèt lo domeni %{target}" @@ -150,6 +163,7 @@ oc: enable_user: "%{name} activèt la connexion per %{target}" memorialize_account: "%{name} transformèt en memorial la pagina de perfil a %{target}" promote_user: "%{name} promoguèt %{target}" + remove_avatar_user: "%{name} suprimèt l’avatar a %{target}" reset_password_user: "%{name} reïnicializèt lo senhal a %{target}" resolve_report: "%{name} anullèt lo rapòrt de %{target}" silence_account: "%{name} metèt en silenci lo compte a %{target}" @@ -239,18 +253,31 @@ oc: expired: Expirats title: Filtre title: Convits + report_notes: + created_msg: Nòta de moderacion corrèctament creada ! + destroyed_msg: Nòta de moderacion corrèctament suprimida ! reports: + account: + note: nòta + report: rapòrt action_taken_by: Mesura menada per are_you_sure: Es segur ? comment: - label: Comentari none: Pas cap + created_at: Creacion delete: Suprimir id: ID - mark_as_resolved: Marcat coma resolgut + mark_as_resolved: Marcar coma resolgut + mark_as_unresolved: Marcar coma pas resolgut + notes: + create: Ajustar una nòta + create_and_resolve: Resòlvre amb una nòta + create_and_unresolve: Tornar dobrir amb una nòta + placeholder: Explicatz las accions que son estadas menadas o çò qu’es estat fach per aqueste rapòrt… nsfw: 'false': Sens contengut sensible 'true': Contengut sensible activat + reopen: Tornar dobrir lo rapòrt report: 'senhalament #%{id}' report_contents: Contenguts reported_account: Compte senhalat @@ -382,6 +409,7 @@ oc: security: Seguretat set_new_password: Picar un nòu senhal authorize_follow: + already_following: Seguètz ja aqueste compte error: O planhèm, i a agut una error al moment de cercar lo compte follow: Sègre follow_request: 'Avètz demandat de sègre :' @@ -552,6 +580,7 @@ oc: '21600': 6 oras '3600': 1 ora '43200': 12 oras + '604800': 1 setmana '86400': 1 jorn expires_in_prompt: Jamai generate: Generar @@ -653,8 +682,12 @@ oc: remote_follow: acct: Picatz vòstre utilizaire@instància que cal utilizar per sègre aqueste utilizaire missing_resource: URL de redireccion pas trobada - proceed: Contunhatz per sègre + proceed: Clicatz per sègre prompt: 'Sètz per sègre :' + remote_unfollow: + error: Error + title: Títol + unfollowed: Pas mai seguit sessions: activity: Darrièra activitat browser: Navigator @@ -720,6 +753,9 @@ oc: video: one: "%{count} vidèo" other: "%{count} vidèos" + disallowed_hashtags: + one: 'conten una etiqueta desactivada : %{tags}' + other: 'conten las etiquetas desactivadas : %{tags}' open_in_web: Dobrir sul web over_character_limit: limit de %{max} caractèrs passat pin_errors: diff --git a/config/locales/pl.yml b/config/locales/pl.yml index 4fba2c0c1..519207d38 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -261,25 +261,16 @@ pl: destroyed_msg: Pomyślnie usunięto notatkę moderacyjną. reports: account: - created_reports: Zgłoszenia utworzone z tego konta - moderation: - silenced: Wyciszone - suspended: Zawieszone - title: Moderacja - moderation_notes: Notatki moderacyjne note: notatka report: zgłoszenie - targeted_reports: Zgłoszenia dotycząće tego konta action_taken_by: Działanie podjęte przez are_you_sure: Czy na pewno? assign_to_self: Przypisz do siebie assigned: Przypisany moderator comment: - label: Komentarz do zgłoszenia none: Brak created_at: Zgłoszono delete: Usuń - history: Historia moderacji id: ID mark_as_resolved: Oznacz jako rozwiązane mark_as_unresolved: Oznacz jako nierozwiązane @@ -288,8 +279,6 @@ pl: create_and_resolve: Rozwiąż i pozostaw notatkę create_and_unresolve: Cofnij rozwiązanie i pozostaw notatkę delete: Usuń - label: Notatki - new_label: Dodaj notatkę moderacyjną placeholder: Opisz wykonane akcje i inne szczegóły dotyczące tego zgłoszenia… nsfw: 'false': Nie oznaczaj jako NSFW @@ -303,7 +292,6 @@ pl: resolved_msg: Pomyślnie rozwiązano zgłoszenie. silence_account: Wycisz konto status: Stan - statuses: Zgłoszone wpisy suspend_account: Zawieś konto target: Cel title: Zgłoszenia @@ -478,7 +466,7 @@ pl: archive_takeout: date: Data download: Pobierz swoje archiwum - hint_html: Możesz uzyskać archiwum swoich <strong>wpisów i wysłanej zawartości multimedialnej</strong>. Wyeksportowane dane będą dostępne w formacie ActivityPub, obsługiwanym przez odpowiednie programy. + hint_html: Możesz uzyskać archiwum swoich <strong>wpisów i wysłanej zawartości multimedialnej</strong>. Wyeksportowane dane będą dostępne w formacie ActivityPub, który możesz otworzyć w obsługujących go programach. in_progress: Tworzenie archiwum… request: Uzyskaj archiwum size: Rozmiar @@ -497,7 +485,7 @@ pl: one: W trakcie usuwania śledzących z jednej domeny… other: W trakcie usuwania śledzących z %{count} domen… true_privacy_html: Pamiętaj, że <strong>rzeczywista prywatność może zostać uzyskana wyłącznie dzięki szyfrowaniu end-to-end</strong>. - unlocked_warning_html: Każdy może Cię śledzić, aby natychmiastowo zobaczyć twoje wpisy. %{lock_link} aby móc kontrolować, kto Cię śledzi. + unlocked_warning_html: Każdy może Cię śledzić, dzięki czemu może zobaczyć Twoje niepubliczne wpisy. %{lock_link} aby móc kontrolować, kto Cię śledzi. unlocked_warning_title: Twoje konto nie jest zablokowane generic: changes_saved_msg: Ustawienia zapisane! @@ -505,10 +493,12 @@ pl: save_changes: Zapisz zmiany use_this: Użyj tego validation_errors: - one: Coś jest wciąż nie tak! Przyjrzyj się błędowi poniżej - other: Coś jest wciąż nie tak! Przejrzyj błędy (%{count}) poniżej + few: Coś jest wciąż nie tak! Przejrzyj %{count} poniższe błędy + many: Coś jest wciąż nie tak! Przejrzyj %{count} poniższych błędów + one: Coś jest wciąż nie tak! Przyjrzyj się poniższemu błędowi + other: Coś jest wciąż nie tak! Przejrzyj poniższe błędy (%{count}) imports: - preface: Możesz zaimportować pewne dane (jak dane kont, które śledzisz lub blokujesz) do swojego konta na tym serwerze, korzystając z danych wyeksportowanych z innego serwera. + preface: Możesz zaimportować pewne dane (np. lista kont, które śledzisz lub blokujesz) do swojego konta na tym serwerze, korzystając z danych wyeksportowanych z innego serwera. success: Twoje dane zostały załadowane i zostaną niebawem przetworzone types: blocking: Lista blokowanych @@ -718,6 +708,9 @@ pl: one: "%{count} film" other: "%{count} filmów" content_warning: 'Ostrzeżenie o zawartości: %{warning}' + disallowed_hashtags: + one: 'zawiera niedozwolony hashtag: %{tags}' + other: 'zawiera niedozwolone hashtagi: %{tags}' open_in_web: Otwórz w przeglądarce over_character_limit: limit %{max} znaków przekroczony pin_errors: diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index ed7879525..a575998a8 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -4,6 +4,7 @@ pt-BR: about_hashtag_html: Estes são toots públicos com a hashtag <strong>#%{hashtag}</strong>. Você pode interagir com eles se tiver uma conta em qualquer lugar no fediverso. about_mastodon_html: Mastodon é uma rede social baseada em protocolos abertos e software gratuito e de código aberto. É descentralizada como e-mail. about_this: Sobre + administered_by: 'Administrado por:' closed_registrations: Os cadastros estão atualmente fechados nesta instância. No entanto, você pode procurar uma instância diferente na qual possa criar uma conta e acessar a mesma rede por lá. contact: Contato contact_missing: Não definido @@ -60,7 +61,15 @@ pt-BR: destroyed_msg: Nota de moderação excluída com sucesso! accounts: are_you_sure: Você tem certeza? + avatar: Avatar by_domain: Domínio + change_email: + changed_msg: E-mail da conta modificado com sucesso! + current_email: E-mail atual + label: Mudar e-mail + new_email: Novo e-mail + submit: Mudar e-mail + title: Mudar e-mail para %{username} confirm: Confirmar confirmed: Confirmado demote: Rebaixar @@ -108,6 +117,7 @@ pt-BR: public: Público push_subscription_expires: Inscrição PuSH expira redownload: Atualizar avatar + remove_avatar: Remover avatar reset: Anular reset_password: Modificar senha resubscribe: Reinscrever-se @@ -128,6 +138,7 @@ pt-BR: statuses: Postagens subscribe: Inscrever-se title: Contas + unconfirmed_email: E-mail não confirmado undo_silenced: Retirar silenciamento undo_suspension: Retirar suspensão unsubscribe: Desinscrever-se @@ -135,6 +146,8 @@ pt-BR: web: Web action_logs: actions: + assigned_to_self_report: "%{name} designou a denúncia %{target} para si" + change_email_user: "%{name} mudou o endereço de e-mail do usuário %{target}" confirm_user: "%{name} confirmou o endereço de e-mail do usuário %{target}" create_custom_emoji: "%{name} enviou o emoji novo %{target}" create_domain_block: "%{name} bloqueou o domínio %{target}" @@ -150,10 +163,13 @@ pt-BR: enable_user: "%{name} habilitou o acesso para o usuário %{target}" memorialize_account: "%{name} transformou a conta de %{target} em um memorial" promote_user: "%{name} promoveu o usuário %{target}" + remove_avatar_user: "%{name} removeu o avatar de %{target}" + reopen_report: "%{name} reabriu a denúncia %{target}" reset_password_user: "%{name} redefiniu a senha do usuário %{target}" - resolve_report: "%{name} dispensou a denúncia %{target}" + resolve_report: "%{name} resolveu a denúncia %{target}" silence_account: "%{name} silenciou a conta de %{target}" suspend_account: "%{name} suspendeu a conta de %{target}" + unassigned_report: "%{name} desatribuiu a denúncia %{target}" unsilence_account: "%{name} desativou o silêncio de %{target}" unsuspend_account: "%{name} desativou a suspensão de %{target}" update_custom_emoji: "%{name} atualizou o emoji %{target}" @@ -239,29 +255,48 @@ pt-BR: expired: Expirados title: Filtro title: Convites + report_notes: + created_msg: Nota de denúncia criada com sucesso! + destroyed_msg: Nota de denúncia excluída com sucesso! reports: + account: + note: nota + report: denúncia action_taken_by: Ação realizada por are_you_sure: Você tem certeza? + assign_to_self: Designar para mim + assigned: Moderador designado comment: - label: Comentário none: Nenhum + created_at: Denunciado delete: Excluir id: ID mark_as_resolved: Marcar como resolvido + mark_as_unresolved: Marcar como não resolvido + notes: + create: Adicionar nota + create_and_resolve: Resolver com nota + create_and_unresolve: Reabrir com nota + delete: Excluir + placeholder: Descreva que ações foram tomadas, ou quaisquer atualizações sobre esta denúncia… nsfw: 'false': Mostrar mídias anexadas 'true': Esconder mídias anexadas + reopen: Reabrir denúncia report: 'Denúncia #%{id}' report_contents: Conteúdos reported_account: Conta denunciada reported_by: Denunciada por resolved: Resolvido + resolved_msg: Denúncia resolvida com sucesso! silence_account: Silenciar conta status: Status suspend_account: Suspender conta target: Alvo title: Denúncias + unassign: Desatribuir unresolved: Não resolvido + updated_at: Atualizado view: Visualizar settings: activity_api_enabled: @@ -319,8 +354,8 @@ pt-BR: back_to_account: Voltar para página da conta batch: delete: Deletar - nsfw_off: NSFW ATIVADO - nsfw_on: NSFW DESATIVADO + nsfw_off: Marcar como não-sensível + nsfw_on: Marcar como sensível execute: Executar failed_to_execute: Falha em executar media: @@ -382,6 +417,7 @@ pt-BR: security: Segurança set_new_password: Definir uma nova senha authorize_follow: + already_following: Você já está seguindo esta conta error: Infelizmente, ocorreu um erro ao buscar a conta remota follow: Seguir follow_request: 'Você mandou uma solicitação de seguidor para:' @@ -474,6 +510,7 @@ pt-BR: '21600': 6 horas '3600': 1 hora '43200': 12 horas + '604800': 1 semana '86400': 1 dia expires_in_prompt: Nunca generate: Gerar @@ -577,6 +614,9 @@ pt-BR: missing_resource: Não foi possível encontrar a URL de direcionamento para a sua conta proceed: Prosseguir para seguir prompt: 'Você irá seguir:' + remote_unfollow: + error: Erro + title: Título sessions: activity: Última atividade browser: Navegador @@ -643,6 +683,9 @@ pt-BR: one: "%{count} vídeo" other: "%{count} vídeos" content_warning: 'Aviso de conteúdo: %{warning}' + disallowed_hashtags: + one: 'continha a hashtag não permitida: %{tags}' + other: 'continha as hashtags não permitidas: %{tags}' open_in_web: Abrir na web over_character_limit: limite de caracteres de %{max} excedido pin_errors: @@ -665,6 +708,83 @@ pt-BR: reblogged: compartilhou sensitive_content: Conteúdo sensível terms: + body_html: | + <h2>Política de privacidade</h2> + <h3 id="collect">Que informação nós coletamos?</h3> + + <ul> + <li><em>Informação básica de conta</em>: Se você se registrar nesse servidor, podemos pedir que você utilize um nome de usuário, um e-mail e uma senha. Você também pode adicionar informações extras como um nome de exibição e biografia; enviar uma imagem de perfil e imagem de cabeçalho. O nome de usuário, nome de exibição, biografia, imagem de perfil e imagem de cabeçalho são sempre listadas publicamente.</li> + <li><em>Posts, informação de seguidores e outras informações públicas</em>: A lista de pessoas que você segue é listada publicamente, o mesmo é verdade para quem te segue. Quando você envia uma mensagem, a data e o horário são armazenados, assim como a aplicação que você usou para enviar a mensagem. Mensagens podem conter mídias anexadas, como imagens e vídeos. Posts públicos e não-listados estão disponíveis publicamente. Quando você destaca um post no seu perfil, isso também é uma informação pública. Seus posts são entregues aos seus seguidores e em alguns casos isso significa que eles são enviados para servidores diferentes e cópias são armazenadas nesses servidores. Quando você remove posts, essa informação também é entregue aos seus seguidores. O ato de compartilhar ou favoritar um outro post é sempre público.<li> + <li><em>Mensagens diretas e posts somente para seguidores</em>: Todos os posts são armazenados e processados no servidor. Posts somente para seguidores são entregues aos seus seguidores e usuários que são mencionados neles; mensagens diretas são entregues somente aos usuários mencionados nelas. Em alguns casos isso significa que as mensagens são entregues para servidores diferentes e cópias são armazenadas nesses servidores. Nós fazemos esforços substanciais para limitar o acesso dessas mensagens somente para as pessoas autorizadas, mas outros servidores podem não fazer o mesmo. É importante portanto revisar os servidores à qual seus seguidores pertencem. Você pode usar uma opção para aprovar ou rejeitar novos seguidores manualmente nas configurações. <em>Por favor tenha em mente que os operadores do servidor e de qualquer servidores do destinatário podem ver tais mensagens</em>, e que os destinatários podem fazer capturas de tela, copiar ou de outra maneira compartilhar as mensagens. <em>Não compartilhe informação confidencial pelo Mastodon.</em></li> + <li><em>IPs and other metadata</em>: When you log in, we record the IP address you log in from, as well as the name of your browser application. All the logged in sessions are available for your review and revocation in the settings. The latest IP address used is stored for up to 12 months. We also may retain server logs which include the IP address of every request to our server.</li> + </ul> + + <hr class="spacer" /> + + <h3 id="use">What do we use your information for?</h3> + + <p>Any of the information we collect from you may be used in the following ways:</p> + + <ul> + <li>To provide the core functionality of Mastodon. You can only interact with other people's content and post your own content when you are logged in. For example, you may follow other people to view their combined posts in your own personalized home timeline.</li> + <li>To aid moderation of the community, for example comparing your IP address with other known ones to determine ban evasion or other violations.</li> + <li>The email address you provide may be used to send you information, notifications about other people interacting with your content or sending you messages, and to respond to inquiries, and/or other requests or questions.</li> + </ul> + + <hr class="spacer" /> + + <h3 id="protect">How do we protect your information?</h3> + + <p>We implement a variety of security measures to maintain the safety of your personal information when you enter, submit, or access your personal information. Among other things, your browser session, as well as the traffic between your applications and the API, are secured with SSL, and your password is hashed using a strong one-way algorithm. You may enable two-factor authentication to further secure access to your account.</p> + + <hr class="spacer" /> + + <h3 id="data-retention">What is our data retention policy?</h3> + + <p>We will make a good faith effort to:</p> + + <ul> + <li>Retain server logs containing the IP address of all requests to this server, in so far as such logs are kept, no more than 90 days.</li> + <li>Retain the IP addresses associated with registered users no more than 12 months.</li> + </ul> + + <p>You can request and download an archive of your content, including your posts, media attachments, profile picture, and header image.</p> + + <p>You may irreversibly delete your account at any time.</p> + + <hr class="spacer"/> + + <h3 id="cookies">Do we use cookies?</h3> + + <p>Yes. Cookies are small files that a site or its service provider transfers to your computer's hard drive through your Web browser (if you allow). These cookies enable the site to recognize your browser and, if you have a registered account, associate it with your registered account.</p> + + <p>We use cookies to understand and save your preferences for future visits.</p> + + <hr class="spacer" /> + + <h3 id="disclose">Do we disclose any information to outside parties?</h3> + + <p>We do not sell, trade, or otherwise transfer to outside parties your personally identifiable information. This does not include trusted third parties who assist us in operating our site, conducting our business, or servicing you, so long as those parties agree to keep this information confidential. We may also release your information when we believe release is appropriate to comply with the law, enforce our site policies, or protect ours or others rights, property, or safety.</p> + + <p>Your public content may be downloaded by other servers in the network. Your public and followers-only posts are delivered to the servers where your followers reside, and direct messages are delivered to the servers of the recipients, in so far as those followers or recipients reside on a different server than this.</p> + + <p>When you authorize an application to use your account, depending on the scope of permissions you approve, it may access your public profile information, your following list, your followers, your lists, all your posts, and your favourites. Applications can never access your e-mail address or password.</p> + + <hr class="spacer" /> + + <h3 id="coppa">Children's Online Privacy Protection Act Compliance</h3> + + <p>Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a>) do not use this site.</p> + + <hr class="spacer" /> + + <h3 id="changes">Changes to our Privacy Policy</h3> + + <p>If we decide to change our privacy policy, we will post those changes on this page.</p> + + <p>This document is CC-BY-SA. It was last updated March 7, 2018.</p> + + <p>Originally adapted from the <a href="https://github.com/discourse/discourse">Discourse privacy policy</a>.</p> title: "%{instance} Termos de Serviço e Política de Privacidade" time: formats: diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 27d4e88e3..fb2a6cad1 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -243,7 +243,6 @@ pt: action_taken_by: Ação tomada por are_you_sure: Tens a certeza? comment: - label: Comentário none: Nenhum delete: Eliminar id: ID diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 176ace92d..bf4225758 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -245,7 +245,6 @@ ru: action_taken_by: 'Действие предпринято:' are_you_sure: Вы уверены? comment: - label: Комментарий none: Нет delete: Удалить id: ID diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml index 414f0c342..28cfa8ab7 100644 --- a/config/locales/simple_form.ar.yml +++ b/config/locales/simple_form.ar.yml @@ -5,8 +5,15 @@ ar: defaults: avatar: ملف PNG أو GIF أو JPG. حجمه على أقصى تصدير 2MB. سيتم تصغيره إلى 400x400px digest: تُرسَل إليك بعد مُضيّ مدة مِن خمول نشاطك و فقط إذا ما تلقيت رسائل شخصية مباشِرة أثناء فترة غيابك مِن الشبكة + display_name: + one: <span class="name-counter">1</span> حرف باقي + other: <span class="name-counter">%{count}</span> حروف متبقية + fields: يُمكنك عرض 4 عناصر على شكل جدول في ملفك الشخصي header: ملف PNG أو GIF أو JPG. حجمه على أقصى تصدير 2MB. سيتم تصغيره إلى 700x335px locked: يتطلب منك الموافقة يدويا على طلبات المتابعة + note: + one: <span class="note-counter">1</span> حرف متبقي + other: <span class="note-counter">%{count}</span> حروف متبقية setting_noindex: ذلك يؤثر على حالة ملفك الشخصي و صفحاتك setting_theme: ذلك يؤثر على الشكل الذي سيبدو عليه ماستدون عندما تقوم بالدخول مِن أي جهاز. imports: @@ -16,6 +23,10 @@ ar: user: filtered_languages: سوف يتم تصفية و إخفاء اللغات المختارة من خيوطك العمومية labels: + account: + fields: + name: التسمية + value: المحتوى defaults: avatar: الصورة الرمزية confirm_new_password: تأكيد كلمة السر الجديدة @@ -25,6 +36,7 @@ ar: display_name: الإسم المعروض email: عنوان البريد الإلكتروني expires_in: تنتهي مدة صلاحيته بعد + fields: واصفات بيانات الملف الشخصي filtered_languages: اللغات التي تم تصفيتها header: الرأسية locale: اللغة diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 300da45a5..1b04da90a 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -8,6 +8,7 @@ ca: display_name: one: <span class="name-counter">1</span> càracter restant other: <span class="name-counter">%{count}</span> càracters restans + fields: Pots tenir fins a 4 elements que es mostren com a taula al teu perfil header: PNG, GIF o JPG. Màxim 2MB. S'escalarà a 700x335px locked: Requereix que aprovis manualment els seguidors note: @@ -22,6 +23,10 @@ ca: user: filtered_languages: Les llengües seleccionades s'eliminaran de les línies de temps públiques labels: + account: + fields: + name: Etiqueta + value: Contingut defaults: avatar: Avatar confirm_new_password: Confirma la contrasenya nova @@ -31,6 +36,7 @@ ca: display_name: Nom visible email: Adreça de correu electrònic expires_in: Expira després + fields: Metadades del perfil filtered_languages: Llengües filtrades header: Capçalera locale: Llengua diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 5a65173be..a9d650a26 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -8,6 +8,7 @@ de: display_name: one: <span class="name-counter">1</span> Zeichen verbleibt other: <span class="name-counter">%{count}</span> Zeichen verbleiben + fields: Du kannst bis zu 4 Elemente als Tabelle dargestellt auf deinem Profil anzeigen lassen header: PNG, GIF oder JPG. Maximal 2 MB. Wird auf 700×335 px herunterskaliert locked: Wer dir folgen möchte, muss um deine Erlaubnis bitten note: @@ -22,6 +23,10 @@ de: user: filtered_languages: Ausgewählte Sprachen werden aus deinen öffentlichen Zeitleisten gefiltert labels: + account: + fields: + name: Bezeichnung + value: Inhalt defaults: avatar: Profilbild confirm_new_password: Neues Passwort bestätigen @@ -31,6 +36,7 @@ de: display_name: Anzeigename email: E-Mail-Adresse expires_in: Gültig bis + fields: Profil-Metadaten filtered_languages: Gefilterte Sprachen header: Kopfbild locale: Sprache diff --git a/config/locales/simple_form.eu.yml b/config/locales/simple_form.eu.yml new file mode 100644 index 000000000..d856feac5 --- /dev/null +++ b/config/locales/simple_form.eu.yml @@ -0,0 +1,32 @@ +--- +eu: + simple_form: + hints: + defaults: + avatar: PNG, GIF edo JPG. Gehienez 2MB. 400x400px neurrira eskalatuko da + locked: Jarraitzaileak eskuz onartu behar dituzu + note: + other: <span class="note-counter"> %{count}</span> karaktere faltan + setting_noindex: Zure profil publiko eta egoera orrietan eragina du + setting_theme: Edozein gailutik konektatzean Mastodon-en itxuran eragiten du. + imports: + data: Mastodon-en beste instantzia batetik CSV fitxategia esportatu da + user: + filtered_languages: Aukeratutako hizkuntzak timeline publikotik filtratuko dira + labels: + account: + fields: + name: Etiketa + value: Edukia + defaults: + confirm_new_password: Pasahitz berria berretsi + confirm_password: Pasahitza berretsi + current_password: Oraingo pasahitza + display_name: Izena erakutsi + email: Helbide elektronikoa + fields: Profilaren metadatuak + filtered_languages: Iragazitako hizkuntzak + locale: Hizkuntza + new_password: Pasahitz berria + note: Bio + password: Pasahitza diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 71674199d..88e1b8873 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -8,6 +8,7 @@ fr: display_name: one: <span class="name-counter">1</span> caractère restant other: <span class="name-counter">%{count}</span> caractères restants + fields: Vous pouvez avoir jusqu'à 4 éléments affichés en tant que tableau sur votre profil header: Au format PNG, GIF ou JPG. 2 Mo maximum. Sera réduit à 700x335px locked: Vous devrez approuver chaque abonné⋅e et vos statuts ne s’afficheront qu’à vos abonné⋅es note: @@ -22,6 +23,10 @@ fr: user: filtered_languages: Les langues sélectionnées seront filtrées hors de vos fils publics pour vous labels: + account: + fields: + name: Étiquette + value: Contenu defaults: avatar: Image de profil confirm_new_password: Confirmation du nouveau mot de passe @@ -31,6 +36,7 @@ fr: display_name: Nom public email: Adresse courriel expires_in: Expire après + fields: Métadonnées du profil filtered_languages: Langues filtrées header: Image d’en-tête locale: Langue diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index 4dcdd0459..72633c759 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -8,6 +8,7 @@ gl: display_name: one: <span class="name-counter">1</span> caracter restante other: <span class="name-counter">%{count}</span> caracteres restantes + fields: Pode ter ate 4 elementos no seu perfil mostrados como unha táboa header: PNG, GIF ou JPG. Como moito 2MB. Será reducida a 700x335px locked: Require que vostede aprove as seguidoras de xeito manual note: @@ -22,6 +23,10 @@ gl: user: filtered_languages: Os idiomas marcados filtraranse das liñas temporais públicas para vostede labels: + account: + fields: + name: Etiqueta + value: Contido defaults: avatar: Avatar confirm_new_password: Confirme o novo contrasinal @@ -31,8 +36,9 @@ gl: display_name: Nome mostrado email: enderezo correo electrónico expires_in: Caducidade despois de + fields: Metadatos do perfil filtered_languages: Idiomas filtrados - header: Cabezallo + header: Cabeceira locale: Idioma locked: Protexer conta max_uses: Número máximo de usos diff --git a/config/locales/simple_form.it.yml b/config/locales/simple_form.it.yml index b2fcef109..5d9ae18f5 100644 --- a/config/locales/simple_form.it.yml +++ b/config/locales/simple_form.it.yml @@ -3,45 +3,77 @@ it: simple_form: hints: defaults: - avatar: PNG, GIF o JPG. Al massimo 2MB. Sarà ridotto a 400x400px - display_name: Al massimo 30 characters - header: PNG, GIF or JPG. Al massimo 2MB. Sarà ridotto a 700x335px - locked: Richiede la tua approvazione per i nuovi seguaci e rende i nuovi post automaticamente visibili solo ai seguaci - note: Al massimo 160 caratteri + avatar: PNG, GIF o JPG. Al massimo 2MB. Verranno scalate a 400x400px + digest: Inviata solo dopo un lungo periodo di intattività e solo se hai ricevuto qualsiasi messaggio personale in tua assenza + display_name: + one: <span class="name-counter">1</span> carattere rimanente + other: <span class="name-counter">%{count}</span> caratteri rimanenti + fields: Puoi avere fino a 4 voci visualizzate come una tabella sul tuo profilo + header: PNG, GIF o JPG. Al massimo 2MB. Verranno scalate a 700x335px + locked: Richiede che approvi i follower manualmente + note: + one: <span class="note-counter">1</span> carattere rimanente + other: <span class="note-counter">%{count}</span> caratteri rimanenti + setting_noindex: Coinvolge il tuo profilo pubblico e le pagine di stato + setting_theme: Coinvolge il modo in cui Mastodon verrà visualizzato quando sarai collegato da qualsiasi dispositivo. imports: - data: CSV esportato da un altro server Mastodon + data: File CSV esportato da un altra istanza di Mastodon + sessions: + otp: Inserisci il codice due-fattori dal tuo telefono o usa uno dei codici di recupero. + user: + filtered_languages: Le lingue selezionate verranno filtrate dalla timeline pubblica per te labels: + account: + fields: + name: Etichetta + value: Contenuto defaults: avatar: Avatar - confirm_new_password: Conferma la nuova password - confirm_password: Conferma la password + confirm_new_password: Conferma nuova password + confirm_password: Conferma password current_password: Password corrente data: Data - display_name: Nome pubblico - email: Indirizzo e-mail + display_name: Nome visualizzato + email: Indirizzo email + expires_in: Scade dopo + fields: Metadata profilo + filtered_languages: Lingue filtrate header: Header locale: Lingua - locked: Rendi l'account privato + locked: Blocca account + max_uses: Numero massimo di utilizzi new_password: Nuova password - note: Biografia - otp_attempt: Codice d'accesso + note: Bio + otp_attempt: Codice due-fattori password: Password - setting_boost_modal: Mostra finestra di conferma prima di condividere - setting_default_privacy: Privacy del post - type: Importa - username: Username + setting_auto_play_gif: Play automatico GIF animate + setting_boost_modal: Mostra dialogo di conferma prima del boost + setting_default_privacy: Privacy post + setting_default_sensitive: Segna sempre i media come sensibili + setting_delete_modal: Mostra dialogo di conferma prima di eliminare un toot + setting_display_sensitive_media: Mostra sempre i media segnati come sensibili + setting_noindex: Non indicizzare dai motori di ricerca + setting_reduce_motion: Riduci movimento nelle animazioni + setting_system_font_ui: Usa il carattere di default del sistema + setting_theme: Tema sito + setting_unfollow_modal: Mostra dialogo di conferma prima di smettere di seguire qualcuno + severity: Severità + type: Tipo importazione + username: Nome utente + username_or_email: Nome utente o email interactions: - must_be_follower: Blocca notifiche da chi non ti segue - must_be_following: Blocca notifiche da chi non segui + must_be_follower: Blocca notifiche dai non follower + must_be_following: Blocca notifiche dalle persone che non segui + must_be_following_dm: Blocca i messaggi diretti dalle persone che non segui notification_emails: - digest: Invia riassunto via e-mail - favourite: Invia e-mail quando qualcuno apprezza i tuoi status - follow: Invia e-mail quando qualcuno ti segue - follow_request: Invia e-mail quando qualcuno ti richiede di seguirti - mention: Invia e-mail quando qualcuno ti menziona - reblog: Invia e-mail quando qualcuno condivide i tuoi status + digest: Invia email riassuntive + favourite: Invia email quando segna come preferito al tuo stato + follow: Invia email quando qualcuno ti segue + follow_request: Invia email quando qualcuno richiede di seguirti + mention: Invia email quando qualcuno ti menziona + reblog: Invia email quando qualcuno da un boost al tuo stato 'no': 'No' required: mark: "*" text: richiesto - 'yes': Sì + 'yes': Si diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index d11430338..9e4d40405 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -6,6 +6,7 @@ ja: avatar: 2MBまでのPNGやGIF、JPGが利用可能です。400x400pxまで縮小されます digest: 長期間使用していない場合と不在時に返信を受けた場合のみ送信されます display_name: あと<span class="name-counter">%{count}</span>文字入力できます。 + fields: プロフィールに表として4つまでの項目を表示することができます header: 2MBまでのPNGやGIF、JPGが利用可能です。 700x335pxまで縮小されます locked: フォロワーを手動で承認する必要があります note: あと<span class="note-counter">%{count}</span>文字入力できます。 @@ -18,6 +19,10 @@ ja: user: filtered_languages: 選択した言語があなたの公開タイムラインから取り除かれます labels: + account: + fields: + name: ラベル + value: 内容 defaults: avatar: アイコン confirm_new_password: 新しいパスワード(確認用) @@ -27,6 +32,7 @@ ja: display_name: 表示名 email: メールアドレス expires_in: 有効期限 + fields: プロフィール補足情報 filtered_languages: 除外する言語 header: ヘッダー locale: 言語 @@ -47,7 +53,7 @@ ja: setting_reduce_motion: アニメーションの動きを減らす setting_system_font_ui: システムのデフォルトフォントを使う setting_theme: サイトテーマ - setting_unfollow_modal: フォロー解除する前に確認ダイアログを表示する + setting_unfollow_modal: フォローを解除する前に確認ダイアログを表示する severity: 重大性 type: インポートする項目 username: ユーザー名 diff --git a/config/locales/simple_form.ko.yml b/config/locales/simple_form.ko.yml index 85eccf091..ccb05fd25 100644 --- a/config/locales/simple_form.ko.yml +++ b/config/locales/simple_form.ko.yml @@ -8,6 +8,7 @@ ko: display_name: one: <span class="name-counter">1</span> 글자 남음 other: <span class="name-counter">%{count}</span> 글자 남음 + fields: 당신의 프로파일에 최대 4개까지 표 형식으로 나타낼 수 있습니다 header: PNG, GIF 혹은 JPG. 최대 2MB. 700x335px로 다운스케일 됨 locked: 수동으로 팔로워를 승인하고, 기본 툿 프라이버시 설정을 팔로워 전용으로 변경 note: @@ -22,6 +23,10 @@ ko: user: filtered_languages: 선택된 언어가 공개 타임라인에서 제외 될 것입니다 labels: + account: + fields: + name: 라벨 + value: 내용 defaults: avatar: 아바타 confirm_new_password: 새로운 비밀번호 다시 입력 @@ -31,6 +36,7 @@ ko: display_name: 표시되는 이름 email: 이메일 주소 expires_in: 만료시각 + fields: 프로필 메타데이터 filtered_languages: 숨긴 언어들 header: 헤더 locale: 언어 diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml index 9876230b3..ec42adfd7 100644 --- a/config/locales/simple_form.nl.yml +++ b/config/locales/simple_form.nl.yml @@ -8,6 +8,7 @@ nl: display_name: one: <span class="name-counter">1</span> teken over other: <span class="name-counter">%{count}</span> tekens over + fields: Je kan maximaal 4 items als een tabel op je profiel weergeven header: PNG, GIF of JPG. Maximaal 2MB. Wordt teruggeschaald naar 700x335px locked: Vereist dat je handmatig volgers moet accepteren note: @@ -22,6 +23,10 @@ nl: user: filtered_languages: De geselecteerde talen worden uit de lokale en globale tijdlijn verwijderd labels: + account: + fields: + name: Label + value: Inhoud defaults: avatar: Avatar confirm_new_password: Nieuw wachtwoord bevestigen @@ -31,6 +36,7 @@ nl: display_name: Weergavenaam email: E-mailadres expires_in: Vervalt na + fields: Metadata profiel filtered_languages: Talen filteren header: Omslagfoto locale: Taal @@ -63,7 +69,7 @@ nl: digest: Periodiek e-mails met een samenvatting versturen favourite: Een e-mail versturen wanneer iemand jouw toot als favoriet markeert follow: Een e-mail versturen wanneer iemand jou volgt - follow_request: Een e-mail versturen wanneer iemand jou wilt volgen + follow_request: Een e-mail versturen wanneer iemand jou wil volgen mention: Een e-mail versturen wanneer iemand jou vermeld reblog: Een e-mail versturen wanneer iemand jouw toot heeft geboost 'no': Nee diff --git a/config/locales/simple_form.oc.yml b/config/locales/simple_form.oc.yml index 690d1de20..4ca58c102 100644 --- a/config/locales/simple_form.oc.yml +++ b/config/locales/simple_form.oc.yml @@ -8,6 +8,7 @@ oc: display_name: one: Demòra encara <span class="name-counter">1</span> caractèr other: Demòran encara <span class="name-counter">%{count}</span> caractèrs + fields: Podètz far veire cap a 4 elements sus vòstre perfil header: PNG, GIF o JPG. Maximum 2 Mo. Serà retalhada en 700x335px locked: Demanda qu’acceptetz manualament lo mond que vos sègon e botarà la visibilitat de vòstras publicacions coma accessiblas a vòstres seguidors solament note: @@ -22,6 +23,10 @@ oc: user: filtered_languages: Las lengas seleccionadas seràn levadas de vòstre flux d’actualitat labels: + account: + fields: + name: Nom + value: Contengut defaults: avatar: Avatar confirm_new_password: Confirmacion del nòu senhal @@ -31,6 +36,7 @@ oc: display_name: Escais email: Corrièl expires_in: Expira aprèp + fields: Metadonada del perfil filtered_languages: Lengas filtradas header: Bandièra locale: Lenga diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index 325cb2691..8a6d47a01 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -63,7 +63,7 @@ pl: setting_system_font_ui: Używaj domyślnej czcionki systemu setting_unfollow_modal: Pytaj o potwierdzenie przed cofnięciem śledzenia severity: Priorytet - type: Typ importu + type: Importowane dane username: Nazwa użytkownika username_or_email: Nazwa użytkownika lub adres e-mail interactions: diff --git a/config/locales/simple_form.pt-BR.yml b/config/locales/simple_form.pt-BR.yml index 0c22b2608..cae1f671d 100644 --- a/config/locales/simple_form.pt-BR.yml +++ b/config/locales/simple_form.pt-BR.yml @@ -8,6 +8,7 @@ pt-BR: display_name: one: <span class="name-counter">1</span> caracter restante other: <span class="name-counter">%{count}</span> caracteres restantes + fields: Você pode ter até 4 itens exibidos em forma de tabela no seu perfil header: PNG, GIF or JPG. Arquivos de até 2MB. Eles serão diminuídos para 700x335px locked: Requer aprovação manual de seguidores note: @@ -22,6 +23,10 @@ pt-BR: user: filtered_languages: Selecione os idiomas que devem ser removidos de suas timelines públicas labels: + account: + fields: + name: Rótulo + value: Conteúdo defaults: avatar: Avatar confirm_new_password: Confirmar nova senha @@ -31,6 +36,7 @@ pt-BR: display_name: Nome de exibição email: Endereço de e-mail expires_in: Expira em + fields: Metadados do perfil filtered_languages: Idiomas filtrados header: Cabeçalho locale: Idioma diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index e504c9774..134e62ee3 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -8,6 +8,7 @@ sk: display_name: one: Ostáva ti <span class="name-counter">1</span> znak other: Ostáva ti <span class="name-counter">%{count}</span> znakov + fields: Môžeš mať 4 položky na svojom profile zobrazené vo forme tabuľky header: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 700x335px locked: Musíte manuálne schváliť sledujúcich note: @@ -22,15 +23,20 @@ sk: user: filtered_languages: Zaškrtnuté jazyky budú pre teba vynechané nebudú z verejnej časovej osi labels: + account: + fields: + name: Označenie + value: Obsah defaults: avatar: Avatar - confirm_new_password: Opäť vaše nové heslo pre potvrdenie - confirm_password: Potvrďte heslo + confirm_new_password: Znovu tvoje nové heslo, pre potvrdenie + confirm_password: Potvrď heslo current_password: Súčasné heslo data: Dáta display_name: Meno email: Emailová adresa expires_in: Expirovať po + fields: Metadáta profilu filtered_languages: Filtrované jazyky header: Obrázok v hlavičke locale: Jazyk @@ -43,9 +49,9 @@ sk: setting_auto_play_gif: Automaticky prehrávať animované GIFy setting_boost_modal: Zobrazovať potvrdzovacie okno pred re-toot setting_default_privacy: Nastavenie súkromia príspevkov - setting_default_sensitive: Označiť každý obrázok/video/súbor ako chúlostivý - setting_delete_modal: Zobrazovať potvrdzovacie okno pred zmazaním toot-u - setting_display_sensitive_media: Vždy zobrazovať médiá označované ako senzitívne + setting_default_sensitive: Označ všetky mediálne súbory ako chúlostivé + setting_delete_modal: Zobrazuj potvrdzovacie okno pred vymazaním toot-u + setting_display_sensitive_media: Vždy zobraz médiá označené ako chúlostivé setting_noindex: Nezaraďuj príspevky do indexu pre vyhľadávče setting_reduce_motion: Redukovať pohyb v animáciách setting_system_font_ui: Použiť základné systémové písmo diff --git a/config/locales/simple_form.sv.yml b/config/locales/simple_form.sv.yml index 52ff32753..81ba61fb3 100644 --- a/config/locales/simple_form.sv.yml +++ b/config/locales/simple_form.sv.yml @@ -8,6 +8,7 @@ sv: display_name: one: <span class="name-counter">1</span> tecken kvar other: <span class="name-counter">%{count}</span> tecken kvar + fields: Du kan ha upp till 4 objekt visade som en tabell på din profil header: NG, GIF eller JPG. Högst 2 MB. Kommer nedskalas till 700x335px locked: Kräver att du manuellt godkänner följare note: @@ -22,6 +23,10 @@ sv: user: filtered_languages: Kontrollerade språk filtreras från offentliga tidslinjer för dig labels: + account: + fields: + name: Etikett + value: Innehåll defaults: avatar: Avatar confirm_new_password: Bekräfta nytt lösenord @@ -31,6 +36,7 @@ sv: display_name: Visningsnamn email: E-postadress expires_in: Förfaller efter + fields: Profil-metadata filtered_languages: Filtrerade språk header: Bakgrundsbild locale: Språk diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml index 6b890b036..a21439a98 100644 --- a/config/locales/simple_form.zh-HK.yml +++ b/config/locales/simple_form.zh-HK.yml @@ -5,19 +5,23 @@ zh-HK: defaults: avatar: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 400x400px digest: 僅在你長時間未登錄,且收到了私信時發送 - display_name: 最多 30 個字元 + display_name: + one: 尚餘 <span class="name-counter">1</span> 個字 + other: 尚餘 <span class="name-counter">%{count}</span> 個字 fields: 個人資料頁可顯示多至 4 個項目 header: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 700x335px locked: 你必須人手核准每個用戶對你的關注請求,而你的文章私隱會被預設為「只有關注你的人能看」 - note: 最多 160 個字元 + note: + one: 尚餘 <span class="note-counter">1</span> 個字 + other: 尚餘 <span class="note-counter">%{count}</span> 個字 setting_noindex: 此設定會影響到你的公開個人資料以及文章頁面 - setting_theme: 此設置會影響到你從任意設備登入時 Mastodon 的顯示樣式 + setting_theme: 此設置會影響到你從任意設備登入時 Mastodon 的顯示樣式。 imports: data: 自其他服務站匯出的 CSV 檔案 sessions: otp: 輸入你手機上生成的雙重認證碼,或者任意一個恢復代碼。 user: - filtered_languages: 下面被選擇的語言的文章將不會出現在你的公共時間軸上。 + filtered_languages: 下面被選擇的語言的文章將不會出現在你的公共時間軸上 labels: account: fields: diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 25e672604..8484ac52c 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -4,6 +4,7 @@ sk: about_hashtag_html: Toto sú verejné toot príspevky otagované <strong>#%{tagom}</strong>. Ak máš účet niekde vo fediverse, môžeš ich používať. about_mastodon_html: Mastodon je sociálna sieť založená na otvorených webových protokoloch. Jej zrojový kód je otvorený a je decentralizovaná podobne ako email. about_this: O tejto instancii + administered_by: 'Správca je:' closed_registrations: Registrácie sú momentálne uzatvorené. Avšak, môžeš nájsť nejaký iný Mastodon server kde si založ účet a získaj tak prístup do presne tej istej siete, odtiaľ. contact: Kontakt contact_missing: Nezadané @@ -60,7 +61,15 @@ sk: destroyed_msg: Poznámka moderátora bola úspešne zmazaná! accounts: are_you_sure: Ste si istý? + avatar: Maskot by_domain: Doména + change_email: + changed_msg: Email k tomuto účtu bol úspešne zmenený! + current_email: Súčastný email + label: Zmeniť email + new_email: Nový email + submit: Zmeniť email + title: Zmeň email pre %{username} confirm: Potvrdiť confirmed: Potvrdený demote: Degradovať @@ -108,6 +117,7 @@ sk: public: Verejná os push_subscription_expires: PuSH odoberanie expiruje redownload: Obnoviť avatar + remove_avatar: Odstrániť avatár reset: Reset reset_password: Obnoviť heslo resubscribe: Znovu odoberať @@ -128,6 +138,7 @@ sk: statuses: Príspevky subscribe: Odoberať title: Účty + unconfirmed_email: Nepotvrdený email undo_silenced: Zrušiť stíšenie undo_suspension: Zrušiť suspendáciu unsubscribe: Prestať odoberať @@ -135,6 +146,8 @@ sk: web: Web action_logs: actions: + assigned_to_self_report: "%{name}pridelil/a hlásenie užívateľa %{target}sebe" + change_email_user: "%{name} zmenil/a emailovú adresu užívateľa %{target}" confirm_user: "%{name} potvrdil e-mailovú adresu používateľa %{target}" create_custom_emoji: "%{name} nahral nový emoji %{target}" create_domain_block: "%{name} zablokoval doménu %{target}" @@ -150,8 +163,10 @@ sk: enable_user: "%{name} povolil prihlásenie pre používateľa %{target}" memorialize_account: '%{name} zmenil účet %{target} na stránku "Navždy budeme spomínať"' promote_user: "%{name} povýšil/a používateľa %{target}" + remove_avatar_user: "%{name} odstránil/a %{target}ov avatár" + reopen_report: "%{name} znovu otvoril/a hlásenie užívateľa %{target}" reset_password_user: "%{name} resetoval/a heslo pre používateľa %{target}" - resolve_report: "%{name} zamietli nahlásenie %{target}" + resolve_report: "%{name} vyriešili nahlásenie užívateľa %{target}" silence_account: "%{name} utíšil/a účet %{target}" suspend_account: "%{name} zablokoval/a účet používateľa %{target}" unsilence_account: "%{name} zrušil/a utíšenie účtu používateľa %{target}" @@ -239,29 +254,48 @@ sk: expired: Expirované title: Filtrovať title: Pozvánky + report_notes: + created_msg: Poznámka o nahlásení úspešne vytvorená! + destroyed_msg: Poznámka o nahlásení úspešne vymazaná! reports: - action_taken_by: Zákrok vykonal + account: + note: poznámka + report: nahlás + action_taken_by: Zákrok vykonal/a are_you_sure: Ste si istý/á? + assign_to_self: Priraď sebe + assigned: Priradený moderátor comment: - label: Vyjadriť sa none: Žiadne + created_at: Nahlásené delete: Vymazať id: Identifikácia mark_as_resolved: Označiť ako vyriešené + mark_as_unresolved: Označ ako nevyriešené + notes: + create: Pridaj poznámku + create_and_resolve: Vyrieš s poznámkou + create_and_unresolve: Otvor znovu, s poznámkou + delete: Vymaž + placeholder: Opíš aké opatrenia boli urobené, alebo akékoľvek iné aktualizácie k tomuto nahláseniu… nsfw: 'false': Odkryť mediálne prílohy 'true': Skryť mediálne prílohy + reopen: Znovu otvor report report: Nahlásiť report_contents: Obsah reported_account: Nahlásený účet reported_by: Nahlásené užívateľom resolved: Vyriešené + resolved_msg: Hlásenie úspešne vyriešené! silence_account: Zamĺčať účet status: Stav suspend_account: Pozastaviť účet target: Cieľ title: Reporty + unassign: Odobrať unresolved: Nevyriešené + updated_at: Aktualizované view: Zobraziť settings: activity_api_enabled: @@ -319,8 +353,8 @@ sk: back_to_account: Späť na účet batch: delete: Vymazať - nsfw_off: Nevhodný obsah je vypnutý - nsfw_on: Nevhodný obsah je zapnutý + nsfw_off: Obsah nieje chúlostivý + nsfw_on: Označ obeah aka chúlostivý execute: Vykonať failed_to_execute: Nepodarilo sa vykonať media: @@ -382,6 +416,7 @@ sk: security: Zabezpečenie set_new_password: Nastaviť nové heslo authorize_follow: + already_following: Tento účet už následuješ error: Naneštastie nastala chyba pri hľadaní vzdialeného účtu follow: Následovať follow_request: 'Poslali ste požiadavku následovať užívateľa:' @@ -473,6 +508,7 @@ sk: '21600': 6 hodín '3600': 1 hodina '43200': 12 hodín + '604800': 1 týždeň '86400': 1 deň expires_in_prompt: Nikdy generate: Vygeneruj @@ -575,6 +611,10 @@ sk: missing_resource: Nemôžeme nájsť potrebnú presmerovaciu adresu k tvojmu účtu proceed: Začni následovať prompt: 'Budeš sledovať:' + remote_unfollow: + error: Chyba + title: Názov + unfollowed: Už nesleduješ sessions: activity: Najnovšia aktivita browser: Prehliadač diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index 8d39d35b0..742c976d1 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -245,7 +245,6 @@ sr-Latn: action_taken_by: Akciju izveo are_you_sure: Da li ste sigurni? comment: - label: Komentar none: Ništa delete: Obriši id: ID diff --git a/config/locales/sr.yml b/config/locales/sr.yml index af4c6a846..0d55910a6 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -245,7 +245,6 @@ sr: action_taken_by: Акцију извео are_you_sure: Да ли сте сигурни? comment: - label: Коментар none: Ништа delete: Обриши id: ID diff --git a/config/locales/sv.yml b/config/locales/sv.yml index f85ed6efb..845248652 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -4,12 +4,13 @@ sv: about_hashtag_html: Dessa är offentliga toots märkta med <strong>#%{hashtag}</strong>. Du kan interagera med dem om du har ett konto någonstans i federationen. about_mastodon_html: Mastodon är ett socialt nätverk baserat på öppna webbprotokoll och gratis, öppen källkodsprogramvara. Det är decentraliserat som e-post. about_this: Om + administered_by: 'Administreras av:' closed_registrations: Registreringar är för närvarande stängda i denna instans. Dock så kan du hitta en annan instans för att skapa ett konto och få tillgång till samma nätverk från det. contact: Kontakt contact_missing: Inte inställd contact_unavailable: N/A description_headline: Vad är %{domain}? - domain_count_after: annan instans + domain_count_after: andra instanser domain_count_before: Uppkopplad mot extended_description_html: | <h3>En bra plats för regler</h3> @@ -29,7 +30,7 @@ sv: other_instances: Instanslista source_code: Källkod status_count_after: statusar - status_count_before: Vem författade + status_count_before: Som skapat user_count_after: användare user_count_before: Hem till what_is_mastodon: Vad är Mastodon? @@ -48,7 +49,7 @@ sv: reserved_username: Användarnamnet är reserverat roles: admin: Admin - moderator: Moderera + moderator: Moderator unfollow: Sluta följa admin: account_moderation_notes: @@ -60,7 +61,15 @@ sv: destroyed_msg: Modereringsnotering borttagen utan problem! accounts: are_you_sure: Är du säker? + avatar: Avatar by_domain: Domän + change_email: + changed_msg: E-postadressen har ändrats! + current_email: Nuvarande E-postadress + label: Byt E-postadress + new_email: Ny E-postadress + submit: Byt E-postadress + title: Byt E-postadress för %{username} confirm: Bekräfta confirmed: Bekräftad demote: Degradera @@ -108,6 +117,7 @@ sv: public: Offentlig push_subscription_expires: PuSH-prenumerationen löper ut redownload: Uppdatera avatar + remove_avatar: Ta bort avatar reset: Återställ reset_password: Återställ lösenord resubscribe: Starta en ny prenumeration @@ -128,6 +138,7 @@ sv: statuses: Status subscribe: Prenumerera title: Konton + unconfirmed_email: Obekräftad E-postadress undo_silenced: Ångra tystnad undo_suspension: Ångra avstängning unsubscribe: Avsluta prenumeration @@ -135,6 +146,8 @@ sv: web: Webb action_logs: actions: + assigned_to_self_report: "%{name} tilldelade anmälan %{target} till sig själv" + change_email_user: "%{name} bytte e-postadress för användare %{target}" confirm_user: "%{name} bekräftade e-postadress för användare %{target}" create_custom_emoji: "%{name} laddade upp ny emoji %{target}" create_domain_block: "%{name} blockerade domän %{target}" @@ -150,10 +163,13 @@ sv: enable_user: "%{name} aktiverade inloggning för användare %{target}" memorialize_account: "%{name} omvandlade %{target}s konto till en memoriam-sida" promote_user: "%{name} flyttade upp användare %{target}" + remove_avatar_user: "%{name} tog bort %{target}s avatar" + reopen_report: "%{name} återupptog anmälan %{target}" reset_password_user: "%{name} återställde lösenord för användaren %{target}" - resolve_report: "%{name} avvisade anmälan %{target}" + resolve_report: "%{name} löste anmälan %{target}" silence_account: "%{name} tystade ner %{target}s konto" suspend_account: "%{name} suspenderade %{target}s konto" + unassigned_report: "%{name} otilldelade anmälan %{target}" unsilence_account: "%{name} återljudade %{target}s konto" unsuspend_account: "%{name} aktiverade %{target}s konto" update_custom_emoji: "%{name} uppdaterade emoji %{target}" @@ -239,29 +255,48 @@ sv: expired: Utgångna title: Filtrera title: Inbjudningar + report_notes: + created_msg: Anmälningsanteckning har skapats! + destroyed_msg: Anmälningsanteckning har raderats! reports: + account: + note: anteckning + report: anmälan action_taken_by: Åtgärder vidtagna av are_you_sure: Är du säker? + assign_to_self: Tilldela till mig + assigned: Tilldelad moderator comment: - label: Kommentar none: Ingen + created_at: Anmäld delete: Radera id: ID mark_as_resolved: Markera som löst + mark_as_unresolved: Markera som olöst + notes: + create: Lägg till anteckning + create_and_resolve: Lös med anteckning + create_and_unresolve: Återuppta med anteckning + delete: Radera + placeholder: Beskriv vilka åtgärder som vidtagits eller andra uppdateringar till den här anmälan… nsfw: 'false': Visa bifogade mediafiler 'true': Dölj bifogade mediafiler + reopen: Återuppta anmälan report: 'Anmäl #%{id}' report_contents: Innehåll reported_account: Anmält konto reported_by: Anmäld av resolved: Löst + resolved_msg: Anmälan har lösts framgångsrikt! silence_account: Tysta ner konto status: Status suspend_account: Suspendera konto target: Mål title: Anmälningar + unassign: Otilldela unresolved: Olösta + updated_at: Uppdaterad view: Granska settings: activity_api_enabled: @@ -319,8 +354,8 @@ sv: back_to_account: Tillbaka till kontosidan batch: delete: Radera - nsfw_off: NSFW AV - nsfw_on: NSFW PÅ + nsfw_off: Markera som ej känslig + nsfw_on: Markera som känslig execute: Utför failed_to_execute: Misslyckades att utföra media: @@ -382,6 +417,7 @@ sv: security: Säkerhet set_new_password: Skriv in nytt lösenord authorize_follow: + already_following: Du följer redan detta konto error: Tyvärr inträffade ett fel när vi kontrollerade fjärrkontot follow: Följ follow_request: 'Du har skickat en följaförfrågan till:' @@ -474,6 +510,7 @@ sv: '21600': 6 timmar '3600': 1 timma '43200': 12 timmar + '604800': 1 vecka '86400': 1 dag expires_in_prompt: Aldrig generate: Skapa @@ -546,7 +583,7 @@ sv: quadrillion: Q thousand: K trillion: T - unit: enhet + unit: '' pagination: newer: Nyare next: Nästa @@ -577,6 +614,10 @@ sv: missing_resource: Det gick inte att hitta den begärda omdirigeringsadressen för ditt konto proceed: Fortsätt för att följa prompt: 'Du kommer att följa:' + remote_unfollow: + error: Fel + title: Titel + unfollowed: Slutade följa sessions: activity: Senaste aktivitet browser: Webbläsare @@ -634,6 +675,18 @@ sv: two_factor_authentication: Tvåstegsautentisering your_apps: Dina applikationer statuses: + attached: + description: 'Bifogad: %{attached}' + image: + one: "%{count} bild" + other: "%{count} bilder" + video: + one: "%{count} video" + other: "%{count} videor" + content_warning: 'Innehållsvarning: %{warning}' + disallowed_hashtags: + one: 'innehöll en otillåten hashtag: %{tags}' + other: 'innehöll de otillåtna hashtagarna: %{tags}' open_in_web: Öppna på webben over_character_limit: teckengräns på %{max} har överskridits pin_errors: diff --git a/config/locales/te.yml b/config/locales/te.yml new file mode 100644 index 000000000..f28b56052 --- /dev/null +++ b/config/locales/te.yml @@ -0,0 +1,5 @@ +--- +te: + about: + about_this: గురించి + contact: సంప్రదించండి diff --git a/config/locales/th.yml b/config/locales/th.yml index 45fe1e475..350b93b52 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -108,7 +108,6 @@ th: title: Known Instances reports: comment: - label: คอมเม้นต์ none: None delete: ลบ id: ไอดี diff --git a/config/locales/tr.yml b/config/locales/tr.yml index ee0e33074..6e7aeb77e 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -107,7 +107,6 @@ tr: title: Bilinen Sunucular reports: comment: - label: Yorum none: Yok delete: Sil id: ID diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 4c1c66b31..44f64b5c9 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -99,7 +99,6 @@ uk: undo: Відмінити reports: comment: - label: Коментар none: Немає delete: Видалити id: ID diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index be868e6e7..78c72bd30 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -241,7 +241,6 @@ zh-CN: action_taken_by: 操作执行者 are_you_sure: 你确定吗? comment: - label: 备注 none: 没有 delete: 删除 id: ID diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index 964ff5811..a27b0c04c 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -259,25 +259,16 @@ zh-HK: destroyed_msg: 舉報筆記已刪除。 reports: account: - created_reports: 由此帳號發出的舉報 - moderation: - silenced: 被靜音的 - suspended: 被停權的 - title: 管理操作 - moderation_notes: 管理筆記 note: 筆記 report: 舉報 - targeted_reports: 關於此帳號的舉報 action_taken_by: 操作執行者 are_you_sure: 你確認嗎? assign_to_self: 指派給自己 assigned: 指派負責人 comment: - label: 詳細解釋 none: 沒有 created_at: 日期 delete: 刪除 - history: 執行紀錄 id: ID mark_as_resolved: 標示為「已處理」 mark_as_unresolved: 標示為「未處理」 @@ -286,8 +277,6 @@ zh-HK: create_and_resolve: 建立筆記並標示為「已處理」 create_and_unresolve: 建立筆記並標示為「未處理」 delete: 刪除 - label: 管理筆記 - new_label: 建立管理筆記 placeholder: 記錄已執行的動作,或其他更新 nsfw: 'false': 取消 NSFW 標記 @@ -301,7 +290,6 @@ zh-HK: resolved_msg: 舉報已處理。 silence_account: 將用戶靜音 status: 狀態 - statuses: 被舉報的文章 suspend_account: 將用戶停權 target: 對象 title: 舉報 diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 2fec09ed8..f69d22d79 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -79,7 +79,6 @@ zh-TW: title: 網域封鎖 reports: comment: - label: 留言 none: 無 delete: 刪除 id: ID diff --git a/config/settings.yml b/config/settings.yml index 580a20895..a92a0bfd0 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -49,6 +49,7 @@ defaults: &defaults - root - webmaster - administrator + disallowed_hashtags: # space separated string or list of hashtags without the hash bootstrap_timeline_accounts: '' activity_api_enabled: true peers_api_enabled: true diff --git a/lib/json_ld/activitystreams.rb b/lib/json_ld/activitystreams.rb deleted file mode 100644 index ce740f93b..000000000 --- a/lib/json_ld/activitystreams.rb +++ /dev/null @@ -1,153 +0,0 @@ -# -*- encoding: utf-8 -*- -# frozen_string_literal: true -# This file generated automatically from https://www.w3.org/ns/activitystreams -require 'json/ld' -class JSON::LD::Context - add_preloaded("https://www.w3.org/ns/activitystreams") do - new(vocab: "_:", processingMode: "json-ld-1.0", term_definitions: { - "Accept" => TermDefinition.new("Accept", id: "https://www.w3.org/ns/activitystreams#Accept", simple: true), - "Activity" => TermDefinition.new("Activity", id: "https://www.w3.org/ns/activitystreams#Activity", simple: true), - "Add" => TermDefinition.new("Add", id: "https://www.w3.org/ns/activitystreams#Add", simple: true), - "Announce" => TermDefinition.new("Announce", id: "https://www.w3.org/ns/activitystreams#Announce", simple: true), - "Application" => TermDefinition.new("Application", id: "https://www.w3.org/ns/activitystreams#Application", simple: true), - "Arrive" => TermDefinition.new("Arrive", id: "https://www.w3.org/ns/activitystreams#Arrive", simple: true), - "Article" => TermDefinition.new("Article", id: "https://www.w3.org/ns/activitystreams#Article", simple: true), - "Audio" => TermDefinition.new("Audio", id: "https://www.w3.org/ns/activitystreams#Audio", simple: true), - "Block" => TermDefinition.new("Block", id: "https://www.w3.org/ns/activitystreams#Block", simple: true), - "Collection" => TermDefinition.new("Collection", id: "https://www.w3.org/ns/activitystreams#Collection", simple: true), - "CollectionPage" => TermDefinition.new("CollectionPage", id: "https://www.w3.org/ns/activitystreams#CollectionPage", simple: true), - "Create" => TermDefinition.new("Create", id: "https://www.w3.org/ns/activitystreams#Create", simple: true), - "Delete" => TermDefinition.new("Delete", id: "https://www.w3.org/ns/activitystreams#Delete", simple: true), - "Dislike" => TermDefinition.new("Dislike", id: "https://www.w3.org/ns/activitystreams#Dislike", simple: true), - "Document" => TermDefinition.new("Document", id: "https://www.w3.org/ns/activitystreams#Document", simple: true), - "Event" => TermDefinition.new("Event", id: "https://www.w3.org/ns/activitystreams#Event", simple: true), - "Flag" => TermDefinition.new("Flag", id: "https://www.w3.org/ns/activitystreams#Flag", simple: true), - "Follow" => TermDefinition.new("Follow", id: "https://www.w3.org/ns/activitystreams#Follow", simple: true), - "Group" => TermDefinition.new("Group", id: "https://www.w3.org/ns/activitystreams#Group", simple: true), - "Ignore" => TermDefinition.new("Ignore", id: "https://www.w3.org/ns/activitystreams#Ignore", simple: true), - "Image" => TermDefinition.new("Image", id: "https://www.w3.org/ns/activitystreams#Image", simple: true), - "IntransitiveActivity" => TermDefinition.new("IntransitiveActivity", id: "https://www.w3.org/ns/activitystreams#IntransitiveActivity", simple: true), - "Invite" => TermDefinition.new("Invite", id: "https://www.w3.org/ns/activitystreams#Invite", simple: true), - "IsContact" => TermDefinition.new("IsContact", id: "https://www.w3.org/ns/activitystreams#IsContact", simple: true), - "IsFollowedBy" => TermDefinition.new("IsFollowedBy", id: "https://www.w3.org/ns/activitystreams#IsFollowedBy", simple: true), - "IsFollowing" => TermDefinition.new("IsFollowing", id: "https://www.w3.org/ns/activitystreams#IsFollowing", simple: true), - "IsMember" => TermDefinition.new("IsMember", id: "https://www.w3.org/ns/activitystreams#IsMember", simple: true), - "Join" => TermDefinition.new("Join", id: "https://www.w3.org/ns/activitystreams#Join", simple: true), - "Leave" => TermDefinition.new("Leave", id: "https://www.w3.org/ns/activitystreams#Leave", simple: true), - "Like" => TermDefinition.new("Like", id: "https://www.w3.org/ns/activitystreams#Like", simple: true), - "Link" => TermDefinition.new("Link", id: "https://www.w3.org/ns/activitystreams#Link", simple: true), - "Listen" => TermDefinition.new("Listen", id: "https://www.w3.org/ns/activitystreams#Listen", simple: true), - "Mention" => TermDefinition.new("Mention", id: "https://www.w3.org/ns/activitystreams#Mention", simple: true), - "Move" => TermDefinition.new("Move", id: "https://www.w3.org/ns/activitystreams#Move", simple: true), - "Note" => TermDefinition.new("Note", id: "https://www.w3.org/ns/activitystreams#Note", simple: true), - "Object" => TermDefinition.new("Object", id: "https://www.w3.org/ns/activitystreams#Object", simple: true), - "Offer" => TermDefinition.new("Offer", id: "https://www.w3.org/ns/activitystreams#Offer", simple: true), - "OrderedCollection" => TermDefinition.new("OrderedCollection", id: "https://www.w3.org/ns/activitystreams#OrderedCollection", simple: true), - "OrderedCollectionPage" => TermDefinition.new("OrderedCollectionPage", id: "https://www.w3.org/ns/activitystreams#OrderedCollectionPage", simple: true), - "Organization" => TermDefinition.new("Organization", id: "https://www.w3.org/ns/activitystreams#Organization", simple: true), - "Page" => TermDefinition.new("Page", id: "https://www.w3.org/ns/activitystreams#Page", simple: true), - "Person" => TermDefinition.new("Person", id: "https://www.w3.org/ns/activitystreams#Person", simple: true), - "Place" => TermDefinition.new("Place", id: "https://www.w3.org/ns/activitystreams#Place", simple: true), - "Profile" => TermDefinition.new("Profile", id: "https://www.w3.org/ns/activitystreams#Profile", simple: true), - "Question" => TermDefinition.new("Question", id: "https://www.w3.org/ns/activitystreams#Question", simple: true), - "Read" => TermDefinition.new("Read", id: "https://www.w3.org/ns/activitystreams#Read", simple: true), - "Reject" => TermDefinition.new("Reject", id: "https://www.w3.org/ns/activitystreams#Reject", simple: true), - "Relationship" => TermDefinition.new("Relationship", id: "https://www.w3.org/ns/activitystreams#Relationship", simple: true), - "Remove" => TermDefinition.new("Remove", id: "https://www.w3.org/ns/activitystreams#Remove", simple: true), - "Service" => TermDefinition.new("Service", id: "https://www.w3.org/ns/activitystreams#Service", simple: true), - "TentativeAccept" => TermDefinition.new("TentativeAccept", id: "https://www.w3.org/ns/activitystreams#TentativeAccept", simple: true), - "TentativeReject" => TermDefinition.new("TentativeReject", id: "https://www.w3.org/ns/activitystreams#TentativeReject", simple: true), - "Tombstone" => TermDefinition.new("Tombstone", id: "https://www.w3.org/ns/activitystreams#Tombstone", simple: true), - "Travel" => TermDefinition.new("Travel", id: "https://www.w3.org/ns/activitystreams#Travel", simple: true), - "Undo" => TermDefinition.new("Undo", id: "https://www.w3.org/ns/activitystreams#Undo", simple: true), - "Update" => TermDefinition.new("Update", id: "https://www.w3.org/ns/activitystreams#Update", simple: true), - "Video" => TermDefinition.new("Video", id: "https://www.w3.org/ns/activitystreams#Video", simple: true), - "View" => TermDefinition.new("View", id: "https://www.w3.org/ns/activitystreams#View", simple: true), - "accuracy" => TermDefinition.new("accuracy", id: "https://www.w3.org/ns/activitystreams#accuracy", type_mapping: "http://www.w3.org/2001/XMLSchema#float"), - "actor" => TermDefinition.new("actor", id: "https://www.w3.org/ns/activitystreams#actor", type_mapping: "@id"), - "altitude" => TermDefinition.new("altitude", id: "https://www.w3.org/ns/activitystreams#altitude", type_mapping: "http://www.w3.org/2001/XMLSchema#float"), - "anyOf" => TermDefinition.new("anyOf", id: "https://www.w3.org/ns/activitystreams#anyOf", type_mapping: "@id"), - "as" => TermDefinition.new("as", id: "https://www.w3.org/ns/activitystreams#", simple: true, prefix: true), - "attachment" => TermDefinition.new("attachment", id: "https://www.w3.org/ns/activitystreams#attachment", type_mapping: "@id"), - "attributedTo" => TermDefinition.new("attributedTo", id: "https://www.w3.org/ns/activitystreams#attributedTo", type_mapping: "@id"), - "audience" => TermDefinition.new("audience", id: "https://www.w3.org/ns/activitystreams#audience", type_mapping: "@id"), - "bcc" => TermDefinition.new("bcc", id: "https://www.w3.org/ns/activitystreams#bcc", type_mapping: "@id"), - "bto" => TermDefinition.new("bto", id: "https://www.w3.org/ns/activitystreams#bto", type_mapping: "@id"), - "cc" => TermDefinition.new("cc", id: "https://www.w3.org/ns/activitystreams#cc", type_mapping: "@id"), - "closed" => TermDefinition.new("closed", id: "https://www.w3.org/ns/activitystreams#closed", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "content" => TermDefinition.new("content", id: "https://www.w3.org/ns/activitystreams#content", simple: true), - "contentMap" => TermDefinition.new("contentMap", id: "https://www.w3.org/ns/activitystreams#content", container_mapping: "@language"), - "context" => TermDefinition.new("context", id: "https://www.w3.org/ns/activitystreams#context", type_mapping: "@id"), - "current" => TermDefinition.new("current", id: "https://www.w3.org/ns/activitystreams#current", type_mapping: "@id"), - "deleted" => TermDefinition.new("deleted", id: "https://www.w3.org/ns/activitystreams#deleted", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "describes" => TermDefinition.new("describes", id: "https://www.w3.org/ns/activitystreams#describes", type_mapping: "@id"), - "duration" => TermDefinition.new("duration", id: "https://www.w3.org/ns/activitystreams#duration", type_mapping: "http://www.w3.org/2001/XMLSchema#duration"), - "endTime" => TermDefinition.new("endTime", id: "https://www.w3.org/ns/activitystreams#endTime", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "endpoints" => TermDefinition.new("endpoints", id: "https://www.w3.org/ns/activitystreams#endpoints", type_mapping: "@id"), - "first" => TermDefinition.new("first", id: "https://www.w3.org/ns/activitystreams#first", type_mapping: "@id"), - "followers" => TermDefinition.new("followers", id: "https://www.w3.org/ns/activitystreams#followers", type_mapping: "@id"), - "following" => TermDefinition.new("following", id: "https://www.w3.org/ns/activitystreams#following", type_mapping: "@id"), - "formerType" => TermDefinition.new("formerType", id: "https://www.w3.org/ns/activitystreams#formerType", type_mapping: "@id"), - "generator" => TermDefinition.new("generator", id: "https://www.w3.org/ns/activitystreams#generator", type_mapping: "@id"), - "height" => TermDefinition.new("height", id: "https://www.w3.org/ns/activitystreams#height", type_mapping: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"), - "href" => TermDefinition.new("href", id: "https://www.w3.org/ns/activitystreams#href", type_mapping: "@id"), - "hreflang" => TermDefinition.new("hreflang", id: "https://www.w3.org/ns/activitystreams#hreflang", simple: true), - "icon" => TermDefinition.new("icon", id: "https://www.w3.org/ns/activitystreams#icon", type_mapping: "@id"), - "id" => TermDefinition.new("id", id: "@id", simple: true), - "image" => TermDefinition.new("image", id: "https://www.w3.org/ns/activitystreams#image", type_mapping: "@id"), - "inReplyTo" => TermDefinition.new("inReplyTo", id: "https://www.w3.org/ns/activitystreams#inReplyTo", type_mapping: "@id"), - "inbox" => TermDefinition.new("inbox", id: "http://www.w3.org/ns/ldp#inbox", type_mapping: "@id"), - "instrument" => TermDefinition.new("instrument", id: "https://www.w3.org/ns/activitystreams#instrument", type_mapping: "@id"), - "items" => TermDefinition.new("items", id: "https://www.w3.org/ns/activitystreams#items", type_mapping: "@id"), - "last" => TermDefinition.new("last", id: "https://www.w3.org/ns/activitystreams#last", type_mapping: "@id"), - "latitude" => TermDefinition.new("latitude", id: "https://www.w3.org/ns/activitystreams#latitude", type_mapping: "http://www.w3.org/2001/XMLSchema#float"), - "ldp" => TermDefinition.new("ldp", id: "http://www.w3.org/ns/ldp#", simple: true, prefix: true), - "liked" => TermDefinition.new("liked", id: "https://www.w3.org/ns/activitystreams#liked", type_mapping: "@id"), - "location" => TermDefinition.new("location", id: "https://www.w3.org/ns/activitystreams#location", type_mapping: "@id"), - "longitude" => TermDefinition.new("longitude", id: "https://www.w3.org/ns/activitystreams#longitude", type_mapping: "http://www.w3.org/2001/XMLSchema#float"), - "mediaType" => TermDefinition.new("mediaType", id: "https://www.w3.org/ns/activitystreams#mediaType", simple: true), - "name" => TermDefinition.new("name", id: "https://www.w3.org/ns/activitystreams#name", simple: true), - "nameMap" => TermDefinition.new("nameMap", id: "https://www.w3.org/ns/activitystreams#name", container_mapping: "@language"), - "next" => TermDefinition.new("next", id: "https://www.w3.org/ns/activitystreams#next", type_mapping: "@id"), - "oauthAuthorizationEndpoint" => TermDefinition.new("oauthAuthorizationEndpoint", id: "https://www.w3.org/ns/activitystreams#oauthAuthorizationEndpoint", type_mapping: "@id"), - "oauthTokenEndpoint" => TermDefinition.new("oauthTokenEndpoint", id: "https://www.w3.org/ns/activitystreams#oauthTokenEndpoint", type_mapping: "@id"), - "object" => TermDefinition.new("object", id: "https://www.w3.org/ns/activitystreams#object", type_mapping: "@id"), - "oneOf" => TermDefinition.new("oneOf", id: "https://www.w3.org/ns/activitystreams#oneOf", type_mapping: "@id"), - "orderedItems" => TermDefinition.new("orderedItems", id: "https://www.w3.org/ns/activitystreams#items", type_mapping: "@id", container_mapping: "@list"), - "origin" => TermDefinition.new("origin", id: "https://www.w3.org/ns/activitystreams#origin", type_mapping: "@id"), - "outbox" => TermDefinition.new("outbox", id: "https://www.w3.org/ns/activitystreams#outbox", type_mapping: "@id"), - "partOf" => TermDefinition.new("partOf", id: "https://www.w3.org/ns/activitystreams#partOf", type_mapping: "@id"), - "preferredUsername" => TermDefinition.new("preferredUsername", id: "https://www.w3.org/ns/activitystreams#preferredUsername", simple: true), - "prev" => TermDefinition.new("prev", id: "https://www.w3.org/ns/activitystreams#prev", type_mapping: "@id"), - "preview" => TermDefinition.new("preview", id: "https://www.w3.org/ns/activitystreams#preview", type_mapping: "@id"), - "provideClientKey" => TermDefinition.new("provideClientKey", id: "https://www.w3.org/ns/activitystreams#provideClientKey", type_mapping: "@id"), - "proxyUrl" => TermDefinition.new("proxyUrl", id: "https://www.w3.org/ns/activitystreams#proxyUrl", type_mapping: "@id"), - "published" => TermDefinition.new("published", id: "https://www.w3.org/ns/activitystreams#published", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "radius" => TermDefinition.new("radius", id: "https://www.w3.org/ns/activitystreams#radius", type_mapping: "http://www.w3.org/2001/XMLSchema#float"), - "rel" => TermDefinition.new("rel", id: "https://www.w3.org/ns/activitystreams#rel", simple: true), - "relationship" => TermDefinition.new("relationship", id: "https://www.w3.org/ns/activitystreams#relationship", type_mapping: "@id"), - "replies" => TermDefinition.new("replies", id: "https://www.w3.org/ns/activitystreams#replies", type_mapping: "@id"), - "result" => TermDefinition.new("result", id: "https://www.w3.org/ns/activitystreams#result", type_mapping: "@id"), - "sharedInbox" => TermDefinition.new("sharedInbox", id: "https://www.w3.org/ns/activitystreams#sharedInbox", type_mapping: "@id"), - "signClientKey" => TermDefinition.new("signClientKey", id: "https://www.w3.org/ns/activitystreams#signClientKey", type_mapping: "@id"), - "source" => TermDefinition.new("source", id: "https://www.w3.org/ns/activitystreams#source", simple: true), - "startIndex" => TermDefinition.new("startIndex", id: "https://www.w3.org/ns/activitystreams#startIndex", type_mapping: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"), - "startTime" => TermDefinition.new("startTime", id: "https://www.w3.org/ns/activitystreams#startTime", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "streams" => TermDefinition.new("streams", id: "https://www.w3.org/ns/activitystreams#streams", type_mapping: "@id"), - "subject" => TermDefinition.new("subject", id: "https://www.w3.org/ns/activitystreams#subject", type_mapping: "@id"), - "summary" => TermDefinition.new("summary", id: "https://www.w3.org/ns/activitystreams#summary", simple: true), - "summaryMap" => TermDefinition.new("summaryMap", id: "https://www.w3.org/ns/activitystreams#summary", container_mapping: "@language"), - "tag" => TermDefinition.new("tag", id: "https://www.w3.org/ns/activitystreams#tag", type_mapping: "@id"), - "target" => TermDefinition.new("target", id: "https://www.w3.org/ns/activitystreams#target", type_mapping: "@id"), - "to" => TermDefinition.new("to", id: "https://www.w3.org/ns/activitystreams#to", type_mapping: "@id"), - "totalItems" => TermDefinition.new("totalItems", id: "https://www.w3.org/ns/activitystreams#totalItems", type_mapping: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"), - "type" => TermDefinition.new("type", id: "@type", simple: true), - "units" => TermDefinition.new("units", id: "https://www.w3.org/ns/activitystreams#units", simple: true), - "updated" => TermDefinition.new("updated", id: "https://www.w3.org/ns/activitystreams#updated", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "uploadMedia" => TermDefinition.new("uploadMedia", id: "https://www.w3.org/ns/activitystreams#uploadMedia", type_mapping: "@id"), - "url" => TermDefinition.new("url", id: "https://www.w3.org/ns/activitystreams#url", type_mapping: "@id"), - "width" => TermDefinition.new("width", id: "https://www.w3.org/ns/activitystreams#width", type_mapping: "http://www.w3.org/2001/XMLSchema#nonNegativeInteger"), - "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true) - }) - end -end diff --git a/lib/json_ld/identity.rb b/lib/json_ld/identity.rb deleted file mode 100644 index cfe50b956..000000000 --- a/lib/json_ld/identity.rb +++ /dev/null @@ -1,86 +0,0 @@ -# -*- encoding: utf-8 -*- -# frozen_string_literal: true -# This file generated automatically from https://w3id.org/identity/v1 -require 'json/ld' -class JSON::LD::Context - add_preloaded("https://w3id.org/identity/v1") do - new(processingMode: "json-ld-1.0", term_definitions: { - "Credential" => TermDefinition.new("Credential", id: "https://w3id.org/credentials#Credential", simple: true), - "CryptographicKey" => TermDefinition.new("CryptographicKey", id: "https://w3id.org/security#Key", simple: true), - "CryptographicKeyCredential" => TermDefinition.new("CryptographicKeyCredential", id: "https://w3id.org/credentials#CryptographicKeyCredential", simple: true), - "EncryptedMessage" => TermDefinition.new("EncryptedMessage", id: "https://w3id.org/security#EncryptedMessage", simple: true), - "GraphSignature2012" => TermDefinition.new("GraphSignature2012", id: "https://w3id.org/security#GraphSignature2012", simple: true), - "Group" => TermDefinition.new("Group", id: "https://www.w3.org/ns/activitystreams#Group", simple: true), - "Identity" => TermDefinition.new("Identity", id: "https://w3id.org/identity#Identity", simple: true), - "LinkedDataSignature2015" => TermDefinition.new("LinkedDataSignature2015", id: "https://w3id.org/security#LinkedDataSignature2015", simple: true), - "Organization" => TermDefinition.new("Organization", id: "http://schema.org/Organization", simple: true), - "Person" => TermDefinition.new("Person", id: "http://schema.org/Person", simple: true), - "PostalAddress" => TermDefinition.new("PostalAddress", id: "http://schema.org/PostalAddress", simple: true), - "about" => TermDefinition.new("about", id: "http://schema.org/about", type_mapping: "@id"), - "accessControl" => TermDefinition.new("accessControl", id: "https://w3id.org/permissions#accessControl", type_mapping: "@id"), - "address" => TermDefinition.new("address", id: "http://schema.org/address", type_mapping: "@id"), - "addressCountry" => TermDefinition.new("addressCountry", id: "http://schema.org/addressCountry", simple: true), - "addressLocality" => TermDefinition.new("addressLocality", id: "http://schema.org/addressLocality", simple: true), - "addressRegion" => TermDefinition.new("addressRegion", id: "http://schema.org/addressRegion", simple: true), - "cipherAlgorithm" => TermDefinition.new("cipherAlgorithm", id: "https://w3id.org/security#cipherAlgorithm", simple: true), - "cipherData" => TermDefinition.new("cipherData", id: "https://w3id.org/security#cipherData", simple: true), - "cipherKey" => TermDefinition.new("cipherKey", id: "https://w3id.org/security#cipherKey", simple: true), - "claim" => TermDefinition.new("claim", id: "https://w3id.org/credentials#claim", type_mapping: "@id"), - "comment" => TermDefinition.new("comment", id: "http://www.w3.org/2000/01/rdf-schema#comment", simple: true), - "created" => TermDefinition.new("created", id: "http://purl.org/dc/terms/created", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "creator" => TermDefinition.new("creator", id: "http://purl.org/dc/terms/creator", type_mapping: "@id"), - "cred" => TermDefinition.new("cred", id: "https://w3id.org/credentials#", simple: true, prefix: true), - "credential" => TermDefinition.new("credential", id: "https://w3id.org/credentials#credential", type_mapping: "@id"), - "dc" => TermDefinition.new("dc", id: "http://purl.org/dc/terms/", simple: true, prefix: true), - "description" => TermDefinition.new("description", id: "http://schema.org/description", simple: true), - "digestAlgorithm" => TermDefinition.new("digestAlgorithm", id: "https://w3id.org/security#digestAlgorithm", simple: true), - "digestValue" => TermDefinition.new("digestValue", id: "https://w3id.org/security#digestValue", simple: true), - "domain" => TermDefinition.new("domain", id: "https://w3id.org/security#domain", simple: true), - "email" => TermDefinition.new("email", id: "http://schema.org/email", simple: true), - "expires" => TermDefinition.new("expires", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "familyName" => TermDefinition.new("familyName", id: "http://schema.org/familyName", simple: true), - "givenName" => TermDefinition.new("givenName", id: "http://schema.org/givenName", simple: true), - "id" => TermDefinition.new("id", id: "@id", simple: true), - "identity" => TermDefinition.new("identity", id: "https://w3id.org/identity#", simple: true, prefix: true), - "identityService" => TermDefinition.new("identityService", id: "https://w3id.org/identity#identityService", type_mapping: "@id"), - "idp" => TermDefinition.new("idp", id: "https://w3id.org/identity#idp", type_mapping: "@id"), - "image" => TermDefinition.new("image", id: "http://schema.org/image", type_mapping: "@id"), - "initializationVector" => TermDefinition.new("initializationVector", id: "https://w3id.org/security#initializationVector", simple: true), - "issued" => TermDefinition.new("issued", id: "https://w3id.org/credentials#issued", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "issuer" => TermDefinition.new("issuer", id: "https://w3id.org/credentials#issuer", type_mapping: "@id"), - "label" => TermDefinition.new("label", id: "http://www.w3.org/2000/01/rdf-schema#label", simple: true), - "member" => TermDefinition.new("member", id: "http://schema.org/member", type_mapping: "@id"), - "memberOf" => TermDefinition.new("memberOf", id: "http://schema.org/memberOf", type_mapping: "@id"), - "name" => TermDefinition.new("name", id: "http://schema.org/name", simple: true), - "nonce" => TermDefinition.new("nonce", id: "https://w3id.org/security#nonce", simple: true), - "normalizationAlgorithm" => TermDefinition.new("normalizationAlgorithm", id: "https://w3id.org/security#normalizationAlgorithm", simple: true), - "owner" => TermDefinition.new("owner", id: "https://w3id.org/security#owner", type_mapping: "@id"), - "password" => TermDefinition.new("password", id: "https://w3id.org/security#password", simple: true), - "paymentProcessor" => TermDefinition.new("paymentProcessor", id: "https://w3id.org/payswarm#processor", simple: true), - "perm" => TermDefinition.new("perm", id: "https://w3id.org/permissions#", simple: true, prefix: true), - "postalCode" => TermDefinition.new("postalCode", id: "http://schema.org/postalCode", simple: true), - "preferences" => TermDefinition.new("preferences", id: "https://w3id.org/payswarm#preferences", type_mapping: "@vocab"), - "privateKey" => TermDefinition.new("privateKey", id: "https://w3id.org/security#privateKey", type_mapping: "@id"), - "privateKeyPem" => TermDefinition.new("privateKeyPem", id: "https://w3id.org/security#privateKeyPem", simple: true), - "ps" => TermDefinition.new("ps", id: "https://w3id.org/payswarm#", simple: true, prefix: true), - "publicKey" => TermDefinition.new("publicKey", id: "https://w3id.org/security#publicKey", type_mapping: "@id"), - "publicKeyPem" => TermDefinition.new("publicKeyPem", id: "https://w3id.org/security#publicKeyPem", simple: true), - "publicKeyService" => TermDefinition.new("publicKeyService", id: "https://w3id.org/security#publicKeyService", type_mapping: "@id"), - "rdf" => TermDefinition.new("rdf", id: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", simple: true, prefix: true), - "rdfs" => TermDefinition.new("rdfs", id: "http://www.w3.org/2000/01/rdf-schema#", simple: true, prefix: true), - "recipient" => TermDefinition.new("recipient", id: "https://w3id.org/credentials#recipient", type_mapping: "@id"), - "revoked" => TermDefinition.new("revoked", id: "https://w3id.org/security#revoked", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "schema" => TermDefinition.new("schema", id: "http://schema.org/", simple: true, prefix: true), - "sec" => TermDefinition.new("sec", id: "https://w3id.org/security#", simple: true, prefix: true), - "signature" => TermDefinition.new("signature", id: "https://w3id.org/security#signature", simple: true), - "signatureAlgorithm" => TermDefinition.new("signatureAlgorithm", id: "https://w3id.org/security#signatureAlgorithm", simple: true), - "signatureValue" => TermDefinition.new("signatureValue", id: "https://w3id.org/security#signatureValue", simple: true), - "streetAddress" => TermDefinition.new("streetAddress", id: "http://schema.org/streetAddress", simple: true), - "title" => TermDefinition.new("title", id: "http://purl.org/dc/terms/title", simple: true), - "type" => TermDefinition.new("type", id: "@type", simple: true), - "url" => TermDefinition.new("url", id: "http://schema.org/url", type_mapping: "@id"), - "writePermission" => TermDefinition.new("writePermission", id: "https://w3id.org/permissions#writePermission", type_mapping: "@id"), - "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true) - }) - end -end diff --git a/lib/json_ld/security.rb b/lib/json_ld/security.rb deleted file mode 100644 index 1230206f0..000000000 --- a/lib/json_ld/security.rb +++ /dev/null @@ -1,50 +0,0 @@ -# -*- encoding: utf-8 -*- -# frozen_string_literal: true -# This file generated automatically from https://w3id.org/security/v1 -require 'json/ld' -class JSON::LD::Context - add_preloaded("https://w3id.org/security/v1") do - new(processingMode: "json-ld-1.0", term_definitions: { - "CryptographicKey" => TermDefinition.new("CryptographicKey", id: "https://w3id.org/security#Key", simple: true), - "EcdsaKoblitzSignature2016" => TermDefinition.new("EcdsaKoblitzSignature2016", id: "https://w3id.org/security#EcdsaKoblitzSignature2016", simple: true), - "EncryptedMessage" => TermDefinition.new("EncryptedMessage", id: "https://w3id.org/security#EncryptedMessage", simple: true), - "GraphSignature2012" => TermDefinition.new("GraphSignature2012", id: "https://w3id.org/security#GraphSignature2012", simple: true), - "LinkedDataSignature2015" => TermDefinition.new("LinkedDataSignature2015", id: "https://w3id.org/security#LinkedDataSignature2015", simple: true), - "LinkedDataSignature2016" => TermDefinition.new("LinkedDataSignature2016", id: "https://w3id.org/security#LinkedDataSignature2016", simple: true), - "authenticationTag" => TermDefinition.new("authenticationTag", id: "https://w3id.org/security#authenticationTag", simple: true), - "canonicalizationAlgorithm" => TermDefinition.new("canonicalizationAlgorithm", id: "https://w3id.org/security#canonicalizationAlgorithm", simple: true), - "cipherAlgorithm" => TermDefinition.new("cipherAlgorithm", id: "https://w3id.org/security#cipherAlgorithm", simple: true), - "cipherData" => TermDefinition.new("cipherData", id: "https://w3id.org/security#cipherData", simple: true), - "cipherKey" => TermDefinition.new("cipherKey", id: "https://w3id.org/security#cipherKey", simple: true), - "created" => TermDefinition.new("created", id: "http://purl.org/dc/terms/created", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "creator" => TermDefinition.new("creator", id: "http://purl.org/dc/terms/creator", type_mapping: "@id"), - "dc" => TermDefinition.new("dc", id: "http://purl.org/dc/terms/", simple: true, prefix: true), - "digestAlgorithm" => TermDefinition.new("digestAlgorithm", id: "https://w3id.org/security#digestAlgorithm", simple: true), - "digestValue" => TermDefinition.new("digestValue", id: "https://w3id.org/security#digestValue", simple: true), - "domain" => TermDefinition.new("domain", id: "https://w3id.org/security#domain", simple: true), - "encryptionKey" => TermDefinition.new("encryptionKey", id: "https://w3id.org/security#encryptionKey", simple: true), - "expiration" => TermDefinition.new("expiration", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "expires" => TermDefinition.new("expires", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "id" => TermDefinition.new("id", id: "@id", simple: true), - "initializationVector" => TermDefinition.new("initializationVector", id: "https://w3id.org/security#initializationVector", simple: true), - "iterationCount" => TermDefinition.new("iterationCount", id: "https://w3id.org/security#iterationCount", simple: true), - "nonce" => TermDefinition.new("nonce", id: "https://w3id.org/security#nonce", simple: true), - "normalizationAlgorithm" => TermDefinition.new("normalizationAlgorithm", id: "https://w3id.org/security#normalizationAlgorithm", simple: true), - "owner" => TermDefinition.new("owner", id: "https://w3id.org/security#owner", type_mapping: "@id"), - "password" => TermDefinition.new("password", id: "https://w3id.org/security#password", simple: true), - "privateKey" => TermDefinition.new("privateKey", id: "https://w3id.org/security#privateKey", type_mapping: "@id"), - "privateKeyPem" => TermDefinition.new("privateKeyPem", id: "https://w3id.org/security#privateKeyPem", simple: true), - "publicKey" => TermDefinition.new("publicKey", id: "https://w3id.org/security#publicKey", type_mapping: "@id"), - "publicKeyPem" => TermDefinition.new("publicKeyPem", id: "https://w3id.org/security#publicKeyPem", simple: true), - "publicKeyService" => TermDefinition.new("publicKeyService", id: "https://w3id.org/security#publicKeyService", type_mapping: "@id"), - "revoked" => TermDefinition.new("revoked", id: "https://w3id.org/security#revoked", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"), - "salt" => TermDefinition.new("salt", id: "https://w3id.org/security#salt", simple: true), - "sec" => TermDefinition.new("sec", id: "https://w3id.org/security#", simple: true, prefix: true), - "signature" => TermDefinition.new("signature", id: "https://w3id.org/security#signature", simple: true), - "signatureAlgorithm" => TermDefinition.new("signatureAlgorithm", id: "https://w3id.org/security#signingAlgorithm", simple: true), - "signatureValue" => TermDefinition.new("signatureValue", id: "https://w3id.org/security#signatureValue", simple: true), - "type" => TermDefinition.new("type", id: "@type", simple: true), - "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true) - }) - end -end diff --git a/lib/mastodon/migration_helpers.rb b/lib/mastodon/migration_helpers.rb index 6f6f99f63..e154b5a2c 100644 --- a/lib/mastodon/migration_helpers.rb +++ b/lib/mastodon/migration_helpers.rb @@ -985,6 +985,17 @@ into similar problems in the future (e.g. when new tables are created). BackgroundMigrationWorker.perform_in(delay_interval * index, job_class_name, [start_id, end_id]) end end + + private + + # https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb#L678-L684 + def extract_foreign_key_action(specifier) + case specifier + when 'c'; :cascade + when 'n'; :nullify + when 'r'; :restrict + end + end end end diff --git a/lib/mastodon/redis_config.rb b/lib/mastodon/redis_config.rb index cf4f20f76..f11d94a45 100644 --- a/lib/mastodon/redis_config.rb +++ b/lib/mastodon/redis_config.rb @@ -1,16 +1,29 @@ # frozen_string_literal: true -if ENV['REDIS_URL'].blank? - password = ENV.fetch('REDIS_PASSWORD') { '' } - host = ENV.fetch('REDIS_HOST') { 'localhost' } - port = ENV.fetch('REDIS_PORT') { 6379 } - db = ENV.fetch('REDIS_DB') { 0 } +def setup_redis_env_url(prefix = nil, defaults = true) + prefix = prefix.to_s.upcase + '_' unless prefix.nil? + prefix = '' if prefix.nil? - ENV['REDIS_URL'] = "redis://#{password.blank? ? '' : ":#{password}@"}#{host}:#{port}/#{db}" + return if ENV[prefix + 'REDIS_URL'].present? + + password = ENV.fetch(prefix + 'REDIS_PASSWORD') { '' if defaults } + host = ENV.fetch(prefix + 'REDIS_HOST') { 'localhost' if defaults } + port = ENV.fetch(prefix + 'REDIS_PORT') { 6379 if defaults } + db = ENV.fetch(prefix + 'REDIS_DB') { 0 if defaults } + + ENV[prefix + 'REDIS_URL'] = if [password, host, port, db].all?(&:nil?) + ENV['REDIS_URL'] + else + "redis://#{password.blank? ? '' : ":#{password}@"}#{host}:#{port}/#{db}" + end end -namespace = ENV.fetch('REDIS_NAMESPACE') { nil } +setup_redis_env_url +setup_redis_env_url(:cache, false) + +namespace = ENV.fetch('REDIS_NAMESPACE') { nil } cache_namespace = namespace ? namespace + '_cache' : 'cache' + REDIS_CACHE_PARAMS = { expires_in: 10.minutes, namespace: cache_namespace, diff --git a/lib/mastodon/snowflake.rb b/lib/mastodon/snowflake.rb index 219e323d4..9e5bc7383 100644 --- a/lib/mastodon/snowflake.rb +++ b/lib/mastodon/snowflake.rb @@ -7,7 +7,7 @@ module Mastodon::Snowflake def self.around_create(record) now = Time.now.utc - if record.created_at.nil? || record.created_at >= now || record.created_at == record.updated_at + if record.created_at.nil? || record.created_at >= now || record.created_at == record.updated_at || record.override_timestamps yield else record.id = Mastodon::Snowflake.id_at(record.created_at) diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake index 505c7e0fa..00a85fa5e 100644 --- a/lib/tasks/mastodon.rake +++ b/lib/tasks/mastodon.rake @@ -2,6 +2,8 @@ require 'optparse' require 'colorize' +require 'tty-command' +require 'tty-prompt' namespace :mastodon do desc 'Configure the instance for production use' @@ -107,9 +109,16 @@ namespace :mastodon do q.convert :int end + env['REDIS_PASSWORD'] = prompt.ask('Redis password:') do |q| + q.required false + q.default nil + q.modify :strip + end + redis_options = { host: env['REDIS_HOST'], port: env['REDIS_PORT'], + password: env['REDIS_PASSWORD'], driver: :hiredis, } diff --git a/spec/controllers/about_controller_spec.rb b/spec/controllers/about_controller_spec.rb index c2c34d34a..2089b3b16 100644 --- a/spec/controllers/about_controller_spec.rb +++ b/spec/controllers/about_controller_spec.rb @@ -17,7 +17,7 @@ RSpec.describe AboutController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -35,7 +35,7 @@ RSpec.describe AboutController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -49,7 +49,7 @@ RSpec.describe AboutController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb index a8ade790c..18c249c07 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/controllers/accounts_controller_spec.rb @@ -40,7 +40,7 @@ RSpec.describe AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns correct format' do diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb index a25998021..47460b22c 100644 --- a/spec/controllers/activitypub/outboxes_controller_spec.rb +++ b/spec/controllers/activitypub/outboxes_controller_spec.rb @@ -13,7 +13,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns application/activity+json' do diff --git a/spec/controllers/admin/accounts_controller_spec.rb b/spec/controllers/admin/accounts_controller_spec.rb index 8be27d866..ff9dbbfb8 100644 --- a/spec/controllers/admin/accounts_controller_spec.rb +++ b/spec/controllers/admin/accounts_controller_spec.rb @@ -63,7 +63,7 @@ RSpec.describe Admin::AccountsController, type: :controller do it 'returns http success' do get :index - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -72,7 +72,7 @@ RSpec.describe Admin::AccountsController, type: :controller do it 'returns http success' do get :show, params: { id: account.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/admin/change_email_controller_spec.rb b/spec/controllers/admin/change_email_controller_spec.rb index 50f94f835..31df0f0fc 100644 --- a/spec/controllers/admin/change_email_controller_spec.rb +++ b/spec/controllers/admin/change_email_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Admin::ChangeEmailsController, type: :controller do get :show, params: { account_id: account.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/admin/confirmations_controller_spec.rb b/spec/controllers/admin/confirmations_controller_spec.rb index 3f2b28c0e..7c8034964 100644 --- a/spec/controllers/admin/confirmations_controller_spec.rb +++ b/spec/controllers/admin/confirmations_controller_spec.rb @@ -20,14 +20,14 @@ RSpec.describe Admin::ConfirmationsController, type: :controller do it 'raises an error when there is no account' do post :create, params: { account_id: 'fake' } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end it 'raises an error when there is no user' do account = Fabricate(:account, user: nil) post :create, params: { account_id: account.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb index b9e73c04b..79e7fea42 100644 --- a/spec/controllers/admin/domain_blocks_controller_spec.rb +++ b/spec/controllers/admin/domain_blocks_controller_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do assigned = assigns(:domain_blocks) expect(assigned.count).to eq 1 expect(assigned.klass).to be DomainBlock - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -32,7 +32,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do get :new expect(assigns(:domain_block)).to be_instance_of(DomainBlock) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -41,7 +41,7 @@ RSpec.describe Admin::DomainBlocksController, type: :controller do domain_block = Fabricate(:domain_block) get :show, params: { id: domain_block.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/admin/email_domain_blocks_controller_spec.rb b/spec/controllers/admin/email_domain_blocks_controller_spec.rb index 295de9073..133d38ff1 100644 --- a/spec/controllers/admin/email_domain_blocks_controller_spec.rb +++ b/spec/controllers/admin/email_domain_blocks_controller_spec.rb @@ -25,7 +25,7 @@ RSpec.describe Admin::EmailDomainBlocksController, type: :controller do assigned = assigns(:email_domain_blocks) expect(assigned.count).to eq 1 expect(assigned.klass).to be EmailDomainBlock - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -34,7 +34,7 @@ RSpec.describe Admin::EmailDomainBlocksController, type: :controller do get :new expect(assigns(:email_domain_block)).to be_instance_of(EmailDomainBlock) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/admin/instances_controller_spec.rb b/spec/controllers/admin/instances_controller_spec.rb index f57e3fa97..412b81443 100644 --- a/spec/controllers/admin/instances_controller_spec.rb +++ b/spec/controllers/admin/instances_controller_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Admin::InstancesController, type: :controller do expect(instances.size).to eq 1 expect(instances[0].domain).to eq 'less.popular' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/admin/reported_statuses_controller_spec.rb b/spec/controllers/admin/reported_statuses_controller_spec.rb index 297807d41..29957ed37 100644 --- a/spec/controllers/admin/reported_statuses_controller_spec.rb +++ b/spec/controllers/admin/reported_statuses_controller_spec.rb @@ -13,7 +13,7 @@ describe Admin::ReportedStatusesController do describe 'POST #create' do subject do - -> { post :create, params: { report_id: report, form_status_batch: { action: action, status_ids: status_ids } } } + -> { post :create, params: { :report_id => report, action => '', :form_status_batch => { status_ids: status_ids } } } end let(:action) { 'nsfw_on' } @@ -84,7 +84,7 @@ describe Admin::ReportedStatusesController do allow(RemovalWorker).to receive(:perform_async) delete :destroy, params: { report_id: report, id: status } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(RemovalWorker). to have_received(:perform_async).with(status.id) end diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb index 9be298df6..e50c02a72 100644 --- a/spec/controllers/admin/reports_controller_spec.rb +++ b/spec/controllers/admin/reports_controller_spec.rb @@ -18,7 +18,7 @@ describe Admin::ReportsController do reports = assigns(:reports).to_a expect(reports.size).to eq 1 expect(reports[0]).to eq specified - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns http success with resolved filter' do @@ -31,7 +31,7 @@ describe Admin::ReportsController do expect(reports.size).to eq 1 expect(reports[0]).to eq specified - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -42,7 +42,7 @@ describe Admin::ReportsController do get :show, params: { id: report } expect(assigns(:report)).to eq report - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -52,7 +52,7 @@ describe Admin::ReportsController do report = Fabricate(:report) put :update, params: { id: report, outcome: 'unknown' } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end diff --git a/spec/controllers/admin/settings_controller_spec.rb b/spec/controllers/admin/settings_controller_spec.rb index 609bc762b..eaf99679a 100644 --- a/spec/controllers/admin/settings_controller_spec.rb +++ b/spec/controllers/admin/settings_controller_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Admin::SettingsController, type: :controller do it 'returns http success' do get :edit - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/admin/statuses_controller_spec.rb b/spec/controllers/admin/statuses_controller_spec.rb index 1515e299b..cbaf39786 100644 --- a/spec/controllers/admin/statuses_controller_spec.rb +++ b/spec/controllers/admin/statuses_controller_spec.rb @@ -20,7 +20,7 @@ describe Admin::StatusesController do statuses = assigns(:statuses).to_a expect(statuses.size).to eq 2 - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns http success with media' do @@ -28,7 +28,7 @@ describe Admin::StatusesController do statuses = assigns(:statuses).to_a expect(statuses.size).to eq 1 - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -99,7 +99,7 @@ describe Admin::StatusesController do allow(RemovalWorker).to receive(:perform_async) delete :destroy, params: { account_id: account.id, id: status } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(RemovalWorker). to have_received(:perform_async).with(status.id) end diff --git a/spec/controllers/admin/subscriptions_controller_spec.rb b/spec/controllers/admin/subscriptions_controller_spec.rb index eb6f12b16..967152abe 100644 --- a/spec/controllers/admin/subscriptions_controller_spec.rb +++ b/spec/controllers/admin/subscriptions_controller_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Admin::SubscriptionsController, type: :controller do expect(subscriptions.count).to eq 1 expect(subscriptions[0]).to eq specified - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/base_controller_spec.rb b/spec/controllers/api/base_controller_spec.rb index 0c7ca8990..750ccc8cf 100644 --- a/spec/controllers/api/base_controller_spec.rb +++ b/spec/controllers/api/base_controller_spec.rb @@ -23,7 +23,7 @@ describe Api::BaseController do it 'does not protect from forgery' do ActionController::Base.allow_forgery_protection = true post 'success' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/api/oembed_controller_spec.rb b/spec/controllers/api/oembed_controller_spec.rb index 7af4a6a5b..7fee15a35 100644 --- a/spec/controllers/api/oembed_controller_spec.rb +++ b/spec/controllers/api/oembed_controller_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Api::OEmbedController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/push_controller_spec.rb b/spec/controllers/api/push_controller_spec.rb index 647698bd1..d769d8554 100644 --- a/spec/controllers/api/push_controller_spec.rb +++ b/spec/controllers/api/push_controller_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Api::PushController, type: :controller do '3600', nil ) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(202) end end @@ -43,7 +43,7 @@ RSpec.describe Api::PushController, type: :controller do account, 'https://callback.host/api', ) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(202) end end diff --git a/spec/controllers/api/salmon_controller_spec.rb b/spec/controllers/api/salmon_controller_spec.rb index 8af8b83a8..5f01f8073 100644 --- a/spec/controllers/api/salmon_controller_spec.rb +++ b/spec/controllers/api/salmon_controller_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Api::SalmonController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(202) end it 'creates remote account' do diff --git a/spec/controllers/api/subscriptions_controller_spec.rb b/spec/controllers/api/subscriptions_controller_spec.rb index d90da9e32..48eb1fc64 100644 --- a/spec/controllers/api/subscriptions_controller_spec.rb +++ b/spec/controllers/api/subscriptions_controller_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Api::SubscriptionsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'echoes back the challenge' do @@ -27,7 +27,7 @@ RSpec.describe Api::SubscriptionsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end @@ -59,7 +59,7 @@ RSpec.describe Api::SubscriptionsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates statuses for feed' do diff --git a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb index efbef439a..08010bcc1 100644 --- a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb @@ -14,7 +14,7 @@ describe Api::V1::Accounts::CredentialsController do describe 'GET #show' do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -36,7 +36,7 @@ describe Api::V1::Accounts::CredentialsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates account info' do diff --git a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb index 33982cb8f..b47af4963 100644 --- a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb @@ -15,7 +15,7 @@ describe Api::V1::Accounts::FollowerAccountsController do it 'returns http success' do get :index, params: { account_id: user.account.id, limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb index e22f54a31..29fd7cd5b 100644 --- a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb @@ -15,7 +15,7 @@ describe Api::V1::Accounts::FollowingAccountsController do it 'returns http success' do get :index, params: { account_id: user.account.id, limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts/lists_controller_spec.rb b/spec/controllers/api/v1/accounts/lists_controller_spec.rb index 0a372f65b..df9fe0e34 100644 --- a/spec/controllers/api/v1/accounts/lists_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/lists_controller_spec.rb @@ -17,7 +17,7 @@ describe Api::V1::Accounts::ListsController do describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb index e0de790c8..7e350da7e 100644 --- a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb @@ -25,7 +25,7 @@ describe Api::V1::Accounts::RelationshipsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns JSON with correct data' do @@ -43,7 +43,7 @@ describe Api::V1::Accounts::RelationshipsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns JSON with correct data' do diff --git a/spec/controllers/api/v1/accounts/search_controller_spec.rb b/spec/controllers/api/v1/accounts/search_controller_spec.rb index 42cc3f64d..dbc4b9f3e 100644 --- a/spec/controllers/api/v1/accounts/search_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/search_controller_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Api::V1::Accounts::SearchController, type: :controller do it 'returns http success' do get :show, params: { q: 'query' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb index c49a77ac3..09bb46937 100644 --- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb @@ -15,7 +15,7 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end @@ -23,7 +23,7 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, only_media: true } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -35,7 +35,7 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, exclude_replies: true } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -47,7 +47,7 @@ describe Api::V1::Accounts::StatusesController do it 'returns http success' do get :index, params: { account_id: user.account.id, pinned: true } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb index 053c53e5a..7a9e0f8e4 100644 --- a/spec/controllers/api/v1/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts_controller_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do describe 'GET #show' do it 'returns http success' do get :show, params: { id: user.account.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -28,7 +28,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do let(:locked) { false } it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns JSON with following=true and requested=false' do @@ -47,7 +47,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do let(:locked) { true } it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns JSON with following=false and requested=true' do @@ -72,7 +72,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes the following relation between user and target user' do @@ -89,7 +89,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes the following relation between user and target user' do @@ -110,7 +110,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes the blocking relation between user and target user' do @@ -127,7 +127,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'does not remove the following relation between user and target user' do @@ -152,7 +152,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'does not remove the following relation between user and target user' do @@ -177,7 +177,7 @@ RSpec.describe Api::V1::AccountsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes the muting relation between user and target user' do diff --git a/spec/controllers/api/v1/apps/credentials_controller_spec.rb b/spec/controllers/api/v1/apps/credentials_controller_spec.rb index 38f2a4e10..0f811d5f3 100644 --- a/spec/controllers/api/v1/apps/credentials_controller_spec.rb +++ b/spec/controllers/api/v1/apps/credentials_controller_spec.rb @@ -16,7 +16,7 @@ describe Api::V1::Apps::CredentialsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'does not contain client credentials' do diff --git a/spec/controllers/api/v1/apps_controller_spec.rb b/spec/controllers/api/v1/apps_controller_spec.rb index 1ad9d6383..60a4c3b41 100644 --- a/spec/controllers/api/v1/apps_controller_spec.rb +++ b/spec/controllers/api/v1/apps_controller_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Api::V1::AppsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates an OAuth app' do diff --git a/spec/controllers/api/v1/blocks_controller_spec.rb b/spec/controllers/api/v1/blocks_controller_spec.rb index 9b2bbdf0e..eff5fb9da 100644 --- a/spec/controllers/api/v1/blocks_controller_spec.rb +++ b/spec/controllers/api/v1/blocks_controller_spec.rb @@ -47,7 +47,7 @@ RSpec.describe Api::V1::BlocksController, type: :controller do it 'returns http success' do get :index - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/custom_emojis_controller_spec.rb b/spec/controllers/api/v1/custom_emojis_controller_spec.rb index 9f3522812..fe8daa7c5 100644 --- a/spec/controllers/api/v1/custom_emojis_controller_spec.rb +++ b/spec/controllers/api/v1/custom_emojis_controller_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Api::V1::CustomEmojisController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/domain_blocks_controller_spec.rb b/spec/controllers/api/v1/domain_blocks_controller_spec.rb index 3713931dc..bae4612a2 100644 --- a/spec/controllers/api/v1/domain_blocks_controller_spec.rb +++ b/spec/controllers/api/v1/domain_blocks_controller_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Api::V1::DomainBlocksController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns blocked domains' do @@ -31,7 +31,7 @@ RSpec.describe Api::V1::DomainBlocksController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates a domain block' do @@ -45,7 +45,7 @@ RSpec.describe Api::V1::DomainBlocksController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'deletes a domain block' do diff --git a/spec/controllers/api/v1/follow_requests_controller_spec.rb b/spec/controllers/api/v1/follow_requests_controller_spec.rb index 51df006a2..3c0b84af8 100644 --- a/spec/controllers/api/v1/follow_requests_controller_spec.rb +++ b/spec/controllers/api/v1/follow_requests_controller_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Api::V1::FollowRequestsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -28,7 +28,7 @@ RSpec.describe Api::V1::FollowRequestsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'allows follower to follow' do @@ -42,7 +42,7 @@ RSpec.describe Api::V1::FollowRequestsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes follow request' do diff --git a/spec/controllers/api/v1/follows_controller_spec.rb b/spec/controllers/api/v1/follows_controller_spec.rb index ea9e76d68..38badb80a 100644 --- a/spec/controllers/api/v1/follows_controller_spec.rb +++ b/spec/controllers/api/v1/follows_controller_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Api::V1::FollowsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates account for remote user' do @@ -45,7 +45,7 @@ RSpec.describe Api::V1::FollowsController, type: :controller do it 'returns http success if already following, too' do post :create, params: { uri: 'gargron@quitter.no' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/instances_controller_spec.rb b/spec/controllers/api/v1/instances_controller_spec.rb index eba233b05..7397d25d6 100644 --- a/spec/controllers/api/v1/instances_controller_spec.rb +++ b/spec/controllers/api/v1/instances_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Api::V1::InstancesController, type: :controller do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/lists/accounts_controller_spec.rb b/spec/controllers/api/v1/lists/accounts_controller_spec.rb index 953e5909d..c37a481d6 100644 --- a/spec/controllers/api/v1/lists/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/lists/accounts_controller_spec.rb @@ -17,7 +17,7 @@ describe Api::V1::Lists::AccountsController do it 'returns http success' do get :show, params: { list_id: list.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -30,7 +30,7 @@ describe Api::V1::Lists::AccountsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'adds account to the list' do @@ -44,7 +44,7 @@ describe Api::V1::Lists::AccountsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes account from the list' do diff --git a/spec/controllers/api/v1/lists_controller_spec.rb b/spec/controllers/api/v1/lists_controller_spec.rb index be08c221f..213429581 100644 --- a/spec/controllers/api/v1/lists_controller_spec.rb +++ b/spec/controllers/api/v1/lists_controller_spec.rb @@ -12,14 +12,14 @@ RSpec.describe Api::V1::ListsController, type: :controller do describe 'GET #index' do it 'returns http success' do get :index - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end describe 'GET #show' do it 'returns http success' do get :show, params: { id: list.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -29,7 +29,7 @@ RSpec.describe Api::V1::ListsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates list' do @@ -44,7 +44,7 @@ RSpec.describe Api::V1::ListsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the list' do @@ -58,7 +58,7 @@ RSpec.describe Api::V1::ListsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'deletes the list' do diff --git a/spec/controllers/api/v1/media_controller_spec.rb b/spec/controllers/api/v1/media_controller_spec.rb index 0e494638f..ce260eb90 100644 --- a/spec/controllers/api/v1/media_controller_spec.rb +++ b/spec/controllers/api/v1/media_controller_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end it 'returns http 422' do - expect(response).to have_http_status(:error) + expect(response).to have_http_status(500) end end end @@ -41,7 +41,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates a media attachment' do @@ -63,7 +63,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates a media attachment' do @@ -85,7 +85,7 @@ RSpec.describe Api::V1::MediaController, type: :controller do end xit 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end xit 'creates a media attachment' do diff --git a/spec/controllers/api/v1/mutes_controller_spec.rb b/spec/controllers/api/v1/mutes_controller_spec.rb index 7387b9d2d..6804c9395 100644 --- a/spec/controllers/api/v1/mutes_controller_spec.rb +++ b/spec/controllers/api/v1/mutes_controller_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Api::V1::MutesController, type: :controller do it 'returns http success' do get :index, params: { limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/api/v1/notifications_controller_spec.rb b/spec/controllers/api/v1/notifications_controller_spec.rb index f493d0d38..2e6163fcd 100644 --- a/spec/controllers/api/v1/notifications_controller_spec.rb +++ b/spec/controllers/api/v1/notifications_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do notification = Fabricate(:notification, account: user.account) get :show, params: { id: notification.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -25,7 +25,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do notification = Fabricate(:notification, account: user.account) post :dismiss, params: { id: notification.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound) end end @@ -36,7 +36,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do post :clear expect(notification.account.reload.notifications).to be_empty - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -56,7 +56,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'includes reblog' do @@ -82,7 +82,7 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'includes reblog' do diff --git a/spec/controllers/api/v1/reports_controller_spec.rb b/spec/controllers/api/v1/reports_controller_spec.rb index 1eb5a4353..1e1ef9308 100644 --- a/spec/controllers/api/v1/reports_controller_spec.rb +++ b/spec/controllers/api/v1/reports_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Api::V1::ReportsController, type: :controller do it 'returns http success' do get :index - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -31,7 +31,7 @@ RSpec.describe Api::V1::ReportsController, type: :controller do it 'creates a report' do expect(status.reload.account.targeted_reports).not_to be_empty - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'sends e-mails to admins' do diff --git a/spec/controllers/api/v1/search_controller_spec.rb b/spec/controllers/api/v1/search_controller_spec.rb index ff0c254b1..024703867 100644 --- a/spec/controllers/api/v1/search_controller_spec.rb +++ b/spec/controllers/api/v1/search_controller_spec.rb @@ -16,7 +16,7 @@ RSpec.describe Api::V1::SearchController, type: :controller do it 'returns http success' do get :index, params: { q: 'test' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb index 556731d57..c873e05dd 100644 --- a/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control it 'returns http success' do get :index, params: { status_id: status.id, limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end @@ -43,7 +43,7 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control it 'returns http unautharized' do get :index, params: { status_id: status.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end @@ -58,7 +58,7 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control it 'returns http success' do get :index, params: { status_id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb index aba7cd458..53f602616 100644 --- a/spec/controllers/api/v1/statuses/favourites_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/favourites_controller_spec.rb @@ -22,7 +22,7 @@ describe Api::V1::Statuses::FavouritesController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the favourites count' do @@ -51,7 +51,7 @@ describe Api::V1::Statuses::FavouritesController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the favourites count' do diff --git a/spec/controllers/api/v1/statuses/mutes_controller_spec.rb b/spec/controllers/api/v1/statuses/mutes_controller_spec.rb index 54c594e92..13b4625d1 100644 --- a/spec/controllers/api/v1/statuses/mutes_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/mutes_controller_spec.rb @@ -22,7 +22,7 @@ describe Api::V1::Statuses::MutesController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'creates a conversation mute' do @@ -39,7 +39,7 @@ describe Api::V1::Statuses::MutesController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'destroys the conversation mute' do diff --git a/spec/controllers/api/v1/statuses/pins_controller_spec.rb b/spec/controllers/api/v1/statuses/pins_controller_spec.rb index 79005c9de..8f5b0800b 100644 --- a/spec/controllers/api/v1/statuses/pins_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/pins_controller_spec.rb @@ -22,7 +22,7 @@ describe Api::V1::Statuses::PinsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the pinned attribute' do @@ -46,7 +46,7 @@ describe Api::V1::Statuses::PinsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the pinned attribute' do diff --git a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb index ba022a96e..9c0c2b60c 100644 --- a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll it 'returns http success' do get :index, params: { status_id: status.id, limit: 1 } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end @@ -42,7 +42,7 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll it 'returns http unautharized' do get :index, params: { status_id: status.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end @@ -57,7 +57,7 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll it 'returns http success' do get :index, params: { status_id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb index 7417ff672..e60f8da2a 100644 --- a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb @@ -22,7 +22,7 @@ describe Api::V1::Statuses::ReblogsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the reblogs count' do @@ -51,7 +51,7 @@ describe Api::V1::Statuses::ReblogsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'updates the reblogs count' do diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb index a36265395..27e4f4eb2 100644 --- a/spec/controllers/api/v1/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/statuses_controller_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do it 'returns http success' do get :show, params: { id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -30,7 +30,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do it 'returns http success' do get :context, params: { id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -40,7 +40,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -52,7 +52,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'removes the status' do @@ -72,7 +72,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do describe 'GET #show' do it 'returns http unautharized' do get :show, params: { id: status.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end @@ -83,14 +83,14 @@ RSpec.describe Api::V1::StatusesController, type: :controller do it 'returns http unautharized' do get :context, params: { id: status.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end describe 'GET #card' do it 'returns http unautharized' do get :card, params: { id: status.id } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end @@ -101,7 +101,7 @@ RSpec.describe Api::V1::StatusesController, type: :controller do describe 'GET #show' do it 'returns http success' do get :show, params: { id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -112,14 +112,14 @@ RSpec.describe Api::V1::StatusesController, type: :controller do it 'returns http success' do get :context, params: { id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end describe 'GET #card' do it 'returns http success' do get :card, params: { id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/timelines/home_controller_spec.rb b/spec/controllers/api/v1/timelines/home_controller_spec.rb index 4d4523520..85b031641 100644 --- a/spec/controllers/api/v1/timelines/home_controller_spec.rb +++ b/spec/controllers/api/v1/timelines/home_controller_spec.rb @@ -23,7 +23,7 @@ describe Api::V1::Timelines::HomeController do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end diff --git a/spec/controllers/api/v1/timelines/list_controller_spec.rb b/spec/controllers/api/v1/timelines/list_controller_spec.rb index 07eba955a..1729217c9 100644 --- a/spec/controllers/api/v1/timelines/list_controller_spec.rb +++ b/spec/controllers/api/v1/timelines/list_controller_spec.rb @@ -24,7 +24,7 @@ describe Api::V1::Timelines::ListController do it 'returns http success' do get :show, params: { id: list.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/timelines/public_controller_spec.rb b/spec/controllers/api/v1/timelines/public_controller_spec.rb index 3acf2e267..68d87bbcb 100644 --- a/spec/controllers/api/v1/timelines/public_controller_spec.rb +++ b/spec/controllers/api/v1/timelines/public_controller_spec.rb @@ -22,7 +22,7 @@ describe Api::V1::Timelines::PublicController do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end @@ -35,7 +35,7 @@ describe Api::V1::Timelines::PublicController do it 'returns http success' do get :show, params: { local: true } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end @@ -48,7 +48,7 @@ describe Api::V1::Timelines::PublicController do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link']).to be_nil end end diff --git a/spec/controllers/api/v1/timelines/tag_controller_spec.rb b/spec/controllers/api/v1/timelines/tag_controller_spec.rb index 6c66ee58e..472779f54 100644 --- a/spec/controllers/api/v1/timelines/tag_controller_spec.rb +++ b/spec/controllers/api/v1/timelines/tag_controller_spec.rb @@ -21,7 +21,7 @@ describe Api::V1::Timelines::TagController do it 'returns http success' do get :show, params: { id: 'test' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end end @@ -33,7 +33,7 @@ describe Api::V1::Timelines::TagController do describe 'GET #show' do it 'returns http success' do get :show, params: { id: 'test' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.headers['Link']).to be_nil end end diff --git a/spec/controllers/api/web/settings_controller_spec.rb b/spec/controllers/api/web/settings_controller_spec.rb index ff211c7b1..815da04c4 100644 --- a/spec/controllers/api/web/settings_controller_spec.rb +++ b/spec/controllers/api/web/settings_controller_spec.rb @@ -13,7 +13,7 @@ describe Api::Web::SettingsController do patch :update, format: :json, params: { data: { 'onboarded' => true } } user.reload - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(user_web_setting.data['onboarded']).to eq('true') end diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 3e4d27e05..c6c78d3f7 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -51,7 +51,7 @@ describe ApplicationController, type: :controller do routes.draw { get 'success' => 'anonymous#success' } allow(Rails.env).to receive(:production?).and_return(false) get 'success' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it "forces ssl if Rails.env.production? is 'true'" do @@ -145,13 +145,13 @@ describe ApplicationController, type: :controller do it 'does nothing if not signed in' do get 'success' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'does nothing if user who signed in is not suspended' do sign_in(Fabricate(:user, account: Fabricate(:account, suspended: false))) get 'success' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'returns http 403 if user who signed in is suspended' do diff --git a/spec/controllers/auth/confirmations_controller_spec.rb b/spec/controllers/auth/confirmations_controller_spec.rb index 80a06c43a..b3af5e0ec 100644 --- a/spec/controllers/auth/confirmations_controller_spec.rb +++ b/spec/controllers/auth/confirmations_controller_spec.rb @@ -7,7 +7,7 @@ describe Auth::ConfirmationsController, type: :controller do it 'returns http success' do @request.env['devise.mapping'] = Devise.mappings[:user] get :new - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/auth/passwords_controller_spec.rb b/spec/controllers/auth/passwords_controller_spec.rb index 992d2e29d..dcfdebb17 100644 --- a/spec/controllers/auth/passwords_controller_spec.rb +++ b/spec/controllers/auth/passwords_controller_spec.rb @@ -9,7 +9,7 @@ describe Auth::PasswordsController, type: :controller do it 'returns http success' do @request.env['devise.mapping'] = Devise.mappings[:user] get :new - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -24,7 +24,7 @@ describe Auth::PasswordsController, type: :controller do context 'with valid reset_password_token' do it 'returns http success' do get :edit, params: { reset_password_token: @token } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb index 97d2c53df..eeb01d5ad 100644 --- a/spec/controllers/auth/registrations_controller_spec.rb +++ b/spec/controllers/auth/registrations_controller_spec.rb @@ -35,7 +35,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do request.env["devise.mapping"] = Devise.mappings[:user] sign_in(Fabricate(:user)) get :edit - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -44,7 +44,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do request.env["devise.mapping"] = Devise.mappings[:user] sign_in(Fabricate(:user), scope: :user) post :update - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -63,7 +63,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do it 'returns http success' do Setting.open_registrations = true get :new - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -73,6 +73,12 @@ RSpec.describe Auth::RegistrationsController, type: :controller do describe 'POST #create' do let(:accept_language) { Rails.application.config.i18n.available_locales.sample.to_s } + around do |example| + current_locale = I18n.locale + example.run + I18n.locale = current_locale + end + before { request.env["devise.mapping"] = Devise.mappings[:user] } context do diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb index d5fed17d6..97719a606 100644 --- a/spec/controllers/auth/sessions_controller_spec.rb +++ b/spec/controllers/auth/sessions_controller_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Auth::SessionsController, type: :controller do it 'returns http success' do get :new - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/authorize_follows_controller_spec.rb b/spec/controllers/authorize_follows_controller_spec.rb index b1cbef7ea..52971c724 100644 --- a/spec/controllers/authorize_follows_controller_spec.rb +++ b/spec/controllers/authorize_follows_controller_spec.rb @@ -47,7 +47,7 @@ describe AuthorizeFollowsController do get :show, params: { acct: 'http://example.com' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(assigns(:account)).to eq account end @@ -59,7 +59,7 @@ describe AuthorizeFollowsController do get :show, params: { acct: 'acct:found@hostname' } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(assigns(:account)).to eq account end end diff --git a/spec/controllers/concerns/account_controller_concern_spec.rb b/spec/controllers/concerns/account_controller_concern_spec.rb index ae46f9ba6..93685103f 100644 --- a/spec/controllers/concerns/account_controller_concern_spec.rb +++ b/spec/controllers/concerns/account_controller_concern_spec.rb @@ -39,7 +39,7 @@ describe ApplicationController, type: :controller do it 'returns http success' do account = Fabricate(:account) get 'success', params: { account_username: account.username } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/concerns/export_controller_concern_spec.rb b/spec/controllers/concerns/export_controller_concern_spec.rb index 9d6f782b9..6a13db69d 100644 --- a/spec/controllers/concerns/export_controller_concern_spec.rb +++ b/spec/controllers/concerns/export_controller_concern_spec.rb @@ -19,7 +19,7 @@ describe ApplicationController, type: :controller do sign_in user get :index, format: :csv - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'text/csv' expect(response.headers['Content-Disposition']).to eq 'attachment; filename="anonymous.csv"' expect(response.body).to eq user.account.username diff --git a/spec/controllers/concerns/localized_spec.rb b/spec/controllers/concerns/localized_spec.rb index f71c96aff..8c80b7d2a 100644 --- a/spec/controllers/concerns/localized_spec.rb +++ b/spec/controllers/concerns/localized_spec.rb @@ -11,13 +11,17 @@ describe ApplicationController, type: :controller do end end + around do |example| + current_locale = I18n.locale + example.run + I18n.locale = current_locale + end + before do routes.draw { get 'success' => 'anonymous#success' } end shared_examples 'default locale' do - after { I18n.locale = I18n.default_locale } - it 'sets available and preferred language' do request.headers['Accept-Language'] = 'ca-ES, fa' get 'success' diff --git a/spec/controllers/follower_accounts_controller_spec.rb b/spec/controllers/follower_accounts_controller_spec.rb index b9b7fef73..3a42a6e18 100644 --- a/spec/controllers/follower_accounts_controller_spec.rb +++ b/spec/controllers/follower_accounts_controller_spec.rb @@ -19,7 +19,7 @@ describe FollowerAccountsController do expect(assigned[0]).to eq follow1 expect(assigned[1]).to eq follow0 - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/following_accounts_controller_spec.rb b/spec/controllers/following_accounts_controller_spec.rb index 55e7265c7..33376365d 100644 --- a/spec/controllers/following_accounts_controller_spec.rb +++ b/spec/controllers/following_accounts_controller_spec.rb @@ -19,7 +19,7 @@ describe FollowingAccountsController do expect(assigned[0]).to eq follow1 expect(assigned[1]).to eq follow0 - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/manifests_controller_spec.rb b/spec/controllers/manifests_controller_spec.rb index 71967e4f0..a549adef3 100644 --- a/spec/controllers/manifests_controller_spec.rb +++ b/spec/controllers/manifests_controller_spec.rb @@ -9,7 +9,7 @@ describe ManifestsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/media_controller_spec.rb b/spec/controllers/media_controller_spec.rb index 5b03899e4..ac44a76f2 100644 --- a/spec/controllers/media_controller_spec.rb +++ b/spec/controllers/media_controller_spec.rb @@ -18,13 +18,13 @@ describe MediaController do media_attachment = Fabricate(:media_attachment, status: nil) get :show, params: { id: media_attachment.to_param } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end it 'raises when shortcode cant be found' do get :show, params: { id: 'missing' } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end it 'raises when not permitted to view' do @@ -33,7 +33,7 @@ describe MediaController do allow_any_instance_of(MediaController).to receive(:authorize).and_raise(ActiveRecord::RecordNotFound) get :show, params: { id: media_attachment.to_param } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/oauth/authorizations_controller_spec.rb b/spec/controllers/oauth/authorizations_controller_spec.rb index 5c2a62b48..91c2d03ef 100644 --- a/spec/controllers/oauth/authorizations_controller_spec.rb +++ b/spec/controllers/oauth/authorizations_controller_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Oauth::AuthorizationsController, type: :controller do it 'returns http success' do subject - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'gives options to authorize and deny' do diff --git a/spec/controllers/oauth/authorized_applications_controller_spec.rb b/spec/controllers/oauth/authorized_applications_controller_spec.rb index 2a2b92283..f967b507f 100644 --- a/spec/controllers/oauth/authorized_applications_controller_spec.rb +++ b/spec/controllers/oauth/authorized_applications_controller_spec.rb @@ -24,7 +24,7 @@ describe Oauth::AuthorizedApplicationsController do it 'returns http success' do subject - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end include_examples 'stores location for user' diff --git a/spec/controllers/remote_follow_controller_spec.rb b/spec/controllers/remote_follow_controller_spec.rb index 86b1eb8d0..5088c2e65 100644 --- a/spec/controllers/remote_follow_controller_spec.rb +++ b/spec/controllers/remote_follow_controller_spec.rb @@ -10,7 +10,7 @@ describe RemoteFollowController do account = Fabricate(:account) get :new, params: { account_username: account.to_param } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response).to render_template(:new) expect(assigns(:remote_follow).acct).to be_nil end @@ -20,7 +20,7 @@ describe RemoteFollowController do account = Fabricate(:account) get :new, params: { account_username: account.to_param } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response).to render_template(:new) expect(assigns(:remote_follow).acct).to eq 'user@example.com' end diff --git a/spec/controllers/settings/applications_controller_spec.rb b/spec/controllers/settings/applications_controller_spec.rb index 90e6a63d5..f87107695 100644 --- a/spec/controllers/settings/applications_controller_spec.rb +++ b/spec/controllers/settings/applications_controller_spec.rb @@ -15,7 +15,7 @@ describe Settings::ApplicationsController do it 'shows apps' do get :index - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(assigns(:applications)).to include(app) expect(assigns(:applications)).to_not include(other_app) end @@ -25,7 +25,7 @@ describe Settings::ApplicationsController do describe 'GET #show' do it 'returns http success' do get :show, params: { id: app.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(assigns[:application]).to eql(app) end @@ -40,7 +40,7 @@ describe Settings::ApplicationsController do describe 'GET #new' do it 'works' do get :new - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -102,7 +102,7 @@ describe Settings::ApplicationsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'renders form again' do @@ -151,7 +151,7 @@ describe Settings::ApplicationsController do end it 'returns http success' do - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'renders form again' do diff --git a/spec/controllers/settings/deletes_controller_spec.rb b/spec/controllers/settings/deletes_controller_spec.rb index 9b55090df..35fd64e9b 100644 --- a/spec/controllers/settings/deletes_controller_spec.rb +++ b/spec/controllers/settings/deletes_controller_spec.rb @@ -13,7 +13,7 @@ describe Settings::DeletesController do it 'renders confirmation page' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/exports_controller_spec.rb b/spec/controllers/settings/exports_controller_spec.rb index 19cb0abda..b7cab4d8f 100644 --- a/spec/controllers/settings/exports_controller_spec.rb +++ b/spec/controllers/settings/exports_controller_spec.rb @@ -17,7 +17,7 @@ describe Settings::ExportsController do export = assigns(:export) expect(export).to be_instance_of Export expect(export.account).to eq user.account - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/follower_domains_controller_spec.rb b/spec/controllers/settings/follower_domains_controller_spec.rb index 333223c61..6d415a654 100644 --- a/spec/controllers/settings/follower_domains_controller_spec.rb +++ b/spec/controllers/settings/follower_domains_controller_spec.rb @@ -36,7 +36,7 @@ describe Settings::FollowerDomainsController do it 'returns http success' do sign_in user, scope: :user subject - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end include_examples 'authenticate user' diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb index 59b10e0da..7a9b02195 100644 --- a/spec/controllers/settings/imports_controller_spec.rb +++ b/spec/controllers/settings/imports_controller_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Settings::ImportsController, type: :controller do describe "GET #show" do it "returns http success" do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/notifications_controller_spec.rb b/spec/controllers/settings/notifications_controller_spec.rb index 0bd993448..981ef674e 100644 --- a/spec/controllers/settings/notifications_controller_spec.rb +++ b/spec/controllers/settings/notifications_controller_spec.rb @@ -12,7 +12,7 @@ describe Settings::NotificationsController do describe 'GET #show' do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/preferences_controller_spec.rb b/spec/controllers/settings/preferences_controller_spec.rb index 0f9431673..7877c7362 100644 --- a/spec/controllers/settings/preferences_controller_spec.rb +++ b/spec/controllers/settings/preferences_controller_spec.rb @@ -12,7 +12,7 @@ describe Settings::PreferencesController do describe 'GET #show' do it 'returns http success' do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/profiles_controller_spec.rb b/spec/controllers/settings/profiles_controller_spec.rb index ee3315be6..a453200af 100644 --- a/spec/controllers/settings/profiles_controller_spec.rb +++ b/spec/controllers/settings/profiles_controller_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Settings::ProfilesController, type: :controller do describe "GET #show" do it "returns http success" do get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb index aee82a3d8..7612bf90e 100644 --- a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb @@ -15,7 +15,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do expect(assigns(:confirmation)).to be_instance_of Form::TwoFactorConfirmation expect(assigns(:provision_url)).to eq 'otpauth://totp/local-part@domain?secret=thisisasecretforthespecofnewview&issuer=cb6e6126.ngrok.io' expect(assigns(:qrcode)).to be_instance_of RQRCode::QRCode - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response).to render_template(:new) end end @@ -71,7 +71,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do expect(assigns(:recovery_codes)).to eq otp_backup_codes expect(flash[:notice]).to eq 'Two-factor authentication successfully enabled' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response).to render_template('settings/two_factor_authentication/recovery_codes/index') end end diff --git a/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb index aa28cdf3f..c04760e53 100644 --- a/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb @@ -19,7 +19,7 @@ describe Settings::TwoFactorAuthentication::RecoveryCodesController do expect(assigns(:recovery_codes)).to eq otp_backup_codes expect(flash[:notice]).to eq 'Recovery codes successfully regenerated' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response).to render_template(:index) end diff --git a/spec/controllers/settings/two_factor_authentications_controller_spec.rb b/spec/controllers/settings/two_factor_authentications_controller_spec.rb index 6c49f6f0d..9f27222ad 100644 --- a/spec/controllers/settings/two_factor_authentications_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentications_controller_spec.rb @@ -18,7 +18,7 @@ describe Settings::TwoFactorAuthenticationsController do user.update(otp_required_for_login: true) get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end @@ -27,7 +27,7 @@ describe Settings::TwoFactorAuthenticationsController do user.update(otp_required_for_login: false) get :show - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb index 95fb4d594..b4f3c5a08 100644 --- a/spec/controllers/statuses_controller_spec.rb +++ b/spec/controllers/statuses_controller_spec.rb @@ -82,10 +82,53 @@ describe StatusesController do expect(assigns(:ancestors)).to eq [] end + it 'assigns @descendant_threads for a thread with several statuses' do + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + grandchild = Fabricate(:status, in_reply_to_id: child.id) + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchild.id] + end + + it 'assigns @descendant_threads for several threads sharing the same descendant' do + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + grandchildren = 2.times.map { Fabricate(:status, in_reply_to_id: child.id) } + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchildren[0].id] + expect(assigns(:descendant_threads)[1][:statuses].pluck(:id)).to eq [grandchildren[1].id] + end + + it 'assigns @max_descendant_thread_id for the last thread if it is hitting the status limit' do + stub_const 'StatusesController::DESCENDANTS_LIMIT', 1 + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)).to eq [] + expect(assigns(:max_descendant_thread_id)).to eq child.id + end + + it 'assigns @descendant_threads for threads with :next_status key if they are hitting the depth limit' do + stub_const 'StatusesController::DESCENDANTS_DEPTH_LIMIT', 1 + status = Fabricate(:status) + child = Fabricate(:status, in_reply_to_id: status.id) + + get :show, params: { account_username: status.account.username, id: status.id } + + expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).not_to include child.id + expect(assigns(:descendant_threads)[0][:next_status].id).to eq child.id + end + it 'returns a success' do status = Fabricate(:status) get :show, params: { account_username: status.account.username, id: status.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'renders stream_entries/show' do diff --git a/spec/controllers/stream_entries_controller_spec.rb b/spec/controllers/stream_entries_controller_spec.rb index 665c5b747..534bc393d 100644 --- a/spec/controllers/stream_entries_controller_spec.rb +++ b/spec/controllers/stream_entries_controller_spec.rb @@ -77,7 +77,7 @@ RSpec.describe StreamEntriesController, type: :controller do it 'returns http success with Atom' do status = Fabricate(:status) get :show, params: { account_username: status.account.username, id: status.stream_entry.id }, format: 'atom' - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/tags_controller_spec.rb b/spec/controllers/tags_controller_spec.rb index b04666c0f..33ccaed61 100644 --- a/spec/controllers/tags_controller_spec.rb +++ b/spec/controllers/tags_controller_spec.rb @@ -12,7 +12,7 @@ RSpec.describe TagsController, type: :controller do context 'when tag exists' do it 'returns http success' do get :show, params: { id: 'test', max_id: late.id } - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) end it 'renders application layout' do @@ -25,7 +25,7 @@ RSpec.describe TagsController, type: :controller do it 'returns http missing for non-existent tag' do get :show, params: { id: 'none' } - expect(response).to have_http_status(:missing) + expect(response).to have_http_status(404) end end end diff --git a/spec/controllers/well_known/host_meta_controller_spec.rb b/spec/controllers/well_known/host_meta_controller_spec.rb index 87c1485ed..b43ae19d8 100644 --- a/spec/controllers/well_known/host_meta_controller_spec.rb +++ b/spec/controllers/well_known/host_meta_controller_spec.rb @@ -7,10 +7,10 @@ describe WellKnown::HostMetaController, type: :controller do it 'returns http success' do get :show, format: :xml - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/xrd+xml' expect(response.body).to eq <<XML -<?xml version="1.0"?> +<?xml version="1.0" encoding="UTF-8"?> <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"> <Link rel="lrdd" type="application/xrd+xml" template="https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}"/> </XRD> diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb index 466f87c45..b05745ea3 100644 --- a/spec/controllers/well_known/webfinger_controller_spec.rb +++ b/spec/controllers/well_known/webfinger_controller_spec.rb @@ -50,7 +50,7 @@ PEM json = body_as_json - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/jrd+json' expect(json[:subject]).to eq 'acct:alice@cb6e6126.ngrok.io' expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice') @@ -61,7 +61,7 @@ PEM xml = Nokogiri::XML(response.body) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/xrd+xml' expect(xml.at_xpath('//xmlns:Subject').content).to eq 'acct:alice@cb6e6126.ngrok.io' expect(xml.xpath('//xmlns:Alias').map(&:content)).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice') @@ -81,7 +81,7 @@ PEM json = body_as_json - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/jrd+json' expect(json[:subject]).to eq 'acct:alice@cb6e6126.ngrok.io' expect(json[:aliases]).to include('https://cb6e6126.ngrok.io/@alice', 'https://cb6e6126.ngrok.io/users/alice') diff --git a/spec/fabricators/account_fabricator.rb b/spec/fabricators/account_fabricator.rb index 446f8ea27..7aa983f82 100644 --- a/spec/fabricators/account_fabricator.rb +++ b/spec/fabricators/account_fabricator.rb @@ -1,4 +1,10 @@ +keypair = OpenSSL::PKey::RSA.new(2048) +public_key = keypair.public_key.to_pem +private_key = keypair.to_pem + Fabricator(:account) do - username { sequence(:username) { |i| "#{Faker::Internet.user_name(nil, %w(_))}#{i}" } } + username { sequence(:username) { |i| "#{Faker::Internet.user_name(nil, %w(_))}#{i}" } } last_webfingered_at { Time.now.utc } + public_key { public_key } + private_key { private_key} end diff --git a/spec/fixtures/requests/activitypub-actor-individual.txt b/spec/fixtures/requests/activitypub-actor-individual.txt new file mode 100644 index 000000000..74411e544 --- /dev/null +++ b/spec/fixtures/requests/activitypub-actor-individual.txt @@ -0,0 +1,9 @@ +HTTP/1.1 200 OK +Cache-Control: max-age=0, private, must-revalidate +Content-Type: application/activity+json; charset=utf-8 +Link: <https://ap.example.com/.well-known/webfinger?resource=acct%3Afoo%40ap.example.com>; rel="lrdd"; type="application/xrd+xml", <https://ap.example.com/users/foo.atom>; rel="alternate"; type="application/atom+xml", <https://ap.example.com/users/foo>; rel="alternate"; type="application/activity+json" +Vary: Accept-Encoding +X-Content-Type-Options: nosniff +X-Xss-Protection: 1; mode=block + +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"vcard": "http://www.w3.org/2006/vcard/ns#"},{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation"}],"id":"https://ap.example.com/users/foo","type":["Person","vcard:individual"],"following":"https://ap.example.com/users/foo/following","followers":"https://ap.example.com/users/foo/followers","inbox":"https://ap.example.com/users/foo/inbox","outbox":"https://ap.example.com/users/foo/outbox","preferredUsername":"foo","vcard:fn":"foo","name":"","summary":"\u003cp\u003etest\u003c/p\u003e","url":"https://ap.example.com/@foo","manuallyApprovesFollowers":false,"publicKey":{"id":"https://ap.example.com/users/foo#main-key","owner":"https://ap.example.com/users/foo","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3L4vnpNLzVH31MeWI39\n4F0wKeJFsLDAsNXGeOu0QF2x+h1zLWZw/agqD2R3JPU9/kaDJGPIV2Sn5zLyUA9S\n6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh\n8lDET6X4Pyw+ZJU0/OLo/41q9w+OrGtlsTm/PuPIeXnxa6BLqnDaxC+4IcjG/FiP\nahNCTINl/1F/TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq+t8nhQYkgAkt64euW\npva3qL5KD1mTIZQEP+LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3Qvu\nHQIDAQAB\n-----END PUBLIC KEY-----\n"},"endpoints":{"sharedInbox":"https://ap.example.com/inbox"},"icon":{"type":"Image","url":"https://quitter.no/avatar/7477-300-20160211190340.png"}} \ No newline at end of file diff --git a/spec/fixtures/requests/json-ld.activitystreams.txt b/spec/fixtures/requests/json-ld.activitystreams.txt new file mode 100644 index 000000000..395797b27 --- /dev/null +++ b/spec/fixtures/requests/json-ld.activitystreams.txt @@ -0,0 +1,391 @@ +HTTP/1.1 200 OK +Date: Tue, 01 May 2018 23:25:57 GMT +Content-Location: activitystreams.jsonld +Vary: negotiate,accept +TCN: choice +Last-Modified: Mon, 16 Apr 2018 00:28:23 GMT +ETag: "1eb0-569ec4caa97c0;d3-540ee27e0eec0" +Accept-Ranges: bytes +Content-Length: 7856 +Cache-Control: max-age=21600 +Expires: Wed, 02 May 2018 05:25:57 GMT +P3P: policyref="http://www.w3.org/2014/08/p3p.xml" +Access-Control-Allow-Origin: * +Content-Type: application/ld+json +Strict-Transport-Security: max-age=15552000; includeSubdomains; preload +Content-Security-Policy: upgrade-insecure-requests + +{ + "@context": { + "@vocab": "_:", + "xsd": "http://www.w3.org/2001/XMLSchema#", + "as": "https://www.w3.org/ns/activitystreams#", + "ldp": "http://www.w3.org/ns/ldp#", + "id": "@id", + "type": "@type", + "Accept": "as:Accept", + "Activity": "as:Activity", + "IntransitiveActivity": "as:IntransitiveActivity", + "Add": "as:Add", + "Announce": "as:Announce", + "Application": "as:Application", + "Arrive": "as:Arrive", + "Article": "as:Article", + "Audio": "as:Audio", + "Block": "as:Block", + "Collection": "as:Collection", + "CollectionPage": "as:CollectionPage", + "Relationship": "as:Relationship", + "Create": "as:Create", + "Delete": "as:Delete", + "Dislike": "as:Dislike", + "Document": "as:Document", + "Event": "as:Event", + "Follow": "as:Follow", + "Flag": "as:Flag", + "Group": "as:Group", + "Ignore": "as:Ignore", + "Image": "as:Image", + "Invite": "as:Invite", + "Join": "as:Join", + "Leave": "as:Leave", + "Like": "as:Like", + "Link": "as:Link", + "Mention": "as:Mention", + "Note": "as:Note", + "Object": "as:Object", + "Offer": "as:Offer", + "OrderedCollection": "as:OrderedCollection", + "OrderedCollectionPage": "as:OrderedCollectionPage", + "Organization": "as:Organization", + "Page": "as:Page", + "Person": "as:Person", + "Place": "as:Place", + "Profile": "as:Profile", + "Question": "as:Question", + "Reject": "as:Reject", + "Remove": "as:Remove", + "Service": "as:Service", + "TentativeAccept": "as:TentativeAccept", + "TentativeReject": "as:TentativeReject", + "Tombstone": "as:Tombstone", + "Undo": "as:Undo", + "Update": "as:Update", + "Video": "as:Video", + "View": "as:View", + "Listen": "as:Listen", + "Read": "as:Read", + "Move": "as:Move", + "Travel": "as:Travel", + "IsFollowing": "as:IsFollowing", + "IsFollowedBy": "as:IsFollowedBy", + "IsContact": "as:IsContact", + "IsMember": "as:IsMember", + "subject": { + "@id": "as:subject", + "@type": "@id" + }, + "relationship": { + "@id": "as:relationship", + "@type": "@id" + }, + "actor": { + "@id": "as:actor", + "@type": "@id" + }, + "attributedTo": { + "@id": "as:attributedTo", + "@type": "@id" + }, + "attachment": { + "@id": "as:attachment", + "@type": "@id" + }, + "bcc": { + "@id": "as:bcc", + "@type": "@id" + }, + "bto": { + "@id": "as:bto", + "@type": "@id" + }, + "cc": { + "@id": "as:cc", + "@type": "@id" + }, + "context": { + "@id": "as:context", + "@type": "@id" + }, + "current": { + "@id": "as:current", + "@type": "@id" + }, + "first": { + "@id": "as:first", + "@type": "@id" + }, + "generator": { + "@id": "as:generator", + "@type": "@id" + }, + "icon": { + "@id": "as:icon", + "@type": "@id" + }, + "image": { + "@id": "as:image", + "@type": "@id" + }, + "inReplyTo": { + "@id": "as:inReplyTo", + "@type": "@id" + }, + "items": { + "@id": "as:items", + "@type": "@id" + }, + "instrument": { + "@id": "as:instrument", + "@type": "@id" + }, + "orderedItems": { + "@id": "as:items", + "@type": "@id", + "@container": "@list" + }, + "last": { + "@id": "as:last", + "@type": "@id" + }, + "location": { + "@id": "as:location", + "@type": "@id" + }, + "next": { + "@id": "as:next", + "@type": "@id" + }, + "object": { + "@id": "as:object", + "@type": "@id" + }, + "oneOf": { + "@id": "as:oneOf", + "@type": "@id" + }, + "anyOf": { + "@id": "as:anyOf", + "@type": "@id" + }, + "closed": { + "@id": "as:closed", + "@type": "xsd:dateTime" + }, + "origin": { + "@id": "as:origin", + "@type": "@id" + }, + "accuracy": { + "@id": "as:accuracy", + "@type": "xsd:float" + }, + "prev": { + "@id": "as:prev", + "@type": "@id" + }, + "preview": { + "@id": "as:preview", + "@type": "@id" + }, + "replies": { + "@id": "as:replies", + "@type": "@id" + }, + "result": { + "@id": "as:result", + "@type": "@id" + }, + "audience": { + "@id": "as:audience", + "@type": "@id" + }, + "partOf": { + "@id": "as:partOf", + "@type": "@id" + }, + "tag": { + "@id": "as:tag", + "@type": "@id" + }, + "target": { + "@id": "as:target", + "@type": "@id" + }, + "to": { + "@id": "as:to", + "@type": "@id" + }, + "url": { + "@id": "as:url", + "@type": "@id" + }, + "altitude": { + "@id": "as:altitude", + "@type": "xsd:float" + }, + "content": "as:content", + "contentMap": { + "@id": "as:content", + "@container": "@language" + }, + "name": "as:name", + "nameMap": { + "@id": "as:name", + "@container": "@language" + }, + "duration": { + "@id": "as:duration", + "@type": "xsd:duration" + }, + "endTime": { + "@id": "as:endTime", + "@type": "xsd:dateTime" + }, + "height": { + "@id": "as:height", + "@type": "xsd:nonNegativeInteger" + }, + "href": { + "@id": "as:href", + "@type": "@id" + }, + "hreflang": "as:hreflang", + "latitude": { + "@id": "as:latitude", + "@type": "xsd:float" + }, + "longitude": { + "@id": "as:longitude", + "@type": "xsd:float" + }, + "mediaType": "as:mediaType", + "published": { + "@id": "as:published", + "@type": "xsd:dateTime" + }, + "radius": { + "@id": "as:radius", + "@type": "xsd:float" + }, + "rel": "as:rel", + "startIndex": { + "@id": "as:startIndex", + "@type": "xsd:nonNegativeInteger" + }, + "startTime": { + "@id": "as:startTime", + "@type": "xsd:dateTime" + }, + "summary": "as:summary", + "summaryMap": { + "@id": "as:summary", + "@container": "@language" + }, + "totalItems": { + "@id": "as:totalItems", + "@type": "xsd:nonNegativeInteger" + }, + "units": "as:units", + "updated": { + "@id": "as:updated", + "@type": "xsd:dateTime" + }, + "width": { + "@id": "as:width", + "@type": "xsd:nonNegativeInteger" + }, + "describes": { + "@id": "as:describes", + "@type": "@id" + }, + "formerType": { + "@id": "as:formerType", + "@type": "@id" + }, + "deleted": { + "@id": "as:deleted", + "@type": "xsd:dateTime" + }, + "inbox": { + "@id": "ldp:inbox", + "@type": "@id" + }, + "outbox": { + "@id": "as:outbox", + "@type": "@id" + }, + "following": { + "@id": "as:following", + "@type": "@id" + }, + "followers": { + "@id": "as:followers", + "@type": "@id" + }, + "streams": { + "@id": "as:streams", + "@type": "@id" + }, + "preferredUsername": "as:preferredUsername", + "endpoints": { + "@id": "as:endpoints", + "@type": "@id" + }, + "uploadMedia": { + "@id": "as:uploadMedia", + "@type": "@id" + }, + "proxyUrl": { + "@id": "as:proxyUrl", + "@type": "@id" + }, + "liked": { + "@id": "as:liked", + "@type": "@id" + }, + "oauthAuthorizationEndpoint": { + "@id": "as:oauthAuthorizationEndpoint", + "@type": "@id" + }, + "oauthTokenEndpoint": { + "@id": "as:oauthTokenEndpoint", + "@type": "@id" + }, + "provideClientKey": { + "@id": "as:provideClientKey", + "@type": "@id" + }, + "signClientKey": { + "@id": "as:signClientKey", + "@type": "@id" + }, + "sharedInbox": { + "@id": "as:sharedInbox", + "@type": "@id" + }, + "Public": { + "@id": "as:Public", + "@type": "@id" + }, + "source": "as:source", + "likes": { + "@id": "as:likes", + "@type": "@id" + }, + "shares": { + "@id": "as:shares", + "@type": "@id" + } + } +} diff --git a/spec/fixtures/requests/json-ld.identity.txt b/spec/fixtures/requests/json-ld.identity.txt new file mode 100644 index 000000000..8810526cb --- /dev/null +++ b/spec/fixtures/requests/json-ld.identity.txt @@ -0,0 +1,100 @@ +HTTP/1.1 200 OK +Accept-Ranges: bytes +Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Accept-Encoding +Access-Control-Allow-Origin: * +Content-Type: application/ld+json +Date: Tue, 01 May 2018 23:28:21 GMT +Etag: "e26-547a6fc75b04a-gzip" +Last-Modified: Fri, 03 Feb 2017 21:30:09 GMT +Server: Apache/2.4.7 (Ubuntu) +Vary: Accept-Encoding +Transfer-Encoding: chunked + +{ + "@context": { + "id": "@id", + "type": "@type", + + "cred": "https://w3id.org/credentials#", + "dc": "http://purl.org/dc/terms/", + "identity": "https://w3id.org/identity#", + "perm": "https://w3id.org/permissions#", + "ps": "https://w3id.org/payswarm#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "sec": "https://w3id.org/security#", + "schema": "http://schema.org/", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "Group": "https://www.w3.org/ns/activitystreams#Group", + + "claim": {"@id": "cred:claim", "@type": "@id"}, + "credential": {"@id": "cred:credential", "@type": "@id"}, + "issued": {"@id": "cred:issued", "@type": "xsd:dateTime"}, + "issuer": {"@id": "cred:issuer", "@type": "@id"}, + "recipient": {"@id": "cred:recipient", "@type": "@id"}, + "Credential": "cred:Credential", + "CryptographicKeyCredential": "cred:CryptographicKeyCredential", + + "about": {"@id": "schema:about", "@type": "@id"}, + "address": {"@id": "schema:address", "@type": "@id"}, + "addressCountry": "schema:addressCountry", + "addressLocality": "schema:addressLocality", + "addressRegion": "schema:addressRegion", + "comment": "rdfs:comment", + "created": {"@id": "dc:created", "@type": "xsd:dateTime"}, + "creator": {"@id": "dc:creator", "@type": "@id"}, + "description": "schema:description", + "email": "schema:email", + "familyName": "schema:familyName", + "givenName": "schema:givenName", + "image": {"@id": "schema:image", "@type": "@id"}, + "label": "rdfs:label", + "name": "schema:name", + "postalCode": "schema:postalCode", + "streetAddress": "schema:streetAddress", + "title": "dc:title", + "url": {"@id": "schema:url", "@type": "@id"}, + "Person": "schema:Person", + "PostalAddress": "schema:PostalAddress", + "Organization": "schema:Organization", + + "identityService": {"@id": "identity:identityService", "@type": "@id"}, + "idp": {"@id": "identity:idp", "@type": "@id"}, + "Identity": "identity:Identity", + + "paymentProcessor": "ps:processor", + "preferences": {"@id": "ps:preferences", "@type": "@vocab"}, + + "cipherAlgorithm": "sec:cipherAlgorithm", + "cipherData": "sec:cipherData", + "cipherKey": "sec:cipherKey", + "digestAlgorithm": "sec:digestAlgorithm", + "digestValue": "sec:digestValue", + "domain": "sec:domain", + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "initializationVector": "sec:initializationVector", + "member": {"@id": "schema:member", "@type": "@id"}, + "memberOf": {"@id": "schema:memberOf", "@type": "@id"}, + "nonce": "sec:nonce", + "normalizationAlgorithm": "sec:normalizationAlgorithm", + "owner": {"@id": "sec:owner", "@type": "@id"}, + "password": "sec:password", + "privateKey": {"@id": "sec:privateKey", "@type": "@id"}, + "privateKeyPem": "sec:privateKeyPem", + "publicKey": {"@id": "sec:publicKey", "@type": "@id"}, + "publicKeyPem": "sec:publicKeyPem", + "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"}, + "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"}, + "signature": "sec:signature", + "signatureAlgorithm": "sec:signatureAlgorithm", + "signatureValue": "sec:signatureValue", + "CryptographicKey": "sec:Key", + "EncryptedMessage": "sec:EncryptedMessage", + "GraphSignature2012": "sec:GraphSignature2012", + "LinkedDataSignature2015": "sec:LinkedDataSignature2015", + + "accessControl": {"@id": "perm:accessControl", "@type": "@id"}, + "writePermission": {"@id": "perm:writePermission", "@type": "@id"} + } +} diff --git a/spec/fixtures/requests/json-ld.security.txt b/spec/fixtures/requests/json-ld.security.txt new file mode 100644 index 000000000..0d29903e6 --- /dev/null +++ b/spec/fixtures/requests/json-ld.security.txt @@ -0,0 +1,61 @@ +HTTP/1.1 200 OK +Accept-Ranges: bytes +Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Accept-Encoding +Access-Control-Allow-Origin: * +Content-Type: application/ld+json +Date: Wed, 02 May 2018 16:25:32 GMT +Etag: "7e3-5651ec0f7c5ed-gzip" +Last-Modified: Tue, 13 Feb 2018 21:34:04 GMT +Server: Apache/2.4.7 (Ubuntu) +Vary: Accept-Encoding +Content-Length: 2019 + +{ + "@context": { + "id": "@id", + "type": "@type", + + "dc": "http://purl.org/dc/terms/", + "sec": "https://w3id.org/security#", + "xsd": "http://www.w3.org/2001/XMLSchema#", + + "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016", + "Ed25519Signature2018": "sec:Ed25519Signature2018", + "EncryptedMessage": "sec:EncryptedMessage", + "GraphSignature2012": "sec:GraphSignature2012", + "LinkedDataSignature2015": "sec:LinkedDataSignature2015", + "LinkedDataSignature2016": "sec:LinkedDataSignature2016", + "CryptographicKey": "sec:Key", + + "authenticationTag": "sec:authenticationTag", + "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm", + "cipherAlgorithm": "sec:cipherAlgorithm", + "cipherData": "sec:cipherData", + "cipherKey": "sec:cipherKey", + "created": {"@id": "dc:created", "@type": "xsd:dateTime"}, + "creator": {"@id": "dc:creator", "@type": "@id"}, + "digestAlgorithm": "sec:digestAlgorithm", + "digestValue": "sec:digestValue", + "domain": "sec:domain", + "encryptionKey": "sec:encryptionKey", + "expiration": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, + "initializationVector": "sec:initializationVector", + "iterationCount": "sec:iterationCount", + "nonce": "sec:nonce", + "normalizationAlgorithm": "sec:normalizationAlgorithm", + "owner": {"@id": "sec:owner", "@type": "@id"}, + "password": "sec:password", + "privateKey": {"@id": "sec:privateKey", "@type": "@id"}, + "privateKeyPem": "sec:privateKeyPem", + "publicKey": {"@id": "sec:publicKey", "@type": "@id"}, + "publicKeyBase58": "sec:publicKeyBase58", + "publicKeyPem": "sec:publicKeyPem", + "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"}, + "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"}, + "salt": "sec:salt", + "signature": "sec:signature", + "signatureAlgorithm": "sec:signingAlgorithm", + "signatureValue": "sec:signatureValue" + } +} diff --git a/spec/fixtures/requests/oembed_json.html b/spec/fixtures/requests/oembed_json.html index 773a4f92a..167085871 100644 --- a/spec/fixtures/requests/oembed_json.html +++ b/spec/fixtures/requests/oembed_json.html @@ -1,7 +1,7 @@ <!DOCTYPE html> <html> <head> - <link href='https://host/provider.json' rel='alternate' type='application/json+oembed'> + <link href='https://host.test/provider.json' rel='alternate' type='application/json+oembed'> </head> <body></body> </html> diff --git a/spec/fixtures/requests/oembed_json_xml.html b/spec/fixtures/requests/oembed_json_xml.html index 8afd8e997..9f5b9e8be 100644 --- a/spec/fixtures/requests/oembed_json_xml.html +++ b/spec/fixtures/requests/oembed_json_xml.html @@ -7,8 +7,8 @@ > The type attribute must contain either application/json+oembed for JSON > responses, or text/xml+oembed for XML. --> - <link href='https://host/provider.json' rel='alternate' type='application/json+oembed'> - <link href='https://host/provider.xml' rel='alternate' type='text/xml+oembed'> + <link href='https://host.test/provider.json' rel='alternate' type='application/json+oembed'> + <link href='https://host.test/provider.xml' rel='alternate' type='text/xml+oembed'> </head> <body></body> </html> diff --git a/spec/fixtures/requests/oembed_xml.html b/spec/fixtures/requests/oembed_xml.html index bdfcca170..788dfaabd 100644 --- a/spec/fixtures/requests/oembed_xml.html +++ b/spec/fixtures/requests/oembed_xml.html @@ -7,7 +7,7 @@ > The type attribute must contain either application/json+oembed for JSON > responses, or text/xml+oembed for XML. --> - <link href='https://host/provider.xml' rel='alternate' type='text/xml+oembed'> + <link href='https://host.test/provider.xml' rel='alternate' type='text/xml+oembed'> </head> <body></body> </html> diff --git a/spec/helpers/jsonld_helper_spec.rb b/spec/helpers/jsonld_helper_spec.rb index 48bfdc306..a5ab249c2 100644 --- a/spec/helpers/jsonld_helper_spec.rb +++ b/spec/helpers/jsonld_helper_spec.rb @@ -32,37 +32,37 @@ describe JsonLdHelper do describe '#fetch_resource' do context 'when the second argument is false' do it 'returns resource even if the retrieved ID and the given URI does not match' do - stub_request(:get, 'https://bob/').to_return body: '{"id": "https://alice/"}' - stub_request(:get, 'https://alice/').to_return body: '{"id": "https://alice/"}' + stub_request(:get, 'https://bob.test/').to_return body: '{"id": "https://alice.test/"}' + stub_request(:get, 'https://alice.test/').to_return body: '{"id": "https://alice.test/"}' - expect(fetch_resource('https://bob/', false)).to eq({ 'id' => 'https://alice/' }) + expect(fetch_resource('https://bob.test/', false)).to eq({ 'id' => 'https://alice.test/' }) end it 'returns nil if the object identified by the given URI and the object identified by the retrieved ID does not match' do - stub_request(:get, 'https://mallory/').to_return body: '{"id": "https://marvin/"}' - stub_request(:get, 'https://marvin/').to_return body: '{"id": "https://alice/"}' + stub_request(:get, 'https://mallory.test/').to_return body: '{"id": "https://marvin.test/"}' + stub_request(:get, 'https://marvin.test/').to_return body: '{"id": "https://alice.test/"}' - expect(fetch_resource('https://mallory/', false)).to eq nil + expect(fetch_resource('https://mallory.test/', false)).to eq nil end end context 'when the second argument is true' do it 'returns nil if the retrieved ID and the given URI does not match' do - stub_request(:get, 'https://mallory/').to_return body: '{"id": "https://alice/"}' - expect(fetch_resource('https://mallory/', true)).to eq nil + stub_request(:get, 'https://mallory.test/').to_return body: '{"id": "https://alice.test/"}' + expect(fetch_resource('https://mallory.test/', true)).to eq nil end end end describe '#fetch_resource_without_id_validation' do it 'returns nil if the status code is not 200' do - stub_request(:get, 'https://host/').to_return status: 400, body: '{}' - expect(fetch_resource_without_id_validation('https://host/')).to eq nil + stub_request(:get, 'https://host.test/').to_return status: 400, body: '{}' + expect(fetch_resource_without_id_validation('https://host.test/')).to eq nil end it 'returns hash' do - stub_request(:get, 'https://host/').to_return status: 200, body: '{}' - expect(fetch_resource_without_id_validation('https://host/')).to eq({}) + stub_request(:get, 'https://host.test/').to_return status: 200, body: '{}' + expect(fetch_resource_without_id_validation('https://host.test/')).to eq({}) end end end diff --git a/spec/lib/activitypub/linked_data_signature_spec.rb b/spec/lib/activitypub/linked_data_signature_spec.rb index a4d6fe8c3..1f413eec9 100644 --- a/spec/lib/activitypub/linked_data_signature_spec.rb +++ b/spec/lib/activitypub/linked_data_signature_spec.rb @@ -16,6 +16,10 @@ RSpec.describe ActivityPub::LinkedDataSignature do subject { described_class.new(json) } + before do + stub_jsonld_contexts! + end + describe '#verify_account!' do context 'when signature matches' do let(:raw_signature) do diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 6e849f379..b8683e720 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' RSpec.describe Formatter do let(:local_account) { Fabricate(:account, domain: nil, username: 'alice') } - let(:remote_account) { Fabricate(:account, domain: 'remote', username: 'bob', url: 'https://remote/') } + let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') } shared_examples 'encode and link URLs' do context 'matches a stand-alone medium URL' do @@ -377,12 +377,12 @@ RSpec.describe Formatter do end context 'contains linkable mentions for remote accounts' do - let(:text) { '@bob@remote' } + let(:text) { '@bob@remote.test' } before { remote_account } it 'links' do - is_expected.to eq '<p><span class="h-card"><a href="https://remote/" class="u-url mention">@<span>bob</span></a></span></p>' + is_expected.to eq '<p><span class="h-card"><a href="https://remote.test/" class="u-url mention">@<span>bob</span></a></span></p>' end end diff --git a/spec/lib/ostatus/atom_serializer_spec.rb b/spec/lib/ostatus/atom_serializer_spec.rb index 00e6f09dc..0bd22880e 100644 --- a/spec/lib/ostatus/atom_serializer_spec.rb +++ b/spec/lib/ostatus/atom_serializer_spec.rb @@ -30,13 +30,13 @@ RSpec.describe OStatus::AtomSerializer do end it 'appends activity:object with target account' do - target_account = Fabricate(:account, domain: 'domain', uri: 'https://domain/id') + target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id') follow_request = Fabricate(:follow_request, target_account: target_account) follow_request_salmon = serialize(follow_request) object = follow_request_salmon.nodes.find { |node| node.name == 'activity:object' } - expect(object.id.text).to eq 'https://domain/id' + expect(object.id.text).to eq 'https://domain.test/id' end end @@ -386,12 +386,6 @@ RSpec.describe OStatus::AtomSerializer do expect(entry.category[:term]).to eq 'tag' end - it 'appends category element for NSFW if status is sensitive' do - status = Fabricate(:status, sensitive: true) - entry = OStatus::AtomSerializer.new.entry(status.stream_entry) - expect(entry.category[:term]).to eq 'nsfw' - end - it 'appends link elements for media attachments' do file = attachment_fixture('attachment.jpg') media_attachment = Fabricate(:media_attachment, file: file) @@ -419,20 +413,20 @@ RSpec.describe OStatus::AtomSerializer do entry = OStatus::AtomSerializer.new.entry(remote_status.stream_entry, true) entry.nodes.delete_if { |node| node[:type] == 'application/activity+json' } # Remove ActivityPub link to simplify test - xml = OStatus::AtomSerializer.render(entry).gsub('cb6e6126.ngrok.io', 'remote') + xml = OStatus::AtomSerializer.render(entry).gsub('cb6e6126.ngrok.io', 'remote.test') remote_status.destroy! remote_account.destroy! account = Account.create!( - domain: 'remote', + domain: 'remote.test', username: 'username', last_webfingered_at: Time.now.utc ) ProcessFeedService.new.call(xml, account) - expect(Status.find_by(uri: "https://remote/users/#{remote_status.account.to_param}/statuses/#{remote_status.id}")).to be_instance_of Status + expect(Status.find_by(uri: "https://remote.test/users/#{remote_status.account.to_param}/statuses/#{remote_status.id}")).to be_instance_of Status end end @@ -782,13 +776,13 @@ RSpec.describe OStatus::AtomSerializer do end it 'appends activity:object element with target account' do - target_account = Fabricate(:account, domain: 'domain', uri: 'https://domain/id') + target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id') block = Fabricate(:block, target_account: target_account) block_salmon = OStatus::AtomSerializer.new.block_salmon(block) object = block_salmon.nodes.find { |node| node.name == 'activity:object' } - expect(object.id.text).to eq 'https://domain/id' + expect(object.id.text).to eq 'https://domain.test/id' end it 'returns element whose rendered view triggers block when processed' do @@ -869,13 +863,13 @@ RSpec.describe OStatus::AtomSerializer do end it 'appends activity:object element with target account' do - target_account = Fabricate(:account, domain: 'domain', uri: 'https://domain/id') + target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id') block = Fabricate(:block, target_account: target_account) unblock_salmon = OStatus::AtomSerializer.new.unblock_salmon(block) object = unblock_salmon.nodes.find { |node| node.name == 'activity:object' } - expect(object.id.text).to eq 'https://domain/id' + expect(object.id.text).to eq 'https://domain.test/id' end it 'returns element whose rendered view triggers block when processed' do @@ -1130,13 +1124,13 @@ RSpec.describe OStatus::AtomSerializer do end it 'appends activity:object element with target account' do - target_account = Fabricate(:account, domain: 'domain', uri: 'https://domain/id') + target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id') follow = Fabricate(:follow, target_account: target_account) follow_salmon = OStatus::AtomSerializer.new.follow_salmon(follow) object = follow_salmon.nodes.find { |node| node.name == 'activity:object' } - expect(object.id.text).to eq 'https://domain/id' + expect(object.id.text).to eq 'https://domain.test/id' end it 'includes description' do @@ -1248,14 +1242,14 @@ RSpec.describe OStatus::AtomSerializer do end it 'appends activity:object element with target account' do - target_account = Fabricate(:account, domain: 'domain', uri: 'https://domain/id') + target_account = Fabricate(:account, domain: 'domain.test', uri: 'https://domain.test/id') follow = Fabricate(:follow, target_account: target_account) follow.destroy! unfollow_salmon = OStatus::AtomSerializer.new.unfollow_salmon(follow) object = unfollow_salmon.nodes.find { |node| node.name == 'activity:object' } - expect(object.id.text).to eq 'https://domain/id' + expect(object.id.text).to eq 'https://domain.test/id' end it 'returns element whose rendered view triggers unfollow when processed' do diff --git a/spec/lib/tag_manager_spec.rb b/spec/lib/tag_manager_spec.rb index 5427a2929..3a804ac0f 100644 --- a/spec/lib/tag_manager_spec.rb +++ b/spec/lib/tag_manager_spec.rb @@ -6,7 +6,7 @@ RSpec.describe TagManager do around do |example| original_local_domain = Rails.configuration.x.local_domain - Rails.configuration.x.local_domain = 'domain' + Rails.configuration.x.local_domain = 'domain.test' example.run @@ -18,11 +18,11 @@ RSpec.describe TagManager do end it 'returns true if the slash-stripped string equals to local domain' do - expect(TagManager.instance.local_domain?('DoMaIn/')).to eq true + expect(TagManager.instance.local_domain?('DoMaIn.Test/')).to eq true end it 'returns false for irrelevant string' do - expect(TagManager.instance.local_domain?('DoMaIn!')).to eq false + expect(TagManager.instance.local_domain?('DoMaIn.Test!')).to eq false end end @@ -31,7 +31,7 @@ RSpec.describe TagManager do around do |example| original_web_domain = Rails.configuration.x.web_domain - Rails.configuration.x.web_domain = 'domain' + Rails.configuration.x.web_domain = 'domain.test' example.run @@ -43,11 +43,11 @@ RSpec.describe TagManager do end it 'returns true if the slash-stripped string equals to web domain' do - expect(TagManager.instance.web_domain?('DoMaIn/')).to eq true + expect(TagManager.instance.web_domain?('DoMaIn.Test/')).to eq true end it 'returns false for string with irrelevant characters' do - expect(TagManager.instance.web_domain?('DoMaIn!')).to eq false + expect(TagManager.instance.web_domain?('DoMaIn.Test!')).to eq false end end @@ -57,7 +57,7 @@ RSpec.describe TagManager do end it 'returns normalized domain' do - expect(TagManager.instance.normalize_domain('DoMaIn/')).to eq 'domain' + expect(TagManager.instance.normalize_domain('DoMaIn.Test/')).to eq 'domain.test' end end @@ -69,18 +69,18 @@ RSpec.describe TagManager do end it 'returns true if the normalized string with port is local URL' do - Rails.configuration.x.web_domain = 'domain:42' - expect(TagManager.instance.local_url?('https://DoMaIn:42/')).to eq true + Rails.configuration.x.web_domain = 'domain.test:42' + expect(TagManager.instance.local_url?('https://DoMaIn.Test:42/')).to eq true end it 'returns true if the normalized string without port is local URL' do - Rails.configuration.x.web_domain = 'domain' - expect(TagManager.instance.local_url?('https://DoMaIn/')).to eq true + Rails.configuration.x.web_domain = 'domain.test' + expect(TagManager.instance.local_url?('https://DoMaIn.Test/')).to eq true end it 'returns false for string with irrelevant characters' do - Rails.configuration.x.web_domain = 'domain' - expect(TagManager.instance.local_url?('https://domainn/')).to eq false + Rails.configuration.x.web_domain = 'domain.test' + expect(TagManager.instance.local_url?('https://domainn.test/')).to eq false end end @@ -88,19 +88,19 @@ RSpec.describe TagManager do # The following comparisons MUST be case-insensitive. it 'returns true if the needle has a correct username and domain for remote user' do - expect(TagManager.instance.same_acct?('username@domain', 'UsErNaMe@DoMaIn')).to eq true + expect(TagManager.instance.same_acct?('username@domain.test', 'UsErNaMe@DoMaIn.Test')).to eq true end it 'returns false if the needle is missing a domain for remote user' do - expect(TagManager.instance.same_acct?('username@domain', 'UsErNaMe')).to eq false + expect(TagManager.instance.same_acct?('username@domain.test', 'UsErNaMe')).to eq false end it 'returns false if the needle has an incorrect domain for remote user' do - expect(TagManager.instance.same_acct?('username@domain', 'UsErNaMe@incorrect')).to eq false + expect(TagManager.instance.same_acct?('username@domain.test', 'UsErNaMe@incorrect.test')).to eq false end it 'returns false if the needle has an incorrect username for remote user' do - expect(TagManager.instance.same_acct?('username@domain', 'incorrect@DoMaIn')).to eq false + expect(TagManager.instance.same_acct?('username@domain.test', 'incorrect@DoMaIn.test')).to eq false end it 'returns true if the needle has a correct username and domain for local user' do diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb index 3ac7208ed..a88b11482 100644 --- a/spec/models/account_spec.rb +++ b/spec/models/account_spec.rb @@ -94,14 +94,14 @@ RSpec.describe Account, type: :model do describe '#save_with_optional_media!' do before do - stub_request(:get, 'https://remote/valid_avatar').to_return(request_fixture('avatar.txt')) - stub_request(:get, 'https://remote/invalid_avatar').to_return(request_fixture('feed.txt')) + stub_request(:get, 'https://remote.test/valid_avatar').to_return(request_fixture('avatar.txt')) + stub_request(:get, 'https://remote.test/invalid_avatar').to_return(request_fixture('feed.txt')) end let(:account) do Fabricate(:account, - avatar_remote_url: 'https://remote/valid_avatar', - header_remote_url: 'https://remote/valid_avatar') + avatar_remote_url: 'https://remote.test/valid_avatar', + header_remote_url: 'https://remote.test/valid_avatar') end let!(:expectation) { account.dup } @@ -121,7 +121,7 @@ RSpec.describe Account, type: :model do context 'with invalid properties' do before do - account.avatar_remote_url = 'https://remote/invalid_avatar' + account.avatar_remote_url = 'https://remote.test/invalid_avatar' account.save_with_optional_media! end @@ -815,7 +815,8 @@ RSpec.describe Account, type: :model do end context 'when is local' do - it 'generates keys' do + # Test disabled because test environment omits autogenerating keys for performance + xit 'generates keys' do account = Account.create!(domain: nil, username: Faker::Internet.user_name(nil, ['_'])) expect(account.keypair.private?).to eq true end diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb index 95bf9561d..9c9b87daf 100644 --- a/spec/models/concerns/account_interactions_spec.rb +++ b/spec/models/concerns/account_interactions_spec.rb @@ -115,13 +115,15 @@ describe AccountInteractions do end describe '#mute!' do + subject { account.mute!(target_account, notifications: arg_notifications) } + context 'Mute does not exist yet' do context 'arg :notifications is nil' do let(:arg_notifications) { nil } - it 'creates Mute, and returns nil' do + it 'creates Mute, and returns Mute' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil + expect(subject).to be_kind_of Mute end.to change { account.mute_relationships.count }.by 1 end end @@ -129,9 +131,9 @@ describe AccountInteractions do context 'arg :notifications is false' do let(:arg_notifications) { false } - it 'creates Mute, and returns nil' do + it 'creates Mute, and returns Mute' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil + expect(subject).to be_kind_of Mute end.to change { account.mute_relationships.count }.by 1 end end @@ -139,9 +141,9 @@ describe AccountInteractions do context 'arg :notifications is true' do let(:arg_notifications) { true } - it 'creates Mute, and returns nil' do + it 'creates Mute, and returns Mute' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil + expect(subject).to be_kind_of Mute end.to change { account.mute_relationships.count }.by 1 end end @@ -165,36 +167,30 @@ describe AccountInteractions do context 'arg :notifications is nil' do let(:arg_notifications) { nil } - it 'returns nil without updating mute.hide_notifications' do + it 'returns Mute without updating mute.hide_notifications' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be true - end + expect(subject).to be_kind_of Mute + end.not_to change { mute.reload.hide_notifications? }.from(true) end end context 'arg :notifications is false' do let(:arg_notifications) { false } - it 'returns true, and updates mute.hide_notifications false' do + it 'returns Mute, and updates mute.hide_notifications false' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be true - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be false - end + expect(subject).to be_kind_of Mute + end.to change { mute.reload.hide_notifications? }.from(true).to(false) end end context 'arg :notifications is true' do let(:arg_notifications) { true } - it 'returns nil without updating mute.hide_notifications' do + it 'returns Mute without updating mute.hide_notifications' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be true - end + expect(subject).to be_kind_of Mute + end.not_to change { mute.reload.hide_notifications? }.from(true) end end end @@ -205,36 +201,30 @@ describe AccountInteractions do context 'arg :notifications is nil' do let(:arg_notifications) { nil } - it 'returns true, and updates mute.hide_notifications true' do + it 'returns Mute, and updates mute.hide_notifications true' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be true - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be true - end + expect(subject).to be_kind_of Mute + end.to change { mute.reload.hide_notifications? }.from(false).to(true) end end context 'arg :notifications is false' do let(:arg_notifications) { false } - it 'returns nil without updating mute.hide_notifications' do + it 'returns Mute without updating mute.hide_notifications' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be nil - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be false - end + expect(subject).to be_kind_of Mute + end.not_to change { mute.reload.hide_notifications? }.from(false) end end context 'arg :notifications is true' do let(:arg_notifications) { true } - it 'returns true, and updates mute.hide_notifications true' do + it 'returns Mute, and updates mute.hide_notifications true' do expect do - expect(account.mute!(target_account, notifications: arg_notifications)).to be true - mute = account.mute_relationships.find_by(target_account: target_account) - expect(mute.hide_notifications?).to be true - end + expect(subject).to be_kind_of Mute + end.to change { mute.reload.hide_notifications? }.from(false).to(true) end end end diff --git a/spec/models/concerns/status_threading_concern_spec.rb b/spec/models/concerns/status_threading_concern_spec.rb index b8ebdd58c..e5736a307 100644 --- a/spec/models/concerns/status_threading_concern_spec.rb +++ b/spec/models/concerns/status_threading_concern_spec.rb @@ -89,34 +89,34 @@ describe StatusThreadingConcern do let!(:viewer) { Fabricate(:account, username: 'viewer') } it 'returns replies' do - expect(status.descendants).to include(reply1, reply2, reply3) + expect(status.descendants(4)).to include(reply1, reply2, reply3) end it 'does not return replies user is not allowed to see' do reply1.update(visibility: :private) reply3.update(visibility: :direct) - expect(status.descendants(viewer)).to_not include(reply1, reply3) + expect(status.descendants(4, viewer)).to_not include(reply1, reply3) end it 'does not return replies from blocked users' do viewer.block!(jeff) - expect(status.descendants(viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply3) end it 'does not return replies from muted users' do viewer.mute!(jeff) - expect(status.descendants(viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply3) end it 'does not return replies from silenced and not followed users' do jeff.update(silenced: true) - expect(status.descendants(viewer)).to_not include(reply3) + expect(status.descendants(4, viewer)).to_not include(reply3) end it 'does not return replies from blocked domains' do viewer.block_domain!('example.com') - expect(status.descendants(viewer)).to_not include(reply2) + expect(status.descendants(4, viewer)).to_not include(reply2) end end end diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index d40ebf6dc..a0cd0800d 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -22,6 +22,101 @@ describe Report do end end + describe 'assign_to_self!' do + subject { report.assigned_account_id } + + let(:report) { Fabricate(:report, assigned_account_id: original_account) } + let(:original_account) { Fabricate(:account) } + let(:current_account) { Fabricate(:account) } + + before do + report.assign_to_self!(current_account) + end + + it 'assigns to a given account' do + is_expected.to eq current_account.id + end + end + + describe 'unassign!' do + subject { report.assigned_account_id } + + let(:report) { Fabricate(:report, assigned_account_id: account.id) } + let(:account) { Fabricate(:account) } + + before do + report.unassign! + end + + it 'unassigns' do + is_expected.to be_nil + end + end + + describe 'resolve!' do + subject(:report) { Fabricate(:report, action_taken: false, action_taken_by_account_id: nil) } + + let(:acting_account) { Fabricate(:account) } + + before do + report.resolve!(acting_account) + end + + it 'records action taken' do + expect(report).to have_attributes(action_taken: true, action_taken_by_account_id: acting_account.id) + end + end + + describe 'unresolve!' do + subject(:report) { Fabricate(:report, action_taken: true, action_taken_by_account_id: acting_account.id) } + + let(:acting_account) { Fabricate(:account) } + + before do + report.unresolve! + end + + it 'unresolves' do + expect(report).to have_attributes(action_taken: false, action_taken_by_account_id: nil) + end + end + + describe 'unresolved?' do + subject { report.unresolved? } + + let(:report) { Fabricate(:report, action_taken: action_taken) } + + context 'if action is taken' do + let(:action_taken) { true } + + it { is_expected.to be false } + end + + context 'if action not is taken' do + let(:action_taken) { false } + + it { is_expected.to be true } + end + end + + describe 'history' do + subject(:action_logs) { report.history } + + let(:report) { Fabricate(:report, target_account_id: target_account.id, status_ids: [status.id], created_at: 3.days.ago, updated_at: 1.day.ago) } + let(:target_account) { Fabricate(:account) } + let(:status) { Fabricate(:status) } + + before do + Fabricate('Admin::ActionLog', target_type: 'Report', account_id: target_account.id, target_id: report.id, created_at: 2.days.ago) + Fabricate('Admin::ActionLog', target_type: 'Account', account_id: target_account.id, target_id: report.target_account_id, created_at: 2.days.ago) + Fabricate('Admin::ActionLog', target_type: 'Status', account_id: target_account.id, target_id: status.id, created_at: 2.days.ago) + end + + it 'returns right logs' do + expect(action_logs.count).to eq 3 + end + end + describe 'validatiions' do it 'has a valid fabricator' do report = Fabricate(:report) diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb index 944baf639..6f0b2feb8 100644 --- a/spec/models/status_pin_spec.rb +++ b/spec/models/status_pin_spec.rb @@ -55,7 +55,7 @@ RSpec.describe StatusPin, type: :model do end it 'allows pins above the max for remote accounts' do - account = Fabricate(:account, domain: 'remote', username: 'bob', url: 'https://remote/') + account = Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') status = [] (max_pins + 1).times do |i| diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8171c939a..760214ded 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -324,4 +324,218 @@ RSpec.describe User, type: :model do expect(admin.role?('moderator')).to be true end end + + describe '#disable!' do + subject(:user) { Fabricate(:user, disabled: false, current_sign_in_at: current_sign_in_at, last_sign_in_at: nil) } + let(:current_sign_in_at) { Time.zone.now } + + before do + user.disable! + end + + it 'disables user' do + expect(user).to have_attributes(disabled: true, current_sign_in_at: nil, last_sign_in_at: current_sign_in_at) + end + end + + describe '#disable!' do + subject(:user) { Fabricate(:user, disabled: false, current_sign_in_at: current_sign_in_at, last_sign_in_at: nil) } + let(:current_sign_in_at) { Time.zone.now } + + before do + user.disable! + end + + it 'disables user' do + expect(user).to have_attributes(disabled: true, current_sign_in_at: nil, last_sign_in_at: current_sign_in_at) + end + end + + describe '#enable!' do + subject(:user) { Fabricate(:user, disabled: true) } + + before do + user.enable! + end + + it 'enables user' do + expect(user).to have_attributes(disabled: false) + end + end + + describe '#confirm!' do + subject(:user) { Fabricate(:user, confirmed_at: confirmed_at) } + + before do + ActionMailer::Base.deliveries.clear + user.confirm! + end + + after { ActionMailer::Base.deliveries.clear } + + context 'when user is new' do + let(:confirmed_at) { nil } + + it 'confirms user' do + expect(user.confirmed_at).to be_present + end + + it 'delivers mails' do + expect(ActionMailer::Base.deliveries.count).to eq 2 + end + end + + context 'when user is not new' do + let(:confirmed_at) { Time.zone.now } + + it 'confirms user' do + expect(user.confirmed_at).to be_present + end + + it 'does not deliver mail' do + expect(ActionMailer::Base.deliveries.count).to eq 0 + end + end + end + + describe '#promote!' do + subject(:user) { Fabricate(:user, admin: is_admin, moderator: is_moderator) } + + before do + user.promote! + end + + context 'when user is an admin' do + let(:is_admin) { true } + + context 'when user is a moderator' do + let(:is_moderator) { true } + + it 'changes moderator filed false' do + expect(user).to be_admin + expect(user).not_to be_moderator + end + end + + context 'when user is not a moderator' do + let(:is_moderator) { false } + + it 'does not change status' do + expect(user).to be_admin + expect(user).not_to be_moderator + end + end + end + + context 'when user is not admin' do + let(:is_admin) { false } + + context 'when user is a moderator' do + let(:is_moderator) { true } + + it 'changes user into an admin' do + expect(user).to be_admin + expect(user).not_to be_moderator + end + end + + context 'when user is not a moderator' do + let(:is_moderator) { false } + + it 'changes user into a moderator' do + expect(user).not_to be_admin + expect(user).to be_moderator + end + end + end + end + + describe '#demote!' do + subject(:user) { Fabricate(:user, admin: admin, moderator: moderator) } + + before do + user.demote! + end + + context 'when user is an admin' do + let(:admin) { true } + + context 'when user is a moderator' do + let(:moderator) { true } + + it 'changes user into a moderator' do + expect(user).not_to be_admin + expect(user).to be_moderator + end + end + + context 'when user is not a moderator' do + let(:moderator) { false } + + it 'changes user into a moderator' do + expect(user).not_to be_admin + expect(user).to be_moderator + end + end + end + + context 'when user is not an admin' do + let(:admin) { false } + + context 'when user is a moderator' do + let(:moderator) { true } + + it 'changes user into a plain user' do + expect(user).not_to be_admin + expect(user).not_to be_moderator + end + end + + context 'when user is not a moderator' do + let(:moderator) { false } + + it 'does not change any fields' do + expect(user).not_to be_admin + expect(user).not_to be_moderator + end + end + end + end + + describe '#active_for_authentication?' do + subject { user.active_for_authentication? } + let(:user) { Fabricate(:user, disabled: disabled, confirmed_at: confirmed_at) } + + context 'when user is disabled' do + let(:disabled) { true } + + context 'when user is confirmed' do + let(:confirmed_at) { Time.zone.now } + + it { is_expected.to be false } + end + + context 'when user is not confirmed' do + let(:confirmed_at) { nil } + + it { is_expected.to be false } + end + end + + context 'when user is not disabled' do + let(:disabled) { false } + + context 'when user is confirmed' do + let(:confirmed_at) { Time.zone.now } + + it { is_expected.to be true } + end + + context 'when user is not confirmed' do + let(:confirmed_at) { nil } + + it { is_expected.to be false } + end + end + end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index dc1f32e08..c575128e4 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -50,6 +50,14 @@ RSpec.configure do |config| Capybara.app_host = "http#{https ? 's' : ''}://#{ENV.fetch('LOCAL_DOMAIN')}" end + config.before :each, type: :controller do + stub_jsonld_contexts! + end + + config.before :each, type: :service do + stub_jsonld_contexts! + end + config.after :each do Rails.cache.clear @@ -69,3 +77,9 @@ end def attachment_fixture(name) File.open(File.join(Rails.root, 'spec', 'fixtures', 'files', name)) end + +def stub_jsonld_contexts! + stub_request(:get, 'https://www.w3.org/ns/activitystreams').to_return(request_fixture('json-ld.activitystreams.txt')) + stub_request(:get, 'https://w3id.org/identity/v1').to_return(request_fixture('json-ld.identity.txt')) + stub_request(:get, 'https://w3id.org/security/v1').to_return(request_fixture('json-ld.security.txt')) +end diff --git a/spec/requests/host_meta_request_spec.rb b/spec/requests/host_meta_request_spec.rb index 0c51b5f48..beb33a859 100644 --- a/spec/requests/host_meta_request_spec.rb +++ b/spec/requests/host_meta_request_spec.rb @@ -5,7 +5,7 @@ describe "The host_meta route" do it "returns an xml response" do get host_meta_url - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq "application/xrd+xml" end end diff --git a/spec/requests/webfinger_request_spec.rb b/spec/requests/webfinger_request_spec.rb index a17d6cc22..7f9e1162e 100644 --- a/spec/requests/webfinger_request_spec.rb +++ b/spec/requests/webfinger_request_spec.rb @@ -7,7 +7,7 @@ describe 'The webfinger route' do it 'returns a json response' do get webfinger_url(resource: alice.to_webfinger_s) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/jrd+json' end end @@ -16,7 +16,7 @@ describe 'The webfinger route' do it 'returns an xml response for xml format' do get webfinger_url(resource: alice.to_webfinger_s, format: :xml) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/xrd+xml' end @@ -24,7 +24,7 @@ describe 'The webfinger route' do headers = { 'HTTP_ACCEPT' => 'application/xrd+xml' } get webfinger_url(resource: alice.to_webfinger_s), headers: headers - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/xrd+xml' end end @@ -33,7 +33,7 @@ describe 'The webfinger route' do it 'returns a json response for json format' do get webfinger_url(resource: alice.to_webfinger_s, format: :json) - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/jrd+json' end @@ -41,7 +41,7 @@ describe 'The webfinger route' do headers = { 'HTTP_ACCEPT' => 'application/jrd+json' } get webfinger_url(resource: alice.to_webfinger_s), headers: headers - expect(response).to have_http_status(:success) + expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/jrd+json' end end diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb index 9bb27edad..c6cbdcce1 100644 --- a/spec/services/account_search_service_spec.rb +++ b/spec/services/account_search_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -describe AccountSearchService do +describe AccountSearchService, type: :service do describe '.call' do describe 'with a query to ignore' do it 'returns empty array for missing query' do @@ -137,5 +137,24 @@ describe AccountSearchService do expect(service).not_to have_received(:call) end end + + describe 'should not include suspended accounts' do + it 'returns the fuzzy match first, and does not return suspended exacts' do + partial = Fabricate(:account, username: 'exactness') + exact = Fabricate(:account, username: 'exact', suspended: true) + + results = subject.call('exact', 10) + expect(results.size).to eq 1 + expect(results).to eq [partial] + end + + it "does not return suspended remote accounts" do + remote = Fabricate(:account, username: 'a', domain: 'remote', display_name: 'e', suspended: true) + + results = subject.call('a@example.com', 2) + expect(results.size).to eq 0 + expect(results).to eq [] + end + end end end diff --git a/spec/services/activitypub/fetch_remote_account_service_spec.rb b/spec/services/activitypub/fetch_remote_account_service_spec.rb index c50d3fb97..dba55c034 100644 --- a/spec/services/activitypub/fetch_remote_account_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_account_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ActivityPub::FetchRemoteAccountService do +RSpec.describe ActivityPub::FetchRemoteAccountService, type: :service do subject { ActivityPub::FetchRemoteAccountService.new } let!(:actor) do diff --git a/spec/services/activitypub/fetch_remote_status_service_spec.rb b/spec/services/activitypub/fetch_remote_status_service_spec.rb index a533e8413..549eb80fa 100644 --- a/spec/services/activitypub/fetch_remote_status_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_status_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ActivityPub::FetchRemoteStatusService do +RSpec.describe ActivityPub::FetchRemoteStatusService, type: :service do include ActionView::Helpers::TextHelper let(:sender) { Fabricate(:account) } diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index 15e1f4bb2..d3318b2ed 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -1,14 +1,14 @@ require 'rails_helper' -RSpec.describe ActivityPub::ProcessAccountService do +RSpec.describe ActivityPub::ProcessAccountService, type: :service do subject { described_class.new } context 'property values' do let(:payload) do { - id: 'https://foo', + id: 'https://foo.test', type: 'Actor', - inbox: 'https://foo/inbox', + inbox: 'https://foo.test/inbox', attachment: [ { type: 'PropertyValue', name: 'Pronouns', value: 'They/them' }, { type: 'PropertyValue', name: 'Occupation', value: 'Unit test' }, diff --git a/spec/services/activitypub/process_collection_service_spec.rb b/spec/services/activitypub/process_collection_service_spec.rb index 3cea970cf..e46f0ae45 100644 --- a/spec/services/activitypub/process_collection_service_spec.rb +++ b/spec/services/activitypub/process_collection_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ActivityPub::ProcessCollectionService do +RSpec.describe ActivityPub::ProcessCollectionService, type: :service do let(:actor) { Fabricate(:account, domain: 'example.com', uri: 'http://example.com/account') } let(:payload) do diff --git a/spec/services/after_block_service_spec.rb b/spec/services/after_block_service_spec.rb index 1b115c938..f63b2045a 100644 --- a/spec/services/after_block_service_spec.rb +++ b/spec/services/after_block_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe AfterBlockService do +RSpec.describe AfterBlockService, type: :service do subject do -> { described_class.new.call(account, target_account) } end diff --git a/spec/services/authorize_follow_service_spec.rb b/spec/services/authorize_follow_service_spec.rb index 6ea4d83da..562ef0041 100644 --- a/spec/services/authorize_follow_service_spec.rb +++ b/spec/services/authorize_follow_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe AuthorizeFollowService do +RSpec.describe AuthorizeFollowService, type: :service do let(:sender) { Fabricate(:account, username: 'alice') } subject { AuthorizeFollowService.new } diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb index 437da2a9d..23c122e59 100644 --- a/spec/services/batched_remove_status_service_spec.rb +++ b/spec/services/batched_remove_status_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe BatchedRemoveStatusService do +RSpec.describe BatchedRemoveStatusService, type: :service do subject { BatchedRemoveStatusService.new } let!(:alice) { Fabricate(:account) } diff --git a/spec/services/block_domain_from_account_service_spec.rb b/spec/services/block_domain_from_account_service_spec.rb index e7ee34372..365c0a4ad 100644 --- a/spec/services/block_domain_from_account_service_spec.rb +++ b/spec/services/block_domain_from_account_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe BlockDomainFromAccountService do +RSpec.describe BlockDomainFromAccountService, type: :service do let!(:wolf) { Fabricate(:account, username: 'wolf', domain: 'evil.org') } let!(:alice) { Fabricate(:account, username: 'alice') } diff --git a/spec/services/block_domain_service_spec.rb b/spec/services/block_domain_service_spec.rb index 5c2cfc8c7..7ef9e2770 100644 --- a/spec/services/block_domain_service_spec.rb +++ b/spec/services/block_domain_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe BlockDomainService do +RSpec.describe BlockDomainService, type: :service do let(:bad_account) { Fabricate(:account, username: 'badguy666', domain: 'evil.org') } let(:bad_status1) { Fabricate(:status, account: bad_account, text: 'You suck') } let(:bad_status2) { Fabricate(:status, account: bad_account, text: 'Hahaha') } diff --git a/spec/services/block_service_spec.rb b/spec/services/block_service_spec.rb index c69ff7804..6584bb90e 100644 --- a/spec/services/block_service_spec.rb +++ b/spec/services/block_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe BlockService do +RSpec.describe BlockService, type: :service do let(:sender) { Fabricate(:account, username: 'alice') } subject { BlockService.new } diff --git a/spec/services/bootstrap_timeline_service_spec.rb b/spec/services/bootstrap_timeline_service_spec.rb index 5189b1de8..a765de791 100644 --- a/spec/services/bootstrap_timeline_service_spec.rb +++ b/spec/services/bootstrap_timeline_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe BootstrapTimelineService do +RSpec.describe BootstrapTimelineService, type: :service do subject { described_class.new } describe '#call' do diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb index 764318e34..b7fc7f7ed 100644 --- a/spec/services/fan_out_on_write_service_spec.rb +++ b/spec/services/fan_out_on_write_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe FanOutOnWriteService do +RSpec.describe FanOutOnWriteService, type: :service do let(:author) { Fabricate(:account, username: 'tom') } let(:status) { Fabricate(:status, text: 'Hello @alice #test', account: author) } let(:alice) { Fabricate(:user, account: Fabricate(:account, username: 'alice')).account } diff --git a/spec/services/favourite_service_spec.rb b/spec/services/favourite_service_spec.rb index 5bf2c74a9..0a20ccf6e 100644 --- a/spec/services/favourite_service_spec.rb +++ b/spec/services/favourite_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe FavouriteService do +RSpec.describe FavouriteService, type: :service do let(:sender) { Fabricate(:account, username: 'alice') } subject { FavouriteService.new } diff --git a/spec/services/fetch_atom_service_spec.rb b/spec/services/fetch_atom_service_spec.rb index 2bd127e92..bb233c12d 100644 --- a/spec/services/fetch_atom_service_spec.rb +++ b/spec/services/fetch_atom_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe FetchAtomService do +RSpec.describe FetchAtomService, type: :service do describe '#call' do let(:url) { 'http://example.com' } subject { FetchAtomService.new.call(url) } diff --git a/spec/services/fetch_link_card_service_spec.rb b/spec/services/fetch_link_card_service_spec.rb index edacc4425..88c5339db 100644 --- a/spec/services/fetch_link_card_service_spec.rb +++ b/spec/services/fetch_link_card_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe FetchLinkCardService do +RSpec.describe FetchLinkCardService, type: :service do subject { FetchLinkCardService.new } before do diff --git a/spec/lib/provider_discovery_spec.rb b/spec/services/fetch_oembed_service_spec.rb index 12e2616c9..706eb3f2a 100644 --- a/spec/lib/provider_discovery_spec.rb +++ b/spec/services/fetch_oembed_service_spec.rb @@ -2,12 +2,19 @@ require 'rails_helper' -describe ProviderDiscovery do +describe FetchOEmbedService, type: :service do + subject { described_class.new } + + before do + stub_request(:get, "https://host.test/provider.json").to_return(status: 404) + stub_request(:get, "https://host.test/provider.xml").to_return(status: 404) + end + describe 'discover_provider' do context 'when status code is 200 and MIME type is text/html' do context 'Both of JSON and XML provider are discoverable' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_json_xml.html') @@ -15,21 +22,21 @@ describe ProviderDiscovery do end it 'returns new OEmbed::Provider for JSON provider if :format option is set to :json' do - provider = ProviderDiscovery.discover_provider('https://host/oembed.html', format: :json) - expect(provider.endpoint).to eq 'https://host/provider.json' - expect(provider.format).to eq :json + subject.call('https://host.test/oembed.html', format: :json) + expect(subject.endpoint_url).to eq 'https://host.test/provider.json' + expect(subject.format).to eq :json end it 'returns new OEmbed::Provider for XML provider if :format option is set to :xml' do - provider = ProviderDiscovery.discover_provider('https://host/oembed.html', format: :xml) - expect(provider.endpoint).to eq 'https://host/provider.xml' - expect(provider.format).to eq :xml + subject.call('https://host.test/oembed.html', format: :xml) + expect(subject.endpoint_url).to eq 'https://host.test/provider.xml' + expect(subject.format).to eq :xml end end context 'JSON provider is discoverable while XML provider is not' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_json.html') @@ -37,15 +44,15 @@ describe ProviderDiscovery do end it 'returns new OEmbed::Provider for JSON provider' do - provider = ProviderDiscovery.discover_provider('https://host/oembed.html') - expect(provider.endpoint).to eq 'https://host/provider.json' - expect(provider.format).to eq :json + subject.call('https://host.test/oembed.html') + expect(subject.endpoint_url).to eq 'https://host.test/provider.json' + expect(subject.format).to eq :json end end context 'XML provider is discoverable while JSON provider is not' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_xml.html') @@ -53,65 +60,65 @@ describe ProviderDiscovery do end it 'returns new OEmbed::Provider for XML provider' do - provider = ProviderDiscovery.discover_provider('https://host/oembed.html') - expect(provider.endpoint).to eq 'https://host/provider.xml' - expect(provider.format).to eq :xml + subject.call('https://host.test/oembed.html') + expect(subject.endpoint_url).to eq 'https://host.test/provider.xml' + expect(subject.format).to eq :xml end end context 'Invalid XML provider is discoverable while JSON provider is not' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_invalid_xml.html') ) end - it 'raises OEmbed::NotFound' do - expect { ProviderDiscovery.discover_provider('https://host/oembed.html') }.to raise_error OEmbed::NotFound + it 'returns nil' do + expect(subject.call('https://host.test/oembed.html')).to be_nil end end context 'Neither of JSON and XML provider is discoverable' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_undiscoverable.html') ) end - it 'raises OEmbed::NotFound' do - expect { ProviderDiscovery.discover_provider('https://host/oembed.html') }.to raise_error OEmbed::NotFound + it 'returns nil' do + expect(subject.call('https://host.test/oembed.html')).to be_nil end end end context 'when status code is not 200' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 400, headers: { 'Content-Type': 'text/html' }, body: request_fixture('oembed_xml.html') ) end - it 'raises OEmbed::NotFound' do - expect { ProviderDiscovery.discover_provider('https://host/oembed.html') }.to raise_error OEmbed::NotFound + it 'returns nil' do + expect(subject.call('https://host.test/oembed.html')).to be_nil end end context 'when MIME type is not text/html' do before do - stub_request(:get, 'https://host/oembed.html').to_return( + stub_request(:get, 'https://host.test/oembed.html').to_return( status: 200, body: request_fixture('oembed_xml.html') ) end - it 'raises OEmbed::NotFound' do - expect { ProviderDiscovery.discover_provider('https://host/oembed.html') }.to raise_error OEmbed::NotFound + it 'returns nil' do + expect(subject.call('https://host.test/oembed.html')).to be_nil end end end diff --git a/spec/services/fetch_remote_account_service_spec.rb b/spec/services/fetch_remote_account_service_spec.rb index 4388d4cf4..1c3abe8f3 100644 --- a/spec/services/fetch_remote_account_service_spec.rb +++ b/spec/services/fetch_remote_account_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe FetchRemoteAccountService do +RSpec.describe FetchRemoteAccountService, type: :service do let(:url) { 'https://example.com' } let(:prefetched_body) { nil } let(:protocol) { :ostatus } diff --git a/spec/services/fetch_remote_status_service_spec.rb b/spec/services/fetch_remote_status_service_spec.rb index fa5782b94..0df9c329a 100644 --- a/spec/services/fetch_remote_status_service_spec.rb +++ b/spec/services/fetch_remote_status_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe FetchRemoteStatusService do +RSpec.describe FetchRemoteStatusService, type: :service do let(:account) { Fabricate(:account) } let(:prefetched_body) { nil } let(:valid_domain) { Rails.configuration.x.local_domain } diff --git a/spec/services/follow_service_spec.rb b/spec/services/follow_service_spec.rb index e59a2f1a6..3c4ec59be 100644 --- a/spec/services/follow_service_spec.rb +++ b/spec/services/follow_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe FollowService do +RSpec.describe FollowService, type: :service do let(:sender) { Fabricate(:account, username: 'alice') } subject { FollowService.new } diff --git a/spec/services/mute_service_spec.rb b/spec/services/mute_service_spec.rb index 2b3e3e152..4bb839b8d 100644 --- a/spec/services/mute_service_spec.rb +++ b/spec/services/mute_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe MuteService do +RSpec.describe MuteService, type: :service do subject do -> { described_class.new.call(account, target_account) } end diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index 1435ec917..ff64eccbe 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe NotifyService do +RSpec.describe NotifyService, type: :service do subject do -> { described_class.new.call(recipient, activity) } end diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index 92fbc73cd..40fa8fbef 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe PostStatusService do +RSpec.describe PostStatusService, type: :service do subject { PostStatusService.new } it 'creates a new status' do diff --git a/spec/services/precompute_feed_service_spec.rb b/spec/services/precompute_feed_service_spec.rb index 43340bffc..1f6b6ed88 100644 --- a/spec/services/precompute_feed_service_spec.rb +++ b/spec/services/precompute_feed_service_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe PrecomputeFeedService do +RSpec.describe PrecomputeFeedService, type: :service do subject { PrecomputeFeedService.new } describe 'call' do diff --git a/spec/services/process_feed_service_spec.rb b/spec/services/process_feed_service_spec.rb index aca675dc6..d8b065063 100644 --- a/spec/services/process_feed_service_spec.rb +++ b/spec/services/process_feed_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ProcessFeedService do +RSpec.describe ProcessFeedService, type: :service do subject { ProcessFeedService.new } describe 'processing a feed' do diff --git a/spec/services/process_interaction_service_spec.rb b/spec/services/process_interaction_service_spec.rb index 3ea7aec59..b858c19d0 100644 --- a/spec/services/process_interaction_service_spec.rb +++ b/spec/services/process_interaction_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ProcessInteractionService do +RSpec.describe ProcessInteractionService, type: :service do let(:receiver) { Fabricate(:user, email: 'alice@example.com', account: Fabricate(:account, username: 'alice')).account } let(:sender) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account } let(:remote_sender) { Fabricate(:account, username: 'carol', domain: 'localdomain.com', uri: 'https://webdomain.com/users/carol') } diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb index 19a8678f0..963924fa9 100644 --- a/spec/services/process_mentions_service_spec.rb +++ b/spec/services/process_mentions_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ProcessMentionsService do +RSpec.describe ProcessMentionsService, type: :service do let(:account) { Fabricate(:account, username: 'alice') } let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}") } diff --git a/spec/services/pubsubhubbub/subscribe_service_spec.rb b/spec/services/pubsubhubbub/subscribe_service_spec.rb index 82094117b..01c956230 100644 --- a/spec/services/pubsubhubbub/subscribe_service_spec.rb +++ b/spec/services/pubsubhubbub/subscribe_service_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -describe Pubsubhubbub::SubscribeService do +describe Pubsubhubbub::SubscribeService, type: :service do describe '#call' do subject { described_class.new } let(:user_account) { Fabricate(:account) } diff --git a/spec/services/pubsubhubbub/unsubscribe_service_spec.rb b/spec/services/pubsubhubbub/unsubscribe_service_spec.rb index 59054ed99..7ed9fc5af 100644 --- a/spec/services/pubsubhubbub/unsubscribe_service_spec.rb +++ b/spec/services/pubsubhubbub/unsubscribe_service_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -describe Pubsubhubbub::UnsubscribeService do +describe Pubsubhubbub::UnsubscribeService, type: :service do describe '#call' do subject { described_class.new } diff --git a/spec/services/reblog_service_spec.rb b/spec/services/reblog_service_spec.rb index 19d3bb6cb..2755da772 100644 --- a/spec/services/reblog_service_spec.rb +++ b/spec/services/reblog_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ReblogService do +RSpec.describe ReblogService, type: :service do let(:alice) { Fabricate(:account, username: 'alice') } context 'OStatus' do diff --git a/spec/services/reject_follow_service_spec.rb b/spec/services/reject_follow_service_spec.rb index bf49dd2c9..e5ac37ed9 100644 --- a/spec/services/reject_follow_service_spec.rb +++ b/spec/services/reject_follow_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe RejectFollowService do +RSpec.describe RejectFollowService, type: :service do let(:sender) { Fabricate(:account, username: 'alice') } subject { RejectFollowService.new } diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb index 5bb75b820..2134f51fd 100644 --- a/spec/services/remove_status_service_spec.rb +++ b/spec/services/remove_status_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe RemoveStatusService do +RSpec.describe RemoveStatusService, type: :service do subject { RemoveStatusService.new } let!(:alice) { Fabricate(:account) } diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb index 2f926ef00..2c392d376 100644 --- a/spec/services/report_service_spec.rb +++ b/spec/services/report_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ReportService do +RSpec.describe ReportService, type: :service do subject { described_class.new } let(:source_account) { Fabricate(:account) } diff --git a/spec/services/resolve_account_service_spec.rb b/spec/services/resolve_account_service_spec.rb index 5f1b4467b..f4c810f75 100644 --- a/spec/services/resolve_account_service_spec.rb +++ b/spec/services/resolve_account_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe ResolveAccountService do +RSpec.describe ResolveAccountService, type: :service do subject { described_class.new } before do @@ -105,6 +105,20 @@ RSpec.describe ResolveAccountService do expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox' end + context 'with multiple types' do + before do + stub_request(:get, "https://ap.example.com/users/foo").to_return(request_fixture('activitypub-actor-individual.txt')) + end + + it 'returns new remote account' do + account = subject.call('foo@ap.example.com') + + expect(account.activitypub?).to eq true + expect(account.domain).to eq 'ap.example.com' + expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox' + end + end + pending end diff --git a/spec/services/resolve_url_service_spec.rb b/spec/services/resolve_url_service_spec.rb index 1e9be4c07..7bb5d1940 100644 --- a/spec/services/resolve_url_service_spec.rb +++ b/spec/services/resolve_url_service_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -describe ResolveURLService do +describe ResolveURLService, type: :service do subject { described_class.new } describe '#call' do diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index 957b60c7d..673de5233 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -describe SearchService do +describe SearchService, type: :service do subject { described_class.new } describe '#call' do diff --git a/spec/services/send_interaction_service_spec.rb b/spec/services/send_interaction_service_spec.rb index ff08394b0..710d8184c 100644 --- a/spec/services/send_interaction_service_spec.rb +++ b/spec/services/send_interaction_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe SendInteractionService do +RSpec.describe SendInteractionService, type: :service do subject { SendInteractionService.new } it 'sends an XML envelope to the Salmon end point of remote user' diff --git a/spec/services/subscribe_service_spec.rb b/spec/services/subscribe_service_spec.rb index 835be5ec5..10bdb1ba8 100644 --- a/spec/services/subscribe_service_spec.rb +++ b/spec/services/subscribe_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe SubscribeService do +RSpec.describe SubscribeService, type: :service do let(:account) { Fabricate(:account, username: 'bob', domain: 'example.com', hub_url: 'http://hub.example.com') } subject { SubscribeService.new } diff --git a/spec/services/suspend_account_service_spec.rb b/spec/services/suspend_account_service_spec.rb index 1cb647e8d..fd303a9d5 100644 --- a/spec/services/suspend_account_service_spec.rb +++ b/spec/services/suspend_account_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe SuspendAccountService do +RSpec.describe SuspendAccountService, type: :service do describe '#call' do subject do -> { described_class.new.call(account) } diff --git a/spec/services/unblock_domain_service_spec.rb b/spec/services/unblock_domain_service_spec.rb index c32e5d655..8e8893d63 100644 --- a/spec/services/unblock_domain_service_spec.rb +++ b/spec/services/unblock_domain_service_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -describe UnblockDomainService do +describe UnblockDomainService, type: :service do subject { described_class.new } describe 'call' do diff --git a/spec/services/unblock_service_spec.rb b/spec/services/unblock_service_spec.rb index ca7a6b77e..5835b912b 100644 --- a/spec/services/unblock_service_spec.rb +++ b/spec/services/unblock_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe UnblockService do +RSpec.describe UnblockService, type: :service do let(:sender) { Fabricate(:account, username: 'alice') } subject { UnblockService.new } diff --git a/spec/services/unfollow_service_spec.rb b/spec/services/unfollow_service_spec.rb index 021e76782..c5914c818 100644 --- a/spec/services/unfollow_service_spec.rb +++ b/spec/services/unfollow_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe UnfollowService do +RSpec.describe UnfollowService, type: :service do let(:sender) { Fabricate(:account, username: 'alice') } subject { UnfollowService.new } diff --git a/spec/services/unmute_service_spec.rb b/spec/services/unmute_service_spec.rb index 5dc971fb1..8463eb283 100644 --- a/spec/services/unmute_service_spec.rb +++ b/spec/services/unmute_service_spec.rb @@ -1,5 +1,5 @@ require 'rails_helper' -RSpec.describe UnmuteService do +RSpec.describe UnmuteService, type: :service do subject { UnmuteService.new } end diff --git a/spec/services/unsubscribe_service_spec.rb b/spec/services/unsubscribe_service_spec.rb index 2a02f4c75..54d4b1b53 100644 --- a/spec/services/unsubscribe_service_spec.rb +++ b/spec/services/unsubscribe_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe UnsubscribeService do +RSpec.describe UnsubscribeService, type: :service do let(:account) { Fabricate(:account, username: 'bob', domain: 'example.com', hub_url: 'http://hub.example.com') } subject { UnsubscribeService.new } diff --git a/spec/services/update_remote_profile_service_spec.rb b/spec/services/update_remote_profile_service_spec.rb index 64ec2dbbb..7ac3a809a 100644 --- a/spec/services/update_remote_profile_service_spec.rb +++ b/spec/services/update_remote_profile_service_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -RSpec.describe UpdateRemoteProfileService do +RSpec.describe UpdateRemoteProfileService, type: :service do let(:xml) { File.read(File.join(Rails.root, 'spec', 'fixtures', 'push', 'feed.atom')) } subject { UpdateRemoteProfileService.new } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a0466dd4b..903032937 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,4 @@ +#require 'rspec/retry' require 'simplecov' GC.disable @@ -11,6 +12,9 @@ end gc_counter = -1 RSpec.configure do |config| + #config.verbose_retry = true + #config.display_try_failure_messages = true + config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end @@ -25,6 +29,10 @@ RSpec.configure do |config| end end + #config.around :each do |ex| + # ex.run_with_retry retry: 3 + #end + config.before :suite do Chewy.strategy(:bypass) end diff --git a/spec/views/stream_entries/show.html.haml_spec.rb b/spec/views/stream_entries/show.html.haml_spec.rb index 6074bbc2e..560039ffa 100644 --- a/spec/views/stream_entries/show.html.haml_spec.rb +++ b/spec/views/stream_entries/show.html.haml_spec.rb @@ -24,6 +24,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:stream_entry, status.stream_entry) assign(:account, alice) assign(:type, status.stream_entry.activity_type.downcase) + assign(:descendant_threads, []) render @@ -49,7 +50,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:account, alice) assign(:type, reply.stream_entry.activity_type.downcase) assign(:ancestors, reply.stream_entry.activity.ancestors(1, bob) ) - assign(:descendants, reply.stream_entry.activity.descendants(bob)) + assign(:descendant_threads, [{ statuses: reply.stream_entry.activity.descendants(1)}]) render @@ -75,6 +76,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:stream_entry, status.stream_entry) assign(:account, alice) assign(:type, status.stream_entry.activity_type.downcase) + assign(:descendant_threads, []) render |