about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.env.nanobox6
-rw-r--r--.ruby-version2
-rw-r--r--.travis.yml2
-rw-r--r--Dockerfile2
-rw-r--r--app/controllers/api/v1/accounts/follower_accounts_controller.rb2
-rw-r--r--app/controllers/api/v1/accounts/following_accounts_controller.rb2
-rw-r--r--app/controllers/api/v1/accounts/statuses_controller.rb2
-rw-r--r--app/controllers/api/v1/blocks_controller.rb2
-rw-r--r--app/controllers/api/v1/domain_blocks_controller.rb2
-rw-r--r--app/controllers/api/v1/favourites_controller.rb2
-rw-r--r--app/controllers/api/v1/follow_requests_controller.rb2
-rw-r--r--app/controllers/api/v1/lists/accounts_controller.rb2
-rw-r--r--app/controllers/api/v1/mutes_controller.rb2
-rw-r--r--app/controllers/api/v1/notifications_controller.rb2
-rw-r--r--app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb2
-rw-r--r--app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb2
-rw-r--r--app/controllers/api/v1/statuses_controller.rb2
-rw-r--r--app/controllers/api/v1/timelines/home_controller.rb2
-rw-r--r--app/controllers/api/v1/timelines/list_controller.rb2
-rw-r--r--app/controllers/api/v1/timelines/public_controller.rb2
-rw-r--r--app/controllers/api/v1/timelines/tag_controller.rb2
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js8
-rw-r--r--app/javascript/mastodon/locales/ar.json2
-rw-r--r--app/javascript/mastodon/locales/bg.json2
-rw-r--r--app/javascript/mastodon/locales/ca.json2
-rw-r--r--app/javascript/mastodon/locales/de.json2
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json39
-rw-r--r--app/javascript/mastodon/locales/en.json2
-rw-r--r--app/javascript/mastodon/locales/eo.json2
-rw-r--r--app/javascript/mastodon/locales/es.json2
-rw-r--r--app/javascript/mastodon/locales/fa.json2
-rw-r--r--app/javascript/mastodon/locales/fi.json2
-rw-r--r--app/javascript/mastodon/locales/fr.json2
-rw-r--r--app/javascript/mastodon/locales/gl.json2
-rw-r--r--app/javascript/mastodon/locales/he.json2
-rw-r--r--app/javascript/mastodon/locales/hr.json2
-rw-r--r--app/javascript/mastodon/locales/hu.json2
-rw-r--r--app/javascript/mastodon/locales/hy.json2
-rw-r--r--app/javascript/mastodon/locales/id.json2
-rw-r--r--app/javascript/mastodon/locales/io.json2
-rw-r--r--app/javascript/mastodon/locales/it.json2
-rw-r--r--app/javascript/mastodon/locales/ja.json2
-rw-r--r--app/javascript/mastodon/locales/ko.json2
-rw-r--r--app/javascript/mastodon/locales/nl.json2
-rw-r--r--app/javascript/mastodon/locales/no.json2
-rw-r--r--app/javascript/mastodon/locales/oc.json2
-rw-r--r--app/javascript/mastodon/locales/pl.json6
-rw-r--r--app/javascript/mastodon/locales/pt-BR.json2
-rw-r--r--app/javascript/mastodon/locales/pt.json2
-rw-r--r--app/javascript/mastodon/locales/ru.json2
-rw-r--r--app/javascript/mastodon/locales/sk.json2
-rw-r--r--app/javascript/mastodon/locales/sr-Latn.json2
-rw-r--r--app/javascript/mastodon/locales/sr.json2
-rw-r--r--app/javascript/mastodon/locales/sv.json2
-rw-r--r--app/javascript/mastodon/locales/th.json2
-rw-r--r--app/javascript/mastodon/locales/tr.json2
-rw-r--r--app/javascript/mastodon/locales/uk.json2
-rw-r--r--app/javascript/mastodon/locales/zh-CN.json2
-rw-r--r--app/javascript/mastodon/locales/zh-HK.json2
-rw-r--r--app/javascript/mastodon/locales/zh-TW.json2
-rw-r--r--app/lib/formatter.rb11
-rw-r--r--app/lib/ostatus/atom_serializer.rb3
-rw-r--r--app/models/account.rb4
-rw-r--r--app/models/remote_profile.rb4
-rw-r--r--app/serializers/activitypub/actor_serializer.rb9
-rw-r--r--app/serializers/rest/account_serializer.rb2
-rw-r--r--app/services/activitypub/fetch_remote_account_service.rb4
-rw-r--r--app/services/activitypub/fetch_remote_key_service.rb4
-rw-r--r--app/services/activitypub/process_account_service.rb28
-rw-r--r--app/services/fetch_atom_service.rb2
-rw-r--r--app/services/resolve_account_service.rb2
-rw-r--r--app/services/resolve_url_service.rb2
-rw-r--r--app/services/update_remote_profile_service.rb21
-rw-r--r--app/views/accounts/_header.html.haml2
-rw-r--r--boxfile.yml31
-rw-r--r--config/locales/activerecord.zh-HK.yml13
-rw-r--r--config/locales/devise.zh-HK.yml21
-rw-r--r--config/locales/doorkeeper.zh-HK.yml6
-rw-r--r--config/locales/simple_form.zh-HK.yml18
-rw-r--r--config/locales/zh-HK.yml351
-rw-r--r--spec/lib/formatter_spec.rb79
82 files changed, 753 insertions, 37 deletions
diff --git a/.env.nanobox b/.env.nanobox
index 0d14f8a00..8e0af6a8a 100644
--- a/.env.nanobox
+++ b/.env.nanobox
@@ -14,9 +14,9 @@ DB_PORT=5432
 DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
 
 # Optional ElasticSearch configuration
-# ES_ENABLED=true
-# ES_HOST=localhost
-# ES_PORT=9200
+ES_ENABLED=true
+ES_HOST=$DATA_ELASTIC_HOST
+ES_PORT=9200
 
 # Optimizations
 LD_PRELOAD=/data/lib/libjemalloc.so
diff --git a/.ruby-version b/.ruby-version
index 437459cd9..73462a5a1 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-2.5.0
+2.5.1
diff --git a/.travis.yml b/.travis.yml
index 96b6bdc1f..3ac83e0e3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -35,7 +35,7 @@ addons:
       - yarn
 
 rvm:
-  - 2.4.2
+  - 2.4.3
   - 2.5.0
 
 services:
diff --git a/Dockerfile b/Dockerfile
index 438cf3ef4..299819e82 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM ruby:2.4.3-alpine3.6
+FROM ruby:2.4.4-alpine3.6
 
 LABEL maintainer="https://github.com/tootsuite/mastodon" \
       description="Your self-hosted, globally interconnected microblogging community"
diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb
index 80b0bef40..c4f600c54 100644
--- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb
+++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb
@@ -63,6 +63,6 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 end
diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb
index 55cffdf37..90b1f7fc5 100644
--- a/app/controllers/api/v1/accounts/following_accounts_controller.rb
+++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb
@@ -63,6 +63,6 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 end
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index 1e1511a7b..cbcc7ef04 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -69,7 +69,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit, :only_media, :exclude_replies).merge(core_params)
+    params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
   end
 
   def insert_pagination_headers
diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb
index 3a6690766..a39701340 100644
--- a/app/controllers/api/v1/blocks_controller.rb
+++ b/app/controllers/api/v1/blocks_controller.rb
@@ -57,6 +57,6 @@ class Api::V1::BlocksController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 end
diff --git a/app/controllers/api/v1/domain_blocks_controller.rb b/app/controllers/api/v1/domain_blocks_controller.rb
index e93dc603b..ae6ad7936 100644
--- a/app/controllers/api/v1/domain_blocks_controller.rb
+++ b/app/controllers/api/v1/domain_blocks_controller.rb
@@ -67,7 +67,7 @@ class Api::V1::DomainBlocksController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 
   def domain_block_params
diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb
index 9d73bb337..b4265ed34 100644
--- a/app/controllers/api/v1/favourites_controller.rb
+++ b/app/controllers/api/v1/favourites_controller.rb
@@ -66,6 +66,6 @@ class Api::V1::FavouritesController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 end
diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb
index b9f50d784..d5c7c565a 100644
--- a/app/controllers/api/v1/follow_requests_controller.rb
+++ b/app/controllers/api/v1/follow_requests_controller.rb
@@ -71,6 +71,6 @@ class Api::V1::FollowRequestsController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 end
diff --git a/app/controllers/api/v1/lists/accounts_controller.rb b/app/controllers/api/v1/lists/accounts_controller.rb
index c29c73b3e..f2bded851 100644
--- a/app/controllers/api/v1/lists/accounts_controller.rb
+++ b/app/controllers/api/v1/lists/accounts_controller.rb
@@ -88,7 +88,7 @@ class Api::V1::Lists::AccountsController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 
   def unlimited?
diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb
index 92ad251ef..ddbf13caa 100644
--- a/app/controllers/api/v1/mutes_controller.rb
+++ b/app/controllers/api/v1/mutes_controller.rb
@@ -76,6 +76,6 @@ class Api::V1::MutesController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 end
diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb
index a949752fb..e58dda77f 100644
--- a/app/controllers/api/v1/notifications_controller.rb
+++ b/app/controllers/api/v1/notifications_controller.rb
@@ -91,6 +91,6 @@ class Api::V1::NotificationsController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit, exclude_types: []).merge(core_params)
+    params.slice(:limit, :exclude_types).permit(:limit, exclude_types: []).merge(core_params)
   end
 end
diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb
index f95cf9457..3fe304153 100644
--- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb
+++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb
@@ -77,6 +77,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 end
diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
index 175217e6e..b065db2c7 100644
--- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
+++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb
@@ -74,6 +74,6 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 end
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 544a4ce21..28c28592a 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -76,7 +76,7 @@ class Api::V1::StatusesController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 
   def authorize_if_got_token
diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb
index bbbcf7f90..cde4e8420 100644
--- a/app/controllers/api/v1/timelines/home_controller.rb
+++ b/app/controllers/api/v1/timelines/home_controller.rb
@@ -43,7 +43,7 @@ class Api::V1::Timelines::HomeController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:local, :limit).merge(core_params)
+    params.slice(:local, :limit).permit(:local, :limit).merge(core_params)
   end
 
   def next_path
diff --git a/app/controllers/api/v1/timelines/list_controller.rb b/app/controllers/api/v1/timelines/list_controller.rb
index f5db71e46..06d596c08 100644
--- a/app/controllers/api/v1/timelines/list_controller.rb
+++ b/app/controllers/api/v1/timelines/list_controller.rb
@@ -45,7 +45,7 @@ class Api::V1::Timelines::ListController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:limit).merge(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
   end
 
   def next_path
diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb
index d7d70b94d..13fe015b7 100644
--- a/app/controllers/api/v1/timelines/public_controller.rb
+++ b/app/controllers/api/v1/timelines/public_controller.rb
@@ -45,7 +45,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:local, :limit, :only_media).merge(core_params)
+    params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params)
   end
 
   def next_path
diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb
index eb32611ad..7de49a5ed 100644
--- a/app/controllers/api/v1/timelines/tag_controller.rb
+++ b/app/controllers/api/v1/timelines/tag_controller.rb
@@ -54,7 +54,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
   end
 
   def pagination_params(core_params)
-    params.permit(:local, :limit, :only_media).merge(core_params)
+    params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params)
   end
 
   def next_path
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index cd59c7845..e036dc1da 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -67,6 +67,8 @@ export default class StatusActionBar extends ImmutablePureComponent {
     navigator.share({
       text: this.props.status.get('search_index'),
       url: this.props.status.get('url'),
+    }).catch((e) => {
+      if (e.name !== 'AbortError') console.error(e);
     });
   }
 
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 663ccfb8e..fe7bb1cb3 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -74,6 +74,14 @@ export default class ComposeForm extends ImmutablePureComponent {
       this.props.onChange(this.autosuggestTextarea.textarea.value);
     }
 
+    // Submit disabled:
+    const { is_submitting, is_uploading, anyMedia } = this.props;
+    const fulltext = [this.props.spoiler_text, countableText(this.props.text)].join('');
+
+    if (is_submitting || is_uploading || length(fulltext) > 500 || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
+      return;
+    }
+
     this.props.onSubmit();
   }
 
diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json
index f9af062d0..3078b5b8c 100644
--- a/app/javascript/mastodon/locales/ar.json
+++ b/app/javascript/mastodon/locales/ar.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "إعادة المحاولة",
   "column.blocks": "الحسابات المحجوبة",
   "column.community": "الخيط العام المحلي",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "المفضلة",
   "column.follow_requests": "طلبات المتابعة",
   "column.home": "الرئيسية",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "هل تود إخفاء الإخطارات القادمة من هذا المستخدم ؟",
   "navigation_bar.blocks": "الحسابات المحجوبة",
   "navigation_bar.community_timeline": "الخيط العام المحلي",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "تعديل الملف الشخصي",
   "navigation_bar.favourites": "المفضلة",
   "navigation_bar.follow_requests": "طلبات المتابعة",
diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json
index 58795ca37..9aaff0ddf 100644
--- a/app/javascript/mastodon/locales/bg.json
+++ b/app/javascript/mastodon/locales/bg.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
   "column.home": "Начало",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Редактирай профил",
   "navigation_bar.favourites": "Favourites",
   "navigation_bar.follow_requests": "Follow requests",
diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json
index b0ce34c6b..ec5a8a1d6 100644
--- a/app/javascript/mastodon/locales/ca.json
+++ b/app/javascript/mastodon/locales/ca.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Torna-ho a provar",
   "column.blocks": "Usuaris blocats",
   "column.community": "Línia de temps local",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorits",
   "column.follow_requests": "Peticions per seguir-te",
   "column.home": "Inici",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Amagar notificacions d'aquest usuari?",
   "navigation_bar.blocks": "Usuaris bloquejats",
   "navigation_bar.community_timeline": "Línia de temps Local",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favorits",
   "navigation_bar.follow_requests": "Sol·licituds de seguiment",
diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index eb0c5056a..a618b853e 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Erneut versuchen",
   "column.blocks": "Blockierte Profile",
   "column.community": "Lokale Zeitleiste",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoriten",
   "column.follow_requests": "Folgeanfragen",
   "column.home": "Startseite",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Benachrichtigungen von diesem Account verbergen?",
   "navigation_bar.blocks": "Blockierte Profile",
   "navigation_bar.community_timeline": "Lokale Zeitleiste",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Profil bearbeiten",
   "navigation_bar.favourites": "Favoriten",
   "navigation_bar.follow_requests": "Folgeanfragen",
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index ac02c6af3..2a89c1153 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -95,6 +95,15 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Unhide {domain}",
+        "id": "account.unblock_domain"
+      }
+    ],
+    "path": "app/javascript/mastodon/components/domain.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Load more",
         "id": "status.load_more"
       }
@@ -301,6 +310,19 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Hide entire domain",
+        "id": "confirmations.domain_block.confirm"
+      },
+      {
+        "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.",
+        "id": "confirmations.domain_block.message"
+      }
+    ],
+    "path": "app/javascript/mastodon/containers/domain_container.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Delete",
         "id": "confirmations.delete.confirm"
       },
@@ -852,6 +874,19 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Hidden domains",
+        "id": "column.domain_blocks"
+      },
+      {
+        "defaultMessage": "Unhide {domain}",
+        "id": "account.unblock_domain"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/domain_blocks/index.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Favourites",
         "id": "column.favourites"
       }
@@ -931,6 +966,10 @@
         "id": "navigation_bar.blocks"
       },
       {
+        "defaultMessage": "Hidden domains",
+        "id": "navigation_bar.domain_blocks"
+      },
+      {
         "defaultMessage": "Muted users",
         "id": "navigation_bar.mutes"
       },
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index da75f5fe2..afc0fce3d 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -41,6 +41,7 @@
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
   "column.direct": "Direct messages",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
   "column.home": "Home",
@@ -159,6 +160,7 @@
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
   "navigation_bar.direct": "Direct messages",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Edit profile",
   "navigation_bar.favourites": "Favourites",
   "navigation_bar.follow_requests": "Follow requests",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 9b00edb00..82b749417 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Bonvolu reprovi",
   "column.blocks": "Blokitaj uzantoj",
   "column.community": "Loka tempolinio",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Stelumoj",
   "column.follow_requests": "Petoj de sekvado",
   "column.home": "Hejmo",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Ĉu kaŝi sciigojn el ĉi tiu uzanto?",
   "navigation_bar.blocks": "Blokitaj uzantoj",
   "navigation_bar.community_timeline": "Loka tempolinio",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Redakti profilon",
   "navigation_bar.favourites": "Stelumoj",
   "navigation_bar.follow_requests": "Petoj de sekvado",
diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json
index 9f03b31c1..6f9c06c5f 100644
--- a/app/javascript/mastodon/locales/es.json
+++ b/app/javascript/mastodon/locales/es.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Inténtalo de nuevo",
   "column.blocks": "Usuarios bloqueados",
   "column.community": "Línea de tiempo local",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Solicitudes de seguimiento",
   "column.home": "Inicio",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Ocultar notificaciones de este usuario?",
   "navigation_bar.blocks": "Usuarios bloqueados",
   "navigation_bar.community_timeline": "Historia local",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritos",
   "navigation_bar.follow_requests": "Solicitudes para seguirte",
diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json
index 9421746b1..4b64ca353 100644
--- a/app/javascript/mastodon/locales/fa.json
+++ b/app/javascript/mastodon/locales/fa.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "تلاش دوباره",
   "column.blocks": "کاربران مسدودشده",
   "column.community": "نوشته‌های محلی",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "پسندیده‌ها",
   "column.follow_requests": "درخواست‌های پیگیری",
   "column.home": "خانه",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "اعلان‌های این کاربر پنهان شود؟",
   "navigation_bar.blocks": "کاربران مسدودشده",
   "navigation_bar.community_timeline": "نوشته‌های محلی",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "ویرایش نمایه",
   "navigation_bar.favourites": "پسندیده‌ها",
   "navigation_bar.follow_requests": "درخواست‌های پیگیری",
diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json
index fce441df4..f4be80514 100644
--- a/app/javascript/mastodon/locales/fi.json
+++ b/app/javascript/mastodon/locales/fi.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Yritä uudestaan",
   "column.blocks": "Estetyt käyttäjät",
   "column.community": "Paikallinen aikajana",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Suosikit",
   "column.follow_requests": "Seurauspyynnöt",
   "column.home": "Koti",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Piilota ilmoitukset tältä käyttäjältä?",
   "navigation_bar.blocks": "Estetyt käyttäjät",
   "navigation_bar.community_timeline": "Paikallinen aikajana",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Muokkaa profiilia",
   "navigation_bar.favourites": "Suosikit",
   "navigation_bar.follow_requests": "Seurauspyynnöt",
diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json
index 6eb34e644..58e6ad54d 100644
--- a/app/javascript/mastodon/locales/fr.json
+++ b/app/javascript/mastodon/locales/fr.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Réessayer",
   "column.blocks": "Comptes bloqués",
   "column.community": "Fil public local",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoris",
   "column.follow_requests": "Demandes de suivi",
   "column.home": "Accueil",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Masquer les notifications de cet utilisateur ?",
   "navigation_bar.blocks": "Comptes bloqués",
   "navigation_bar.community_timeline": "Fil public local",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Modifier le profil",
   "navigation_bar.favourites": "Favoris",
   "navigation_bar.follow_requests": "Demandes de suivi",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index a0823b93f..8d586404d 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Inténteo de novo",
   "column.blocks": "Usuarias bloqueadas",
   "column.community": "Liña temporal local",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoritas",
   "column.follow_requests": "Peticións de seguimento",
   "column.home": "Inicio",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Esconder notificacións deste usuario?",
   "navigation_bar.blocks": "Usuarias bloqueadas",
   "navigation_bar.community_timeline": "Liña temporal local",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritas",
   "navigation_bar.follow_requests": "Peticións de seguimento",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index 0e2ee8da4..6bec26fd8 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "לנסות שוב",
   "column.blocks": "חסימות",
   "column.community": "ציר זמן מקומי",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "חיבובים",
   "column.follow_requests": "בקשות מעקב",
   "column.home": "בבית",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "להסתיר הודעות מחשבון זה?",
   "navigation_bar.blocks": "חסימות",
   "navigation_bar.community_timeline": "ציר זמן מקומי",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "עריכת פרופיל",
   "navigation_bar.favourites": "חיבובים",
   "navigation_bar.follow_requests": "בקשות מעקב",
diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json
index 1e8ce8e29..f7a5d0a3f 100644
--- a/app/javascript/mastodon/locales/hr.json
+++ b/app/javascript/mastodon/locales/hr.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Blokirani korisnici",
   "column.community": "Lokalni timeline",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoriti",
   "column.follow_requests": "Zahtjevi za slijeđenje",
   "column.home": "Dom",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blokirani korisnici",
   "navigation_bar.community_timeline": "Lokalni timeline",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Uredi profil",
   "navigation_bar.favourites": "Favoriti",
   "navigation_bar.follow_requests": "Zahtjevi za slijeđenje",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index deb17c6f4..8b9c14993 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Próbálja újra",
   "column.blocks": "Letiltott felhasználók",
   "column.community": "Helyi idővonal",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Kedvencek",
   "column.follow_requests": "Követési kérések",
   "column.home": "Kezdőlap",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Értesítések elrejtése ezen felhasználótól?",
   "navigation_bar.blocks": "Tiltott felhasználók",
   "navigation_bar.community_timeline": "Helyi idővonal",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Profil szerkesztése",
   "navigation_bar.favourites": "Kedvencek",
   "navigation_bar.follow_requests": "Követési kérések",
diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json
index ee2055397..22ba89a43 100644
--- a/app/javascript/mastodon/locales/hy.json
+++ b/app/javascript/mastodon/locales/hy.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Կրկին փորձել",
   "column.blocks": "Արգելափակված օգտատերեր",
   "column.community": "Տեղական հոսք",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Հավանածներ",
   "column.follow_requests": "Հետեւելու հայցեր",
   "column.home": "Հիմնական",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Թաքցնե՞լ ցանուցումներն այս օգտատիրոջից։",
   "navigation_bar.blocks": "Արգելափակված օգտատերեր",
   "navigation_bar.community_timeline": "Տեղական հոսք",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Խմբագրել անձնական էջը",
   "navigation_bar.favourites": "Հավանածներ",
   "navigation_bar.follow_requests": "Հետեւելու հայցեր",
diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json
index cae3211ee..1ef610fba 100644
--- a/app/javascript/mastodon/locales/id.json
+++ b/app/javascript/mastodon/locales/id.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Coba lagi",
   "column.blocks": "Pengguna diblokir",
   "column.community": "Linimasa Lokal",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorit",
   "column.follow_requests": "Permintaan mengikuti",
   "column.home": "Beranda",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Pengguna diblokir",
   "navigation_bar.community_timeline": "Linimasa lokal",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Ubah profil",
   "navigation_bar.favourites": "Favorit",
   "navigation_bar.follow_requests": "Permintaan mengikuti",
diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json
index 121d745ca..1435178a8 100644
--- a/app/javascript/mastodon/locales/io.json
+++ b/app/javascript/mastodon/locales/io.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Blokusita uzeri",
   "column.community": "Lokala tempolineo",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorati",
   "column.follow_requests": "Demandi di sequado",
   "column.home": "Hemo",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blokusita uzeri",
   "navigation_bar.community_timeline": "Lokala tempolineo",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Modifikar profilo",
   "navigation_bar.favourites": "Favorati",
   "navigation_bar.follow_requests": "Demandi di sequado",
diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json
index 5e57143a9..226127e6b 100644
--- a/app/javascript/mastodon/locales/it.json
+++ b/app/javascript/mastodon/locales/it.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Utenti bloccati",
   "column.community": "Timeline locale",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Apprezzati",
   "column.follow_requests": "Richieste di amicizia",
   "column.home": "Home",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Utenti bloccati",
   "navigation_bar.community_timeline": "Timeline locale",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Modifica profilo",
   "navigation_bar.favourites": "Apprezzati",
   "navigation_bar.follow_requests": "Richieste di amicizia",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index e6f2bbcff..cd3023795 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -41,6 +41,7 @@
   "column.blocks": "ブロックしたユーザー",
   "column.community": "ローカルタイムライン",
   "column.direct": "ダイレクトメッセージ",
+  "column.domain_blocks": "非表示にしたドメイン",
   "column.favourites": "お気に入り",
   "column.follow_requests": "フォローリクエスト",
   "column.home": "ホーム",
@@ -159,6 +160,7 @@
   "navigation_bar.blocks": "ブロックしたユーザー",
   "navigation_bar.community_timeline": "ローカルタイムライン",
   "navigation_bar.direct": "ダイレクトメッセージ",
+  "navigation_bar.domain_blocks": "非表示にしたドメイン",
   "navigation_bar.edit_profile": "プロフィールを編集",
   "navigation_bar.favourites": "お気に入り",
   "navigation_bar.follow_requests": "フォローリクエスト",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index fa15214c9..449df42b8 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "다시 시도",
   "column.blocks": "차단 중인 사용자",
   "column.community": "로컬 타임라인",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "즐겨찾기",
   "column.follow_requests": "팔로우 요청",
   "column.home": "홈",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "이 사용자로부터의 알림을 뮤트하시겠습니까?",
   "navigation_bar.blocks": "차단한 사용자",
   "navigation_bar.community_timeline": "로컬 타임라인",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "프로필 편집",
   "navigation_bar.favourites": "즐겨찾기",
   "navigation_bar.follow_requests": "팔로우 요청",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index ff827991d..7bfb74a5c 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Opnieuw proberen",
   "column.blocks": "Geblokkeerde gebruikers",
   "column.community": "Lokale tijdlijn",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorieten",
   "column.follow_requests": "Volgverzoeken",
   "column.home": "Start",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Verberg meldingen van deze persoon?",
   "navigation_bar.blocks": "Geblokkeerde gebruikers",
   "navigation_bar.community_timeline": "Lokale tijdlijn",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Profiel bewerken",
   "navigation_bar.favourites": "Favorieten",
   "navigation_bar.follow_requests": "Volgverzoeken",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index d3bc75708..b79277ce3 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Prøv igjen",
   "column.blocks": "Blokkerte brukere",
   "column.community": "Lokal tidslinje",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Likt",
   "column.follow_requests": "Følgeforespørsler",
   "column.home": "Hjem",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Skjul varslinger fra denne brukeren?",
   "navigation_bar.blocks": "Blokkerte brukere",
   "navigation_bar.community_timeline": "Lokal tidslinje",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Rediger profil",
   "navigation_bar.favourites": "Favoritter",
   "navigation_bar.follow_requests": "Følgeforespørsler",
diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json
index 39ba31de3..5b12f8811 100644
--- a/app/javascript/mastodon/locales/oc.json
+++ b/app/javascript/mastodon/locales/oc.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Tornar ensajar",
   "column.blocks": "Personas blocadas",
   "column.community": "Flux public local",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favorits",
   "column.follow_requests": "Demandas d’abonament",
   "column.home": "Acuèlh",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?",
   "navigation_bar.blocks": "Personas blocadas",
   "navigation_bar.community_timeline": "Flux public local",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Modificar lo perfil",
   "navigation_bar.favourites": "Favorits",
   "navigation_bar.follow_requests": "Demandas d’abonament",
diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json
index fa25192e6..c0877262f 100644
--- a/app/javascript/mastodon/locales/pl.json
+++ b/app/javascript/mastodon/locales/pl.json
@@ -18,7 +18,7 @@
   "account.mute_notifications": "Wycisz powiadomienia o @{name}",
   "account.muted": "Wyciszony",
   "account.posts": "Wpisy",
-  "account.posts_with_replies": "Wpisy z odpowiedziami",
+  "account.posts_with_replies": "Wpisy i odpowiedzi",
   "account.report": "Zgłoś @{name}",
   "account.requested": "Oczekująca prośba, kliknij aby anulować",
   "account.share": "Udostępnij profil @{name}",
@@ -41,6 +41,7 @@
   "column.blocks": "Zablokowani użytkownicy",
   "column.community": "Lokalna oś czasu",
   "column.direct": "Wiadomości bezpośrednie",
+  "column.domain_blocks": "Ukryte domeny",
   "column.favourites": "Ulubione",
   "column.follow_requests": "Prośby o śledzenie",
   "column.home": "Strona główna",
@@ -61,7 +62,7 @@
   "column_subheading.lists": "Listy",
   "column_subheading.navigation": "Nawigacja",
   "column_subheading.settings": "Ustawienia",
-  "compose_form.direct_message_warning": "This toot will only be visible to all the mentioned users.",
+  "compose_form.direct_message_warning": "Ten wpis będzie widoczny tylko dla wszystkich wspomnianych użytkowników.",
   "compose_form.hashtag_warning": "Ten wpis nie będzie widoczny pod podanymi hashtagami, ponieważ jest oznaczony jako niewidoczny. Tylko publiczne wpisy mogą zostać znalezione z użyciem hashtagów.",
   "compose_form.lock_disclaimer": "Twoje konto nie jest {locked}. Każdy, kto Cię śledzi, może wyświetlać Twoje wpisy przeznaczone tylko dla śledzących.",
   "compose_form.lock_disclaimer.lock": "zablokowane",
@@ -159,6 +160,7 @@
   "navigation_bar.blocks": "Zablokowani użytkownicy",
   "navigation_bar.community_timeline": "Lokalna oś czasu",
   "navigation_bar.direct": "Wiadomości bezpośrednie",
+  "navigation_bar.domain_blocks": "Ukryte domeny",
   "navigation_bar.edit_profile": "Edytuj profil",
   "navigation_bar.favourites": "Ulubione",
   "navigation_bar.follow_requests": "Prośby o śledzenie",
diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json
index 3d42eedb3..b056ec8bd 100644
--- a/app/javascript/mastodon/locales/pt-BR.json
+++ b/app/javascript/mastodon/locales/pt-BR.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Tente novamente",
   "column.blocks": "Usuários bloqueados",
   "column.community": "Local",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Seguidores pendentes",
   "column.home": "Página inicial",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Esconder notificações deste usuário?",
   "navigation_bar.blocks": "Usuários bloqueados",
   "navigation_bar.community_timeline": "Local",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritos",
   "navigation_bar.follow_requests": "Seguidores pendentes",
diff --git a/app/javascript/mastodon/locales/pt.json b/app/javascript/mastodon/locales/pt.json
index 5c93614a9..65983000c 100644
--- a/app/javascript/mastodon/locales/pt.json
+++ b/app/javascript/mastodon/locales/pt.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Tente de novo",
   "column.blocks": "Utilizadores Bloqueados",
   "column.community": "Local",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoritos",
   "column.follow_requests": "Seguidores Pendentes",
   "column.home": "Início",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Esconder notificações deste utilizador?",
   "navigation_bar.blocks": "Utilizadores bloqueados",
   "navigation_bar.community_timeline": "Local",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Editar perfil",
   "navigation_bar.favourites": "Favoritos",
   "navigation_bar.follow_requests": "Seguidores pendentes",
diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json
index 7dffbb210..15959092c 100644
--- a/app/javascript/mastodon/locales/ru.json
+++ b/app/javascript/mastodon/locales/ru.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Попробовать снова",
   "column.blocks": "Список блокировки",
   "column.community": "Локальная лента",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Понравившееся",
   "column.follow_requests": "Запросы на подписку",
   "column.home": "Главная",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Убрать уведомления от этого пользователя?",
   "navigation_bar.blocks": "Список блокировки",
   "navigation_bar.community_timeline": "Локальная лента",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Изменить профиль",
   "navigation_bar.favourites": "Понравившееся",
   "navigation_bar.follow_requests": "Запросы на подписку",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index 0a248d261..7bfae0302 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Skúsiť znova",
   "column.blocks": "Blokovaní užívatelia",
   "column.community": "Lokálna časová os",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Obľúbené",
   "column.follow_requests": "Žiadosti o sledovaní",
   "column.home": "Domov",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Skryť notifikácie od tohoto užívateľa?",
   "navigation_bar.blocks": "Blokovaní užívatelia",
   "navigation_bar.community_timeline": "Lokálna časová os",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Upraviť profil",
   "navigation_bar.favourites": "Obľúbené",
   "navigation_bar.follow_requests": "Žiadosti o sledovanie",
diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json
index b9effce96..8fae49a03 100644
--- a/app/javascript/mastodon/locales/sr-Latn.json
+++ b/app/javascript/mastodon/locales/sr-Latn.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Pokušajte ponovo",
   "column.blocks": "Blokirani korisnici",
   "column.community": "Lokalna lajna",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Omiljeni",
   "column.follow_requests": "Zahtevi za praćenje",
   "column.home": "Početna",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Sakrij obaveštenja od ovog korisnika?",
   "navigation_bar.blocks": "Blokirani korisnici",
   "navigation_bar.community_timeline": "Lokalna lajna",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Izmeni profil",
   "navigation_bar.favourites": "Omiljeni",
   "navigation_bar.follow_requests": "Zahtevi za praćenje",
diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json
index a6c5f220e..a39fea582 100644
--- a/app/javascript/mastodon/locales/sr.json
+++ b/app/javascript/mastodon/locales/sr.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Покушајте поново",
   "column.blocks": "Блокирани корисници",
   "column.community": "Локална лајна",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Омиљени",
   "column.follow_requests": "Захтеви за праћење",
   "column.home": "Почетна",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Сакриј обавештења од овог корисника?",
   "navigation_bar.blocks": "Блокирани корисници",
   "navigation_bar.community_timeline": "Локална лајна",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Измени профил",
   "navigation_bar.favourites": "Омиљени",
   "navigation_bar.follow_requests": "Захтеви за праћење",
diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json
index 6dc3d7a98..014492e19 100644
--- a/app/javascript/mastodon/locales/sv.json
+++ b/app/javascript/mastodon/locales/sv.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Försök igen",
   "column.blocks": "Blockerade användare",
   "column.community": "Lokal tidslinje",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoriter",
   "column.follow_requests": "Följ förfrågningar",
   "column.home": "Hem",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Dölj notifikationer från denna användare?",
   "navigation_bar.blocks": "Blockerade användare",
   "navigation_bar.community_timeline": "Lokal tidslinje",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Redigera profil",
   "navigation_bar.favourites": "Favoriter",
   "navigation_bar.follow_requests": "Följförfrågningar",
diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json
index 4de354007..ecfe7c9b5 100644
--- a/app/javascript/mastodon/locales/th.json
+++ b/app/javascript/mastodon/locales/th.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Blocked users",
   "column.community": "Local timeline",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favourites",
   "column.follow_requests": "Follow requests",
   "column.home": "Home",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Blocked users",
   "navigation_bar.community_timeline": "Local timeline",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Edit profile",
   "navigation_bar.favourites": "Favourites",
   "navigation_bar.follow_requests": "Follow requests",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 9d0affea4..58d0e5785 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Engellenen kullanıcılar",
   "column.community": "Yerel zaman tüneli",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Favoriler",
   "column.follow_requests": "Takip istekleri",
   "column.home": "Anasayfa",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Engellenen kullanıcılar",
   "navigation_bar.community_timeline": "Yerel zaman tüneli",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Profili düzenle",
   "navigation_bar.favourites": "Favoriler",
   "navigation_bar.follow_requests": "Takip istekleri",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index c49d3c7ae..63866d339 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "Try again",
   "column.blocks": "Заблоковані користувачі",
   "column.community": "Локальна стрічка",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "Вподобане",
   "column.follow_requests": "Запити на підписку",
   "column.home": "Головна",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "Заблоковані користувачі",
   "navigation_bar.community_timeline": "Локальна стрічка",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "Редагувати профіль",
   "navigation_bar.favourites": "Вподобане",
   "navigation_bar.follow_requests": "Запити на підписку",
diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json
index e95cf81f4..f7cb49632 100644
--- a/app/javascript/mastodon/locales/zh-CN.json
+++ b/app/javascript/mastodon/locales/zh-CN.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "重试",
   "column.blocks": "屏蔽用户",
   "column.community": "本站时间轴",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "收藏过的嘟文",
   "column.follow_requests": "关注请求",
   "column.home": "主页",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "同时隐藏来自这个用户的通知",
   "navigation_bar.blocks": "被屏蔽的用户",
   "navigation_bar.community_timeline": "本站时间轴",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "修改个人资料",
   "navigation_bar.favourites": "收藏的内容",
   "navigation_bar.follow_requests": "关注请求",
diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json
index 1801c838d..0504a8c7a 100644
--- a/app/javascript/mastodon/locales/zh-HK.json
+++ b/app/javascript/mastodon/locales/zh-HK.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "重試",
   "column.blocks": "封鎖用戶",
   "column.community": "本站時間軸",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "最愛的文章",
   "column.follow_requests": "關注請求",
   "column.home": "主頁",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "被你封鎖的用戶",
   "navigation_bar.community_timeline": "本站時間軸",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "修改個人資料",
   "navigation_bar.favourites": "最愛的內容",
   "navigation_bar.follow_requests": "關注請求",
diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json
index acbe6eb8e..fab7ecf06 100644
--- a/app/javascript/mastodon/locales/zh-TW.json
+++ b/app/javascript/mastodon/locales/zh-TW.json
@@ -40,6 +40,7 @@
   "bundle_modal_error.retry": "重試",
   "column.blocks": "封鎖的使用者",
   "column.community": "本地時間軸",
+  "column.domain_blocks": "Hidden domains",
   "column.favourites": "最愛",
   "column.follow_requests": "關注請求",
   "column.home": "家",
@@ -153,6 +154,7 @@
   "mute_modal.hide_notifications": "Hide notifications from this user?",
   "navigation_bar.blocks": "封鎖的使用者",
   "navigation_bar.community_timeline": "本地時間軸",
+  "navigation_bar.domain_blocks": "Hidden domains",
   "navigation_bar.edit_profile": "編輯用者資訊",
   "navigation_bar.favourites": "最愛",
   "navigation_bar.follow_requests": "關注請求",
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index 1df4ff8d4..f7e7a3c23 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -51,9 +51,14 @@ class Formatter
     strip_tags(text)
   end
 
-  def simplified_format(account)
-    return reformat(account.note).html_safe unless account.local? # rubocop:disable Rails/OutputSafety
-    linkify(account.note)
+  def simplified_format(account, **options)
+    html = if account.local?
+             linkify(account.note)
+           else
+             reformat(account.note)
+           end
+    html = encode_custom_emojis(html, CustomEmoji.from_text(account.note, account.domain)) if options[:custom_emojify]
+    html.html_safe # rubocop:disable Rails/OutputSafety
   end
 
   def sanitize(html, config)
diff --git a/app/lib/ostatus/atom_serializer.rb b/app/lib/ostatus/atom_serializer.rb
index 46d0a8b37..055b4649c 100644
--- a/app/lib/ostatus/atom_serializer.rb
+++ b/app/lib/ostatus/atom_serializer.rb
@@ -26,6 +26,9 @@ class OStatus::AtomSerializer
     append_element(author, 'link', nil, rel: :alternate, type: 'text/html', href: ::TagManager.instance.url_for(account))
     append_element(author, 'link', nil, rel: :avatar, type: account.avatar_content_type, 'media:width': 120, 'media:height': 120, href: full_asset_url(account.avatar.url(:original))) if account.avatar?
     append_element(author, 'link', nil, rel: :header, type: account.header_content_type, 'media:width': 700, 'media:height': 335, href: full_asset_url(account.header.url(:original))) if account.header?
+    account.emojis.each do |emoji|
+      append_element(author, 'link', nil, rel: :emoji, href: full_asset_url(emoji.image.url), name: emoji.shortcode)
+    end
     append_element(author, 'poco:preferredUsername', account.username)
     append_element(author, 'poco:displayName', account.display_name) if account.display_name?
     append_element(author, 'poco:note', account.local? ? account.note : strip_tags(account.note)) if account.note?
diff --git a/app/models/account.rb b/app/models/account.rb
index 95ecc3c1c..4b73f1fa3 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -352,6 +352,10 @@ class Account < ApplicationRecord
     end
   end
 
+  def emojis
+    CustomEmoji.from_text(note, domain)
+  end
+
   before_create :generate_keys
   before_validation :normalize_domain
   before_validation :prepare_contents, if: :local?
diff --git a/app/models/remote_profile.rb b/app/models/remote_profile.rb
index 613911c57..742d2b56f 100644
--- a/app/models/remote_profile.rb
+++ b/app/models/remote_profile.rb
@@ -41,6 +41,10 @@ class RemoteProfile
     @header ||= link_href_from_xml(author, 'header')
   end
 
+  def emojis
+    @emojis ||= author.xpath('./xmlns:link[@rel="emoji"]', xmlns: OStatus::TagManager::XMLNS)
+  end
+
   def locked?
     scope == 'private'
   end
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index afcd37771..df3090726 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -10,6 +10,8 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
 
   has_one :public_key, serializer: ActivityPub::PublicKeySerializer
 
+  has_many :virtual_tags, key: :tag
+
   attribute :moved_to, if: :moved?
 
   class EndpointsSerializer < ActiveModel::Serializer
@@ -101,7 +103,14 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer
     object.locked
   end
 
+  def virtual_tags
+    object.emojis
+  end
+
   def moved_to
     ActivityPub::TagManager.instance.uri_for(object.moved_to_account)
   end
+
+  class CustomEmojiSerializer < ActivityPub::EmojiSerializer
+  end
 end
diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb
index 19b746520..6097acda5 100644
--- a/app/serializers/rest/account_serializer.rb
+++ b/app/serializers/rest/account_serializer.rb
@@ -14,7 +14,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
   end
 
   def note
-    Formatter.instance.simplified_format(object)
+    Formatter.instance.simplified_format(object, custom_emojify: true)
   end
 
   def url
diff --git a/app/services/activitypub/fetch_remote_account_service.rb b/app/services/activitypub/fetch_remote_account_service.rb
index d6ba625a9..5024853ca 100644
--- a/app/services/activitypub/fetch_remote_account_service.rb
+++ b/app/services/activitypub/fetch_remote_account_service.rb
@@ -3,6 +3,8 @@
 class ActivityPub::FetchRemoteAccountService < BaseService
   include JsonLdHelper
 
+  SUPPORTED_TYPES = %w(Application Group Organization Person Service).freeze
+
   # Should be called when uri has already been checked for locality
   # Does a WebFinger roundtrip on each call
   def call(uri, id: true, prefetched_body: nil)
@@ -54,6 +56,6 @@ class ActivityPub::FetchRemoteAccountService < BaseService
   end
 
   def expected_type?
-    @json['type'] == 'Person'
+    SUPPORTED_TYPES.include?(@json['type'])
   end
 end
diff --git a/app/services/activitypub/fetch_remote_key_service.rb b/app/services/activitypub/fetch_remote_key_service.rb
index ce1048fee..41837d462 100644
--- a/app/services/activitypub/fetch_remote_key_service.rb
+++ b/app/services/activitypub/fetch_remote_key_service.rb
@@ -43,7 +43,7 @@ class ActivityPub::FetchRemoteKeyService < BaseService
   end
 
   def person?
