about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorJenkins <jenkins@jenkins.ninjawedding.org>2018-06-12 13:17:21 +0000
committerJenkins <jenkins@jenkins.ninjawedding.org>2018-06-12 13:17:21 +0000
commit4d8d1fa129b5f8692311de211203bc18c614951f (patch)
treee9a1130c9a9e5a5b8babd2ba3153e4ed03a4581e /app
parent34f1fd2a621ca869c17009487e2f10063812fbd0 (diff)
parent6151308c47efb0e05bcb2c54aa1693f5ff04da5c (diff)
Merge remote-tracking branch 'tootsuite/master' into glitchsoc/master
Diffstat (limited to 'app')
-rw-r--r--app/controllers/api/v1/domain_blocks_controller.rb3
-rw-r--r--app/controllers/intents_controller.rb19
-rw-r--r--app/controllers/settings/follower_domains_controller.rb2
-rw-r--r--app/javascript/mastodon/features/account_timeline/containers/header_container.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js14
-rw-r--r--app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js2
-rw-r--r--app/javascript/mastodon/features/emoji/__tests__/emoji-test.js2
-rw-r--r--app/javascript/mastodon/features/emoji/__tests__/emoji_index-test.js62
-rw-r--r--app/javascript/mastodon/features/emoji/emoji_compressed.js10
-rw-r--r--app/javascript/mastodon/features/emoji/emoji_mart_search_light.js26
-rw-r--r--app/javascript/mastodon/features/emoji/emoji_picker.js4
-rw-r--r--app/javascript/mastodon/locales/ca.json18
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json2
-rw-r--r--app/javascript/mastodon/locales/en.json2
-rw-r--r--app/javascript/mastodon/locales/eu.json22
-rw-r--r--app/javascript/mastodon/locales/fa.json6
-rw-r--r--app/javascript/mastodon/locales/fr.json6
-rw-r--r--app/javascript/mastodon/locales/ja.json6
-rw-r--r--app/javascript/mastodon/locales/nl.json6
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json12
-rw-r--r--app/javascript/mastodon/locales/sk.json34
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json18
-rw-r--r--app/javascript/styles/mastodon-light/diff.scss24
-rw-r--r--app/javascript/styles/mastodon/components.scss2
-rw-r--r--app/javascript/styles/mastodon/emoji_picker.scss6
-rw-r--r--app/javascript/styles/mastodon/footer.scss1
-rw-r--r--app/lib/activitypub/activity/follow.rb10
-rw-r--r--app/models/web/push_subscription.rb2
-rw-r--r--app/services/after_block_domain_from_account_service.rb42
-rw-r--r--app/services/block_domain_from_account_service.rb8
-rw-r--r--app/views/accounts/_follow_button.html.haml5
-rw-r--r--app/workers/after_account_domain_block_worker.rb11
-rw-r--r--app/workers/soft_block_domain_followers_worker.rb14
-rw-r--r--app/workers/soft_block_worker.rb17
34 files changed, 287 insertions, 133 deletions
diff --git a/app/controllers/api/v1/domain_blocks_controller.rb b/app/controllers/api/v1/domain_blocks_controller.rb
index ae6ad7936..e55d622c3 100644
--- a/app/controllers/api/v1/domain_blocks_controller.rb
+++ b/app/controllers/api/v1/domain_blocks_controller.rb
@@ -15,7 +15,8 @@ class Api::V1::DomainBlocksController < Api::BaseController
   end
 
   def create
-    BlockDomainFromAccountService.new.call(current_account, domain_block_params[:domain])
+    current_account.block_domain!(domain_block_params[:domain])
+    AfterAccountDomainBlockWorker.perform_async(current_account.id, domain_block_params[:domain])
     render_empty
   end
 
diff --git a/app/controllers/intents_controller.rb b/app/controllers/intents_controller.rb
index 504befd1f..56129d69a 100644
--- a/app/controllers/intents_controller.rb
+++ b/app/controllers/intents_controller.rb
@@ -1,9 +1,10 @@
 # frozen_string_literal: true
 
 class IntentsController < ApplicationController
-  def show
-    uri = Addressable::URI.parse(params[:uri])
+  before_action :check_uri
+  rescue_from Addressable::URI::InvalidURIError, with: :handle_invalid_uri
 
+  def show
     if uri.scheme == 'web+mastodon'
       case uri.host
       when 'follow'
@@ -15,4 +16,18 @@ class IntentsController < ApplicationController
 
     not_found
   end
+
+  private
+
+  def check_uri
+    not_found if uri.blank?
+  end
+
+  def handle_invalid_uri
+    not_found
+  end
+
+  def uri
+    @uri ||= Addressable::URI.parse(params[:uri])
+  end
 end
diff --git a/app/controllers/settings/follower_domains_controller.rb b/app/controllers/settings/follower_domains_controller.rb
index 02533b81a..83945df52 100644
--- a/app/controllers/settings/follower_domains_controller.rb
+++ b/app/controllers/settings/follower_domains_controller.rb
@@ -11,7 +11,7 @@ class Settings::FollowerDomainsController < Settings::BaseController
   def update
     domains = bulk_params[:select] || []
 
-    SoftBlockDomainFollowersWorker.push_bulk(domains) do |domain|
+    AfterAccountDomainBlockWorker.push_bulk(domains) do |domain|
       [current_account.id, domain]
     end
 
diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
index 4d5308219..7681430b7 100644
--- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js
+++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
@@ -96,7 +96,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
 
   onBlockDomain (domain) {
     dispatch(openModal('CONFIRM', {
-      message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.' values={{ domain: <strong>{domain}</strong> }} />,
+      message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
       confirm: intl.formatMessage(messages.blockDomainConfirm),
       onConfirm: () => dispatch(blockDomain(domain)),
     }));
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 60485e3c6..83f2f4d34 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -128,12 +128,24 @@ export default class ComposeForm extends ImmutablePureComponent {
     } else if(prevProps.is_submitting && !this.props.is_submitting) {
       this.autosuggestTextarea.textarea.focus();
     }
+
+    if (this.props.spoiler !== prevProps.spoiler) {
+      if (this.props.spoiler) {
+        this.spoilerText.focus();
+      } else {
+        this.autosuggestTextarea.textarea.focus();
+      }
+    }
   }
 
   setAutosuggestTextarea = (c) => {
     this.autosuggestTextarea = c;
   }
 
