Browse Source

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

Conflicts manually resolved:
- app/services/post_status_service.rb
- config/locales/simple_form.pl.yml
- config/routes.rb
- config/webpack/loaders/sass.js
- config/webpack/shared.js
- package.json
- yarn.lock
Thibaut Girka 1 month ago
parent
commit
571d219bb9
100 changed files with 902 additions and 654 deletions
  1. 27
    27
      .circleci/config.yml
  2. 1
    1
      .codeclimate.yml
  3. 2
    0
      .eslintrc.yml
  4. 0
    9
      .postcssrc.yml
  5. 1
    1
      .ruby-version
  6. 5
    1
      Dockerfile
  7. 3
    3
      Gemfile
  8. 4
    4
      Gemfile.lock
  9. 1
    1
      Vagrantfile
  10. 1
    1
      app/controllers/admin/account_actions_controller.rb
  11. 2
    3
      app/controllers/admin/accounts_controller.rb
  12. 22
    0
      app/controllers/admin/followers_controller.rb
  13. 6
    4
      app/controllers/api/base_controller.rb
  14. 6
    4
      app/controllers/api/v1/accounts/statuses_controller.rb
  15. 23
    3
      app/controllers/api/v1/accounts_controller.rb
  16. 9
    1
      app/controllers/auth/confirmations_controller.rb
  17. 1
    0
      app/controllers/auth/registrations_controller.rb
  18. 1
    0
      app/controllers/concerns/signature_verification.rb
  19. 19
    0
      app/controllers/settings/exports/blocked_domains_controller.rb
  20. 19
    0
      app/controllers/settings/exports/lists_controller.rb
  21. 1
    0
      app/helpers/settings_helper.rb
  22. 1
    1
      app/javascript/images/screen_interactions.svg
  23. 6
    6
      app/javascript/mastodon/actions/compose.js
  24. 1
    1
      app/javascript/mastodon/api.js
  25. 4
    1
      app/javascript/mastodon/components/status_action_bar.js
  26. 1
    2
      app/javascript/mastodon/containers/domain_container.js
  27. 1
    1
      app/javascript/mastodon/features/emoji/emoji_map.json
  28. 2
    2
      app/javascript/mastodon/features/getting_started/index.js
  29. 2
    2
      app/javascript/mastodon/features/introduction/index.js
  30. 8
    10
      app/javascript/mastodon/features/notifications/components/notification.js
  31. 8
    1
      app/javascript/mastodon/features/status/components/action_bar.js
  32. 1
    1
      app/javascript/mastodon/features/ui/containers/columns_area_container.js
  33. 2
    2
      app/javascript/mastodon/features/ui/containers/loading_bar_container.js
  34. 1
    1
      app/javascript/mastodon/features/ui/index.js
  35. 1
    0
      app/javascript/mastodon/initial_state.js
  36. 0
    33
      app/javascript/mastodon/link_header.js
  37. 18
    18
      app/javascript/mastodon/locales/ar.json
  38. 1
    1
      app/javascript/mastodon/locales/ca.json
  39. 26
    26
      app/javascript/mastodon/locales/co.json
  40. 24
    24
      app/javascript/mastodon/locales/cs.json
  41. 21
    21
      app/javascript/mastodon/locales/de.json
  42. 22
    22
      app/javascript/mastodon/locales/el.json
  43. 1
    1
      app/javascript/mastodon/locales/en.json
  44. 19
    19
      app/javascript/mastodon/locales/eo.json
  45. 22
    22
      app/javascript/mastodon/locales/fr.json
  46. 22
    22
      app/javascript/mastodon/locales/gl.json
  47. 22
    22
      app/javascript/mastodon/locales/ko.json
  48. 22
    22
      app/javascript/mastodon/locales/nl.json
  49. 1
    1
      app/javascript/mastodon/locales/oc.json
  50. 28
    28
      app/javascript/mastodon/locales/sk.json
  51. 1
    1
      app/javascript/mastodon/reducers/timelines.js
  52. 1
    1
      app/javascript/mastodon/store/configureStore.js
  53. 7
    1
      app/javascript/styles/mastodon/dashboard.scss
  54. 2
    0
      app/lib/activitypub/activity.rb
  55. 3
    2
      app/lib/activitypub/activity/block.rb
  56. 1
    1
      app/lib/activitypub/activity/create.rb
  57. 0
    2
      app/lib/activitypub/activity/flag.rb
  58. 2
    2
      app/lib/activitypub/activity/follow.rb
  59. 43
    0
      app/lib/activitypub/activity/move.rb
  60. 1
    0
      app/lib/activitypub/adapter.rb
  61. 1
    1
      app/lib/ostatus/activity/creation.rb
  62. 12
    0
      app/lib/request.rb
  63. 10
    6
      app/mailers/notification_mailer.rb
  64. 14
    4
      app/models/account.rb
  65. 15
    0
      app/models/concerns/domain_normalizable.rb
  66. 2
    8
      app/models/domain_block.rb
  67. 1
    7
      app/models/email_domain_block.rb
  68. 34
    4
      app/models/export.rb
  69. 0
    2
      app/models/form/status_batch.rb
  70. 1
    1
      app/models/report_note.rb
  71. 12
    3
      app/models/user.rb
  72. 5
    0
      app/serializers/activitypub/actor_serializer.rb
  73. 1
    0
      app/serializers/initial_state_serializer.rb
  74. 1
    0
      app/services/activitypub/process_account_service.rb
  75. 1
    1
      app/services/activitypub/process_collection_service.rb
  76. 2
    2
      app/services/after_block_domain_from_account_service.rb
  77. 23
    0
      app/services/app_sign_up_service.rb
  78. 2
    2
      app/services/authorize_follow_service.rb
  79. 2
    4
      app/services/block_service.rb
  80. 3
    5
      app/services/follow_service.rb
  81. 4
    2
      app/services/post_status_service.rb
  82. 4
    2
      app/services/process_mentions_service.rb
  83. 2
    2
      app/services/reject_follow_service.rb
  84. 1
    0
      app/services/resolve_account_service.rb
  85. 2
    0
      app/services/search_service.rb
  86. 2
    2
      app/services/unblock_service.rb
  87. 2
    2
      app/services/unfollow_service.rb
  88. 0
    1
      app/services/verify_link_service.rb
  89. 9
    8
      app/validators/blacklisted_email_validator.rb
  90. 7
    2
      app/validators/disallowed_hashtags_validator.rb
  91. 12
    10
      app/validators/status_length_validator.rb
  92. 166
    181
      app/views/admin/accounts/show.html.haml
  93. 28
    0
      app/views/admin/followers/index.html.haml
  94. 1
    1
      app/views/admin/statuses/index.html.haml
  95. 1
    1
      app/views/notification_mailer/digest.html.haml
  96. 1
    1
      app/views/notification_mailer/digest.text.erb
  97. 8
    0
      app/views/settings/exports/show.html.haml
  98. 6
    2
      app/views/user_mailer/confirmation_instructions.html.haml
  99. 1
    1
      app/views/user_mailer/confirmation_instructions.text.erb
  100. 0
    0
      app/workers/activitypub/distribution_worker.rb

+ 27
- 27
.circleci/config.yml View File

@@ -3,7 +3,7 @@ version: 2
3 3
 aliases:
4 4
   - &defaults
5 5
     docker:
6
-      - image: circleci/ruby:2.5.1-stretch-node
6
+      - image: circleci/ruby:2.6.0-stretch-node
7 7
         environment: &ruby_environment
8 8
           BUNDLE_APP_CONFIG: ./.bundle/
9 9
           DB_HOST: localhost
@@ -98,21 +98,21 @@ jobs:
98 98
     <<: *defaults
99 99
     <<: *install_steps
100 100
 
101
-  install-ruby2.5:
101
+  install-ruby2.6:
102 102
     <<: *defaults
103 103
     <<: *install_ruby_dependencies
104 104
 
105
-  install-ruby2.4:
105
+  install-ruby2.5:
106 106
     <<: *defaults
107 107
     docker:
108
-      - image: circleci/ruby:2.4.4-stretch-node
108
+      - image: circleci/ruby:2.5.3-stretch-node
109 109
         environment: *ruby_environment
110 110
     <<: *install_ruby_dependencies
111 111
 
112
-  install-ruby2.3:
112
+  install-ruby2.4:
113 113
     <<: *defaults
114 114
     docker:
115
-      - image: circleci/ruby:2.3.7-stretch-node
115
+      - image: circleci/ruby:2.4.5-stretch-node
116 116
         environment: *ruby_environment
117 117
     <<: *install_ruby_dependencies
118 118
 
@@ -131,43 +131,43 @@ jobs:
131 131
               - ./mastodon/public/assets
132 132
               - ./mastodon/public/packs-test/
133 133
 
134
-  test-ruby2.5:
134
+  test-ruby2.6:
135 135
     <<: *defaults
136 136
     docker:
137
-      - image: circleci/ruby:2.5.1-stretch-node
137
+      - image: circleci/ruby:2.6.0-stretch-node
138 138
         environment: *ruby_environment
139
-      - image: circleci/postgres:10.3-alpine
139
+      - image: circleci/postgres:10.6-alpine
140 140
         environment:
141 141
           POSTGRES_USER: root
142
-      - image: circleci/redis:4.0.9-alpine
142
+      - image: circleci/redis:5.0.3-alpine3.8
143 143
     <<: *test_steps
144 144
 
145
-  test-ruby2.4:
145
+  test-ruby2.5:
146 146
     <<: *defaults
147 147
     docker:
148
-      - image: circleci/ruby:2.4.4-stretch-node
148
+      - image: circleci/ruby:2.5.3-stretch-node
149 149
         environment: *ruby_environment
150
-      - image: circleci/postgres:10.3-alpine
150
+      - image: circleci/postgres:10.6-alpine
151 151
         environment:
152 152
           POSTGRES_USER: root
153
-      - image: circleci/redis:4.0.9-alpine
153
+      - image: circleci/redis:4.0.12-alpine
154 154
     <<: *test_steps
155 155
 
156
-  test-ruby2.3:
156
+  test-ruby2.4:
157 157
     <<: *defaults
158 158
     docker:
159
-      - image: circleci/ruby:2.3.7-stretch-node
159
+      - image: circleci/ruby:2.4.5-stretch-node
160 160
         environment: *ruby_environment
161
-      - image: circleci/postgres:10.3-alpine
161
+      - image: circleci/postgres:10.6-alpine
162 162
         environment:
163 163
           POSTGRES_USER: root
164
-      - image: circleci/redis:4.0.9-alpine
164
+      - image: circleci/redis:4.0.12-alpine
165 165
     <<: *test_steps
166 166
 
167 167
   test-webui:
168 168
     <<: *defaults
169 169
     docker:
170
-      - image: circleci/node:8.11.1-stretch
170
+      - image: circleci/node:8.15.0-stretch
171 171
     steps:
172 172
       - *attach_workspace
173 173
       - run: ./bin/retry yarn test:jest
@@ -186,20 +186,24 @@ workflows:
186 186
   build-and-test:
187 187
     jobs:
188 188
       - install
189
-      - install-ruby2.5:
189
+      - install-ruby2.6:
190 190
           requires:
191 191
             - install
192
-      - install-ruby2.4:
192
+            - install-ruby2.5
193
+      - install-ruby2.5:
193 194
           requires:
194 195
             - install
195
-            - install-ruby2.5
196
-      - install-ruby2.3:
196
+      - install-ruby2.4:
197 197
           requires:
198 198
             - install
199 199
             - install-ruby2.5
200 200
       - build:
201 201
           requires:
202 202
             - install-ruby2.5
203
+      - test-ruby2.6:
204
+          requires:
205
+            - install-ruby2.6
206
+            - build
203 207
       - test-ruby2.5:
204 208
           requires:
205 209
             - install-ruby2.5
@@ -208,10 +212,6 @@ workflows:
208 212
           requires:
209 213
             - install-ruby2.4
210 214
             - build
211
-      - test-ruby2.3:
212
-          requires:
213
-            - install-ruby2.3
214
-            - build
215 215
       - test-webui:
216 216
           requires:
217 217
             - install

+ 1
- 1
.codeclimate.yml View File

@@ -27,7 +27,7 @@ plugins:
27 27
     enabled: true
28 28
   eslint:
29 29
     enabled: true
30
-    channel: eslint-4
30
+    channel: eslint-5
31 31
   rubocop:
32 32
     enabled: true
33 33
     channel: rubocop-0-54

+ 2
- 0
.eslintrc.yml View File

@@ -26,6 +26,8 @@ parserOptions:
26 26
   ecmaVersion: 2018
27 27
 
28 28
 settings:
29
+  react:
30
+    version: detect
29 31
   import/extensions:
30 32
   - .js
31 33
   import/ignore:

+ 0
- 9
.postcssrc.yml View File

@@ -1,9 +0,0 @@
1
-plugins:
2
-  postcss-smart-import: {}
3
-  precss: {}
4
-  autoprefixer:
5
-    browsers:
6
-      - last 2 versions
7
-      - IE >= 11
8
-      - iOS >= 9
9
-  postcss-object-fit-images: {}

+ 1
- 1
.ruby-version View File

@@ -1 +1 @@
1
-2.5.3
1
+2.6.0

+ 5
- 1
Dockerfile View File

@@ -31,6 +31,8 @@ RUN apk -U upgrade \
31 31
     libidn-dev \
32 32
     libressl \
33 33
     libtool \
34
+    libxml2-dev \
35
+    libxslt-dev \
34 36
     postgresql-dev \
35 37
     protobuf-dev \
36 38
     python \
@@ -43,6 +45,8 @@ RUN apk -U upgrade \
43 45
     imagemagick \
44 46
     libidn \
45 47
     libpq \
48
+    libxml2 \
49
+    libxslt \
46 50
     protobuf \
47 51
     tini \
48 52
     tzdata \
@@ -67,7 +71,7 @@ COPY stack-fix.c /lib
67 71
 RUN gcc -shared -fPIC /lib/stack-fix.c -o /lib/stack-fix.so
68 72
 RUN rm /lib/stack-fix.c
69 73
 
70
-RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \
74
+RUN bundle config build.nokogiri --use-system-libraries --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \
71 75
  && bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \
72 76
  && yarn install --pure-lockfile --ignore-engines \
73 77
  && yarn cache clean

+ 3
- 3
Gemfile View File

@@ -1,7 +1,7 @@
1 1
 # frozen_string_literal: true
2 2
 
3 3
 source 'https://rubygems.org'
4
-ruby '>= 2.3.0', '< 2.6.0'
4
+ruby '>= 2.4.0', '< 2.7.0'
5 5
 
6 6
 gem 'pkg-config', '~> 1.3'
7 7
 
@@ -29,7 +29,7 @@ gem 'browser'
29 29
 gem 'charlock_holmes', '~> 0.7.6'
30 30
 gem 'iso-639'
31 31
 gem 'chewy', '~> 5.0'
32
-gem 'cld3', '~> 3.2.0'
32
+gem 'cld3', '~> 3.2.3'
33 33
 gem 'devise', '~> 4.5'
34 34
 gem 'devise-two-factor', '~> 3.0'
35 35
 
