logo

mastofe

My custom branche(s) on git.pleroma.social/pleroma/mastofe
commit: 4b8b31c4075016c70fac236ea938a91737c76216
parent: fa5858c4ac3f2bf0e9b8c74c62a54fec8935b0b5
Author: Morgan Bazalgette <the@howl.moe>
Date:   Mon, 16 Apr 2018 22:44:51 +0200

Merge branch 'master' of github.com:tootsuite/mastodon

Diffstat:

M.env.test4++++
M.travis.yml3++-
MGemfile62+++++++++++++++++++++++++++++++-------------------------------
MGemfile.lock382+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Aapp/controllers/admin/change_emails_controller.rb49+++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/controllers/admin/report_notes_controller.rb17++++++++++++-----
Mapp/controllers/admin/reports_controller.rb20+++++++++-----------
Mapp/controllers/api/v1/statuses_controller.rb2+-
Mapp/controllers/api/web/push_subscriptions_controller.rb25++++++++++++++++---------
Mapp/controllers/settings/follower_domains_controller.rb2+-
Mapp/controllers/settings/profiles_controller.rb6++++--
Mapp/controllers/statuses_controller.rb7+++++--
Mapp/controllers/stream_entries_controller.rb3+--
Mapp/helpers/admin/action_logs_helper.rb4+++-
Mapp/javascript/mastodon/actions/importer/normalizer.js8++++++++
Mapp/javascript/mastodon/actions/notifications.js37++++++++++++++++++++++++-------------
Aapp/javascript/mastodon/actions/tos.js37+++++++++++++++++++++++++++++++++++++
Aapp/javascript/mastodon/components/load_gap.js33+++++++++++++++++++++++++++++++++
Mapp/javascript/mastodon/components/scrollable_list.js32++++++++++++--------------------
Mapp/javascript/mastodon/components/status_list.js20+-------------------
Mapp/javascript/mastodon/features/account/components/action_bar.js3---
Mapp/javascript/mastodon/features/account/components/header.js14++++++++++++++
Mapp/javascript/mastodon/features/compose/components/privacy_dropdown.js22+++++++++++++++++-----
Mapp/javascript/mastodon/features/domain_blocks/index.js2+-
Mapp/javascript/mastodon/features/notifications/index.js20+-------------------
Mapp/javascript/mastodon/locales/ar.json2++
Mapp/javascript/mastodon/locales/bg.json2++
Mapp/javascript/mastodon/locales/ca.json2++
Mapp/javascript/mastodon/locales/de.json2++
Mapp/javascript/mastodon/locales/defaultMessages.json22+++++++++-------------
Mapp/javascript/mastodon/locales/eo.json14++++++++------
Mapp/javascript/mastodon/locales/es.json2++
Mapp/javascript/mastodon/locales/fa.json2++
Mapp/javascript/mastodon/locales/fi.json230++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mapp/javascript/mastodon/locales/fr.json2++
Mapp/javascript/mastodon/locales/gl.json6++++--
Mapp/javascript/mastodon/locales/he.json2++
Mapp/javascript/mastodon/locales/hr.json2++
Mapp/javascript/mastodon/locales/hu.json2++
Mapp/javascript/mastodon/locales/hy.json2++
Mapp/javascript/mastodon/locales/id.json2++
Mapp/javascript/mastodon/locales/io.json2++
Mapp/javascript/mastodon/locales/it.json2++
Mapp/javascript/mastodon/locales/ja.json4+++-
Mapp/javascript/mastodon/locales/ko.json2++
Mapp/javascript/mastodon/locales/nl.json2++
Mapp/javascript/mastodon/locales/no.json2++
Mapp/javascript/mastodon/locales/oc.json2++
Mapp/javascript/mastodon/locales/pl.json3++-
Mapp/javascript/mastodon/locales/pt-BR.json2++
Mapp/javascript/mastodon/locales/pt.json2++
Mapp/javascript/mastodon/locales/ru.json2++
Mapp/javascript/mastodon/locales/sk.json28+++++++++++++++-------------
Mapp/javascript/mastodon/locales/sr-Latn.json2++
Mapp/javascript/mastodon/locales/sr.json2++
Mapp/javascript/mastodon/locales/sv.json2++
Mapp/javascript/mastodon/locales/th.json2++
Mapp/javascript/mastodon/locales/tr.json2++
Mapp/javascript/mastodon/locales/uk.json2++
Mapp/javascript/mastodon/locales/zh-CN.json2++
Mapp/javascript/mastodon/locales/zh-HK.json2++
Mapp/javascript/mastodon/locales/zh-TW.json126++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mapp/javascript/mastodon/reducers/contexts.js38++++++++++++++++++++++----------------
Mapp/javascript/styles/mastodon/accounts.scss54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/javascript/styles/mastodon/admin.scss35+++++++++++++++++++++++++++++++++++
Mapp/javascript/styles/mastodon/components.scss41+++++++++++++++++++++++++++++++++++++++++
Mapp/javascript/styles/mastodon/forms.scss12++++++++++++
Mapp/javascript/styles/mastodon/stream_entries.scss14+++++++++++++-
Mapp/lib/activitypub/adapter.rb3+++
Mapp/lib/formatter.rb18+++++++++++++++---
Mapp/models/account.rb41+++++++++++++++++++++++++++++++++++++++--
Mapp/models/admin/action_log.rb5+++++
Mapp/models/concerns/status_threading_concern.rb24++++++++++++++++--------
Mapp/models/custom_emoji.rb4++++
Mapp/models/custom_emoji_filter.rb2+-
Mapp/models/media_attachment.rb5+++--
Mapp/models/notification.rb2--
Mapp/models/report.rb46++++++++++++++++++++++++++++++++++++++++++++++
Mapp/models/report_note.rb2+-
Mapp/models/status.rb2+-
Mapp/policies/user_policy.rb4++++
Mapp/serializers/activitypub/actor_serializer.rb17+++++++++++++++++
Mapp/serializers/rest/account_serializer.rb10++++++++++
Mapp/services/activitypub/process_account_service.rb19++++++++++++++-----
Mapp/services/post_status_service.rb2+-
Mapp/services/search_service.rb2+-
Mapp/validators/status_pin_validator.rb2+-
Mapp/views/accounts/_header.html.haml12++++++++++--
Mapp/views/admin/accounts/show.html.haml6+++++-
Aapp/views/admin/change_emails/show.html.haml7+++++++
Mapp/views/admin/report_notes/_report_note.html.haml12+++++-------
Mapp/views/admin/reports/show.html.haml47+++++++++++++++++++++++++++++++----------------
Mapp/views/settings/profiles/show.html.haml10++++++++++
Mapp/views/stream_entries/_status.html.haml4++++
Mapp/workers/activitypub/synchronize_featured_collection_worker.rb2+-
Mbin/bundle2+-
Mbin/setup3+--
Mbin/update6++++--
Mbin/webpack14++++++--------
Mbin/webpack-dev-server14++++++--------
Abin/yarn11+++++++++++
Mconfig/application.rb11+----------
Mconfig/boot.rb2+-
Mconfig/deploy.rb2+-
Mconfig/environments/development.rb3++-
Mconfig/environments/production.rb4++++
Mconfig/environments/test.rb13++++++++++++-
Aconfig/initializers/content_security_policy.rb20++++++++++++++++++++
Aconfig/initializers/cors.rb26++++++++++++++++++++++++++
Aconfig/initializers/rack_attack_logging.rb4++++
Mconfig/initializers/sidekiq.rb6++++--
Mconfig/locales/ar.yml4++++
Mconfig/locales/devise.fi.yml91+++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mconfig/locales/devise.sk.yml10+++++-----
Mconfig/locales/doorkeeper.fi.yml108++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Mconfig/locales/doorkeeper.sk.yml2+-
Mconfig/locales/en.yml25++++++++++++++++++++++---
Mconfig/locales/eo.yml6+++---
Mconfig/locales/es.yml13+++++++++++--
Mconfig/locales/fi.yml698+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mconfig/locales/ja.yml106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mconfig/locales/pl.yml19+++++++++++++++++++
Mconfig/locales/simple_form.en.yml6++++++
Mconfig/locales/simple_form.eo.yml4++--
Mconfig/locales/simple_form.fi.yml66+++++++++++++++++++++++++++++++++++-------------------------------
Mconfig/locales/simple_form.sk.yml16++++++++--------
Mconfig/locales/sk.yml27++++++++++++++-------------
Mconfig/routes.rb1+
Adb/migrate/20180410204633_add_fields_to_accounts.rb5+++++
Mdb/schema.rb4+++-
Mdocker-compose.yml4++--
Mpackage.json4++--
Aspec/controllers/admin/change_email_controller_spec.rb47+++++++++++++++++++++++++++++++++++++++++++++++
Mspec/controllers/auth/sessions_controller_spec.rb51+++++++++++++++++++++++++++++++++++++++++++++++++++
Mspec/controllers/stream_entries_controller_spec.rb10+++-------
Mspec/models/concerns/status_threading_concern_spec.rb38++++++++++++++++++++++++++++++--------
Mspec/models/custom_emoji_spec.rb24++++++++++++++++++++++++
Mspec/models/status_pin_spec.rb31+++++++++++++++++++++++++++++++
Mspec/services/activitypub/process_account_service_spec.rb28+++++++++++++++++++++++++++-
Mspec/views/stream_entries/show.html.haml_spec.rb2+-
140 files changed, 2480 insertions(+), 920 deletions(-)