-    @json['type'] == 'Person'
+    ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(@json['type'])
   end
 
   def public_key?
@@ -55,6 +55,6 @@ class ActivityPub::FetchRemoteKeyService < BaseService
   end
 
   def confirmed_owner?
-    @owner['type'] == 'Person' && value_or_id(@owner['publicKey']) == @json['id']
+    ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(@owner['type']) && value_or_id(@owner['publicKey']) == @json['id']
   end
 end
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 7d8dc1369..cf8462821 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -22,6 +22,7 @@ class ActivityPub::ProcessAccountService < BaseService
 
         create_account if @account.nil?
         update_account
+        process_tags(@account)
       end
     end
 
@@ -187,4 +188,31 @@ class ActivityPub::ProcessAccountService < BaseService
   def lock_options
     { redis: Redis.current, key: "process_account:#{@uri}" }
   end
+
+  def process_tags(account)
+    return if @json['tag'].blank?
+    as_array(@json['tag']).each do |tag|
+      case tag['type']
+      when 'Emoji'
+        process_emoji tag, account
+      end
+    end
+  end
+
+  def process_emoji(tag, _account)
+    return if skip_download?
+    return if tag['name'].blank? || tag['icon'].blank? || tag['icon']['url'].blank?
+
+    shortcode = tag['name'].delete(':')
+    image_url = tag['icon']['url']
+    uri       = tag['id']
+    updated   = tag['updated']
+    emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: @account.domain)
+
+    return unless emoji.nil? || emoji.updated_at >= updated
+
+    emoji ||= CustomEmoji.new(domain: @account.domain, shortcode: shortcode, uri: uri)
+    emoji.image_remote_url = image_url
+    emoji.save
+  end
 end
diff --git a/app/services/fetch_atom_service.rb b/app/services/fetch_atom_service.rb
index 87076cc07..0444baf74 100644
--- a/app/services/fetch_atom_service.rb
+++ b/app/services/fetch_atom_service.rb
@@ -42,7 +42,7 @@ class FetchAtomService < BaseService
     elsif ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].include?(response.mime_type)
       body = response.body_with_limit
       json = body_to_json(body)
-      if supported_context?(json) && json['type'] == 'Person' && json['inbox'].present?
+      if supported_context?(json) && ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(json['type']) && json['inbox'].present?
         [json['id'], { prefetched_body: body, id: true }, :activitypub]
       elsif supported_context?(json) && expected_type?(json)
         [json['id'], { prefetched_body: body, id: true }, :activitypub]
diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb
index 744ea24f4..8cba88f01 100644
--- a/app/services/resolve_account_service.rb
+++ b/app/services/resolve_account_service.rb
@@ -189,7 +189,7 @@ class ResolveAccountService < BaseService
     return @actor_json if defined?(@actor_json)
 
     json        = fetch_resource(actor_url, false)
-    @actor_json = supported_context?(json) && json['type'] == 'Person' ? json : nil
+    @actor_json = supported_context?(json) && ActivityPub::FetchRemoteAccountService::SUPPORTED_TYPES.include?(json['type']) ? json : nil
   end
 
   def atom
diff --git a/app/services/resolve_url_service.rb b/app/services/resolve_url_service.rb
index 9499dc286..c19b568cb 100644
--- a/app/services/resolve_url_service.rb
+++ b/app/services/resolve_url_service.rb
@@ -17,7 +17,7 @@ class ResolveURLService < BaseService
 
   def process_url
     case type
-    when 'Person'
+    when 'Application', 'Group', 'Organization', 'Person', 'Service'
       FetchRemoteAccountService.new.call(atom_url, body, protocol)
     when 'Note', 'Article', 'Image', 'Video'
       FetchRemoteStatusService.new.call(atom_url, body, protocol)
diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb
index 49a907682..aca1185de 100644
--- a/app/services/update_remote_profile_service.rb
+++ b/app/services/update_remote_profile_service.rb
@@ -40,6 +40,27 @@ class UpdateRemoteProfileService < BaseService
         account.header_remote_url = ''
         account.header.destroy
       end
+
+      save_emojis(account) if remote_profile.emojis.present?
+    end
+  end
+
+  def save_emojis(parent)
+    do_not_download = DomainBlock.find_by(domain: parent.account.domain)&.reject_media?
+
+    return if do_not_download
+
+    remote_account.emojis.each do |link|
+      next unless link['href'] && link['name']
+
+      shortcode = link['name'].delete(':')
+      emoji     = CustomEmoji.find_by(shortcode: shortcode, domain: parent.account.domain)
+
+      next unless emoji.nil?
+
+      emoji = CustomEmoji.new(shortcode: shortcode, domain: parent.account.domain)
+      emoji.image_remote_url = link['href']
+      emoji.save
     end
   end
 end
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
index 74251b923..73af27a4c 100644
--- a/app/views/accounts/_header.html.haml
+++ b/app/views/accounts/_header.html.haml
@@ -1,4 +1,4 @@
-- processed_bio = FrontmatterHandler.instance.process_bio Formatter.instance.simplified_format account
+- processed_bio = FrontmatterHandler.instance.process_bio Formatter.instance.simplified_format(account, custom_emojify: true)
 .card.h-card.p-author{ style: "background-image: url(#{account.header.url(:original)})" }
   .card__illustration
     = render 'accounts/follow_button', account: account
diff --git a/boxfile.yml b/boxfile.yml
index aa2003a1b..9368a7d9d 100644
--- a/boxfile.yml
+++ b/boxfile.yml
@@ -61,6 +61,11 @@ deploy.config:
   before_live:
     web.web:
       - bundle exec rake db:migrate:setup
+      - |-
+          if [[ "${ES_ENABLED}" != "false" ]]
+          then
+            bundle exec rake chewy:deploy
+          fi
 
 
 web.web:
@@ -208,6 +213,32 @@ data.db:
         done
 
 
+data.elastic:
+  image: nanobox/elasticsearch:5
+
+  cron:
+    - id: backup
+      schedule: '0 3 * * *'
+      command: |
+        id=$(cat /proc/sys/kernel/random/uuid)
+        curl -X PUT -H "Content-Type: application/json" "127.0.0.1:9200/_snapshot/${id}" -d "{\"type\": \"fs\",\"settings\": {\"location\": \"/var/tmp/${id}\",\"compress\": true}}"
+        curl -X PUT -H "Content-Type: application/json" "127.0.0.1:9200/_snapshot/${id}/backup?wait_for_completion=true&pretty"
+        tar -cz -C "/var/tmp/${id}" . |
+        curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/backup-${HOSTNAME}-$(date -u +%Y-%m-%d.%H-%M-%S).tgz -X POST -T - >&2
+        curl -X DELETE -H "Content-Type: application/json" "127.0.0.1:9200/_snapshot/${id}"
+        rm -rf "/var/tmp/${id}"
+        curl -k -s -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/ |
+        sed 's/,/\n/g' |
+        grep ${HOSTNAME} |
+        sort |
+        head -n-${BACKUP_COUNT:-1} |
+        sed 's/.*: \?"\(.*\)".*/\1/' |
+        while read file
+        do
+          curl -k -H "X-AUTH-TOKEN: ${WAREHOUSE_DATA_HOARDER_TOKEN}" https://${WAREHOUSE_DATA_HOARDER_HOST}:7410/blobs/${file} -X DELETE
+        done
+
+
 data.redis:
   image: nanobox/redis:4.0
 
diff --git a/config/locales/activerecord.zh-HK.yml b/config/locales/activerecord.zh-HK.yml
new file mode 100644
index 000000000..2ebf9586c
--- /dev/null
+++ b/config/locales/activerecord.zh-HK.yml
@@ -0,0 +1,13 @@
+---
+zh-HK:
+  activerecord:
+    errors:
+      models:
+        account:
+          attributes:
+            username:
+              invalid: 只能使用字母、數字和下劃線
+        status:
+          attributes:
+            reblog:
+              taken: 已經被轉推過
diff --git a/config/locales/devise.zh-HK.yml b/config/locales/devise.zh-HK.yml
index a3e5980d5..12d6e3c2c 100644
--- a/config/locales/devise.zh-HK.yml
+++ b/config/locales/devise.zh-HK.yml
@@ -17,11 +17,32 @@ zh-HK:
       unconfirmed: 你必須先確認電郵地址,繼續使用。
     mailer:
       confirmation_instructions:
+        action: 驗證電子郵件地址
+        explanation: 你在 %{host} 上使用這個電子郵件地址建立了一個帳戶。只需點擊下面的連結,即可啟用帳戶。如果你並沒有建立過帳戶,請忽略此郵件。
+        extra_html: 請記得閱讀本服務站的<a href="%{terms_path}">相關規定</a>和<a href="%{policy_path}">使用條款</a>。
         subject: 'Mastodon: 確認電郵地址'
+        title: 驗證電子郵件地址
+      email_changed:
+        explanation: 你的帳戶的電子郵件地址即將變更為:
+        extra: 如果你沒有請求更改你的電子郵件地址,則他人很有可能已經入侵你的帳戶。請立即更改你的密碼;如果你已經無法訪問你的帳戶,請聯繫服務站的管理員請求協助。
+        subject: Mastodon:電子郵件地址已被更改
+        title: 新電子郵件地址
       password_change:
+        explanation: 你的帳戶的密碼已被更改。
+        extra: 如果你沒有請求更改你的密碼,則他人很有可能已經入侵你的帳戶。請立即更改你的密碼;如果你已經無法訪問你的帳戶,請聯繫服務站的管理員請求協助。
         subject: 'Mastodon: 更改密碼'
+        title: 密碼已被重設
+      reconfirmation_instructions:
+        explanation: 點擊下面的連結來確認你的新電子郵件地址。
+        extra: 如果你沒有請求本次變更,請忽略此郵件。 Mastodon 帳戶的電子郵件地址只有在你點擊上面的連結後才會更改。
+        subject: Mastodon:確認 %{instance} 電子郵件地址
+        title: 驗證電子郵件地址
       reset_password_instructions:
+        action: 更改密碼
+        explanation: 點擊下面的連結來更改帳戶的密碼。
+        extra: 如果你沒有請求本次變更,請忽略此郵件。你的密碼只有在你點擊上面的連結並輸入新密碼後才會更改。
         subject: 'Mastodon: 重設密碼'
+        title: 重設密碼
       unlock_instructions:
         subject: 'Mastodon: 解除用戶鎖定'
     omniauth_callbacks:
diff --git a/config/locales/doorkeeper.zh-HK.yml b/config/locales/doorkeeper.zh-HK.yml
index c8edc2b72..747108762 100644
--- a/config/locales/doorkeeper.zh-HK.yml
+++ b/config/locales/doorkeeper.zh-HK.yml
@@ -5,6 +5,8 @@ zh-HK:
       doorkeeper/application:
         name: 名稱
         redirect_uri: 轉接 URI