+  setSpoilerText = (c) => {
+    this.spoilerText = c;
+  }
+
   handleEmojiPick = (data) => {
     const { text }     = this.props;
     const position     = this.autosuggestTextarea.textarea.selectionStart;
@@ -164,7 +176,7 @@ export default class ComposeForm extends ImmutablePureComponent {
         <div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
           <label>
             <span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
-            <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input'  id='cw-spoiler-input' />
+            <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input'  id='cw-spoiler-input' ref={this.setSpoilerText} />
           </label>
         </div>
 
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
index 84665a7e8..4fc32db8a 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
@@ -28,7 +28,7 @@ const messages = defineMessages({
 const assetHost = process.env.CDN_HOST || '';
 let EmojiPicker, Emoji; // load asynchronously
 
-const backgroundImageFn = () => `${assetHost}/emoji/sheet.png`;
+const backgroundImageFn = () => `${assetHost}/emoji/sheet_10.png`;
 const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
 
 const categoriesSort = [
diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
index a49703bb1..d91b48497 100644
--- a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
+++ b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
@@ -51,7 +51,7 @@ describe('emoji', () => {
     });
 
     it('does an emoji that has no shortcode', () => {
-      expect(emojify('🕉️')).toEqual('<img draggable="false" class="emojione" alt="🕉️" title="" src="/emoji/1f549.svg" />');
+      expect(emojify('👁‍🗨')).toEqual('<img draggable="false" class="emojione" alt="👁‍🗨" title="" src="/emoji/1f441-200d-1f5e8.svg" />');
     });
 
     it('does an emoji whose filename is irregular', () => {
diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji_index-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji_index-test.js
index 53efa5743..9df2d34a0 100644
--- a/app/javascript/mastodon/features/emoji/__tests__/emoji_index-test.js
+++ b/app/javascript/mastodon/features/emoji/__tests__/emoji_index-test.js
@@ -44,6 +44,57 @@ describe('emoji_index', () => {
     expect(emojiIndex.search('apple').map(trimEmojis)).toEqual(expected);
   });
 
+  it('can include/exclude categories', () => {
+    expect(search('flag', { include: ['people'] })).toEqual([]);
+    expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([]);
+  });
+
+  it('(different behavior from emoji-mart) do not erases custom emoji if not passed again', () => {
+    const custom = [
+      {
+        id: 'mastodon',
+        name: 'mastodon',
+        short_names: ['mastodon'],
+        text: '',
+        emoticons: [],
+        keywords: ['mastodon'],
+        imageUrl: 'http://example.com',
+        custom: true,
+      },
+    ];
+    search('', { custom });
+    emojiIndex.search('', { custom });
+    const expected = [];
+    const lightExpected = [
+      {
+        id: 'mastodon',
+        custom: true,
+      },
+    ];
+    expect(search('masto').map(trimEmojis)).toEqual(lightExpected);
+    expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected);
+  });
+
+  it('(different behavior from emoji-mart) erases custom emoji if another is passed', () => {
+    const custom = [
+      {
+        id: 'mastodon',
+        name: 'mastodon',
+        short_names: ['mastodon'],
+        text: '',
+        emoticons: [],
+        keywords: ['mastodon'],
+        imageUrl: 'http://example.com',
+        custom: true,
+      },
+    ];
+    search('', { custom });
+    emojiIndex.search('', { custom });
+    const expected = [];
+    expect(search('masto', { custom: [] }).map(trimEmojis)).toEqual(expected);
+    expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected);
+  });
+
   it('handles custom emoji', () => {
     const custom = [
       {
@@ -65,23 +116,18 @@ describe('emoji_index', () => {
         custom: true,
       },
     ];
-    expect(search('masto').map(trimEmojis)).toEqual(expected);
-    expect(emojiIndex.search('masto').map(trimEmojis)).toEqual(expected);
+    expect(search('masto', { custom }).map(trimEmojis)).toEqual(expected);
+    expect(emojiIndex.search('masto', { custom }).map(trimEmojis)).toEqual(expected);
   });
 
   it('should filter only emojis we care about, exclude pineapple', () => {
-    const emojisToShowFilter = unified => unified !== '1F34D';
+    const emojisToShowFilter = emoji => emoji.unified !== '1F34D';
     expect(search('apple', { emojisToShowFilter }).map((obj) => obj.id))
       .not.toContain('pineapple');
     expect(emojiIndex.search('apple', { emojisToShowFilter }).map((obj) => obj.id))
       .not.toContain('pineapple');
   });
 
-  it('can include/exclude categories', () => {
-    expect(search('flag', { include: ['people'] })).toEqual([]);
-    expect(emojiIndex.search('flag', { include: ['people'] })).toEqual([]);
-  });
-
   it('does an emoji whose unified name is irregular', () => {
     const expected = [
       {
diff --git a/app/javascript/mastodon/features/emoji/emoji_compressed.js b/app/javascript/mastodon/features/emoji/emoji_compressed.js
index e5b834a74..a8a5cff94 100644
--- a/app/javascript/mastodon/features/emoji/emoji_compressed.js
+++ b/app/javascript/mastodon/features/emoji/emoji_compressed.js
@@ -9,7 +9,13 @@ const { unicodeToFilename } = require('./unicode_to_filename');
 const { unicodeToUnifiedName } = require('./unicode_to_unified_name');
 const emojiMap         = require('./emoji_map.json');
 const { emojiIndex } = require('emoji-mart');
-const { default: emojiMartData } = require('emoji-mart/dist/data');
+const { uncompress: emojiMartUncompress } = require('emoji-mart/dist/utils/data');
+let data = require('emoji-mart/data/all.json');
+
+if(data.compressed) {
+  data = emojiMartUncompress(data);
+}
+const emojiMartData = data;
 
 const excluded       = ['®', '©', '™'];
 const skins          = ['🏻', '🏼', '🏽', '🏾', '🏿'];
@@ -88,6 +94,6 @@ module.exports = JSON.parse(JSON.stringify([
   shortCodesToEmojiData,
   emojiMartData.skins,
   emojiMartData.categories,
-  emojiMartData.short_names,
+  emojiMartData.aliases,
   emojisWithoutShortCodes,
 ]));
diff --git a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
index 5755bf1c4..36351ec02 100644
--- a/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
+++ b/app/javascript/mastodon/features/emoji/emoji_mart_search_light.js
@@ -8,6 +8,7 @@ let originalPool = {};
 let index = {};
 let emojisList = {};
 let emoticonsList = {};
+let customEmojisList = [];
 
 for (let emoji in data.emojis) {
   let emojiData = data.emojis[emoji];
@@ -28,7 +29,18 @@ for (let emoji in data.emojis) {
   originalPool[id] = emojiData;
 }
 
+function clearCustomEmojis(pool) {
+  customEmojisList.forEach((emoji) => {
+    let emojiId = emoji.id || emoji.short_names[0];
+
+    delete pool[emojiId];
+    delete emojisList[emojiId];
+  });
+}
+
 function addCustomToPool(custom, pool) {
+  if (customEmojisList.length) clearCustomEmojis(pool);
+
   custom.forEach((emoji) => {
     let emojiId = emoji.id || emoji.short_names[0];
 
@@ -37,10 +49,18 @@ function addCustomToPool(custom, pool) {
       emojisList[emojiId] = getSanitizedData(emoji);
     }
   });
+
+  customEmojisList = custom;
+  index = {};
 }
 
-function search(value, { emojisToShowFilter, maxResults, include, exclude, custom = [] } = {}) {
-  addCustomToPool(custom, originalPool);
+function search(value, { emojisToShowFilter, maxResults, include, exclude, custom } = {}) {
+  if (custom !== undefined) {
+    if (customEmojisList !== custom)
+      addCustomToPool(custom, originalPool);
+  } else {
+    custom = [];
+  }
 
   maxResults = maxResults || 75;
   include = include || [];
@@ -143,7 +163,7 @@ function search(value, { emojisToShowFilter, maxResults, include, exclude, custo
 
   if (results) {
     if (emojisToShowFilter) {
-      results = results.filter((result) => emojisToShowFilter(data.emojis[result.id].unified));
+      results = results.filter((result) => emojisToShowFilter(data.emojis[result.id]));
     }
 
     if (results && results.length > maxResults) {
diff --git a/app/javascript/mastodon/features/emoji/emoji_picker.js b/app/javascript/mastodon/features/emoji/emoji_picker.js
index 7e145381e..044d38cb2 100644
--- a/app/javascript/mastodon/features/emoji/emoji_picker.js
+++ b/app/javascript/mastodon/features/emoji/emoji_picker.js
@@ -1,5 +1,5 @@
-import Picker from 'emoji-mart/dist-es/components/picker';
-import Emoji from 'emoji-mart/dist-es/components/emoji';
+import Picker from 'emoji-mart/dist-es/components/picker/picker';
+import Emoji from 'emoji-mart/dist-es/components/emoji/emoji';
 
 export {
   Picker,
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index 48b4d28cd..2f2163df2 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -60,7 +60,7 @@
   "column_header.unpin": "No fixis",
   "column_subheading.settings": "Configuració",
   "compose_form.direct_message_warning": "Aquest toot només serà enviat als usuaris esmentats. De totes maneres, els operadors de la teva o de qualsevol de les instàncies receptores poden inspeccionar aquest missatge.",
-  "compose_form.direct_message_warning_learn_more": "Learn more",
+  "compose_form.direct_message_warning_learn_more": "Aprèn més",
   "compose_form.hashtag_warning": "Aquest toot no es mostrarà en cap etiqueta ja que no està llistat. Només els toots públics poden ser cercats per etiqueta.",
   "compose_form.lock_disclaimer": "El teu compte no està bloquejat {locked}. Tothom pot seguir-te i veure els teus missatges a seguidors.",
   "compose_form.lock_disclaimer.lock": "blocat",
@@ -83,8 +83,8 @@
   "confirmations.domain_block.message": "Estàs realment, realment segur que vols blocar totalment {domain}? En la majoria dels casos blocar o silenciar uns pocs objectius és suficient i preferible.",
   "confirmations.mute.confirm": "Silencia",
   "confirmations.mute.message": "Estàs segur que vols silenciar {name}?",
-  "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+  "confirmations.redraft.confirm": "Esborrar i refer",
+  "confirmations.redraft.message": "Estàs segur que vols esborrar aquesta publicació i tornar a redactar-la? Perderàs totes les respostes, impulsos i favorits.",
   "confirmations.unfollow.confirm": "Deixa de seguir",
   "confirmations.unfollow.message": "Estàs segur que vols deixar de seguir {name}?",
   "embed.instructions": "Incrusta aquest estat al lloc web copiant el codi a continuació.",
@@ -116,7 +116,7 @@
   "getting_started.documentation": "Documentation",
   "getting_started.heading": "Començant",
   "getting_started.open_source_notice": "Mastodon és un programari de codi obert. Pots contribuir o informar de problemes a GitHub a {github}.",
-  "getting_started.terms": "Terms of service",
+  "getting_started.terms": "Termes del servei",
   "home.column_settings.advanced": "Avançat",
   "home.column_settings.basic": "Bàsic",
   "home.column_settings.filter_regex": "Filtrar per expressió regular",
@@ -160,7 +160,7 @@
   "navigation_bar.blocks": "Usuaris bloquejats",
   "navigation_bar.community_timeline": "Línia de temps Local",
   "navigation_bar.direct": "Missatges directes",
-  "navigation_bar.discover": "Discover",
+  "navigation_bar.discover": "Descobreix",
   "navigation_bar.domain_blocks": "Dominis ocults",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favorits",
@@ -174,7 +174,7 @@
   "navigation_bar.pins": "Toots fixats",
   "navigation_bar.preferences": "Preferències",
   "navigation_bar.public_timeline": "Línia de temps federada",
-  "navigation_bar.security": "Security",
+  "navigation_bar.security": "Seguretat",
   "notification.favourite": "{name} ha afavorit el teu estat",
   "notification.follow": "{name} et segueix",
   "notification.mention": "{name} t'ha esmentat",
@@ -190,7 +190,7 @@
   "notifications.column_settings.reblog": "Impulsos:",
   "notifications.column_settings.show": "Mostrar en la columna",
   "notifications.column_settings.sound": "Reproduïr so",
-  "notifications.group": "{count} notifications",
+  "notifications.group": "{count} notificacions",
   "onboarding.done": "Fet",
   "onboarding.next": "Següent",
   "onboarding.page_five.public_timelines": "La línia de temps local mostra missatges públics de tothom de {domain}. La línia de temps federada mostra els missatges públics de tothom que la gent de {domain} segueix. Aquests són les línies de temps Públiques, una bona manera de descobrir noves persones.",
@@ -266,7 +266,7 @@
   "status.reblog": "Impuls",
   "status.reblog_private": "Impulsar a l'audiència original",
   "status.reblogged_by": "{name} ha retootejat",
-  "status.redraft": "Delete & re-draft",
+  "status.redraft": "Esborrar i reescriure",
   "status.reply": "Respondre",
   "status.replyAll": "Respondre al tema",
   "status.report": "Informar sobre @{name}",
@@ -286,7 +286,7 @@
   "tabs_bar.search": "Cerca",
   "timeline.media": "Media",
   "timeline.posts": "Toots",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.count_by_accounts": "{count} {rawCount, plural, una {person} altres {people}} parlant",
   "ui.beforeunload": "El vostre esborrany es perdrà si sortiu de Mastodon.",
   "upload_area.title": "Arrossega i deixa anar per carregar",
   "upload_button.label": "Afegir multimèdia",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 464592121..3fa60bf01 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -449,7 +449,7 @@
         "id": "confirmations.block.message"
       },
       {
-        "defaultMessage": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
+        "defaultMessage": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
         "id": "confirmations.domain_block.message"
       }
     ],
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index ae3b96c6d..d0ad4fb2c 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -84,7 +84,7 @@
   "confirmations.delete_list.confirm": "Delete",
   "confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
   "confirmations.domain_block.confirm": "Hide entire domain",
-  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
+  "confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
   "confirmations.mute.confirm": "Mute",
   "confirmations.mute.message": "Are you sure you want to mute {name}?",
   "confirmations.redraft.confirm": "Delete & redraft",
diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json
index 49be810e2..c6904e9e2 100644
--- a/app/javascript/mastodon/locales/eu.json
+++ b/app/javascript/mastodon/locales/eu.json
@@ -10,7 +10,7 @@
   "account.follow": "Jarraitu",
   "account.followers": "Jarraitzaileak",
   "account.follows": "Jarraitzen",
-  "account.follows_you": "Jarraitzen dizu",
+  "account.follows_you": "Jarraitzen zaitu",
   "account.hide_reblogs": "Ezkutatu @{name}(r)en bultzadak",
   "account.media": "Media",
   "account.mention": "Aipatu @{name}",
@@ -42,7 +42,7 @@
   "column.blocks": "Blokeatutako erabiltzaileak",
   "column.community": "Denbora-lerro lokala",
   "column.direct": "Mezu zuzenak",
-  "column.domain_blocks": "Domeinu ezkutuak",
+  "column.domain_blocks": "Ezkutatutako domeinuak",
   "column.favourites": "Gogokoak",
   "column.follow_requests": "Jarraitzeko eskariak",
   "column.home": "Hasiera",
@@ -75,16 +75,16 @@
   "confirmation_modal.cancel": "Utzi",
   "confirmations.block.confirm": "Block",
   "confirmations.block.message": "Ziur {name} blokeatu nahi duzula?",
-  "confirmations.delete.confirm": "Delete",
+  "confirmations.delete.confirm": "Ezabatu",
   "confirmations.delete.message": "Ziur mezu hau ezabatu nahi duzula?",
-  "confirmations.delete_list.confirm": "Delete",
+  "confirmations.delete_list.confirm": "Ezabatu",
   "confirmations.delete_list.message": "Ziur behin betiko ezabatu nahi duzula zerrenda hau?",
   "confirmations.domain_block.confirm": "Ezkutatu domeinu osoa",
   "confirmations.domain_block.message": "Ziur, erabat ziur, {domain} domeinu osoa blokeatu nahi duzula? Gehienetan gutxi batzuk blokeatu edo mututzearekin nahikoa da.",
   "confirmations.mute.confirm": "Mututu",
   "confirmations.mute.message": "Ziur {name} mututu nahi duzula?",
-  "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+  "confirmations.redraft.confirm": "Ezabatu eta berridatzi",
+  "confirmations.redraft.message": "Ziur mezu hau ezabatu eta berridatzi nahi duzula? Berari egindako erantzun, bultzada eta gogokoak galduko dira.",
   "confirmations.unfollow.confirm": "Utzi jarraitzeari",
   "confirmations.unfollow.message": "Ziur {name} jarraitzeari utzi nahi diozula?",
   "embed.instructions": "Txertatu mezu hau zure webgunean beheko kodea kopatuz.",
@@ -114,7 +114,7 @@
   "follow_request.authorize": "Baimendu",
   "follow_request.reject": "Ukatu",
   "getting_started.documentation": "Dokumentazioa",
-  "getting_started.heading": "Abiapuntua",
+  "getting_started.heading": "Menua",
   "getting_started.open_source_notice": "Mastodon software librea da. Ekarpenak egin ditzakezu edo akatsen berri eman GitHub bidez: {github}.",
   "getting_started.terms": "Erabilera baldintzak",
   "home.column_settings.advanced": "Aurreratua",
@@ -146,7 +146,7 @@
   "lightbox.previous": "Aurrekoa",
   "lists.account.add": "Gehitu zerrendara",
   "lists.account.remove": "Kendu zerrendatik",
-  "lists.delete": "Delete list",
+  "lists.delete": "Ezabatu zerrenda",
   "lists.edit": "Editatu zerrenda",
   "lists.new.create": "Gehitu zerrenda",
   "lists.new.title_placeholder": "Zerrenda berriaren izena",
@@ -161,7 +161,7 @@
   "navigation_bar.community_timeline": "Denbora-lerro lokala",
   "navigation_bar.direct": "Mezu zuzenak",
   "navigation_bar.discover": "Aurkitu",
-  "navigation_bar.domain_blocks": "Domeinu ezkutuak",
+  "navigation_bar.domain_blocks": "Ezkutatutako domeinuak",
   "navigation_bar.edit_profile": "Aldatu profila",
   "navigation_bar.favourites": "Gogokoak",
   "navigation_bar.follow_requests": "Jarraitzeko eskariak",
@@ -250,7 +250,7 @@
   "status.block": "Block @{name}",
   "status.cancel_reblog_private": "Kendu bultzada",
   "status.cannot_reblog": "Mezu honi ezin zaio bultzada eman",
-  "status.delete": "Delete",
+  "status.delete": "Ezabatu",
   "status.direct": "Mezu zuzena @{name}(r)i",
   "status.embed": "Txertatu",
   "status.favourite": "Gogokoa",
@@ -266,7 +266,7 @@
   "status.reblog": "Bultzada",
   "status.reblog_private": "Bultzada jatorrizko hartzaileei",
   "status.reblogged_by": "{name}(r)en bultzada",
-  "status.redraft": "Delete & re-draft",
+  "status.redraft": "Ezabatu eta berridatzi",
   "status.reply": "Erantzun",
   "status.replyAll": "Erantzun harian",
   "status.report": "Salatu @{name}",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index bf7a256d6..0d5b8bf24 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -83,8 +83,8 @@
   "confirmations.domain_block.message": "آیا جدی جدی می‌خواهید کل دامین {domain} را مسدود کنید؟ بیشتر وقت‌ها مسدودکردن یا بی‌صداکردن چند حساب کاربری خاص کافی است و توصیه می‌شود.",
   "confirmations.mute.confirm": "بی‌صدا کن",
   "confirmations.mute.message": "آیا واقعاً می‌خواهید {name} را بی‌صدا کنید؟",
-  "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+  "confirmations.redraft.confirm": "پاک‌کردن و بازنویسی",
+  "confirmations.redraft.message": "آیا واقعاً می‌خواهید این نوشته را پاک کنید و آن را از نو بنویسید؟ با این کار همهٔ پاسخ‌ها، بازبوق‌ها، و پسندیده‌شدن‌های آن از دست می‌رود.",
   "confirmations.unfollow.confirm": "لغو پیگیری",
   "confirmations.unfollow.message": "آیا واقعاً می‌خواهید به پیگیری از {name} پایان دهید؟",
   "embed.instructions": "برای جاگذاری این نوشته در سایت خودتان، کد زیر را کپی کنید.",
@@ -266,7 +266,7 @@
   "status.reblog": "بازبوقیدن",
   "status.reblog_private": "بازبوق به مخاطبان اولیه",
   "status.reblogged_by": "‫{name}‬ بازبوقید",
-  "status.redraft": "Delete & re-draft",
+  "status.redraft": "پاک‌کردن و بازنویسی",
   "status.reply": "پاسخ",
   "status.replyAll": "به نوشته پاسخ دهید",
   "status.report": "گزارش دادن @{name}",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 35c753091..a6849cc94 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -116,7 +116,7 @@
   "getting_started.documentation": "Documentation",
   "getting_started.heading": "Pour commencer",
   "getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {github} sur GitHub.",
-  "getting_started.terms": "Terms of service",
+  "getting_started.terms": "Conditions d’utilisation",
   "home.column_settings.advanced": "Avancé",
   "home.column_settings.basic": "Basique",
   "home.column_settings.filter_regex": "Filtrer avec une expression rationnelle",
@@ -160,7 +160,7 @@
   "navigation_bar.blocks": "Comptes bloqués",
   "navigation_bar.community_timeline": "Fil public local",
   "navigation_bar.direct": "Messages directs",
-  "navigation_bar.discover": "Discover",
+  "navigation_bar.discover": "Découvrir",
   "navigation_bar.domain_blocks": "Domaines cachés",
   "navigation_bar.edit_profile": "Modifier le profil",
   "navigation_bar.favourites": "Favoris",
@@ -174,7 +174,7 @@
   "navigation_bar.pins": "Pouets épinglés",
   "navigation_bar.preferences": "Préférences",
   "navigation_bar.public_timeline": "Fil public global",
-  "navigation_bar.security": "Security",
+  "navigation_bar.security": "Sécurité",
   "notification.favourite": "{name} a ajouté à ses favoris :",
   "notification.follow": "{name} vous suit",
   "notification.mention": "{name} vous a mentionné⋅e :",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index 9bb3fca25..4d7f96708 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -87,8 +87,8 @@
   "confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。",
   "confirmations.mute.confirm": "ミュート",
   "confirmations.mute.message": "本当に{name}さんをミュートしますか?",
-  "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+  "confirmations.redraft.confirm": "削除し下書きに戻す",
+  "confirmations.redraft.message": "本当にこのトゥートを削除し下書きに戻しますか?このトゥートへの全ての返信やブースト、お気に入り登録を失うことになります。",
   "confirmations.unfollow.confirm": "フォロー解除",
   "confirmations.unfollow.message": "本当に{name}さんのフォローを解除しますか?",
   "embed.instructions": "下記のコードをコピーしてウェブサイトに埋め込みます。",
@@ -271,7 +271,7 @@
   "status.reblog": "ブースト",
   "status.reblog_private": "ブースト",
   "status.reblogged_by": "{name}さんがブースト",
-  "status.redraft": "Delete & re-draft",
+  "status.redraft": "削除し下書きに戻す",
   "status.reply": "返信",
   "status.replyAll": "全員に返信",
   "status.report": "@{name}さんを通報",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index d744a8723..76b90ac2a 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -83,8 +83,8 @@
   "confirmations.domain_block.message": "Weet je het echt heel erg zeker dat je alles van {domain} wil negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en gepaster.",
   "confirmations.mute.confirm": "Negeren",
   "confirmations.mute.message": "Weet je het zeker dat je {name} wilt negeren?",
-  "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+  "confirmations.redraft.confirm": "Verwijderen en herschrijven",
+  "confirmations.redraft.message": "Weet je zeker dat je deze toot wilt verwijderen en herschrijven? Je verliest wel alle reacties, boosts en favorieten.",
   "confirmations.unfollow.confirm": "Ontvolgen",
   "confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?",
   "embed.instructions": "Embed deze toot op jouw website, door de onderstaande code te kopiëren.",
@@ -266,7 +266,7 @@
   "status.reblog": "Boost",
   "status.reblog_private": "Boost naar oorspronkelijke ontvangers",
   "status.reblogged_by": "{name} boostte",
-  "status.redraft": "Delete & re-draft",
+  "status.redraft": "Verwijderen en herschrijven",
   "status.reply": "Reageren",
   "status.replyAll": "Reageer op iedereen",
   "status.report": "Rapporteer @{name}",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index fcb2a5686..89a2cf3e3 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -59,7 +59,7 @@
   "column_header.show_settings": "Mostrar configurações",
   "column_header.unpin": "Desafixar",
   "column_subheading.settings": "Configurações",
-  "compose_form.direct_message_warning": "Este toot só será enviado aos usuários mencionados. A mensagem não é encriptada e será armazenada nos servidores dos usuários mencionados.",
+  "compose_form.direct_message_warning": "Este toot só será enviado aos usuários mencionados.",
   "compose_form.direct_message_warning_learn_more": "Saber mais",
   "compose_form.hashtag_warning": "Esse toot não será listado em nenhuma hashtag por ser não listado. Somente toots públicos podem ser pesquisados por hashtag.",
   "compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar postagens direcionadas a apenas seguidores.",
@@ -83,8 +83,8 @@
   "confirmations.domain_block.message": "Você quer mesmo bloquear {domain} inteiro? Na maioria dos casos, silenciar ou bloquear alguns usuários é o suficiente e o recomendado.",
   "confirmations.mute.confirm": "Silenciar",
   "confirmations.mute.message": "Você tem certeza de que quer silenciar {name}?",
-  "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+  "confirmations.redraft.confirm": "Apagar & usar como rascunho",
+  "confirmations.redraft.message": "Você tem certeza que deseja apagar esse status e usá-lo como rascunho? Você vai perder todas as respostas, compartilhamentos e favoritos relacionados a ele.",
   "confirmations.unfollow.confirm": "Deixar de seguir",
   "confirmations.unfollow.message": "Você tem certeza de que quer deixar de seguir {name}?",
   "embed.instructions": "Incorpore esta postagem em seu site copiando o código abaixo.",
@@ -248,10 +248,10 @@
   "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
   "standalone.public_title": "Dê uma espiada...",
   "status.block": "Block @{name}",
-  "status.cancel_reblog_private": "Retirar o compartilhamento",
+  "status.cancel_reblog_private": "Desfazer compartilhamento",
   "status.cannot_reblog": "Esta postagem não pode ser compartilhada",
   "status.delete": "Excluir",
-  "status.direct": "Enviar mensagem direta à @{name}",
+  "status.direct": "Enviar mensagem direta a @{name}",
   "status.embed": "Incorporar",
   "status.favourite": "Adicionar aos favoritos",
   "status.load_more": "Carregar mais",
@@ -266,7 +266,7 @@
   "status.reblog": "Compartilhar",
   "status.reblog_private": "Compartilhar com a audiência original",
   "status.reblogged_by": "{name} compartilhou",
-  "status.redraft": "Delete & re-draft",
+  "status.redraft": "Apagar & usar como rascunho",
   "status.reply": "Responder",
   "status.replyAll": "Responder à sequência",
   "status.report": "Denunciar @{name}",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 983018dd8..c71d25327 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -1,7 +1,7 @@
 {
   "account.badges.bot": "Bot",
-  "account.block": "Blokovať @{name}",
-  "account.block_domain": "Ukryť všetko z {domain}",
+  "account.block": "Blokuj @{name}",
+  "account.block_domain": "Ukry všetko z {domain}",
   "account.blocked": "Blokovaný/á",
   "account.direct": "Súkromná správa pre @{name}",
   "account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.",
@@ -16,7 +16,7 @@
   "account.mention": "Spomeň @{name}",
   "account.moved_to": "{name} sa presunul/a na:",
   "account.mute": "Ignorovať @{name}",
-  "account.mute_notifications": "Stĺmiť notifikácie od @{name}",
+  "account.mute_notifications": "Stĺmiť oznámenia od @{name}",
   "account.muted": "Utíšený/á",
   "account.posts": "Hlášky",
   "account.posts_with_replies": "Príspevky s odpoveďami",
@@ -48,7 +48,7 @@
   "column.home": "Domov",
   "column.lists": "Zoznamy",
   "column.mutes": "Ignorovaní užívatelia",
-  "column.notifications": "Notifikácie",
+  "column.notifications": "Oznámenia",
   "column.pins": "Pripnuté tooty",
   "column.public": "Federovaná časová os",
   "column_back_button.label": "Späť",
@@ -83,8 +83,8 @@
   "confirmations.domain_block.message": "Ste si naozaj istý, že chcete blokovať celú {domain}? Vo väčšine prípadov stačí blokovať alebo ignorovať daných používateľov, čiže to sa doporučuje.",
   "confirmations.mute.confirm": "Ignoruj",
   "confirmations.mute.message": "Naozaj chcete ignorovať {name}?",
-  "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+  "confirmations.redraft.confirm": "Vyčistiť a prepísať",
+  "confirmations.redraft.message": "Si si istý/á, že chceš vymazať a prepísať tento príspevok? Stratíš všetky jeho nadobudnuté odpovede, povýšenia a obľúbenia.",
   "confirmations.unfollow.confirm": "Nesledovať",
   "confirmations.unfollow.message": "Naozaj chcete prestať sledovať {name}?",
   "embed.instructions": "Umiestni kód uvedený nižšie pre pridanie tohto statusu na tvoju web stránku.",
@@ -109,11 +109,11 @@
   "empty_column.home": "Vaša lokálna osa je zatiaľ prázdna! Pre začiatok pozrite {public} alebo použite vyhľadávanie a nájdite tak ostatných používateľov.",
   "empty_column.home.public_timeline": "verejná časová os",
   "empty_column.list": "Tento zoznam je ešte prázdny. Keď ale členovia tohoto zoznamu napíšu nové správy, tak tie sa objavia priamo tu.",
-  "empty_column.notifications": "Nemáte ešte žiadne notifikácie. Napíšte niekomu, následujte niekoho a komunikujte s ostatnými aby diskusia mohla začať.",
-  "empty_column.public": "Ešte tu nič nie je. Napíšte niečo verejne alebo začnite sledovať používateľov z iných Mastodon serverov aby tu niečo pribudlo",
+  "empty_column.notifications": "Nemáš ešte žiadne oznámenia. Zapoj sa s niekym do debaty a komunikuj s ostatnými aby diskusia mohla začať.",
+  "empty_column.public": "Ešte tu nič nie je. Napíš niečo verejne alebo začnite sledovať používateľov z iných Mastodon serverov aby tu niečo pribudlo",
   "follow_request.authorize": "Povoľ prístup",
   "follow_request.reject": "Odmietni",
-  "getting_started.documentation": "Documentation",
+  "getting_started.documentation": "Dokumentácia",
   "getting_started.heading": "Začni tu",
   "getting_started.open_source_notice": "Mastodon má otvorený kód. Nahlásiť chyby, alebo prispieť môžeš na GitHube v {github}.",
   "getting_started.terms": "Podmienky prevozu",
@@ -168,9 +168,9 @@
   "navigation_bar.info": "O tomto Mastodon serveri",
   "navigation_bar.keyboard_shortcuts": "Klávesové skratky",
   "navigation_bar.lists": "Zoznamy",
-  "navigation_bar.logout": "Odhlásiť",
+  "navigation_bar.logout": "Odhlás sa",
   "navigation_bar.mutes": "Ignorovaní užívatelia",
-  "navigation_bar.personal": "Personal",
+  "navigation_bar.personal": "Osobné",
   "navigation_bar.pins": "Pripnuté tooty",
   "navigation_bar.preferences": "Voľby",
   "navigation_bar.public_timeline": "Federovaná časová os",
@@ -248,7 +248,7 @@
   "search_results.total": "{count, number} {count, plural, jeden {výsledok} ostatné {výsledky}}",
   "standalone.public_title": "Náhľad dovnútra...",
   "status.block": "Blokovať @{name}",
-  "status.cancel_reblog_private": "Unboost",
+  "status.cancel_reblog_private": "Nezdieľaj",
   "status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý",
   "status.delete": "Zmazať",
   "status.direct": "Súkromná správa @{name}",
@@ -266,7 +266,7 @@
   "status.reblog": "Povýšiť",
   "status.reblog_private": "Boost to original audience",
   "status.reblogged_by": "{name} povýšil/a",
-  "status.redraft": "Delete & re-draft",
+  "status.redraft": "Vymaž a prepíš",
   "status.reply": "Odpovedať",
   "status.replyAll": "Odpovedať na diskusiu",
   "status.report": "Nahlásiť @{name}",
@@ -284,11 +284,11 @@
   "tabs_bar.local_timeline": "Lokálna",
   "tabs_bar.notifications": "Notifikácie",
   "tabs_bar.search": "Hľadaj",
-  "timeline.media": "Media",
+  "timeline.media": "Médiá",
   "timeline.posts": "Príspevky",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
-  "ui.beforeunload": "Čo máte rozpísané sa stratí, ak opustíte Mastodon.",
-  "upload_area.title": "Ťahaj a pusti pre nahratie",
+  "trends.count_by_accounts": "{count} {rawCount, viacerí, jeden {person} iní {people}} diskutujú",
+  "ui.beforeunload": "Čo máš rozpísané sa stratí, ak opustíš Mastodon.",
+  "upload_area.title": "Pretiahni a pusť pre nahratie",
   "upload_button.label": "Pridať médiá",
   "upload_form.description": "Opis pre slabo vidiacich",
   "upload_form.focus": "Vystrihni",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index 24bab6f80..3b5af6dd6 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -83,8 +83,8 @@
   "confirmations.domain_block.message": "你真的确定要隐藏所有来自 {domain} 的内容吗?多数情况下,屏蔽或隐藏几个特定的用户应该就能满足你的需要了。",
   "confirmations.mute.confirm": "隐藏",
   "confirmations.mute.message": "你确定要隐藏 {name} 吗?",
-  "confirmations.redraft.confirm": "Delete & redraft",
-  "confirmations.redraft.message": "Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.",
+  "confirmations.redraft.confirm": "删除并重新编辑",
+  "confirmations.redraft.message": "你确定要删除这条嘟文并重新编辑它吗?所有相关的回复、转嘟和收藏都会被清除。",
   "confirmations.unfollow.confirm": "取消关注",
   "confirmations.unfollow.message": "你确定要取消关注 {name} 吗?",
   "embed.instructions": "要在你的网站上嵌入这条嘟文,请复制以下代码。",
@@ -113,10 +113,10 @@
   "empty_column.public": "这里神马都没有!写一些公开的嘟文,或者关注其他实例的用户后,这里就会有嘟文出现了哦!",
   "follow_request.authorize": "同意",
   "follow_request.reject": "拒绝",
-  "getting_started.documentation": "Documentation",
+  "getting_started.documentation": "文档",
   "getting_started.heading": "开始使用",
   "getting_started.open_source_notice": "Mastodon 是一个开源软件。欢迎前往 GitHub({github})贡献代码或反馈问题。",
-  "getting_started.terms": "Terms of service",
+  "getting_started.terms": "使用条款",
   "home.column_settings.advanced": "高级设置",
   "home.column_settings.basic": "基本设置",
   "home.column_settings.filter_regex": "使用正则表达式(regex)过滤",
@@ -160,7 +160,7 @@
   "navigation_bar.blocks": "已屏蔽的用户",
   "navigation_bar.community_timeline": "本站时间轴",
   "navigation_bar.direct": "私信",
-  "navigation_bar.discover": "Discover",
+  "navigation_bar.discover": "发现",
   "navigation_bar.domain_blocks": "已屏蔽的网站",
   "navigation_bar.edit_profile": "修改个人资料",
   "navigation_bar.favourites": "收藏的内容",
@@ -170,11 +170,11 @@
   "navigation_bar.lists": "列表",
   "navigation_bar.logout": "注销",
   "navigation_bar.mutes": "已隐藏的用户",
-  "navigation_bar.personal": "Personal",
+  "navigation_bar.personal": "个人",
   "navigation_bar.pins": "置顶嘟文",
   "navigation_bar.preferences": "首选项",
   "navigation_bar.public_timeline": "跨站公共时间轴",
-  "navigation_bar.security": "Security",
+  "navigation_bar.security": "安全",
   "notification.favourite": "{name} 收藏了你的嘟文",
   "notification.follow": "{name} 开始关注你",
   "notification.mention": "{name} 提及你",
@@ -266,7 +266,7 @@
   "status.reblog": "转嘟",
   "status.reblog_private": "转嘟给原有关注者",
   "status.reblogged_by": "{name} 转嘟了",
-  "status.redraft": "Delete & re-draft",
+  "status.redraft": "删除并重新编辑",
   "status.reply": "回复",
   "status.replyAll": "回复所有人",
   "status.report": "举报 @{name}",
@@ -286,7 +286,7 @@
   "tabs_bar.search": "搜索",
   "timeline.media": "媒体",
   "timeline.posts": "嘟文",
-  "trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
+  "trends.count_by_accounts": "{count} 人正在讨论",
   "ui.beforeunload": "如果你现在离开 Mastodon,你的草稿内容将会被丢弃。",
   "upload_area.title": "将文件拖放到此处开始上传",
   "upload_button.label": "上传媒体文件",
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index 460dc53a9..fad7feb98 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -47,6 +47,19 @@
   background: darken($ui-base-color, 6%);
 }
 
+.emoji-mart-bar {
+  border-color: lighten($ui-base-color, 8%);
+
+  &:first-child {
+    background: $ui-base-color;
+  }
+}
+
+.emoji-mart-search input {
+  background: rgba($ui-base-color, 0.3);
+  border-color: $ui-base-color;
+}
+
 .focusable:focus {
   background: $ui-base-color;
 }
@@ -74,6 +87,17 @@
   }
 }
 
+// Change the background colors of media and video spoiler
+
+.media-spoiler,
+.video-player__spoiler {
+  background: $ui-base-color;
+}
+
+.account-gallery__item a {
+  background-color: $ui-base-color;
+}
+
 // Change the colors used in the dropdown menu
 .dropdown-menu {
   background: $ui-base-color;
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index a261b582b..58f1d6835 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -4555,7 +4555,7 @@ a.status-card {
     height: 100%;
     z-index: 4;
     border: 0;
-    background: $base-shadow-color;
+    background: $base-overlay-background;
     color: $darker-text-color;
     transition: none;
     pointer-events: none;
diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss
index cf9547586..e49084b5f 100644
--- a/app/javascript/styles/mastodon/emoji_picker.scss
+++ b/app/javascript/styles/mastodon/emoji_picker.scss
@@ -62,16 +62,16 @@
   }
 
   .emoji-mart-anchor-bar {
-    bottom: 0;
+    bottom: -1px;
   }
 }
 
 .emoji-mart-anchor-bar {
   position: absolute;
-  bottom: -3px;
+  bottom: -5px;
   left: 0;
   width: 100%;
-  height: 3px;
+  height: 4px;
   background-color: $highlight-text-color;
 }
 
diff --git a/app/javascript/styles/mastodon/footer.scss b/app/javascript/styles/mastodon/footer.scss
index fe2d40c0c..81eb1ce2d 100644
--- a/app/javascript/styles/mastodon/footer.scss
+++ b/app/javascript/styles/mastodon/footer.scss
@@ -1,6 +1,7 @@
 .footer {
   text-align: center;
   margin-top: 30px;
+  padding-bottom: 60px;
   font-size: 12px;
   color: $darker-text-color;
 
diff --git a/app/lib/activitypub/activity/follow.rb b/app/lib/activitypub/activity/follow.rb
index fbbf358a8..826dcf18e 100644
--- a/app/lib/activitypub/activity/follow.rb
+++ b/app/lib/activitypub/activity/follow.rb
@@ -6,6 +6,11 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
 
     return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account)
 
+    if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain)
+      reject_follow_request!(target_account)
+      return
+    end
+
     # Fast-forward repeat follow requests
     if @account.following?(target_account)
       AuthorizeFollowService.new.call(@account, target_account, skip_follow_request: true)
@@ -21,4 +26,9 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
       NotifyService.new.call(target_account, ::Follow.find_by(account: @account, target_account: target_account))
     end
   end
+
+  def reject_follow_request!(target_account)
+    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))
+    ActivityPub::DeliveryWorker.perform_async(json, target_account.id, @account.inbox_url)
+  end
 end
diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb
index 867bc9519..d19b20c48 100644
--- a/app/models/web/push_subscription.rb
+++ b/app/models/web/push_subscription.rb
@@ -18,7 +18,7 @@ class Web::PushSubscription < ApplicationRecord
   belongs_to :user, optional: true
   belongs_to :access_token, class_name: 'Doorkeeper::AccessToken', optional: true
 
-  has_one :session_activation
+  has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription
 
   def push(notification)
     I18n.with_locale(associated_user&.locale || I18n.default_locale) do
diff --git a/app/services/after_block_domain_from_account_service.rb b/app/services/after_block_domain_from_account_service.rb
new file mode 100644
index 000000000..0f1a8505d
--- /dev/null
+++ b/app/services/after_block_domain_from_account_service.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class AfterBlockDomainFromAccountService < BaseService
+  # This service does not create an AccountDomainBlock record,
+  # it's meant to be called after such a record has been created
+  # synchronously, to "clean up"
+  def call(account, domain)
+    @account = account
+    @domain  = domain
+
+    reject_existing_followers!
+    reject_pending_follow_requests!
+  end
+
+  private
+
+  def reject_existing_followers!
+    @account.passive_relationships.where(account: Account.where(domain: @domain)).includes(:account).find_each do |follow|
+      reject_follow!(follow)
+    end
+  end
+
+  def reject_pending_follow_requests!
+    FollowRequest.where(target_account: @account).where(account: Account.where(domain: @domain)).includes(:account).find_each do |follow_request|
+      reject_follow!(follow_request)
+    end
+  end
+
+  def reject_follow!(follow)
+    follow.destroy
+
+    return unless follow.account.activitypub?
+
+    json = Oj.dump(ActivityPub::LinkedDataSignature.new(ActiveModelSerializers::SerializableResource.new(
+      follow,
+      serializer: ActivityPub::RejectFollowSerializer,
+      adapter: ActivityPub::Adapter
+    ).as_json).sign!(@account))
+
+    ActivityPub::DeliveryWorker.perform_async(json, @account.id, follow.account.inbox_url)
+  end
+end
diff --git a/app/services/block_domain_from_account_service.rb b/app/services/block_domain_from_account_service.rb
deleted file mode 100644
index cae7abcbd..000000000
--- a/app/services/block_domain_from_account_service.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-# frozen_string_literal: true
-
-class BlockDomainFromAccountService < BaseService
-  def call(account, domain)
-    account.block_domain!(domain)
-    account.passive_relationships.where(account: Account.where(domain: domain)).delete_all
-  end
-end
diff --git a/app/views/accounts/_follow_button.html.haml b/app/views/accounts/_follow_button.html.haml
index 96ae23234..558ced010 100644
--- a/app/views/accounts/_follow_button.html.haml
+++ b/app/views/accounts/_follow_button.html.haml
@@ -15,6 +15,11 @@
         = link_to (account.local? ? account_follow_path(account) : authorize_follow_path(acct: account.acct)), data: { method: :post }, class: 'icon-button' do
           = fa_icon 'user-plus'
           = t('accounts.follow')
+  - elsif user_signed_in? && current_account.id == account.id
+    .controls
+      = link_to settings_profile_url, class: 'icon-button' do
+        = fa_icon 'pencil'
+        = t('settings.edit_profile')
   - elsif !user_signed_in?
     .controls
       .remote-follow
diff --git a/app/workers/after_account_domain_block_worker.rb b/app/workers/after_account_domain_block_worker.rb
new file mode 100644
index 000000000..043c33311
--- /dev/null
+++ b/app/workers/after_account_domain_block_worker.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AfterAccountDomainBlockWorker
+  include Sidekiq::Worker
+
+  def perform(account_id, domain)
+    AfterBlockDomainFromAccountService.new.call(Account.find(account_id), domain)
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+end
diff --git a/app/workers/soft_block_domain_followers_worker.rb b/app/workers/soft_block_domain_followers_worker.rb
deleted file mode 100644
index 85445c7fb..000000000
--- a/app/workers/soft_block_domain_followers_worker.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-class SoftBlockDomainFollowersWorker
-  include Sidekiq::Worker
-
-  sidekiq_options queue: 'pull'
-
-  def perform(account_id, domain)
-    followers_id = Account.find(account_id).followers.where(domain: domain).pluck(:id)
-    SoftBlockWorker.push_bulk(followers_id) do |follower_id|
-      [account_id, follower_id]
-    end
-  end
-end
diff --git a/app/workers/soft_block_worker.rb b/app/workers/soft_block_worker.rb
deleted file mode 100644
index 312d880b9..000000000
--- a/app/workers/soft_block_worker.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-class SoftBlockWorker
-  include Sidekiq::Worker
-
-  sidekiq_options queue: 'pull'
-
-  def perform(account_id, target_account_id)
-    account        = Account.find(account_id)
-    target_account = Account.find(target_account_id)
-
-    BlockService.new.call(account, target_account)
-    UnblockService.new.call(account, target_account)
-  rescue ActiveRecord::RecordNotFound
-    true
-  end
-end