@@ -115,7 +115,7 @@ group :test do
115 115
   gem 'rails-controller-testing', '~> 1.0'
116 116
   gem 'rspec-sidekiq', '~> 3.0'
117 117
   gem 'simplecov', '~> 0.16', require: false
118
-  gem 'webmock', '~> 3.4'
118
+  gem 'webmock', '~> 3.5'
119 119
   gem 'parallel_tests', '~> 2.27'
120 120
 end
121 121
 

+ 4
- 4
Gemfile.lock View File

@@ -142,7 +142,7 @@ GEM
142 142
       elasticsearch (>= 2.0.0)
143 143
       elasticsearch-dsl
144 144
     chunky_png (1.3.10)
145
-    cld3 (3.2.2)
145
+    cld3 (3.2.3)
146 146
       ffi (>= 1.1.0, < 1.10.0)
147 147
     climate_control (0.2.0)
148 148
     cocaine (0.5.8)
@@ -631,7 +631,7 @@ GEM
631 631
     uniform_notifier (1.12.1)
632 632
     warden (1.2.7)
633 633
       rack (>= 1.0)
634
-    webmock (3.4.2)
634
+    webmock (3.5.1)
635 635
       addressable (>= 2.3.6)
636 636
       crack (>= 0.3.2)
637 637
       hashdiff
@@ -672,7 +672,7 @@ DEPENDENCIES
672 672
   capybara (~> 3.12)
673 673
   charlock_holmes (~> 0.7.6)
674 674
   chewy (~> 5.0)
675
-  cld3 (~> 3.2.0)
675
+  cld3 (~> 3.2.3)
676 676
   climate_control (~> 0.2)
677 677
   derailed_benchmarks
678 678
   devise (~> 4.5)
@@ -766,7 +766,7 @@ DEPENDENCIES
766 766
   tty-prompt (~> 0.18)
767 767
   twitter-text (~> 1.14)
768 768
   tzinfo-data (~> 1.2018)
769
-  webmock (~> 3.4)
769
+  webmock (~> 3.5)
770 770
   webpacker (~> 3.5)
771 771
   webpush
772 772
 

+ 1
- 1
Vagrantfile View File

@@ -44,7 +44,7 @@ sudo apt-get install \
44 44
 
45 45
 # Install rvm
46 46
 read RUBY_VERSION < .ruby-version
47
-gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
47
+gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
48 48
 curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
49 49
 source /home/vagrant/.rvm/scripts/rvm
50 50
 

+ 1
- 1
app/controllers/admin/account_actions_controller.rb View File

@@ -17,7 +17,7 @@ module Admin
17 17
       account_action.save!
18 18
 
19 19
       if account_action.with_report?
20
-        redirect_to admin_report_path(account_action.report)
20
+        redirect_to admin_reports_path
21 21
       else
22 22
         redirect_to admin_account_path(@account.id)
23 23
       end

+ 2
- 3
app/controllers/admin/accounts_controller.rb View File

@@ -62,9 +62,8 @@ module Admin
62 62
     def redownload
63 63
       authorize @account, :redownload?
64 64
 
65
-      @account.reset_avatar!
66
-      @account.reset_header!
67
-      @account.save!
65
+      @account.update!(last_webfingered_at: nil)
66
+      ResolveAccountService.new.call(@account)
68 67
 
69 68
       redirect_to admin_account_path(@account.id)
70 69
     end

+ 22
- 0
app/controllers/admin/followers_controller.rb View File

@@ -0,0 +1,22 @@
1
+# frozen_string_literal: true
2
+
3
+module Admin
4
+  class FollowersController < BaseController
5
+    before_action :set_account
6
+
7
+    PER_PAGE = 40
8
+
9
+    def index
10
+      authorize :account, :index?
11
+      @followers = followers.recent.page(params[:page]).per(PER_PAGE)
12
+    end
13
+
14
+    def set_account
15
+      @account = Account.find(params[:account_id])
16
+    end
17
+
18
+    def followers
19
+      Follow.includes(:account).where(target_account: @account)
20
+    end
21
+  end
22
+end

+ 6
- 4
app/controllers/api/base_controller.rb View File

@@ -68,12 +68,14 @@ class Api::BaseController < ApplicationController
68 68
   end
69 69
 
70 70
   def require_user!
71
-    if current_user && !current_user.disabled?
72
-      set_user_activity
73
-    elsif current_user
71
+    if !current_user
72
+      render json: { error: 'This method requires an authenticated user' }, status: 422
73
+    elsif current_user.disabled?
74 74
       render json: { error: 'Your login is currently disabled' }, status: 403
75
+    elsif !current_user.confirmed?
76
+      render json: { error: 'Email confirmation is not completed' }, status: 403
75 77
     else
76
-      render json: { error: 'This method requires an authenticated user' }, status: 422
78
+      set_user_activity
77 79
     end
78 80
   end
79 81
 

+ 6
- 4
app/controllers/api/v1/accounts/statuses_controller.rb View File

@@ -28,13 +28,11 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
28 28
 
29 29
   def account_statuses
30 30
     statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
31
-    statuses = statuses.paginate_by_id(
32
-      limit_param(DEFAULT_STATUSES_LIMIT),
33
-      params_slice(:max_id, :since_id, :min_id)
34
-    )
31
+    statuses = statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id))
35 32
 
36 33
     statuses.merge!(only_media_scope) if truthy_param?(:only_media)
37 34
     statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
35
+    statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
38 36
 
39 37
     statuses
40 38
   end
@@ -65,6 +63,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
65 63
     Status.without_replies
66 64
   end
67 65
 
66
+  def no_reblogs_scope
67
+    Status.without_reblogs
68
+  end
69
+
68 70
   def pagination_params(core_params)
69 71
     params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
70 72
   end

+ 23
- 3
app/controllers/api/v1/accounts_controller.rb View File

@@ -1,14 +1,16 @@
1 1
 # frozen_string_literal: true
2 2
 
3 3
 class Api::V1::AccountsController < Api::BaseController
4
-  before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
4
+  before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :block, :unblock, :mute, :unmute]
5 5
   before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, only: [:follow, :unfollow]
6 6
   before_action -> { doorkeeper_authorize! :follow, :'write:mutes' }, only: [:mute, :unmute]
7 7
   before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, only: [:block, :unblock]
8
+  before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create]
8 9
 
9
-  before_action :require_user!, except: [:show]
10
-  before_action :set_account
10
+  before_action :require_user!, except: [:show, :create]
11
+  before_action :set_account, except: [:create]
11 12
   before_action :check_account_suspension, only: [:show]
13
+  before_action :check_enabled_registrations, only: [:create]
12 14
 
13 15
   respond_to :json
14 16
 
@@ -16,6 +18,16 @@ class Api::V1::AccountsController < Api::BaseController
16 18
     render json: @account, serializer: REST::AccountSerializer
17 19
   end
18 20
 
21
+  def create
22
+    token    = AppSignUpService.new.call(doorkeeper_token.application, account_params)
23
+    response = Doorkeeper::OAuth::TokenResponse.new(token)
24
+
25
+    headers.merge!(response.headers)
26
+
27
+    self.response_body = Oj.dump(response.body)
28
+    self.status        = response.status
29
+  end
30
+
19 31
   def follow
20 32
     FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))
21 33
 
@@ -62,4 +74,12 @@ class Api::V1::AccountsController < Api::BaseController
62 74
   def check_account_suspension
63 75
     gone if @account.suspended?
64 76
   end
77
+
78
+  def account_params
79
+    params.permit(:username, :email, :password, :agreement)
80
+  end
81
+
82
+  def check_enabled_registrations
83
+    forbidden if single_user_mode? || !Setting.open_registrations
84
+  end
65 85
 end

+ 9
- 1
app/controllers/auth/confirmations_controller.rb View File

@@ -7,9 +7,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
7 7
   before_action :set_user, only: [:finish_signup]
8 8
   before_action :set_pack
9 9
 
10
-  # GET/PATCH /users/:id/finish_signup
11 10
   def finish_signup
12 11
     return unless request.patch? && params[:user]
12
+
13 13
     if @user.update(user_params)
14 14
       @user.skip_reconfirmation!
15 15
       bypass_sign_in(@user)
@@ -36,4 +36,12 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
36 36
   def user_params
37 37
     params.require(:user).permit(:email)
38 38
   end
39
+
40
+  def after_confirmation_path_for(_resource_name, user)
41
+    if user.created_by_application && truthy_param?(:redirect_to_app)
42
+      user.created_by_application.redirect_uri
43
+    else
44
+      super
45
+    end
46
+  end
39 47
 end

+ 1
- 0
app/controllers/auth/registrations_controller.rb View File

@@ -27,6 +27,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
27 27
 
28 28
     resource.locale      = I18n.locale
29 29
     resource.invite_code = params[:invite_code] if resource.invite_code.blank?
30
+    resource.agreement   = true
30 31
 
31 32
     resource.build_account if resource.account.nil?
32 33
   end

+ 1
- 0
app/controllers/concerns/signature_verification.rb View File

@@ -47,6 +47,7 @@ module SignatureVerification
47 47
       .with_fallback { nil }
48 48
       .with_threshold(1)
49 49
       .with_cool_off_time(5.minutes.seconds)
50
+      .with_error_handler { |error, handle| error.is_a?(HTTP::Error) ? handle.call(error) : raise(error) }
50 51
 
51 52
     account = account_stoplight.run
52 53
 

+ 19
- 0
app/controllers/settings/exports/blocked_domains_controller.rb View File

@@ -0,0 +1,19 @@
1
+# frozen_string_literal: true
2
+
3
+module Settings
4
+  module Exports
5
+    class BlockedDomainsController < ApplicationController
6
+      include ExportControllerConcern
7
+
8
+      def index
9
+        send_export_file
10
+      end
11
+
12
+      private
13
+
14
+      def export_data
15
+        @export.to_blocked_domains_csv
16
+      end
17
+    end
18
+  end
19
+end

+ 19
- 0
app/controllers/settings/exports/lists_controller.rb View File

@@ -0,0 +1,19 @@
1
+# frozen_string_literal: true
2
+
3
+module Settings
4
+  module Exports
5
+    class ListsController < ApplicationController
6
+      include ExportControllerConcern
7
+
8
+      def index
9
+        send_export_file
10
+      end
11
+
12
+      private
13
+
14
+      def export_data
15
+        @export.to_lists_csv
16
+      end
17
+    end
18
+  end
19
+end

+ 1
- 0
app/helpers/settings_helper.rb View File

@@ -30,6 +30,7 @@ module SettingsHelper
30 30
     ja: '日本語',
31 31
     ka: 'ქართული',
32 32
     ko: '한국어',
33
+    ml: 'മലയാളം',
33 34
     nl: 'Nederlands',
34 35
     no: 'Norsk',
35 36
     oc: 'Occitan',

+ 1
- 1
app/javascript/images/screen_interactions.svg
File diff suppressed because it is too large
View File


+ 6
- 6
app/javascript/mastodon/actions/compose.js View File

@@ -130,6 +130,12 @@ export function submitCompose(routerHistory) {
130 130
         'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
131 131
       },
132 132
     }).then(function (response) {
133
+      if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
134
+        routerHistory.push('/timelines/direct');
135
+      } else if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
136
+        routerHistory.goBack();
137
+      }
138
+
133 139
       dispatch(insertIntoTagHistory(response.data.tags, status));
134 140
       dispatch(submitComposeSuccess({ ...response.data }));
135 141
 
@@ -142,12 +148,6 @@ export function submitCompose(routerHistory) {
142 148
         }
143 149
       };
144 150
 
145
-      if (response.data.visibility === 'direct' && getState().getIn(['conversations', 'mounted']) <= 0 && routerHistory) {
146
-        routerHistory.push('/timelines/direct');
147
-      } else if (routerHistory && routerHistory.location.pathname === '/statuses/new' && window.history.state) {
148
-        routerHistory.goBack();
149
-      }
150
-
151 151
       if (response.data.visibility !== 'direct') {
152 152
         insertIfOnline('home');
153 153
       }

+ 1
- 1
app/javascript/mastodon/api.js View File

@@ -1,6 +1,6 @@
1 1
 import axios from 'axios';
2
+import LinkHeader from 'http-link-header';
2 3
 import ready from './ready';
3
-import LinkHeader from './link_header';
4 4
 