+        scopes: 權限範圍
+        website: 應用網站
     errors:
       models:
         doorkeeper/application:
@@ -33,9 +35,13 @@ zh-HK:
         redirect_uri: 每行輸入一個 URI
         scopes: 請用半形空格分開權限範圍 (scope)。留空表示使用預設的權限範圍
       index:
+        application: 應用
         callback_url: 回傳網址
+        delete: 刪除
         name: 名稱
         new: 新增應用程式
+        scopes: 權限範圍
+        show: 顯示
         title: 你的應用程式
       new:
         title: 新增應用程式
diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml
index 1e545da88..01ba61fdf 100644
--- a/config/locales/simple_form.zh-HK.yml
+++ b/config/locales/simple_form.zh-HK.yml
@@ -4,12 +4,17 @@ zh-HK:
     hints:
       defaults:
         avatar: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 400x400px
+        digest: 僅在你長時間未登錄,且收到了私信時發送
         display_name: 最多 30 個字元
         header: 支援 PNG, GIF 或 JPG 圖片,檔案最大為 2MB,會縮裁成 700x335px
         locked: 你必須人手核准每個用戶對你的關注請求,而你的文章私隱會被預設為「只有關注你的人能看」
         note: 最多 160 個字元
+        setting_noindex: 此設定會影響到你的公開個人資料以及文章頁面
+        setting_theme: 此設置會影響到你從任意設備登入時 Mastodon 的顯示樣式
       imports:
         data: 自其他服務站匯出的 CSV 檔案
+      sessions:
+        otp: 輸入你手機上生成的雙重認證碼,或者任意一個恢復代碼。
       user:
         filtered_languages: 下面被選擇的語言的文章將不會出現在你的公共時間軸上。
     labels:
@@ -21,22 +26,33 @@ zh-HK:
         data: 資料
         display_name: 顯示名稱
         email: 電郵地址
-        filtered_languages: 封鎖下面语言的文章
+        expires_in: 失效時間
+        filtered_languages: 封鎖下面語言的文章
         header: 個人頁面頂部
         locale: 語言
         locked: 將用戶轉為「私人」
+        max_uses: 最大使用次數
         new_password: 新密碼
         note: 簡介
         otp_attempt: 雙重認證碼
         password: 密碼
+        setting_auto_play_gif: 自動播放 GIF
         setting_boost_modal: 在轉推前詢問我
         setting_default_privacy: 文章預設為
+        setting_default_sensitive: 預設我的內容為敏感內容
+        setting_delete_modal: 刪推前詢問我
+        setting_noindex: 阻止搜尋引擎檢索
+        setting_reduce_motion: 減低動畫效果
+        setting_system_font_ui: 使用系統預設字型
+        setting_theme: 網站主題
+        setting_unfollow_modal: 取消關注前跳出詢問我
         severity: 等級
         type: 匯入資料類型
         username: 用戶名稱
       interactions:
         must_be_follower: 隱藏沒有關注你的用戶的通知
         must_be_following: 隱藏你不關注的用戶的通知
+        must_be_following_dm: 隱藏你不關注的用戶的私信
       notification_emails:
         digest: 定期電郵摘要
         favourite: 當有用戶喜歡你的文章時,發電郵通知
diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml
index e7ab347a1..cc1cade1e 100644
--- a/config/locales/zh-HK.yml
+++ b/config/locales/zh-HK.yml
@@ -1,53 +1,98 @@
 ---
 zh-HK:
   about:
+    about_hashtag_html: 這些是包含「<strong>#%{hashtag}</strong>」標籤的公開文章。只要你有任何 Mastodon 服務站、或者聯盟網站的用戶,便可以與他們互動。
     about_mastodon_html: Mastodon(萬象)是<em>自由、開源</em>的社交網絡。服務站<em>各自獨立而互連</em>,避免單一商業機構壟斷。找你所信任的服務站,建立帳號,你即可與任何服務站上的用戶溝通,享受無縫的<em>網絡交流</em>。
     about_this: 關於本服務站
     closed_registrations: 本服務站暫時停止接受登記。
     contact: 聯絡
+    contact_missing: 未設定
+    contact_unavailable: 未公開
     description_headline: 關於 %{domain}
     domain_count_after: 個其他服務站
     domain_count_before: 已連接至
+    extended_description_html: |
+      <h3>這裡可以寫一些網站規則</h3>
+      <p>本站未有詳細介紹</p>
+    features:
+      humane_approach_body: Mastodon 從其他網絡的失敗經驗中汲取教訓,以合乎道德的設計對抗社交媒體的濫用問題。
+      humane_approach_title: 以人為本
+      not_a_product_body: Mastodon 不是商業網絡。沒有廣告,沒有數據挖掘,也沒有中央機構操控。平台完全開放。
+      not_a_product_title: 你是用戶,不是商品
+      real_conversation_body: Mastodon 的字數限制高達 500 字,並支援仔細的媒體警告選項,令你暢所欲言。
+      real_conversation_title: 為真正的交流而生
+      within_reach_body: 簡易的 API 系統,令用戶可以透過不同的 iOS、Android 及其他平台的應用軟件,與朋友保持聯繫。
+      within_reach_title: 無處不在
+    generic_description: "%{domain} 是 Mastodon 網絡中其中一個服務站"
+    hosted_on: 在 %{domain} 運作的 Mastodon 服務站
+    learn_more: 了解更多
     other_instances: 其他服務站
     source_code: 源代碼
     status_count_after: 篇文章
     status_count_before: 他們共發佈了
     user_count_after: 位使用者
     user_count_before: 這裏共註冊有
+    what_is_mastodon: Mastodon 是甚麼?
   accounts:
     follow: 關注
     followers: 關注者
     following: 正在關注
+    media: 媒體
+    moved_html: "%{name} 已經轉移到 %{new_profile_link}:"
     nothing_here: 暫時未有內容可以顯示
     people_followed_by: "%{name} 關注的人"
     people_who_follow: 關注 %{name} 的人
     posts: 文章
+    posts_with_replies: 文章和回覆
     remote_follow: 跨站關注
+    reserved_username: 此用戶名已被保留
+    roles:
+      admin: 管理員
+      moderator: 監察员
     unfollow: 取消關注
   admin:
+    account_moderation_notes:
+      account: 管理員
+      create: 新增
+      created_at: 日期
+      created_msg: 管理記錄已新增
+      delete: 刪除
+      destroyed_msg: 管理記錄已被刪除
     accounts:
       are_you_sure: 你確定嗎?
+      by_domain: 域名
       confirm: 確定
       confirmed: 已確定
+      demote: 降任
+      disable: 停用
       disable_two_factor_authentication: 停用雙重認證
+      disabled: 已停用
       display_name: 顯示名稱
       domain: 域名
       edit: 編輯
       email: 電郵地址
+      enable: 啟用
+      enabled: 已啟用
       feed_url: Feed URL
       followers: 關注者
+      followers_url: 關注者(Followers)URL
       follows: 正在關注
+      inbox_url: 收件箱(Inbox)URL
+      ip: IP 位域
       location:
         all: 全部
         local: 本地
         remote: 遠端
         title: 地點
+      login_status: 登入狀態
       media_attachments: 媒體檔案
+      memorialize: 設定為追悼帳戶
       moderation:
         all: 全部
         silenced: 被靜音的
         suspended: 被停權的
         title: 管理操作
+      moderation_notes: 管理記錄
       most_recent_activity: 最新活動
       most_recent_ip: 最新 IP 位域
       not_subscribed: 未訂閱
@@ -55,23 +100,90 @@ zh-HK:
         alphabetic: 按字母
         most_recent: 按時間
         title: 排序
+      outbox_url: 寄件箱(Outbox)URL
       perform_full_suspension: 實行完全暫停
       profile_url: 個人檔案 URL
+      promote: 升任
+      protocol: 協議
       public: 公共
       push_subscription_expires: PuSH 訂閱過期
+      redownload: 更新頭像
+      reset: 重設
       reset_password: 重設密碼
+      resubscribe: 重新訂閱
+      role: 身份
+      roles:
+        admin: 管理員
+        moderator: 監察员
+        staff: 管理人員
+        user: 普通用戶
       salmon_url: Salmon 反饋 URL
+      search: 搜索
+      shared_inbox_url: 公共收件箱(Shared Inbox)URL
       show:
         created_reports: 此用戶所提舉報的紀錄
         report: 舉報
         targeted_reports: 此用戶被舉報的紀錄
       silence: 靜音
       statuses: 文章
+      subscribe: 訂閱
       title: 用戶
       undo_silenced: 解除靜音
       undo_suspension: 解除停權
+      unsubscribe: 取消訂閱
       username: 用戶名稱
       web: 用戶頁面
+    action_logs:
+      actions:
+        confirm_user: "%{name} 確認了用戶 %{target} 的電郵地址"
+        create_custom_emoji: "%{name} 加入自訂表情符號 %{target}"
+        create_domain_block: "%{name} 阻隔了網域 %{target}"
+        create_email_domain_block: "%{name} 阻隔了電郵網域 %{target}"
+        demote_user: "%{name} 把用戶 %{target} 降任"
+        destroy_domain_block: "%{name} 取消了對網域 %{target} 的阻隔"
+        destroy_email_domain_block: "%{name} 取消了對電郵網域 %{target} 的阻隔"
+        destroy_status: "%{name} 刪除了 %{target} 的文章"
+        disable_2fa_user: "%{name} 停用了用戶 %{target} 的雙重認證"
+        disable_custom_emoji: "%{name} 停用了自訂表情符號 %{target}"
+        disable_user: "%{name} 把用戶 %{target} 設定為禁止登入"
+        enable_custom_emoji: "%{name} 啟用了自訂表情符號 %{target}"
+        enable_user: "%{name} 把用戶 %{target} 設定為允許登入"
+        memorialize_account: "%{name} 把 %{target} 設定為追悼帳戶"
+        promote_user: "%{name} 對用戶 %{target} 进行了升任操作"
+        reset_password_user: "%{name} 重設了用戶 %{target} 的密碼"
+        resolve_report: "%{name} 處理了 %{target} 的舉報"
+        silence_account: "%{name} 靜音了用戶 %{target}"
+        suspend_account: "%{name} 停權了用戶 %{target}"
+        unsilence_account: "%{name} 取消了用戶 %{target} 的靜音狀態"
+        unsuspend_account: "%{name} 取消了用戶 %{target} 的停權狀態"
+        update_custom_emoji: "%{name} 更新了自訂表情符號 %{target}"
+        update_status: "%{name} 刷新了 %{target} 的文章"
+      title: 營運日誌
+    custom_emojis:
+      by_domain: 網域
+      copied_msg: 成功將表情複製到本地
+      copy: 複製
+      copy_failed_msg: 無法將表情複製到本地
+      created_msg: 已新增表情符號
+      delete: 刪除
+      destroyed_msg: 已刪除表情符號
+      disable: 停用
+      disabled_msg: 已停用表情符號
+      emoji: emoji
+      enable: 啟用
+      enabled_msg: 已啟用表情符號
+      image_hint: PNG 格式,最大 50KB
+      listed: 已顯示
+      new:
+        title: 加入新的自訂表情符號
+      overwrite: 覆蓋
+      shortcode: 短代碼
+      shortcode_hint: 至少 2 個字元,只能使用字母、數字和下劃線
+      title: 自訂 emoji
+      unlisted: 已隱藏
+      update_failed_msg: 無法更新表情符號
+      updated_msg: 已更新表情符號
+      upload: 上傳新的表情符號
     domain_blocks:
       add_new: 新增
       created_msg: 正處理域名阻隔