diff --git a/.env.test b/.env.test @@ -1,3 +1,7 @@ # Federation LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_HTTPS=true +# test pam authentication +PAM_ENABLED=true +PAM_DEFAULT_SERVICE=pam_test +PAM_CONTROLLED_SERVICE=pam_test_controlled diff --git a/.travis.yml b/.travis.yml @@ -23,6 +23,7 @@ env: - RAILS_ENV=test - NOKOGIRI_USE_SYSTEM_LIBRARIES=true - PARALLEL_TEST_PROCESSORS=2 + - ALLOW_NOPAM=true addons: postgresql: 9.4 @@ -47,7 +48,7 @@ services: install: - nvm install - - bundle install --path=vendor/bundle --without development production --retry=3 --jobs=16 + - bundle install --path=vendor/bundle --with pam_authentication --without development production --retry=3 --jobs=16 - yarn install before_script: diff --git a/Gemfile b/Gemfile @@ -5,12 +5,12 @@ ruby '>= 2.3.0', '< 2.6.0' gem 'pkg-config', '~> 1.2' -gem 'puma', '~> 3.10' -gem 'rails', '~> 5.1.4' +gem 'puma', '~> 3.11' +gem 'rails', '~> 5.2.0' gem 'hamlit-rails', '~> 0.2' -gem 'pg', '~> 0.20' -gem 'pghero', '~> 1.7' +gem 'pg', '~> 1.0' +gem 'pghero', '~> 2.1' gem 'dotenv-rails', '~> 2.2' gem 'aws-sdk-s3', '~> 1.8', require: false @@ -23,17 +23,17 @@ gem 'streamio-ffmpeg', '~> 3.0' gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.5' -gem 'bootsnap' +gem 'bootsnap', '~> 1.3' gem 'browser' gem 'charlock_holmes', '~> 0.7.6' gem 'iso-639' gem 'chewy', '~> 5.0' gem 'cld3', '~> 3.2.0' gem 'devise', '~> 4.4' -gem 'devise-two-factor', '~> 3.0' +gem 'devise-two-factor', '~> 3.0', git: 'https://github.com/ykzts/devise-two-factor.git', branch: 'rails-5.2' group :pam_authentication, optional: true do - gem 'devise_pam_authenticatable2', '~> 9.0' + gem 'devise_pam_authenticatable2', '~> 9.1' end gem 'net-ldap', '~> 0.10' @@ -41,7 +41,7 @@ gem 'omniauth-cas', '~> 1.1' gem 'omniauth-saml', '~> 1.10' gem 'omniauth', '~> 1.2' -gem 'doorkeeper', '~> 4.2' +gem 'doorkeeper', '~> 4.3' gem 'fast_blank', '~> 1.0' gem 'fastimage' gem 'goldfinger', '~> 2.1' @@ -50,50 +50,50 @@ gem 'redis-namespace', '~> 1.5' gem 'htmlentities', '~> 4.3' gem 'http', '~> 3.0' gem 'http_accept_language', '~> 2.1' -gem 'httplog', '~> 0.99' +gem 'httplog', '~> 1.0' gem 'idn-ruby', require: 'idn' gem 'kaminari', '~> 1.1' gem 'link_header', '~> 0.0' gem 'mime-types', '~> 3.1' gem 'nokogiri', '~> 1.8' gem 'nsa', '~> 0.2' -gem 'oj', '~> 3.3' +gem 'oj', '~> 3.4' gem 'ostatus2', '~> 2.0' gem 'ox', '~> 2.8' gem 'pundit', '~> 1.1' gem 'premailer-rails' -gem 'rack-attack', '~> 5.0' -gem 'rack-cors', '~> 0.4', require: 'rack/cors' +gem 'rack-attack', '~> 5.2' +gem 'rack-cors', '~> 1.0', require: 'rack/cors' gem 'rack-timeout', '~> 0.4' -gem 'rails-i18n', '~> 5.0' +gem 'rails-i18n', '~> 5.1' gem 'rails-settings-cached', '~> 0.6' -gem 'redis', '~> 3.3', require: ['redis', 'redis/connection/hiredis'] +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.4' -gem 'sidekiq', '~> 5.0' -gem 'sidekiq-scheduler', '~> 2.1' +gem 'sanitize', '~> 4.6' +gem 'sidekiq', '~> 5.1' +gem 'sidekiq-scheduler', '~> 2.2' gem 'sidekiq-unique-jobs', '~> 5.0' gem 'sidekiq-bulk', '~>0.1.1' gem 'simple-navigation', '~> 4.0' -gem 'simple_form', '~> 3.4' +gem 'simple_form', '~> 4.0' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'stoplight', '~> 2.1.3' -gem 'strong_migrations' +gem 'strong_migrations', '~> 0.2' gem 'tty-command' gem 'tty-prompt' gem 'twitter-text', '~> 1.14' -gem 'tzinfo-data', '~> 1.2017' -gem 'webpacker', '~> 3.0' +gem 'tzinfo-data', '~> 1.2018' +gem 'webpacker', '~> 3.4' gem 'webpush' -gem 'json-ld-preloaded', '~> 2.2.1' -gem 'rdf-normalize', '~> 0.3.1' +gem 'json-ld-preloaded', '~> 2.2' +gem 'rdf-normalize', '~> 0.3' group :development, :test do - gem 'fabrication', '~> 2.18' + gem 'fabrication', '~> 2.20' gem 'fuubar', '~> 2.2' gem 'i18n-tasks', '~> 0.9', require: false gem 'pry-rails', '~> 0.3' @@ -105,15 +105,15 @@ group :production, :test do end group :test do - gem 'capybara', '~> 2.15' + gem 'capybara', '~> 2.18' gem 'climate_control', '~> 0.2' - gem 'faker', '~> 1.7' + gem 'faker', '~> 1.8' gem 'microformats', '~> 4.0' gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq', '~> 3.0' gem 'simplecov', '~> 0.14', require: false - gem 'webmock', '~> 3.0' - gem 'parallel_tests', '~> 2.17' + gem 'webmock', '~> 3.3' + gem 'parallel_tests', '~> 2.21' end group :development do @@ -121,12 +121,12 @@ group :development do gem 'annotate', '~> 2.7' gem 'better_errors', '~> 2.4' gem 'binding_of_caller', '~> 0.7' - gem 'bullet', '~> 5.5' + gem 'bullet', '~> 5.7' gem 'letter_opener', '~> 1.4' gem 'letter_opener_web', '~> 1.3' gem 'memory_profiler' gem 'rubocop', require: false - gem 'brakeman', '~> 4.0', require: false + gem 'brakeman', '~> 4.2', require: false gem 'bundler-audit', '~> 0.6', require: false gem 'scss_lint', '~> 0.55', require: false @@ -137,6 +137,6 @@ group :development do end group :production do - gem 'lograge', '~> 0.7' + gem 'lograge', '~> 0.9' gem 'redis-rails', '~> 5.0' end diff --git a/Gemfile.lock b/Gemfile.lock @@ -1,25 +1,37 @@ +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: - actioncable (5.1.4) - actionpack (= 5.1.4) + actioncable (5.2.0) + actionpack (= 5.2.0) nio4r (~> 2.0) - websocket-driver (~> 0.6.1) - actionmailer (5.1.4) - actionpack (= 5.1.4) - actionview (= 5.1.4) - activejob (= 5.1.4) + websocket-driver (>= 0.6.1) + actionmailer (5.2.0) + actionpack (= 5.2.0) + actionview (= 5.2.0) + activejob (= 5.2.0) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.1.4) - actionview (= 5.1.4) - activesupport (= 5.1.4) + actionpack (5.2.0) + actionview (= 5.2.0) + activesupport (= 5.2.0) rack (~> 2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.1.4) - activesupport (= 5.1.4) + actionview (5.2.0) + activesupport (= 5.2.0) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -30,18 +42,22 @@ GEM case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) active_record_query_trace (1.5.4) - activejob (5.1.4) - activesupport (= 5.1.4) + activejob (5.2.0) + activesupport (= 5.2.0) globalid (>= 0.3.6) - activemodel (5.1.4) - activesupport (= 5.1.4) - activerecord (5.1.4) - activemodel (= 5.1.4) - activesupport (= 5.1.4) - arel (~> 8.0) - activesupport (5.1.4) + activemodel (5.2.0) + activesupport (= 5.2.0) + activerecord (5.2.0) + activemodel (= 5.2.0) + activesupport (= 5.2.0) + arel (>= 9.0) + activestorage (5.2.0) + actionpack (= 5.2.0) + activerecord (= 5.2.0) + marcel (~> 0.3.1) + activesupport (5.2.0) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (~> 0.7) + i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) addressable (2.5.2) @@ -51,9 +67,9 @@ GEM annotate (2.7.2) activerecord (>= 3.2, < 6.0) rake (>= 10.4, < 13.0) - arel (8.0.0) - ast (2.3.0) - attr_encrypted (3.0.3) + arel (9.0.0) + ast (2.4.0) + attr_encrypted (3.1.0) encryptor (~> 3.0.0) av (0.9.0) cocaine (~> 0.5.3) @@ -77,18 +93,18 @@ GEM rack (>= 0.9.0) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - bootsnap (1.1.5) + bootsnap (1.3.0) msgpack (~> 1.0) - brakeman (4.0.1) + brakeman (4.2.1) browser (2.5.2) builder (3.2.3) - bullet (5.6.1) + bullet (5.7.5) activesupport (>= 3.0.0) - uniform_notifier (~> 1.10.0) + uniform_notifier (~> 1.11.0) bundler-audit (0.6.0) bundler (~> 1.2) thor (~> 0.18) - capistrano (3.10.0) + capistrano (3.10.1) airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) @@ -104,13 +120,13 @@ GEM sshkit (~> 1.3) capistrano-yarn (2.0.2) capistrano (~> 3.0) - capybara (2.16.1) + capybara (2.18.0) addressable mini_mime (>= 0.1.3) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) - xpath (~> 2.0) + xpath (>= 2.0, < 4.0) case_transform (0.2) activesupport charlock_holmes (0.7.6) @@ -118,7 +134,7 @@ GEM activesupport (>= 4.0) elasticsearch (>= 2.0.0) elasticsearch-dsl - chunky_png (1.3.8) + chunky_png (1.3.10) cld3 (3.2.2) ffi (>= 1.1.0, < 1.10.0) climate_control (0.2.0) @@ -130,37 +146,30 @@ GEM connection_pool (2.2.1) crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.3) + crass (1.0.4) css_parser (1.6.0) addressable debug_inspector (0.0.3) - devise (4.4.0) + devise (4.4.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) - railties (>= 4.1.0, < 5.2) + railties (>= 4.1.0, < 6.0) responders warden (~> 1.2.3) - devise-two-factor (3.0.2) - activesupport (< 5.2) - attr_encrypted (>= 1.3, < 4, != 2) - devise (~> 4.0) - railties (< 5.2) - rotp (~> 2.0) - devise_pam_authenticatable2 (9.0.0) + devise_pam_authenticatable2 (9.1.0) devise (>= 4.0.0) - rpam2 (~> 3.0) + rpam2 (~> 4.0) diff-lcs (1.3) docile (1.1.5) domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) - doorkeeper (4.2.6) + doorkeeper (4.3.2) railties (>= 4.2) - dotenv (2.2.1) - dotenv-rails (2.2.1) - dotenv (= 2.2.1) - railties (>= 3.2, < 5.2) - easy_translate (0.5.0) - json + dotenv (2.2.2) + dotenv-rails (2.2.2) + dotenv (= 2.2.2) + railties (>= 3.2, < 6.0) + easy_translate (0.5.1) thread thread_safe elasticsearch (6.0.1) @@ -174,18 +183,18 @@ GEM multi_json encryptor (3.0.0) equatable (0.5.0) - erubi (1.7.0) - et-orbi (1.0.8) + erubi (1.7.1) + et-orbi (1.0.9) tzinfo - excon (0.59.0) - fabrication (2.18.0) - faker (1.8.4) - i18n (~> 0.5) + excon (0.60.0) + fabrication (2.20.1) + faker (1.8.7) + i18n (>= 0.7) faraday (0.14.0) multipart-post (>= 1.2, < 3) fast_blank (1.0.0) fastimage (2.1.1) - ffi (1.9.18) + ffi (1.9.21) fog-core (1.45.0) builder excon (~> 0.58) @@ -195,12 +204,12 @@ GEM multi_json (~> 1.10) fog-local (0.4.0) fog-core (~> 1.27) - fog-openstack (0.1.22) - fog-core (>= 1.40) + fog-openstack (0.1.23) + fog-core (~> 1.40) fog-json (>= 1.0) ipaddress (>= 0.8) formatador (0.2.5) - fuubar (2.2.0) + fuubar (2.3.1) rspec-core (~> 3.0) ruby-progressbar (~> 1.4) globalid (0.4.1) @@ -210,7 +219,7 @@ GEM http (~> 3.0) nokogiri (~> 1.8) oj (~> 3.0) - hamlit (2.8.5) + hamlit (2.8.8) temple (>= 0.8.0) thor tilt @@ -238,33 +247,33 @@ GEM http-form_data (2.0.0) http_accept_language (2.1.1) http_parser.rb (0.6.0) - httplog (0.99.7) - colorize - rack - i18n (0.9.5) + httplog (1.0.2) + colorize (~> 0.8) + rack (>= 1.0) + i18n (1.0.0) concurrent-ruby (~> 1.0) - i18n-tasks (0.9.19) + i18n-tasks (0.9.21) activesupport (>= 4.0.2) ast (>= 2.1.0) - easy_translate (>= 0.5.0) + easy_translate (>= 0.5.1) erubi highline (>= 1.7.3) i18n parser (>= 2.2.3.0) - rainbow (~> 2.2) + rainbow (>= 2.2.2, < 4.0) terminal-table (>= 1.5.1) idn-ruby (0.1.0) ipaddress (0.8.3) iso-639 (0.2.8) jmespath (1.3.1) json (2.1.0) - json-ld (2.1.7) + 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, >= 2.2.8) - json-ld-preloaded (2.2.2) - json-ld (~> 2.1, >= 2.1.5) - multi_json (~> 1.11) - rdf (~> 2.2) + rdf (>= 2.2, < 4.0) jsonapi-renderer (0.2.0) jwt (2.1.0) kaminari (1.1.1) @@ -281,25 +290,27 @@ GEM kaminari-core (1.1.1) launchy (2.4.3) addressable (~> 2.3) - letter_opener (1.4.1) + letter_opener (1.6.0) launchy (~> 2.2) - letter_opener_web (1.3.1) + letter_opener_web (1.3.4) actionmailer (>= 3.2) letter_opener (~> 1.0) railties (>= 3.2) link_header (0.0.8) - lograge (0.7.1) - actionpack (>= 4, < 5.2) - activesupport (>= 4, < 5.2) - railties (>= 4, < 5.2) + lograge (0.9.0) + actionpack (>= 4) + activesupport (>= 4) + railties (>= 4) request_store (~> 1.0) - loofah (2.2.1) + loofah (2.2.2) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.0) mini_mime (>= 0.1.1) - mario-redis-lock (1.2.0) - redis (~> 3, >= 3.0.5) + marcel (0.3.2) + mimemagic (~> 0.3.2) + mario-redis-lock (1.2.1) + redis (>= 3.0.5) memory_profiler (0.9.10) method_source (0.9.0) microformats (4.0.7) @@ -312,15 +323,15 @@ GEM mini_mime (1.0.0) mini_portile2 (2.3.0) minitest (5.11.3) - msgpack (1.1.0) - multi_json (1.12.2) + msgpack (1.2.4) + multi_json (1.13.1) multipart-post (2.0.0) necromancer (0.4.0) net-ldap (0.16.1) net-scp (1.2.1) net-ssh (>= 2.6.5) net-ssh (4.2.0) - nio4r (2.1.0) + nio4r (2.3.0) nokogiri (1.8.2) mini_portile2 (~> 2.3.0) nokogumbo (1.5.0) @@ -330,7 +341,7 @@ GEM concurrent-ruby (~> 1.0.0) sidekiq (>= 3.5.0) statsd-ruby (~> 1.2.0) - oj (3.3.10) + oj (3.4.0) omniauth (1.8.1) hashie (>= 3.4.6, < 3.6.0) rack (>= 1.6.2, < 3) @@ -356,24 +367,24 @@ GEM paperclip-av-transcoder (0.6.4) av (~> 0.9.0) paperclip (>= 2.5.2) - parallel (1.12.0) - parallel_tests (2.19.0) + parallel (1.12.1) + parallel_tests (2.21.1) parallel - parser (2.4.0.2) - ast (~> 2.3) + parser (2.5.1.0) + ast (~> 2.4.0) pastel (0.7.2) equatable (~> 0.5.0) tty-color (~> 0.4.0) - pg (0.21.0) - pghero (1.7.0) + pg (1.0.0) + pghero (2.1.0) activerecord - pkg-config (1.2.8) + pkg-config (1.2.9) powerpack (0.1.1) premailer (1.11.1) addressable css_parser (>= 1.6.0) htmlentities (>= 4.0.0) - premailer-rails (1.10.1) + premailer-rails (1.10.2) actionmailer (>= 3, < 6) premailer (~> 1.7, >= 1.7.9) private_address_check (0.4.1) @@ -382,32 +393,33 @@ GEM method_source (~> 0.9.0) pry-rails (0.3.6) pry (>= 0.10.4) - public_suffix (3.0.1) - puma (3.11.0) + public_suffix (3.0.2) + puma (3.11.3) pundit (1.1.0) activesupport (>= 3.0.0) - rack (2.0.3) - rack-attack (5.0.1) + rack (2.0.4) + rack-attack (5.2.0) rack - rack-cors (0.4.1) - rack-protection (2.0.0) + rack-cors (1.0.2) + rack-protection (2.0.1) rack - rack-proxy (0.6.2) + rack-proxy (0.6.4) rack - rack-test (0.8.2) + rack-test (1.0.0) rack (>= 1.0, < 3) rack-timeout (0.4.2) - rails (5.1.4) - actioncable (= 5.1.4) - actionmailer (= 5.1.4) - actionpack (= 5.1.4) - actionview (= 5.1.4) - activejob (= 5.1.4) - activemodel (= 5.1.4) - activerecord (= 5.1.4) - activesupport (= 5.1.4) + rails (5.2.0) + actioncable (= 5.2.0) + actionmailer (= 5.2.0) + actionpack (= 5.2.0) + actionview (= 5.2.0) + activejob (= 5.2.0) + activemodel (= 5.2.0) + activerecord (= 5.2.0) + activestorage (= 5.2.0) + activesupport (= 5.2.0) bundler (>= 1.3.0) - railties (= 5.1.4) + railties (= 5.2.0) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.2) actionpack (~> 5.x, >= 5.0.1) @@ -416,31 +428,30 @@ GEM rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) - rails-i18n (5.0.4) - i18n (~> 0.7) - railties (~> 5.0) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + rails-i18n (5.1.1) + i18n (>= 0.7, < 2) + railties (>= 5.0, < 6) rails-settings-cached (0.6.6) rails (>= 4.2.0) - railties (5.1.4) - actionpack (= 5.1.4) - activesupport (= 5.1.4) + railties (5.2.0) + actionpack (= 5.2.0) + activesupport (= 5.2.0) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rainbow (2.2.2) - rake - rake (12.3.0) + rainbow (3.0.0) + rake (12.3.1) rb-fsevent (0.10.2) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) - rdf (2.2.12) + rdf (3.0.1) hamster (~> 3.0) link_header (~> 0.0, >= 0.0.8) - rdf-normalize (0.3.2) - rdf (~> 2.0) - redis (3.3.5) + rdf-normalize (0.3.3) + rdf (>= 2.2, < 4.0) + redis (4.0.1) redis-actionpack (5.0.2) actionpack (>= 4.0, < 6) redis-rack (>= 1, < 3) @@ -450,7 +461,7 @@ GEM redis-store (>= 1.3, < 2) redis-namespace (1.6.0) redis (>= 3.0.4) - redis-rack (2.0.3) + redis-rack (2.0.4) rack (>= 1.5, < 3) redis-store (>= 1.2, < 2) redis-rails (5.0.2) @@ -459,15 +470,16 @@ GEM redis-store (>= 1.2, < 2) redis-store (1.4.1) redis (>= 2.2, < 5) - request_store (1.3.2) + request_store (1.4.0) + rack (>= 1.4) responders (2.4.0) actionpack (>= 4.2.0, < 5.3) railties (>= 4.2.0, < 5.3) rotp (2.1.2) - rpam2 (3.1.0) + rpam2 (4.0.2) rqrcode (0.10.1) chunky_png (~> 1.0) - rspec-core (3.7.0) + rspec-core (3.7.1) rspec-support (~> 3.7.0) rspec-expectations (3.7.0) diff-lcs (>= 1.2.0, < 2.0) @@ -486,12 +498,12 @@ GEM rspec-sidekiq (3.0.3) rspec-core (~> 3.0, >= 3.0.0) sidekiq (>= 2.4.0) - rspec-support (3.7.0) - rubocop (0.51.0) + rspec-support (3.7.1) + rubocop (0.52.1) parallel (~> 1.10) - parser (>= 2.3.3.1, < 3.0) + parser (>= 2.4.0.2, < 3.0) powerpack (~> 0.1) - rainbow (>= 2.2.2, < 3.0) + rainbow (>= 2.2.2, < 4.0) ruby-progressbar (~> 1.7) unicode-display_width (~> 1.0, >= 1.0.1) ruby-oembed (0.12.0) @@ -505,7 +517,7 @@ GEM crass (~> 1.0.2) nokogiri (>= 1.4.4) nokogumbo (~> 1.4) - sass (3.5.3) + sass (3.5.5) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) @@ -513,15 +525,15 @@ GEM scss_lint (0.56.0) rake (>= 0.9, < 13) sass (~> 3.5.3) - sidekiq (5.0.5) + sidekiq (5.1.3) concurrent-ruby (~> 1.0) connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) - redis (>= 3.3.4, < 5) + redis (>= 3.3.5, < 5) sidekiq-bulk (0.1.1) activesupport sidekiq - sidekiq-scheduler (2.1.10) + sidekiq-scheduler (2.2.1) redis (>= 3, < 5) rufus-scheduler (~> 3.2) sidekiq (>= 3) @@ -531,9 +543,9 @@ GEM thor (~> 0) simple-navigation (4.0.5) activesupport (>= 2.3.2) - simple_form (3.5.0) - actionpack (> 4, < 5.2) - activemodel (> 4, < 5.2) + simple_form (4.0.0) + actionpack (> 4) + activemodel (> 4) simplecov (0.15.1) docile (~> 1.1.0) json (>= 1.8, < 3) @@ -546,14 +558,14 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.15.1) + sshkit (1.16.0) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) statsd-ruby (1.2.1) stoplight (2.1.3) streamio-ffmpeg (3.0.2) multi_json (~> 1.8) - strong_migrations (0.1.9) + strong_migrations (0.2.2) activerecord (>= 3.2.0) temple (0.8.0) terminal-table (1.8.0) @@ -585,32 +597,32 @@ GEM unf (~> 0.1.0) tzinfo (1.2.5) thread_safe (~> 0.1) - tzinfo-data (1.2017.3) + tzinfo-data (1.2018.4) tzinfo (>= 1.0.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.4) + unf_ext (0.0.7.5) unicode-display_width (1.3.0) - uniform_notifier (1.10.0) + uniform_notifier (1.11.0) warden (1.2.7) rack (>= 1.0) - webmock (3.1.1) + webmock (3.3.0) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff - webpacker (3.0.2) + webpacker (3.4.3) activesupport (>= 4.2) rack-proxy (>= 0.6.1) railties (>= 4.2) webpush (0.3.3) hkdf (~> 0.2) jwt (~> 2.0) - websocket-driver (0.6.5) + websocket-driver (0.7.0) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) wisper (2.0.0) - xpath (2.1.0) - nokogiri (~> 1.3) + xpath (3.0.0) + nokogiri (~> 1.8) PLATFORMS ruby @@ -623,27 +635,27 @@ DEPENDENCIES aws-sdk-s3 (~> 1.8) better_errors (~> 2.4) binding_of_caller (~> 0.7) - bootsnap - brakeman (~> 4.0) + bootsnap (~> 1.3) + brakeman (~> 4.2) browser - bullet (~> 5.5) + bullet (~> 5.7) bundler-audit (~> 0.6) capistrano (~> 3.10) capistrano-rails (~> 1.3) capistrano-rbenv (~> 2.1) capistrano-yarn (~> 2.0) - capybara (~> 2.15) + capybara (~> 2.18) charlock_holmes (~> 0.7.6) chewy (~> 5.0) cld3 (~> 3.2.0) climate_control (~> 0.2) devise (~> 4.4) - devise-two-factor (~> 3.0) - devise_pam_authenticatable2 (~> 9.0) - doorkeeper (~> 4.2) + devise-two-factor (~> 3.0)! + devise_pam_authenticatable2 (~> 9.1) + doorkeeper (~> 4.3) dotenv-rails (~> 2.2) - fabrication (~> 2.18) - faker (~> 1.7) + fabrication (~> 2.20) + faker (~> 1.8) fast_blank (~> 1.0) fastimage fog-core (~> 1.45) @@ -656,16 +668,16 @@ DEPENDENCIES htmlentities (~> 4.3) http (~> 3.0) http_accept_language (~> 2.1) - httplog (~> 0.99) + httplog (~> 1.0) i18n-tasks (~> 0.9) idn-ruby iso-639 - json-ld-preloaded (~> 2.2.1) + json-ld-preloaded (~> 2.2) kaminari (~> 1.1) letter_opener (~> 1.4) letter_opener_web (~> 1.3) link_header (~> 0.0) - lograge (~> 0.7) + lograge (~> 0.9) mario-redis-lock (~> 1.2) memory_profiler microformats (~> 4.0) @@ -673,7 +685,7 @@ DEPENDENCIES net-ldap (~> 0.10) nokogiri (~> 1.8) nsa (~> 0.2) - oj (~> 3.3) + oj (~> 3.4) omniauth (~> 1.2) omniauth-cas (~> 1.1) omniauth-saml (~> 1.10) @@ -681,24 +693,24 @@ DEPENDENCIES ox (~> 2.8) paperclip (~> 6.0) paperclip-av-transcoder (~> 0.6) - parallel_tests (~> 2.17) - pg (~> 0.20) - pghero (~> 1.7) + parallel_tests (~> 2.21) + pg (~> 1.0) + pghero (~> 2.1) pkg-config (~> 1.2) premailer-rails private_address_check (~> 0.4.1) pry-rails (~> 0.3) - puma (~> 3.10) + puma (~> 3.11) pundit (~> 1.1) - rack-attack (~> 5.0) - rack-cors (~> 0.4) + rack-attack (~> 5.2) + rack-cors (~> 1.0) rack-timeout (~> 0.4) - rails (~> 5.1.4) + rails (~> 5.2.0) rails-controller-testing (~> 1.0) - rails-i18n (~> 5.0) + rails-i18n (~> 5.1) rails-settings-cached (~> 0.6) - rdf-normalize (~> 0.3.1) - redis (~> 3.3) + rdf-normalize (~> 0.3) + redis (~> 4.0) redis-namespace (~> 1.5) redis-rails (~> 5.0) rqrcode (~> 0.10) @@ -707,25 +719,25 @@ DEPENDENCIES rubocop ruby-oembed (~> 0.12) ruby-progressbar (~> 1.4) - sanitize (~> 4.6.4) + sanitize (~> 4.6) scss_lint (~> 0.55) - sidekiq (~> 5.0) + sidekiq (~> 5.1) sidekiq-bulk (~> 0.1.1) - sidekiq-scheduler (~> 2.1) + sidekiq-scheduler (~> 2.2) sidekiq-unique-jobs (~> 5.0) simple-navigation (~> 4.0) - simple_form (~> 3.4) + simple_form (~> 4.0) simplecov (~> 0.14) sprockets-rails (~> 3.2) stoplight (~> 2.1.3) streamio-ffmpeg (~> 3.0) - strong_migrations + strong_migrations (~> 0.2) tty-command tty-prompt twitter-text (~> 1.14) - tzinfo-data (~> 1.2017) - webmock (~> 3.0) - webpacker (~> 3.0) + tzinfo-data (~> 1.2018) + webmock (~> 3.3) + webpacker (~> 3.4) webpush RUBY VERSION diff --git a/app/controllers/admin/change_emails_controller.rb b/app/controllers/admin/change_emails_controller.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Admin + class ChangeEmailsController < BaseController + before_action :set_account + before_action :require_local_account! + + def show + authorize @user, :change_email? + end + + def update + authorize @user, :change_email? + + new_email = resource_params.fetch(:unconfirmed_email) + + if new_email != @user.email + @user.update!( + unconfirmed_email: new_email, + # Regenerate the confirmation token: + confirmation_token: nil + ) + + log_action :change_email, @user + + @user.send_confirmation_instructions + end + + redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.change_email.changed_msg') + end + + private + + def set_account + @account = Account.find(params[:account_id]) + @user = @account.user + end + + def require_local_account! + redirect_to admin_account_path(@account.id) unless @account.local? && @account.user.present? + end + + def resource_params + params.require(:user).permit( + :unconfirmed_email + ) + end + end +end diff --git a/app/controllers/admin/report_notes_controller.rb b/app/controllers/admin/report_notes_controller.rb @@ -8,19 +8,26 @@ module Admin authorize ReportNote, :create? @report_note = current_account.report_notes.new(resource_params) + @report = @report_note.report if @report_note.save if params[:create_and_resolve] - @report_note.report.update!(action_taken: true, action_taken_by_account_id: current_account.id) - log_action :resolve, @report_note.report + @report.resolve!(current_account) + log_action :resolve, @report redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg') - else - redirect_to admin_report_path(@report_note.report_id), notice: I18n.t('admin.report_notes.created_msg') + return end + + if params[:create_and_unresolve] + @report.unresolve! + log_action :reopen, @report + end + + redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg') else - @report = @report_note.report @report_notes = @report.notes.latest + @report_history = @report.history @form = Form::StatusBatch.new render template: 'admin/reports/show' diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb @@ -13,6 +13,7 @@ module Admin authorize @report, :show? @report_note = @report.notes.new @report_notes = @report.notes.latest + @report_history = @report.history @form = Form::StatusBatch.new end @@ -38,36 +39,33 @@ module Admin @report.update!(assigned_account_id: nil) log_action :unassigned, @report when 'reopen' - @report.update!(action_taken: false, action_taken_by_account_id: nil) + @report.unresolve! log_action :reopen, @report when 'resolve' - @report.update!(action_taken_by_current_attributes) + @report.resolve!(current_account) log_action :resolve, @report when 'suspend' Admin::SuspensionWorker.perform_async(@report.target_account.id) + log_action :resolve, @report log_action :suspend, @report.target_account + resolve_all_target_account_reports - @report.reload when 'silence' @report.target_account.update!(silenced: true) + log_action :resolve, @report log_action :silence, @report.target_account + resolve_all_target_account_reports - @report.reload else raise ActiveRecord::RecordNotFound end - end - - def action_taken_by_current_attributes - { action_taken: true, action_taken_by_account_id: current_account.id } + @report.reload end def resolve_all_target_account_reports - unresolved_reports_for_target_account.update_all( - action_taken_by_current_attributes - ) + unresolved_reports_for_target_account.update_all(action_taken: true, action_taken_by_account_id: current_account.id) end def unresolved_reports_for_target_account diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb @@ -17,7 +17,7 @@ class Api::V1::StatusesController < Api::BaseController end def context - ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(current_account) + ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(DEFAULT_STATUSES_LIMIT, current_account) descendants_results = @status.descendants(current_account) loaded_ancestors = cache_collection(ancestors_results, Status) loaded_descendants = cache_collection(descendants_results, Status) diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb @@ -7,9 +7,6 @@ class Api::Web::PushSubscriptionsController < Api::BaseController protect_from_forgery with: :exception def create - params.require(:subscription).require(:endpoint) - params.require(:subscription).require(:keys).require([:auth, :p256dh]) - active_session = current_session unless active_session.web_push_subscription.nil? @@ -29,12 +26,12 @@ class Api::Web::PushSubscriptionsController < Api::BaseController }, } - data.deep_merge!(params[:data]) if params[:data] + data.deep_merge!(data_params) if params[:data] web_subscription = ::Web::PushSubscription.create!( - endpoint: params[:subscription][:endpoint], - key_p256dh: params[:subscription][:keys][:p256dh], - key_auth: params[:subscription][:keys][:auth], + endpoint: subscription_params[:endpoint], + key_p256dh: subscription_params[:keys][:p256dh], + key_auth: subscription_params[:keys][:auth], data: data ) @@ -44,12 +41,22 @@ class Api::Web::PushSubscriptionsController < Api::BaseController end def update - params.require([:id, :data]) + params.require([:id]) web_subscription = ::Web::PushSubscription.find(params[:id]) - web_subscription.update!(data: params[:data]) + web_subscription.update!(data: data_params) render json: web_subscription.as_payload end + + private + + def subscription_params + @subscription_params ||= params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh]) + end + + def data_params + @data_params ||= params.require(:data).permit(:alerts) + end end diff --git a/app/controllers/settings/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb @@ -9,7 +9,7 @@ class Settings::FollowerDomainsController < ApplicationController def show @account = current_account - @domains = current_account.followers.reorder('MIN(follows.id) DESC').group('accounts.domain').select('accounts.domain, count(accounts.id) as accounts_from_domain').page(params[:page]).per(10) + @domains = current_account.followers.reorder(Arel.sql('MIN(follows.id) DESC')).group('accounts.domain').select('accounts.domain, count(accounts.id) as accounts_from_domain').page(params[:page]).per(10) end def update diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb @@ -11,7 +11,9 @@ class Settings::ProfilesController < ApplicationController obfuscate_filename [:account, :avatar] obfuscate_filename [:account, :header] - def show; end + def show + @account.build_fields + end def update if UpdateAccountService.new.call(@account, account_params) @@ -25,7 +27,7 @@ class Settings::ProfilesController < ApplicationController private def account_params - params.require(:account).permit(:display_name, :note, :avatar, :header, :locked) + params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, fields_attributes: [:name, :value]) end def set_account diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb @@ -4,6 +4,8 @@ class StatusesController < ApplicationController include SignatureAuthentication include Authorization + ANCESTORS_LIMIT = 20 + layout 'public' before_action :set_account @@ -16,8 +18,9 @@ class StatusesController < ApplicationController def show respond_to do |format| format.html do - @ancestors = @status.reply? ? cache_collection(@status.ancestors(current_account), Status) : [] - @descendants = cache_collection(@status.descendants(current_account), Status) + @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) render 'stream_entries/show' end diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb @@ -15,8 +15,7 @@ class StreamEntriesController < ApplicationController def show respond_to do |format| format.html do - @ancestors = @stream_entry.activity.reply? ? cache_collection(@stream_entry.activity.ancestors(current_account), Status) : [] - @descendants = cache_collection(@stream_entry.activity.descendants(current_account), Status) + redirect_to short_account_status_url(params[:account_username], @stream_entry.activity) if @type == 'status' end format.atom do diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb @@ -45,6 +45,8 @@ module Admin::ActionLogsHelper log.recorded_changes.slice('domain', 'visible_in_picker') elsif log.target_type == 'User' && [:promote, :demote].include?(log.action) log.recorded_changes.slice('moderator', 'admin') + elsif log.target_type == 'User' && [:change_email].include?(log.action) + log.recorded_changes.slice('email', 'unconfirmed_email') elsif log.target_type == 'DomainBlock' log.recorded_changes.slice('severity', 'reject_media') elsif log.target_type == 'Status' && log.action == :update @@ -84,7 +86,7 @@ module Admin::ActionLogsHelper 'positive' when :create opposite_verbs?(log) ? 'negative' : 'positive' - when :update, :reset_password, :disable_2fa, :memorialize + when :update, :reset_password, :disable_2fa, :memorialize, :change_email 'neutral' when :demote, :silence, :disable, :suspend, :remove_avatar, :reopen 'negative' diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js @@ -10,6 +10,14 @@ export function normalizeAccount(account) { account.display_name_html = emojify(escapeTextContentForBrowser(displayName)); account.note_emojified = emojify(account.note); + if (account.fields) { + account.fields = account.fields.map(pair => ({ + ...pair, + name_emojified: emojify(escapeTextContentForBrowser(pair.name)), + value_emojified: emojify(pair.value), + })); + } + if (account.moved) { account.moved = account.moved.id; } diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js @@ -9,7 +9,8 @@ import { } from './importer'; import { defineMessages } from 'react-intl'; -export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; +export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; +export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP'; export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; @@ -39,21 +40,30 @@ const unescapeHTML = (html) => { export function updateNotifications(notification, intlMessages, intlLocale) { return (dispatch, getState) => { - const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); - const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); + const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true); + const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); + const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); - dispatch(importFetchedAccount(notification.account)); - if (notification.status) { - dispatch(importFetchedStatus(notification.status)); - } + if (showInColumn) { + dispatch(importFetchedAccount(notification.account)); - dispatch({ - type: NOTIFICATIONS_UPDATE, - notification, - meta: playSound ? { sound: 'boop' } : undefined, - }); + if (notification.status) { + dispatch(importFetchedStatus(notification.status)); + } + + dispatch({ + type: NOTIFICATIONS_UPDATE, + notification, + meta: playSound ? { sound: 'boop' } : undefined, + }); - fetchRelatedRelationships(dispatch, [notification]); + fetchRelatedRelationships(dispatch, [notification]); + } else if (playSound) { + dispatch({ + type: NOTIFICATIONS_UPDATE_NOOP, + meta: { sound: 'boop' }, + }); + } // Desktop notifications if (typeof window.Notification !== 'undefined' && showAlert) { @@ -61,6 +71,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) { const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : ''); const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id }); + notify.addEventListener('click', () => { window.focus(); notify.close(); diff --git a/app/javascript/mastodon/actions/tos.js b/app/javascript/mastodon/actions/tos.js @@ -0,0 +1,37 @@ +import api from '../api'; + +export const TOS_FETCH_REQUEST = 'TOS_FETCH_REQUEST'; +export const TOS_FETCH_SUCCESS = 'TOS_FETCH_SUCCESS'; +export const TOS_FETCH_FAIL = 'TOS_FETCH_FAIL'; + +export function fetchTOS() { + return (dispatch, getState) => { + dispatch(fetchTOSRequest()); + + api(getState).get('/static/terms-of-service.html').then(response => { + dispatch(fetchTOSSuccess(response)); + }).catch(error => { + dispatch(fetchTOSFail(error)); + }); + }; +}; + +export function fetchTOSRequest() { + return { + type: TOS_FETCH_REQUEST, + }; +}; + +export function fetchTOSSuccess(data) { + return { + type: TOS_FETCH_SUCCESS, + data, + }; +}; + +export function fetchTOSFail(error) { + return { + type: TOS_FETCH_FAIL, + error, + }; +}; diff --git a/app/javascript/mastodon/components/load_gap.js b/app/javascript/mastodon/components/load_gap.js @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, defineMessages } from 'react-intl'; + +const messages = defineMessages({ + load_more: { id: 'status.load_more', defaultMessage: 'Load more' }, +}); + +@injectIntl +export default class LoadGap extends React.PureComponent { + + static propTypes = { + disabled: PropTypes.bool, + maxId: PropTypes.string, + onClick: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + handleClick = () => { + this.props.onClick(this.props.maxId); + } + + render () { + const { disabled, intl } = this.props; + + return ( + <button className='load-more load-gap' disabled={disabled} onClick={this.handleClick} aria-label={intl.formatMessage(messages.load_more)}> + <i className='fa fa-ellipsis-h' /> + </button> + ); + } + +} diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js @@ -34,7 +34,7 @@ export default class ScrollableList extends PureComponent { }; state = { - lastMouseMove: null, + fullscreen: null, }; intersectionObserverWrapper = new IntersectionObserverWrapper(); @@ -43,7 +43,6 @@ export default class ScrollableList extends PureComponent { if (this.node) { const { scrollTop, scrollHeight, clientHeight } = this.node; const offset = scrollHeight - scrollTop - clientHeight; - this._oldScrollPosition = scrollHeight - scrollTop; if (400 > offset && this.props.onLoadMore && !this.props.isLoading) { this.props.onLoadMore(); @@ -59,14 +58,6 @@ export default class ScrollableList extends PureComponent { trailing: true, }); - handleMouseMove = throttle(() => { - this._lastMouseMove = new Date(); - }, 300); - - handleMouseLeave = () => { - this._lastMouseMove = null; - } - componentDidMount () { this.attachScrollListener(); this.attachIntersectionObserver(); @@ -76,21 +67,26 @@ export default class ScrollableList extends PureComponent { this.handleScroll(); } - componentDidUpdate (prevProps) { + getSnapshotBeforeUpdate (prevProps) { 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) { + return this.node.scrollHeight - this.node.scrollTop; + } else { + return null; + } + } + componentDidUpdate (prevProps, prevState, snapshot) { // Reset the scroll position when a new child comes in in order not to // jerk the scrollbar around if you're already scrolled down the page. - if (someItemInserted && this._oldScrollPosition && this.node.scrollTop > 0) { - const newScrollTop = this.node.scrollHeight - this._oldScrollPosition; + if (snapshot !== null) { + const newScrollTop = this.node.scrollHeight - snapshot; if (this.node.scrollTop !== newScrollTop) { this.node.scrollTop = newScrollTop; } - } else { - this._oldScrollPosition = this.node.scrollHeight - this.node.scrollTop; } } @@ -143,10 +139,6 @@ export default class ScrollableList extends PureComponent { this.props.onLoadMore(); } - _recentlyMoved () { - return this._lastMouseMove !== null && ((new Date()) - this._lastMouseMove < 600); - } - render () { const { children, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage, onLoadMore } = this.props; const { fullscreen } = this.state; @@ -157,7 +149,7 @@ export default class ScrollableList extends PureComponent { if (isLoading || childrenCount > 0 || !emptyMessage) { scrollableArea = ( - <div className={classNames('scrollable', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove} onMouseLeave={this.handleMouseLeave}> + <div className={classNames('scrollable', { fullscreen })} ref={this.setRef}> <div role='feed' className='item-list'> {prepend} diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js @@ -4,28 +4,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import StatusContainer from '../containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import LoadMore from './load_more'; +import LoadGap from './load_gap'; import ScrollableList from './scrollable_list'; import { FormattedMessage } from 'react-intl'; -class LoadGap extends ImmutablePureComponent { - - static propTypes = { - disabled: PropTypes.bool, - maxId: PropTypes.string, - onClick: PropTypes.func.isRequired, - }; - - handleClick = () => { - this.props.onClick(this.props.maxId); - } - - render () { - return <LoadMore onClick={this.handleClick} disabled={this.props.disabled} />; - } - -} - export default class StatusList extends ImmutablePureComponent { static propTypes = { diff --git a/app/javascript/mastodon/features/account/components/action_bar.js b/app/javascript/mastodon/features/account/components/action_bar.js @@ -31,7 +31,6 @@ export default class ActionBar extends React.PureComponent { onMention: PropTypes.func.isRequired, onDirect: PropTypes.func.isRequired, onReblogToggle: PropTypes.func.isRequired, - onReport: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; @@ -72,8 +71,6 @@ export default class ActionBar extends React.PureComponent { } else { menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock }); } - - menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport }); } if (account.get('acct') !== account.get('username')) { diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js @@ -125,6 +125,7 @@ export default class Header extends ImmutablePureComponent { const content = { __html: account.get('note_emojified') }; const displayNameHtml = { __html: account.get('display_name_html') }; + const fields = account.get('fields') || []; return ( <div className={classNames('account__header', { inactive: !!account.get('moved') })} style={{ backgroundImage: `url(${account.get('header')})` }}> @@ -135,6 +136,19 @@ export default class Header extends ImmutablePureComponent { <span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span> <div className='account__header__content' dangerouslySetInnerHTML={content} /> + {fields.size > 0 && ( + <table className='account__header__fields'> + <tbody> + {fields.map((pair, i) => ( + <tr key={i}> + <th dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} /> + <td dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} /> + </tr> + ))} + </tbody> + </table> + )} + {info} {mutingInfo} {actionBtn} diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js @@ -32,6 +32,10 @@ class PrivacyDropdownMenu extends React.PureComponent { onChange: PropTypes.func.isRequired, }; + state = { + mounted: false, + }; + handleDocumentClick = e => { if (this.node && !this.node.contains(e.target)) { this.props.onClose(); @@ -54,6 +58,7 @@ class PrivacyDropdownMenu extends React.PureComponent { componentDidMount () { document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); + this.setState({ mounted: true }); } componentWillUnmount () { @@ -66,12 +71,16 @@ class PrivacyDropdownMenu extends React.PureComponent { } render () { + const { mounted } = this.state; const { style, items, value } = this.props; return ( <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}> {({ opacity, scaleX, scaleY }) => ( - <div className='privacy-dropdown__dropdown' style={{ ...style, opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }} ref={this.setRef}> + // It should not be transformed when mounting because the resulting + // size will be used to determine the coordinate of the menu by + // react-overlays + <div className='privacy-dropdown__dropdown' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}> {items.map(item => ( <div role='button' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleClick} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })}> <div className='privacy-dropdown__option__icon'> @@ -107,9 +116,10 @@ export default class PrivacyDropdown extends React.PureComponent { state = { open: false, + placement: null, }; - handleToggle = () => { + handleToggle = ({ target }) => { if (this.props.isUserTouching()) { if (this.state.open) { this.props.onModalClose(); @@ -120,6 +130,8 @@ export default class PrivacyDropdown extends React.PureComponent { }); } } else { + const { top } = target.getBoundingClientRect(); + this.setState({ placement: top * 2 < innerHeight ? 'bottom' : 'top' }); this.setState({ open: !this.state.open }); } } @@ -136,7 +148,7 @@ export default class PrivacyDropdown extends React.PureComponent { handleKeyDown = e => { switch(e.key) { case 'Enter': - this.handleToggle(); + this.handleToggle(e); break; case 'Escape': this.handleClose(); @@ -165,7 +177,7 @@ export default class PrivacyDropdown extends React.PureComponent { render () { const { value, intl } = this.props; - const { open } = this.state; + const { open, placement } = this.state; const valueOption = this.options.find(item => item.value === value); @@ -185,7 +197,7 @@ export default class PrivacyDropdown extends React.PureComponent { /> </div> - <Overlay show={open} placement='bottom' target={this}> + <Overlay show={open} placement={placement} target={this}> <PrivacyDropdownMenu items={this.options} value={value} diff --git a/app/javascript/mastodon/features/domain_blocks/index.js b/app/javascript/mastodon/features/domain_blocks/index.js @@ -52,7 +52,7 @@ export default class Blocks extends ImmutablePureComponent { } return ( - <Column icon='ban' heading={intl.formatMessage(messages.heading)}> + <Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}> <ColumnBackButtonSlim /> <ScrollableList scrollKey='domain_blocks' onLoadMore={this.handleLoadMore}> {domains.map(domain => diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js @@ -13,7 +13,7 @@ import { createSelector } from 'reselect'; import { List as ImmutableList } from 'immutable'; import { debounce } from 'lodash'; import ScrollableList from '../../components/scrollable_list'; -import LoadMore from '../../components/load_more'; +import LoadGap from '../../components/load_gap'; const messages = defineMessages({ title: { id: 'column.notifications', defaultMessage: 'Notifications' }, @@ -24,24 +24,6 @@ const getNotifications = createSelector([ state => state.getIn(['notifications', 'items']), ], (excludedTypes, notifications) => notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')))); -class LoadGap extends React.PureComponent { - - static propTypes = { - disabled: PropTypes.bool, - maxId: PropTypes.string, - onClick: PropTypes.func.isRequired, - }; - - handleClick = () => { - this.props.onClick(this.props.maxId); - } - - render () { - return <LoadMore onClick={this.handleClick} disabled={this.props.disabled} />; - } - -} - const mapStateToProps = state => ({ notifications: getNotifications(state), isLoading: state.getIn(['notifications', 'isLoading'], true), diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "تعذرت ترقية هذا المنشور", "status.delete": "إحذف", + "status.direct": "Direct message @{name}", "status.embed": "إدماج", "status.favourite": "أضف إلى المفضلة", "status.load_more": "حمّل المزيد", @@ -269,6 +270,7 @@ "tabs_bar.home": "الرئيسية", "tabs_bar.local_timeline": "المحلي", "tabs_bar.notifications": "الإخطارات", + "tabs_bar.search": "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 @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Изтриване", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Предпочитани", "status.load_more": "Load more", @@ -269,6 +270,7 @@ "tabs_bar.home": "Начало", "tabs_bar.local_timeline": "Local", "tabs_bar.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": "Добави медия", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Aquesta publicació no pot ser repostejada", "status.delete": "Esborrar", + "status.direct": "Direct message @{name}", "status.embed": "Incrustar", "status.favourite": "Favorit", "status.load_more": "Carrega més", @@ -269,6 +270,7 @@ "tabs_bar.home": "Inici", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificacions", + "tabs_bar.search": "Search", "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 @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden", "status.delete": "Löschen", + "status.direct": "Direct message @{name}", "status.embed": "Einbetten", "status.favourite": "Favorisieren", "status.load_more": "Weitere laden", @@ -269,6 +270,7 @@ "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.", "upload_area.title": "Zum Hochladen hereinziehen", "upload_button.label": "Mediendatei hinzufügen", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json @@ -121,6 +121,15 @@ "id": "status.load_more" } ], + "path": "app/javascript/mastodon/components/load_gap.json" + }, + { + "descriptors": [ + { + "defaultMessage": "Load more", + "id": "status.load_more" + } + ], "path": "app/javascript/mastodon/components/load_more.json" }, { @@ -1806,18 +1815,5 @@ } ], "path": "app/javascript/mastodon/features/video/index.json" - }, - { - "descriptors": [ - { - "defaultMessage": "Oops!", - "id": "alert.unexpected.title" - }, - { - "defaultMessage": "An unexpected error occurred.", - "id": "alert.unexpected.message" - } - ], - "path": "app/javascript/mastodon/middleware/errors.json" } ] diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json @@ -60,7 +60,7 @@ "column_subheading.settings": "Agordado", "compose_form.direct_message_warning": "This post will only be visible to all the mentioned users.", "compose_form.hashtag_warning": "Ĉi tiu mesaĝo ne estos listigita per ajna kradvorto. Nur publikaj mesaĝoj estas serĉeblaj per kradvortoj.", - "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn nur por sekvantoj.", + "compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn, kiuj estas nur por sekvantoj.", "compose_form.lock_disclaimer.lock": "ŝlosita", "compose_form.placeholder": "Pri kio vi pensas?", "compose_form.publish": "Hup", @@ -102,7 +102,7 @@ "empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!", "empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.", "empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.", - "empty_column.home.public_timeline": "la publika tempolinio", + "empty_column.home.public_timeline": "la publikan tempolinion", "empty_column.list": "Ankoraŭ estas nenio en ĉi tiu listo. Kiam membroj de ĉi tiu listo afiŝos novajn mesaĝojn, ili aperos ĉi tie.", "empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.", "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj nodoj por plenigi la publikan tempolinion", @@ -150,8 +150,8 @@ "loading_indicator.label": "Ŝargado…", "media_gallery.toggle_visible": "Baskuligi videblecon", "missing_indicator.label": "Ne trovita", - "missing_indicator.sublabel": "Ĉi tiu rimedo ne estis trovita", - "mute_modal.hide_notifications": "Ĉu kaŝi sciigojn el ĉi tiu uzanto?", + "missing_indicator.sublabel": "Ĉi tiu elemento ne estis trovita", + "mute_modal.hide_notifications": "Ĉu vi volas kaŝi la sciigojn el ĉi tiu uzanto?", "navigation_bar.blocks": "Blokitaj uzantoj", "navigation_bar.community_timeline": "Loka tempolinio", "navigation_bar.domain_blocks": "Hidden domains", @@ -240,6 +240,7 @@ "status.block": "Bloki @{name}", "status.cannot_reblog": "Ĉi tiu mesaĝo ne diskonigeblas", "status.delete": "Forigi", + "status.direct": "Direct message @{name}", "status.embed": "Enkorpigi", "status.favourite": "Stelumi", "status.load_more": "Ŝargi pli", @@ -248,8 +249,8 @@ "status.more": "Pli", "status.mute": "Silentigi @{name}", "status.mute_conversation": "Silentigi konversacion", - "status.open": "Grandigi ĉi tiun mesaĝon", - "status.pin": "Alpingli en la profilo", + "status.open": "Grandigi", + "status.pin": "Alpingli profile", "status.pinned": "Alpinglita mesaĝo", "status.reblog": "Diskonigi", "status.reblogged_by": "{name} diskonigis", @@ -269,6 +270,7 @@ "tabs_bar.home": "Hejmo", "tabs_bar.local_timeline": "Loka tempolinio", "tabs_bar.notifications": "Sciigoj", + "tabs_bar.search": "Search", "ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.", "upload_area.title": "Altreni kaj lasi por alŝuti", "upload_button.label": "Aldoni aŭdovidaĵon", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Este post no puede repostearse", "status.delete": "Borrar", + "status.direct": "Direct message @{name}", "status.embed": "Incrustado", "status.favourite": "Favorito", "status.load_more": "Cargar más", @@ -269,6 +270,7 @@ "tabs_bar.home": "Inicio", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificaciones", + "tabs_bar.search": "Search", "ui.beforeunload": "Tu borrador se perderá si sales de Mastodon.", "upload_area.title": "Arrastra y suelta para subir", "upload_button.label": "Subir multimedia", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "این نوشته را نمی‌شود بازبوقید", "status.delete": "پاک‌کردن", + "status.direct": "Direct message @{name}", "status.embed": "جاگذاری", "status.favourite": "پسندیدن", "status.load_more": "بیشتر نشان بده", @@ -269,6 +270,7 @@ "tabs_bar.home": "خانه", "tabs_bar.local_timeline": "محلی", "tabs_bar.notifications": "اعلان‌ها", + "tabs_bar.search": "Search", "ui.beforeunload": "اگر از ماستدون خارج شوید پیش‌نویس شما پاک خواهد شد.", "upload_area.title": "برای بارگذاری به این‌جا بکشید", "upload_button.label": "افزودن تصویر", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json @@ -2,7 +2,7 @@ "account.block": "Estä @{name}", "account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}", "account.blocked": "Estetty", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.", "account.domain_blocked": "Verkko-osoite piilotettu", "account.edit_profile": "Muokkaa", @@ -17,37 +17,37 @@ "account.mute": "Mykistä @{name}", "account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}", "account.muted": "Mykistetty", - "account.posts": "Töötit", - "account.posts_with_replies": "Töötit ja vastaukset", - "account.report": "Report @{name}", - "account.requested": "Odottaa hyväksyntää. Klikkaa peruuttaaksesi seurauspyynnön", + "account.posts": "Tuuttaukset", + "account.posts_with_replies": "Tuuttaukset ja vastaukset", + "account.report": "Raportoi @{name}", + "account.requested": "Odottaa hyväksyntää. Peruuta seuraamispyyntö klikkaamalla", "account.share": "Jaa käyttäjän @{name} profiili", - "account.show_reblogs": "Näytä boostaukset käyttäjältä @{name}", + "account.show_reblogs": "Näytä buustaukset käyttäjältä @{name}", "account.unblock": "Salli @{name}", "account.unblock_domain": "Näytä {domain}", "account.unfollow": "Lakkaa seuraamasta", - "account.unmute": "Poista mykistys käyttäjältä @{name}", + "account.unmute": "Poista käyttäjän @{name} mykistys", "account.unmute_notifications": "Poista mykistys käyttäjän @{name} ilmoituksilta", "account.view_full_profile": "Näytä koko profiili", "alert.unexpected.message": "An unexpected error occurred.", "alert.unexpected.title": "Oops!", - "boost_modal.combo": "Voit painaa näppäimiä {combo} ohittaaksesi tämän ensi kerralla", - "bundle_column_error.body": "Jokin meni vikaan tätä komponenttia ladatessa.", + "boost_modal.combo": "Ensi kerralla voit ohittaa tämän painamalla {combo}", + "bundle_column_error.body": "Jokin meni vikaan komponenttia ladattaessa.", "bundle_column_error.retry": "Yritä uudestaan", - "bundle_column_error.title": "Network error", + "bundle_column_error.title": "Verkkovirhe", "bundle_modal_error.close": "Sulje", - "bundle_modal_error.message": "Jokin meni vikaan tätä komponenttia ladatessa.", + "bundle_modal_error.message": "Jokin meni vikaan komponenttia ladattaessa.", "bundle_modal_error.retry": "Yritä uudestaan", "column.blocks": "Estetyt käyttäjät", "column.community": "Paikallinen aikajana", "column.domain_blocks": "Hidden domains", "column.favourites": "Suosikit", - "column.follow_requests": "Seurauspyynnöt", + "column.follow_requests": "Seuraamispyynnöt", "column.home": "Koti", "column.lists": "Listat", "column.mutes": "Mykistetyt käyttäjät", "column.notifications": "Ilmoitukset", - "column.pins": "Pinned post", + "column.pins": "Kiinnitetty tuuttaus", "column.public": "Yleinen aikajana", "column_back_button.label": "Takaisin", "column_header.hide_settings": "Piilota asetukset", @@ -58,33 +58,33 @@ "column_header.unpin": "Poista kiinnitys", "column_subheading.navigation": "Navigaatio", "column_subheading.settings": "Asetukset", - "compose_form.direct_message_warning": "This post will only be visible to all the mentioned users.", - "compose_form.hashtag_warning": "Tämä töötti ei tule näkymään hashtag-hauissa, koska se ei näy julkisilla aikajanoilla. Vain julkisia tööttejä voi hakea hashtageilla.", - "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille -postauksesi.", + "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.", + "compose_form.hashtag_warning": "Tämä tuuttaus ei näy hashtag-hauissa, koska se on listaamaton. Hashtagien avulla voi hakea vain julkisia tuuttauksia.", + "compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille rajaamasi julkaisut.", "compose_form.lock_disclaimer.lock": "lukittu", - "compose_form.placeholder": "Mitä sinulla on mielessä?", - "compose_form.publish": "Post", + "compose_form.placeholder": "Mitä mietit?", + "compose_form.publish": "Tuuttaa", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.marked": "Media on merkitty arkaluontoiseksi", "compose_form.sensitive.unmarked": "Mediaa ei ole merkitty arkaluontoiseksi", "compose_form.spoiler.marked": "Teksti on piilotettu varoituksen taakse", "compose_form.spoiler.unmarked": "Teksti ei ole piilotettu", - "compose_form.spoiler_placeholder": "Content warning", + "compose_form.spoiler_placeholder": "Sisältövaroitus", "confirmation_modal.cancel": "Peruuta", "confirmations.block.confirm": "Estä", - "confirmations.block.message": "Oletko varma, että haluat estää käyttäjän {name}?", - "confirmations.delete.confirm": "Delete", - "confirmations.delete.message": "Oletko varma, että haluat poistaa tämän statuspäivityksen?", - "confirmations.delete_list.confirm": "Delete", - "confirmations.delete_list.message": "Oletko varma, että haluat poistaa tämän listan pysyvästi?", + "confirmations.block.message": "Haluatko varmasti estää käyttäjän {name}?", + "confirmations.delete.confirm": "Poista", + "confirmations.delete.message": "Haluatko varmasti poistaa tämän tilapäivityksen?", + "confirmations.delete_list.confirm": "Poista", + "confirmations.delete_list.message": "Haluatko varmasti poistaa tämän listan kokonaan?", "confirmations.domain_block.confirm": "Piilota koko verkko-osoite", - "confirmations.domain_block.message": "Oletko aivan oikeasti varma että haluat estää koko verkko-osoitteen {domain}? Useimmissa tapauksissa muutamat kohdistetut estot ja mykistykset ovat riittäviä ja suositeltavampia.", + "confirmations.domain_block.message": "Haluatko aivan varmasti estää koko verkko-osoitteen {domain}? Useimmiten jokunen kohdistettu esto ja mykistys riittää, ja se on suositeltavampi tapa toimia.", "confirmations.mute.confirm": "Mykistä", - "confirmations.mute.message": "Oletko varma että haluat mykistää käyttäjän {name}?", + "confirmations.mute.message": "Haluatko varmasti mykistää käyttäjän {name}?", "confirmations.unfollow.confirm": "Lakkaa seuraamasta", - "confirmations.unfollow.message": "Oletko varma, että haluat lakata seuraamasta käyttäjää {name}?", - "embed.instructions": "Upota tämä statuspäivitys sivullesi kopioimalla alla oleva koodi.", - "embed.preview": "Tältä se tulee näyttämään:", + "confirmations.unfollow.message": "Haluatko varmasti lakata seuraamasta käyttäjää {name}?", + "embed.instructions": "Upota statuspäivitys sivullesi kopioimalla alla oleva koodi.", + "embed.preview": "Se tulee näyttämään tältä:", "emoji_button.activity": "Aktiviteetit", "emoji_button.custom": "Mukautetut", "emoji_button.flags": "Liput", @@ -92,154 +92,155 @@ "emoji_button.label": "Lisää emoji", "emoji_button.nature": "Luonto", "emoji_button.not_found": "Ei emojeja!! (╯°□°)╯︵ ┻━┻", - "emoji_button.objects": "Objektit", + "emoji_button.objects": "Esineet", "emoji_button.people": "Ihmiset", "emoji_button.recent": "Usein käytetyt", "emoji_button.search": "Etsi...", "emoji_button.search_results": "Hakutulokset", "emoji_button.symbols": "Symbolit", "emoji_button.travel": "Matkailu", - "empty_column.community": "Paikallinen aikajana on tyhjä. Kirjoita jotain julkista saadaksesi pyörät pyörimään!", - "empty_column.hashtag": "Tässä hashtagissa ei ole vielä mitään.", - "empty_column.home": "Kotiaikajanasi on tyhjä! Käy vierailemassa {public}ssa tai käytä hakutoimintoa aloittaaksesi ja tavataksesi muita käyttäjiä.", + "empty_column.community": "Paikallinen aikajana on tyhjä. Homma lähtee käyntiin, kun kirjoitat jotain julkista!", + "empty_column.hashtag": "Tällä hashtagilla ei ole vielä mitään.", + "empty_column.home": "Kotiaikajanasi on tyhjä! {public} ja hakutoiminto auttavat alkuun ja kohtaamaan muita käyttäjiä.", "empty_column.home.public_timeline": "yleinen aikajana", - "empty_column.list": "Tämä lista on vielä tyhjä. Kun listan jäsenet julkaisevat statuspäivityksiä, ne näkyvät tässä.", - "empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Juttele muille aloittaaksesi keskustelun.", - "empty_column.public": "Täällä ei ole mitään! Kirjoita jotain julkisesti, tai käy manuaalisesti seuraamassa käyttäjiä muista instansseista saadaksesi sisältöä", + "empty_column.list": "Lista on vielä tyhjä. Listan jäsenten julkaisemat tilapäivitykset tulevat tähän näkyviin.", + "empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Aloita keskustelu juttelemalla muille.", + "empty_column.public": "Täällä ei ole mitään! Saat sisältöä, kun kirjoitat jotain julkisesti tai käyt manuaalisesti seuraamassa muiden instanssien käyttäjiä", "follow_request.authorize": "Valtuuta", "follow_request.reject": "Hylkää", "getting_started.appsshort": "Sovellukset", "getting_started.faq": "FAQ", "getting_started.heading": "Aloitus", - "getting_started.open_source_notice": "Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}.", + "getting_started.open_source_notice": "Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHubissa: {github}.", "getting_started.userguide": "Käyttöopas", - "home.column_settings.advanced": "Tarkemmat asetukset", + "home.column_settings.advanced": "Lisäasetukset", "home.column_settings.basic": "Perusasetukset", - "home.column_settings.filter_regex": "Suodata säännöllisten lauseiden avulla", + "home.column_settings.filter_regex": "Suodata säännöllisillä lausekkeilla", "home.column_settings.show_reblogs": "Näytä buustaukset", "home.column_settings.show_replies": "Näytä vastaukset", "home.settings": "Sarakeasetukset", - "keyboard_shortcuts.back": "liikkuaksesi taaksepäin", - "keyboard_shortcuts.boost": "buustataksesi", - "keyboard_shortcuts.column": "keskittääksesi statuspäivitykseen yhdessä sarakkeista", - "keyboard_shortcuts.compose": "aktivoidaksesi tekstinkirjoitusalueen", - "keyboard_shortcuts.description": "Description", - "keyboard_shortcuts.down": "liikkuaksesi listassa alaspäin", - "keyboard_shortcuts.enter": "to open status", - "keyboard_shortcuts.favourite": "tykätäksesi", - "keyboard_shortcuts.heading": "Näppäinoikotiet", + "keyboard_shortcuts.back": "liiku taaksepäin", + "keyboard_shortcuts.boost": "buustaa", + "keyboard_shortcuts.column": "siirrä fokus tietyn sarakkeen tilapäivitykseen", + "keyboard_shortcuts.compose": "siirry tekstinsyöttöön", + "keyboard_shortcuts.description": "Kuvaus", + "keyboard_shortcuts.down": "siirry listassa alaspäin", + "keyboard_shortcuts.enter": "avaa tilapäivitys", + "keyboard_shortcuts.favourite": "tykkää", + "keyboard_shortcuts.heading": "Näppäinkomennot", "keyboard_shortcuts.hotkey": "Pikanäppäin", - "keyboard_shortcuts.legend": "näyttääksesi tämän selitteen", - "keyboard_shortcuts.mention": "mainitaksesi julkaisijan", - "keyboard_shortcuts.reply": "vastataksesi", - "keyboard_shortcuts.search": "aktivoidaksesi hakukentän", - "keyboard_shortcuts.toot": "aloittaaksesi uuden töötin kirjoittamisen", - "keyboard_shortcuts.unfocus": "poistaaksesi aktivoinnin tekstikentästä/hakukentästä", - "keyboard_shortcuts.up": "liikkuaksesi listassa ylöspäin", + "keyboard_shortcuts.legend": "näytä tämä selite", + "keyboard_shortcuts.mention": "mainitse julkaisija", + "keyboard_shortcuts.reply": "vastaa", + "keyboard_shortcuts.search": "siirry hakukenttään", + "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", "lightbox.close": "Sulje", "lightbox.next": "Seuraava", "lightbox.previous": "Edellinen", "lists.account.add": "Lisää listaan", - "lists.account.remove": "Poista listalta", - "lists.delete": "Delete list", + "lists.account.remove": "Poista listasta", + "lists.delete": "Poista lista", "lists.edit": "Muokkaa listaa", "lists.new.create": "Lisää lista", - "lists.new.title_placeholder": "Uuden listan otsikko", - "lists.search": "Etsi seuraamiesi henkilöiden joukosta", + "lists.new.title_placeholder": "Uuden listan nimi", + "lists.search": "Etsi seuraamistasi henkilöistä", "lists.subheading": "Omat listat", "loading_indicator.label": "Ladataan...", "media_gallery.toggle_visible": "Säädä näkyvyyttä", - "missing_indicator.label": "Ei löydetty", + "missing_indicator.label": "Ei löytynyt", "missing_indicator.sublabel": "Tätä resurssia ei löytynyt", - "mute_modal.hide_notifications": "Piilota ilmoitukset tältä käyttäjältä?", + "mute_modal.hide_notifications": "Piilota tältä käyttäjältä tulevat ilmoitukset?", "navigation_bar.blocks": "Estetyt käyttäjät", "navigation_bar.community_timeline": "Paikallinen aikajana", "navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.edit_profile": "Muokkaa profiilia", "navigation_bar.favourites": "Suosikit", - "navigation_bar.follow_requests": "Seurauspyynnöt", + "navigation_bar.follow_requests": "Seuraamispyynnöt", "navigation_bar.info": "Tietoa tästä instanssista", - "navigation_bar.keyboard_shortcuts": "Näppäinoikotiet", + "navigation_bar.keyboard_shortcuts": "Näppäinkomennot", "navigation_bar.lists": "Listat", "navigation_bar.logout": "Kirjaudu ulos", "navigation_bar.mutes": "Mykistetyt käyttäjät", - "navigation_bar.pins": "Kiinnitetyt töötit", - "navigation_bar.preferences": "Ominaisuudet", + "navigation_bar.pins": "Kiinnitetyt tuuttaukset", + "navigation_bar.preferences": "Asetukset", "navigation_bar.public_timeline": "Yleinen aikajana", - "notification.favourite": "{name} tykkäsi statuksestasi", + "notification.favourite": "{name} tykkäsi tilastasi", "notification.follow": "{name} seurasi sinua", "notification.mention": "{name} mainitsi sinut", - "notification.reblog": "{name} buustasi statustasi", + "notification.reblog": "{name} buustasi tilaasi", "notifications.clear": "Tyhjennä ilmoitukset", - "notifications.clear_confirmation": "Oletko varma, että haluat lopullisesti tyhjentää kaikki ilmoituksesi?", - "notifications.column_settings.alert": "Työpöytä ilmoitukset", - "notifications.column_settings.favourite": "Tykkäyksiä:", - "notifications.column_settings.follow": "Uusia seuraajia:", - "notifications.column_settings.mention": "Mainintoja:", + "notifications.clear_confirmation": "Haluatko varmasti poistaa kaikki ilmoitukset pysyvästi?", + "notifications.column_settings.alert": "Työpöytäilmoitukset", + "notifications.column_settings.favourite": "Tykkäykset:", + "notifications.column_settings.follow": "Uudet seuraajat:", + "notifications.column_settings.mention": "Maininnat:", "notifications.column_settings.push": "Push-ilmoitukset", "notifications.column_settings.push_meta": "Tämä laite", - "notifications.column_settings.reblog": "Buusteja:", + "notifications.column_settings.reblog": "Buustit:", "notifications.column_settings.show": "Näytä sarakkeessa", - "notifications.column_settings.sound": "Soita ääni", + "notifications.column_settings.sound": "Äänimerkki", "onboarding.done": "Valmis", "onboarding.next": "Seuraava", - "onboarding.page_five.public_timelines": "Paikallinen aikajana näyttää kaikki julkiset julkaisut kaikilta, jotka ovat verkko-osoitteessa {domain}. Yleinen aikajana näyttää julkiset julkaisut kaikilta niiltä, joita käyttäjät verkko-osoitteessa {domain} seuraavat. Nämä ovat julkiset aikajanat, ja ne ovat hyviä tapoja löytää uusia ihmisiä.", - "onboarding.page_four.home": "Kotiaikajana näyttää julkaisut ihmisiltä joita seuraat.", - "onboarding.page_four.notifications": "Ilmoitukset-sarake näyttää sinulle, kun joku on viestii kanssasi.", - "onboarding.page_one.federation": "Mastodon on yhteisöpalvelu, joka toimii monen itsenäisen palvelimen muodostamassa verkossa. Me kutsumme näitä palvelimia instansseiksi.", + "onboarding.page_five.public_timelines": "Paikallisella aikajanalla näytetään instanssin {domain} kaikkien käyttäjien julkiset julkaisut. Yleisellä aikajanalla näytetään kaikkien instanssin {domain} käyttäjien seuraamien käyttäjien julkiset julkaisut. Nämä julkiset aikajanat ovat loistavia paikkoja löytää uusia ihmisiä.", + "onboarding.page_four.home": "Kotiaikajanalla näytetään seuraamiesi ihmisten julkaisut.", + "onboarding.page_four.notifications": "Ilmoitukset-sarakkeessa näytetään muiden sinuun liittyvä toiminta.", + "onboarding.page_one.federation": "Mastodon on usean itsenäisen palvelimen muodostama yhteisöpalvelu. Näitä palvelimia kutsutaan instansseiksi.", "onboarding.page_one.full_handle": "Koko käyttäjänimesi", - "onboarding.page_one.handle_hint": "Tämä on se, mitä voisit ehdottaa ystäviäsi etsimään.", + "onboarding.page_one.handle_hint": "Tällä nimellä ystäväsi löytävät sinut.", "onboarding.page_one.welcome": "Tervetuloa Mastodoniin!", - "onboarding.page_six.admin": "Instanssisi ylläpitäjä on {admin}.", + "onboarding.page_six.admin": "Instanssin ylläpitäjä on {admin}.", "onboarding.page_six.almost_done": "Melkein valmista...", - "onboarding.page_six.appetoot": "Bon Appetööt!", + "onboarding.page_six.appetoot": "Tuuttailun iloa!", "onboarding.page_six.apps_available": "{apps} on saatavilla iOS:lle, Androidille ja muille alustoille.", - "onboarding.page_six.github": "Mastodon on ilmainen, vapaan lähdekoodin ohjelma. Voit raportoida bugeja, pyytää ominaisuuksia tai osallistua kehittämiseen GitHub-palvelussa: {github}.", + "onboarding.page_six.github": "Mastodon on ilmainen, vapaan lähdekoodin ohjelma. Voit raportoida bugeja, ehdottaa ominaisuuksia tai osallistua kehittämiseen GitHubissa: {github}.", "onboarding.page_six.guidelines": "yhteisön säännöt", "onboarding.page_six.read_guidelines": "Ole hyvä ja lue {domain}:n {guidelines}!", "onboarding.page_six.various_app": "mobiilisovellukset", - "onboarding.page_three.profile": "Muokkaa profiiliasi muuttaaksesi kuvakettasi, esittelyäsi ja nimimerkkiäsi. Löydät sieltä myös muita henkilökohtaisia asetuksia.", - "onboarding.page_three.search": "Käytä hakukenttää löytääksesi ihmisiä ja etsiäksesi hashtageja, kuten {illustration} tai {introductions}. Hakeaksesi henkilöä joka on toisessa instanssissa, käytä hänen käyttäjänimeään kokonaisuudessaan.", - "onboarding.page_two.compose": "Kirjoita postauksia kirjoita-sarakkeessa. Voit ladata kuvia, vaihtaa yksityisyysasetuksia ja lisätä sisältövaroituksia alla olevista painikkeista.", + "onboarding.page_three.profile": "Voit muuttaa profiilikuvaasi, esittelyäsi ja nimimerkkiäsi sekä muita asetuksia muokkaamalla profiiliasi.", + "onboarding.page_three.search": "Etsi ihmisiä ja hashtageja (esimerkiksi {illustration} tai {introductions}) hakukentän avulla. Jos haet toista instanssia käyttävää henkilöä, käytä hänen koko käyttäjänimeään.", + "onboarding.page_two.compose": "Kirjoita julkaisuja kirjoitussarakkeessa. Voit ladata kuvia, vaihtaa näkyvyysasetuksia ja lisätä sisältövaroituksia alla olevista painikkeista.", "onboarding.skip": "Ohita", - "privacy.change": "Säädä töötin yksityisyysasetuksia", + "privacy.change": "Säädä tuuttauksen näkyvyyttä", "privacy.direct.long": "Julkaise vain mainituille käyttäjille", - "privacy.direct.short": "Yksityisviesti", + "privacy.direct.short": "Suora viesti", "privacy.private.long": "Julkaise vain seuraajille", "privacy.private.short": "Vain seuraajat", "privacy.public.long": "Julkaise julkisille aikajanoille", "privacy.public.short": "Julkinen", - "privacy.unlisted.long": "Älä julkaise yleisillä aikajanoilla", - "privacy.unlisted.short": "Julkinen, mutta älä näytä julkisella aikajanalla", + "privacy.unlisted.long": "Älä julkaise julkisilla aikajanoilla", + "privacy.unlisted.short": "Listaamaton julkinen", "regeneration_indicator.label": "Ladataan…", "regeneration_indicator.sublabel": "Kotinäkymääsi valmistellaan!", - "relative_time.days": "{number}d", - "relative_time.hours": "{number}h", + "relative_time.days": "{number} pv", + "relative_time.hours": "{number} h", "relative_time.just_now": "nyt", - "relative_time.minutes": "{number}m", - "relative_time.seconds": "{number}s", + "relative_time.minutes": "{number} m", + "relative_time.seconds": "{number} s", "reply_indicator.cancel": "Peruuta", - "report.forward": "Uudelleenohjaa kohteeseen {target}", - "report.forward_hint": "Tämä tili on toiselta serveriltä. Haluatko, että myös sinne lähetetään anonymisoitu kopio ilmiantoraportista?", - "report.hint": "Ilmianto lähetetään instanssisi moderaattoreille. Voit antaa kuvauksen käyttäjän ilmiantamisen syystä alle:", + "report.forward": "Välitä kohteeseen {target}", + "report.forward_hint": "Tämä tili on toisella palvelimella. Haluatko lähettää nimettömän raportin myös sinne?", + "report.hint": "Raportti lähetetään oman instanssisi moderaattoreille. Seuraavassa voit kertoa, miksi raportoit tästä tilistä:", "report.placeholder": "Lisäkommentit", - "report.submit": "Submit", - "report.target": "Reporting", + "report.submit": "Lähetä", + "report.target": "Raportoidaan {target}", "search.placeholder": "Hae", "search_popout.search_format": "Tarkennettu haku", - "search_popout.tips.full_text": "Tekstihaku palauttaa statuspäivitykset jotka olet kirjoittanut, lisännyt suosikkeihisi, boostannut tai joissa sinut mainitaan, sekä käyttäjänimet, nimimerkit ja hastagit jotka sisältävät tekstin.", - "search_popout.tips.hashtag": "hashtagi", - "search_popout.tips.status": "status", - "search_popout.tips.text": "Pelkkä tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit", + "search_popout.tips.full_text": "Tekstihaku palauttaa tilapäivitykset, jotka olet kirjoittanut, lisännyt suosikkeihisi, boostannut tai joissa sinut mainitaan, sekä tekstin sisältävät käyttäjänimet, nimimerkit ja hastagit.", + "search_popout.tips.hashtag": "hashtag", + "search_popout.tips.status": "tila", + "search_popout.tips.text": "Tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit", "search_popout.tips.user": "käyttäjä", "search_results.accounts": "Ihmiset", "search_results.hashtags": "Hashtagit", - "search_results.statuses": "Töötit", + "search_results.statuses": "Tuuttaukset", "search_results.total": "{count, number} {count, plural, one {result} other {results}}", "standalone.public_title": "Kurkistus sisälle...", - "status.block": "Block @{name}", - "status.cannot_reblog": "Tätä postausta ei voi buustata", + "status.block": "Estä @{name}", + "status.cannot_reblog": "Tätä julkaisua ei voi buustata", "status.delete": "Poista", + "status.direct": "Direct message @{name}", "status.embed": "Upota", "status.favourite": "Tykkää", "status.load_more": "Lataa lisää", @@ -248,29 +249,30 @@ "status.more": "Lisää", "status.mute": "Mykistä @{name}", "status.mute_conversation": "Mykistä keskustelu", - "status.open": "Laajenna statuspäivitys", + "status.open": "Laajenna tilapäivitys", "status.pin": "Kiinnitä profiiliin", - "status.pinned": "Kiinnitetty töötti", + "status.pinned": "Kiinnitetty tuuttaus", "status.reblog": "Buustaa", "status.reblogged_by": "{name} buustasi", "status.reply": "Vastaa", "status.replyAll": "Vastaa ketjuun", - "status.report": "Report @{name}", + "status.report": "Raportoi @{name}", "status.sensitive_toggle": "Klikkaa nähdäksesi", "status.sensitive_warning": "Arkaluontoista sisältöä", "status.share": "Jaa", "status.show_less": "Näytä vähemmän", "status.show_less_all": "Näytä vähemmän kaikista", "status.show_more": "Näytä lisää", - "status.show_more_all": "Näytä enemmän kaikista", - "status.unmute_conversation": "Poista mykistys keskustelulta", + "status.show_more_all": "Näytä lisää kaikista", + "status.unmute_conversation": "Poista keskustelun mykistys", "status.unpin": "Irrota profiilista", "tabs_bar.federated_timeline": "Yleinen", "tabs_bar.home": "Koti", "tabs_bar.local_timeline": "Paikallinen", "tabs_bar.notifications": "Ilmoitukset", - "ui.beforeunload": "Luonnoksesi menetetään, jos poistut Mastodonista.", - "upload_area.title": "Raahaa ja pudota tähän ladataksesi", + "tabs_bar.search": "Search", + "ui.beforeunload": "Luonnos häviää, jos poistut Mastodonista.", + "upload_area.title": "Lataa raahaamalla ja pudottamalla tähän", "upload_button.label": "Lisää mediaa", "upload_form.description": "Anna kuvaus näkörajoitteisia varten", "upload_form.focus": "Rajaa", @@ -279,10 +281,10 @@ "video.close": "Sulje video", "video.exit_fullscreen": "Poistu koko näytön tilasta", "video.expand": "Laajenna video", - "video.fullscreen": "Full screen", + "video.fullscreen": "Koko näyttö", "video.hide": "Piilota video", "video.mute": "Mykistä ääni", "video.pause": "Keskeytä", "video.play": "Toista", - "video.unmute": "Poista mykistys ääneltä" + "video.unmute": "Poista äänen mykistys" } diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Cette publication ne peut être boostée", "status.delete": "Effacer", + "status.direct": "Direct message @{name}", "status.embed": "Intégrer", "status.favourite": "Ajouter aux favoris", "status.load_more": "Charger plus", @@ -269,6 +270,7 @@ "tabs_bar.home": "Accueil", "tabs_bar.local_timeline": "Fil public local", "tabs_bar.notifications": "Notifications", + "tabs_bar.search": "Search", "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 @@ -2,7 +2,7 @@ "account.block": "Bloquear @{name}", "account.block_domain": "Ocultar calquer contido de {domain}", "account.blocked": "Blocked", - "account.direct": "Direct Message @{name}", + "account.direct": "Direct message @{name}", "account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.", "account.domain_blocked": "Domain hidden", "account.edit_profile": "Editar perfil", @@ -148,7 +148,7 @@ "lists.search": "Procurar entre a xente que segues", "lists.subheading": "As túas listas", "loading_indicator.label": "Cargando...", - "media_gallery.toggle_visible": "Dar visibilidade", + "media_gallery.toggle_visible": "Ocultar", "missing_indicator.label": "Non atopado", "missing_indicator.sublabel": "This resource could not be found", "mute_modal.hide_notifications": "Esconder notificacións deste usuario?", @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Esta mensaxe non pode ser promocionada", "status.delete": "Eliminar", + "status.direct": "Direct message @{name}", "status.embed": "Incrustar", "status.favourite": "Favorita", "status.load_more": "Cargar máis", @@ -269,6 +270,7 @@ "tabs_bar.home": "Inicio", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificacións", + "tabs_bar.search": "Search", "ui.beforeunload": "O borrador perderase se sae de Mastodon.", "upload_area.title": "Arrastre e solte para subir", "upload_button.label": "Engadir medios", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "לא ניתן להדהד הודעה זו", "status.delete": "מחיקה", + "status.direct": "Direct message @{name}", "status.embed": "הטמעה", "status.favourite": "חיבוב", "status.load_more": "עוד", @@ -269,6 +270,7 @@ "tabs_bar.home": "בבית", "tabs_bar.local_timeline": "ציר זמן מקומי", "tabs_bar.notifications": "התראות", + "tabs_bar.search": "Search", "ui.beforeunload": "הטיוטא תאבד אם תעזבו את מסטודון.", "upload_area.title": "ניתן להעלות על ידי Drag & drop", "upload_button.label": "הוספת מדיה", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Ovaj post ne može biti boostan", "status.delete": "Obriši", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Označi omiljenim", "status.load_more": "Učitaj više", @@ -269,6 +270,7 @@ "tabs_bar.home": "Dom", "tabs_bar.local_timeline": "Lokalno", "tabs_bar.notifications": "Notifikacije", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Povuci i spusti kako bi uploadao", "upload_button.label": "Dodaj media", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Ezen státusz nem rebloggolható", "status.delete": "Törlés", + "status.direct": "Direct message @{name}", "status.embed": "Beágyaz", "status.favourite": "Kedvenc", "status.load_more": "Többet", @@ -269,6 +270,7 @@ "tabs_bar.home": "Kezdőlap", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Értesítések", + "tabs_bar.search": "Search", "ui.beforeunload": "A piszkozata el fog vesztődni ha elhagyja Mastodon-t.", "upload_area.title": "Húzza ide a feltöltéshez", "upload_button.label": "Média hozzáadása", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json @@ -240,6 +240,7 @@ "status.block": "Արգելափակել @{name}֊ին", "status.cannot_reblog": "Այս թութը չի կարող տարածվել", "status.delete": "Ջնջել", + "status.direct": "Direct message @{name}", "status.embed": "Ներդնել", "status.favourite": "Հավանել", "status.load_more": "Բեռնել ավելին", @@ -269,6 +270,7 @@ "tabs_bar.home": "Հիմնական", "tabs_bar.local_timeline": "Տեղական", "tabs_bar.notifications": "Ծանուցումներ", + "tabs_bar.search": "Search", "ui.beforeunload": "Քո սեւագիրը կկորի, եթե լքես Մաստոդոնը։", "upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար", "upload_button.label": "Ավելացնել մեդիա", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Hapus", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Difavoritkan", "status.load_more": "Tampilkan semua", @@ -269,6 +270,7 @@ "tabs_bar.home": "Beranda", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Notifikasi", + "tabs_bar.search": "Search", "ui.beforeunload": "Naskah anda akan hilang jika anda keluar dari Mastodon.", "upload_area.title": "Seret & lepaskan untuk mengunggah", "upload_button.label": "Tambahkan media", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Efacar", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Favorizar", "status.load_more": "Kargar pluse", @@ -269,6 +270,7 @@ "tabs_bar.home": "Hemo", "tabs_bar.local_timeline": "Lokala", "tabs_bar.notifications": "Savigi", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Tranar faligar por kargar", "upload_button.label": "Adjuntar kontenajo", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "This post cannot be boosted", "status.delete": "Elimina", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Apprezzato", "status.load_more": "Mostra di più", @@ -269,6 +270,7 @@ "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Locale", "tabs_bar.notifications": "Notifiche", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Trascina per caricare", "upload_button.label": "Aggiungi file multimediale", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json @@ -186,7 +186,7 @@ "onboarding.page_five.public_timelines": "連合タイムラインでは{domain}の人がフォローしているMastodon全体での公開投稿を表示します。同じくローカルタイムラインでは{domain}のみの公開投稿を表示します。", "onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。", "onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。", - "onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。", + "onboarding.page_one.federation": "Mastodonは独立したインスタンス(サーバー)の集合体です。", "onboarding.page_one.full_handle": "あなたのフルハンドル", "onboarding.page_one.handle_hint": "あなたを探している友達に伝えるといいでしょう。", "onboarding.page_one.welcome": "Mastodonへようこそ!", @@ -240,6 +240,7 @@ "status.block": "@{name}さんをブロック", "status.cannot_reblog": "この投稿はブーストできません", "status.delete": "削除", + "status.direct": "@{name}さんにダイレクトメッセージ", "status.embed": "埋め込み", "status.favourite": "お気に入り", "status.load_more": "もっと見る", @@ -269,6 +270,7 @@ "tabs_bar.home": "ホーム", "tabs_bar.local_timeline": "ローカル", "tabs_bar.notifications": "通知", + "tabs_bar.search": "検索", "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。", "upload_area.title": "ドラッグ&ドロップでアップロード", "upload_button.label": "メディアを追加", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json @@ -240,6 +240,7 @@ "status.block": "@{name} 차단", "status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다", "status.delete": "삭제", + "status.direct": "Direct message @{name}", "status.embed": "공유하기", "status.favourite": "즐겨찾기", "status.load_more": "더 보기", @@ -269,6 +270,7 @@ "tabs_bar.home": "홈", "tabs_bar.local_timeline": "로컬", "tabs_bar.notifications": "알림", + "tabs_bar.search": "Search", "ui.beforeunload": "지금 나가면 저장되지 않은 항목을 잃게 됩니다.", "upload_area.title": "드래그 & 드롭으로 업로드", "upload_button.label": "미디어 추가", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json @@ -240,6 +240,7 @@ "status.block": "Blokkeer @{name}", "status.cannot_reblog": "Deze post kan niet geboost worden", "status.delete": "Verwijderen", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Favoriet", "status.load_more": "Meer laden", @@ -269,6 +270,7 @@ "tabs_bar.home": "Start", "tabs_bar.local_timeline": "Lokaal", "tabs_bar.notifications": "Meldingen", + "tabs_bar.search": "Search", "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 @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Denne posten kan ikke fremheves", "status.delete": "Slett", + "status.direct": "Direct message @{name}", "status.embed": "Bygge inn", "status.favourite": "Lik", "status.load_more": "Last mer", @@ -269,6 +270,7 @@ "tabs_bar.home": "Hjem", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Varslinger", + "tabs_bar.search": "Search", "ui.beforeunload": "Din kladd vil bli forkastet om du forlater Mastodon.", "upload_area.title": "Dra og slipp for å laste opp", "upload_button.label": "Legg til media", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json @@ -240,6 +240,7 @@ "status.block": "Blocar @{name}", "status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat", "status.delete": "Escafar", + "status.direct": "Direct message @{name}", "status.embed": "Embarcar", "status.favourite": "Apondre als favorits", "status.load_more": "Cargar mai", @@ -269,6 +270,7 @@ "tabs_bar.home": "Acuèlh", "tabs_bar.local_timeline": "Flux public local", "tabs_bar.notifications": "Notificacions", + "tabs_bar.search": "Search", "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 @@ -2,7 +2,7 @@ "account.block": "Blokuj @{name}", "account.block_domain": "Blokuj wszystko z {domain}", "account.blocked": "Zablokowany", - "account.direct": "Direct Message @{name}", + "account.direct": "Wyślij wiadomość bezpośrednią do @{name}", "account.disclaimer_full": "Poniższe informacje mogą nie odwzorowywać bezbłędnie profilu użytkownika.", "account.domain_blocked": "Ukryto domenę", "account.edit_profile": "Edytuj profil", @@ -240,6 +240,7 @@ "status.block": "Zablokuj @{name}", "status.cannot_reblog": "Ten wpis nie może zostać podbity", "status.delete": "Usuń", + "status.direct": "Direct message @{name}", "status.embed": "Osadź", "status.favourite": "Ulubione", "status.load_more": "Załaduj więcej", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Esta postagem não pode ser compartilhada", "status.delete": "Excluir", + "status.direct": "Direct message @{name}", "status.embed": "Incorporar", "status.favourite": "Adicionar aos favoritos", "status.load_more": "Carregar mais", @@ -269,6 +270,7 @@ "tabs_bar.home": "Página inicial", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", + "tabs_bar.search": "Search", "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 @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Este post não pode ser partilhado", "status.delete": "Eliminar", + "status.direct": "Direct message @{name}", "status.embed": "Incorporar", "status.favourite": "Adicionar aos favoritos", "status.load_more": "Carregar mais", @@ -269,6 +270,7 @@ "tabs_bar.home": "Home", "tabs_bar.local_timeline": "Local", "tabs_bar.notifications": "Notificações", + "tabs_bar.search": "Search", "ui.beforeunload": "O teu rascunho vai ser perdido se abandonares o Mastodon.", "upload_area.title": "Arraste e solte para enviar", "upload_button.label": "Adicionar media", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json @@ -240,6 +240,7 @@ "status.block": "Заблокировать @{name}", "status.cannot_reblog": "Этот статус не может быть продвинут", "status.delete": "Удалить", + "status.direct": "Direct message @{name}", "status.embed": "Встроить", "status.favourite": "Нравится", "status.load_more": "Показать еще", @@ -269,6 +270,7 @@ "tabs_bar.home": "Главная", "tabs_bar.local_timeline": "Локальная", "tabs_bar.notifications": "Уведомления", + "tabs_bar.search": "Search", "ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.", "upload_area.title": "Перетащите сюда, чтобы загрузить", "upload_button.label": "Добавить медиаконтент", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json @@ -8,11 +8,11 @@ "account.edit_profile": "Upraviť profil", "account.follow": "Následovať", "account.followers": "Sledujúci", - "account.follows": "Sledujete", + "account.follows": "Následuje", "account.follows_you": "Následuje ťa", "account.hide_reblogs": "Skryť povýšenia od @{name}", "account.media": "Médiá", - "account.mention": "Spomeňte @{name}", + "account.mention": "Spomeň @{name}", "account.moved_to": "{name} sa presunul/a na:", "account.mute": "Ignorovať @{name}", "account.mute_notifications": "Stĺmiť notifikácie od @{name}", @@ -22,7 +22,7 @@ "account.report": "Nahlás @{name}", "account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti", "account.share": "Zdieľať @{name} profil", - "account.show_reblogs": "Zobraziť povýšenia od @{name}", + "account.show_reblogs": "Ukáž povýšenia od @{name}", "account.unblock": "Odblokovať @{name}", "account.unblock_domain": "Prestať blokovať {domain}", "account.unfollow": "Prestať nasledovať", @@ -83,7 +83,7 @@ "confirmations.mute.message": "Naozaj chcete ignorovať {name}?", "confirmations.unfollow.confirm": "Nesledovať", "confirmations.unfollow.message": "Naozaj chcete prestať sledovať {name}?", - "embed.instructions": "Umiestnite kód uvedený nižšie pre pridanie tohto statusu na vašu web stránku.", + "embed.instructions": "Umiestni kód uvedený nižšie pre pridanie tohto statusu na tvoju web stránku.", "embed.preview": "Tu je ako to bude vyzerať:", "emoji_button.activity": "Aktivita", "emoji_button.custom": "Vlastné", @@ -111,13 +111,13 @@ "getting_started.appsshort": "Aplikácie", "getting_started.faq": "Časté otázky", "getting_started.heading": "Začni tu", - "getting_started.open_source_notice": "Mastodon má otvorený kód. Nahlásiť chyby, alebo prispievať vlastným kódom môžete na GitHube v {github}.", + "getting_started.open_source_notice": "Mastodon má otvorený kód. Nahlásiť chyby, alebo prispieť môžeš na GitHube v {github}.", "getting_started.userguide": "Používateľská príručka", "home.column_settings.advanced": "Pokročilé", "home.column_settings.basic": "Základné", "home.column_settings.filter_regex": "Filtrovať použitím regulárnych výrazov", "home.column_settings.show_reblogs": "Zobraziť povýšené", - "home.column_settings.show_replies": "Zobraziť odpovede", + "home.column_settings.show_replies": "Ukázať odpovede", "home.settings": "Nastavenia stĺpcov", "keyboard_shortcuts.back": "dostať sa naspäť", "keyboard_shortcuts.boost": "vyzdvihnúť", @@ -169,7 +169,7 @@ "notification.favourite": "{name} sa páči tvoj status", "notification.follow": "{name} ťa začal/a následovať", "notification.mention": "{name} ťa spomenul/a", - "notification.reblog": "{name} re-postol tvoj status", + "notification.reblog": "{name} zdieľal/a tvoj status", "notifications.clear": "Vyčistiť zoznam notifikácii", "notifications.clear_confirmation": "Naozaj chcete nenávratne prečistiť všetky vaše notifikácie?", "notifications.column_settings.alert": "Notifikácie na ploche", @@ -235,24 +235,25 @@ "search_results.accounts": "Ľudia", "search_results.hashtags": "Haštagy", "search_results.statuses": "Príspevky", - "search_results.total": "{count, number} {count, plural, one {result} ostatné {results}}", - "standalone.public_title": "Pohľad dovnútra...", + "search_results.total": "{count, number} {count, plural, jeden {výsledok} ostatné {výsledky}}", + "standalone.public_title": "Náhľad dovnútra...", "status.block": "Blokovať @{name}", "status.cannot_reblog": "Tento príspevok nemôže byť re-postnutý", "status.delete": "Zmazať", + "status.direct": "Direct message @{name}", "status.embed": "Vložiť", "status.favourite": "Páči sa mi", "status.load_more": "Zobraz viac", "status.media_hidden": "Skryté médiá", - "status.mention": "Napísať @{name}", + "status.mention": "Spomeň @{name}", "status.more": "Viac", "status.mute": "Utíšiť @{name}", "status.mute_conversation": "Ignorovať konverzáciu", "status.open": "Otvoriť tento status", - "status.pin": "Pripnúť na profil", + "status.pin": "Pripni na profil", "status.pinned": "Pripnutý príspevok", "status.reblog": "Povýšiť", - "status.reblogged_by": "{name} povýšil", + "status.reblogged_by": "{name} povýšil/a", "status.reply": "Odpovedať", "status.replyAll": "Odpovedať na diskusiu", "status.report": "Nahlásiť @{name}", @@ -261,7 +262,7 @@ "status.share": "Zdieľať", "status.show_less": "Zobraz menej", "status.show_less_all": "Všetkým ukáž menej", - "status.show_more": "Zobraz viac", + "status.show_more": "Zobraziť viac", "status.show_more_all": "Všetkým ukáž viac", "status.unmute_conversation": "Prestať ignorovať konverzáciu", "status.unpin": "Odopnúť z profilu", @@ -269,6 +270,7 @@ "tabs_bar.home": "Domov", "tabs_bar.local_timeline": "Lokálna", "tabs_bar.notifications": "Notifikácie", + "tabs_bar.search": "Search", "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 @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Ovaj status ne može da se podrži", "status.delete": "Obriši", + "status.direct": "Direct message @{name}", "status.embed": "Ugradi na sajt", "status.favourite": "Omiljeno", "status.load_more": "Učitaj još", @@ -269,6 +270,7 @@ "tabs_bar.home": "Početna", "tabs_bar.local_timeline": "Lokalno", "tabs_bar.notifications": "Obaveštenja", + "tabs_bar.search": "Search", "ui.beforeunload": "Ako napustite Mastodont, izgubićete napisani nacrt.", "upload_area.title": "Prevucite ovde da otpremite", "upload_button.label": "Dodaj multimediju", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Овај статус не може да се подржи", "status.delete": "Обриши", + "status.direct": "Direct message @{name}", "status.embed": "Угради на сајт", "status.favourite": "Омиљено", "status.load_more": "Учитај још", @@ -269,6 +270,7 @@ "tabs_bar.home": "Почетна", "tabs_bar.local_timeline": "Локално", "tabs_bar.notifications": "Обавештења", + "tabs_bar.search": "Search", "ui.beforeunload": "Ако напустите Мастодонт, изгубићете написани нацрт.", "upload_area.title": "Превуците овде да отпремите", "upload_button.label": "Додај мултимедију", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Detta inlägg kan inte knuffas", "status.delete": "Ta bort", + "status.direct": "Direct message @{name}", "status.embed": "Bädda in", "status.favourite": "Favorit", "status.load_more": "Ladda fler", @@ -269,6 +270,7 @@ "tabs_bar.home": "Hem", "tabs_bar.local_timeline": "Lokal", "tabs_bar.notifications": "Meddelanden", + "tabs_bar.search": "Search", "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/th.json b/app/javascript/mastodon/locales/th.json @@ -239,6 +239,7 @@ "status.block": "Block @{name}", "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", @@ -268,6 +269,7 @@ "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", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Bu gönderi boost edilemez", "status.delete": "Sil", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Favorilere ekle", "status.load_more": "Daha fazla", @@ -269,6 +270,7 @@ "tabs_bar.home": "Ana sayfa", "tabs_bar.local_timeline": "Yerel", "tabs_bar.notifications": "Bildirimler", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Upload için sürükle bırak yapınız", "upload_button.label": "Görsel ekle", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json @@ -240,6 +240,7 @@ "status.block": "Block @{name}", "status.cannot_reblog": "Цей допис не може бути передмухнутий", "status.delete": "Видалити", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "Подобається", "status.load_more": "Завантажити більше", @@ -269,6 +270,7 @@ "tabs_bar.home": "Головна", "tabs_bar.local_timeline": "Локальна", "tabs_bar.notifications": "Сповіщення", + "tabs_bar.search": "Search", "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", "upload_area.title": "Перетягніть сюди, щоб завантажити", "upload_button.label": "Додати медіаконтент", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json @@ -240,6 +240,7 @@ "status.block": "屏蔽 @{name}", "status.cannot_reblog": "无法转嘟这条嘟文", "status.delete": "删除", + "status.direct": "Direct message @{name}", "status.embed": "嵌入", "status.favourite": "收藏", "status.load_more": "加载更多", @@ -269,6 +270,7 @@ "tabs_bar.home": "主页", "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", + "tabs_bar.search": "Search", "ui.beforeunload": "如果你现在离开 Mastodon,你的草稿内容将会被丢弃。", "upload_area.title": "将文件拖放到此处开始上传", "upload_button.label": "上传媒体文件", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json @@ -240,6 +240,7 @@ "status.block": "封鎖 @{name}", "status.cannot_reblog": "這篇文章無法被轉推", "status.delete": "刪除", + "status.direct": "Direct message @{name}", "status.embed": "鑲嵌", "status.favourite": "收藏", "status.load_more": "載入更多", @@ -269,6 +270,7 @@ "tabs_bar.home": "主頁", "tabs_bar.local_timeline": "本站", "tabs_bar.notifications": "通知", + "tabs_bar.search": "Search", "ui.beforeunload": "如果你現在離開 Mastodon,你的草稿內容將會被丟棄。", "upload_area.title": "將檔案拖放至此上載", "upload_button.label": "上載媒體檔案", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json @@ -1,24 +1,24 @@ { "account.block": "封鎖 @{name}", "account.block_domain": "隱藏來自 {domain} 的一切貼文", - "account.blocked": "Blocked", - "account.direct": "Direct message @{name}", + "account.blocked": "已被封鎖的", + "account.direct": "給 @{name} 私人訊息", "account.disclaimer_full": "下列資料不一定完整。", - "account.domain_blocked": "Domain hidden", - "account.edit_profile": "編輯用者資訊", + "account.domain_blocked": "域名隱藏", + "account.edit_profile": "編輯使用者資訊", "account.follow": "關注", - "account.followers": "專注者", + "account.followers": "關注者", "account.follows": "正關注", "account.follows_you": "關注你", - "account.hide_reblogs": "Hide boosts from @{name}", + "account.hide_reblogs": "隱藏來自 @{name} 的轉推", "account.media": "媒體", "account.mention": "提到 @{name}", - "account.moved_to": "{name} has moved to:", + "account.moved_to": "{name} 已經移至:", "account.mute": "消音 @{name}", - "account.mute_notifications": "Mute notifications from @{name}", - "account.muted": "Muted", + "account.mute_notifications": "消音來自 @{name} 的通知", + "account.muted": "消音的", "account.posts": "貼文", - "account.posts_with_replies": "Posts with replies", + "account.posts_with_replies": "貼文及回覆", "account.report": "檢舉 @{name}", "account.requested": "正在等待許可", "account.share": "分享 @{name} 的用者資訊", @@ -27,10 +27,10 @@ "account.unblock_domain": "不再隱藏 {domain}", "account.unfollow": "取消關注", "account.unmute": "不再消音 @{name}", - "account.unmute_notifications": "Unmute notifications from @{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,11 +40,11 @@ "bundle_modal_error.retry": "重試", "column.blocks": "封鎖的使用者", "column.community": "本地時間軸", - "column.domain_blocks": "Hidden domains", + "column.domain_blocks": "隱藏域名", "column.favourites": "最愛", "column.follow_requests": "關注請求", "column.home": "家", - "column.lists": "Lists", + "column.lists": "名單", "column.mutes": "消音的使用者", "column.notifications": "通知", "column.pins": "置頂貼文", @@ -58,17 +58,17 @@ "column_header.unpin": "取下", "column_subheading.navigation": "瀏覽", "column_subheading.settings": "設定", - "compose_form.direct_message_warning": "This post will only be visible to all the mentioned users.", - "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.", + "compose_form.direct_message_warning": "此則推文只會被所有提到的使用者看見。", + "compose_form.hashtag_warning": "此則推文將不會在任何主題標籤中看見,只有公開的推文可以用主題標籤來搜尋。", "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": "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": "此媒體已被標註為敏感的", + "compose_form.sensitive.unmarked": "此媒體未被標註為敏感的", + "compose_form.spoiler.marked": "文字隱藏在警告後", + "compose_form.spoiler.unmarked": "文字不是隱藏的", "compose_form.spoiler_placeholder": "內容警告", "confirmation_modal.cancel": "取消", "confirmations.block.confirm": "封鎖", @@ -86,17 +86,17 @@ "embed.instructions": "要內嵌此貼文,請將以下代碼貼進你的網站。", "embed.preview": "看上去會變成這樣:", "emoji_button.activity": "活動", - "emoji_button.custom": "Custom", + "emoji_button.custom": "自訂", "emoji_button.flags": "旗幟", - "emoji_button.food": "食物與飲料", + "emoji_button.food": "飲食", "emoji_button.label": "插入表情符號", "emoji_button.nature": "自然", - "emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻", + "emoji_button.not_found": "沒有表情符號吼!! (╯°□°)╯︵ ┻━┻", "emoji_button.objects": "物件", "emoji_button.people": "人", - "emoji_button.recent": "Frequently used", + "emoji_button.recent": "常用", "emoji_button.search": "搜尋…", - "emoji_button.search_results": "Search results", + "emoji_button.search_results": "搜尋結果", "emoji_button.symbols": "符號", "emoji_button.travel": "旅遊與地點", "empty_column.community": "本地時間軸是空的。公開寫點什麼吧!", @@ -108,8 +108,8 @@ "empty_column.public": "這裡什麼都沒有!公開寫些什麼,或是關注其他副本的使用者。", "follow_request.authorize": "授權", "follow_request.reject": "拒絕", - "getting_started.appsshort": "Apps", - "getting_started.faq": "FAQ", + "getting_started.appsshort": "應用程式", + "getting_started.faq": "常見問答", "getting_started.heading": "馬上開始", "getting_started.open_source_notice": "Mastodon 是開源軟體。你可以在 GitHub {github} 上做出貢獻或是回報問題。", "getting_started.userguide": "使用者指南", @@ -119,19 +119,19 @@ "home.column_settings.show_reblogs": "顯示轉推", "home.column_settings.show_replies": "顯示回應", "home.settings": "欄位設定", - "keyboard_shortcuts.back": "to navigate back", - "keyboard_shortcuts.boost": "to boost", + "keyboard_shortcuts.back": "回到上一個", + "keyboard_shortcuts.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.compose": "焦點移至撰寫文字區塊", + "keyboard_shortcuts.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.favourite": "收藏", + "keyboard_shortcuts.heading": "鍵盤快速鍵", + "keyboard_shortcuts.hotkey": "快速鍵", "keyboard_shortcuts.legend": "to display this legend", - "keyboard_shortcuts.mention": "to mention author", - "keyboard_shortcuts.reply": "to reply", + "keyboard_shortcuts.mention": "到提到的作者", + "keyboard_shortcuts.reply": "到回應", "keyboard_shortcuts.search": "to focus search", "keyboard_shortcuts.toot": "to start a brand new post", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", @@ -150,11 +150,11 @@ "loading_indicator.label": "讀取中...", "media_gallery.toggle_visible": "切換可見性", "missing_indicator.label": "找不到", - "missing_indicator.sublabel": "This resource could not be found", - "mute_modal.hide_notifications": "Hide notifications from this user?", + "missing_indicator.sublabel": "找不到此資源", + "mute_modal.hide_notifications": "隱藏來自這個使用者的通知?", "navigation_bar.blocks": "封鎖的使用者", "navigation_bar.community_timeline": "本地時間軸", - "navigation_bar.domain_blocks": "Hidden domains", + "navigation_bar.domain_blocks": "隱藏的域名", "navigation_bar.edit_profile": "編輯用者資訊", "navigation_bar.favourites": "最愛", "navigation_bar.follow_requests": "關注請求", @@ -197,7 +197,7 @@ "onboarding.page_six.github": "Mastodon 是自由的開源軟體。你可以在 {github} 上回報臭蟲、請求新功能或是做出貢獻。", "onboarding.page_six.guidelines": "社群指南", "onboarding.page_six.read_guidelines": "請閱讀 {domain} 的 {guidelines} !", - "onboarding.page_six.various_app": "行動 apps", + "onboarding.page_six.various_app": "行動版應用程式", "onboarding.page_three.profile": "編輯你的大頭貼、自傳和顯示名稱。你也可以在這邊找到其他設定。", "onboarding.page_three.search": "利用搜尋列來找到其他人或是主題標籤,像是 {illustration} 或 {introductions} 。用完整的帳號名稱來找不在這個副本上的使用者。", "onboarding.page_two.compose": "在編輯欄寫些什麼。可以上傳圖片、改變隱私設定或是用下面的圖示加上內容警告。", @@ -211,13 +211,13 @@ "privacy.public.short": "公開貼", "privacy.unlisted.long": "不要貼到公開時間軸", "privacy.unlisted.short": "不列出來", - "regeneration_indicator.label": "Loading…", + "regeneration_indicator.label": "載入中…", "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", + "relative_time.days": "{number} 天", + "relative_time.hours": "{number} 時", + "relative_time.just_now": "現在", + "relative_time.minutes": "{number} 分", + "relative_time.seconds": "{number} 秒", "reply_indicator.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?", @@ -226,20 +226,21 @@ "report.submit": "送出", "report.target": "通報中", "search.placeholder": "搜尋", - "search_popout.search_format": "Advanced search format", + "search_popout.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.hashtag": "主題標籤", + "search_popout.tips.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": "Posts", + "search_popout.tips.user": "使用者", + "search_results.accounts": "人", + "search_results.hashtags": "主題標籤", + "search_results.statuses": "推文", "search_results.total": "{count, number} 項結果", "standalone.public_title": "站點一瞥…", - "status.block": "Block @{name}", + "status.block": "封鎖 @{name}", "status.cannot_reblog": "此貼文無法轉推", "status.delete": "刪除", + "status.direct": "Direct message @{name}", "status.embed": "Embed", "status.favourite": "收藏", "status.load_more": "載入更多", @@ -250,7 +251,7 @@ "status.mute_conversation": "消音對話", "status.open": "展開這個狀態", "status.pin": "置頂到個人資訊頁", - "status.pinned": "Pinned post", + "status.pinned": "置頂的推文", "status.reblog": "轉推", "status.reblogged_by": "{name} 轉推了", "status.reply": "回應", @@ -258,7 +259,7 @@ "status.report": "通報 @{name}", "status.sensitive_toggle": "點來看", "status.sensitive_warning": "敏感內容", - "status.share": "Share", + "status.share": "分享", "status.show_less": "看少點", "status.show_less_all": "Show less for all", "status.show_more": "看更多", @@ -269,17 +270,18 @@ "tabs_bar.home": "家", "tabs_bar.local_timeline": "本地", "tabs_bar.notifications": "通知", - "ui.beforeunload": "Your draft will be lost if you leave Mastodon.", + "tabs_bar.search": "Search", + "ui.beforeunload": "如果離開 Mastodon,你的草稿將會不見。", "upload_area.title": "拖放來上傳", "upload_button.label": "增加媒體", - "upload_form.description": "Describe for the visually impaired", - "upload_form.focus": "Crop", + "upload_form.description": "爲視障者加上描述", + "upload_form.focus": "裁切", "upload_form.undo": "復原", "upload_progress.label": "上傳中...", "video.close": "關閉影片", - "video.exit_fullscreen": "退出全熒幕", + "video.exit_fullscreen": "退出全螢幕", "video.expand": "展開影片", - "video.fullscreen": "全熒幕", + "video.fullscreen": "全螢幕", "video.hide": "隱藏影片", "video.mute": "消音", "video.pause": "暫停", diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js @@ -21,24 +21,30 @@ const normalizeContext = (state, id, ancestors, descendants) => { }); }; -const deleteFromContexts = (state, id) => { - state.getIn(['descendants', id], ImmutableList()).forEach(descendantId => { - state = state.updateIn(['ancestors', descendantId], ImmutableList(), list => list.filterNot(itemId => itemId === id)); - }); +const deleteFromContexts = (immutableState, ids) => immutableState.withMutations(state => { + state.update('ancestors', immutableAncestors => immutableAncestors.withMutations(ancestors => { + state.update('descendants', immutableDescendants => immutableDescendants.withMutations(descendants => { + ids.forEach(id => { + descendants.get(id, ImmutableList()).forEach(descendantId => { + ancestors.update(descendantId, ImmutableList(), list => list.filterNot(itemId => itemId === id)); + }); - state.getIn(['ancestors', id], ImmutableList()).forEach(ancestorId => { - state = state.updateIn(['descendants', ancestorId], ImmutableList(), list => list.filterNot(itemId => itemId === id)); - }); + ancestors.get(id, ImmutableList()).forEach(ancestorId => { + descendants.update(ancestorId, ImmutableList(), list => list.filterNot(itemId => itemId === id)); + }); - state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]); + descendants.delete(id); + ancestors.delete(id); + }); + })); + })); +}); - return state; -}; +const filterContexts = (state, relationship, statuses) => { + const ownedStatusIds = statuses.filter(status => status.get('account') === relationship.id) + .map(status => status.get('id')); -const filterContexts = (state, relationship) => { - return state.map( - statuses => statuses.filter( - status => status.get('account') !== relationship.id)); + return deleteFromContexts(state, ownedStatusIds); }; const updateContext = (state, status, references) => { @@ -61,11 +67,11 @@ export default function contexts(state = initialState, action) { switch(action.type) { case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_MUTE_SUCCESS: - return filterContexts(state, action.relationship); + return filterContexts(state, action.relationship, action.statuses); case CONTEXT_FETCH_SUCCESS: return normalizeContext(state, action.id, action.ancestors, action.descendants); case TIMELINE_DELETE: - return deleteFromContexts(state, action.id); + return deleteFromContexts(state, [action.id]); case TIMELINE_CONTEXT_UPDATE: return updateContext(state, action.status, action.references); default: diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss @@ -563,3 +563,57 @@ border-color: rgba(lighten($error-red, 12%), 0.5); } } + +.account__header__fields { + border-collapse: collapse; + padding: 0; + margin: 15px -15px -15px; + border: 0 none; + border-top: 1px solid lighten($ui-base-color, 4%); + border-bottom: 1px solid lighten($ui-base-color, 4%); + + th, + td { + padding: 15px; + padding-left: 15px; + border: 0 none; + border-bottom: 1px solid lighten($ui-base-color, 4%); + vertical-align: middle; + } + + th { + padding-left: 15px; + font-weight: 500; + text-align: center; + width: 94px; + color: $ui-secondary-color; + background: rgba(darken($ui-base-color, 8%), 0.5); + } + + td { + color: $ui-primary-color; + text-align: center; + width: 100%; + padding-left: 0; + } + + a { + color: $ui-highlight-color; + text-decoration: none; + + &:hover, + &:focus, + &:active { + text-decoration: underline; + } + } + + tr { + &:last-child { + th, + td { + border-bottom: 0; + } + } + } +} diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss @@ -145,6 +145,11 @@ border: 0; background: transparent; border-bottom: 1px solid $ui-base-color; + + &.section-break { + margin: 30px 0; + border-bottom: 2px solid $ui-base-lighter-color; + } } .muted-hint { @@ -330,6 +335,36 @@ } } +.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: $ui-base-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; + } +} + .batch-form-box { display: flex; flex-wrap: wrap; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss @@ -2474,6 +2474,10 @@ a.status-card { } } +.load-gap { + border-bottom: 1px solid lighten($ui-base-color, 8%); +} + .regeneration-indicator { text-align: center; font-size: 16px; @@ -5172,3 +5176,40 @@ noscript { background: lighten($ui-highlight-color, 7%); } } + +.account__header .account__header__fields { + font-size: 14px; + line-height: 20px; + overflow: hidden; + border-collapse: collapse; + margin: 20px -10px -20px; + border-bottom: 0; + + tr { + border-top: 1px solid lighten($ui-base-color, 8%); + text-align: center; + } + + th, + td { + padding: 14px 20px; + vertical-align: middle; + max-height: 40px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + th { + color: $ui-primary-color; + background: darken($ui-base-color, 4%); + max-width: 120px; + font-weight: 500; + } + + td { + flex: auto; + color: $primary-text-color; + background: $ui-base-color; + } +} diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss @@ -15,6 +15,18 @@ code { overflow: hidden; } + .row { + display: flex; + margin: 0 -5px; + + .input { + box-sizing: border-box; + flex: 1 1 auto; + width: 50%; + padding: 0 5px; + } + } + span.hint { display: block; color: $ui-primary-color; diff --git a/app/javascript/styles/mastodon/stream_entries.scss b/app/javascript/styles/mastodon/stream_entries.scss @@ -6,7 +6,8 @@ background: $simple-background-color; .detailed-status.light, - .status.light { + .status.light, + .more.light { border-bottom: 1px solid $ui-secondary-color; animation: none; } @@ -290,6 +291,17 @@ text-decoration: underline; } } + + .more { + color: $classic-primary-color; + display: block; + padding: 14px; + text-align: center; + + &:not(:hover) { + text-decoration: none; + } + } } .embed { diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb @@ -19,6 +19,9 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base 'Emoji' => 'toot:Emoji', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' }, 'featured' => 'toot:featured', + 'schema' => 'http://schema.org#', + 'PropertyValue' => 'schema:PropertyValue', + 'value' => 'schema:value', }, ], }.freeze diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb @@ -71,6 +71,11 @@ class Formatter html.html_safe # rubocop:disable Rails/OutputSafety end + def format_field(account, str) + return reformat(str).html_safe unless account.local? # rubocop:disable Rails/OutputSafety + encode_and_link_urls(str, me: true).html_safe # rubocop:disable Rails/OutputSafety + end + def linkify(text) html = encode_and_link_urls(text) html = simple_format(html, {}, sanitize: false) @@ -85,12 +90,17 @@ class Formatter HTMLEntities.new.encode(html) end - def encode_and_link_urls(html, accounts = nil) + def encode_and_link_urls(html, accounts = nil, options = {}) entities = Extractor.extract_entities_with_indices(html, extract_url_without_protocol: false) + if accounts.is_a?(Hash) + options = accounts + accounts = nil + end + rewrite(html.dup, entities) do |entity| if entity[:url] - link_to_url(entity) + link_to_url(entity, options) elsif entity[:hashtag] link_to_hashtag(entity) elsif entity[:screen_name] @@ -177,10 +187,12 @@ class Formatter result.flatten.join end - def link_to_url(entity) + def link_to_url(entity, options = {}) url = Addressable::URI.parse(entity[:url]) html_attrs = { target: '_blank', rel: 'nofollow noopener' } + html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me] + Twitter::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs) rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError encode(entity[:url]) diff --git a/app/models/account.rb b/app/models/account.rb @@ -44,6 +44,7 @@ # memorial :boolean default(FALSE), not null # moved_to_account_id :integer # featured_collection_url :string +# fields :jsonb # class Account < ApplicationRecord @@ -124,6 +125,7 @@ class Account < ApplicationRecord scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) } delegate :email, + :unconfirmed_email, :current_sign_in_ip, :current_sign_in_at, :confirmed?, @@ -188,6 +190,30 @@ class Account < ApplicationRecord @keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key) end + def fields + (self[:fields] || []).map { |f| Field.new(self, f) } + end + + def fields_attributes=(attributes) + fields = [] + + attributes.each_value do |attr| + next if attr[:name].blank? + fields << attr + end + + self[:fields] = fields + end + + def build_fields + return if fields.size >= 4 + + raw_fields = self[:fields] || [] + add_fields = 4 - raw_fields.size + add_fields.times { raw_fields << { name: '', value: '' } } + self.fields = raw_fields + end + def magic_key modulus, exponent = [keypair.public_key.n, keypair.public_key.e].map do |component| result = [] @@ -237,17 +263,28 @@ class Account < ApplicationRecord shared_inbox_url.presence || inbox_url end + class Field < ActiveModelSerializers::Model + attributes :name, :value, :account, :errors + + def initialize(account, attr) + @account = account + @name = attr['name'] + @value = attr['value'] + @errors = {} + end + end + class << self def readonly_attributes super - %w(statuses_count following_count followers_count) end def domains - reorder(nil).pluck('distinct accounts.domain') + reorder(nil).pluck(Arel.sql('distinct accounts.domain')) end def inboxes - urls = reorder(nil).where(protocol: :activitypub).pluck("distinct coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url)") + urls = reorder(nil).where(protocol: :activitypub).pluck(Arel.sql("distinct coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url)")) DeliveryFailureTracker.filter(urls) end diff --git a/app/models/admin/action_log.rb b/app/models/admin/action_log.rb @@ -35,6 +35,11 @@ class Admin::ActionLog < ApplicationRecord self.recorded_changes = target.attributes when :update, :promote, :demote self.recorded_changes = target.previous_changes + when :change_email + self.recorded_changes = ActiveSupport::HashWithIndifferentAccess.new( + email: [target.email, nil], + unconfirmed_email: [nil, target.unconfirmed_email] + ) end end end diff --git a/app/models/concerns/status_threading_concern.rb b/app/models/concerns/status_threading_concern.rb @@ -3,8 +3,8 @@ module StatusThreadingConcern extend ActiveSupport::Concern - def ancestors(account = nil) - find_statuses_from_tree_path(ancestor_ids, account) + def ancestors(limit, account = nil) + find_statuses_from_tree_path(ancestor_ids(limit), account) end def descendants(account = nil) @@ -13,14 +13,21 @@ module StatusThreadingConcern private - def ancestor_ids - Rails.cache.fetch("ancestors:#{id}") do - ancestor_statuses.pluck(:id) + def ancestor_ids(limit) + key = "ancestors:#{id}" + ancestors = Rails.cache.fetch(key) + + if ancestors.nil? || ancestors[:limit] < limit + ids = ancestor_statuses(limit).pluck(:id).reverse! + Rails.cache.write key, limit: limit, ids: ids + ids + else + ancestors[:ids].last(limit) end end - def ancestor_statuses - Status.find_by_sql([<<-SQL.squish, id: in_reply_to_id]) + def ancestor_statuses(limit) + Status.find_by_sql([<<-SQL.squish, id: in_reply_to_id, limit: limit]) WITH RECURSIVE search_tree(id, in_reply_to_id, path) AS ( SELECT id, in_reply_to_id, ARRAY[id] @@ -34,7 +41,8 @@ module StatusThreadingConcern ) SELECT id FROM search_tree - ORDER BY path DESC + ORDER BY path + LIMIT :limit SQL end diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb @@ -58,5 +58,9 @@ class CustomEmoji < ApplicationRecord where(shortcode: shortcodes, domain: domain, disabled: false) end + + def search(shortcode) + where('"custom_emojis"."shortcode" ILIKE ?', "%#{shortcode}%") + end end end diff --git a/app/models/custom_emoji_filter.rb b/app/models/custom_emoji_filter.rb @@ -28,7 +28,7 @@ class CustomEmojiFilter when 'by_domain' CustomEmoji.where(domain: value) when 'shortcode' - CustomEmoji.where(shortcode: value) + CustomEmoji.search(value) else raise "Unknown filter: #{key}" end diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb @@ -130,8 +130,9 @@ class MediaAttachment < ApplicationRecord 'pix_fmt' => 'yuv420p', 'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'', 'vsync' => 'cfr', - 'b:v' => '1300K', - 'maxrate' => '500K', + 'c:v' => 'h264', + 'b:v' => '500K', + 'maxrate' => '1300K', 'bufsize' => '1300K', 'crf' => 18, }, diff --git a/app/models/notification.rb b/app/models/notification.rb @@ -81,8 +81,6 @@ class Notification < ApplicationRecord end end - private - def activity_types_from_types(types) types.map { |type| TYPE_CLASS_MAP[type.to_sym] }.compact end diff --git a/app/models/report.rb b/app/models/report.rb @@ -39,4 +39,50 @@ class Report < ApplicationRecord def media_attachments MediaAttachment.where(status_id: status_ids) end + + def assign_to_self!(current_account) + update!(assigned_account_id: current_account.id) + end + + def unassign! + update!(assigned_account_id: nil) + end + + def resolve!(acting_account) + update!(action_taken: true, action_taken_by_account_id: acting_account.id) + end + + def unresolve! + update!(action_taken: false, action_taken_by_account_id: nil) + end + + def unresolved? + !action_taken? + end + + def history + time_range = created_at..updated_at + + sql = [ + Admin::ActionLog.where( + target_type: 'Report', + target_id: id, + created_at: time_range + ).unscope(:order), + + Admin::ActionLog.where( + target_type: 'Account', + target_id: target_account_id, + created_at: time_range + ).unscope(:order), + + Admin::ActionLog.where( + target_type: 'Status', + target_id: status_ids, + created_at: time_range + ).unscope(:order), + ].map { |query| "(#{query.to_sql})" }.join(' UNION ALL ') + + Admin::ActionLog.from("(#{sql}) AS admin_action_logs") + end end diff --git a/app/models/report_note.rb b/app/models/report_note.rb @@ -13,7 +13,7 @@ class ReportNote < ApplicationRecord belongs_to :account - belongs_to :report, inverse_of: :notes + belongs_to :report, inverse_of: :notes, touch: true scope :latest, -> { reorder('created_at ASC') } diff --git a/app/models/status.rb b/app/models/status.rb @@ -322,7 +322,7 @@ class Status < ApplicationRecord self.in_reply_to_account_id = carried_over_reply_to_account_id self.conversation_id = thread.conversation_id if conversation_id.nil? elsif conversation_id.nil? - create_conversation + self.conversation = Conversation.new end end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb @@ -5,6 +5,10 @@ class UserPolicy < ApplicationPolicy staff? && !record.staff? end + def change_email? + staff? && !record.staff? + end + def disable_2fa? admin? && !record.staff? end diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb @@ -11,6 +11,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer has_one :public_key, serializer: ActivityPub::PublicKeySerializer has_many :virtual_tags, key: :tag + has_many :virtual_attachments, key: :attachment attribute :moved_to, if: :moved? @@ -107,10 +108,26 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer object.emojis end + def virtual_attachments + object.fields + end + def moved_to ActivityPub::TagManager.instance.uri_for(object.moved_to_account) end class CustomEmojiSerializer < ActivityPub::EmojiSerializer end + + class Account::FieldSerializer < ActiveModel::Serializer + attributes :type, :name, :value + + def type + 'PropertyValue' + end + + def value + Formatter.instance.format_field(object.account, object.value) + end + end end diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb @@ -9,6 +9,16 @@ class REST::AccountSerializer < ActiveModel::Serializer has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested? + class FieldSerializer < ActiveModel::Serializer + attributes :name, :value + + def value + Formatter.instance.format_field(object.account, object.value) + end + end + + has_many :fields + def id object.id.to_s end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb @@ -22,13 +22,15 @@ class ActivityPub::ProcessAccountService < BaseService create_account if @account.nil? update_account - process_tags(@account) + process_tags end end + return if @account.nil? + after_protocol_change! if protocol_changed? after_key_change! if key_changed? - check_featured_collection! if @account&.featured_collection_url&.present? + check_featured_collection! if @account.featured_collection_url.present? @account rescue Oj::ParseError @@ -68,6 +70,7 @@ class ActivityPub::ProcessAccountService < BaseService @account.display_name = @json['name'] || '' @account.note = @json['summary'] || '' @account.locked = @json['manuallyApprovesFollowers'] || false + @account.fields = property_values || {} end def set_fetchable_attributes! @@ -124,6 +127,11 @@ class ActivityPub::ProcessAccountService < BaseService end end + def property_values + return unless @json['attachment'].is_a?(Array) + @json['attachment'].select { |attachment| attachment['type'] == 'PropertyValue' }.map { |attachment| attachment.slice('name', 'value') } + end + def mismatching_origin?(url) needle = Addressable::URI.parse(url).host haystack = Addressable::URI.parse(@uri).host @@ -189,17 +197,18 @@ class ActivityPub::ProcessAccountService < BaseService { redis: Redis.current, key: "process_account:#{@uri}" } end - def process_tags(account) + def process_tags return if @json['tag'].blank? + as_array(@json['tag']).each do |tag| case tag['type'] when 'Emoji' - process_emoji tag, account + process_emoji tag end end end - def process_emoji(tag, _account) + def process_emoji(tag) return if skip_download? return if tag['name'].blank? || tag['icon'].blank? || tag['icon']['url'].blank? diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb @@ -28,7 +28,7 @@ class PostStatusService < BaseService status = account.statuses.create!(text: text, media_attachments: media || [], thread: in_reply_to, - sensitive: options[:sensitive], + sensitive: (options[:sensitive].nil? ? account.user&.setting_default_sensitive : options[:sensitive]), spoiler_text: options[:spoiler_text] || '', visibility: options[:visibility] || account.user&.setting_default_privacy, language: LanguageDetector.instance.detect(text, account), diff --git a/app/services/search_service.rb b/app/services/search_service.rb @@ -4,7 +4,7 @@ class SearchService < BaseService attr_accessor :query, :account, :limit, :resolve def call(query, limit, resolve = false, account = nil) - @query = query + @query = query.strip @account = account @limit = limit @resolve = resolve diff --git a/app/validators/status_pin_validator.rb b/app/validators/status_pin_validator.rb @@ -5,6 +5,6 @@ class StatusPinValidator < ActiveModel::Validator pin.errors.add(:base, I18n.t('statuses.pin_errors.reblog')) if pin.status.reblog? pin.errors.add(:base, I18n.t('statuses.pin_errors.ownership')) if pin.account_id != pin.status.account_id pin.errors.add(:base, I18n.t('statuses.pin_errors.private')) unless %w(public unlisted).include?(pin.status.visibility) - pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 + pin.errors.add(:base, I18n.t('statuses.pin_errors.limit')) if pin.account.status_pins.count > 4 && pin.account.local? end end diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml @@ -6,8 +6,8 @@ .card__bio %h1.name %span.p-name.emojify= display_name(account) - %small - %span @#{account.local_username_and_domain} + %small< + %span>< @#{account.local_username_and_domain} = fa_icon('lock') if account.locked? - if Setting.show_staff_badge @@ -23,6 +23,14 @@ .bio .account__header__content.p-note.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) + - unless account.fields.empty? + %table.account__header__fields + %tbody + - account.fields.each do |field| + %tr + %th.emojify= field.name + %td.emojify= Formatter.instance.format_field(account, field.value) + .details-counters .counter{ class: active_nav_class(short_account_url(account)) } = link_to short_account_url(account), class: 'u-url u-uid' do diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml @@ -36,9 +36,13 @@ %th= t('admin.accounts.email') %td = @account.user_email - - if @account.user_confirmed? = fa_icon('check') + = table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user) + - if @account.user_unconfirmed_email.present? + %th= t('admin.accounts.unconfirmed_email') + %td + = @account.user_unconfirmed_email %tr %th= t('admin.accounts.login_status') %td diff --git a/app/views/admin/change_emails/show.html.haml b/app/views/admin/change_emails/show.html.haml @@ -0,0 +1,7 @@ +- content_for :page_title do + = t('admin.accounts.change_email.title', username: @account.acct) + += simple_form_for @user, url: admin_account_change_email_path(@account.id) do |f| + = f.input :email, wrapper: :with_label, disabled: true, label: t('admin.accounts.change_email.current_email') + = f.input :unconfirmed_email, wrapper: :with_label, label: t('admin.accounts.change_email.new_email') + = f.button :submit, class: "button", value: t('admin.accounts.change_email.submit') diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml @@ -1,11 +1,9 @@ -%tr - %td - %p - %strong= report_note.account.acct - on +%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) - %br/ - %br/ + %div{ class: 'report-note__comment' } = simple_format(h(report_note.content)) diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml @@ -5,7 +5,7 @@ = t('admin.reports.report', id: @report.id) %div{ style: 'overflow: hidden; margin-bottom: 20px' } - - if !@report.action_taken? + - if @report.unresolved? %div{ style: 'float: right' } = link_to t('admin.reports.silence_account'), admin_report_path(@report, outcome: 'silence'), method: :put, class: 'button' = link_to t('admin.reports.suspend_account'), admin_report_path(@report, outcome: 'suspend'), method: :put, class: 'button' @@ -18,21 +18,28 @@ %table.table.inline-table %tbody %tr + %th= t('admin.reports.created_at') + %td{colspan: 2} + %time.formatted{ datetime: @report.created_at.iso8601 } + %tr %th= t('admin.reports.updated_at') %td{colspan: 2} %time.formatted{ datetime: @report.updated_at.iso8601 } %tr %th= t('admin.reports.status') - %td{colspan: 2} + %td - if @report.action_taken? = t('admin.reports.resolved') - = table_link_to 'envelope-open', t('admin.reports.reopen'), admin_report_path(@report, outcome: 'reopen'), method: :put - else = t('admin.reports.unresolved') + %td{style: "text-align: right; overflow: hidden;"} + - 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= @report.action_taken_by_account.acct + %td{colspan: 2} + = @report.action_taken_by_account.acct - else %tr %th= t('admin.reports.assigned') @@ -47,6 +54,8 @@ - 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') @@ -88,22 +97,28 @@ = 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/ +%hr{ class: "section-break"}/ %h3= t('admin.reports.notes.label') - if @report_notes.length > 0 - .table-wrapper - %table.table - %thead - %tr - %th - %tbody - = render @report_notes + %ul + = render @report_notes -= simple_form_for @report_note, url: admin_report_notes_path do |f| +%h4= t('admin.reports.notes.new_label') += form_for @report_note, url: admin_report_notes_path, html: { class: 'report-note__form' } do |f| = render 'shared/error_messages', object: @report_note - = f.input :content + = f.text_area :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6, class: 'report-note__textarea' = f.hidden_field :report_id - = f.button :button, t('admin.reports.notes.create'), type: :submit - = f.button :button, t('admin.reports.notes.create_and_resolve'), type: :submit, name: :create_and_resolve + %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' + +- if @report_history.length > 0 + %h3= t('admin.reports.history') + + %ul + = render @report_history diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml @@ -19,6 +19,16 @@ .fields-group = f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked') + .fields-group + .input.with_block_label + %label= t('simple_form.labels.defaults.fields') + %span.hint= t('simple_form.hints.defaults.fields') + + = f.simple_fields_for :fields do |fields_f| + .row + = fields_f.input :name, placeholder: t('simple_form.labels.account.fields.name') + = fields_f.input :value, placeholder: t('simple_form.labels.account.fields.value') + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml @@ -14,6 +14,10 @@ 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 partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id } .entry{ class: entry_classes } diff --git a/app/workers/activitypub/synchronize_featured_collection_worker.rb b/app/workers/activitypub/synchronize_featured_collection_worker.rb @@ -3,7 +3,7 @@ class ActivityPub::SynchronizeFeaturedCollectionWorker include Sidekiq::Worker - sidekiq_options queue: 'pull' + sidekiq_options queue: 'pull', unique: :until_executed def perform(account_id) ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id)) diff --git a/bin/bundle b/bin/bundle @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) load Gem.bin_path('bundler', 'bundle') diff --git a/bin/setup b/bin/setup @@ -1,10 +1,9 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") diff --git a/bin/update b/bin/update @@ -1,10 +1,9 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -18,6 +17,9 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') + # Install JavaScript dependencies if using Yarn + system('bin/yarn') + puts "\n== Updating database ==" system! 'bin/rails db:migrate' diff --git a/bin/webpack b/bin/webpack @@ -1,11 +1,7 @@ #!/usr/bin/env ruby -# frozen_string_literal: true -# -# This file was generated by Bundler. -# -# The application 'webpack' is installed as part of a gem, and -# this file is here to facilitate running it. -# + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= ENV["NODE_ENV"] || "development" require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", @@ -14,4 +10,6 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", require "rubygems" require "bundler/setup" -load Gem.bin_path("webpacker", "webpack") +require "webpacker" +require "webpacker/webpack_runner" +Webpacker::WebpackRunner.run(ARGV) diff --git a/bin/webpack-dev-server b/bin/webpack-dev-server @@ -1,11 +1,7 @@ #!/usr/bin/env ruby -# frozen_string_literal: true -# -# This file was generated by Bundler. -# -# The application 'webpack-dev-server' is installed as part of a gem, and -# this file is here to facilitate running it. -# + +ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" +ENV["NODE_ENV"] ||= ENV["NODE_ENV"] || "development" require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", @@ -14,4 +10,6 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", require "rubygems" require "bundler/setup" -load Gem.bin_path("webpacker", "webpack-dev-server") +require "webpacker" +require "webpacker/dev_server_runner" +Webpacker::DevServerRunner.run(ARGV) diff --git a/bin/yarn b/bin/yarn @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do + begin + exec "yarnpkg #{ARGV.join(' ')}" unless Dir.exist?('node_modules') + rescue Errno::ENOENT + $stderr.puts "Yarn executable was not detected in the system." + $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" + exit 1 + end +end diff --git a/config/application.rb b/config/application.rb @@ -23,7 +23,7 @@ require_relative '../lib/mastodon/redis_config' module Mastodon class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 5.1 + config.load_defaults 5.2 # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers @@ -85,15 +85,6 @@ module Mastodon config.active_job.queue_adapter = :sidekiq - config.middleware.insert_before 0, Rack::Cors do - allow do - origins '*' - resource '/@:username', headers: :any, methods: [:get], credentials: false - resource '/api/*', headers: :any, methods: [:post, :put, :delete, :get, :patch, :options], credentials: false, expose: ['Link', 'X-RateLimit-Reset', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-Request-Id'] - resource '/oauth/token', headers: :any, methods: [:post], credentials: false - end - end - config.middleware.use Rack::Attack config.middleware.use Rack::Deflater diff --git a/config/boot.rb b/config/boot.rb @@ -1,7 +1,7 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. -require 'bootsnap' +require 'bootsnap' # Speed up boot time by caching expensive operations. Bootsnap.setup( cache_dir: 'tmp/cache', diff --git a/config/deploy.rb b/config/deploy.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -lock '3.10.0' +lock '3.10.1' set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git') set :branch, ENV.fetch('BRANCH', 'master') diff --git a/config/environments/development.rb b/config/environments/development.rb @@ -13,13 +13,14 @@ Rails.application.configure do config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. + # Run rails dev:cache to toggle caching. if Rails.root.join('tmp/caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :redis_store, ENV['REDIS_URL'], REDIS_CACHE_PARAMS config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}", + 'Cache-Control' => "public, max-age=#{2.days.to_i}", } else config.action_controller.perform_caching = false diff --git a/config/environments/production.rb b/config/environments/production.rb @@ -15,6 +15,10 @@ Rails.application.configure do config.action_controller.perform_caching = true config.action_controller.asset_host = ENV['CDN_HOST'] if ENV.key?('CDN_HOST') + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true + # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? diff --git a/config/environments/test.rb b/config/environments/test.rb @@ -15,7 +15,7 @@ Rails.application.configure do # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } config.assets.digest = false @@ -59,3 +59,14 @@ Rails.application.configure do end Paperclip::Attachment.default_options[:path] = "#{Rails.root}/spec/test_files/:class/:id_partition/:style.:extension" + +# set fake_data for pam, don't do real calls, just use fake data +if ENV['PAM_ENABLED'] == 'true' + Rpam2.fake_data = + { + usernames: Set['pam_user1', 'pam_user2'], + servicenames: Set['pam_test', 'pam_test_controlled'], + password: '123456', + env: { email: 'pam@example.com' } + } +end diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb @@ -0,0 +1,20 @@ +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +# Rails.application.config.content_security_policy do |p| +# p.default_src :self, :https +# p.font_src :self, :https, :data +# p.img_src :self, :https, :data +# p.object_src :none +# p.script_src :self, :https +# p.style_src :self, :https, :unsafe_inline +# +# # Specify URI for violation reports +# # p.report_uri "/csp-violation-report-endpoint" +# end + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb @@ -0,0 +1,26 @@ +# Be sure to restart your server when you modify this file. + +# Avoid CORS issues when API is called from the frontend app. +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. + +# Read more: https://github.com/cyu/rack-cors + +Rails.application.config.middleware.insert_before 0, Rack::Cors do + allow do + origins '*' + + resource '/@:username', + headers: :any, + methods: [:get], + credentials: false + resource '/api/*', + headers: :any, + methods: [:post, :put, :delete, :get, :patch, :options], + credentials: false, + expose: ['Link', 'X-RateLimit-Reset', 'X-RateLimit-Limit', 'X-RateLimit-Remaining', 'X-Request-Id'] + resource '/oauth/token', + headers: :any, + methods: [:post], + credentials: false + end +end diff --git a/config/initializers/rack_attack_logging.rb b/config/initializers/rack_attack_logging.rb @@ -0,0 +1,4 @@ +ActiveSupport::Notifications.subscribe('rack.attack') do |_name, _start, _finish, _request_id, req| + next unless [:throttle, :blacklist].include? req.env['rack.attack.match_type'] + Rails.logger.info("Rate limit hit (#{req.env['rack.attack.match_type']}): #{req.ip} #{req.request_method} #{req.fullpath}") +end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -namespace = ENV.fetch('REDIS_NAMESPACE') { nil } +namespace = ENV.fetch('REDIS_NAMESPACE') { nil } redis_params = { url: ENV['REDIS_URL'] } if namespace - redis_params [:namespace] = namespace + redis_params[:namespace] = namespace end Sidekiq.configure_server do |config| @@ -18,3 +18,5 @@ end Sidekiq.configure_client do |config| config.redis = redis_params end + +Sidekiq::Logging.logger.level = ::Logger::const_get(ENV.fetch('RAILS_LOG_LEVEL', 'info').upcase.to_s) diff --git a/config/locales/ar.yml b/config/locales/ar.yml @@ -204,6 +204,7 @@ ar: severity: الشدة show: affected_accounts: + one: هناك حساب واحد متأثر في قاعدة البيانات other: هناك %{count} حسابات في قاعدة البيانات متأثرة بذلك retroactive: silence: إلغاء الكتم عن كافة الحسابات المتواجدة على هذا النطاق @@ -262,6 +263,7 @@ ar: settings: activity_api_enabled: desc_html: عدد المنشورات المحلية و المستخدمين النشطين و التسجيلات الأسبوعية الجديدة + title: نشر مُجمل الإحصائيات عن نشاط المستخدمين bootstrap_timeline_accounts: title: الإشتراكات الإفتراضية للمستخدمين الجدد contact_information: @@ -274,12 +276,14 @@ ar: title: نشر عدد مثيلات الخوادم التي تم مصادفتها registrations: closed_message: + desc_html: يتم عرضه على الصفحة الرئيسية عندما يتم غلق تسجيل الحسابات الجديدة. يمكنكم إستخدام علامات الأيتش تي أم أل HTML title: رسالة التسجيلات المقفلة deletion: desc_html: السماح لأي مستخدم إغلاق حسابه title: السماح بحذف الحسابات min_invite_role: disabled: لا أحد + title: المستخدِمون المصرح لهم لإرسال الدعوات open: desc_html: السماح للجميع بإنشاء حساب title: فتح التسجيل diff --git a/config/locales/devise.fi.yml b/config/locales/devise.fi.yml @@ -2,60 +2,81 @@ fi: devise: confirmations: - confirmed: Sähköpostisi on onnistuneesti vahvistettu. - send_instructions: Saat kohta sähköpostiisi ohjeet kuinka voit aktivoida tilisi. - send_paranoid_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet sen varmentamiseen. + confirmed: Sähköpostiosoitteen vahvistus onnistui. + send_instructions: Saat kohta sähköpostitse ohjeet, kuinka vahvistat sähköpostiosoitteen. Jos et saa viestiä, tarkista roskapostikansio. + send_paranoid_instructions: Jos sähköpostiosoite on tietokannassamme, saat pian ohjeet, kuinka vahvistat osoitteen. Jos et saa viestiä, tarkista roskapostikansio. failure: already_authenticated: Olet jo kirjautunut sisään. - inactive: Tiliäsi ei ole viellä aktivoitu. + inactive: Tiliäsi ei ole vielä aktivoitu. invalid: Virheellinen %{authentication_keys} tai salasana. - last_attempt: Sinulla on yksi yritys jäljellä tai tili lukitaan. + last_attempt: Voit yrittää enää kerran, ennen kuin tili lukitaan. locked: Tili on lukittu. not_found_in_database: Virheellinen %{authentication_keys} tai salasana. - timeout: Sessiosi on umpeutunut. Kirjaudu sisään jatkaaksesi. - unauthenticated: Sinun tarvitsee kirjautua sisään tai rekisteröityä jatkaaksesi. - unconfirmed: Sinun tarvitsee varmentaa sähköpostisi jatkaaksesi. + timeout: Istunto on umpeutunut. Jatka kirjautumalla sisään. + unauthenticated: Kirjaudu sisään tai rekisteröidy, jos haluat jatkaa. + unconfirmed: Vahvista sähköpostiosoitteesi, ennen kuin jatkat. mailer: confirmation_instructions: - subject: 'Mastodon: Varmistus ohjeet' + action: Vahvista sähköpostiosoite + explanation: Olet luonut tilin palvelimelle %{host} käyttäen tätä sähköpostiosoitetta. Aktivoi tili yhdellä klikkauksella. Jos et luonut tiliä itse, voit jättää tämän viestin huomiotta. + extra_html: Katso myös <a href="%{terms_path}">instanssin säännöt</a> ja <a href="%{policy_path}">käyttöehdot</a>. + subject: 'Mastodon: Vahvistusohjeet - %{instance}' + title: Vahvista sähköpostiosoite + email_changed: + explanation: 'Tilin sähköpostiosoitteeksi vaihdetaan:' + extra: Jos et vaihtanut sähköpostiosoitettasi, joku muu on todennäköisesti päässyt käyttämään tiliäsi. Vaihda salasanasi viipymättä. Jos et pääse kirjautumaan tilillesi, ota yhteyttä instanssin ylläpitäjään. + subject: 'Mastodon: Sähköpostiosoite vaihdettu' + title: Uusi sähköpostiosoite password_change: + explanation: Tilin salasana on vaihdettu. + extra: Jos et vaihtanut salasanaasi, joku muu on todennäköisesti päässyt käyttämään tiliäsi. Vaihda salasanasi viipymättä. Jos et pääse kirjautumaan tilillesi, ota yhteyttä instanssin ylläpitäjään. subject: 'Mastodon: Salasana vaihdettu' + title: Salasana vaihdettu + reconfirmation_instructions: + explanation: Vahvista uusi sähköpostiosoite, niin muutos astuu voimaan. + extra: Jos et tehnyt muutosta itse, voit jättää tämän viestin huomiotta. Mastodon-tilin sähköpostiosoitetta ei vaihdeta, ennen kuin klikkaat yllä olevaa linkkiä. + subject: 'Mastodon: Vahvista sähköpostiosoite - %{instance}' + title: Vahvista sähköpostiosoite reset_password_instructions: - subject: 'Mastodon: Salasanan vaihto ohjeet' + action: Vaihda salasana + explanation: Pyysit tilillesi uuden salasanan. + extra: Jos et tehnyt pyyntöä itse, voit jättää tämän viestin huomiotta. Salasanaasi ei vaihdeta, ennen kuin klikkaat yllä olevaa linkkiä ja luot uuden salasanan. + subject: 'Mastodon: Ohjeet salasanan vaihtoon' + title: Salasanan vaihto unlock_instructions: - subject: 'Mastodon: Avauksen ohjeet' + subject: 'Mastodon: Ohjeet lukituksen poistoon' omniauth_callbacks: - failure: Varmennus %{kind} epäonnistui koska "%{reason}". - success: Onnistuneesti varmennettu %{kind} tilillä. + failure: Tunnistautuminen lähteestä %{kind} ei onnistunut, koska "%{reason}". + success: Tunnistautuminen tililtä %{kind} onnistui. passwords: - no_token: Et pääse tälle sivulle ilman salasanan vaihto sähköpostia. Jos tulet tämmöisestä postista, varmista että sinulla on täydellinen URL. - send_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet salasanan palautukseen. - send_paranoid_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet salasanan palautukseen. - updated: Salasanasi vaihdettu onnistuneesti. Olet nyt kirjautunut sisään. - updated_not_active: Salasanasi vaihdettu onnistuneesti. + no_token: Tälle sivulle pääsee vain salasananvaihtoviestin kautta. Jos tiedät tulevasi salasananvaihtoviestin kautta, varmista, että käytät koko viestissä mainittua URL-osoitetta. + send_instructions: Jos sähköpostiosoite on tietokannassamme, siihen lähetetään pian linkki salasanan vaihtoon. Jos et saa viestiä, tarkista roskapostikansio. + send_paranoid_instructions: Jos sähköpostiosoite on tietokannassamme, siihen lähetetään pian linkki salasanan vaihtoon. Jos et saa viestiä, tarkista roskapostikansio. + updated: Salasanan vaihto onnistui. Olet nyt kirjautunut sisään. + updated_not_active: Salasanan vaihto onnistui. registrations: - destroyed: Näkemiin! Tilisi on onnistuneesti peruttu. Toivottavasti näemme joskus uudestaan. - signed_up: Tervetuloa! Rekisteröitymisesi onnistu. - signed_up_but_inactive: Olet onnistuneesti rekisteröitynyt, mutta emme voi kirjata sinua sisään koska tiliäsi ei ole viellä aktivoitu. - signed_up_but_locked: Olet onnistuneesti rekisteröitynyt, mutta emme voi kirjata sinua sisään koska tilisi on lukittu. - signed_up_but_unconfirmed: Varmistuslinkki on lähetty sähköpostiisi. Seuraa sitä jotta tilisi voidaan aktivoida. - update_needs_confirmation: Tilisi on onnistuneesti päivitetty, mutta meidän tarvitsee vahvistaa sinun uusi sähköpostisi. Tarkista sähköpostisi ja seuraa viestissä tullutta linkkiä varmistaaksesi uuden osoitteen.. - updated: Tilisi on onnistuneesti päivitetty. + destroyed: Tilisi on poistettu. Näkemiin ja tervetuloa uudelleen! + signed_up: Tervetuloa! Rekisteröityminen onnistui. + signed_up_but_inactive: Rekisteröityminen onnistui. Emme kuitenkaan voi kirjata sinua sisään, sillä tiliäsi ei ole vielä aktivoitu. + signed_up_but_locked: Rekisteröityminen onnistui. Emme kuitenkaan voi kirjata sinua sisään, sillä tilisi on lukittu. + signed_up_but_unconfirmed: Sähköpostiosoitteeseesi on lähetetty vahvistuslinkki. Aktivoi tili seuraamalla linkkiä. Jos et saanut viestiä, tarkista roskapostikansio. + update_needs_confirmation: Tilin päivitys onnistui, mutta uusi sähköpostiosoite on vahvistettava. Tarkista sähköpostisi ja vahvista uusi sähköpostiosoite seuraamalla vahvistuslinkkiä. Jos et saanut viestiä, tarkista roskapostikansio. + updated: Tilin päivitys onnistui. sessions: - already_signed_out: Ulos kirjautuminen onnistui. + already_signed_out: Uloskirjautuminen onnistui. signed_in: Sisäänkirjautuminen onnistui. - signed_out: Ulos kirjautuminen onnistui. + signed_out: Uloskirjautuminen onnistui. unlocks: - send_instructions: Saat sähköpostiisi pian ohjeet, jolla voit avata tilisi uudestaan. - send_paranoid_instructions: Jos tilisi on olemassa, saat sähköpostiisi pian ohjeet tilisi avaamiseen. - unlocked: Tilisi on avattu onnistuneesti. Kirjaudu normaalisti sisään. + send_instructions: Saat pian sähköpostitse ohjeet tilin lukituksen poistoon. Jos et saanut viestiä, tarkista roskapostikansio. + send_paranoid_instructions: Jos tili on olemassa, saat pian sähköpostitse ohjeet tilin lukituksen poistoon. Jos et saanut viestiä, tarkista roskapostikansio. + unlocked: Tilin lukituksen poisto onnistui. Jatka kirjautumalla sisään. errors: messages: - already_confirmed: on jo varmistettu. Yritä kirjautua sisään - confirmation_period_expired: pitää varmistaa %{period} sisällä, ole hyvä ja pyydä uusi - expired: on erääntynyt, ole hyvä ja pyydä uusi + already_confirmed: on jo vahvistettu. Yritä kirjautua sisään + confirmation_period_expired: on vahvistettava %{period} sisällä. Pyydä uusi + expired: on vanhentunut. Pyydä uusi not_found: ei löydy not_locked: ei ollut lukittu not_saved: - one: '1 virhe esti %{resource} tallennuksen:' - other: "%{count} virhettä esti %{resource} tallennuksen:" + one: '1 virhe esti kohteen %{resource} tallennuksen:' + other: "%{count} virhettä esti kohteen %{resource} tallennuksen:" diff --git a/config/locales/devise.sk.yml b/config/locales/devise.sk.yml @@ -13,13 +13,13 @@ sk: locked: Váš účet je zamknutý. not_found_in_database: Nesprávny %{authentication_keys} alebo heslo. timeout: Vaša aktívna sezóna vypršala. Pre pokračovanie sa prosím znovu prihláste. - unauthenticated: Pred pokračovaním sa musíte zaregistrovať alebo prihlásiť. - unconfirmed: Pred pokračovaním musíte potvrdiť svoj email. + unauthenticated: K pokračovaniu sa musíš zaregistrovať alebo prihlásiť. + unconfirmed: Pred pokračovaním musíš potvrdiť svoj email. mailer: confirmation_instructions: - action: Potvrite emailovú adresu - explanation: S touto email adresou ste si vytvoril/a účet na %{host}. Si iba jeden klik od jeho aktivácie. Pokiaľ ste to ale nebol/a vy, prosím ignoruj tento email. - extra_html: Prosím pozri sa aj na <a href="%{terms_path}"> pravidla tohto servera,</a> a <a href="%{policy_path}"> naše užívaťeľské podiemky</a>. + action: Potvŕď emailovú adresu + explanation: S touto emailovou adresou si si vytvoril/a účet na %{host}. Si iba jeden klik od jeho aktivácie. Pokiaľ si to ale nebol/a ty, prosím ignoruj tento email. + extra_html: Prosím pozri sa aj na <a href="%{terms_path}"> pravidlá tohto servera,</a> a <a href="%{policy_path}"> naše užívaťeľské podiemky</a>. subject: 'Mastodon: Potvrdzovacie inštrukcie pre %{instance}' title: Potvrď emailovú adresu email_changed: diff --git a/config/locales/doorkeeper.fi.yml b/config/locales/doorkeeper.fi.yml @@ -3,17 +3,19 @@ fi: activerecord: attributes: doorkeeper/application: - name: Nimi - redirect_uri: Uudelleenohjaus URI + name: Sovelluksen nimi + redirect_uri: Uudelleenohjauksen URI + scopes: Oikeudet + website: Sovelluksen verkkosivu errors: models: doorkeeper/application: attributes: redirect_uri: fragment_present: ei voi sisältää osia. - invalid_uri: pitää olla validi URI. - relative_uri: pitää olla täydellinen URI. - secured_uri: pitää olla HTTPS/SSL URI. + invalid_uri: on oltava kelvollinen URI. + relative_uri: on oltava täydellinen URI. + secured_uri: on oltava HTTPS/SSL-URI. doorkeeper: applications: buttons: @@ -25,89 +27,93 @@ fi: confirmations: destroy: Oletko varma? edit: - title: Muokkaa applikaatiota + title: Muokkaa sovellusta form: - error: Whoops! Tarkista lomakkeesi mahdollisten virheiden varalta + error: Hups! Tarkista, että lomakkeessa ei ole virheitä help: native_redirect_uri: Käytä %{native_redirect_uri} paikallisiin testeihin - redirect_uri: Käytä yhtä riviä per URI - scopes: Erota scopet välilyönnein. Jätä tyhjäksi käyteksi oletus scopeja. + redirect_uri: Lisää jokainen URI omalle rivilleen + scopes: Erota oikeudet välilyönnein. Jos kenttä jätetään tyhjäksi, käytetään oletusoikeuksia. index: - callback_url: Callback URL + application: Sovellus + callback_url: Takaisinkutsu-URL + delete: Poista name: Nimi - new: Uusi applikaatio - title: Sinun applikaatiosi + new: Uusi sovellus + scopes: Oikeudet + show: Näytä + title: Omat sovellukset new: - title: Uusi applikaatio + title: Uusi sovellus show: actions: Toiminnot - application_id: Applikaation Id - callback_urls: Callback urls - scopes: Scopet - secret: Salainen avain - title: 'Applikaatio: %{name}' + application_id: Asiakasohjelman tunnus + callback_urls: Takaisinkutsu-URL:t + scopes: Oikeudet + secret: Asiakasohjelman salainen avain + title: 'Sovellus: %{name}' authorizations: buttons: authorize: Valtuuta deny: Evää error: - title: Virhe on tapahtunut + title: Tapahtui virhe new: able_to: Se voi - prompt: Applikaatio %{client_name} pyytää lupaa tilillesi + prompt: Sovellus %{client_name} pyytää lupaa käyttää tiliäsi title: Valtuutus vaaditaan show: - title: Kopioi tämä valtuutuskoodi ja liitä se applikaatioon. + title: Kopioi tämä valtuutuskoodi ja liitä se sovellukseen. authorized_applications: buttons: - revoke: Evää + revoke: Peru confirmations: revoke: Oletko varma? index: - application: Applikaatio + application: Sovellus created_at: Valtuutettu date_format: "%Y-%m-%d %H:%M:%S" - scopes: Scopet - title: Valtuuttamasi applikaatiot + scopes: Oikeudet + title: Valtuutetut sovellukset errors: messages: - access_denied: Resurssin omistaja tai valtuutus palvelin hylkäsi pyynnönr. - credential_flow_not_configured: Resurssin omistajan salasana epäonnistui koska Doorkeeper.configure.resource_owner_from_credentials ei ole konfiguroitu. - invalid_client: Asiakkaan valtuutus epäonnistui koska tuntematon asiakas, asiakas ei sisältänyt valtuutusta, tai tukematon valtuutus tapa. - invalid_grant: Antamasi valtuutus lupa on joko väärä, erääntynyt, peruttu, ei vastaa uudelleenohjaus URI jota käytetään valtuutus pyynnössä, tai se myönnettin toiselle asiakkaalle. - invalid_redirect_uri: Uudelleenohjaus uri ei ole oikein. - invalid_request: Pyynnöstä puutti parametri, sisältää tukemattoman parametri arvonn, tai on korruptoitunut. - invalid_resource_owner: Annetut resurssin omistajan tunnnukset ovat väärät, tai resurssin omistajaa ei löydy - invalid_scope: Pyydetty scope on väärä, tuntemat, tai korruptoitunut. + access_denied: Resurssin omistaja tai valtuutuspalvelin hylkäsi pyynnön. + credential_flow_not_configured: Resurssin omistajan salasana epäonnistui, koska asetusta Doorkeeper.configure.resource_owner_from_credentials ei ole konfiguroitu. + invalid_client: Asiakasohjelman valtuutus epäonnistui, koska asiakas on tuntematon, asiakkaan valtuutus ei ollut mukana tai valtuutustapaa ei tueta. + invalid_grant: Valtuutuslupa on virheellinen, umpeutunut, peruttu, valtuutuspyynnössä käytettyä uudelleenohjaus-URI:tä vastaamaton tai myönnetty toiselle asiakkaalle. + invalid_redirect_uri: Uudelleenohjaus-URI on virheellinen. + invalid_request: Pyynnöstä puuttuu vaadittu parametri, se sisältää tukemattoman parametriarvon tai on muulla tavoin väärin muotoiltu. + invalid_resource_owner: Annetut resurssin omistajan tunnnukset ovat virheelliset, tai resurssin omistajaa ei löydy + invalid_scope: Pyydetyt oikeudet ovat virheellisiä, tuntemattomia tai väärin muotoiltuja. invalid_token: - expired: Access token vanhentunut - revoked: Access token evätty - unknown: Access token väärä - resource_owner_authenticator_not_configured: Resurssin omistajan etsiminen epäonnistui koska Doorkeeper.configure.resource_owner_authenticator ei ole konfiguroitu. - server_error: Valtuutus palvelin kohtasi odottamattoman virheen joka esti sitä täyttämästä pyyntöä. - temporarily_unavailable: Valtuutus palvelin ei voi tällä hetkellä käsitellä pyyntöäsi joko väliaikaisen ruuhkan tai huollon takia. - unauthorized_client: Asiakas ei ole valtuutettu tekemään tätä pyyntöä käyttäen tätä metodia. - unsupported_grant_type: Valtuutus grant type ei ole tuettu valtuutus palvelimella. - unsupported_response_type: Valtuutus palvelin ei tue tätä vastaus tyyppiä. + expired: Käyttöoikeustunnus on vanhentunut + revoked: Käyttöoikeustunnus on peruttu + unknown: Käyttöoikeustunnus on virheellinen + resource_owner_authenticator_not_configured: Resurssin omistajaa ei löytynyt, koska asetusta Doorkeeper.configure.resource_owner_authenticator ei ole konfiguroitu. + server_error: Valtuutuspalvelin kohtasi odottamattoman virheen, joka esti pyynnön täyttämisen. + temporarily_unavailable: Valtuutuspalvelin ei voi tällä hetkellä käsitellä pyyntöä joko väliaikaisen ruuhkan tai huollon takia. + unauthorized_client: Asiakkaalla ei ole valtuuksia tehdä tätä pyyntöä tällä metodilla. + unsupported_grant_type: Valtuutuspalvelin ei tue tätä valtuutusluvan tyyppiä. + unsupported_response_type: Valtuutuspalvelin ei tue tätä vastauksen tyyppiä. flash: applications: create: - notice: Applikaatio luotu. + notice: Sovellus luotu. destroy: - notice: Applikaatio poistettu. + notice: Sovellus poistettu. update: - notice: Applikaatio päivitetty. + notice: Sovellus päivitetty. authorized_applications: destroy: - notice: Applikaatio tuhottu. + notice: Sovellus peruttu. layouts: admin: nav: - applications: Applikaatiot - oauth2_provider: OAuth2 Provider + applications: Sovellukset + oauth2_provider: OAuth2-palveluntarjoaja application: - title: OAuth valtuutus tarvitaan + title: OAuth-valtuutus tarvitaan scopes: - follow: seuraa, estä, peru esto ja lopeta tilien seuraaminen - read: lukea tilin dataa + follow: seurata, estää, perua eston ja lopettaa tilien seuraaminen + read: lukea tilin tietoja write: julkaista puolestasi diff --git a/config/locales/doorkeeper.sk.yml b/config/locales/doorkeeper.sk.yml @@ -63,7 +63,7 @@ sk: prompt: Aplikácia %{client_name} žiada prístup k vašemu účtu title: Je potrebná autorizácia show: - title: Skopírujte tento autorizačný kód a vložte ho do aplikácie. + title: Skopíruj tento autorizačný kód a vlož ho do aplikácie. authorized_applications: buttons: revoke: Zrušiť oprávnenie diff --git a/config/locales/en.yml b/config/locales/en.yml @@ -63,6 +63,13 @@ en: are_you_sure: Are you sure? avatar: Avatar by_domain: Domain + change_email: + changed_msg: Account email successfully changed! + current_email: Current Email + label: Change Email + new_email: New Email + submit: Change Email + title: Change Email for %{username} confirm: Confirm confirmed: Confirmed demote: Demote @@ -131,6 +138,7 @@ en: statuses: Statuses subscribe: Subscribe title: Accounts + unconfirmed_email: Unconfirmed E-mail undo_silenced: Undo silence undo_suspension: Undo suspension unsubscribe: Unsubscribe @@ -139,6 +147,7 @@ en: action_logs: actions: assigned_to_self_report: "%{name} assigned report %{target} to themselves" + change_email_user: "%{name} changed the e-mail address of user %{target}" confirm_user: "%{name} confirmed e-mail address of user %{target}" create_custom_emoji: "%{name} uploaded new emoji %{target}" create_domain_block: "%{name} blocked domain %{target}" @@ -247,8 +256,8 @@ en: title: Filter title: Invites report_notes: - created_msg: Moderation note successfully created! - destroyed_msg: Moderation note successfully destroyed! + created_msg: Report note successfully created! + destroyed_msg: Report note successfully deleted! reports: action_taken_by: Action taken by are_you_sure: Are you sure? @@ -257,18 +266,24 @@ en: 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 delete: Delete - label: Notes + 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 report: 'Report #%{id}' report_contents: Contents reported_account: Reported account @@ -601,6 +616,10 @@ en: missing_resource: Could not find the required redirect URL for your account proceed: Proceed to follow prompt: 'You are going to follow:' + remote_unfollow: + error: Error + title: Title + unfollowed: Unfollowed sessions: activity: Last activity browser: Browser diff --git a/config/locales/eo.yml b/config/locales/eo.yml @@ -24,12 +24,12 @@ eo: within_reach_body: Pluraj aplikaĵoj por iOS, Android, kaj aliaj platformoj danke al API-medio bonveniga por programistoj permesas resti en kontakto kun viaj amikoj ĉie. within_reach_title: Ĉiam kontaktebla generic_description: "%{domain} estas unu servilo en la reto" - hosted_on: Mastodon gastigita en %{domain} + hosted_on: "%{domain} estas nodo de Mastodon" learn_more: Lerni pli other_instances: Listo de nodoj source_code: Fontkodo status_count_after: mesaĝoj - status_count_before: Kiu publikigis + status_count_before: Kie skribiĝis user_count_after: uzantoj user_count_before: Hejmo de what_is_mastodon: Kio estas Mastodon? @@ -358,7 +358,7 @@ eo: warning: Estu tre atenta kun ĉi tiu datumo. Neniam diskonigu ĝin al iu ajn! your_token: Via alira ĵetono auth: - agreement_html: Per registriĝo, vi konsentas kun <a href="%{rules_path}">la reguloj de la nodo</a> kaj <a href="%{terms_path}">niaj uzkondiĉoj</a>. + agreement_html: Per registriĝo, vi konsentas kun <a href="%{rules_path}">la reguloj de nia nodo</a> kaj <a href="%{terms_path}">niaj uzkondiĉoj</a>. change_password: Pasvorto confirm_email: Konfirmi retadreson delete_account: Forigi konton diff --git a/config/locales/es.yml b/config/locales/es.yml @@ -546,7 +546,7 @@ es: quadrillion: Q thousand: K trillion: T - unit: '' + unit: " " pagination: newer: Más nuevo next: Próximo @@ -634,6 +634,15 @@ es: two_factor_authentication: Autenticación de dos factores your_apps: Tus aplicaciones statuses: + attached: + description: 'Adjunto: %{attached}' + image: + one: "%{count} imagen" + other: "%{count} imágenes" + video: + one: "%{count} vídeo" + other: "%{count} vídeos" + content_warning: 'Alerta de contenido: %{warning}' open_in_web: Abrir en web over_character_limit: Límite de caracteres de %{max} superado pin_errors: @@ -682,7 +691,7 @@ es: backup_ready: explanation: Has solicitado una copia completa de tu cuenta de Mastodon. ¡Ya está preparada para descargar! subject: Tu archivo está preparado para descargar - title: Recogida del archivo + 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. diff --git a/config/locales/fi.yml b/config/locales/fi.yml @@ -1,31 +1,35 @@ --- fi: about: - about_mastodon_html: Mastodon on <em>vapaa, avoimeen lähdekoodiin perustuva</em> sosiaalinen verkosto. <em>Hajautettu</em> vaihtoehto kaupallisille alustoille, se välttää eiskit yhden yrityksen monopolisoinnin sinun viestinnässäsi. Valitse palvelin mihin luotat &mdash; minkä tahansa valitset, voit vuorovaikuttaa muiden kanssa. Kuka tahansa voi luoda Mastodon palvelimen ja ottaa osaa <em>sosiaaliseen verkkoon</em> saumattomasti. + about_hashtag_html: Nämä ovat hashtagilla <strong>#%{hashtag}</strong> merkittyjä julkisia tuuttauksia. Voit vastata niihin, jos sinulla on tili jossain päin fediversumia. + about_mastodon_html: Mastodon on sosiaalinen verkosto. Se on toteutettu avoimilla verkkoprotokollilla ja vapailla, avoimen lähdekoodin ohjelmistoilla, ja se toimii hajautetusti samaan tapaan kuin sähköposti. about_this: Tietoja tästä palvelimesta - closed_registrations: Rekisteröityminen tässä instanssissa on juuri nyt suljettu. Mutta! Voit yhdistää täysin samaan, yhteiseen verkostoon rekisteröitymällä jossain toisessa instanssissa. + closed_registrations: Tähän instanssiin ei voi tällä hetkellä rekisteröityä. Voit kuitenkin luoda tilin johonkin toiseen instanssiin ja käyttää samaa verkostoa sitä kautta. contact: Ota yhteyttä contact_missing: Ei asetettu contact_unavailable: Ei saatavilla description_headline: Mikä on %{domain}? - domain_count_after: muuhun palvelimeen + domain_count_after: muuhun instanssiin domain_count_before: Yhdistyneenä extended_description_html: | <h3>Hyvä paikka säännöille</h3> - <p>Pidennettyä kuvausta ei ole vielä asetettu.</p> + <p>Pidempää kuvausta ei ole vielä laadittu.</p> features: - humane_approach_body: Muiden verkostojen virheistä oppien, Mastodon pyrkii tekemään eettisiä valintoja suunnittelussa taistellakseen sosiaalisen median väärinkäyttöä vastaan. - humane_approach_title: Humaanimpi lähestymistapa - not_a_product_body: Mastodon ei ole kaupallinen verkosto. Ei mainoksia, ei tiedonlouhintaa, ei suljettuja sisäpiirejä. Mastodonissa ei ole keskitettyä auktoriteettiä. + humane_approach_body: Mastodonissa otetaan oppia muiden verkostojen virheistä, ja sen suunnittelussa pyritään toimimaan eettisesti ja ehkäisemään sosiaalisen median väärinkäyttöä. + humane_approach_title: Ihmisläheisempi ote + not_a_product_body: Mastodon ei ole kaupallinen verkosto. Ei mainoksia, ei tiedonlouhintaa, ei suljettuja protokollia. Mastodonissa ei ole keskusjohtoa. not_a_product_title: Olet henkilö, et tuote - real_conversation_title: Rakennettu oikealle keskustelulle - within_reach_body: Kehittäjäystävällisen rajapintaekosysteemin ansiosta useita appeja Androidille, iOS:lle ja muille alustoille, jotka mahdollistavat yhteydenpidon ystäviesi kanssa missä vain. + real_conversation_body: 'Voit ilmaista itseäsi niin kuin itse haluat: tilaa on 500 merkkiä, ja sisältövaroituksia voi tehdä monin tavoin.' + real_conversation_title: Tehty oikeaa keskustelua varten + within_reach_body: Rajapintoja on tarjolla moniin eri kehitysympäristöihin, minkä ansiosta iOS:lle, Androidille ja muille alustoille on saatavana useita eri sovelluksia. Näin voit pitää yhteyttä ystäviisi missä vain. within_reach_title: Aina lähellä + generic_description: "%{domain} on yksi verkostoon kuuluvista palvelimista" + hosted_on: Mastodon palvelimella %{domain} learn_more: Lisätietoja other_instances: Muut palvelimet source_code: Lähdekoodi status_count_after: statusta - status_count_before: Ovat luoneet + status_count_before: He ovat luoneet user_count_after: käyttäjälle user_count_before: Koti what_is_mastodon: Mikä on Mastodon? @@ -33,161 +37,681 @@ fi: follow: Seuraa followers: Seuraajat following: Seuratut + media: Media + moved_html: "%{name} on muuttanut osoitteeseen %{new_profile_link}:" nothing_here: Täällä ei ole mitään! - people_followed_by: Henkilöitä joita %{name} seuraa - people_who_follow: Henkilöt jotka seuraa %{name} - posts: Postaukset + people_followed_by: Henkilöt, joita %{name} seuraa + people_who_follow: Käyttäjän %{name} seuraajat + posts: Tuuttaukset + posts_with_replies: Tuuttaukset ja vastaukset remote_follow: Etäseuranta reserved_username: Käyttäjänimi on varattu roles: admin: Ylläpitäjä + moderator: Moderaattori unfollow: Lopeta seuraaminen admin: account_moderation_notes: account: Moderaattori create: Luo created_at: Päiväys - created_msg: Moderointimerkintä luotu onnistuneesti! + created_msg: Moderointimerkinnän luonti onnistui! delete: Poista - destroyed_msg: Moderointimerkintä tuhottu onnistuneesti! + destroyed_msg: Moderointimerkinnän poisto onnistui! accounts: are_you_sure: Oletko varma? - confirm: Hyväksy - confirmed: Hyväksytty + by_domain: Verkko-osoite + confirm: Vahvista + confirmed: Vahvistettu + demote: Alenna disable: Poista käytöstä disable_two_factor_authentication: Poista 2FA käytöstä disabled: Poistettu käytöstä + display_name: Näyttönimi + domain: Verkko-osoite edit: Muokkaa email: Sähköposti + enable: Ota käyttöön + enabled: Käytössä + feed_url: Syötteen URL followers: Seuraajat - followers_url: Seuraajat URL + followers_url: Seuraajien URL + follows: Seuraa + inbox_url: Saapuvan postilaatikon URL + ip: IP + location: + all: Kaikki + local: Paikalliset + remote: Etätilit + title: Sijainti + login_status: Sisäänkirjautumisen tila + media_attachments: Medialiitteet + memorialize: Muuta muistosivuksi + moderation: + all: Kaikki + silenced: Hiljennetty + suspended: Jäähyllä + title: Moderointi + moderation_notes: Moderointimerkinnät + most_recent_activity: Viimeisin toiminta + most_recent_ip: Viimeisin IP + not_subscribed: Ei tilaaja + order: + alphabetic: Aakkosjärjestys + most_recent: Uusin + title: Järjestys + outbox_url: Lähtevän postilaatikon URL + perform_full_suspension: Siirrä kokonaan jäähylle + profile_url: Profiilin URL + promote: Ylennä + protocol: Protokolla + public: Julkinen + push_subscription_expires: PuSH-tilaus vanhenee + redownload: Päivitä profiilikuva + reset: Palauta + reset_password: Palauta salasana + resubscribe: Tilaa uudelleen + role: Oikeudet + roles: + admin: Ylläpitäjä + moderator: Moderaattori + staff: Henkilöstö + user: Käyttäjä + salmon_url: Salmon-URL + search: Haku + shared_inbox_url: Jaetun saapuvan postilaatikon URL + show: + created_reports: Tilin luomat raportit + report: raportti + targeted_reports: Tästä tilistä tehdyt raportit + silence: Hiljennä + statuses: Tilat + subscribe: Tilaa + title: Tilit + undo_silenced: Peru hiljennys + undo_suspension: Peru jäähy + unsubscribe: Lopeta tilaus + username: Käyttäjänimi + web: Web + action_logs: + actions: + confirm_user: "%{name} vahvisti käyttäjän %{target} sähköpostiosoitteen" + create_custom_emoji: "%{name} lähetti uuden emojin %{target}" + create_domain_block: "%{name} esti verkkotunnuksen %{target}" + create_email_domain_block: "%{name} lisäsi sähköpostiverkkotunnuksen %{target} estolistalle" + demote_user: "%{name} alensi käyttäjän %{target}" + destroy_domain_block: "%{name} poisti verkkotunnuksen %{target} eston" + destroy_email_domain_block: "%{name} lisäsi sähköpostiverkkotunnuksen %{target} sallittujen listalle" + destroy_status: "%{name} poisti käyttäjän %{target} tilan" + disable_2fa_user: "%{name} poisti käyttäjältä %{target} kaksivaiheisen todentamisen vaatimuksen" + disable_custom_emoji: "%{name} poisti emojin %{target} käytöstä" + disable_user: "%{name} poisti sisäänkirjautumisen käytöstä käyttäjältä %{target}" + enable_custom_emoji: "%{name} salli emojin %{target} käyttöön" + enable_user: "%{name} salli sisäänkirjautumisen käyttäjälle %{target}" + memorialize_account: "%{name} muutti käyttäjän %{target} tilin muistosivuksi" + promote_user: "%{name} ylensi käyttäjän %{target}" + reset_password_user: "%{name} palautti käyttäjän %{target} salasanan" + resolve_report: "%{name} hylkäsi raportin %{target}" + silence_account: "%{name} hiljensi käyttäjän %{target}" + suspend_account: "%{name} siirsi käyttäjän %{target} jäähylle" + unsilence_account: "%{name} poisti käyttäjän %{target} hiljennyksen" + unsuspend_account: "%{name} perui käyttäjän %{target} jäähyn" + update_custom_emoji: "%{name} päivitti emojin %{target}" + update_status: "%{name} päivitti käyttäjän %{target} tilan" + title: Auditointiloki + custom_emojis: + by_domain: Verkkotunnus + copied_msg: Emojin paikallisen kopion luonti onnistui + copy: Kopioi + copy_failed_msg: Emojista ei voitu tehdä paikallista kopiota + created_msg: Emojin luonti onnistui! + delete: Poista + destroyed_msg: Emojon poisto onnistui! + disable: Poista käytöstä + disabled_msg: Emojin käytöstäpoisto onnistui + emoji: Emoji + enable: Ota käyttöön + enabled_msg: Emojin käyttöönotto onnistui + image_hint: PNG enintään 50 kt + listed: Listassa + new: + title: Lisää uusi mukautettu emoji + overwrite: Kirjoita yli + shortcode: Lyhennekoodi + shortcode_hint: Vähintään kaksi merkkiä, vain kirjaimia, numeroita ja alaviivoja + title: Mukautetut emojit + unlisted: Ei listassa + update_failed_msg: Emojin päivitys epäonnistui + updated_msg: Emojin päivitys onnistui! + upload: Lähetä + domain_blocks: + add_new: Lisää uusi + created_msg: Verkkotunnuksen estoa käsitellään + destroyed_msg: Verkkotunnuksen esto on peruttu + domain: Verkkotunnus + new: + create: Luo esto + hint: Verkkotunnuksen esto ei estä tilien luomista ja lisäämistä tietokantaan, mutta se soveltaa näihin tileihin automaattisesti määrättyjä moderointitoimia tilin luomisen jälkeen. + severity: + desc_html: "<strong>Hiljennys</strong> estää tilin julkaisuja näkymästä muille kuin tilin seuraajille. <strong>Jäähy</strong> poistaa tilin kaiken sisällön, median ja profiilitiedot. Jos haluat vain hylätä mediatiedostot, valitse <strong>Ei mitään</strong>." + noop: Ei mitään + silence: Hiljennys + suspend: Jäähy + title: Uusi verkkotunnuksen esto + reject_media: Hylkää mediatiedostot + reject_media_hint: Poistaa paikallisesti tallennetut mediatiedostot eikä lataa niitä enää jatkossa. Ei merkitystä jäähyn kohdalla + severities: + noop: Ei mitään + silence: Hiljennys + suspend: Jäähy + severity: Vakavuus + show: + affected_accounts: + one: Vaikuttaa yhteen tiliin tietokannassa + other: Vaikuttaa %{count} tiliin tietokannassa + retroactive: + silence: Peru kaikkien tässä verkkotunnuksessa jo olemassa olevien tilien hiljennys + suspend: Peru kaikkien tässä verkkotunnuksessa jo olemassa olevien tilien jäähy + title: Peru verkkotunnuksen %{domain} esto + undo: Peru + title: Verkkotunnusten estot + undo: Peru + email_domain_blocks: + add_new: Lisää uusi + created_msg: Sähköpostiverkkotunnuksen lisäys estolistalle onnistui + delete: Poista + destroyed_msg: Sähköpostiverkkotunnuksen poisto estolistalta onnistui + domain: Verkkotunnus + new: + create: Lisää verkkotunnus + title: Uusi sähköpostiestolistan merkintä + title: Sähköpostiestolista + instances: + account_count: Tiedossa olevat tilit + domain_name: Verkkotunnus + reset: Palauta + search: Hae + title: Tiedossa olevat instanssit + invites: + filter: + all: Kaikki + available: Saatavilla + expired: Vanhentunut + title: Suodata + title: Kutsut + reports: + action_taken_by: Toimenpiteen tekijä + are_you_sure: Oletko varma? + comment: + label: Kommentti + none: Ei mitään + delete: Poista + id: Tunniste + mark_as_resolved: Merkitse ratkaistuksi + nsfw: + 'false': Peru medialiitteiden piilotus + 'true': Piilota medialiitteet + report: Raportti nro %{id} + report_contents: Sisältö + reported_account: Raportoitu tili + reported_by: Raportoija + resolved: Ratkaistut + silence_account: Hiljennä tili + status: Tila + suspend_account: Siirrä tili jäähylle + target: Kohde + title: Raportit + unresolved: Ratkaisemattomat + view: Näytä + settings: + activity_api_enabled: + desc_html: Paikallisesti julkaistujen tilojen, aktiivisten käyttäjien ja uusien rekisteröintien määrät viikoittain + title: Julkaise koostetilastoja käyttäjien aktiivisuudesta + bootstrap_timeline_accounts: + desc_html: Erota käyttäjänimet pilkulla. Vain paikalliset ja lukitsemattomat tilit toimivat. Jos kenttä jätetään tyhjäksi, oletusarvona ovat kaikki paikalliset ylläpitäjät. + title: Uudet käyttäjät seuraavat oletuksena seuraavia tilejä + contact_information: + email: Työsähköposti + username: Yhteyshenkilön käyttäjänimi + hero: + desc_html: Näytetään etusivulla. Suosituskoko vähintään 600x100 pikseliä. Jos kuvaa ei aseteta, käytetään instanssin pikkukuvaa + title: Sankarin kuva + peers_api_enabled: + desc_html: Verkkotunnukset, jotka tämä instanssi on kohdannut fediversumissa + title: Julkaise löydettyjen instanssien luettelo + registrations: + closed_message: + desc_html: Näytetään etusivulla, kun rekisteröinti on suljettu. HTML-tagit käytössä + title: Viesti, kun rekisteröinti on suljettu + deletion: + desc_html: Salli jokaisen poistaa oma tilinsä + title: Avoin tilin poisto + min_invite_role: + disabled: Ei kukaan + title: Salli kutsut käyttäjältä + open: + desc_html: Salli kenen tahansa luoda tili + title: Avoin rekisteröinti + show_known_fediverse_at_about_page: + desc_html: Kun tämä on valittu, esikatselussa näytetään tuuttaukset kaikkialta tunnetusta fediversumista. Muutoin näytetään vain paikalliset tuuttaukset. + title: Näytä aikajanan esikatselussa koko tunnettu fediversumi + show_staff_badge: + desc_html: Näytä käyttäjäsivulla henkilöstömerkki + title: Näytä henkilöstömerkki + site_description: + desc_html: Esittelykappale etusivulla ja metatunnisteissa. HTML-tagit käytössä, tärkeimmät ovat <code>&lt;a&gt;</code> ja <code>&lt;em&gt;</code>. + title: Instanssin kuvaus + site_description_extended: + desc_html: Hyvä paikka käytösohjeille, säännöille, ohjeistuksille ja muille instanssin muista erottaville asioille. HTML-tagit käytössä + title: Omavalintaiset laajat tiedot + site_terms: + desc_html: Tähän voi kirjoittaa instanssin tietosuojakäytännöstä, käyttöehdoista ja sen sellaisista asioista. HTML-tagit käytössä + title: Omavalintaiset käyttöehdot + site_title: Instanssin nimi + thumbnail: + desc_html: Käytetään esikatseluissa OpenGraphin ja API:n kautta. Suosituskoko 1200x630 pikseliä + title: Instanssin pikkukuva + timeline_preview: + desc_html: Näytä julkinen aikajana aloitussivulla + title: Aikajanan esikatselu + title: Sivuston asetukset + statuses: + back_to_account: Takaisin tilin sivulle + batch: + delete: Poista + nsfw_off: NSFW POIS + nsfw_on: NSFW PÄÄLLÄ + execute: Suorita + failed_to_execute: Suoritus epäonnistui + media: + hide: Piilota media + show: Näytä media + title: Media + no_media: Ei mediaa + title: Tilin tilat + with_media: Sisältää mediaa + subscriptions: + callback_url: Paluu-URL + confirmed: Vahvistettu + expires_in: Vanhenee + last_delivery: Viimeisin toimitus + title: WebSub + topic: Aihe + title: Ylläpito + admin_mailer: + new_report: + body: "%{reporter} on raportoinut kohteen %{target}" + subject: Uusi raportti instanssista %{instance} (nro %{id}) application_mailer: - settings: 'Muokkaa sähköpostiasetuksia: %{link}' - view: 'Katso:' + notification_preferences: Muuta sähköpostiasetuksia + salutation: "%{name}," + settings: 'Muuta sähköpostiasetuksia: %{link}' + view: 'Näytä:' + view_profile: Näytä profiili + view_status: Näytä tila applications: - invalid_url: Annettu URL on väärä + created: Sovelluksen luonti onnistui + destroyed: Sovelluksen poisto onnistui + invalid_url: Annettu URL on virheellinen + regenerate_token: Luo pääsytunnus uudelleen + token_regenerated: Pääsytunnuksen uudelleenluonti onnistui + warning: Säilytä tietoa hyvin. Älä milloinkaan jaa sitä muille! + your_token: Pääsytunnus auth: - didnt_get_confirmation: Etkö saanut varmennusohjeita? + agreement_html: Rekisteröityessäsi sitoudut noudattamaan <a href="%{rules_path}">instanssin sääntöjä</a> ja <a href="%{terms_path}">käyttöehtoja</a>. + change_password: Salasana + confirm_email: Vahvista sähköpostiosoite + delete_account: Poista tili + delete_account_html: Jos haluat poistaa tilisi, <a href="%{path}">paina tästä</a>. Poisto on vahvistettava. + didnt_get_confirmation: Etkö saanut vahvistusohjeita? forgot_password: Unohditko salasanasi? + invalid_reset_password_token: Salasananpalautustunnus on virheellinen tai vanhentunut. Pyydä uusi. login: Kirjaudu sisään logout: Kirjaudu ulos + migrate_account: Muuta toiseen tiliin + migrate_account_html: Jos haluat ohjata tämän tilin toiseen tiliin, voit <a href="%{path}">asettaa toisen tilin tästä</a>. + or: tai + or_log_in_with: Tai käytä kirjautumiseen + providers: + cas: CAS + saml: SAML register: Rekisteröidy - resend_confirmation: Lähetä varmennusohjeet uudestaan + register_elsewhere: Rekisteröidy toiselle palvelimelle + resend_confirmation: Lähetä vahvistusohjeet uudestaan reset_password: Palauta salasana security: Tunnukset set_new_password: Aseta uusi salasana authorize_follow: - error: Valitettavasti tapahtui virhe etätilin haussa. + error: Valitettavasti etätilin haussa tapahtui virhe follow: Seuraa - title: Seuraa %{acct} + follow_request: 'Olet lähettänyt seuraamispyynnön käyttäjälle:' + following: 'Onnistui! Seuraat käyttäjää:' + post_follow: + close: Tai voit sulkea tämän ikkunan. + return: Palaa käyttäjän profiiliin + web: Siirry verkkosivulle + title: Seuraa käyttäjää %{acct} datetime: distance_in_words: - about_x_hours: "%{count}t" - about_x_months: "%{count}kk" - about_x_years: "%{count}v" - almost_x_years: "%{count}v" - half_a_minute: Juuri nyt - less_than_x_minutes: "%{count}m" - less_than_x_seconds: Juuri nyt - over_x_years: "%{count}v" - x_days: "%{count}pv" - x_minutes: "%{count}m" - x_months: "%{count}kk" - x_seconds: "%{count}s" + about_x_hours: "%{count} h" + about_x_months: "%{count} kk" + about_x_years: "%{count} v" + almost_x_years: "%{count} v" + half_a_minute: Nyt + less_than_x_minutes: "%{count} m" + less_than_x_seconds: Nyt + over_x_years: "%{count} v" + x_days: "%{count} pv" + x_minutes: "%{count} m" + x_months: "%{count} kk" + x_seconds: "%{count} s" + deletes: + bad_password_msg: Hyvä yritys, hakkerit! Väärä salasana + confirm_password: Tunnistaudu syöttämällä nykyinen salasanasi + description_html: Tämä poistaa <strong>pysyvästi ja peruuttamattomasti</strong> kaiken tilisi sisällön ja poistaa tilin käytöstä. Käyttäjänimesi pysyy varattuna, jotta identiteettiäsi ei myöhemmin varasteta. + proceed: Poista tili + success_msg: Tilin poisto onnistui + warning_html: Sisällön poistaminen taataan vain tämän instanssin osalta. Jos sisältöä on jaettu paljon, siitä todennäköisesti jää jälkiä. Palvelimet, joihin ei saada yhteyttä tai jotka ovat lopettaneet päivitystesi tilaamisen, eivät päivitä tietokantojaan. + warning_title: Sisällön saatavuustieto levitetty + errors: + '403': Sinulla ei ole lupaa nähdä tätä sivua. + '404': Etsimääsi sivua ei ole olemassa. + '410': Etsimääsi sivua ei ole enää olemassa. + '422': + content: Turvallisuusvahvistus epäonnistui. Oletko estänyt evästeet? + title: Turvallisuusvahvistus epäonnistui + '429': Rajoitettu + '500': + content: Valitettavasti jokin meni pieleen meidän päässämme. + title: Sivu ei ole oikein + noscript_html: Mastodon-selainsovelluksen käyttöön vaaditaan JavaScript. Voit vaihtoehtoisesti kokeilla jotakin omalle käyttöjärjestelmällesi tehtyä Mastodon<a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">sovellusta</a>. exports: - blocks: Estosi + archive_takeout: + date: Päiväys + download: Lataa arkisto + hint_html: Voit pyytää arkistoa omista <strong>tuuttauksistasi ja mediastasi</strong>. Vientitiedot ovat ActivityPub-muodossa, ja ne voi lukea millä tahansa yhteensopivalla ohjelmalla. + in_progress: Arkistoa kootaan... + request: Pyydä arkisto + size: Koko + blocks: Estot csv: CSV follows: Seurattavat - storage: Mediasi + mutes: Mykistetyt + storage: Media-arkisto + followers: + domain: Verkkotunnus + explanation_html: Jos haluat olla varma tilapäivitystesi yksityisyydestä, sinun täytyy tietää, ketkä seuraavat sinua. <strong>Yksityiset tilapäivityksesi lähetetään kaikkiin niihin instansseihin, joissa sinulla on seuraajia</strong>. Jos et luota siihen, että näiden instanssien ylläpitäjät tai ohjelmisto kunnioittavat yksityisyyttäsi, käy läpi seuraajaluettelosi ja poista tarvittaessa käyttäjiä. + followers_count: Seuraajien määrä + lock_link: Lukitse tili + purge: Poista seuraajista + success: + one: Estetään kevyesti seuraajia yhdestä verkkotunnuksesta... + other: Estetään kevyesti seuraajia %{count} verkkotunnuksesta... + true_privacy_html: Muista, että <strong>kunnollinen yksityisyys voidaan varmistaa vain päästä päähän -salauksella</strong>. + unlocked_warning_html: Kuka tahansa voi seurata sinua ja nähdä saman tien yksityiset tilapäivityksesi. %{lock_link}, niin voit tarkastaa ja torjua seuraajia. + unlocked_warning_title: Tiliäsi ei ole lukittu generic: - changes_saved_msg: Muutokset onnistuneesti tallennettu! - powered_by: powered by %{link} + changes_saved_msg: Muutosten tallennus onnistui! + powered_by: voimanlähteenä %{link} save_changes: Tallenna muutokset validation_errors: - one: Jokin ei ole viellä oikein! Katso virhe alapuolelta. - other: Jokin ei ole viellä oikein! Katso %{count} virhettä alapuolelta. + one: Kaikki ei ole aivan oikein! Tarkasta alla oleva virhe + other: Kaikki ei ole aivan oikein! Tarkasta alla olevat %{count} virhettä imports: - preface: Voit tuoda tiettyä dataa kaikista ihmisistä joita seuraat tai estät tilillesi tälle palvelimelle tiedostoista, jotka on luotu toisella palvelimella. - success: Datasi on onnistuneesti ladattu ja käsitellään pian + preface: Voit tuoda toisesta instanssista viemiäsi tietoja, kuten esimerkiksi seuraamiesi tai estämiesi henkilöiden listan. + success: Tietojen lähettäminen onnistui, ja ne käsitellään kohtapuoliin types: - blocking: Estetyt lista - following: Seuratut lista + blocking: Estettyjen lista + following: Seurattujen lista + muting: Mykistettyjen lista upload: Lähetä - landing_strip_html: "<strong>%{name}</strong> on käyttäjä domainilla %{link_to_root_path}. Voit seurata tai vuorovaikuttaa heidän kanssaan jos sinulla on tili yleisessä verkossa." - landing_strip_signup_html: Jos sinulla ei ole tiliä, voit <a href="%{sign_up_path}">rekisteröityä täällä</a>. + in_memoriam_html: Muistoissamme. + invites: + delete: Poista käytöstä + expired: Vanhentunut + expires_in: + '1800': 30 minuuttia + '21600': 6 tuntia + '3600': 1 tunti + '43200': 12 tuntia + '86400': 1 vuorokausi + expires_in_prompt: Ei koskaan + generate: Luo + max_uses: + one: kertakäyttöinen + other: "%{count} käyttökertaa" + max_uses_prompt: Ei rajoitusta + prompt: Luo linkkejä ja jaa niiden avulla muille pääsyoikeus tähän instanssiin + table: + expires_at: Vanhenee + uses: Käytetty + title: Kutsu ihmisiä + landing_strip_html: "<strong>%{name}</strong> on käyttäjänä palvelimella %{link_to_root_path}. Voit seurata heitä tai pitää heihin yhteyttä, jos sinulla on tili missä tahansa fediversumin kolkassa." + landing_strip_signup_html: Jos sinulla ei ole tiliä, voit <a href="%{sign_up_path}">rekisteröityä tätä kautta</a>. + lists: + errors: + limit: Sinulla on jo suurin sallittu määrä listoja + media_attachments: + validations: + images_and_video: Videota ei voi liittää tilapäivitykseen, jossa on jo kuvia + too_many: Tiedostoja voi liittää enintään 4 + migrations: + acct: uuden tilin käyttäjätunnus@verkkotunnus + currently_redirecting: 'Profiiliisi on asetettu uudelleenohjaus:' + proceed: Tallenna + updated_msg: Tilinsiirtoasetusten päivitys onnistui! + moderation: + title: Moderointi notification_mailer: digest: - body: 'Tässä on pieni yhteenveto palvelimelta %{instance} viimeksi kun olit paikalla %{since}:' + action: Näytä kaikki ilmoitukset + body: Tässä lyhyt yhteenveto viime käyntisi (%{since}) jälkeen tulleista viesteistä mention: "%{name} mainitsi sinut:" new_followers_summary: - one: Olet myös saanut yhden uuden seuraajan poissaollessasi! Jee! - other: Olet saanut %{count} uutta seuraajaa poissaollessasi! Loistavaa! + one: Olet myös saanut yhden uuden seuraajan! Juhuu! + other: Olet myös saanut %{count} uutta seuraajaa! Aivan mahtavaa! subject: - one: "1 uusi ilmoitus viimeisen käyntisi jälkeen \U0001F418" - other: "%{count} uutta ilmoitusta viimeisen käyntisi jälkeen \U0001F418" + one: "1 uusi ilmoitus viime käyntisi jälkeen \U0001F418" + other: "%{count} uutta ilmoitusta viime käyntisi jälkeen \U0001F418" + title: Poissaollessasi… favourite: - body: 'Statuksestasi tykkäsi %{name}:' - subject: "%{name} tykkäsi sinun statuksestasi" + body: "%{name} tykkäsi tilastasi:" + subject: "%{name} tykkäsi tilastasi" + title: Uusi tykkäys follow: body: "%{name} seuraa nyt sinua!" subject: "%{name} seuraa nyt sinua" + title: Uusi seuraaja follow_request: - body: "%{name} on pyytänyt seurata sinua" - subject: 'Odottava seuraus pyyntö: %{name}' + action: Hallinnoi seuraamispyyntöjä + body: "%{name} haluaa seurata sinua" + subject: 'Odottava seuraamispyyntö: %{name}' + title: Uusi seuraamispyyntö mention: - body: 'Sinut mainitsi %{name} postauksessa:' - subject: Sinut mainitsi %{name} + action: Vastaa + body: "%{name} mainitsi sinut:" + subject: "%{name} mainitsi sinut" + title: Uusi maininta reblog: - body: 'Sinun statustasi boostasi %{name}:' - subject: "%{name} boostasi statustasi" + body: "%{name} buustasi tilaasi:" + subject: "%{name} boostasi tilaasi" + title: Uusi buustaus number: human: decimal_units: - format: "%n%u" + format: "%n %u" units: - billion: B + billion: Mrd million: M - quadrillion: Q - thousand: K - trillion: T + quadrillion: Brd + thousand: k + trillion: B unit: '' pagination: + newer: Uudemmat next: Seuraava + older: Vanhemmat prev: Edellinen + truncate: "&hellip;" + preferences: + languages: Kielet + other: Muut + publishing: Julkaiseminen + web: Web + push_notifications: + favourite: + title: "%{name} tykkäsi tilastasi" + follow: + title: "%{name} seuraa nyt sinua" + group: + title: "%{count} ilmoitusta" + mention: + action_boost: Buustaa + action_expand: Näytä lisää + action_favourite: Tykkää + title: "%{nimi} mainitsi sinut" + reblog: + title: "%{name} buustasi tilaasi" remote_follow: - acct: Syötä sinun käyttäjänimesi@domain jos haluat seurata palvelimelta - missing_resource: Ei löydetty tarvittavaa uudelleenohjaavaa URL-linkkiä tilillesi - proceed: Siirry seuraamiseen - prompt: 'Sinä aiot seurata:' + acct: Syötä se käyttäjätunnus@verkkotunnus, josta haluat seurata + missing_resource: Vaadittavaa uudelleenohjaus-URL:ää tiliisi ei löytynyt + proceed: Siirry seuraamaan + prompt: 'Olet aikeissa seurata:' + sessions: + activity: Viimeisin toiminta + browser: Selain + browsers: + alipay: Alipay + blackberry: Blackberry + chrome: Chrome + edge: Microsoft Edge + electron: Electron + firefox: Firefox + generic: Tuntematon selain + ie: Internet Explorer + micro_messenger: MicroMessenger + nokia: Nokia S40 Ovi -selain + opera: Opera + otter: Otter + phantom_js: PhantomJS + qq: QQ Browser + safari: Safari + uc_browser: UCBrowser + weibo: Weibo + current_session: Nykyinen istunto + description: "%{selain}, %{platform}" + explanation: Nämä verkkoselaimet ovat tällä hetkellä kirjautuneet Mastodon-tilillesi. + ip: IP + platforms: + adobe_air: Adobe Air + android: Android + blackberry: Blackberry + chrome_os: ChromeOS + firefox_os: Firefox OS + ios: iOS + linux: Linux + mac: Mac + other: tuntematon järjestelmä + windows: Windows + windows_mobile: Windows Mobile + windows_phone: Windows Phone + revoke: Hylkää + revoke_success: Istunnon hylkäys onnistui + title: Istunnot settings: - authorized_apps: Valtuutetut ohjelmat + authorized_apps: Valtuutetut sovellukset back: Takaisin Mastodoniin + delete: Tilin poisto + development: Kehittäminen edit_profile: Muokkaa profiilia - export: Vie dataa - import: Tuo dataa + export: Vie tietoja + followers: Valtuutetut seuraajat + import: Tuo + migrate: Tilin muutto muualle + notifications: Ilmoitukset preferences: Ominaisuudet settings: Asetukset - two_factor_authentication: Kaksivaiheinen tunnistus + two_factor_authentication: Kaksivaiheinen todentaminen + your_apps: Omat sovellukset statuses: - open_in_web: Avaa webissä - over_character_limit: sallittu kirjanmäärä %{max} ylitetty + attached: + description: 'Liitetty: %{attached}' + image: + one: "%{count} kuva" + other: "%{count} kuvaa" + video: + one: "%{count} video" + other: "%{count} videota" + content_warning: 'Sisältövaroitus: %{warning}' + open_in_web: Avaa selaimessa + over_character_limit: merkkimäärän rajoitus %{max} ylitetty + pin_errors: + limit: Olet jo kiinnittänyt suurimman mahdollisen määrän tuuttauksia + ownership: Muiden tuuttauksia ei voi kiinnittää + private: Piilotettua tuuttausta ei voi kiinnittää + reblog: Buustausta ei voi kiinnittää show_more: Näytä lisää + title: "%{name}: ”%{quote}”" visibilities: - private: Näytä vain seuraajille + private: Vain seuraajille + private_long: Näytä vain seuraajille public: Julkinen - unlisted: Julkinen, mutta älä näytä julkisella aikajanalla + public_long: Kaikki voivat nähdä + unlisted: Listaamaton julkinen + unlisted_long: Kaikki voivat nähdä, mutta ei näytetä julkisilla aikajanoilla stream_entries: - click_to_show: Klikkaa näyttääksesi - reblogged: boosted - sensitive_content: Herkkä materiaali + click_to_show: Katso napsauttamalla + pinned: Kiinnitetty tuuttaus + reblogged: buustasi + sensitive_content: Arkaluontoista sisältöä + terms: + title: "%{instance}, käyttöehdot ja tietosuojakäytäntö" + themes: + default: Mastodon time: formats: - default: "%b %d, %Y, %H:%M" + default: "%d.%m.%Y klo %H.%M" two_factor_authentication: - description_html: Jos otat käyttöön <strong>kaksivaiheisen tunnistuksen</strong>, kirjautumiseen vaaditaan puhelin, joka voi luoda tokeneita kirjautumista varten. + code_hint: Vahvista syöttämällä todentamissovelluksen generoima koodi + description_html: Jos otat käyttöön <strong>kaksivaiheisen todentamisen</strong>, kirjautumiseen vaaditaan puhelin, jolla voidaan luoda kirjautumistunnuksia. disable: Poista käytöstä enable: Ota käyttöön - instructions_html: "<strong>Skannaa tämä QR-koodi Google Authenticator- tai vastaavaan sovellukseen puhelimellasi</strong>. Tästä hetkestä lähtien ohjelma luo koodin, mikä sinun tarvitsee syöttää sisäänkirjautuessa." + enabled: Kaksivaiheinen todentaminen käytössä + enabled_success: Kaksivaiheisen todentamisen käyttöönotto onnistui + generate_recovery_codes: Luo palautuskoodit + instructions_html: "<strong>Lue tämä QR-koodi puhelimen Google Authenticator- tai vastaavalla TOTP-sovelluksella</strong>. Sen jälkeen sovellus luo tunnuksia, joita tarvitset sisäänkirjautuessasi." + lost_recovery_codes: Palautuskoodien avulla voit käyttää tiliä, jos menetät puhelimesi. Jos olet hukannut palautuskoodit, voit luoda uudet tästä. Vanhat palautuskoodit poistetaan käytöstä. + manual_instructions: 'Jos et voi lukea QR-koodia ja haluat syöttää sen käsin, tässä on salainen koodi tekstinä:' + recovery_codes: Varapalautuskoodit + recovery_codes_regenerated: Uusien palautuskoodien luonti onnistui + recovery_instructions_html: Jos menetät puhelimesi, voit kirjautua tilillesi jollakin alla olevista palautuskoodeista. <strong>Pidä palautuskoodit hyvässä tallessa</strong>. Voit esimerkiksi tulostaa ne ja säilyttää muiden tärkeiden papereiden joukossa. + setup: Ota käyttöön + wrong_code: Annettu koodi oli virheellinen! Ovatko palvelimen aika ja laitteen aika oikein? + user_mailer: + backup_ready: + explanation: Pyysit täydellistä varmuuskopiota Mastodon-tilistäsi. Voit nyt ladata sen! + subject: Arkisto on valmiina ladattavaksi + title: Arkiston tallennus + welcome: + edit_profile_action: Aseta profiili + edit_profile_step: Voit mukauttaa profiiliasi lataamalla profiilikuvan ja otsakekuvan, muuttamalla näyttönimeäsi ym. Jos haluat hyväksyä uudet seuraajat ennen kuin he voivat seurata sinua, voit lukita tilisi. + explanation: Näillä vinkeillä pääset alkuun + final_action: Ala julkaista + final_step: 'Ala julkaista! Vaikkei sinulla olisi seuraajia, monet voivat nähdä julkiset viestisi esimerkiksi paikallisella aikajanalla ja hashtagien avulla. Kannattaa esittäytyä! Käytä hashtagia #introductions. (Jos haluat esittäytyä myös suomeksi, se kannattaa tehdä erillisessä tuuttauksessa ja käyttää hashtagia #esittely.)' + full_handle: Koko käyttäjätunnuksesi + full_handle_hint: Kerro tämä ystävillesi, niin he voivat lähettää sinulle viestejä tai löytää sinut toisen instanssin kautta. + review_preferences_action: Muuta asetuksia + review_preferences_step: Käy tarkistamassa, että asetukset ovat haluamallasi tavalla. Voit valita, missä tilanteissa haluat saada sähköpostia, mikä on julkaisujesi oletusnäkyvyys jne. Jos et saa helposti pahoinvointia, voit valita, että GIF-animaatiot toistetaan automaattisesti. + subject: Tervetuloa Mastodoniin + tip_bridge_html: Jos tulet Twitteristä, voit etsiä ystäviäsi Mastodonista <a href="%{bridge_url}">siltasovelluksen</a> avulla. Se kuitenkin löytää heidät vain, jos hekin käyttävät sitä! + tip_federated_timeline: Yleinen aikajana näyttää sisältöä koko Mastodon-verkostosta. Siinä näkyvät kuitenkin vain ne henkilöt, joita oman instanssisi käyttäjät seuraavat. Siinä ei siis näytetä aivan kaikkea. + tip_following: Oletusarvoisesti seuraat oman palvelimesi ylläpitäjiä. Etsi lisää kiinnostavia ihmisiä paikalliselta ja yleiseltä aikajanalta. + tip_local_timeline: Paikallinen aikajana näyttää instanssin %{instance} käyttäjien julkaisut. He ovat naapureitasi! + tip_mobile_webapp: Jos voit lisätä Mastodonin mobiiliselaimen kautta aloitusnäytöllesi, voit vastaanottaa push-ilmoituksia. Toiminta vastaa monin tavoin tavanomaista sovellusta! + tips: Vinkkejä + title: Tervetuloa mukaan, %name}! users: - invalid_email: Virheellinen sähköposti - invalid_otp_token: Virheellinen kaksivaihetunnistuskoodi + invalid_email: Virheellinen sähköpostiosoite + invalid_otp_token: Virheellinen kaksivaiheisen todentamisen koodi + seamless_external_login: Olet kirjautunut ulkoisen palvelun kautta, joten salasana- ja sähköpostiasetukset eivät ole käytettävissä. + signed_in_as: 'Kirjautunut henkilönä:' diff --git a/config/locales/ja.yml b/config/locales/ja.yml @@ -4,6 +4,7 @@ ja: about_hashtag_html: ハッシュタグ <strong>#%{hashtag}</strong> の付いた公開トゥートです。どこでもいいので、連合に参加しているSNS上にアカウントを作れば会話に参加することができます。 about_mastodon_html: Mastodon は、オープンなウェブプロトコルを採用した、自由でオープンソースなソーシャルネットワークです。電子メールのような分散型の仕組みを採っています。 about_this: 詳細情報 + administered_by: '管理者:' closed_registrations: 現在このインスタンスでの新規登録は受け付けていません。しかし、他のインスタンスにアカウントを作成しても全く同じネットワークに参加することができます。 contact: 連絡先 contact_missing: 未設定 @@ -17,7 +18,7 @@ ja: features: humane_approach_body: 他の SNS の失敗から学び、Mastodon はソーシャルメディアが誤った使い方をされることの無いように倫理的な設計を目指しています。 humane_approach_title: より思いやりのある設計 - not_a_product_body: Mastodon は営利的な SNS ではありません。広告や、データの収集・解析は無く、またユーザーの囲い込みもありません。 + not_a_product_body: Mastodon は営利的な SNS ではありません。広告や、データの収集・解析は無く、またユーザーの囲い込みもありません。ここには中央権力はありません。 not_a_product_title: あなたは人間であり、商品ではありません real_conversation_body: 好きなように書ける500文字までの投稿や、文章やメディアの内容に警告をつけられる機能で、思い通りに自分自身を表現することができます。 real_conversation_title: 本当のコミュニケーションのために @@ -62,6 +63,13 @@ ja: are_you_sure: 本当に実行しますか? avatar: アイコン by_domain: ドメイン + change_email: + changed_msg: メールアドレスの変更に成功しました! + current_email: 現在のメールアドレス + label: メールアドレスを変更 + new_email: 新しいメールアドレス + submit: Change Email + title: "%{username} さんのメールアドレスを変更" confirm: 確認 confirmed: 確認済み demote: 降格 @@ -71,7 +79,7 @@ ja: display_name: 表示名 domain: ドメイン edit: 編集 - email: E-mail + email: メールアドレス enable: 有効化 enabled: 有効 feed_url: フィードURL @@ -130,6 +138,7 @@ ja: statuses: トゥート数 subscribe: 購読する title: アカウント + unconfirmed_email: 確認待ちのメールアドレス undo_silenced: サイレンスから戻す undo_suspension: 停止から戻す unsubscribe: 購読の解除 @@ -138,6 +147,7 @@ ja: 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} をブロックしました" @@ -246,8 +256,8 @@ ja: title: フィルター title: 招待 report_notes: - created_msg: モデレーションメモを書き込みました! - destroyed_msg: モデレーションメモを削除しました! + created_msg: レポートメモを書き込みました! + destroyed_msg: レポートメモを削除しました! reports: action_taken_by: レポート処理者 are_you_sure: 本当に実行しますか? @@ -256,15 +266,20 @@ ja: comment: label: コメント none: なし + created_at: レポート日時 delete: 削除 + history: モデレーション履歴 id: ID mark_as_resolved: 解決済みとしてマーク mark_as_unresolved: 未解決として再び開く notes: create: 書き込む create_and_resolve: 書き込み、解決済みにする + create_and_unresolve: 書き込み、未解決として開く delete: 削除 - label: メモ + label: モデレーターメモ + new_label: モデレーターメモの追加 + placeholder: このレポートに取られた措置やその他更新を記述してください nsfw: 'false': NSFW オフ 'true': NSFW オン @@ -601,6 +616,10 @@ ja: missing_resource: リダイレクト先が見つかりませんでした proceed: フォローする prompt: 'フォローしようとしています:' + remote_unfollow: + error: エラー + title: タイトル + unfollowed: フォロー解除しました sessions: activity: 最後のアクティビティ browser: ブラウザ @@ -689,6 +708,83 @@ ja: reblogged: さんがブースト sensitive_content: 閲覧注意 terms: + body_html: | + <h2>プライバシーポリシー</h2> + <h3 id="collect">どのような情報を収集しますか?</h3> + + <ul> + <li><em>基本的なアカウント情報</em>: 当サイトに登録すると、ユーザー名・メールアドレス・パスワードの入力を求められることがあります。また表示名や自己紹介・プロフィール画像・ヘッダー画像といった追加のプロフィールを登録できます。ユーザー名・表示名・自己紹介・プロフィール画像・ヘッダー画像は常に公開されます。</li> + <li><em>投稿・フォロー・その他公開情報</em>: フォローしているユーザーの一覧は一般公開されます。フォロワーも同様です。メッセージを投稿する際、日時だけでなく投稿に使用したアプリケーション名も記録されます。メッセージには写真や動画といった添付メディアを含むことがあります。「公開」や「未収載」の投稿は一般公開されます。プロフィールに投稿を載せるとそれもまた公開情報となります。投稿はフォロワーに配信されます。場合によっては他のサーバーに配信され、そこにコピーが保存されることを意味します。投稿を削除した場合も同様にフォロワーに配信されます。他の投稿をリブログやお気に入り登録する行動は常に公開されます。</li> + <li><em>「ダイレクト」と「非公開」投稿</em>: すべての投稿はサーバーに保存され、処理されます。「非公開」投稿はフォロワーと投稿に書かれたユーザーに配信されます。「ダイレクト」投稿は投稿に書かれたユーザーにのみ配信されます。場合によっては他のサーバーに配信され、そこにコピーが保存されることを意味します。私たちはこれらの閲覧を一部の許可された者に限定するよう誠意を持って努めます。しかし他のサーバーにおいても同様に扱われるとは限りません。したがって、相手の所属するサーバーを吟味することが重要です。設定で新しいフォロワーの承認または拒否を手動で行うよう切り替えることもできます。<em>サーバー管理者は「ダイレクト」や「非公開」投稿も閲覧する可能性があることを忘れないでください。</em>また受信者がスクリーンショットやコピー、もしくは共有する可能性があることを忘れないでください。<em>いかなる危険な情報もMastodon上で共有しないでください。</em></li> + <li><em>IPアドレスやその他メタデータ</em>: ログインする際IPアドレスだけでなくブラウザーアプリケーション名を記録します。ログインしたセッションはすべてユーザー設定で見直し、取り消すことができます。使用されている最新のIPアドレスは最大12ヵ月間保存されます。またサーバーへのIPアドレスを含むすべてのリクエストのログを保持することがあります。</li> + </ul> + + <hr class="spacer" /> + + <h3 id="use">情報を何に使用しますか?</h3> + + <p>収集した情報は次の用途に使用されることがあります:</p> + + <ul> + <li>Mastodonのコア機能の提供: ログインしている間にかぎり他の人たちと投稿を通じて交流することができます。例えば自分専用のホームタイムラインで投稿をまとめて読むために他の人たちをフォローできます。</li> + <li>コミュニティ維持の補助: 例えばIPアドレスを既知のものと比較し、BAN回避目的の複数登録者やその他違反者を判別します。</li> + <li>提供されたメールアドレスはお知らせの送信・投稿に対するリアクションやメッセージ送信の通知・お問い合わせやその他要求や質問への返信に使用されることがあります。</li> + </ul> + + <hr class="spacer" /> + + <h3 id="protect">情報をどのように保護しますか?</h3> + + <p>私たちはあなたが入力・送信する際や自身の情報にアクセスする際に個人情報を安全に保つため、さまざまなセキュリティ上の対策を実施します。特にブラウザーセッションだけでなくアプリケーションとAPI間の通信もSSLによって保護されます。またパスワードは強力な不可逆アルゴリズムでハッシュ化されます。二段階認証を有効にし、アカウントへのアクセスをさらに安全にすることができます。</p> + + <hr class="spacer" /> + + <h3 id="data-retention">データ保持方針はどうなっていますか?</h3> + + <p>私たちは次のように誠意を持って努めます:</p> + + <ul> + <li>当サイトへのIPアドレスを含むすべての要求に対するサーバーログを90日以内のできるかぎりの間保持します。</li> + <li>登録されたユーザーに関連付けられたIPアドレスを12ヵ月以内の間保持します。</li> + </ul> + + <p>あなたは投稿・添付メディア・プロフィール画像・ヘッダー画像を含む自身のデータのアーカイブを要求し、ダウンロードすることができます。</p> + + <p>あなたはいつでもアカウントの削除を要求できます。削除は取り消すことができません。</p> + + <hr class="spacer"/> + + <h3 id="cookies">クッキーを使用していますか?</h3> + + <p>はい。クッキーは (あなたが許可した場合に) WebサイトやサービスがWebブラウザーを介してコンピューターに保存する小さなファイルです。使用することで Web サイトがブラウザーを識別し、登録済みのアカウントがある場合関連付けます。</p> + + <p>私たちはクッキーを将来の訪問のために設定を保存し呼び出す用途に使用します。</p> + + <hr class="spacer" /> + + <h3 id="disclose">なんらかの情報を外部に提供していますか?</h3> + + <p>私たちは個人を特定できる情報を外部へ販売・取引・その他方法で渡すことはありません。これには当サイトの運営・業務遂行・サービス提供を行ううえで補助する信頼できる第三者をこの機密情報の保護に同意するかぎり含みません。法令の遵守やサイトポリシーの施行、権利・財産・安全の保護に適切と判断した場合、あなたの情報を公開することがあります。</p> + + <p>あなたの公開情報はネットワーク上の他のサーバーにダウンロードされることがあります。相手が異なるサーバーに所属する場合、「公開」と「非公開」投稿はフォロワーの所属するサーバーに配信され、「ダイレクト」投稿は受信者の所属するサーバーに配信されます。</p> + + <p>あなたがアカウントの使用をアプリケーションに許可すると、承認した権限の範囲内で公開プロフィール情報・フォローリスト・フォロワー・リスト・すべての投稿・お気に入り登録にアクセスできます。アプリケーションはメールアドレスやパスワードに決してアクセスできません。</p> + + <hr class="spacer" /> + + <h3 id="coppa">児童オンラインプライバシー保護法の遵守</h3> + + <p>当サイト・製品・サービスは13歳以上の人を対象としています。サーバーが米国にあり、あなたが13歳未満の場合、COPPA (<a href="https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act">Children's Online Privacy Protection Act</a> - 児童オンラインプライバシー保護法) により当サイトを使用できません。</p> + + <hr class="spacer" /> + + <h3 id="changes">プライバシーポリシーの変更</h3> + + <p>プライバシーポリシーの変更を決定した場合、このページに変更点を掲載します。</p> + + <p>この文章のライセンスはCC-BY-SAです。最終更新日は2018年3月7日です。</p> + + <p>オリジナルの出典: <a href="https://github.com/discourse/discourse">Discourse privacy policy</a></p> title: "%{instance} 利用規約・プライバシーポリシー" themes: default: Mastodon diff --git a/config/locales/pl.yml b/config/locales/pl.yml @@ -63,6 +63,13 @@ pl: are_you_sure: Jesteś tego pewien? avatar: Awatar by_domain: Domena + change_email: + changed_msg: Pomyślnie zmieniono adres e-mail konta! + current_email: Obecny adres e-mail + label: Zmień adres e-mail + new_email: Nowy adres e-mail + submit: Zmień adres e-mail + title: Zmień adres e-mail dla %{username} confirm: Potwierdź confirmed: Potwierdzono demote: Degraduj @@ -131,6 +138,7 @@ pl: statuses: Wpisy subscribe: Subskrybuj title: Konta + unconfirmed_email: Niepotwierdzony adres e-mail undo_silenced: Cofnij wyciszenie undo_suspension: Cofnij zawieszenie unsubscribe: Przestań subskrybować @@ -139,6 +147,7 @@ pl: action_logs: actions: assigned_to_self_report: "%{name} przypisał sobie zgłoszenie %{target}" + change_email_user: "%{name} zmienił adres-email użytkownika %{target}" confirm_user: "%{name} potwierdził adres e-mail użytkownika %{target}" create_custom_emoji: "%{name} dodał nowe emoji %{target}" create_domain_block: "%{name} zablokował domenę %{target}" @@ -258,18 +267,24 @@ pl: 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 notes: create: Utwórz notatkę 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 'true': Oznaczaj jako NSFW + reopen: Otwórz ponownie report: 'Zgłoszenie #%{id}' report_contents: Zawartość reported_account: Zgłoszone konto @@ -608,6 +623,10 @@ pl: missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny proceed: Śledź prompt: 'Zamierzasz śledzić:' + remote_unfollow: + error: Błąd + title: Tytuł + unfollowed: Przestałeś śledzić sessions: activity: Ostatnia aktywność browser: Przeglądarka diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml @@ -8,6 +8,7 @@ en: display_name: one: <span class="name-counter">1</span> character left other: <span class="name-counter">%{count}</span> characters left + fields: You can have up to 4 items displayed as a table on your profile header: PNG, GIF or JPG. At most 2MB. Will be downscaled to 700x335px locked: Requires you to manually approve followers note: @@ -22,6 +23,10 @@ en: user: filtered_languages: Checked languages will be filtered from public timelines for you labels: + account: + fields: + name: Label + value: Content defaults: avatar: Avatar confirm_new_password: Confirm new password @@ -31,6 +36,7 @@ en: display_name: Display name email: E-mail address expires_in: Expire after + fields: Profile metadata filtered_languages: Filtered languages header: Header locale: Language diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml @@ -14,7 +14,7 @@ eo: one: <span class="note-counter">1</span> signo restas other: <span class="note-counter">%{count}</span> signoj restas setting_noindex: Influas vian publikan profilon kaj mesaĝajn paĝojn - setting_theme: Influas kiel Mastodon aspektas kiam vi ensalutis en ajna aparato. + setting_theme: Influas kiel Mastodon aspektas post ensaluto de ajna aparato. imports: data: CSV-dosiero el alia nodo de Mastodon sessions: @@ -45,7 +45,7 @@ eo: setting_default_privacy: Mesaĝa videbleco setting_default_sensitive: Ĉiam marki aŭdovidaĵojn tiklaj setting_delete_modal: Montri fenestron por konfirmi antaŭ ol forigi mesaĝon - setting_display_sensitive_media: Ĉiam montri aŭdovidaĵon markitajn tiklaj + setting_display_sensitive_media: Ĉiam montri aŭdovidaĵojn markitajn tiklaj setting_noindex: Ellistiĝi de retserĉila indeksado setting_reduce_motion: Malrapidigi animaciojn setting_system_font_ui: Uzi la dekomencan tiparon de la sistemo diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml @@ -3,64 +3,68 @@ fi: simple_form: hints: defaults: - avatar: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 400x400px - digest: Lähetetään vain pitkän poissaolon jälkeen, ja vain jos olet vastaanottanut yksityisviestejä poissaolosi aikana - display_name: Korkeintaan 30 merkkiä - header: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 700x335px - locked: Vaatii sinua manuaalisesti hyväksymään seuraajat - note: Korkeintaan 160 merkkiä - setting_noindex: Vaikuttaa julkiseen profiiliisi ja statuspäivityksiisi - setting_theme: Vaikuttaa siihen, miltä Mastodon näyttää kun olet kirjautuneena milllä tahansa laitteella. + avatar: PNG, GIF tai JPG. Enintään 2 Mt. Skaalataan kokoon 400 x 400 px + digest: Lähetetään vain pitkän poissaolon jälkeen ja vain, jos olet saanut suoria viestejä poissaolosi aikana + display_name: + one: <span class="name-counter">1</span> merkki jäljellä + other: <span class="name-counter">%{count}</span> merkkiä jäljellä + header: PNG, GIF tai JPG. Enintään 2 Mt. Skaalataan kokoon 700 x 335 px + locked: Sinun täytyy hyväksyä seuraajat manuaalisesti + note: + one: <span class="note-counter">1</span> merkki jäljellä + other: <span class="note-counter">%{count}</span> merkkiä jäljellä + setting_noindex: Vaikuttaa julkiseen profiiliisi ja tilasivuihisi + setting_theme: Vaikuttaa Mastodonin ulkoasuun millä tahansa laitteella kirjauduttaessa. imports: - data: CSV tiedosto, joka on tuotu toiselta Mastodon-palvelimelta + data: Toisesta Mastodon-instanssista tuotu CSV-tiedosto sessions: - otp: Syötä kaksivaiheisen tunnistuksen koodi puhelimestasi tai käytä yhtä palautuskoodeistasi. + otp: Syötä puhelimeen saamasi kaksivaiheisen tunnistautumisen koodi tai käytä palautuskoodia. user: - filtered_languages: Valitut kielet suodatetaan julkisilta aikajanoilta + filtered_languages: Valitut kielet suodatetaan pois julkisilta aikajanoilta labels: defaults: avatar: Profiilikuva - confirm_new_password: Varmista uusi salasana - confirm_password: Varmista salasana + confirm_new_password: Vahvista uusi salasana + confirm_password: Vahvista salasana current_password: Nykyinen salasana - data: Data + data: Tiedot display_name: Nimimerkki email: Sähköpostiosoite - expires_in: Vanhentuu + expires_in: Vanhenee filtered_languages: Suodatetut kielet header: Otsakekuva locale: Kieli - locked: Tee tilistä yksityinen - max_uses: Max käyttökerrat + locked: Lukitse tili + max_uses: Käyttökertoja enintään new_password: Uusi salasana note: Kuvaus - otp_attempt: Kaksivaiheinen koodi + otp_attempt: Kaksivaiheisen tunnistautumisen koodi password: Salasana - setting_auto_play_gif: Animoitujen GIFfien automaattitoisto - setting_boost_modal: Näytä vahvistusikkuna ennen boostausta - setting_default_privacy: Julkaisun yksityisyys + setting_auto_play_gif: Toista GIF-animaatiot automaattisesti + setting_boost_modal: Kysy vahvistusta ennen buustausta + setting_default_privacy: Julkaisun näkyvyys setting_default_sensitive: Merkitse media aina arkaluontoiseksi - setting_delete_modal: Näytä vahvistusikkuna ennen töötin poistamista + setting_delete_modal: Kysy vahvistusta ennen tuuttauksen poistamista setting_display_sensitive_media: Näytä aina arkaluontoiseksi merkitty media setting_noindex: Jättäydy pois hakukoneindeksoinnista - setting_reduce_motion: Vähennä liikettä animaatioissa - setting_system_font_ui: Käytä käyttöjärjestelmän oletusfonttia + setting_reduce_motion: Vähennä animaatioiden liikettä + setting_system_font_ui: Käytä järjestelmän oletusfonttia setting_theme: Sivuston teema - setting_unfollow_modal: Näytä vahvistusikkuna ennen seuraamisen lopettamista - severity: Vakavuusaste - type: Tuontityyppi + setting_unfollow_modal: Kysy vahvistusta, ennen kuin lopetat seuraamisen + severity: Vakavuus + type: Tietojen laji username: Käyttäjänimi username_or_email: Käyttäjänimi tai sähköposti interactions: must_be_follower: Estä ilmoitukset käyttäjiltä, jotka eivät seuraa sinua must_be_following: Estä ilmoitukset käyttäjiltä, joita et seuraa - must_be_following_dm: Estä suorat viestit ihmisiltä, joita et seuraa + must_be_following_dm: Estä suorat viestit käyttäjiltä, joita et seuraa notification_emails: - digest: Lähetä koosteviestejä sähköpostilla - favourite: Lähetä sähköposti, kun joku tykkää statuksestasi + digest: Lähetä koosteviestejä sähköpostitse + favourite: Lähetä sähköposti, kun joku tykkää tilastasi follow: Lähetä sähköposti, kun joku seuraa sinua follow_request: Lähetä sähköposti, kun joku pyytää seurata sinua - mention: Lähetä sähköposti, kun joku mainitsee sinut + mention: Lähetä sähköposti, kun sinut mainitaan reblog: Lähetä sähköposti, kun joku buustaa julkaisusi 'no': Ei required: diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml @@ -6,21 +6,21 @@ sk: avatar: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 400x400px digest: Odoslané iba v prípade dlhodobej neprítomnosti, a len ak ste obdŕžali nejaké osobné správy kým ste boli preč display_name: - one: Ostáva vám <span class="name-counter">1</span> znak - other: Ostáva vám <span class="name-counter">%{count}</span> znakov + one: Ostáva ti <span class="name-counter">1</span> znak + other: Ostáva ti <span class="name-counter">%{count}</span> znakov header: PNG, GIF alebo JPG. Maximálne 2MB. Bude zmenšený na 700x335px locked: Musíte manuálne schváliť sledujúcich note: one: Ostáva vám <span class="note-counter">1</span> znak - other: Ostáva vám <span class="note-counter">%{count}</span>znakov - setting_noindex: Ovplyvňuje profil a správy tak, že ich nebude možné nájsť vyhľadávaním - setting_theme: Ovplyvní ako bude Mastodon vyzerať pri prihlásení z hociktorého zariadenia. + other: Ostáva ti <span class="note-counter">%{count}</span> znakov + setting_noindex: Ovplyvňuje profil a správy tak, že ich nebude možné nájsť vyhľadávaním + setting_theme: Toto ovplyvní ako bude Mastodon vyzerať pri prihlásení z hociktorého zariadenia. imports: data: CSV súbor vyexportovaný z inej Mastodon inštancie sessions: - otp: Vložte 2FA kód z telefónu alebo použite jeden z vašich obnovovacích kódov. + otp: Napíš sem dvoj-faktorový kód z telefónu, alebo použite jeden z vašich obnovovacích kódov. user: - filtered_languages: Zaškrtnuté jazyky vám nebudú zobrazené vo verejnej časovej osi + filtered_languages: Zaškrtnuté jazyky budú pre teba vynechané nebudú z verejnej časovej osi labels: defaults: avatar: Avatar @@ -53,7 +53,7 @@ sk: setting_unfollow_modal: Zobrazovať potvrdzovacie okno pred skončením sledovania iného používateľa severity: Závažnosť type: Typ importu - username: Používateľské meno + username: Užívateľské meno username_or_email: Prezívka, alebo Email interactions: must_be_follower: Blokovať notifikácie pod používateľov, ktorí ťa nesledujú diff --git a/config/locales/sk.yml b/config/locales/sk.yml @@ -149,15 +149,15 @@ sk: enable_custom_emoji: "%{name} povolil emoji %{target}" 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 používateľa %{target}" - reset_password_user: "%{name} resetoval heslo pre používateľa %{target}" - resolve_report: "%{name} zamietol nahlásenie %{target}" - silence_account: "%{name} stíšil účet %{target}" - suspend_account: "%{name} suspendoval účet používateľa %{target}" - unsilence_account: "%{name} zrušil stíšenie účtu používateľa %{target}" - unsuspend_account: "%{name} zrušil suspendáciu účtu používateľa %{target}" - update_custom_emoji: "%{name} aktualizoval emoji %{target}" - update_status: "%{name} aktualizoval status %{target}" + promote_user: "%{name} povýšil/a používateľa %{target}" + reset_password_user: "%{name} resetoval/a heslo pre používateľa %{target}" + resolve_report: "%{name} zamietli nahlásenie %{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}" + unsuspend_account: "%{name} zrušil/a blokovanie účtu používateľa %{target}" + update_custom_emoji: "%{name} aktualizoval/a emoji %{target}" + update_status: "%{name} aktualizoval/a status pre %{target}" title: Kontrólny záznam custom_emojis: by_domain: Doména @@ -358,7 +358,7 @@ sk: warning: Na tieto údaje dávajte ohromný pozor. Nikdy ich s nikým nezďieľajte! your_token: Váš prístupový token auth: - agreement_html: V rámci registrácie súhlasíte, že sa budete riadiť <a href="%{rules_path}"> 1 pravidlami tejto instancie</a> 2 a taktiež <a href="%{terms_path}"> 3 našími servisnými podmienkami </a> 4. + agreement_html: V rámci registrácie súhlasíš, že sa budeš riadiť <a href="%{rules_path}"> pravidlami tejto instancie</a>, a taktiež <a href="%{terms_path}"> našími servisnými podmienkami </a>. change_password: Heslo confirm_email: Potvrdiť email delete_account: Vymazať účet @@ -366,8 +366,8 @@ sk: didnt_get_confirmation: Neobdŕžali ste kroky pre potvrdenie? forgot_password: Zabudli ste heslo? invalid_reset_password_token: Token na obnovu hesla vypršal. Prosím vypítajte si nový. - login: Prihlásenie - logout: Odhlásiť sa + login: Prihlás sa + logout: Odhlás sa migrate_account: Presunúť sa na iný účet migrate_account_html: Pokiaľ si želáte presmerovať tento účet na nejaký iný, môžete <a href="%{path}"> tak urobiť tu</a>. or: alebo @@ -375,7 +375,7 @@ sk: providers: cas: CAS saml: SAML - register: Zaregistrovať sa + register: Zaregistruj sa register_elsewhere: Zaregistruj sa na inom serveri resend_confirmation: Poslať potvrdzujúce pokyny znovu reset_password: Resetovať heslo @@ -677,6 +677,7 @@ sk: full_handle: Adresa tvojho profilu v celom formáte full_handle_hint: Toto je čo musíš dať vedieť svojím priateľom aby ti mohli posielať správy, alebo ťa následovať z inej instancie. review_preferences_action: Zmeniť nastavenia + review_preferences_step: Daj si záležať na svojích nastaveniach, napríklad že aké emailové notifikácie chceš dostávať, alebo pod aký level súkromia sa tvoje príspevky majú sami automaticky zaradiť. Pokiaľ nemáš malátnosť z pohybu, môžeš si zvoliť aj automatické spúšťanie GIF animácií. subject: Vitaj na Mastodone tip_bridge_html: Ak prichádzaš z Twitteru, môžeš svojích priateľov nájsť na Mastodone pomocou tzv. <a href="%{bridge_url}">mostíkovej aplikácie</a>. Ale tá funguje iba ak ju aj oni niekedy použili! tip_federated_timeline: Federovaná os zobrazuje sieť Mastodonu až po jej hranice. Ale zahŕňa iba ľúdí ktorých ostatní okolo teba sledujú, takže predsa nieje úplne celistvá. diff --git a/config/routes.rb b/config/routes.rb @@ -151,6 +151,7 @@ Rails.application.routes.draw do post :memorialize end + resource :change_email, only: [:show, :update] resource :reset, only: [:create] resource :silence, only: [:create, :destroy] resource :suspension, only: [:create, :destroy] diff --git a/db/migrate/20180410204633_add_fields_to_accounts.rb b/db/migrate/20180410204633_add_fields_to_accounts.rb @@ -0,0 +1,5 @@ +class AddFieldsToAccounts < ActiveRecord::Migration[5.1] + def change + add_column :accounts, :fields, :jsonb + end +end diff --git a/db/schema.rb b/db/schema.rb @@ -10,9 +10,10 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180402040909) do +ActiveRecord::Schema.define(version: 2018_04_10_204633) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_stat_statements" enable_extension "plpgsql" create_table "account_domain_blocks", force: :cascade do |t| @@ -74,6 +75,7 @@ ActiveRecord::Schema.define(version: 20180402040909) do t.boolean "memorial", default: false, null: false t.bigint "moved_to_account_id" t.string "featured_collection_url" + t.jsonb "fields" t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower" t.index ["uri"], name: "index_accounts_on_uri" diff --git a/docker-compose.yml b/docker-compose.yml @@ -40,7 +40,7 @@ services: - external_network - internal_network ports: - - "3000:3000" + - "127.0.0.1:3000:3000" depends_on: - db - redis @@ -60,7 +60,7 @@ services: - external_network - internal_network ports: - - "4000:4000" + - "127.0.0.1:4000:4000" depends_on: - db - redis diff --git a/package.json b/package.json @@ -86,8 +86,8 @@ "prop-types": "^15.5.10", "punycode": "^2.1.0", "rails-ujs": "^5.1.2", - "react": "^16.2.0", - "react-dom": "^16.2.0", + "react": "^16.3.0", + "react-dom": "^16.3.0", "react-hotkeys": "^0.10.0", "react-immutable-proptypes": "^2.1.0", "react-immutable-pure-component": "^1.1.1", diff --git a/spec/controllers/admin/change_email_controller_spec.rb b/spec/controllers/admin/change_email_controller_spec.rb @@ -0,0 +1,47 @@ +require 'rails_helper' + +RSpec.describe Admin::ChangeEmailsController, type: :controller do + render_views + + let(:admin) { Fabricate(:user, admin: true) } + + before do + sign_in admin + end + + describe "GET #show" do + it "returns http success" do + account = Fabricate(:account) + user = Fabricate(:user, account: account) + + get :show, params: { account_id: account.id } + + expect(response).to have_http_status(:success) + end + end + + describe "GET #update" do + before do + allow(UserMailer).to receive(:confirmation_instructions).and_return(double('email', deliver_later: nil)) + end + + it "returns http success" do + account = Fabricate(:account) + user = Fabricate(:user, account: account) + + previous_email = user.email + + post :update, params: { account_id: account.id, user: { unconfirmed_email: 'test@example.com' } } + + user.reload + + expect(user.email).to eq previous_email + expect(user.unconfirmed_email).to eq 'test@example.com' + expect(user.confirmation_token).not_to be_nil + + expect(UserMailer).to have_received(:confirmation_instructions).with(user, user.confirmation_token, { to: 'test@example.com' }) + + expect(response).to redirect_to(admin_account_path(account.id)) + end + end +end diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb @@ -48,6 +48,57 @@ RSpec.describe Auth::SessionsController, type: :controller do request.env['devise.mapping'] = Devise.mappings[:user] end + context 'using PAM authentication' do + context 'using a valid password' do + before do + post :create, params: { user: { email: "pam_user1", password: '123456' } } + end + + it 'redirects to home' do + expect(response).to redirect_to(root_path) + end + + it 'logs the user in' do + expect(controller.current_user).to be_instance_of(User) + end + end + + context 'using an invalid password' do + before do + post :create, params: { user: { email: "pam_user1", password: 'WRONGPW' } } + end + + it 'shows a login error' do + expect(flash[:alert]).to match I18n.t('devise.failure.invalid', authentication_keys: 'Email') + end + + it "doesn't log the user in" do + expect(controller.current_user).to be_nil + end + end + + context 'using a valid email and existing user' do + let(:user) do + account = Fabricate.build(:account, username: 'pam_user1') + account.save!(validate: false) + user = Fabricate(:user, email: 'pam@example.com', password: nil, account: account) + user + end + + before do + post :create, params: { user: { email: user.email, password: '123456' } } + end + + it 'redirects to home' do + expect(response).to redirect_to(root_path) + end + + it 'logs the user in' do + expect(controller.current_user).to eq user + end + end + end + context 'using password authentication' do let(:user) { Fabricate(:user, email: 'foo@bar.com', password: 'abcdefgh') } diff --git a/spec/controllers/stream_entries_controller_spec.rb b/spec/controllers/stream_entries_controller_spec.rb @@ -66,16 +66,12 @@ RSpec.describe StreamEntriesController, type: :controller do describe 'GET #show' do include_examples 'before_action', :show - it 'renders with HTML' do - ancestor = Fabricate(:status) - status = Fabricate(:status, in_reply_to_id: ancestor.id) - descendant = Fabricate(:status, in_reply_to_id: status.id) + it 'redirects to status page' do + status = Fabricate(:status) get :show, params: { account_username: status.account.username, id: status.stream_entry.id } - expect(assigns(:ancestors)).to eq [ancestor] - expect(assigns(:descendants)).to eq [descendant] - expect(response).to have_http_status(:success) + expect(response).to redirect_to(short_account_status_url(status.account, status)) end it 'returns http success with Atom' do diff --git a/spec/models/concerns/status_threading_concern_spec.rb b/spec/models/concerns/status_threading_concern_spec.rb @@ -14,34 +14,34 @@ describe StatusThreadingConcern do let!(:viewer) { Fabricate(:account, username: 'viewer') } it 'returns conversation history' do - expect(reply3.ancestors).to include(status, reply1, reply2) + expect(reply3.ancestors(4)).to include(status, reply1, reply2) end it 'does not return conversation history user is not allowed to see' do reply1.update(visibility: :private) status.update(visibility: :direct) - expect(reply3.ancestors(viewer)).to_not include(reply1, status) + expect(reply3.ancestors(4, viewer)).to_not include(reply1, status) end it 'does not return conversation history from blocked users' do viewer.block!(jeff) - expect(reply3.ancestors(viewer)).to_not include(reply1) + expect(reply3.ancestors(4, viewer)).to_not include(reply1) end it 'does not return conversation history from muted users' do viewer.mute!(jeff) - expect(reply3.ancestors(viewer)).to_not include(reply1) + expect(reply3.ancestors(4, viewer)).to_not include(reply1) end it 'does not return conversation history from silenced and not followed users' do jeff.update(silenced: true) - expect(reply3.ancestors(viewer)).to_not include(reply1) + expect(reply3.ancestors(4, viewer)).to_not include(reply1) end it 'does not return conversation history from blocked domains' do viewer.block_domain!('example.com') - expect(reply3.ancestors(viewer)).to_not include(reply2) + expect(reply3.ancestors(4, viewer)).to_not include(reply2) end it 'ignores deleted records' do @@ -49,10 +49,32 @@ describe StatusThreadingConcern do second_status = Fabricate(:status, thread: first_status, account: alice) # Create cache and delete cached record - second_status.ancestors + second_status.ancestors(4) first_status.destroy - expect(second_status.ancestors).to eq([]) + expect(second_status.ancestors(4)).to eq([]) + end + + it 'can return more records than previously requested' do + first_status = Fabricate(:status, account: bob) + second_status = Fabricate(:status, thread: first_status, account: alice) + third_status = Fabricate(:status, thread: second_status, account: alice) + + # Create cache + second_status.ancestors(1) + + expect(third_status.ancestors(2)).to eq([first_status, second_status]) + end + + it 'can return fewer records than previously requested' do + first_status = Fabricate(:status, account: bob) + second_status = Fabricate(:status, thread: first_status, account: alice) + third_status = Fabricate(:status, thread: second_status, account: alice) + + # Create cache + second_status.ancestors(2) + + expect(third_status.ancestors(1)).to eq([second_status]) end end diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb @@ -1,6 +1,30 @@ require 'rails_helper' RSpec.describe CustomEmoji, type: :model do + describe '#search' do + let(:custom_emoji) { Fabricate(:custom_emoji, shortcode: shortcode) } + + subject { described_class.search(search_term) } + + context 'shortcode is exact' do + let(:shortcode) { 'blobpats' } + let(:search_term) { 'blobpats' } + + it 'finds emoji' do + is_expected.to include(custom_emoji) + end + end + + context 'shortcode is partial' do + let(:shortcode) { 'blobpats' } + let(:search_term) { 'blob' } + + it 'finds emoji' do + is_expected.to include(custom_emoji) + end + end + end + describe '#local?' do let(:custom_emoji) { Fabricate(:custom_emoji, domain: domain) } diff --git a/spec/models/status_pin_spec.rb b/spec/models/status_pin_spec.rb @@ -37,5 +37,36 @@ RSpec.describe StatusPin, type: :model do expect(StatusPin.new(account: account, status: status).save).to be false end + + max_pins = 5 + it 'does not allow pins above the max' do + account = Fabricate(:account) + status = [] + + (max_pins + 1).times do |i| + status[i] = Fabricate(:status, account: account) + end + + max_pins.times do |i| + expect(StatusPin.new(account: account, status: status[i]).save).to be true + end + + expect(StatusPin.new(account: account, status: status[max_pins]).save).to be false + end + + it 'allows pins above the max for remote accounts' do + account = Fabricate(:account, domain: 'remote', username: 'bob', url: 'https://remote/') + status = [] + + (max_pins + 1).times do |i| + status[i] = Fabricate(:status, account: account) + end + + max_pins.times do |i| + expect(StatusPin.new(account: account, status: status[i]).save).to be true + end + + expect(StatusPin.new(account: account, status: status[max_pins]).save).to be true + end end end diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb @@ -1,5 +1,31 @@ require 'rails_helper' RSpec.describe ActivityPub::ProcessAccountService do - pending + subject { described_class.new } + + context 'property values' do + let(:payload) do + { + id: 'https://foo', + type: 'Actor', + inbox: 'https://foo/inbox', + attachment: [ + { type: 'PropertyValue', name: 'Pronouns', value: 'They/them' }, + { type: 'PropertyValue', name: 'Occupation', value: 'Unit test' }, + ], + }.with_indifferent_access + end + + it 'parses out of attachment' do + account = subject.call('alice', 'example.com', payload) + expect(account.fields).to be_a Array + expect(account.fields.size).to eq 2 + expect(account.fields[0]).to be_a Account::Field + expect(account.fields[0].name).to eq 'Pronouns' + expect(account.fields[0].value).to eq 'They/them' + expect(account.fields[1]).to be_a Account::Field + expect(account.fields[1].name).to eq 'Occupation' + expect(account.fields[1].value).to eq 'Unit test' + end + end end diff --git a/spec/views/stream_entries/show.html.haml_spec.rb b/spec/views/stream_entries/show.html.haml_spec.rb @@ -48,7 +48,7 @@ describe 'stream_entries/show.html.haml', without_verify_partial_doubles: true d assign(:stream_entry, reply.stream_entry) assign(:account, alice) assign(:type, reply.stream_entry.activity_type.downcase) - assign(:ancestors, reply.stream_entry.activity.ancestors(bob) ) + assign(:ancestors, reply.stream_entry.activity.ancestors(1, bob) ) assign(:descendants, reply.stream_entry.activity.descendants(bob)) render