5 5
 export const getLinks = response => {
6 6
   const value = response.headers.link;

+ 4
- 1
app/javascript/mastodon/components/status_action_bar.js View File

@@ -148,6 +148,7 @@ class StatusActionBar extends ImmutablePureComponent {
148 148
 
149 149
     let menu = [];
150 150
     let reblogIcon = 'retweet';
151
+    let replyIcon;
151 152
     let replyTitle;
152 153
 
153 154
     menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
@@ -190,8 +191,10 @@ class StatusActionBar extends ImmutablePureComponent {
190 191
     }
191 192
 
192 193
     if (status.get('in_reply_to_id', null) === null) {
194
+      replyIcon = 'reply';
193 195
       replyTitle = intl.formatMessage(messages.reply);
194 196
     } else {
197
+      replyIcon = 'reply-all';
195 198
       replyTitle = intl.formatMessage(messages.replyAll);
196 199
     }
197 200
 
@@ -201,7 +204,7 @@ class StatusActionBar extends ImmutablePureComponent {
201 204
 
202 205
     return (
203 206
       <div className='status__action-bar'>
204
-        <div className='status__action-bar__counter'><IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon='reply' onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div>
207
+        <div className='status__action-bar__counter'><IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div>
205 208
         <IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
206 209
         <IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
207 210
         {shareButton}

+ 1
- 2
app/javascript/mastodon/containers/domain_container.js View File

@@ -10,8 +10,7 @@ const messages = defineMessages({
10 10
 });
11 11
 
12 12
 const makeMapStateToProps = () => {
13
-  const mapStateToProps = (state, { }) => ({
14
-  });
13
+  const mapStateToProps = () => ({});
15 14
 
16 15
   return mapStateToProps;
17 16
 };

+ 1
- 1
app/javascript/mastodon/features/emoji/emoji_map.json
File diff suppressed because it is too large
View File


+ 2
- 2
app/javascript/mastodon/features/getting_started/index.js View File

@@ -7,7 +7,7 @@ import { connect } from 'react-redux';
7 7
 import PropTypes from 'prop-types';
8 8
 import ImmutablePropTypes from 'react-immutable-proptypes';
9 9
 import ImmutablePureComponent from 'react-immutable-pure-component';
10
-import { me, invitesEnabled, version } from '../../initial_state';
10
+import { me, invitesEnabled, version, profile_directory } from '../../initial_state';
11 11
 import { fetchFollowRequests } from '../../actions/accounts';
12 12
 import { List as ImmutableList } from 'immutable';
13 13
 import { Link } from 'react-router-dom';
@@ -136,7 +136,7 @@ class GettingStarted extends ImmutablePureComponent {
136 136
 
137 137
           <div className='getting-started__footer'>
138 138
             <ul>
139
-              <li><a href='/explore' target='_blank'><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></a> · </li>
139
+              {profile_directory && <li><a href='/explore' target='_blank'><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></a> · </li>}
140 140
               {invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
141 141
               {multiColumn && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>}
142 142
               <li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>

+ 2
- 2
app/javascript/mastodon/features/introduction/index.js View File

@@ -98,8 +98,8 @@ FrameInteractions.propTypes = {
98 98
   onNext: PropTypes.func.isRequired,
99 99
 };
100 100
 
101
-@connect(state => ({ domain: state.getIn(['meta', 'domain']) }))
102
-export default class Introduction extends React.PureComponent {
101
+export default @connect(state => ({ domain: state.getIn(['meta', 'domain']) }))
102
+class Introduction extends React.PureComponent {
103 103
 
104 104
   static propTypes = {
105 105
     domain: PropTypes.string.isRequired,

+ 8
- 10
app/javascript/mastodon/features/notifications/components/notification.js View File

@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
3 3
 import ImmutablePropTypes from 'react-immutable-proptypes';
4 4
 import StatusContainer from '../../../containers/status_container';
5 5
 import AccountContainer from '../../../containers/account_container';
6
-import RelativeTimestamp from '../../../components/relative_timestamp';
7 6
 import { injectIntl, FormattedMessage } from 'react-intl';
8 7
 import Permalink from '../../../components/permalink';
9 8
 import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -86,13 +85,12 @@ class Notification extends ImmutablePureComponent {
86 85
             <div className='notification__favourite-icon-wrapper'>
87 86
               <i className='fa fa-fw fa-user-plus' />
88 87
             </div>
88
+
89 89
             <span title={notification.get('created_at')}>
90 90
               <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
91
-              <span className='notification__relative_time'>
92
-                <RelativeTimestamp timestamp={notification.get('created_at')} />
93
-              </span>
94 91
             </span>
95 92
           </div>
93
+
96 94
           <AccountContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
97 95
         </div>
98 96
       </HotKeys>
@@ -122,9 +120,9 @@ class Notification extends ImmutablePureComponent {
122 120
             <div className='notification__favourite-icon-wrapper'>
123 121
               <i className='fa fa-fw fa-star star-icon' />
124 122
             </div>
125
-            <FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
126
-            <span className='notification__relative_time'>
127
-              <RelativeTimestamp className='notification__relative_time' timestamp={notification.get('created_at')} />
123
+
124
+            <span title={notification.get('created_at')}>
125
+              <FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
128 126
             </span>
129 127
           </div>
130 128
 
@@ -144,9 +142,9 @@ class Notification extends ImmutablePureComponent {
144 142
             <div className='notification__favourite-icon-wrapper'>
145 143
               <i className='fa fa-fw fa-retweet' />
146 144
             </div>
147
-            <FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
148
-            <span className='notification__relative_time'>
149
-              <RelativeTimestamp className='notification__relative_time' timestamp={notification.get('created_at')} />
145
+
146
+            <span title={notification.get('created_at')}>
147
+              <FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
150 148
             </span>
151 149
           </div>
152 150
 

+ 8
- 1
app/javascript/mastodon/features/status/components/action_bar.js View File

@@ -151,6 +151,13 @@ class ActionBar extends React.PureComponent {
151 151
       <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
152 152
     );
153 153
 
154
+    let replyIcon;
155
+    if (status.get('in_reply_to_id', null) === null) {
156
+      replyIcon = 'reply';
157
+    } else {
158
+      replyIcon = 'reply-all';
159
+    }
160
+
154 161
     let reblogIcon = 'retweet';
155 162
     if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
156 163
     else if (status.get('visibility') === 'private') reblogIcon = 'lock';
@@ -159,7 +166,7 @@ class ActionBar extends React.PureComponent {
159 166
 
160 167
     return (
161 168
       <div className='detailed-status__action-bar'>
162
-        <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReplyClick} /></div>
169
+        <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
163 170
         <div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
164 171
         <div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
165 172
         {shareButton}

+ 1
- 1
app/javascript/mastodon/features/ui/containers/columns_area_container.js View File

@@ -6,4 +6,4 @@ const mapStateToProps = state => ({
6 6
   isModalOpen: !!state.get('modal').modalType,
7 7
 });
8 8
 
9
-export default connect(mapStateToProps, null, null, { withRef: true })(ColumnsArea);
9
+export default connect(mapStateToProps, null, null, { forwardRef: true })(ColumnsArea);

+ 2
- 2
app/javascript/mastodon/features/ui/containers/loading_bar_container.js View File

@@ -1,8 +1,8 @@
1 1
 import { connect }    from 'react-redux';
2 2
 import LoadingBar from 'react-redux-loading-bar';
3 3
 
4
-const mapStateToProps = (state) => ({
5
-  loading: state.get('loadingBar'),
4
+const mapStateToProps = (state, ownProps) => ({
5
+  loading: state.get('loadingBar')[ownProps.scope || 'default'],
6 6
 });
7 7
 
8 8
 export default connect(mapStateToProps)(LoadingBar.WrappedComponent);

+ 1
- 1
app/javascript/mastodon/features/ui/index.js View File

@@ -134,7 +134,7 @@ class SwitchingColumnsArea extends React.PureComponent {
134 134
   });
135 135
 
136 136
   setRef = c => {
137
-    this.node = c.getWrappedInstance().getWrappedInstance();
137
+    this.node = c.getWrappedInstance();
138 138
   }
139 139
 
140 140
   render () {

+ 1
- 0
app/javascript/mastodon/initial_state.js View File

@@ -16,5 +16,6 @@ export const maxChars = (initialState && initialState.max_toot_chars) || 500;
16 16
 export const invitesEnabled = getMeta('invites_enabled');
17 17
 export const version = getMeta('version');
18 18
 export const mascot = getMeta('mascot');
19
+export const profile_directory = getMeta('profile_directory');
19 20
 
20 21
 export default initialState;

+ 0
- 33
app/javascript/mastodon/link_header.js View File

@@ -1,33 +0,0 @@
1
-import Link from 'http-link-header';
2
-import querystring from 'querystring';
3
-
4
-Link.parseAttrs = (link, parts) => {
5
-  let match = null;
6
-  let attr  = '';
7
-  let value = '';
8
-  let attrs = '';
9
-
10
-  let uriAttrs = /<(.*)>;\s*(.*)/gi.exec(parts);
11
-
12
-  if(uriAttrs) {
13
-    attrs = uriAttrs[2];
14
-    link  = Link.parseParams(link, uriAttrs[1]);
15
-  }
16
-
17
-  while(match = Link.attrPattern.exec(attrs)) { // eslint-disable-line no-cond-assign
18
-    attr  = match[1].toLowerCase();
19
-    value = match[4] || match[3] || match[2];
20
-
21
-    if( /\*$/.test(attr)) {
22
-      Link.setAttr(link, attr, Link.parseExtendedValue(value));
23
-    } else if(/%/.test(value)) {
24
-      Link.setAttr(link, attr, querystring.decode(value));
25
-    } else {
26
-      Link.setAttr(link, attr, value);
27
-    }
28
-  }
29
-
30
-  return link;
31
-};
32
-
33
-export default Link;

+ 18
- 18
app/javascript/mastodon/locales/ar.json View File

@@ -149,22 +149,22 @@
149 149
   "home.column_settings.basic": "أساسية",
150 150
   "home.column_settings.show_reblogs": "عرض الترقيات",
151 151
   "home.column_settings.show_replies": "عرض الردود",
152
-  "introduction.federation.action": "Next",
152
+  "introduction.federation.action": "التالي",
153 153
   "introduction.federation.federated.headline": "Federated",
154
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
154
+  "introduction.federation.federated.text": "كافة المنشورات التي نُشِرت إلى العامة على الخوادم الأخرى للفديفرس سوف يتم عرضها على الخيط المُوحَّد.",
155 155
   "introduction.federation.home.headline": "Home",
156
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
156
+  "introduction.federation.home.text": "سوف تُعرَض منشورات الأشخاص الذين تُتابِعهم على الخيط الرئيسي. بإمكانك متابعة أي حساب أيا كان الخادم الذي هو عليه!",
157 157
   "introduction.federation.local.headline": "Local",
158 158
   "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
159 159
   "introduction.interactions.action": "Finish tutorial!",
160
-  "introduction.interactions.favourite.headline": "Favourite",
161
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
162
-  "introduction.interactions.reblog.headline": "Boost",
163
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
164
-  "introduction.interactions.reply.headline": "Reply",
160
+  "introduction.interactions.favourite.headline": "الإضافة إلى المفضلة",
161
+  "introduction.interactions.favourite.text": "يمكِنك إضافة أي تبويق إلى المفضلة و إعلام صاحبه أنك أعجِبت بذاك التبويق.",
162
+  "introduction.interactions.reblog.headline": "الترقية",
163
+  "introduction.interactions.reblog.text": "يمكنكم مشاركة تبويقات الأشخاص الآخرين مع متابِعيكم عن طريق ترقيتها.",
164
+  "introduction.interactions.reply.headline": "الرد",
165 165
   "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
166
-  "introduction.welcome.action": "Let's go!",
167
-  "introduction.welcome.headline": "First steps",
166
+  "introduction.welcome.action": "هيا بنا!",
167
+  "introduction.welcome.headline": "الخطوات الأولى",
168 168
   "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
169 169
   "keyboard_shortcuts.back": "للعودة",
170 170
   "keyboard_shortcuts.blocked": "لفتح قائمة المستخدمين المحظورين",
@@ -242,20 +242,20 @@
242 242
   "notifications.clear_confirmation": "أمتأكد من أنك تود مسح جل الإخطارات الخاصة بك و المتلقاة إلى حد الآن ؟",
243 243
   "notifications.column_settings.alert": "إشعارات سطح المكتب",
244 244
   "notifications.column_settings.favourite": "المُفَضَّلة :",
245
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
246
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
247
-  "notifications.column_settings.filter_bar.show": "Show",
245
+  "notifications.column_settings.filter_bar.advanced": "عرض كافة الفئات",
246
+  "notifications.column_settings.filter_bar.category": "شريط الفلترة السريعة",
247
+  "notifications.column_settings.filter_bar.show": "عرض",
248 248
   "notifications.column_settings.follow": "متابعُون جُدُد :",
249 249
   "notifications.column_settings.mention": "الإشارات :",
250 250
   "notifications.column_settings.push": "الإخطارات المدفوعة",
251 251
   "notifications.column_settings.reblog": "الترقيّات:",
252 252
   "notifications.column_settings.show": "إعرِضها في عمود",
253 253
   "notifications.column_settings.sound": "أصدر صوتا",
254
-  "notifications.filter.all": "All",
255
-  "notifications.filter.boosts": "Boosts",
256
-  "notifications.filter.favourites": "Favourites",
257
-  "notifications.filter.follows": "Follows",
258
-  "notifications.filter.mentions": "Mentions",
254
+  "notifications.filter.all": "الكل",
255
+  "notifications.filter.boosts": "الترقيات",
256
+  "notifications.filter.favourites": "المفضلة",
257
+  "notifications.filter.follows": "يتابِع",
258
+  "notifications.filter.mentions": "الإشارات",
259 259
   "notifications.group": "{count} إشعارات",
260 260
   "privacy.change": "إضبط خصوصية المنشور",
261 261
   "privacy.direct.long": "أنشر إلى المستخدمين المشار إليهم فقط",

+ 1
- 1
app/javascript/mastodon/locales/ca.json View File

@@ -341,7 +341,7 @@
341 341
   "upload_area.title": "Arrossega i deixa anar per carregar",
342 342
   "upload_button.label": "Afegir multimèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
343 343
   "upload_form.description": "Descriure els problemes visuals",
344
-  "upload_form.focus": "Retallar",
344
+  "upload_form.focus": "Modificar la previsualització",
345 345
   "upload_form.undo": "Esborra",
346 346
   "upload_progress.label": "Pujant...",
347 347
   "video.close": "Tancar el vídeo",

+ 26
- 26
app/javascript/mastodon/locales/co.json View File

@@ -145,27 +145,27 @@
145 145
   "hashtag.column_settings.tag_mode.all": "Tutti quessi",
146 146
   "hashtag.column_settings.tag_mode.any": "Unu di quessi",
147 147
   "hashtag.column_settings.tag_mode.none": "Nisunu di quessi",
148
-  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
148
+  "hashtag.column_settings.tag_toggle": "Inchjude tag addiziunali per sta colonna",
149 149
   "home.column_settings.basic": "Bàsichi",
150 150
   "home.column_settings.show_reblogs": "Vede e spartere",
151 151
   "home.column_settings.show_replies": "Vede e risposte",
152
-  "introduction.federation.action": "Next",
153
-  "introduction.federation.federated.headline": "Federated",
154
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
155
-  "introduction.federation.home.headline": "Home",
156
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
157
-  "introduction.federation.local.headline": "Local",
158
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
159
-  "introduction.interactions.action": "Finish tutorial!",
160
-  "introduction.interactions.favourite.headline": "Favourite",
161
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
162
-  "introduction.interactions.reblog.headline": "Boost",
163
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
164
-  "introduction.interactions.reply.headline": "Reply",
165
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
166
-  "introduction.welcome.action": "Let's go!",
167
-  "introduction.welcome.headline": "First steps",
168
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
152
+  "introduction.federation.action": "Cuntinuà",
153
+  "introduction.federation.federated.headline": "Federata",
154
+  "introduction.federation.federated.text": "I statuti pubblichi da l'altri servori di u fediverse saranu mustrati nant'à a linea pubblica federata.",
155
+  "introduction.federation.home.headline": "Accolta",
156
+  "introduction.federation.home.text": "I statuti da a ghjente che vo siguitate saranu affissati nant'à a linea d'accolta. Pudete seguità qualvogliasia nant'à tutti i servori!",
157
+  "introduction.federation.local.headline": "Lucale",
158
+  "introduction.federation.local.text": "I statuti pubblichi da quelli chì sò nant'a listessu servore chì voi ponu esse visti indè a linea pubblica lucale.",
159
+  "introduction.interactions.action": "Finisce u tutoriale!",
160
+  "introduction.interactions.favourite.headline": "Favuritu",
161
+  "introduction.interactions.favourite.text": "Pudete salvà un statutu per ritruvallu più tardi, è fà sapè à l'autore chì v'hè piaciutu, l'aghustendu à i vostri favuriti.",
162
+  "introduction.interactions.reblog.headline": "Sparte",
163
+  "introduction.interactions.reblog.text": "Pudete sparte i statuti d'altre persone à i vostri abbunati cù u buttone di spartera.",
164
+  "introduction.interactions.reply.headline": "Risponde",
165
+  "introduction.interactions.reply.text": "Pudete risponde à d'altre persone o a i vostri propii statuti, cio chì i ligarà indè una cunversazione.",
166
+  "introduction.welcome.action": "Andemu!",
167
+  "introduction.welcome.headline": "Primi passi",
168
+  "introduction.welcome.text": "Benvenutu·a indè u fediverse! In qualchi minuta, puderete diffonde missaghji è parlà à i vostri amichi nant'à una varietà maiò di servori. Mà quess'istanza, {domain}, hè speciale—ghjè induve hè uspitatu u vostru prufile, allora ricurdatevi di u so nome.",
169 169
   "keyboard_shortcuts.back": "rivultà",
170 170
   "keyboard_shortcuts.blocked": "per apre una lista d'utilizatori bluccati",
171 171
   "keyboard_shortcuts.boost": "sparte",
@@ -242,20 +242,20 @@
242 242
   "notifications.clear_confirmation": "Site sicuru·a che vulete toglie tutte ste nutificazione?",
243 243
   "notifications.column_settings.alert": "Nutificazione nant'à l'urdinatore",
244 244
   "notifications.column_settings.favourite": "Favuriti:",
245
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
246
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
247
-  "notifications.column_settings.filter_bar.show": "Show",
245
+  "notifications.column_settings.filter_bar.advanced": "Affissà tutte e categurie",
246
+  "notifications.column_settings.filter_bar.category": "Barra di ricerca pronta",
247
+  "notifications.column_settings.filter_bar.show": "Mustrà",
248 248
   "notifications.column_settings.follow": "Abbunati novi:",
249 249
   "notifications.column_settings.mention": "Minzione:",
250 250
   "notifications.column_settings.push": "Nutificazione Push",
251 251
   "notifications.column_settings.reblog": "Spartere:",
252 252
   "notifications.column_settings.show": "Mustrà indè a colonna",
253 253
   "notifications.column_settings.sound": "Sunà",
254
-  "notifications.filter.all": "All",
255
-  "notifications.filter.boosts": "Boosts",
256
-  "notifications.filter.favourites": "Favourites",
257
-  "notifications.filter.follows": "Follows",
258
-  "notifications.filter.mentions": "Mentions",
254
+  "notifications.filter.all": "Tuttu",
255
+  "notifications.filter.boosts": "Spartere",
256
+  "notifications.filter.favourites": "Favuriti",
257
+  "notifications.filter.follows": "Abbunamenti",
258
+  "notifications.filter.mentions": "Minzione",
259 259
   "notifications.group": "{count} nutificazione",
260 260
   "privacy.change": "Mudificà a cunfidenzialità di u statutu",
261 261
   "privacy.direct.long": "Mandà solu à quelli chì so mintuvati",

+ 24
- 24
app/javascript/mastodon/locales/cs.json View File

@@ -149,23 +149,23 @@
149 149
   "home.column_settings.basic": "Základní",
150 150
   "home.column_settings.show_reblogs": "Zobrazit boosty",
151 151
   "home.column_settings.show_replies": "Zobrazit odpovědi",
152
-  "introduction.federation.action": "Next",
153
-  "introduction.federation.federated.headline": "Federated",
154
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
155
-  "introduction.federation.home.headline": "Home",
156
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
157
-  "introduction.federation.local.headline": "Local",
158
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
159
-  "introduction.interactions.action": "Finish tutorial!",
160
-  "introduction.interactions.favourite.headline": "Favourite",
161
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
152
+  "introduction.federation.action": "Další",
153
+  "introduction.federation.federated.headline": "Federovaná",
154
+  "introduction.federation.federated.text": "Veřejné příspěvky z jiných serverů na fediverse se zobrazí na federované časové ose.",
155
+  "introduction.federation.home.headline": "Domů",
156
+  "introduction.federation.home.text": "Příspěvky od lidí, které sledujete, se objeví ve vašem domovském proudu. Můžete sledovat kohokoliv na jakémkoliv serveru!",
157
+  "introduction.federation.local.headline": "Místní",
158
+  "introduction.federation.local.text": "Veřejné příspěvky od lidí ze stejného serveru, jako vy, se zobrazí na místní časové ose.",
159
+  "introduction.interactions.action": "Dokončit tutoriál!",
160
+  "introduction.interactions.favourite.headline": "Oblíbení",
161
+  "introduction.interactions.favourite.text": "Oblíbením si můžete uložit toot na později a dát jeho autorovi vědět, že se vám líbí.",
162 162
   "introduction.interactions.reblog.headline": "Boost",
163
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
164
-  "introduction.interactions.reply.headline": "Reply",
165
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
166
-  "introduction.welcome.action": "Let's go!",
167
-  "introduction.welcome.headline": "First steps",
168
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
163
+  "introduction.interactions.reblog.text": "Boostnutím můžete sdílet tooty jiných lidí s vašimi sledovately.",
164
+  "introduction.interactions.reply.headline": "Odpověď",
165
+  "introduction.interactions.reply.text": "Můžete odpovídat na tooty jiných lidí i vaše vlastní, což je propojí do konverzace.",
166
+  "introduction.welcome.action": "Jdeme na to!",
167
+  "introduction.welcome.headline": "První kroky",
168
+  "introduction.welcome.text": "Vítejte na fediverse! Za malou chvíli budete moci posílat zprávy a povídat si se svými přátely přes širokou škálu serverů. Tento server, {domain}, je však speciální—je na něm váš profil, proto si zapamatujte jeho jméno.",
169 169
   "keyboard_shortcuts.back": "k návratu zpět",
170 170
   "keyboard_shortcuts.blocked": "k otevření seznamu blokovaných uživatelů",
171 171
   "keyboard_shortcuts.boost": "k boostnutí",
@@ -242,20 +242,20 @@
242 242
   "notifications.clear_confirmation": "Jste si jistý/á, že chcete trvale vymazat všechna vaše oznámení?",
243 243
   "notifications.column_settings.alert": "Desktopová oznámení",
244 244
   "notifications.column_settings.favourite": "Oblíbené:",
245
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
246
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
247
-  "notifications.column_settings.filter_bar.show": "Show",
245
+  "notifications.column_settings.filter_bar.advanced": "Zobrazit všechny kategorie",
246
+  "notifications.column_settings.filter_bar.category": "Panel rychlého filtrování",
247
+  "notifications.column_settings.filter_bar.show": "Zobrazit",
248 248
   "notifications.column_settings.follow": "Noví sledovatelé:",
249 249
   "notifications.column_settings.mention": "Zmínky:",
250 250
   "notifications.column_settings.push": "Push oznámení",
251 251
   "notifications.column_settings.reblog": "Boosty:",
252 252
   "notifications.column_settings.show": "Zobrazit ve sloupci",
253 253
   "notifications.column_settings.sound": "Přehrát zvuk",
254
-  "notifications.filter.all": "All",
255
-  "notifications.filter.boosts": "Boosts",
256
-  "notifications.filter.favourites": "Favourites",
257
-  "notifications.filter.follows": "Follows",
258
-  "notifications.filter.mentions": "Mentions",
254
+  "notifications.filter.all": "Vše",
255
+  "notifications.filter.boosts": "Boosty",
256
+  "notifications.filter.favourites": "Oblíbení",
257
+  "notifications.filter.follows": "Sledování",
258
+  "notifications.filter.mentions": "Zmínky",
259 259
   "notifications.group": "{count} oznámení",
260 260
   "privacy.change": "Změnit soukromí příspěvku",
261 261
   "privacy.direct.long": "Odeslat pouze zmíněným uživatelům",

+ 21
- 21
app/javascript/mastodon/locales/de.json View File

@@ -17,7 +17,7 @@
17 17
   "account.follows_you": "Folgt dir",
18 18
   "account.hide_reblogs": "Geteilte Beiträge von @{name} verbergen",
19 19
   "account.link_verified_on": "Besitz dieses Links wurde geprüft am {date}",
20
-  "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
20
+  "account.locked_info": "Der Privatsphärenstatus dieses Accounts wurde auf gesperrt gesetzt. Die Person bestimmt manuell wer ihm/ihr folgen darf.",
21 21
   "account.media": "Medien",
22 22
   "account.mention": "@{name} erwähnen",
23 23
   "account.moved_to": "{name} ist umgezogen auf:",
@@ -149,23 +149,23 @@
149 149
   "home.column_settings.basic": "Einfach",
150 150
   "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen",
151 151
   "home.column_settings.show_replies": "Antworten anzeigen",
152
-  "introduction.federation.action": "Next",
152
+  "introduction.federation.action": "Weiter",
153 153
   "introduction.federation.federated.headline": "Federated",
154
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
154
+  "introduction.federation.federated.text": "Öffentliche Beiträge von anderen Servern im Fediverse werden in der föderierten Zeitleiste erscheinen.",
155 155
   "introduction.federation.home.headline": "Home",
156
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
156
+  "introduction.federation.home.text": "Beiträge von Leuten, denen du folgst werden in deiner Startseite erscheinen. Du kannst jedem auf irgendeinen Server folgen!",
157 157
   "introduction.federation.local.headline": "Local",
158
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
159
-  "introduction.interactions.action": "Finish tutorial!",
160
-  "introduction.interactions.favourite.headline": "Favourite",
161
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
162
-  "introduction.interactions.reblog.headline": "Boost",
163
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
164
-  "introduction.interactions.reply.headline": "Reply",
165
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
166
-  "introduction.welcome.action": "Let's go!",
167
-  "introduction.welcome.headline": "First steps",
168
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
158
+  "introduction.federation.local.text": "Öffentliche Beiträge von Leuten auf demselben Server wie du werden in der lokalen Zeitleiste erscheinen.",
159
+  "introduction.interactions.action": "Tutorial beenden!",
160
+  "introduction.interactions.favourite.headline": "Favorisieren",
161
+  "introduction.interactions.favourite.text": "Du kannst einen Beitrag für später speichern und dem Autor wissen lassen, dass du ihn magst, indem du ihn favorisierst.",
162
+  "introduction.interactions.reblog.headline": "Teilen",
163
+  "introduction.interactions.reblog.text": "Du kannst Beiträge von anderen Leuten an deine Follower teilen (oder auch \"boosten\").",
164
+  "introduction.interactions.reply.headline": "Antworten",
165
+  "introduction.interactions.reply.text": "Du kannst auf die Beiträge von anderen Leuten antworten und die Beiträge werden dann in eine Konversation zusammengebunden.",
166
+  "introduction.welcome.action": "Lasst uns loslegen!",
167
+  "introduction.welcome.headline": "Erste Schritte",
168
+  "introduction.welcome.text": "Willkommen im Fediverse! In wenigen Momenten wirst du in der Lage sein Nachrichten zu versenden und mit deinen Freunden über Server hinweg in Kontakt zu treten. Aber dieser Server, {domain}, ist sehr speziell — er hostet dein Profil, also merke dir den Namen.",
169 169
   "keyboard_shortcuts.back": "zurück navigieren",
170 170
   "keyboard_shortcuts.blocked": "Liste blockierter Profile öffnen",
171 171
   "keyboard_shortcuts.boost": "boosten",
@@ -242,20 +242,20 @@
242 242
   "notifications.clear_confirmation": "Bist du dir sicher, dass du alle Mitteilungen löschen möchtest?",
243 243
   "notifications.column_settings.alert": "Desktop-Benachrichtigungen",
244 244
   "notifications.column_settings.favourite": "Favorisierungen:",
245
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
246
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
247
-  "notifications.column_settings.filter_bar.show": "Show",
245
+  "notifications.column_settings.filter_bar.advanced": "Zeige alle Kategorien an",
246
+  "notifications.column_settings.filter_bar.category": "Schnellfilterleiste",
247
+  "notifications.column_settings.filter_bar.show": "Anzeigen",
248 248
   "notifications.column_settings.follow": "Neue Folgende:",
249 249
   "notifications.column_settings.mention": "Erwähnungen:",
250 250
   "notifications.column_settings.push": "Push-Benachrichtigungen",
251 251
   "notifications.column_settings.reblog": "Geteilte Beiträge:",
252 252
   "notifications.column_settings.show": "In der Spalte anzeigen",
253 253
   "notifications.column_settings.sound": "Ton abspielen",
254
-  "notifications.filter.all": "All",
254
+  "notifications.filter.all": "Alle",
255 255
   "notifications.filter.boosts": "Boosts",
256
-  "notifications.filter.favourites": "Favourites",
256
+  "notifications.filter.favourites": "Favoriten",
257 257
   "notifications.filter.follows": "Follows",
258
-  "notifications.filter.mentions": "Mentions",
258
+  "notifications.filter.mentions": "Erwähnungen",
259 259
   "notifications.group": "{count} Benachrichtigungen",
260 260
   "privacy.change": "Sichtbarkeit des Beitrags anpassen",
261 261
   "privacy.direct.long": "Beitrag nur an erwähnte Profile",

+ 22
- 22
app/javascript/mastodon/locales/el.json View File

@@ -149,23 +149,23 @@
149 149
   "home.column_settings.basic": "Βασικά",
150 150
   "home.column_settings.show_reblogs": "Εμφάνιση προωθήσεων",
151 151
   "home.column_settings.show_replies": "Εμφάνιση απαντήσεων",
152
-  "introduction.federation.action": "Next",
152
+  "introduction.federation.action": "Επόμενο",
153 153
   "introduction.federation.federated.headline": "Federated",
154
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
154
+  "introduction.federation.federated.text": "Οι δημόσιες αναρτήσεις από άλλους κόμβους του fediverse θα εμφανίζονται στην ομοσπονδιακή ροή.",
155 155
   "introduction.federation.home.headline": "Home",
156
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
156
+  "introduction.federation.home.text": "Οι αναρτήσεις όσων ακολουθείς θα εμφανίζονται στην αρχική ροή. Μπορείς να ακολουθήσεις όποιον θέλεις σε οποιονδήποτε κόμβο!",
157 157
   "introduction.federation.local.headline": "Local",
158
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
159
-  "introduction.interactions.action": "Finish tutorial!",
160
-  "introduction.interactions.favourite.headline": "Favourite",
161
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
162
-  "introduction.interactions.reblog.headline": "Boost",
163
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
164
-  "introduction.interactions.reply.headline": "Reply",
165
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
166
-  "introduction.welcome.action": "Let's go!",
167
-  "introduction.welcome.headline": "First steps",
168
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
158
+  "introduction.federation.local.text": "Οι δημόσιες αναρτήσεις από άτομα στον ίδιο κόμβο με εσένα θα εμφανίζονται στην τοπική ροή.",
159
+  "introduction.interactions.action": "Τέλος μαθήματος!",
160
+  "introduction.interactions.favourite.headline": "Αγαπημένο",
161
+  "introduction.interactions.favourite.text": "Φύλαξε ένα τουτ για αργότερα και να ειδοποιήσεις τον δημιουργό του ότι σου άρεσε σημειώνοντας το ως αγαπημένο.",
162
+  "introduction.interactions.reblog.headline": "Προώθηση",
163
+  "introduction.interactions.reblog.text": "Μοιράσου τουτ άλλων χρηστών με όσους σε ακολουθούν προωθώντας τα.",
164
+  "introduction.interactions.reply.headline": "Απάντηση",
165
+  "introduction.interactions.reply.text": "Μπορείς να απαντήσεις στα τουτ άλλων αλλά ακόμα και στα δικά σου, δένοντας τα όλα μαζί σε μια συζήτηση.",
166
+  "introduction.welcome.action": "Ας ξεκινήσουμε!",
167
+  "introduction.welcome.headline": "Πρώτα βήματα",
168
+  "introduction.welcome.text": "Καλώς ήρθες στο fediverse! Σε πολύ λίγο θα μπορείς να στέλνεις δημοσιεύσεις και να μιλάς με τους φίλους σου σε πολλούς, διαφορετικούς κόμβους. Ο κόμβος {domain} όμως είναι ξεχωριστός — φιλοξενεί τον λογαριασμό σου, για αυτό μα θυμάσαι το όνομά του.",
169 169
   "keyboard_shortcuts.back": "επιστροφή",
170 170
   "keyboard_shortcuts.blocked": "άνοιγμα λίστας αποκλεισμένων χρηστών",
171 171
   "keyboard_shortcuts.boost": "προώθηση",
@@ -242,20 +242,20 @@
242 242
   "notifications.clear_confirmation": "Σίγουρα θέλεις να καθαρίσεις όλες τις ειδοποιήσεις σου;",
243 243
   "notifications.column_settings.alert": "Ειδοποιήσεις επιφάνειας εργασίας",
244 244
   "notifications.column_settings.favourite": "Αγαπημένα:",
245
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
246
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
247
-  "notifications.column_settings.filter_bar.show": "Show",
245
+  "notifications.column_settings.filter_bar.advanced": "Εμφάνιση όλων των κατηγοριών",
246
+  "notifications.column_settings.filter_bar.category": "Μπάρα γρήγορου φίλτρου",
247
+  "notifications.column_settings.filter_bar.show": "Εμφάνιση",
248 248
   "notifications.column_settings.follow": "Νέοι ακόλουθοι:",
249 249
   "notifications.column_settings.mention": "Αναφορές:",
250 250
   "notifications.column_settings.push": "Άμεσες ειδοποιήσεις",
251 251
   "notifications.column_settings.reblog": "Προωθήσεις:",
252 252
   "notifications.column_settings.show": "Εμφάνισε σε στήλη",
253 253
   "notifications.column_settings.sound": "Ηχητική ειδοποίηση",
254
-  "notifications.filter.all": "All",
255
-  "notifications.filter.boosts": "Boosts",
256
-  "notifications.filter.favourites": "Favourites",
257
-  "notifications.filter.follows": "Follows",
258
-  "notifications.filter.mentions": "Mentions",
254
+  "notifications.filter.all": "Όλες",
255
+  "notifications.filter.boosts": "Προωθήσεις",
256
+  "notifications.filter.favourites": "Αγαπημένα",
257
+  "notifications.filter.follows": "Ακόλουθοι",
258
+  "notifications.filter.mentions": "Αναφορές",
259 259
   "notifications.group": "{count} ειδοποιήσεις",
260 260
   "privacy.change": "Προσαρμογή ιδιωτικότητας δημοσίευσης",
261 261
   "privacy.direct.long": "Δημοσίευση μόνο σε όσους και όσες αναφέρονται",

+ 1
- 1
app/javascript/mastodon/locales/en.json View File

@@ -346,7 +346,7 @@
346 346
   "upload_area.title": "Drag & drop to upload",
347 347
   "upload_button.label": "Add media (JPEG, PNG, GIF, WebM, MP4, MOV)",
348 348
   "upload_form.description": "Describe for the visually impaired",
349
-  "upload_form.focus": "Crop",
349
+  "upload_form.focus": "Change preview",
350 350
   "upload_form.undo": "Delete",
351 351
   "upload_progress.label": "Uploading...",
352 352
   "video.close": "Close video",

+ 19
- 19
app/javascript/mastodon/locales/eo.json View File

@@ -11,13 +11,13 @@
11 11
   "account.endorse": "Montri en profilo",
12 12
   "account.follow": "Sekvi",
13 13
   "account.followers": "Sekvantoj",
14
-  "account.followers.empty": "Neniu ankoraŭ sekvas ĉi tiun uzanton.",
14
+  "account.followers.empty": "Ankoraŭ neniu sekvas tiun uzanton.",
15 15
   "account.follows": "Sekvatoj",
16
-  "account.follows.empty": "Ĉi tiu uzanto ne ankoraŭ sekvas iun.",
16
+  "account.follows.empty": "Tiu uzanto ankoraŭ ne sekvas iun.",
17 17
   "account.follows_you": "Sekvas vin",
18 18
   "account.hide_reblogs": "Kaŝi diskonigojn de @{name}",
19
-  "account.link_verified_on": "Proprieto de ĉi tiu ligilo estis kontrolita je {date}",
20
-  "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
19
+  "account.link_verified_on": "La posedanto de tiu ligilo estis kontrolita je {date}",
20
+  "account.locked_info": "La privateco de tiu konto estas elektita kiel fermita. La posedanto povas mane akcepti tiun, kiu povas sekvi rin.",
21 21
   "account.media": "Aŭdovidaĵoj",
22 22
   "account.mention": "Mencii @{name}",
23 23
   "account.moved_to": "{name} moviĝis al:",
@@ -92,9 +92,9 @@
92 92
   "confirmations.mute.confirm": "Silentigi",
93 93
   "confirmations.mute.message": "Ĉu vi certas, ke vi volas silentigi {name}?",
94 94
   "confirmations.redraft.confirm": "Forigi kaj reskribi",
95
-  "confirmations.redraft.message": "Ĉu vi certas ke vi volas forigi tiun mesaĝon kaj reskribi ĝin? Ĉiuj diskonigoj kaj stelumoj estos perditaj, kaj respondoj al la originala mesaĝo estos orfigitaj.",
95
+  "confirmations.redraft.message": "Ĉu vi certas ke vi volas forigi tiun mesaĝon kaj reskribi ĝin? Ĉiuj diskonigoj kaj stelumoj estos perditaj, kaj respondoj al la originala mesaĝo estos senparentaj.",
96 96
   "confirmations.reply.confirm": "Respondi",
97
-  "confirmations.reply.message": "Respondi nun anstataŭigos la mesaĝon ke vi aktuale skribas. Ĉu vi certas ke vi volas daŭrigi?",
97
+  "confirmations.reply.message": "Respondi nun anstataŭigos la mesaĝon, kiun vi nun skribas. Ĉu vi certas, ke vi volas daŭrigi?",
98 98
   "confirmations.unfollow.confirm": "Ne plu sekvi",
99 99
   "confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?",
100 100
   "embed.instructions": "Enkorpigu ĉi tiun mesaĝon en vian retejon per kopio de la suba kodo.",
@@ -114,18 +114,18 @@
114 114
   "emoji_button.symbols": "Simboloj",
115 115
   "emoji_button.travel": "Vojaĝoj kaj lokoj",
116 116
   "empty_column.account_timeline": "Neniu mesaĝo ĉi tie!",
117
-  "empty_column.blocks": "Vi ne ankoraŭ blokis iun uzanton.",
117
+  "empty_column.blocks": "Vi ankoraŭ ne blokis uzanton.",
118 118
   "empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
119 119
   "empty_column.direct": "Vi ankoraŭ ne havas rektan mesaĝon. Kiam vi sendos aŭ ricevos iun, ĝi aperos ĉi tie.",
120
-  "empty_column.domain_blocks": "Ankoraŭ estas neniu domajno blokita.",
121
-  "empty_column.favourited_statuses": "Vi ne ankoraŭ havas iun stelumitan mesaĝon. Kiam vi stelumos iun, tiu aperos ĉi tie.",
122
-  "empty_column.favourites": "Neniu ankoraŭ stelumis ĉi tiun mesaĝon. Kiam iu faros ĝin, tiu aperos ĉi tie.",
120
+  "empty_column.domain_blocks": "Ankoraŭ neniu domajno estas blokita.",
121
+  "empty_column.favourited_statuses": "Vi ankoraŭ ne stelumis mesaĝon. Kiam vi stelumos iun, tiu aperos ĉi tie.",
122
+  "empty_column.favourites": "Ankoraŭ neniu stelumis tiun mesaĝon. Kiam iu faros tion, tiu aperos ĉi tie.",
123 123
   "empty_column.follow_requests": "Vi ne ankoraŭ havas iun peton de sekvado. Kiam vi ricevos unu, ĝi aperos ĉi tie.",
124 124
   "empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
125 125
   "empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
126 126
   "empty_column.home.public_timeline": "la publikan tempolinion",
127 127
   "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.",
128
-  "empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
128
+  "empty_column.lists": "Vi ankoraŭ ne havas liston. Kiam vi kreos iun, ĝi aperos ĉi tie.",
129 129
   "empty_column.mutes": "Vi ne ankoraŭ silentigis iun uzanton.",
130 130
   "empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
131 131
   "empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj nodoj por plenigi la publikan tempolinion",
@@ -139,9 +139,9 @@
139 139
   "getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
140 140
   "getting_started.security": "Sekureco",
141 141
   "getting_started.terms": "Uzkondiĉoj",
142
-  "hashtag.column_header.tag_mode.all": "and {additional}",
143
-  "hashtag.column_header.tag_mode.any": "or {additional}",
144
-  "hashtag.column_header.tag_mode.none": "without {additional}",
142
+  "hashtag.column_header.tag_mode.all": "kaj {additional}",
143
+  "hashtag.column_header.tag_mode.any": " {additional}",
144
+  "hashtag.column_header.tag_mode.none": "sen {additional}",
145 145
   "hashtag.column_settings.tag_mode.all": "Ĉiuj",
146 146
   "hashtag.column_settings.tag_mode.any": "Iu ajn",
147 147
   "hashtag.column_settings.tag_mode.none": "Neniu",
@@ -149,7 +149,7 @@
149 149
   "home.column_settings.basic": "Bazaj agordoj",
150 150
   "home.column_settings.show_reblogs": "Montri diskonigojn",
151 151
   "home.column_settings.show_replies": "Montri respondojn",
152
-  "introduction.federation.action": "Next",
152
+  "introduction.federation.action": "Sekva",
153 153
   "introduction.federation.federated.headline": "Federated",
154 154
   "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
155 155
   "introduction.federation.home.headline": "Home",
@@ -216,7 +216,7 @@
216 216
   "navigation_bar.apps": "Telefonaj aplikaĵoj",
217 217
   "navigation_bar.blocks": "Blokitaj uzantoj",
218 218
   "navigation_bar.community_timeline": "Loka tempolinio",
219
-  "navigation_bar.compose": "Redakti novan mesaĝon",
219
+  "navigation_bar.compose": "Skribi novan mesaĝon",
220 220
   "navigation_bar.direct": "Rektaj mesaĝoj",
221 221
   "navigation_bar.discover": "Esplori",
222 222
   "navigation_bar.domain_blocks": "Kaŝitaj domajnoj",
@@ -314,7 +314,7 @@
314 314
   "status.reblog": "Diskonigi",
315 315
   "status.reblog_private": "Diskonigi al la originala atentaro",
316 316
   "status.reblogged_by": "{name} diskonigis",
317
-  "status.reblogs.empty": "Neniu ankoraŭ diskonigis ĉi tiun mesaĝon. Kiam iu faris ĝin, tiu aperos ĉi tie.",
317
+  "status.reblogs.empty": "Ankoraŭ neniu diskonigis tiun mesaĝon. Kiam iu faros tion, tiu aperos ĉi tie.",
318 318
   "status.redraft": "Forigi kaj reskribi",
319 319
   "status.reply": "Respondi",
320 320
   "status.replyAll": "Respondi al la fadeno",
@@ -326,8 +326,8 @@
326 326
   "status.show_less_all": "Malgrandigi ĉiujn",
327 327
   "status.show_more": "Grandigi",
328 328
   "status.show_more_all": "Grandigi ĉiujn",
329
-  "status.show_thread": "Montri fadenon",
330
-  "status.unmute_conversation": "Malsilentigi konversacion",
329
+  "status.show_thread": "Montri la fadenon",
330
+  "status.unmute_conversation": "Malsilentigi la konversacion",
331 331
   "status.unpin": "Depingli de profilo",
332 332
   "suggestions.dismiss": "Forigi la proponon",
333 333
   "suggestions.header": "Vi povus interesiĝi pri…",

+ 22
- 22
app/javascript/mastodon/locales/fr.json View File

@@ -149,23 +149,23 @@
149 149
   "home.column_settings.basic": "Basique",
150 150
   "home.column_settings.show_reblogs": "Afficher les partages",
151 151
   "home.column_settings.show_replies": "Afficher les réponses",
152
-  "introduction.federation.action": "Next",
152
+  "introduction.federation.action": "Suivant",
153 153
   "introduction.federation.federated.headline": "Federated",
154
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
154
+  "introduction.federation.federated.text": "Les messages publics provenant d'autres serveurs du fediverse apparaîtront dans le fil public global.",
155 155
   "introduction.federation.home.headline": "Home",
156
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
156
+  "introduction.federation.home.text": "Les messages des personnes que vous suivez apparaîtront dans votre fil d'accueil. Vous pouvez suivre n'importe qui sur n'importe quel serveur !",
157 157
   "introduction.federation.local.headline": "Local",
158
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
159
-  "introduction.interactions.action": "Finish tutorial!",
160
-  "introduction.interactions.favourite.headline": "Favourite",
161
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
162
-  "introduction.interactions.reblog.headline": "Boost",
163
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
164
-  "introduction.interactions.reply.headline": "Reply",
165
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
166
-  "introduction.welcome.action": "Let's go!",
167
-  "introduction.welcome.headline": "First steps",
168
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
158
+  "introduction.federation.local.text": "Les messages publics de personnes se trouvant sur le même serveur que vous apparaîtront sur le fil public local.",
159
+  "introduction.interactions.action": "Finir le tutoriel !",
160
+  "introduction.interactions.favourite.headline": "Favoris",
161
+  "introduction.interactions.favourite.text": "Vous pouvez garder un pouet pour plus tard, et faire savoir à l'auteur que vous l'avez aimé, en le favorisant.",
162
+  "introduction.interactions.reblog.headline": "Repartager",
163
+  "introduction.interactions.reblog.text": "Vous pouvez partager les pouets d'autres personnes avec vos suiveurs en les repartageant.",
164
+  "introduction.interactions.reply.headline": "Répondre",
165
+  "introduction.interactions.reply.text": "Vous pouvez répondre aux pouets d'autres personnes et à vos propres pouets, ce qui les enchaînera dans une conversation.",
166
+  "introduction.welcome.action": "Allons-y !",
167
+  "introduction.welcome.headline": "Premiers pas",
168
+  "introduction.welcome.text": "Bienvenue dans le fediverse ! Dans quelques instants, vous pourrez diffuser des messages et parler à vos amis sur une grande variété de serveurs. Mais ce serveur, {domain}, est spécial - il héberge votre profil, alors souvenez-vous de son nom.",
169 169
   "keyboard_shortcuts.back": "revenir en arrière",
170 170
   "keyboard_shortcuts.blocked": "pour ouvrir une liste d’utilisateurs bloqués",
171 171
   "keyboard_shortcuts.boost": "partager",
@@ -242,19 +242,19 @@
242 242
   "notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?",
243 243
   "notifications.column_settings.alert": "Notifications locales",
244 244
   "notifications.column_settings.favourite": "Favoris :",
245
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
246
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
247
-  "notifications.column_settings.filter_bar.show": "Show",
245
+  "notifications.column_settings.filter_bar.advanced": "Afficher toutes les catégories",
246
+  "notifications.column_settings.filter_bar.category": "Barre de recherche rapide",
247
+  "notifications.column_settings.filter_bar.show": "Afficher",
248 248
   "notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e·s :",
249 249
   "notifications.column_settings.mention": "Mentions :",
250 250
   "notifications.column_settings.push": "Notifications",
251 251
   "notifications.column_settings.reblog": "Partages :",
252 252
   "notifications.column_settings.show": "Afficher dans la colonne",
253 253
   "notifications.column_settings.sound": "Émettre un son",
254
-  "notifications.filter.all": "All",
255
-  "notifications.filter.boosts": "Boosts",
256
-  "notifications.filter.favourites": "Favourites",
257
-  "notifications.filter.follows": "Follows",
254
+  "notifications.filter.all": "Tout",
255
+  "notifications.filter.boosts": "Repartages",
256
+  "notifications.filter.favourites": "Favoris",
257
+  "notifications.filter.follows": "Suiveurs",
258 258
   "notifications.filter.mentions": "Mentions",
259 259
   "notifications.group": "{count} notifications",
260 260
   "privacy.change": "Ajuster la confidentialité du message",
@@ -341,7 +341,7 @@
341 341
   "upload_area.title": "Glissez et déposez pour envoyer",
342 342
   "upload_button.label": "Joindre un média (JPEG, PNG, GIF, WebM, MP4, MOV)",
343 343
   "upload_form.description": "Décrire pour les malvoyant·e·s",
344
-  "upload_form.focus": "Recadrer",
344
+  "upload_form.focus": "Modifier l’aperçu",
345 345
   "upload_form.undo": "Supprimer",
346 346
   "upload_progress.label": "Envoi en cours…",
347 347
   "video.close": "Fermer la vidéo",

+ 22
- 22
app/javascript/mastodon/locales/gl.json View File

@@ -149,23 +149,23 @@
149 149
   "home.column_settings.basic": "Básico",
150 150
   "home.column_settings.show_reblogs": "Mostrar repeticións",
151 151
   "home.column_settings.show_replies": "Mostrar respostas",
152
-  "introduction.federation.action": "Next",
152
+  "introduction.federation.action": "Seguinte",
153 153
   "introduction.federation.federated.headline": "Federated",
154
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
154
+  "introduction.federation.federated.text": "Publicacións públicas desde outros servidores do fediverso aparecerán na liña temporal federada.",
155 155
   "introduction.federation.home.headline": "Home",
156
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
156
+  "introduction.federation.home.text": "Publicacións de xente que vostede segue aparecerán no TL de Inicio. Pode seguir a calquera en calquer servidor!",
157 157
   "introduction.federation.local.headline": "Local",
158
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
159
-  "introduction.interactions.action": "Finish tutorial!",
160
-  "introduction.interactions.favourite.headline": "Favourite",
161
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
162
-  "introduction.interactions.reblog.headline": "Boost",
163
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
164
-  "introduction.interactions.reply.headline": "Reply",
165
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
166
-  "introduction.welcome.action": "Let's go!",
167
-  "introduction.welcome.headline": "First steps",
168
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
158
+  "introduction.federation.local.text": "Publicacións públicas de xente no seu mesmo servidor aparecerán na liña temporal local.",
159
+  "introduction.interactions.action": "Rematar titorial!",
160
+  "introduction.interactions.favourite.headline": "Favorito",
161
+  "introduction.interactions.favourite.text": "Pode gardar un toot para máis tarde, e facerlle saber a autora que lle gustou, dándolle a Favorito.",
162
+  "introduction.interactions.reblog.headline": "Promocionar",
163
+  "introduction.interactions.reblog.text": "Pode compartir os toots de outra xente coas súas seguirodas promocionándoos.",
164
+  "introduction.interactions.reply.headline": "Respostar",
165
+  "introduction.interactions.reply.text": "Pode respostar aos toots de outras persoas e aos seus propios, así quedarán encadeados nunha conversa.",
166
+  "introduction.welcome.action": "Imos!",
167
+  "introduction.welcome.headline": "Primeiros pasos",
168
+  "introduction.welcome.text": "Benvida ao fediverso! Nun intre poderá difundir mensaxes e falar cos seus amigos nun gran número de servidores. Pero este servidor (dominio) é especial—hospeda o seu perfil, así que lémbreo.",
169 169
   "keyboard_shortcuts.back": "voltar atrás",
170 170
   "keyboard_shortcuts.blocked": "abrir lista de usuarias bloqueadas",
171 171
   "keyboard_shortcuts.boost": "promover",
@@ -242,20 +242,20 @@
242 242
   "notifications.clear_confirmation": "Estás seguro de que queres limpar permanentemente todas as túas notificacións?",
243 243
   "notifications.column_settings.alert": "Notificacións de escritorio",
244 244
   "notifications.column_settings.favourite": "Favoritas:",
245
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
246
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
247
-  "notifications.column_settings.filter_bar.show": "Show",
245
+  "notifications.column_settings.filter_bar.advanced": "Mostrar todas as categorías",
246
+  "notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
247
+  "notifications.column_settings.filter_bar.show": "Mostrar",
248 248
   "notifications.column_settings.follow": "Novos seguidores:",
249 249
   "notifications.column_settings.mention": "Mencións:",
250 250
   "notifications.column_settings.push": "Enviar notificacións",
251 251
   "notifications.column_settings.reblog": "Promocións:",
252 252
   "notifications.column_settings.show": "Mostrar en columna",
253 253
   "notifications.column_settings.sound": "Reproducir son",
254
-  "notifications.filter.all": "All",
255
-  "notifications.filter.boosts": "Boosts",
256
-  "notifications.filter.favourites": "Favourites",
257
-  "notifications.filter.follows": "Follows",
258
-  "notifications.filter.mentions": "Mentions",
254
+  "notifications.filter.all": "Todo",
255
+  "notifications.filter.boosts": "Promocións",
256
+  "notifications.filter.favourites": "Favoritos",
257
+  "notifications.filter.follows": "Seguimentos",
258
+  "notifications.filter.mentions": "Mencións",
259 259
   "notifications.group": "{count} notificacións",
260 260
   "privacy.change": "Axustar a intimidade do estado",
261 261
   "privacy.direct.long": "Enviar exclusivamente as usuarias mencionadas",

+ 22
- 22
app/javascript/mastodon/locales/ko.json View File

@@ -149,23 +149,23 @@
149 149
   "home.column_settings.basic": "기본 설정",
150 150
   "home.column_settings.show_reblogs": "부스트 표시",
151 151
   "home.column_settings.show_replies": "답글 표시",
152
-  "introduction.federation.action": "Next",
152
+  "introduction.federation.action": "다음",
153 153
   "introduction.federation.federated.headline": "Federated",
154
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
154
+  "introduction.federation.federated.text": "페디버스의 다른 서버의 공개 게시물이 연합 타임라인에 나타납니다.",
155 155
   "introduction.federation.home.headline": "Home",
156
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
156
+  "introduction.federation.home.text": "당신이 팔로우 하고 있는 사람의 게시물이 홈 타임라인에 나타납니다. 어느 서버에 있는 사람이라도 팔로우가 가능합니다!",
157 157
   "introduction.federation.local.headline": "Local",
158
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
159
-  "introduction.interactions.action": "Finish tutorial!",
160
-  "introduction.interactions.favourite.headline": "Favourite",
161
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
162
-  "introduction.interactions.reblog.headline": "Boost",
163
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
164
-  "introduction.interactions.reply.headline": "Reply",
165
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
166
-  "introduction.welcome.action": "Let's go!",
167
-  "introduction.welcome.headline": "First steps",
168
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
158
+  "introduction.federation.local.text": "같은 서버에 있는 공개 게시물은 로컬 타임라인에 나타납니다.",
159
+  "introduction.interactions.action": "튜토리얼 마치기!",
160
+  "introduction.interactions.favourite.headline": "즐겨찾기",
161
+  "introduction.interactions.favourite.text": "나중을 위해 툿을 저장할 수 있습니다, 그리고 작성자에게 당신이 이 글을 마음에 들어한다는 걸 알립니다.",
162
+  "introduction.interactions.reblog.headline": "부스트",
163
+  "introduction.interactions.reblog.text": "부스트를 통해 다른 사람의 툿을 당신의 팔로워들에게 공유할 수 있습니다.",
164
+  "introduction.interactions.reply.headline": "답글",
165
+  "introduction.interactions.reply.text": "다른 사람이나 나의 툿에 답글을 달 수 있습니다, 이 답글은 하나의 타래글로 이어집니다.",
166
+  "introduction.welcome.action": "출발!",
167
+  "introduction.welcome.headline": "첫걸음",
168
+  "introduction.welcome.text": "페디버스에 오신 것을 환영합니다! 잠시 후, 당신은 수 많은 다양한 서버들에 존재하는 친구들에게 메시지를 보내고 대화 할 수 있게 됩니다. 하지만 이 서버, {domain}은 특별합니다. 이 서버는 당신의 프로필을 제공하니 이름을 기억하세요.",
169 169
   "keyboard_shortcuts.back": "뒤로가기",
170 170
   "keyboard_shortcuts.blocked": "차단한 유저 리스트 열기",
171 171
   "keyboard_shortcuts.boost": "부스트",
@@ -242,20 +242,20 @@
242 242
   "notifications.clear_confirmation": "정말로 알림을 삭제하시겠습니까?",
243 243
   "notifications.column_settings.alert": "데스크탑 알림",
244 244
   "notifications.column_settings.favourite": "즐겨찾기:",
245
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
246
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
247
-  "notifications.column_settings.filter_bar.show": "Show",
245
+  "notifications.column_settings.filter_bar.advanced": "카테고리의 모든 종류를 표시",
246
+  "notifications.column_settings.filter_bar.category": "퀵 필터 바",
247
+  "notifications.column_settings.filter_bar.show": "표시",
248 248
   "notifications.column_settings.follow": "새 팔로워:",
249 249
   "notifications.column_settings.mention": "답글:",
250 250
   "notifications.column_settings.push": "푸시 알림",
251 251
   "notifications.column_settings.reblog": "부스트:",
252 252
   "notifications.column_settings.show": "컬럼에 표시",
253 253
   "notifications.column_settings.sound": "효과음 재생",
254
-  "notifications.filter.all": "All",
255
-  "notifications.filter.boosts": "Boosts",
256
-  "notifications.filter.favourites": "Favourites",
257
-  "notifications.filter.follows": "Follows",
258
-  "notifications.filter.mentions": "Mentions",
254
+  "notifications.filter.all": "모두",
255
+  "notifications.filter.boosts": "부스트",
256
+  "notifications.filter.favourites": "즐겨찾기",
257
+  "notifications.filter.follows": "팔로우",
258
+  "notifications.filter.mentions": "멘션",
259 259
   "notifications.group": "{count} 개의 알림",
260 260
   "privacy.change": "포스트의 프라이버시 설정을 변경",
261 261
   "privacy.direct.long": "멘션한 사용자에게만 공개",

+ 22
- 22
app/javascript/mastodon/locales/nl.json View File

@@ -145,27 +145,27 @@
145 145
   "hashtag.column_settings.tag_mode.all": "Allemaal",
146 146
   "hashtag.column_settings.tag_mode.any": "Een van deze",
147 147
   "hashtag.column_settings.tag_mode.none": "Geen van deze",
148
-  "hashtag.column_settings.tag_toggle": "Include additional tags in this column",
148
+  "hashtag.column_settings.tag_toggle": "Additionele tags aan deze kolom toevoegen",
149 149
   "home.column_settings.basic": "Algemeen",
150 150
   "home.column_settings.show_reblogs": "Boosts tonen",
151 151
   "home.column_settings.show_replies": "Reacties tonen",
152
-  "introduction.federation.action": "Next",
153
-  "introduction.federation.federated.headline": "Federated",
154
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
152
+  "introduction.federation.action": "Volgende",
153
+  "introduction.federation.federated.headline": "Globaal",
154
+  "introduction.federation.federated.text": "Openbare toots van mensen op andere servers in de fediverse verschijnen op de globale tijdlijn.",
155 155
   "introduction.federation.home.headline": "Home",
156
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
156
+  "introduction.federation.home.text": "Toots van mensen die jij volgt verschijnen onder start. Je kunt iedereen op elke server volgen!",
157 157
   "introduction.federation.local.headline": "Local",
158
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
159
-  "introduction.interactions.action": "Finish tutorial!",
160
-  "introduction.interactions.favourite.headline": "Favourite",
161
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
158
+  "introduction.federation.local.text": "Openbare toots van mensen die ook op jouw server zitten verschijnen op de lokale tijdlijn.",
159
+  "introduction.interactions.action": "Introductie beëindigen!",
160
+  "introduction.interactions.favourite.headline": "Favorieten",
161
+  "introduction.interactions.favourite.text": "Je kunt door een toot als favoriet te markeren, deze voor later bewaren en de auteur laten weten dat je het leuk vond.",
162 162
   "introduction.interactions.reblog.headline": "Boost",
163
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
164
-  "introduction.interactions.reply.headline": "Reply",
165
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
166
-  "introduction.welcome.action": "Let's go!",
167
-  "introduction.welcome.headline": "First steps",
168
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
163
+  "introduction.interactions.reblog.text": "Je kunt toots van andere mensen met jouw volgers delen door deze te boosten.",
164
+  "introduction.interactions.reply.headline": "Reageren",
165
+  "introduction.interactions.reply.text": "Je kunt op toots van andere mensen en op die van jezelf reageren, waardoor er een gesprek ontstaat.",
166
+  "introduction.welcome.action": "Laten we beginnen!",
167
+  "introduction.welcome.headline": "Eerste stappen",
168
+  "introduction.welcome.text": "Welkom in de fediverse! Binnen enkele ogenblikken kun jij berichten (toots) versturen en met vrienden op veel verschillende servers praten. Maar deze server, {domain}, is speciaal—het herbergt jouw profiel, onthou dus de naam.",
169 169
   "keyboard_shortcuts.back": "om terug te gaan",
170 170
   "keyboard_shortcuts.blocked": "om de door jou geblokkeerde gebruikers te tonen",
171 171
   "keyboard_shortcuts.boost": "om te boosten",
@@ -242,20 +242,20 @@
242 242
   "notifications.clear_confirmation": "Weet je het zeker dat je al jouw meldingen wilt verwijderen?",
243 243
   "notifications.column_settings.alert": "Desktopmeldingen",
244 244
   "notifications.column_settings.favourite": "Favorieten:",
245
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
246
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
247
-  "notifications.column_settings.filter_bar.show": "Show",
245
+  "notifications.column_settings.filter_bar.advanced": "Alle categorieën tonen",
246
+  "notifications.column_settings.filter_bar.category": "Snelle filterbalk",
247
+  "notifications.column_settings.filter_bar.show": "Tonen",
248 248
   "notifications.column_settings.follow": "Nieuwe volgers:",
249 249
   "notifications.column_settings.mention": "Vermeldingen:",
250 250
   "notifications.column_settings.push": "Pushmeldingen",
251 251
   "notifications.column_settings.reblog": "Boosts:",
252 252
   "notifications.column_settings.show": "In kolom tonen",
253 253
   "notifications.column_settings.sound": "Geluid afspelen",
254
-  "notifications.filter.all": "All",
254
+  "notifications.filter.all": "Alles",
255 255
   "notifications.filter.boosts": "Boosts",
256
-  "notifications.filter.favourites": "Favourites",
257
-  "notifications.filter.follows": "Follows",
258
-  "notifications.filter.mentions": "Mentions",
256
+  "notifications.filter.favourites": "Favorieten",
257
+  "notifications.filter.follows": "Die jij volgt",
258
+  "notifications.filter.mentions": "Vermeldingen",
259 259
   "notifications.group": "{count} meldingen",
260 260
   "privacy.change": "Zichtbaarheid toot aanpassen",
261 261
   "privacy.direct.long": "Alleen aan vermelde gebruikers tonen",

+ 1
- 1
app/javascript/mastodon/locales/oc.json View File

@@ -341,7 +341,7 @@
341 341
   "upload_area.title": "Lisatz e depausatz per mandar",
342 342
   "upload_button.label": "Ajustar un mèdia (JPEG, PNG, GIF, WebM, MP4, MOV)",
343 343
   "upload_form.description": "Descripcion pels mal vesents",
344
-  "upload_form.focus": "Retalhar",
344
+  "upload_form.focus": "Modificar l’apercebut",
345 345
   "upload_form.undo": "Suprimir",
346 346
   "upload_progress.label": "Mandadís…",
347 347
   "video.close": "Tampar la vidèo",

+ 28
- 28
app/javascript/mastodon/locales/sk.json View File

@@ -29,9 +29,9 @@
29 29
   "account.report": "Nahlás @{name}",
30 30
   "account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti",
31 31
   "account.share": "Zdieľať @{name} profil",
32
-  "account.show_reblogs": "Ukáž povýšenia od @{name}",
33
-  "account.unblock": "Odblokovať @{name}",
34
-  "account.unblock_domain": "Prestať blokovať {domain}",
32
+  "account.show_reblogs": "Ukáž vyzdvihnutia od @{name}",
33
+  "account.unblock": "Odblokuj @{name}",
34
+  "account.unblock_domain": "Prestaň skrývať {domain}",
35 35
   "account.unendorse": "Nezobrazuj na profile",
36 36
   "account.unfollow": "Prestať nasledovať",
37 37
   "account.unmute": "Prestať ignorovať @{name}",
@@ -92,8 +92,8 @@
92 92
   "confirmations.mute.confirm": "Ignoruj",
93 93
   "confirmations.mute.message": "Naozaj chcete ignorovať {name}?",
94 94
   "confirmations.redraft.confirm": "Vyčistiť a prepísať",
95
-  "confirmations.redraft.message": "Si si istý/á, že chceš premazať a prepísať tento príspevok? Jeho nadobudnuté odpovede, povýšenia a obľúbenia, ale i odpovede na pôvodný príspevok budú odlúčené.",
96
-  "confirmations.reply.confirm": "Odpoved",
95
+  "confirmations.redraft.message": "Si si istý/á, že chceš premazať a prepísať tento príspevok? Jeho nadobudnuté vyzdvihnutia a obľúbenia, ale i odpovede na pôvodný príspevok budú odlúčené.",
96
+  "confirmations.reply.confirm": "Odpovedz",
97 97
   "confirmations.reply.message": "Odpovedaním akurát teraz prepíšeš správu, ktorú máš práve rozpísanú. Si si istý/á, že chceš pokračovať?",
98 98
   "confirmations.unfollow.confirm": "Nesledovať",
99 99
   "confirmations.unfollow.message": "Naozaj chcete prestať sledovať {name}?",
@@ -149,23 +149,23 @@
149 149
   "home.column_settings.basic": "Základné",
150 150
   "home.column_settings.show_reblogs": "Zobraziť povýšené",
151 151
   "home.column_settings.show_replies": "Ukázať odpovede",
152
-  "introduction.federation.action": "Next",
152
+  "introduction.federation.action": "Ďalej",
153 153
   "introduction.federation.federated.headline": "Federated",
154
-  "introduction.federation.federated.text": "Public posts from other servers of the fediverse will appear in the federated timeline.",
154
+  "introduction.federation.federated.text": "Verejné príspevky z ostatných serverov vo fediverse budú zobrazenie vo federovanej časovej osi.",
155 155
   "introduction.federation.home.headline": "Home",
156
-  "introduction.federation.home.text": "Posts from people you follow will appear in your home feed. You can follow anyone on any server!",
156
+  "introduction.federation.home.text": "Príspevky od ľudí ktorých následuješ sa zobrazia na tvojej domovskej nástenke. Môžeš následovať hocikoho na ktoromkoľvek serveri!",
157 157
   "introduction.federation.local.headline": "Local",
158
-  "introduction.federation.local.text": "Public posts from people on the same server as you will appear in the local timeline.",
159
-  "introduction.interactions.action": "Finish tutorial!",
160
-  "introduction.interactions.favourite.headline": "Favourite",
161
-  "introduction.interactions.favourite.text": "You can save a toot for later, and let the author know that you liked it, by favouriting it.",
162
-  "introduction.interactions.reblog.headline": "Boost",
163
-  "introduction.interactions.reblog.text": "You can share other people's toots with your followers by boosting them.",
164
-  "introduction.interactions.reply.headline": "Reply",
165
-  "introduction.interactions.reply.text": "You can reply to other people's and your own toots, which will chain them together in a conversation.",
166
-  "introduction.welcome.action": "Let's go!",
167
-  "introduction.welcome.headline": "First steps",
168
-  "introduction.welcome.text": "Welcome to the fediverse! In a few moments, you'll be able to broadcast messages and talk to your friends across a wide variety of servers. But this server, {domain}, is special—it hosts your profile, so remember its name.",
158
+  "introduction.federation.local.text": "Verejné príspevky od ľudí v rámci toho istého serveru na akom si aj ty, budú zobrazované na miestnej časovej osi.",
159
+  "introduction.interactions.action": "Ukonči návod!",
160
+  "introduction.interactions.favourite.headline": "Obľúbené",
161
+  "introduction.interactions.favourite.text": "Obľúbením si môžeš príspevok uložiť na neskôr, a zároveň dať jeho autorovi vedieť, že sa ti páčil.",
162
+  "introduction.interactions.reblog.headline": "Povýš",
163
+  "introduction.interactions.reblog.text": "Môžeš zdieľať príspevky iných ľudí s vašimi následovateľmi tým, že ich povýšiš.",
164
+  "introduction.interactions.reply.headline": "Odpovedz",
165
+  "introduction.interactions.reply.text": "Odpovedať môžeš na príspevky iných ľudí, aj na svoje vlastné, čím sa sspolu prepoja do konverzácie.",
166
+  "introduction.welcome.action": "Poďme do toho!",
167
+  "introduction.welcome.headline": "Prvé kroky",
168
+  "introduction.welcome.text": "Vitaj vo fediverse! Za malú chvíľu budeš môcť posielať správy a rozpovedať sa so svojími priateľmi cez širokú škálu rôznorodých serverov. Ale tento server, {domain}, je špeciálny v tom, že ukladá tvoj profil, takže si jeho názov zapametaj.",
169 169
   "keyboard_shortcuts.back": "dostať sa naspäť",
170 170
   "keyboard_shortcuts.blocked": "otvor zoznam blokovaných užívateľov",
171 171
   "keyboard_shortcuts.boost": "vyzdvihnúť",
@@ -242,20 +242,20 @@
242 242
   "notifications.clear_confirmation": "Naozaj chcete nenávratne prečistiť všetky vaše notifikácie?",
243 243
   "notifications.column_settings.alert": "Notifikácie na ploche",
244 244
   "notifications.column_settings.favourite": "Obľúbené:",
245
-  "notifications.column_settings.filter_bar.advanced": "Display all categories",
246
-  "notifications.column_settings.filter_bar.category": "Quick filter bar",
247
-  "notifications.column_settings.filter_bar.show": "Show",
245
+  "notifications.column_settings.filter_bar.advanced": "Zobraz všetky kategórie",
246
+  "notifications.column_settings.filter_bar.category": "Rýchle triedenie",
247
+  "notifications.column_settings.filter_bar.show": "Ukáž",
248 248
   "notifications.column_settings.follow": "Noví následujúci:",
249 249
   "notifications.column_settings.mention": "Zmienenia:",
250 250
   "notifications.column_settings.push": "Push notifikácie",
251 251
   "notifications.column_settings.reblog": "Boosty:",
252 252
   "notifications.column_settings.show": "Zobraziť v stĺpci",
253 253
   "notifications.column_settings.sound": "Prehrať zvuk",
254
-  "notifications.filter.all": "All",
255
-  "notifications.filter.boosts": "Boosts",
256
-  "notifications.filter.favourites": "Favourites",
257
-  "notifications.filter.follows": "Follows",
258
-  "notifications.filter.mentions": "Mentions",
254
+  "notifications.filter.all": "Všetky",
255
+  "notifications.filter.boosts": "Vyzdvihnutia",
256
+  "notifications.filter.favourites": "Obľúbené",
257
+  "notifications.filter.follows": "Sledovania",
258
+  "notifications.filter.mentions": "Spomenutia",
259 259
   "notifications.group": "{count} oznámenia",
260 260
   "privacy.change": "Zmeňiť viditeľnosť statusu",
261 261
   "privacy.direct.long": "Poslať priamo iba spomenutým používateľom",
@@ -319,7 +319,7 @@
319 319
   "status.reply": "Odpovedať",
320 320
   "status.replyAll": "Odpovedať na diskusiu",
321 321
   "status.report": "Nahlásiť @{name}",
322
-  "status.sensitive_toggle": "Kliknite pre zobrazenie",
322
+  "status.sensitive_toggle": "Klikni pre zobrazenie",
323 323
   "status.sensitive_warning": "Chúlostivý obsah",
324 324
   "status.share": "Zdieľať",
325 325
   "status.show_less": "Zobraz menej",

+ 1
- 1
app/javascript/mastodon/reducers/timelines.js View File

@@ -88,7 +88,7 @@ const deleteStatus = (state, id, accountId, references) => {
88 88
 };
89 89
 
90 90
 const clearTimeline = (state, timeline) => {
91
-  return state.updateIn([timeline, 'items'], list => list.clear());
91
+  return state.set(timeline, initialTimeline);
92 92
 };
93 93
 
94 94
 const filterTimelines = (state, relationship, statuses) => {

+ 1
- 1
app/javascript/mastodon/store/configureStore.js View File

@@ -11,5 +11,5 @@ export default function configureStore() {
11 11
     loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
12 12
     errorsMiddleware(),
13 13
     soundsMiddleware()
14
-  ), window.devToolsExtension ? window.devToolsExtension() : f => f));
14
+  ), window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f));
15 15
 };

+ 7
- 1
app/javascript/styles/mastodon/dashboard.scss View File

@@ -30,15 +30,21 @@
30 30
     }
31 31
   }
32 32
 
33
-  &__num {
33
+  &__num,
34
+  &__text {
34 35
     text-align: center;
35 36
     font-weight: 500;
36 37
     font-size: 24px;
38
+    line-height: 21px;
37 39
     color: $primary-text-color;
38 40
     font-family: $font-display, sans-serif;
39 41
     margin-bottom: 20px;
40 42
   }
41 43
 
44
+  &__text {
45
+    font-size: 18px;
46
+  }
47
+
42 48
   &__label {
43 49
     font-size: 14px;
44 50
     color: $darker-text-color;

+ 2
- 0
app/lib/activitypub/activity.rb View File

@@ -50,6 +50,8 @@ class ActivityPub::Activity
50 50
         ActivityPub::Activity::Add
51 51
       when 'Remove'
52 52
         ActivityPub::Activity::Remove
53
+      when 'Move'
54
+        ActivityPub::Activity::Move
53 55
       end
54 56
     end
55 57
   end

+ 3
- 2
app/lib/activitypub/activity/block.rb View File

@@ -4,9 +4,10 @@ class ActivityPub::Activity::Block < ActivityPub::Activity
4 4
   def perform
5 5
     target_account = account_from_uri(object_uri)
6 6
 
7
-    return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.blocking?(target_account)
7
+    return if target_account.nil? || !target_account.local? || @account.blocking?(target_account)
8 8
 
9 9
     UnfollowService.new.call(target_account, @account) if target_account.following?(@account)
10
-    @account.block!(target_account, uri: @json['id'])
10
+
11
+    @account.block!(target_account, uri: @json['id']) unless delete_arrived_first?(@json['id'])
11 12
   end
12 13
 end

+ 1
- 1
app/lib/activitypub/activity/create.rb View File

@@ -210,7 +210,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
210 210
   end
211 211
 
212 212
   def resolve_thread(status)
213
-    return unless status.reply? && status.thread.nil?
213
+    return unless status.reply? && status.thread.nil? && Request.valid_url?(in_reply_to_uri)
214 214
     ThreadResolveWorker.perform_async(status.id, in_reply_to_uri)
215 215
   end
216 216
 

+ 0
- 2
app/lib/activitypub/activity/flag.rb View File

@@ -8,8 +8,6 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
8 8
     target_statuses_by_account = object_uris.map { |uri| status_from_uri(uri) }.compact.select(&:local?).group_by(&:account_id)
9 9
 
10 10
     target_accounts.each do |target_account|
11
-      next if Report.where(account: @account, target_account: target_account).exists?
12
-
13 11
       target_statuses = target_statuses_by_account[target_account.id]
14 12
 
15 13
       ReportService.new.call(

+ 2
- 2
app/lib/activitypub/activity/follow.rb View File

@@ -6,7 +6,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
6 6
 
7 7
     return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account)
8 8
 
9
-    if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain)
9
+    if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved?
10 10
       reject_follow_request!(target_account)
11 11
       return
12 12
     end
@@ -28,7 +28,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
28 28
   end
29 29
 
30 30
   def reject_follow_request!(target_account)
31
-    json = Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(FollowRequest.new(account: @account, target_account: target_account, uri: @json['id']), serializer: ActivityPub::RejectFollowSerializer, adapter: ActivityPub::Adapter).as_json).sign!(target_account))
31
+    json = ActiveModelSerializers::SerializableResource.new(FollowRequest.new(account: @account, target_account: target_account, uri: @json['id']), serializer: ActivityPub::RejectFollowSerializer, adapter: ActivityPub::Adapter).to_json
32 32
     ActivityPub::DeliveryWorker.perform_async(json, target_account.id, @account.inbox_url)
33 33
   end
34 34
 end

+ 43
- 0
app/lib/activitypub/activity/move.rb View File

@@ -0,0 +1,43 @@
1
+# frozen_string_literal: true
2
+
3
+class ActivityPub::Activity::Move < ActivityPub::Activity
4
+  PROCESSING_COOLDOWN = 7.days.seconds
5
+
6
+  def perform
7
+    return if origin_account.uri != object_uri || processed?
8
+
9
+    mark_as_processing!
10
+
11
+    target_account = ActivityPub::FetchRemoteAccountService.new.call(target_uri)
12
+
13
+    return if target_account.nil? || !target_account.also_known_as.include?(origin_account.uri)
14
+
15
+    # In case for some reason we didn't have a redirect for the profile already, set it
16
+    origin_account.update(moved_to_account: target_account) if origin_account.moved_to_account_id.nil?
17
+
18
+    # Initiate a re-follow for each follower
19
+    origin_account.followers.local.select(:id).find_in_batches do |follower_accounts|
20
+      UnfollowFollowWorker.push_bulk(follower_accounts.map(&:id)) do |follower_account_id|
21
+        [follower_account_id, origin_account.id, target_account.id]
22
+      end
23
+    end
24
+  end
25
+
26
+  private
27
+
28
+  def origin_account
29
+    @account
30
+  end
31
+
32
+  def target_uri
33
+    value_or_id(@json['target'])
34
+  end
35
+
36
+  def processed?
37
+    redis.exists("move_in_progress:#{@account.id}")
38
+  end
39
+
40
+  def mark_as_processing!
41
+    redis.setex("move_in_progress:#{@account.id}", PROCESSING_COOLDOWN, true)
42
+  end
43
+end

+ 1
- 0
app/lib/activitypub/adapter.rb View File

@@ -10,6 +10,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
10 10
         'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
11 11
         'sensitive'                 => 'as:sensitive',
12 12
         'movedTo'                   => { '@id' => 'as:movedTo', '@type' => '@id' },
13
+        'alsoKnownAs'               => { '@id' => 'as:alsoKnownAs', '@type' => '@id' },
13 14
         'Hashtag'                   => 'as:Hashtag',
14 15
         'ostatus'                   => 'http://ostatus.org#',
15 16
         'atomUri'                   => 'ostatus:atomUri',

+ 1
- 1
app/lib/ostatus/activity/creation.rb View File

@@ -57,7 +57,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
57 57
       save_emojis(status)
58 58
     end
59 59
 
60
-    if thread? && status.thread.nil?
60
+    if thread? && status.thread.nil? && Request.valid_url?(thread.second)
61 61
       Rails.logger.debug "Trying to attach #{status.id} (#{id}) to #{thread.first}"
62 62
       ThreadResolveWorker.perform_async(status.id, thread.second)
63 63
     end

+ 12
- 0
app/lib/request.rb View File

@@ -66,6 +66,18 @@ class Request
66 66
     (@account ? @headers.merge('Signature' => signature) : @headers).without(REQUEST_TARGET)
67 67
   end
68 68
 
69
+  class << self
70
+    def valid_url?(url)
71
+      begin
72
+        parsed_url = Addressable::URI.parse(url)
73
+      rescue Addressable::URI::InvalidURIError
74
+        return false
75
+      end
76
+
77
+      %w(http https).include?(parsed_url.scheme) && parsed_url.host.present?
78
+    end
79
+  end
80
+
69 81
   private
70 82
 
71 83
   def set_common_headers!

+ 10
- 6
app/mailers/notification_mailer.rb View File

@@ -66,16 +66,20 @@ class NotificationMailer < ApplicationMailer
66 66
   end
67 67
 
68 68
   def digest(recipient, **opts)
69
-    @me            = recipient
70
-    @since         = opts[:since] || @me.user.last_emailed_at || @me.user.current_sign_in_at
71
-    @notifications = Notification.where(account: @me, activity_type: 'Mention').where('created_at > ?', @since)
72
-    @follows_since = Notification.where(account: @me, activity_type: 'Follow').where('created_at > ?', @since).count
69
+    return if recipient.user.disabled?
70
+
71
+    @me                  = recipient
72
+    @since               = opts[:since] || [@me.user.last_emailed_at, (@me.user.current_sign_in_at + 1.day)].compact.max
73
+    @notifications_count = Notification.where(account: @me, activity_type: 'Mention').where('created_at > ?', @since).count
73 74
 
74
-    return if @me.user.disabled? || @notifications.empty?
75
+    return if @notifications_count.zero?
76
+
77
+    @notifications = Notification.where(account: @me, activity_type: 'Mention').where('created_at > ?', @since).limit(40)
78
+    @follows_since = Notification.where(account: @me, activity_type: 'Follow').where('created_at > ?', @since).count
75 79
 
76 80
     locale_for_account(@me) do
77 81
       mail to: @me.user.email,
78
-           subject: I18n.t(:subject, scope: [:notification_mailer, :digest], count: @notifications.size)
82
+           subject: I18n.t(:subject, scope: [:notification_mailer, :digest], count: @notifications_count)
79 83
     end
80 84
   end
81 85
 

+ 14
- 4
app/models/account.rb View File

@@ -44,6 +44,7 @@
44 44
 #  fields                  :jsonb
45 45
 #  actor_type              :string
46 46
 #  discoverable            :boolean
47
+#  also_known_as           :string           is an Array
47 48
 #
48 49
 
49 50
 class Account < ApplicationRecord
@@ -59,6 +60,7 @@ class Account < ApplicationRecord
59 60
   include Attachmentable
60 61
   include Paginable
61 62
   include AccountCounters
63
+  include DomainNormalizable
62 64
 
63 65
   MAX_DISPLAY_NAME_LENGTH = (ENV['MAX_DISPLAY_NAME_CHARS'] || 30).to_i
64 66
   MAX_NOTE_LENGTH = (ENV['MAX_BIO_CHARS'] || 500).to_i
@@ -87,6 +89,7 @@ class Account < ApplicationRecord
87 89
   scope :silenced, -> { where(silenced: true) }
88 90
   scope :suspended, -> { where(suspended: true) }
89 91
   scope :without_suspended, -> { where(suspended: false) }
92
+  scope :without_silenced, -> { where(silenced: false) }
90 93
   scope :recent, -> { reorder(id: :desc) }
91 94
   scope :bots, -> { where(actor_type: %w(Application Service)) }
92 95
   scope :alphabetic, -> { order(domain: :asc, username: :asc) }
@@ -94,8 +97,8 @@ class Account < ApplicationRecord
94 97
   scope :matches_username, ->(value) { where(arel_table[:username].matches("#{value}%")) }
95 98
   scope :matches_display_name, ->(value) { where(arel_table[:display_name].matches("#{value}%")) }
96 99
   scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
97
-  scope :searchable, -> { where(suspended: false).where(moved_to_account_id: nil) }
98
-  scope :discoverable, -> { searchable.where(silenced: false).where(discoverable: true).joins(:account_stat).where(AccountStat.arel_table[:followers_count].gteq(MIN_FOLLOWERS_DISCOVERY)).by_recent_status }
100
+  scope :searchable, -> { without_suspended.where(moved_to_account_id: nil) }
101
+  scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).joins(:account_stat).where(AccountStat.arel_table[:followers_count].gteq(MIN_FOLLOWERS_DISCOVERY)).by_recent_status }
99 102
   scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
100 103
   scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) }
101 104
   scope :popular, -> { order('account_stats.followers_count desc') }
@@ -142,6 +145,10 @@ class Account < ApplicationRecord
142 145
     "#{username}@#{Rails.configuration.x.local_domain}"
143 146
   end
144 147
 
148
+  def local_followers_count
149
+    Follow.where(target_account_id: id).count
150
+  end
151
+
145 152
   def to_webfinger_s
146 153
     "acct:#{local_username_and_domain}"
147 154
   end
@@ -226,6 +233,10 @@ class Account < ApplicationRecord
226 233
     end
227 234
   end
228 235
 
236
+  def also_known_as
237
+    self[:also_known_as] || []
238
+  end
239
+
229 240
   def fields
230 241
     (self[:fields] || []).map { |f| Field.new(self, f) }
231 242
   end
@@ -459,7 +470,6 @@ class Account < ApplicationRecord
459 470
   end
460 471
 
461 472
   before_create :generate_keys
462
-  before_validation :normalize_domain
463 473
   before_validation :prepare_contents, if: :local?
464 474
   before_destroy :clean_feed_manager
465 475
 
@@ -497,7 +507,7 @@ class Account < ApplicationRecord
497 507
   def normalize_domain
498 508
     return if local?
499 509
 
500
-    self.domain = TagManager.instance.normalize_domain(domain)
510
+    super
501 511
   end
502 512
 
503 513
   def emojifiable_text

+ 15
- 0
app/models/concerns/domain_normalizable.rb View File

@@ -0,0 +1,15 @@
1
+# frozen_string_literal: true
2
+
3
+module DomainNormalizable
4
+  extend ActiveSupport::Concern
5
+
6
+  included do
7
+    before_validation :normalize_domain
8
+  end
9
+
10
+  private
11
+
12
+  def normalize_domain
13
+    self.domain = TagManager.instance.normalize_domain(domain)
14
+  end
15
+end

+ 2
- 8
app/models/domain_block.rb View File

@@ -13,6 +13,8 @@
13 13
 #
14 14
 
15 15
 class DomainBlock < ApplicationRecord
16
+  include DomainNormalizable
17
+
16 18
   enum severity: [:silence, :suspend, :noop]
17 19
 
18 20
   attr_accessor :retroactive
@@ -25,12 +27,4 @@ class DomainBlock < ApplicationRecord
25 27
   def self.blocked?(domain)
26 28
     where(domain: domain, severity: :suspend).exists?
27 29
   end
28
-
29
-  before_validation :normalize_domain
30
-
31
-  private
32
-
33
-  def normalize_domain
34
-    self.domain = TagManager.instance.normalize_domain(domain)
35
-  end
36 30
 end

+ 1
- 7
app/models/email_domain_block.rb View File

@@ -10,7 +10,7 @@
10 10
 #
11 11
 
12 12
 class EmailDomainBlock < ApplicationRecord
13
-  before_validation :normalize_domain
13
+  include DomainNormalizable
14 14
 
15 15
   validates :domain, presence: true, uniqueness: true
16 16
 
@@ -27,10 +27,4 @@ class EmailDomainBlock < ApplicationRecord
27 27
 
28 28
     where(domain: domain).exists?
29 29
   end
30
-
31
-  private
32
-
33
-  def normalize_domain
34
-    self.domain = TagManager.instance.normalize_domain(domain)
35
-  end
36 30
 end

+ 34
- 4
app/models/export.rb View File

@@ -9,15 +9,33 @@ class Export
9 9
   end
10 10
 
11 11
   def to_blocked_accounts_csv
12
-    to_csv account.blocking
12
+    to_csv account.blocking.select(:username, :domain)
13 13
   end
14 14
 
15 15
   def to_muted_accounts_csv
16
-    to_csv account.muting
16
+    to_csv account.muting.select(:username, :domain)
17 17
   end
18 18
 
19 19
   def to_following_accounts_csv
20
-    to_csv account.following
20
+    to_csv account.following.select(:username, :domain)
21
+  end
22
+
23
+  def to_lists_csv
24
+    CSV.generate do |csv|
25
+      account.owned_lists.select(:title).each do |list|
26
+        list.accounts.select(:username, :domain).each do |account|
27
+          csv << [list.title, acct(account)]
28
+        end
29
+      end
30
+    end
31
+  end
32
+
33
+  def to_blocked_domains_csv
34
+    CSV.generate do |csv|
35
+      account.domain_blocks.pluck(:domain).each do |domain|
36
+        csv << [domain]
37
+      end
38
+    end
21 39
   end
22 40
 
23 41
   def total_storage
@@ -32,6 +50,10 @@ class Export
32 50
     account.following_count
33 51
   end
34 52
 
53
+  def total_lists
54
+    account.owned_lists.count
55
+  end
56
+
35 57
   def total_followers
36 58
     account.followers_count
37 59
   end
@@ -44,13 +66,21 @@ class Export
44 66
     account.muting.count
45 67
   end
46 68
 
69
+  def total_domain_blocks
70
+    account.domain_blocks.count
71
+  end
72
+
47 73
   private
48 74
 
49 75
   def to_csv(accounts)
50 76
     CSV.generate do |csv|
51 77
       accounts.each do |account|
52
-        csv << [(account.local? ? account.local_username_and_domain : account.acct)]
78
+        csv << [acct(account)]
53 79
       end
54 80
     end
55 81
   end
82
+
83
+  def acct(account)
84
+    account.local? ? account.local_username_and_domain : account.acct
85
+  end
56 86
 end

+ 0
- 2
app/models/form/status_batch.rb View File

@@ -6,8 +6,6 @@ class Form::StatusBatch
6 6
 
7 7
   attr_accessor :status_ids, :action, :current_account
8 8
 
9
-  ACTION_TYPE = %w(nsfw_on nsfw_off delete).freeze
10
-
11 9
   def save
12 10
     case action
13 11
     when 'nsfw_on', 'nsfw_off'

+ 1
- 1
app/models/report_note.rb View File

@@ -15,7 +15,7 @@ class ReportNote < ApplicationRecord
15 15
   belongs_to :account
16 16
   belongs_to :report, inverse_of: :notes, touch: true
17 17
 
18
-  scope :latest, -> { reorder('created_at ASC') }
18
+  scope :latest, -> { reorder(created_at: :desc) }
19 19
 
20 20
   validates :content, presence: true, length: { maximum: 500 }
21 21
 end

+ 12
- 3
app/models/user.rb View File

@@ -36,6 +36,7 @@
36 36
 #  invite_id                 :bigint(8)
37 37
 #  remember_token            :string
38 38
 #  chosen_languages          :string           is an Array
39
+#  created_by_application_id :bigint(8)
39 40
 #
40 41
 
41 42
 class User < ApplicationRecord
@@ -49,7 +50,7 @@ class User < ApplicationRecord
49 50
   # every day. Raising the duration reduces the amount of expensive
50 51
   # RegenerationWorker jobs that need to be run when those people come
51 52
   # to check their feed
52
-  ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days
53
+  ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days.freeze
53 54
 
54 55
   devise :two_factor_authenticatable,
55 56
          otp_secret_encryption_key: Rails.configuration.x.otp_secret
@@ -66,6 +67,7 @@ class User < ApplicationRecord
66 67
 
67 68
   belongs_to :account, inverse_of: :user
68 69
   belongs_to :invite, counter_cache: :uses, optional: true
70
+  belongs_to :created_by_application, class_name: 'Doorkeeper::Application', optional: true
69 71
   accepts_nested_attributes_for :account
70 72
 
71 73
   has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
@@ -74,15 +76,18 @@ class User < ApplicationRecord
74 76
   validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
75 77
   validates_with BlacklistedEmailValidator, if: :email_changed?
76 78
   validates_with EmailMxValidator, if: :validate_email_dns?
79
+  validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
77 80
 
78 81
   scope :recent, -> { order(id: :desc) }
79 82
   scope :admins, -> { where(admin: true) }
80 83
   scope :moderators, -> { where(moderator: true) }
81 84
   scope :staff, -> { admins.or(moderators) }
82 85
   scope :confirmed, -> { where.not(confirmed_at: nil) }
86
+  scope :enabled, -> { where(disabled: false) }
83 87
   scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
84 88
   scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended: false }) }
85 89
   scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
90
+  scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) }
86 91
 
87 92
   before_validation :sanitize_languages
88 93
 
@@ -136,6 +141,10 @@ class User < ApplicationRecord
136 141
     confirmed_at.present?
137 142
   end
138 143
 
144
+  def invited?
145
+    invite_id.present?
146
+  end
147
+
139 148
   def staff?
140