@@ -82,12 +194,14 @@ zh-HK:
         hint: "「域名阻隔」不會隔絕該域名用戶的用戶進入本站資料庫,而是會在時候自動套用特定的審批操作。"
         severity:
           desc_html: "「<strong>自動靜音</strong>」令該域名用戶的文章,設為只對關注者顯示,沒有關注的人會看不到。 「<strong>自動刪除</strong>」會自動將該域名用戶的文章、媒體檔案、個人資料自本服務站刪除。"
+          noop: 無
           silence: 自動靜音
           suspend: 自動刪除
         title: 新增域名阻隔
       reject_media: 拒絕媒體檔案
       reject_media_hint: 刪除本地緩存的媒體檔案,再也不在未來下載這個站點的檔案。和自動刪除無關。
       severities:
+        noop: 無
         silence: 自動靜音
         suspend: 自動刪除
       severity: 阻隔分級
@@ -101,17 +215,41 @@ zh-HK:
         undo: 撤銷
       title: 域名阻隔
       undo: 撤銷
+    email_domain_blocks:
+      add_new: 加入新項目
+      created_msg: 已新增電郵網域阻隔
+      delete: 刪除
+      destroyed_msg: 已刪除電郵網域阻隔
+      domain: 網域
+      new:
+        create: 新增網域
+        title: 新增電郵網域阻隔
+      title: 電郵網域阻隔
     instances:
       account_count: 已知帳號
       domain_name: 域名
+      reset: 重設
+      search: 搜索
       title: 已知服務站
+    invites:
+      filter:
+        all: 全部
+        available: 可用
+        expired: 已失效
+        title: 篩選
+      title: 邀請用戶
     reports:
+      action_taken_by: 操作執行者
+      are_you_sure: 你確認嗎?
       comment:
         label: 詳細解釋
         none: 沒有
       delete: 刪除
       id: ID
       mark_as_resolved: 標示為「已處理」
+      nsfw:
+        'false': 取消 NSFW 標記
+        'true': 添加 NSFW 標記
       report: '舉報 #%{id}'
       report_contents: 舉報內容
       reported_account: 舉報用戶
@@ -125,23 +263,66 @@ zh-HK:
       unresolved: 未處理
       view: 檢視
     settings:
+      activity_api_enabled:
+        desc_html: 本站用戶發佈的文章,以及本站的活躍用戶和一週內新用戶數
+        title: 公開用戶活躍度的統計數據
+      bootstrap_timeline_accounts:
+        desc_html: 以半形逗號分隔多個用戶名。只能加入來自本站且未開啟保護的帳號。如果留空,則默認關注本站所有管理員。
+        title: 新用戶默認關注
       contact_information:
         email: 輸入一個公開的電郵地址
         username: 輸入用戶名稱
+      peers_api_enabled:
+        desc_html: 現時本服務站在網絡中已發現的域名
+        title: 公開已知服務站的列表
       registrations:
         closed_message:
           desc_html: 當本站暫停接受註冊時,會顯示這個訊息。<br/> 可使用 HTML
           title: 暫停註冊訊息
+        deletion:
+          desc_html: 允許所有人刪除自己的帳戶
+          title: 開放刪除帳戶的權限
+        min_invite_role:
+          disabled: 沒有人
+          title: 允許發送邀請的身份
         open:
+          desc_html: 允許所有人建立帳戶
           title: 開放註冊
+      show_staff_badge:
+        desc_html: 在個人資料頁上顯示管理人員標誌
+        title: 顯示管理人員標誌
       site_description:
         desc_html: 在首頁顯示,及在 meta 標籤使用作網站介紹。<br/> 你可以在此使用 <code>&lt;a&gt;</code> 和 <code>&lt;em&gt;</code> 等 HTML 標籤。
         title: 本站介紹
       site_description_extended:
         desc_html: 本站詳細資訊頁的內文<br/>你可以在此使用 HTML
         title: 本站詳細資訊
+      site_terms:
+        desc_html: 可以填寫自己的隱私權政策、使用條款或其他法律文本。可以使用 HTML 標籤
+        title: 自訂使用條款
       site_title: 本站名稱
+      thumbnail:
+        desc_html: 用於在 OpenGraph 和 API 中顯示預覽圖。推薦大小 1200×630px
+        title: 本站縮圖
+      timeline_preview:
+        desc_html: 在主頁顯示本站時間軸
+        title: 時間軸預覽
       title: 網站設定
+    statuses:
+      back_to_account: 返回帳戶信息頁
+      batch:
+        delete: 刪除
+        nsfw_off: 取消 NSFW 標記
+        nsfw_on: 添加 NSFW 標記
+      execute: 執行
+      failed_to_execute: 執行失敗
+      media:
+        hide: 隱藏媒體檔案
+        show: 顯示媒體檔案
+        title: 媒體檔案
+      no_media: 不含媒體檔案
+      title: 帳戶文章
+      with_media: 含有媒體檔案
     subscriptions:
       callback_url: 回傳 URL
       confirmed: 確定
@@ -150,16 +331,36 @@ zh-HK:
       title: PuSH 訂閱
       topic: 所訂閱資源
     title: 管理
+  admin_mailer:
+    new_report:
+      body: "%{reporter} 舉報了用戶 %{target}。"
+      subject: 來自 %{instance} 的用戶舉報(#%{id})
   application_mailer:
+    notification_preferences: 更改電郵首選項
+    salutation: "%{name}:"
     settings: 修改電郵設定︰%{link}
     view: 進入瀏覽︰
+    view_profile: 檢視個人資料頁
+    view_status: 檢視文章
   applications:
+    created: 已建立應用
+    destroyed: 已刪除應用
     invalid_url: 所提供的網址不正確
+    regenerate_token: 重設 token
+    token_regenerated: 已重設 token
+    warning: 警告,不要把它分享給任何人!
+    your_token: token
   auth:
+    agreement_html: 登記即表示你同意遵守<a href="%{rules_path}">本服務站的規則</a>和<a href="%{terms_path}">使用條款</a>。
+    delete_account: 刪除帳戶
+    delete_account_html: 如果你想刪除你的帳戶,請<a href="%{path}">點擊這裡繼續</a>。你需要確認你的操作。
     didnt_get_confirmation: 沒有收到確認指示電郵?
     forgot_password: 忘記了密碼?
+    invalid_reset_password_token: 密碼重置 token 無效或已過期。請重新重設密碼。
     login: 登入
     logout: 登出
+    migrate_account: 轉移到另一個帳號
+    migrate_account_html: 想要將這個帳號指向另一個帳號可<a href="%{path}">到這裡設定</a>。
     register: 登記
     resend_confirmation: 重發確認指示電郵
     reset_password: 重設密碼
@@ -168,6 +369,12 @@ zh-HK:
   authorize_follow:
     error: 對不起,尋找這個跨站用戶的過程發生錯誤
     follow: 關注
+    follow_request: 關注請求已發送给:
+    following: 成功!你正在關注:
+    post_follow:
+      close: 你也可以直接關閉這個頁面
+      return: 返回至個人資料頁
+      web: 返回本站
     title: 關注 %{acct}
   datetime:
     distance_in_words:
@@ -183,6 +390,14 @@ zh-HK:
       x_minutes: "%{count}分鐘"
       x_months: "%{count}個月"
       x_seconds: "%{count}秒"
+  deletes:
+    bad_password_msg: 想得美,黑客!密碼輸入錯誤
+    confirm_password: 輸入你現在的密碼來驗證身份
+    description_html: 繼續操作將會<strong>永久地、不可還原地</strong>刪除帳戶中的所有內容,然後凍結帳戶。你的用戶名將會被保留,以防有人冒用你的身份。
+    proceed: 刪除帳戶
+    success_msg: 你的帳戶已經成功刪除
+    warning_html: 我們只能保證本服務站上的內容將會被徹底刪除。對於已經被廣泛傳播的內容,它們在本服務站以外的某些地方可能仍然可見。此外,失去連接的服務站以及停止接收訂閱的服務站所存儲的數據亦無法刪除。
+    warning_title: 關於已傳播的內容的警告
   errors:
     '403': 你沒有觀看本頁的權限
     '404': 找不到內容
@@ -191,6 +406,10 @@ zh-HK:
       content: 無法確認登入資訊。會不會你阻擋了本站使用 Cookies 的權限?
       title: 無法確認登入資訊
     '429': 伺服器繁忙
+    '500':
+      content: 抱歉,我們的後台出錯了。
+      title: 這個頁面有問題
+    noscript_html: 使用 Mastodon 網頁版應用需要啟用 JavaScript。你也可以選擇適用於你的平台的 <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md">Mastodon 應用</a>。
   exports:
     blocks: 被你封鎖的用戶
     csv: CSV
@@ -224,14 +443,44 @@ zh-HK:
       following: 你所關注的用戶名單
       muting: 靜音名單
     upload: 上載
+  in_memoriam_html: 謹此悼念。
+  invites:
+    delete: 停用
+    expired: 已失效
+    expires_in:
+      '1800': 30 分鐘後
+      '21600': 6 小時後
+      '3600': 1 小時後
+      '43200': 12 小時後
+      '86400': 1 天後
+    expires_in_prompt: 永不過期
+    generate: 生成邀請連結
+    max_uses: "%{count} 次"
+    max_uses_prompt: 無限制
+    prompt: 生成分享連結,邀請他人在本服務站註冊
+    table:
+      expires_at: 失效時間
+      uses: 已使用次數
+    title: 邀請用戶
   landing_strip_html: "<strong>%{name}</strong> 是一個在 %{link_to_root_path} 的用戶。只要你有任何 Mastodon 服務站、或者聯盟網站的用戶,便可以跨站關注此站用戶,或者與他們互動。"
   landing_strip_signup_html: 如果你沒有這類用戶,歡迎在<a href="%{sign_up_path}">此處登記</a>。
+  lists:
+    errors:
+      limit: 你所建立的列表數量已經達到上限
   media_attachments:
     validations:
       images_and_video: 不能在已有圖片的文章上加入影片
       too_many: 不可以加入超過 4 個檔案
+  migrations:
+    acct: 新帳戶的 用戶名@域名
+    currently_redirecting: 目前你的個人資料頁顯示的新帳戶是:
+    proceed: 保存
+    updated_msg: 帳戶遷移設置更新成功!
+  moderation:
+    title: 營運
   notification_mailer:
     digest:
+      action: 查看所有通知
       body: 這是自從你在%{since}使用%{instance}以後,你錯失了的訊息︰
       mention: "%{name} 在此提及了你︰"
       new_followers_summary:
@@ -240,21 +489,29 @@ zh-HK:
       subject:
         one: "自從上次登入以來,你收到 1 則新的通知 \U0001F418"
         other: "自從上次登入以來,你收到 %{count} 則新的通知 \U0001F418"
+      title: 在你不在的這段時間……
     favourite:
       body: 你的文章是 %{name} 的最愛!
       subject: "%{name} 收藏了你的文章"
+      title: 新的收藏
     follow:
       body: "%{name} 開始關注你!"
       subject: "%{name} 現正關注你"
+      title: 新的關注者
     follow_request:
+      action: 處理關注請求
       body: "%{name} 要求關注你"
       subject: 等候關注你的用戶︰ %{name}
+      title: 新的關注請求
     mention:
+      action: 回覆
       body: "%{name} 在文章中提及你︰"
       subject: "%{name} 在文章中提及你"
+      title: 新的提及
     reblog:
       body: 你的文章得到 %{name} 的轉推
       subject: "%{name} 轉推了你的文章"
+      title: 新的轉推
   number:
     human:
       decimal_units:
@@ -270,25 +527,94 @@ zh-HK:
     next: 下一頁
     prev: 上一頁
     truncate: "……"
+  preferences:
+    languages: 語言
+    other: 其他
+    publishing: 發佈
+    web: 站内
+  push_notifications:
+    favourite:
+      title: "%{name} 收藏了你的文章"
+    follow:
+      title: "%{name} 關注了你"
+    group:
+      title: "%{count} 條新通知"
+    mention:
+      action_boost: 轉推
+      action_expand: 顯示更多
+      action_favourite: 收藏
+      title: "%{name} 提到了你"
+    reblog:
+      title: "%{name} 轉推了你的文章"
   remote_follow:
     acct: 請輸入你的︰用戶名稱@服務點域名
     missing_resource: 無法找到你用戶的轉接網址
     proceed: 下一步
     prompt: 你希望關注︰
+  sessions:
+    activity: 最近活動
+    browser: 瀏覽器
+    browsers:
+      alipay: 支付寶
+      blackberry: Blackberry
+      chrome: Chrome
+      edge: Microsoft Edge
+      firefox: Firefox
+      generic: 未知的瀏覽器
+      ie: Internet Explorer
+      micro_messenger: 微信
+      nokia: Nokia S40 Ovi 瀏覽器
+      opera: Opera
+      phantom_js: PhantomJS
+      qq: QQ瀏覽器
+      safari: Safari
+      uc_browser: UC瀏覽器
+      weibo: 新浪微博
+    current_session: 目前的 session
+    description: "%{platform} 上的 %{browser}"
+    explanation: 這些是現在正登入於你的 Mastodon 帳號的瀏覽器。
+    ip: IP 位址
+    platforms:
+      adobe_air: Adobe Air
+      android: Android
+      blackberry: Blackberry
+      chrome_os: ChromeOS
+      firefox_os: Firefox OS
+      ios: iOS
+      linux: Linux
+      mac: Mac
+      other: 未知平台
+      windows: Windows
+      windows_mobile: Windows Mobile
+      windows_phone: Windows Phone
+    revoke: 取消
+    revoke_success: Session 取消成功。
+    title: Session
   settings:
     authorized_apps: 授權應用程式
     back: 回到 Mastodon
+    delete: 刪除帳戶
+    development: 開發
     edit_profile: 修改個人資料
     export: 匯出
     followers: 授權關注
     import: 匯入
+    migrate: 帳戶遷移
+    notifications: 通知
     preferences: 偏好設定
     settings: 設定
     two_factor_authentication: 雙重認證
+    your_apps: 你的應用程式
   statuses:
     open_in_web: 開啟網頁
     over_character_limit: 超過了 %{max} 字的限制
+    pin_errors:
+      limit: 你所置頂的文章數量已經達到上限
+      ownership: 不能置頂他人的文章
+      private: 不能置頂非公開的文章
+      reblog: 不能置頂轉推
     show_more: 顯示更多
+    title: "%{name}:「%{quote}」"
     visibilities:
       private: 關注者觀看
       private_long: 只有關注你的人能看到
@@ -298,8 +624,11 @@ zh-HK:
       unlisted_long: 所有人都能看到,但不在公共時間軸(本站時間軸、跨站時間軸)顯示
   stream_entries:
     click_to_show: 點擊顯示
+    pinned: 置頂文章
     reblogged: 轉推
     sensitive_content: 敏感內容
+  terms:
+    title: "%{instance} 使用條款和隱私權政策"
   time:
     formats:
       default: "%Y年%-m月%d日 %H:%M"
@@ -308,15 +637,37 @@ zh-HK:
     description_html: 當你啟用<strong>雙重認證</strong>後,你登入時將需要使你手機、或其他種類認證器產生的代碼。
     disable: 停用
     enable: 啟用
+    enabled: 雙重認證已啟用
     enabled_success: 已成功啟用雙重認證
     generate_recovery_codes: 產生備用驗證碼
     instructions_html: "<strong>請用你手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裏的QR 圖形碼</strong>。在雙重認證啟用後,你登入時將須要使用此應用程式產生的認證碼。"
     lost_recovery_codes: 讓你可以在遺失電話時,使用備用驗證碼登入。如果你遺失了備用驗證碼,可以在這裏產生一批新的,舊有的備用驗證碼將會失效。
     manual_instructions: 如果你無法掃描 QR 圖形碼,請手動輸入這個文字密碼︰
+    recovery_codes: 備份恢復驗證碼
     recovery_codes_regenerated: 成功產生新的備用驗證碼
     recovery_instructions_html: 如果你遺失了安裝認證器的裝置(如︰你的電話),你可以使用備用驗證碼進行登入。請確保將備用驗證碼收藏穩當,(如列印出來,和你其他重要文件一起存放)
     setup: 設定
     wrong_code: 你輸入的認證碼並不正確!可能伺服器時間和你手機不一致,請檢查你手機的時鐘,或與本站管理員聯絡。
+  user_mailer:
+    welcome:
+      edit_profile_action: 設定個人資料
+      edit_profile_step: 你可以設定你的個人資料,包括上傳頭像、橫幅圖片、更改顯示名稱等等。如果你想在新的關注者關注你之前對他們進行審核,你也可以選擇為你的帳戶設為「私人」。
+      explanation: 下面是幾個小貼士,希望它們能幫到你
+      final_action: 開始發文
+      final_step: '開始發文吧!即使你現在沒有關注者,其他人仍然能在本站時間軸或者話題標籤等地方看到你的公開文章。試著用 #introductions 這個話題標籤介紹一下自己吧。'
+      full_handle: 你的完整用戶地址
+      full_handle_hint: 你需要把這個告訴你的朋友們,這樣他們就能從另一個實例向你發送信息或者關注你。
+      review_preferences_action: 更改首選項
+      review_preferences_step: 記得調整你的偏好設置,比如你想接收什麼類型的郵件,或者你想把你的文章可見範圍默認設定為什麼級別。如果你沒有暈車的話,考慮一下啟用「自動播放 GIF 動畫」這個選項吧。
+      subject: 歡迎來到 Mastodon
+      tip_bridge_html: 如果你剛從 Twitter 來到這裡,你可以在<a href="%{bridge_url}">橋樑站(bridge app)</a>上尋找你的朋友。當然,前提是他們也登錄了橋樑站!
+      tip_federated_timeline: 跨站公共時間軸可以讓你一窺更廣闊的 Mastodon 網絡。不過,由於它只顯示你的鄰居們所訂閱的內容,所以並不是全部。
+      tip_following: 默認情況下,你會自動關注你所在服務站的管理員。想結交更多有趣的人的話,記得多逛逛本站時間軸和跨站公共時間軸哦。
+      tip_local_timeline: 本站時間軸可以讓你一窺 %{instance} 上的用戶。他們就是離你最近的鄰居!
+      tip_mobile_webapp: 如果你的移動設備瀏覽器允許你將 Mastodon 添加到主屏幕,你就能夠接收推送消息。它就像本地應用一樣好使!
+      tips: 小貼士
+      title: "%{name},歡迎你的加入!"
   users:
     invalid_email: 電郵地址格式不正確
     invalid_otp_token: 雙重認證確認碼不正確
+    signed_in_as: 目前登入的帳戶:
diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb
index 67fbfe92d..6e849f379 100644
--- a/spec/lib/formatter_spec.rb
+++ b/spec/lib/formatter_spec.rb
@@ -394,6 +394,45 @@ RSpec.describe Formatter do
         end
       end
 
+      context 'with custom_emojify option' do
+        let!(:emoji) { Fabricate(:custom_emoji) }
+
+        before { account.note = text }
+        subject { Formatter.instance.simplified_format(account, custom_emojify: true) }
+
+        context 'with emoji at the start' do
+          let(:text) { ':coolcat: Beep boop' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with emoji in the middle' do
+          let(:text) { 'Beep :coolcat: boop' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with concatenated emoji' do
+          let(:text) { ':coolcat::coolcat:' }
+
+          it 'does not touch the shortcodes' do
+            is_expected.to match(/:coolcat::coolcat:/)
+          end
+        end
+
+        context 'with emoji at the end' do
+          let(:text) { 'Beep boop :coolcat:' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/boop <img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+      end
+
       include_examples 'encode and link URLs'
     end
 
@@ -404,6 +443,46 @@ RSpec.describe Formatter do
       it 'reformats' do
         is_expected.to_not include '<script>alert("Hello")</script>'
       end
+
+      context 'with custom_emojify option' do
+        let!(:emoji) { Fabricate(:custom_emoji, domain: remote_account.domain) }
+
+        before { remote_account.note = text }
+
+        subject { Formatter.instance.simplified_format(remote_account, custom_emojify: true) }
+
+        context 'with emoji at the start' do
+          let(:text) { '<p>:coolcat: Beep boop<br />' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/<p><img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with emoji in the middle' do
+          let(:text) { '<p>Beep :coolcat: boop</p>' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/Beep <img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+
+        context 'with concatenated emoji' do
+          let(:text) { '<p>:coolcat::coolcat:</p>' }
+
+          it 'does not touch the shortcodes' do
+            is_expected.to match(/<p>:coolcat::coolcat:<\/p>/)
+          end
+        end
+
+        context 'with emoji at the end' do
+          let(:text) { '<p>Beep boop<br />:coolcat:</p>' }
+
+          it 'converts shortcode to image tag' do
+            is_expected.to match(/<br><img draggable="false" class="emojione" alt=":coolcat:"/)
+          end
+        end
+      end
     end
   end